// Loads data/bookings.json (produced by scripts/fetch-bookings.mjs).
// One shared promise so all consumers get the same fetch.

// Prefer the live /api/bookings endpoint (always fresh, auth-gated). If it
// fails (404 because we're on the old python http.server, 401 not signed in),
// fall back to the static data/bookings.json snapshot.
let _liveDataPromise = null;
const _liveDataSubscribers = new Set();
function loadLiveData() {
  if (!_liveDataPromise) {
    const f = window.authFetch || fetch;
    _liveDataPromise = f("/api/bookings", { cache: "no-store" })
      .then(r => r.ok ? r.json() : Promise.reject(new Error("api/bookings HTTP " + r.status)))
      .catch(() => fetch("data/bookings.json", { cache: "no-store" })
        .then(r => r.ok ? r.json() : Promise.reject(new Error("snapshot HTTP " + r.status))))
      .catch(err => { console.warn("[live-data] failed:", err); return null; });
  }
  return _liveDataPromise;
}

function useLiveData() {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    loadLiveData().then(d => { if (alive) setData(d); });
    const onRefresh = (d) => { if (alive) setData(d); };
    _liveDataSubscribers.add(onRefresh);
    return () => { alive = false; _liveDataSubscribers.delete(onRefresh); };
  }, []);
  return data;
}

// Re-fetches bookings live from the API (which always aggregates fresh from Monday)
// and pushes the new payload to every component using useLiveData.
async function refreshBookings() {
  _liveDataPromise = null;
  const fresh = await loadLiveData();
  _liveDataSubscribers.forEach(fn => { try { fn(fresh); } catch {} });
  return fresh;
}
window.refreshBookings = refreshBookings;

// Format helpers shared by KPI and Finance components.
function fmtMoney(n) {
  if (n == null) return "—";
  const abs = Math.abs(n);
  if (abs >= 1_000_000) return (n < 0 ? "-" : "") + "₪" + (abs / 1_000_000).toFixed(2) + "M";
  if (abs >= 1_000)     return (n < 0 ? "-" : "") + "₪" + Math.round(abs / 1_000) + "K";
  return (n < 0 ? "-" : "") + "₪" + Math.round(abs);
}
function fmtSigned(n) {
  if (n == null) return "—";
  return (n >= 0 ? "+" : "") + fmtMoney(n);
}

window.useLiveData = useLiveData;
window.fmtMoney = fmtMoney;
window.fmtSigned = fmtSigned;

// Resolve a company's bookings from the snapshot's byClient map.
// Strategy:
//   1. Exact (case-insensitive) match — preferred
//   2. Soft-collapsed match — strip ALL non-alphanumeric so "Bright-Data" === "BrightData"
//   3. Domain-stripped + space-tolerant match
//   4. First-word match — "Eleos" matches "Eleos Health" and "Eleos Health - Events"
//   When multiple keys match (e.g. "eleos health" + "Eleos Health - Events"), the
//   numbers are SUMMED. Returns null only if nothing matches.
function findClientBookings(name, liveData) {
  if (!liveData || !liveData.byClient || !name) return null;
  const keys = Object.keys(liveData.byClient);
  // Hard-normalize: lowercase, drop non-alphanumeric → catches hyphen/space/dot variants.
  // E.g. "Bright-Data" → "brightdata", "monday.com" → "mondaycom" (but we strip TLDs first below).
  const stripTld = s => (s || "").toLowerCase().replace(/\.(com|io|co|ai|net|org|health)$/i, "");
  const hard  = s => stripTld(s).replace(/[^a-z0-9]/gi, "");
  const soft  = s => stripTld(s).replace(/[\s\-_]+/g, " ").trim();
  const firstWord = s => soft(s).split(" ")[0];
  const targetHard  = hard(name);
  const targetSoft  = soft(name);
  const targetFirst = firstWord(name);

  const matches = keys.filter(k => {
    if (k === name) return true;
    if (k.toLowerCase() === name.toLowerCase()) return true;
    if (hard(k) === targetHard && targetHard.length >= 3) return true;
    if (soft(k) === targetSoft) return true;
    if (firstWord(k) === targetFirst && targetFirst.length >= 3) return true;
    return false;
  });

  if (matches.length === 0) return null;
  if (matches.length === 1) return liveData.byClient[matches[0]];

  // Aggregate when multiple keys belong to the same logical company.
  const agg = {
    name, _matchedKeys: matches,
    booked: 0, paid: 0,
    awaitingInvoice: 0, awaitingPayment: 0, late: 0,
    byOp: { MD: 0, Events: 0, WIX: 0, MedOps: 0 },
  };
  for (const k of matches) {
    const c = liveData.byClient[k];
    agg.booked          += c.booked          || 0;
    agg.paid            += c.paid            || 0;
    agg.awaitingInvoice += c.awaitingInvoice || 0;
    agg.awaitingPayment += c.awaitingPayment || 0;
    agg.late            += c.late            || 0;
    if (c.byOp) for (const op of Object.keys(agg.byOp)) agg.byOp[op] += (c.byOp[op] || 0);
  }
  return agg;
}
window.findClientBookings = findClientBookings;

