// api-client.jsx — шар даних: під'єднання прототипу до реального бекенду (FastAPI).
//
// LIVE-режим: бере дані з /api/* (той самий origin), з JWT-авторизацією і ролями.
// DEMO-режим: якщо API недосяжний (пісочниця / file:// / немає бекенду) — тихо
//   відкочується на вбудовані seed-дані (window.DATA.* з data.jsx), щоб прототип
//   лишався придатним для ітерацій дизайну. Перемикання — автоматичне.
//
// Контракт: відповіді API мають ІДЕНТИЧНУ форму до масивів у data.jsx
// (camelCase-поля, рядкові slug-id, гроші числом, дати "YYYY-MM-DD"),
// тому рендер-код не змінюється — змінюється лише джерело даних.

(function () {
  const LS_TOKEN = "erp_token";
  const LS_BASE  = "erp_api_base"; // override для локалки, напр. http://localhost:8000/api

  function resolveBase() {
    try { const ov = localStorage.getItem(LS_BASE); if (ov) return ov.replace(/\/+$/, ""); } catch (e) {}
    if (location.protocol === "file:") return null;
    const o = location.origin;
    if (!o || o === "null") return null;
    return o + "/api";
  }

  const API = {
    base: resolveBase(),
    mode: "demo",      // 'live' | 'demo'
    user: null,
    token: null,
    needAuth: false,
  };
  let _resolveReady;
  API.ready = new Promise(r => { _resolveReady = r; });
  try { API.token = localStorage.getItem(LS_TOKEN) || null; } catch (e) {}

  API.setToken = (t) => {
    API.token = t || null;
    try { t ? localStorage.setItem(LS_TOKEN, t) : localStorage.removeItem(LS_TOKEN); } catch (e) {}
  };

  function ApiError(message, status, data) { this.message = message; this.status = status; this.data = data; this.isApiError = true; }
  ApiError.prototype = Object.create(Error.prototype);
  API.ApiError = ApiError;

  // --- низькорівневий fetch із Bearer і обробкою 401/403 ---
  async function apiFetch(path, opts) {
    opts = opts || {};
    if (!API.base) throw new ApiError("no-api-base", 0);
    const headers = Object.assign({}, opts.headers || {});
    const isForm = opts.body instanceof FormData;
    if (opts.body && !isForm) headers["Content-Type"] = "application/json";
    if (API.token) headers["Authorization"] = "Bearer " + API.token;
    let res;
    try {
      res = await fetch(API.base + path, Object.assign({}, opts, { headers }));
    } catch (e) {
      throw new ApiError("network", 0, e);
    }
    if (res.status === 401) {
      API.setToken(null); API.user = null;
      window.dispatchEvent(new Event("erp-unauth"));
      throw new ApiError("unauthorized", 401);
    }
    if (res.status === 403) throw new ApiError("forbidden", 403);
    if (res.status === 204) return null;
    const ct = res.headers.get("content-type") || "";
    if (!res.ok) {
      let data = null; try { data = ct.includes("json") ? await res.json() : await res.text(); } catch (e) {}
      throw new ApiError((data && data.detail) || ("http-" + res.status), res.status, data);
    }
    if (!ct.includes("application/json")) return res.blob();
    return res.json();
  }

  API.get   = (p)       => apiFetch(p);
  API.post  = (p, body) => apiFetch(p, { method: "POST", body: body instanceof FormData ? body : JSON.stringify(body || {}) });
  API.patch = (p, body) => apiFetch(p, { method: "PATCH", body: JSON.stringify(body || {}) });
  API.del   = (p)       => apiFetch(p, { method: "DELETE" });

  // повертає items[] з {items,total,limit,offset} або сирий масив
  API.list = async (resource, params) => {
    const qs = params ? "?" + new URLSearchParams(params).toString() : "";
    const r = await API.get("/" + resource + qs);
    if (r && Array.isArray(r.items)) return r.items;
    return Array.isArray(r) ? r : [];
  };

  // --- авторизація ---
  API.login = async (email, password) => {
    const r = await API.post("/auth/login", { email, password });
    API.setToken(r.token);
    API.user = r.user;
    return r.user;
  };
  API.fetchMe = async () => { const u = await API.get("/auth/me"); API.user = u; return u; };
  API.logout = () => { API.setToken(null); API.user = null; try { location.reload(); } catch (e) {} };

  // Первинне встановлення пароля при примусовій зміні (перший вхід) — лише новий пароль.
  API.setPassword = async (newPassword) => {
    const r = await API.post("/auth/set-initial-password", { newPassword });
    if (API.user) API.user.mustChangePassword = false;
    return r;
  };
  // Самозміна пароля (поточний → новий). Знімає прапорець mustChangePassword.
  API.changePassword = async (currentPassword, newPassword) => {
    const r = await API.post("/auth/change-password", { currentPassword, newPassword });
    if (API.user) API.user.mustChangePassword = false;
    return r;
  };

  // --- завантаження файлів (скани, акти) ---
  API.uploadFile = (file, meta) => {
    const fd = new FormData();
    fd.append("file", file);
    if (meta) Object.keys(meta).forEach(k => meta[k] != null && fd.append(k, meta[k]));
    return API.post("/files", fd);
  };

  // ============ BOOTSTRAP ============
  // resource → ключ у window.DATA (slug-id зберігаються як є)
  const DATA_COLLECTIONS = {
    objects: "OBJECTS", tasks: "TASKS", "task-templates": "TASK_TEMPLATES",
    employees: "TEAM", vacations: "VACATIONS", documents: "DOCUMENTS",
    trainings: "TRAININGS", "active-onboardings": "ACTIVE_ONBOARDINGS",
    "one-on-ones": "ONE_ON_ONES",
    assets: "ASSETS", "inventory-sessions": "INVENTORY_SESSIONS",
    clients: "CLIENTS", contracts: "CONTRACTS", invoices: "INVOICES",
    suppliers: "SUPPLIERS", "sub-payments": "SUB_PAYMENTS", leads: "LEADS",
    expenses: "EXPENSES", "cash-accounts": "CASH_ACCOUNTS", "cash-ops": "CASH_OPS",
    "advance-reports": "ADVANCE_REPORTS",
    // Проєктна діяльність ГО — у LIVE тягнемо з бекенду (порожньо, поки не введуть реальні)
    projects: "PROJECTS", donors: "DONORS", "ngo-partners": "NGO_PARTNERS",
  };

  function done(mode) { API.mode = mode; _resolveReady(mode); return mode; }

  function goDemo(reason) {
    if (reason) console.warn("[ERP] DEMO-режим:", reason);
    if (!API.user) API.user = {
      id: "ok", name: "Олена Кравченко", email: "o.kravchenko@ukrbudproiekt.ua",
      role: "director", canSeeSalary: true, canSeeCash: true,
    };
    API.needAuth = false;
    return done("demo");
  }

  // Публічний health-чек: чи бекенд узагалі є. /api/meta вимагає авторизації,
  // тому для детекту LIVE використовуємо саме /api/health (без токена).
  async function probeBackend() {
    try {
      const res = await fetch(API.base + "/health");
      return { present: res.ok };
    } catch (e) {
      return { present: false };
    }
  }

  API.bootstrap = async function () {
    if (!API.base) return goDemo("немає origin (пісочниця/file://)");
    const probe = await probeBackend();
    if (!probe.present) return goDemo("бекенд недосяжний на " + API.base + " (/health)");

    // Бекенд є → LIVE. Порядок: health → /auth/me (або вхід) → /meta + колекції
    API.mode = "live";
    if (API.token) {
      try { await API.fetchMe(); } catch (e) { if (e.status === 401) API.setToken(null); }
    }
    if (!API.token || !API.user) { API.needAuth = true; return done("live"); }

    try {
      await API.loadData(null);
      API.needAuth = false;
      return done("live");
    } catch (e) {
      return goDemo("не вдалося завантажити дані: " + (e.message || e));
    }
  };

  // Викликається після успішного логіну
  API.afterLogin = async function () {
    await API.loadData(null);
    API.needAuth = false;
    API.mode = "live";
  };

  // Тягне мету + усі колекції фази, кладе у window.DATA.*, перецілює геттери і обчислення
  API.loadData = async function (metaPreloaded) {
    window.DATA = window.DATA || {};
    const meta = metaPreloaded || await API.get("/meta");
    if (meta) applyMeta(meta);

    // довідники категорій (top-level, не під DATA)
    const [expCats, assetCats] = await Promise.all([
      API.list("expense-categories").catch(() => null),
      API.list("asset-categories").catch(() => null),
    ]);
    if (expCats)   window.EXPENSE_CATEGORIES = expCats;
    if (assetCats) window.ASSET_CATEGORIES = assetCats;

    // основні колекції — паралельно, нестачу ігноруємо (можуть бути приховані за роллю)
    const entries = Object.entries(DATA_COLLECTIONS);
    const results = await Promise.allSettled(entries.map(([res]) => API.list(res)));
    results.forEach((r, i) => { if (r.status === "fulfilled") window.DATA[entries[i][1]] = r.value; });

    // обчислювані набори — кеш для синхронного рендеру (мають форму прототипу)
    if (API.user && API.user.canSeeSalary) {
      const period = meta && meta.payrollPeriod ? meta.payrollPeriod : null;
      try {
        const reg = await API.list("payroll" + (period ? "?period=" + period : ""));
        window.__payrollCache = indexBy(reg, r => (r.person && r.person.id) || r.employeeId || r.id);
      } catch (e) {}
      try {
        const yr = (meta && meta.sysDate ? meta.sysDate.slice(0, 4) : "2026");
        window.__plCache = { [yr]: await API.get("/reports/pl?year=" + yr) };
      } catch (e) {}
    }

    rebindGettersAndCompute();
  };

  function indexBy(arr, keyFn) { const m = {}; (arr || []).forEach(x => { const k = keyFn(x); if (k != null) m[k] = x; }); return m; }

  // Перенести значення /meta у глобали, які очікує UI (тільки наявні ключі)
  function applyMeta(meta) {
    if (meta.sysDate) {
      window.SYS_DATE = meta.sysDate;
      if (window.TODAY) window.TODAY = Object.assign({}, window.TODAY, { day: String(parseInt(meta.sysDate.slice(8, 10), 10)) });
    }
    if (meta.currentWeek)        window.CURRENT_WEEK = meta.currentWeek;
    if (meta.company)            window.COMPANY = meta.company;
    if (meta.fxRates)            window.FX_RATES = meta.fxRates;
    if (meta.taxRates)           window.TAX_RATES = meta.taxRates;
    if (meta.overheadRates)      window.OVERHEAD_RATES = meta.overheadRates;
    if (meta.permissionsCatalog) window.PERMISSIONS_CATALOG = meta.permissionsCatalog;
    const L = meta.labels || {};
    if (L.employment)     window.EMPLOYMENT_LABELS    = L.employment;
    if (L.salaryModel)    window.SALARY_MODEL_LABELS  = L.salaryModel;
    if (L.assetStatus)    window.ASSET_STATUS         = L.assetStatus;
    if (L.assetLocations) window.ASSET_LOCATIONS      = L.assetLocations;
    if (L.reconcileResult)window.RECONCILE_RESULT     = L.reconcileResult;
    if (L.contractStatus) window.CONTRACT_STATUS      = L.contractStatus;
  }

  // Геттери в data.jsx/warehouse-data.jsx замкнені на локальні seed-масиви —
  // після підміни window.DATA.* їх треба перецілити на свіжі дані.
  // Обчислення — перевага серверним полям (asset.depreciation, session.stats, кеш ЗП/P&L).
  function rebindGettersAndCompute() {
    window.getTeam    = (id) => (window.DATA.TEAM || []).find(x => x.id === id);
    window.getObject  = (id) => (window.DATA.OBJECTS || []).find(x => x.id === id);
    window.getClient  = (id) => (window.DATA.CLIENTS || []).find(x => x.id === id);
    window.getContract= (id) => (window.DATA.CONTRACTS || []).find(x => x.id === id);
    window.getAsset   = (id) => (window.DATA.ASSETS || []).find(x => x.id === id);
    window.getInventorySession = (id) => (window.DATA.INVENTORY_SESSIONS || []).find(x => x.id === id);
    window.getCategory = (id) => (window.EXPENSE_CATEGORIES || []).find(c => c.id === id);
    window.getAssetCategory = (id) => (window.ASSET_CATEGORIES || []).find(c => c.id === id) || (window.ASSET_CATEGORIES || [])[0];

    // амортизація: сервер кладе у asset.depreciation
    const _dep = window.computeDepreciation;
    window.computeDepreciation = (a) => (a && a.depreciation) ? a.depreciation : (_dep ? _dep(a) : null);

    // зведення інвентаризації: сервер кладе у session.stats
    const _stats = window.inventoryStats;
    window.inventoryStats = (s) => (s && s.stats) ? s.stats : (_stats ? _stats(s) : null);

    // ЗП: перевага серверному кешу /payroll
    const _pay = window.computePayroll;
    window.computePayroll = (id, h, p) => {
      if (window.__payrollCache && window.__payrollCache[id]) return window.__payrollCache[id];
      return _pay ? _pay(id, h, p) : null;
    };

    // P&L: перевага серверному /reports/pl
    const _pl = window.computeAnnualPL;
    window.computeAnnualPL = (year) => {
      const y = year || "2026";
      if (window.__plCache && window.__plCache[y]) return window.__plCache[y];
      return _pl ? _pl(y) : null;
    };

    // готівковий контур: видимість за роллю з сервера
    window.canSeeCash = (role) => API.user ? !!API.user.canSeeCash : (role === "director" || role === "accountant");

    // наступний інв.№ для прев'ю (реальний номер генерує сервер при POST)
    window.nextInvNo = (catId) => {
      const cat = window.getAssetCategory(catId);
      const nums = (window.DATA.ASSETS || []).filter(a => a.cat === catId)
        .map(a => parseInt(String(a.invNo || "").split("-").pop(), 10)).filter(n => !isNaN(n));
      const next = (nums.length ? Math.max.apply(null, nums) : 0) + 1;
      return "УБП-" + (cat ? cat.prefix : "??") + "-" + String(next).padStart(4, "0");
    };
  }

  // Зручний хелпер для майбутніх мутацій: зберегти й оновити локальний кеш
  API.save = async (resource, id, patch) => {
    return id ? API.patch("/" + resource + "/" + id, patch) : API.post("/" + resource, patch);
  };

  window.API = API;
})();