// ============================================================
// Vendors / Doctors snapshot (data/vendors.json)
// ============================================================
let _vendorsPromise = null;
function loadVendors() {
  if (!_vendorsPromise) {
    _vendorsPromise = fetch("data/vendors.json", { cache: "no-store" })
      .then(r => r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status)))
      .catch(err => { console.warn("[vendors] failed:", err); return null; });
  }
  return _vendorsPromise;
}
function useVendors() {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    loadVendors().then(d => { if (alive) setData(d); });
    return () => { alive = false; };
  }, []);
  return data;
}
window.useVendors = useVendors;

// ============================================================
// Roadmap (Operations Roadmap board, two-way live)
// ============================================================
// API is now same-origin (Vercel serverless functions in /api/*).
// For local dev with `vercel dev` it's also same-origin.
const API_BASE = "";

// 90-day window with today sitting ~10 days in. Origin = midnight, today - 10d.
function roadmapOrigin() {
  const t = new Date();
  t.setHours(0, 0, 0, 0);
  t.setDate(t.getDate() - 10);
  return t;
}
function isoOf(d) { return d.toISOString().slice(0, 10); }
function daysBetween(a, b) { return Math.round((b - a) / 86400000); }

function offsetFromIso(iso, origin) {
  if (!iso) return null;
  const [y,m,d] = iso.split("-").map(Number);
  const dt = new Date(y, m-1, d);
  return daysBetween(origin, dt);
}
function isoFromOffset(offset, origin) {
  const dt = new Date(origin);
  dt.setDate(dt.getDate() + offset);
  return isoOf(dt);
}

// Translate the API's lane shape (ISO start/end) to the Gantt's day-offset shape.
function denormaliseLanes(apiLanes, origin) {
  return apiLanes.map(lane => ({
    ...lane,
    bars: lane.bars.map(b => {
      const startOffset = b.start ? offsetFromIso(b.start, origin) : null;
      const endOffset   = b.end   ? offsetFromIso(b.end,   origin) : null;
      // Items without dates: ghost bar at today, length 1.
      const start = startOffset == null ? 10 : startOffset;
      const len = (endOffset != null && startOffset != null)
        ? Math.max(1, endOffset - startOffset + 1)
        : 1;
      return {
        ...b,
        start,
        len,
        // Keep the original ISOs so save can preserve the un-edited side.
        _startIso: b.start,
        _endIso:   b.end,
        _ghost: !b.start,
      };
    }),
  }));
}

function useRoadmap({ pollMs = 30000 } = {}) {
  const [data, setData] = React.useState(null);
  const [version, bump] = React.useReducer(x => x + 1, 0);
  const origin = React.useMemo(() => roadmapOrigin(), []);

  React.useEffect(() => {
    let alive = true;
    const f = window.authFetch || fetch;
    const load = () => f(API_BASE + "/api/roadmap", { cache: "no-store" })
      .then(r => r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status)))
      .then(j => { if (alive) setData({ ...j, lanes: denormaliseLanes(j.lanes, origin) }); })
      .catch(err => console.warn("[roadmap] fetch failed:", err));
    load();
    const id = setInterval(load, pollMs);
    const onFocus = () => load();
    window.addEventListener("focus", onFocus);
    return () => { alive = false; clearInterval(id); window.removeEventListener("focus", onFocus); };
  }, [origin, version]);

  const refresh = React.useCallback(() => bump(), []);
  return { data, origin, refresh };
}

const roadmapApi = {
  async update(itemId, patch) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/roadmap?id=${encodeURIComponent(itemId)}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(patch),
    });
    if (!r.ok) throw new Error("update failed: " + r.status);
    return r.json();
  },
  async create(payload) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/roadmap`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    if (!r.ok) throw new Error("create failed: " + r.status);
    return r.json();
  },
  async remove(itemId) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/roadmap?id=${encodeURIComponent(itemId)}`, { method: "DELETE" });
    if (!r.ok) throw new Error("delete failed: " + r.status);
    return r.json();
  },
};

window.useRoadmap = useRoadmap;
window.roadmapApi = roadmapApi;
window.roadmapHelpers = { isoFromOffset, offsetFromIso };

// ============================================================
// Tasks (= Monday subitems of an Operations Roadmap event)
// ============================================================
const tasksApi = {
  async list(parentId) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/tasks?parent=${encodeURIComponent(parentId)}`, { cache: "no-store" });
    if (!r.ok) throw new Error("tasks list failed: " + r.status);
    return r.json(); // { tasks: [...], users: [...] }
  },
  async create(payload) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/tasks`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    if (!r.ok) throw new Error("task create failed: " + r.status);
    return r.json();
  },
  async update(taskId, patch) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/tasks?id=${encodeURIComponent(taskId)}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(patch),
    });
    if (!r.ok) throw new Error("task update failed: " + r.status);
    return r.json();
  },
  async remove(taskId) {
    const f = window.authFetch || fetch;
    const r = await f(`${API_BASE}/api/tasks?id=${encodeURIComponent(taskId)}`, { method: "DELETE" });
    if (!r.ok) throw new Error("task delete failed: " + r.status);
    return r.json();
  },
};
window.tasksApi = tasksApi;
