// WorkingCapitalPage (Platform rebuild) — window.WorkingCapitalPage
// Cash conversion & collections: DSO/DPO/DIO/CCC, AR & AP aging, collections
// priority. Live AR/AP from ar_invoices / ap_bills subledgers; DSO/DPO from LTM
// revenue & COGS (plHistory). 12-month CCC trend reads working_capital_snapshots
// when present — otherwise an honest "awaiting snapshots" note.

(function () {
  const h = React.createElement;
  const { useState, useEffect, useMemo } = React;
  const BUCKET_COLORS = ["#18a867", "#b8921e", "#d97706", "#d94f47"];
  const BUCKET_LABELS = ["0-30", "31-60", "61-90", "90+"];

  // Age open items into [0-30, 31-60, 61-90, 90+] by days past due.
  function ageRows(rows, asOf, dateKey, balKey, nameKey) {
    const buckets = [0, 0, 0, 0]; const byName = {};
    for (const it of rows || []) {
      const bal = Number(it[balKey] || 0); if (!(bal > 0)) continue;
      const due = it[dateKey] ? new Date(it[dateKey]) : null;
      const days = due ? Math.floor((asOf - due) / 86400000) : 0;
      const bi = days <= 30 ? 0 : days <= 60 ? 1 : days <= 90 ? 2 : 3;
      buckets[bi] += bal;
      const nm = it[nameKey] || "—";
      const e = byName[nm] || (byName[nm] = { name: nm, total: 0, b: [0, 0, 0, 0], oldest: -9999 });
      e.total += bal; e.b[bi] += bal; if (days > e.oldest) e.oldest = days;
    }
    const total = buckets.reduce((a, b) => a + b, 0);
    return { buckets, total, byName: Object.values(byName).sort((a, b) => b.total - a.total) };
  }

  function AgingBar(props) {
    const { buckets, total } = props;
    if (!total) return h("div", { style: { fontSize: 12, color: "#94a3b8", padding: 12 } }, "No open balance to age.");
    const F = window.PerduraFormat;
    return h("div", null,
      h("div", { style: { display: "flex", height: 22, borderRadius: 6, overflow: "hidden", border: "1px solid rgba(13,32,64,.08)" } },
        buckets.map((v, i) => v > 0 ? h("div", { key: i, title: BUCKET_LABELS[i] + ": " + (F ? F.money(v) : v), style: { width: (v / total * 100) + "%", background: BUCKET_COLORS[i], transition: "width .5s ease" } }) : null)),
      h("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 10 } },
        buckets.map((v, i) => h("div", { key: i, style: { fontSize: 11 } },
          h("div", { style: { display: "flex", alignItems: "center", gap: 5, color: "#475569", fontWeight: 600 } },
            h("span", { style: { width: 9, height: 9, borderRadius: 2, background: BUCKET_COLORS[i] } }), BUCKET_LABELS[i]),
          h("div", { style: { fontWeight: 800, fontFamily: "'JetBrains Mono',monospace", color: "#0d2040", marginTop: 2 } }, F ? F.money(v, { compact: true }) : v),
          h("div", { style: { color: "#94a3b8", fontSize: 10 } }, (v / total * 100).toFixed(0) + "% of total")))));
  }

  // Decompose the latest month's WC move into AR / AP / inventory / other drivers
  // with plain-English explanations from the DSO/DPO deltas.
  function buildMovementExplanation(snapshots) {
    if (!snapshots || snapshots.length < 2) return null;
    const cur = snapshots[snapshots.length - 1];
    const prev = snapshots[snapshots.length - 2];
    const n = (v) => Number(v) || 0;
    const arChange = n(cur.ar_total) - n(prev.ar_total);
    const apChange = n(cur.ap_total) - n(prev.ap_total);
    const invChange = n(cur.inventory_total) - n(prev.inventory_total);
    // WC per snapshot: stored working_capital → current_assets−current_liabilities
    // (BS fallback / raw_data) → AR + inventory − AP, so it always resolves.
    const wcVal = (s) => {
      if (s.working_capital != null) return n(s.working_capital);
      const ca = s.current_assets != null ? n(s.current_assets) : (s.raw_data && s.raw_data.current_assets != null ? n(s.raw_data.current_assets) : null);
      const cl = s.current_liabilities != null ? n(s.current_liabilities) : (s.raw_data && s.raw_data.current_liabilities != null ? n(s.raw_data.current_liabilities) : null);
      if (ca != null && cl != null) return ca - cl;
      return n(s.ar_total) + n(s.inventory_total) - n(s.ap_total);
    };
    const wcCur = wcVal(cur);
    const wcPrev = wcVal(prev);
    const wcChange = wcCur - wcPrev;
    // WC impact: AR up = WC up, AP up = WC down, inventory up = WC up.
    const arImpact = arChange;
    const apImpact = -apChange;
    const invImpact = invChange;
    const otherImpact = wcChange - arImpact - apImpact - invImpact;

    const dsoCur = cur.dso_days, dsoPrev = prev.dso_days;
    const haveDso = dsoCur != null && dsoPrev != null;
    const arExplain = arChange > 0
      ? (haveDso && dsoCur > dsoPrev ? "Collections slower — DSO extended " + Math.round(dsoCur - dsoPrev) + " days" : "Receivables grew this period")
      : (haveDso && dsoCur < dsoPrev ? "Collections improved — DSO shortened " + Math.round(dsoPrev - dsoCur) + " days" : "Receivables declined");
    const dpoCur = cur.dpo_days, dpoPrev = prev.dpo_days;
    const haveDpo = dpoCur != null && dpoPrev != null;
    const apExplain = apChange > 0
      ? (haveDpo && dpoCur > dpoPrev ? "Extending vendor terms — DPO improved " + Math.round(dpoCur - dpoPrev) + " days" : "More purchases this period")
      : (haveDpo && dpoCur < dpoPrev ? "Paying vendors faster — DPO shortened" : "Fewer purchases / early payments");
    const invExplain = invChange > 0 ? "Inventory building up — check turn rate" : "Inventory turning faster — positive cash signal";

    return { cur, prev, wcCur, wcPrev, arChange, apChange, invChange, otherImpact, wcChange,
      arImpact, apImpact, invImpact, arExplain, apExplain, invExplain };
  }

  function Page(props) {
    const K = window.PerduraPageKit;
    if (!K) return h("div", { className: "pc-page" }, "Loading…");
    const { data, scopedCompanyId } = props;
    const M = (v) => K.moneyStr(v, { compact: true });
    const plH = (data && (data.plHistory || data.pl)) || { labels: [], revenue: [], cogs: [] };
    const anchor = K.anchorFromPlH(plH);
    const ps = K.usePeriodState("working_capital", "ltm");
    const range = K.resolvePeriod(ps.mode, anchor, ps.custom);
    const span = K.plIdxRange(plH, range);

    const [arRows, setArRows] = useState(null);
    const [apRows, setApRows] = useState(null);
    const [snaps, setSnaps] = useState(null);
    const [wcInlinePeriod, setWcInlinePeriod] = useState("12M"); // 1M|3M|6M|YTD|12M|13M

    useEffect(() => {
      let cancelled = false;
      const db = window.supabaseClient;
      if (!db || !scopedCompanyId) { setArRows([]); setApRows([]); setSnaps([]); return; }
      setArRows(null); setApRows(null);
      db.from("ar_invoices").select("due_date,customer_name,balance").eq("company_id", scopedCompanyId).gt("balance", 0).limit(5000)
        .then(({ data: d }) => { if (!cancelled) setArRows(d || []); }, () => { if (!cancelled) setArRows([]); });
      db.from("ap_bills").select("due_date,vendor_name,open_balance").eq("company_id", scopedCompanyId).gt("open_balance", 0).limit(5000)
        .then(({ data: d }) => { if (!cancelled) setApRows(d || []); }, () => { if (!cancelled) setApRows([]); });
      db.from("working_capital_snapshots").select("period_end,dso_days,dpo_days,dio_days,cash_conversion_cycle,working_capital,current_ratio,quick_ratio,ar_total,ap_total,inventory_total,raw_data").eq("company_id", scopedCompanyId).order("period_end", { ascending: true }).limit(36)
        .then(({ data: d }) => { if (!cancelled) setSnaps(d || []); }, () => { if (!cancelled) setSnaps([]); });
      return () => { cancelled = true; };
    }, [scopedCompanyId]);

    // ── FIX 1: derive monthly WC history from the GL (data.txns) ──────────────
    // working_capital_snapshots has no writer, so build the period-end series
    // here: cumulative current-asset / current-liability balances as of each
    // month-end, using the SAME taxonomy the Balance Sheet page uses
    // (window.PerduraTaxonomy.sectionForCategory) — never hardcoded buckets.
    // Produces the exact row shape the renderers below already consume.
    const txnMonthly = useMemo(() => {
      const txns = (data && data.txns) || [];
      const tax = (typeof window !== "undefined") ? window.PerduraTaxonomy : null;
      if (!txns.length || !tax || !tax.sectionForCategory) return [];
      const profile = (data && data.companyProfile) || props.companyProfile;
      const MAB = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

      // Monthly revenue / COGS keyed by yyyymm, for trailing DSO / DPO / DIO.
      const revYm = {}, cogYm = {};
      { const L = plH.labels || [], Y = plH.years || [], R = plH.revenue || [], C = plH.cogs || [];
        for (let i = 0; i < L.length; i++) { const mi = MAB.indexOf(String(L[i] || "").slice(0, 3)); if (mi >= 0 && Y[i]) { const k = Y[i] * 100 + (mi + 1); revYm[k] = Number(R[i]) || 0; cogYm[k] = Number(C[i]) || 0; } } }

      // Per-month deltas for current-asset / current-liability accounts. AR / AP
      // / inventory detection mirrors buildBalanceSheetTotals in data-live.js.
      const monthly = {};
      txns.forEach((tx) => {
        if (!tx.posted_date) return;
        const cat = tx.canonical_category; if (!cat) return;
        const sec = tax.sectionForCategory(cat, profile); if (!sec) return;
        const d = new Date(tx.posted_date); const ym = d.getFullYear() * 100 + (d.getMonth() + 1);
        const amt = parseFloat(tx.amount) || 0;
        const m = monthly[ym] || (monthly[ym] = { ca: 0, cl: 0, ar: 0, ap: 0, inv: 0 });
        if (sec === "current_assets") {
          m.ca += amt;
          if (cat === "Accounts Receivable") m.ar += amt;
          else if (/invent/i.test(cat)) m.inv += amt;
        } else if (sec === "current_liab") {
          m.cl += amt;
          if (cat === "Accounts Payable" || cat === "Credit Cards") m.ap += amt;
        }
      });

      const yms = Object.keys(monthly).map(Number).sort((a, b) => a - b);
      if (!yms.length) return [];
      const A = Math.abs;
      // Walk a continuous month range so balances carry forward across any month
      // with no BS activity (cumulative point-in-time, no gaps in the columns).
      let ca = 0, cl = 0, ar = 0, ap = 0, inv = 0;
      const rows = [];
      let y = Math.floor(yms[0] / 100), mo = yms[0] % 100;
      const eY = Math.floor(yms[yms.length - 1] / 100), eM = yms[yms.length - 1] % 100;
      while (y < eY || (y === eY && mo <= eM)) {
        const m = monthly[y * 100 + mo];
        if (m) { ca += m.ca; cl += m.cl; ar += m.ar; ap += m.ap; inv += m.inv; }
        const CA = A(ca), CL = A(cl), AR = A(ar) || null, AP = A(ap) || null, INV = A(inv) || null;
        // Trailing-3-month annualised DSO / DPO / DIO.
        let r3 = 0, c3 = 0;
        for (let b = 0; b < 3; b++) { const mm = mo - b; const k = (mm > 0) ? (y * 100 + mm) : ((y - 1) * 100 + (mm + 12)); r3 += revYm[k] || 0; c3 += cogYm[k] || 0; }
        rows.push({
          period_end: y + "-" + String(mo).padStart(2, "0") + "-15",
          current_assets: CA, current_liabilities: CL, working_capital: CA - CL,
          ar_total: AR, ap_total: AP, inventory_total: INV,
          dso_days: (r3 > 0 && AR != null) ? Math.round(AR / (r3 / 91)) : null,
          dpo_days: (c3 > 0 && AP != null) ? Math.round(AP / (c3 / 91)) : null,
          dio_days: (c3 > 0 && INV != null) ? Math.round(INV / (c3 / 91)) : null,
          current_ratio: CL > 0 ? Number((CA / CL).toFixed(2)) : null,
          quick_ratio: CL > 0 ? Number(((CA - (INV || 0)) / CL).toFixed(2)) : null,
        });
        mo++; if (mo > 12) { mo = 1; y++; }
      }
      return rows.slice(-13);
    }, [data, plH]);

    const asOf = useMemo(() => new Date(), []);
    const ar = useMemo(() => arRows ? ageRows(arRows, asOf, "due_date", "balance", "customer_name") : null, [arRows, asOf]);
    const ap = useMemo(() => apRows ? ageRows(apRows, asOf, "due_date", "open_balance", "vendor_name") : null, [apRows, asOf]);
    const loading = arRows === null || apRows === null;

    // LTM denominators (use selected window length; default LTM).
    const ltmRev = K.sumIdx(plH.revenue, span) || (plH.revenue || []).slice(-12).reduce((a, b) => a + (Number(b) || 0), 0);
    const ltmCogs = K.sumIdx(plH.cogs, span) || (plH.cogs || []).slice(-12).reduce((a, b) => a + (Number(b) || 0), 0);
    const arTotal = ar ? ar.total : 0;
    const apTotal = ap ? ap.total : 0;
    const inventory = (data && data.bs && Number(data.bs.inventory)) || null;

    const dso = ltmRev ? arTotal / ltmRev * 365 : null;
    const dpo = ltmCogs ? apTotal / ltmCogs * 365 : null;
    const dio = (inventory != null && ltmCogs) ? inventory / ltmCogs * 365 : null;
    const ccc = (dso != null && dpo != null) ? (dso + (dio || 0) - dpo) : null;
    const wc = arTotal + (inventory || 0) - apTotal;

    const days = (v) => v == null ? "—" : Math.round(v) + "d";
    const dsoColor = dso == null ? "navy" : dso <= 45 ? "green" : dso <= 60 ? "amber" : "red";
    const cccColor = ccc == null ? "navy" : ccc <= 30 ? "green" : ccc <= 60 ? "teal" : "amber";
    const kpis = [
      { label: "AR Days (DSO)", value: days(dso), valueColor: dsoColor, sub: "vs 45-day benchmark" },
      { label: "AP Days (DPO)", value: days(dpo), valueColor: "teal", sub: "supplier terms in use" },
      { label: "Inventory Days (DIO)", value: days(dio), valueColor: "navy", sub: dio == null ? "needs inventory balance" : "stock on hand" },
      { label: "Cash Conversion Cycle", value: ccc == null ? "—" : Math.round(ccc) + "d", valueColor: cccColor, sub: "DSO + DIO − DPO" },
      { label: "Working Capital", value: M(wc), valueColor: wc >= 0 ? "navy" : "red", sub: "AR + Inv − AP (operating)" },
    ];

    // 12-month CCC trend from snapshots.
    // Stored snapshots if a writer ever populates them; otherwise the monthly
    // series derived from the GL drives the trend chart and ratio sparklines.
    const snapArr = (snaps && snaps.length) ? snaps : txnMonthly;
    const snapLabels = snapArr.map((s) => (s.period_end || "").slice(2, 7));
    const trendSeries = snapArr.length >= 2 ? [
      { type: "line", color: "#1C4ED8", data: snapArr.map((s) => s.dso_days == null ? null : Number(s.dso_days)) },
      { type: "line", color: "#009fa0", data: snapArr.map((s) => s.dpo_days == null ? null : Number(s.dpo_days)) },
      { type: "dashed-line", color: "#d97706", data: snapArr.map((s) => s.dio_days == null ? null : Number(s.dio_days)) },
    ] : null;
    const swatch = (color, label, dashed) => h("span", { style: { display: "inline-flex", alignItems: "center", gap: 5, fontSize: 10, color: "#475569", fontWeight: 600 } },
      h("span", { style: { width: 14, height: dashed ? 0 : 8, borderRadius: 2, background: dashed ? "transparent" : color, borderTop: dashed ? "2px dashed " + color : "none", display: "inline-block" } }), label);

    // Collections priority: customers by AR balance, days outstanding, flag >60d.
    const collections = ar ? ar.byName.slice(0, 12) : [];

    const nameTable = (entity, title, sub) => h(K.Card, { title: title, sub: sub, padding: 0 },
      (entity && entity.byName.length) ? h("table", { className: "pa-table" },
        h("thead", null, h("tr", null, h("th", null, title.indexOf("Customer") >= 0 ? "Customer" : "Vendor"), h("th", { className: "num" }, "Balance"), h("th", { className: "num" }, "% of total"), h("th", { className: "num" }, "Oldest"))),
        h("tbody", null, entity.byName.slice(0, 10).map((c, i) => h("tr", { key: i },
          h("td", { style: { fontWeight: 600 } }, c.name),
          h("td", { className: "num" }, M(c.total)),
          h("td", { className: "num muted" }, (entity.total ? c.total / entity.total * 100 : 0).toFixed(0) + "%"),
          h("td", { className: "num", style: { color: c.oldest > 90 ? "#d94f47" : c.oldest > 60 ? "#d97706" : "#475569", fontWeight: 700 } }, c.oldest <= 0 ? "current" : c.oldest + "d")))))
        : h("div", { className: "pa-card-body", style: { color: "#6475a0", fontSize: 13 } }, "No open balances — connect the subledger to populate."));

    const cccTakeaway = "Cash conversion cycle is <b>" + (ccc == null ? "—" : Math.round(ccc) + " days") + "</b>" +
      (dso != null ? " (DSO " + Math.round(dso) + "d" + (dpo != null ? ", DPO " + Math.round(dpo) + "d" : "") + (dio != null ? ", DIO " + Math.round(dio) + "d" : "") + ")." : ".") +
      (dso != null && dso > 60 ? " Collections are slow — AR is the lever to free cash." : ccc != null && ccc < 30 ? " A short cycle means working capital is funding itself efficiently." : "");

    // ── Working Capital monthly breakdown ─────────────────────────────────────
    // Primary source is working_capital_snapshots. When no snapshots have accrued
    // yet, fall back to a single current-period row synthesised from data.bs (the
    // balance-sheet totals computed in data-live) so the section always renders
    // something real rather than an empty "awaiting data" state.
    const nz = (v) => Number(v) || 0;
    const monthAbbr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    const bs = data && data.bs;
    const curMonthEnd = (function () { const d = new Date(); return d.getUTCFullYear() + "-" + String(d.getUTCMonth() + 1).padStart(2, "0") + "-01"; })();
    // Fallback chain so the monthly table ALWAYS renders a real column when any
    // WC data exists: stored snapshots → balance-sheet totals → the live
    // subledger totals this page already computed for the KPI strip (AR/AP from
    // ar_invoices / ap_bills, inventory from the BS). Only when none of those
    // resolve does the table fall through to its explicit empty state.
    const bsSnap = bs ? [{
      period_end: curMonthEnd,
      working_capital: bs.working_capital,
      current_assets: bs.current_assets,
      current_liabilities: bs.current_liabilities,
      ar_total: bs.accounts_receivable,
      ap_total: bs.accounts_payable,
      inventory_total: bs.inventory,
    }] : (arTotal || apTotal || inventory) ? [{
      period_end: curMonthEnd,
      working_capital: wc,
      ar_total: arTotal || null,
      ap_total: apTotal || null,
      inventory_total: inventory || null,
    }] : [];
    const wcAll = (snaps && snaps.length) ? snaps : (txnMonthly.length ? txnMonthly : bsSnap);
    // Inline period selector: window the monthly table to a trailing-N (or YTD)
    // slice of wcAll. wcAll is chronological, so every window is a contiguous
    // tail — keeping the MoM base-index math below correct.
    const WC_INLINE_N = { "1M": 1, "3M": 3, "6M": 6, "12M": 12, "13M": 13 };
    const wcWindowed = (function () {
      if (!wcAll.length) return wcAll;
      if (wcInlinePeriod === "YTD") {
        const last = wcAll[wcAll.length - 1];
        const yr = ((last && last.period_end) || "").slice(0, 4);
        const f = wcAll.filter((s) => (s.period_end || "").slice(0, 4) === yr);
        return f.length ? f : wcAll.slice(-12);
      }
      return wcAll.slice(-(WC_INLINE_N[wcInlinePeriod] || 12));
    })();
    // Current assets/liabilities aren't stored columns — read them from a row
    // (the BS fallback supplies them) or raw_data when a future writer adds them.
    const caOf = (s) => s.current_assets != null ? nz(s.current_assets) : (s.raw_data && s.raw_data.current_assets != null ? nz(s.raw_data.current_assets) : null);
    const clOf = (s) => s.current_liabilities != null ? nz(s.current_liabilities) : (s.raw_data && s.raw_data.current_liabilities != null ? nz(s.raw_data.current_liabilities) : null);
    // Monthly revenue per snapshot month from plHistory → WC % of revenue.
    const revByYm = {};
    (function () { const labels = plH.labels || [], years = plH.years || [], rev = plH.revenue || []; for (let i = 0; i < labels.length; i++) { const mi = monthAbbr.indexOf(String(labels[i] || "").slice(0, 3)); if (mi >= 0 && years[i]) revByYm[years[i] * 100 + (mi + 1)] = Number(rev[i]) || 0; } })();
    const monthlyRevFor = (pe) => { if (!pe) return null; const d = new Date(pe); const k = d.getUTCFullYear() * 100 + (d.getUTCMonth() + 1); return revByYm[k] != null ? revByYm[k] : null; };
    const colHdr = (s) => { const d = s.period_end ? new Date(s.period_end) : null; return d ? (monthAbbr[d.getUTCMonth()].toUpperCase() + " '" + String(d.getUTCFullYear()).slice(-2)) : "—"; };
    const wcOf = (s) => s.working_capital != null ? nz(s.working_capital)
      : (caOf(s) != null && clOf(s) != null) ? caOf(s) - clOf(s)
      : (s.ar_total != null || s.ap_total != null || s.inventory_total != null) ? nz(s.ar_total) + nz(s.inventory_total) - nz(s.ap_total)
      : null;

    function renderWcMonthly() {
      if (wcAll.length < 1) {
        return h(K.Card, { title: "WORKING CAPITAL BY MONTH", sub: "Period-end balances from stored snapshots" },
          h("div", { style: { padding: 18, fontSize: 13, color: "#6475a0" } }, h("b", null, "Building your working capital history — "), "this table populates as period-end snapshots accrue from the monthly sync."));
      }
      const shown = wcWindowed;
      const base = wcAll.length - shown.length;
      const curIdx = shown.length - 1;
      const allNull = (get) => shown.every((s) => { const v = get(s); return v == null || v === 0; });
      // Asset/liability row definitions. Liabilities render as positive balances
      // in parentheses. Every row hides when entirely empty across the window.
      // current_assets / current_liabilities aren't stored columns, so the
      // "Other" and "TOTAL" rows hide unless a row carries them (BS fallback or
      // raw_data). When they hide, the table still shows AR / Inventory / AP and
      // the WORKING CAPITAL total — all fully data-backed.
      const assetRows = [
        { label: "Accounts Receivable", get: (s) => s.ar_total != null ? nz(s.ar_total) : null, indent: true, hideIfEmpty: true },
        { label: "Inventory", get: (s) => s.inventory_total != null ? nz(s.inventory_total) : null, indent: true, hideIfEmpty: true },
        { label: "Other Current Assets", get: (s) => caOf(s) != null ? caOf(s) - nz(s.ar_total) - nz(s.inventory_total) : null, indent: true, hideIfEmpty: true },
        { label: "TOTAL CURRENT ASSETS", get: (s) => caOf(s), subtotal: "assets", hideIfEmpty: true },
      ].filter((r) => !(r.hideIfEmpty && allNull(r.get)));
      const liabRows = [
        { label: "Accounts Payable", get: (s) => s.ap_total != null ? nz(s.ap_total) : null, indent: true, liab: true, hideIfEmpty: true },
        { label: "Other Current Liab.", get: (s) => clOf(s) != null ? clOf(s) - nz(s.ap_total) : null, indent: true, liab: true, hideIfEmpty: true },
        { label: "TOTAL CURRENT LIAB.", get: (s) => clOf(s), subtotal: "liab", liab: true, hideIfEmpty: true },
      ].filter((r) => !(r.hideIfEmpty && allNull(r.get)));

      const fmtCell = (v, liab) => v == null ? "—" : (liab ? "(" + M(Math.abs(v)) + ")" : M(v));
      const avgOf = (get) => { const vals = shown.map(get).filter((v) => v != null); return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null; };
      const th = (txt, isCur, align) => h("th", { style: { textAlign: align || "right", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, letterSpacing: 0.5, textTransform: "uppercase", color: isCur ? "#b8921e" : "rgba(255,255,255,.8)", whiteSpace: "nowrap" } }, txt);
      const dataCell = (v, liab, style) => h("td", { style: Object.assign({ textAlign: "right", padding: "7px 12px", fontFamily: "'JetBrains Mono',monospace", whiteSpace: "nowrap" }, style || {}) }, fmtCell(v, liab));

      const bodyRow = (r) => h("tr", { key: r.label,
        onClick: () => window.openAccountDetail && window.openAccountDetail({ name: r.label, category: "Working Capital", page: "working-capital" }),
        title: "View account detail",
        style: Object.assign({ cursor: "pointer" }, r.subtotal ? { background: r.subtotal === "liab" ? "rgba(217,79,71,.05)" : "rgba(13,32,64,.04)", fontWeight: 700, borderTop: "2px solid rgba(13,32,64,.10)" } : {}) },
        h("td", { style: { textAlign: "left", padding: r.subtotal ? "8px 12px" : "7px 12px 7px 20px", fontSize: r.subtotal ? 12 : 11.5, fontWeight: r.subtotal ? 700 : 400, color: "#1a2540", position: "sticky", left: 0, background: r.subtotal ? (r.subtotal === "liab" ? "#fbecec" : "#eef0f4") : "#fff", whiteSpace: "nowrap" } }, r.label),
        shown.map((s, j) => dataCell(r.get(s), r.liab, j === curIdx ? { background: "rgba(184,146,30,.05)" } : null)),
        dataCell(avgOf(r.get), r.liab, { color: "#6475a0" }));

      // WORKING CAPITAL + MoM + WC % revenue rows.
      const wcVals = shown.map(wcOf);
      const momVals = shown.map((s, j) => { const gi = base + j; if (gi < 1) return null; const c = wcOf(wcAll[gi]); const p = wcOf(wcAll[gi - 1]); return (c == null || p == null) ? null : c - p; });
      const wcRow = h("tr", { key: "wc", style: { background: "rgba(13,32,64,.06)", fontWeight: 800, borderTop: "3px solid #0d2040" } },
        h("td", { style: { textAlign: "left", padding: "9px 12px", fontSize: 13, fontWeight: 800, color: "#0d2040", position: "sticky", left: 0, background: "#e7eaf0", whiteSpace: "nowrap" } }, "WORKING CAPITAL"),
        wcVals.map((v, j) => h("td", { key: j, style: { textAlign: "right", padding: "9px 12px", fontFamily: "'JetBrains Mono',monospace", fontSize: 13, fontWeight: 800, color: "#0d2040", background: j === curIdx ? "rgba(184,146,30,.08)" : undefined } }, v == null ? "—" : M(v))),
        h("td", { style: { textAlign: "right", padding: "9px 12px", fontFamily: "'JetBrains Mono',monospace", fontWeight: 800, color: "#6475a0" } }, M(avgOf(wcOf))));
      const momRow = h("tr", { key: "mom" },
        h("td", { style: { textAlign: "left", padding: "6px 12px 6px 20px", fontSize: 10.5, color: "#4a5680", position: "sticky", left: 0, background: "#fff", whiteSpace: "nowrap" } }, "MoM Change"),
        momVals.map((v, j) => h("td", { key: j, style: { textAlign: "right", padding: "6px 12px", fontFamily: "'JetBrains Mono',monospace", fontSize: 11, fontWeight: 700, color: v == null ? "#94a3b8" : v > 0 ? "#18a867" : v < 0 ? "#d94f47" : "#94a3b8" } }, v == null ? "—" : (v > 0 ? "+" : v < 0 ? "−" : "") + M(Math.abs(v)))),
        h("td", null));
      const wcrRow = h("tr", { key: "wcr" },
        h("td", { style: { textAlign: "left", padding: "6px 12px 6px 20px", fontSize: 10.5, color: "#6475a0", fontStyle: "italic", position: "sticky", left: 0, background: "#fff", whiteSpace: "nowrap" } }, "WC % of Revenue"),
        shown.map((s, j) => { const mr = monthlyRevFor(s.period_end); const wv = wcVals[j]; const r = (mr && wv != null) ? wv / mr * 100 : null; return h("td", { key: j, style: { textAlign: "right", padding: "6px 12px", fontSize: 10.5, color: "#6475a0", fontStyle: "italic" } }, r == null ? "—" : r.toFixed(1) + "%"); }),
        h("td", null));

      const periodStrip = h("div", { style: { display: "flex", alignItems: "center", gap: 6, padding: "12px 16px 10px", borderBottom: "1px solid rgba(13,32,64,.08)", flexWrap: "wrap" } },
        h("span", { style: { fontSize: 10, fontWeight: 700, color: "#6475a0", textTransform: "uppercase", letterSpacing: ".06em", marginRight: 2 } }, "View:"),
        ["1M", "3M", "6M", "YTD", "12M", "13M"].map((p) => h("button", { key: p, onClick: () => setWcInlinePeriod(p), style: { padding: "3px 9px", fontSize: 10.5, fontWeight: 700, cursor: "pointer", borderRadius: 5, border: "1px solid rgba(13,32,64,.15)", background: wcInlinePeriod === p ? "#0d2040" : "rgba(13,32,64,.05)", color: wcInlinePeriod === p ? "#fff" : "#6475a0" } }, p)));

      return h(K.Card, { title: "WORKING CAPITAL BY MONTH", sub: shown.length + "-month period-end breakdown · current month in gold", padding: 0 },
        periodStrip,
        h("div", { style: { overflowX: "auto" } },
          h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 640 } },
            h("thead", null, h("tr", { style: { background: "#0d2040" } },
              th("Component", false, "left"),
              shown.map((s, j) => h(React.Fragment, { key: j }, th(colHdr(s), j === curIdx))),
              th("Avg", false))),
            h("tbody", null,
              assetRows.map(bodyRow), liabRows.map(bodyRow), wcRow, momRow, wcrRow))));
    }

    function renderWcMovement() {
      const mv = buildMovementExplanation(wcAll);
      if (!mv) {
        return h(K.Card, { title: "WORKING CAPITAL MOVEMENT" },
          h("div", { style: { padding: 16, fontSize: 13, color: "#6475a0" } }, "Building your working capital history — this view will populate after the next monthly sync (needs at least two period-end snapshots)."));
      }
      const monthName = (function () { const d = mv.cur.period_end ? new Date(mv.cur.period_end) : null; return d ? monthAbbr[d.getUTCMonth()] + " " + d.getUTCFullYear() : "latest month"; })();
      const driverRow = (sign, label, impact, explain) => h("div", { style: { display: "flex", alignItems: "baseline", gap: 10, padding: "7px 14px", borderBottom: "1px solid #f1f3f7" } },
        h("span", { style: { width: 12, color: impact >= 0 ? "#18a867" : "#d94f47", fontWeight: 800 } }, impact >= 0 ? "+" : "−"),
        h("span", { style: { width: 120, color: "#4a5680", fontSize: 11 } }, label),
        h("span", { style: { width: 78, textAlign: "right", fontFamily: "'JetBrains Mono',monospace", fontWeight: 700, color: impact >= 0 ? "#18a867" : "#d94f47" } }, (impact >= 0 ? "+" : "−") + M(Math.abs(impact))),
        h("span", { style: { flex: 1, color: "#6475a0", fontSize: 10.5, fontStyle: "italic" } }, explain));
      const headRow = (label, val) => h("div", { style: { display: "flex", justifyContent: "space-between", padding: "9px 14px", background: "rgba(13,32,64,.05)", fontWeight: 700, color: "#0d2040" } },
        h("span", null, label), h("span", { style: { fontFamily: "'JetBrains Mono',monospace" } }, M(val)));
      const drivers = [
        { label: "Accounts Receivable", impact: mv.arImpact, explain: mv.arExplain },
        mv.invChange !== 0 ? { label: "Inventory", impact: mv.invImpact, explain: mv.invExplain } : null,
        { label: "Accounts Payable", impact: mv.apImpact, explain: mv.apExplain },
        Math.abs(mv.otherImpact) >= 1 ? { label: "Other movements", impact: mv.otherImpact, explain: "Net of other current assets / liabilities" } : null,
      ].filter(Boolean);
      return h(K.Card, { title: "WORKING CAPITAL MOVEMENT — " + monthName.toUpperCase(), sub: "What drove the change since the prior month", padding: 0 },
        headRow("Opening WC (prior month)", mv.wcPrev),
        drivers.map((d, i) => h(React.Fragment, { key: i }, driverRow(d.impact >= 0, d.label, d.impact, d.explain))),
        h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "11px 14px", borderTop: "2px solid #0d2040", background: "rgba(13,32,64,.05)" } },
          h("span", { style: { fontWeight: 800, color: "#0d2040" } }, "Closing WC (" + monthName + ")"),
          h("span", { style: { display: "flex", alignItems: "center", gap: 10 } },
            h("span", { style: { fontFamily: "'JetBrains Mono',monospace", fontWeight: 800, fontSize: 15, color: "#0d2040" } }, M(mv.wcCur)),
            h("span", { style: { fontSize: 14, fontWeight: 800, color: mv.wcChange >= 0 ? "#18a867" : "#d94f47" } }, (mv.wcChange >= 0 ? "▲ +" : "▼ −") + M(Math.abs(mv.wcChange)) + (mv.wcChange >= 0 ? " improvement" : " decline")))));
    }

    // ── W2: liquidity ratios (point-in-time from data.bs) + sparkline trend ──
    // current_ratio / quick_ratio are stored snapshot columns, so those two get
    // a real 12-month sparkline; cash ratio / WC-%-rev / WC-days are computed
    // from the current balance sheet (no stored history) so they show value only.
    const nzr = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; };
    const caB = bs ? nzr(bs.current_assets) : null;
    const clB = bs ? nzr(bs.current_liabilities) : null;
    const invB = bs ? nzr(bs.inventory) : null;
    const cashB = bs ? nzr(bs.cash) : null;
    const wcB = bs ? (nzr(bs.working_capital) != null ? nzr(bs.working_capital) : (caB != null && clB != null ? caB - clB : null)) : null;
    const currentRatio = (caB != null && clB) ? caB / clB : null;
    const quickRatio = (caB != null && clB) ? (caB - (invB || 0)) / clB : null;
    const cashRatio = (cashB != null && clB) ? cashB / clB : null;
    const wcPctRev = (wcB != null && ltmRev) ? wcB / ltmRev * 100 : null;
    const wcDays = (wcB != null && ltmRev) ? wcB / (ltmRev / 365) : null;
    const ratioColor = (r, good, ok) => r == null ? "#6475a0" : r >= good ? "#18a867" : r >= ok ? "#d97706" : "#d94f47";
    const ratioTiles = [
      { label: "Current Ratio", value: currentRatio == null ? "—" : currentRatio.toFixed(2) + "×", color: ratioColor(currentRatio, 1.5, 1), sub: "Current assets ÷ current liabilities", spark: snapArr.map((s) => s.current_ratio == null ? null : Number(s.current_ratio)) },
      { label: "Quick Ratio", value: quickRatio == null ? "—" : quickRatio.toFixed(2) + "×", color: ratioColor(quickRatio, 1, 0.7), sub: "(CA − inventory) ÷ current liab.", spark: snapArr.map((s) => s.quick_ratio == null ? null : Number(s.quick_ratio)) },
      { label: "Cash Ratio", value: cashRatio == null ? "—" : cashRatio.toFixed(2) + "×", color: ratioColor(cashRatio, 0.5, 0.2), sub: "Cash ÷ current liabilities", spark: null },
      { label: "WC % of Revenue", value: wcPctRev == null ? "—" : wcPctRev.toFixed(1) + "%", color: "#0d2040", sub: "Working capital ÷ LTM revenue", spark: null },
      { label: "WC Days of Revenue", value: wcDays == null ? "—" : Math.round(wcDays) + "d", color: "#0d2040", sub: "Days of revenue tied up in WC", spark: null },
    ];
    const renderLiquidityRatios = () => h(K.Card, { title: "LIQUIDITY RATIOS", sub: "Point-in-time from the latest balance sheet · current/quick ratio trend from stored snapshots" },
      h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(150px,1fr))", gap: 12 } },
        ratioTiles.map((t, i) => h("div", { key: i, style: { padding: "12px 14px", border: "1px solid rgba(13,32,64,.08)", borderRadius: 10, background: "#fff", display: "flex", flexDirection: "column", gap: 4 } },
          h("div", { style: { fontSize: 10, fontWeight: 700, letterSpacing: 0.5, textTransform: "uppercase", color: "#6475a0" } }, t.label),
          h("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 8 } },
            h("div", { style: { fontSize: 22, fontWeight: 800, fontFamily: "'JetBrains Mono',monospace", color: t.color } }, t.value),
            t.spark ? h(K.Spark, { values: t.spark, color: t.color, width: 64, height: 22 }) : null),
          h("div", { style: { fontSize: 10.5, color: "#94a3b8" } }, t.sub)))));

    return h(K.Shell, { hero: {
      eyebrow: "WORKING CAPITAL", title: "Cash Conversion & Collections",
      subtitle: "AR / AP aging · cash conversion cycle · " + range.label,
      controls: h(K.PeriodControls, Object.assign({}, ps, { showCompare: false })),
    } },

      // Row 1 — KPI tiles
      h("div", { className: "pa-kpi-strip pa-kpi-strip-5" }, kpis.map((k, i) => h(K.Kpi, Object.assign({ key: i, animDelay: i * 0.05, onClick: () => window.__perduraSetPage && window.__perduraSetPage("arap") }, k)))),

      // Row 1a — W2 liquidity ratios (current / quick / cash / WC%rev / WC days)
      renderLiquidityRatios(),

      // Row 1b — working capital monthly breakdown + movement explanation
      renderWcMonthly(),
      renderWcMovement(),

      // Row 2 — cash conversion cycle trend
      h(K.Card, { title: "CASH CONVERSION CYCLE — TREND", sub: "DSO · DPO · DIO over time", right: h("div", { style: { display: "flex", gap: 12 } }, swatch("#1C4ED8", "DSO"), swatch("#009fa0", "DPO"), swatch("#d97706", "DIO", true)) },
        trendSeries ? h(React.Fragment, null,
          h(K.MultiSeriesBarChart, { months: snapLabels, series: trendSeries, height: 180 }),
          h(K.KeyTakeaway, { text: cccTakeaway }))
          : h("div", { style: { padding: 18, fontSize: 13, color: "#6475a0" } },
            h("b", null, "Awaiting stored snapshots. "),
            "The 12-month DSO/DPO/DIO trend reads from working_capital_snapshots. Current point-in-time metrics are shown in the KPI strip above; the line chart populates as period-end snapshots accrue.",
            h("div", { style: { marginTop: 10 } }, h(K.KeyTakeaway, { text: cccTakeaway })))),

      // Row 3 — AR aging + AP aging
      h("div", { className: "pa-grid-2" },
        h(K.Card, { title: "AR Aging", sub: loading ? "loading…" : "Total " + M(arTotal) + " · by days past due" }, h(AgingBar, { buckets: ar ? ar.buckets : [0, 0, 0, 0], total: arTotal })),
        h(K.Card, { title: "AP Aging", sub: loading ? "loading…" : "Total " + M(apTotal) + " · by days past due" }, h(AgingBar, { buckets: ap ? ap.buckets : [0, 0, 0, 0], total: apTotal }))),

      // Row 4 — customer & vendor level
      h("div", { className: "pa-grid-2" },
        nameTable(ar, "AR by Customer", "Largest balances"),
        nameTable(ap, "AP by Vendor", "Largest balances")),

      // Row 5 — collections priority + CFO panel
      h("div", { className: "pa-grid-2" },
        h(K.Card, { title: "Collections priority", sub: "Customers with aged balances · flagged > 60 days", padding: 0 },
          collections.length ? h("table", { className: "pa-table" },
            h("thead", null, h("tr", null, h("th", null, "Customer"), h("th", { className: "num" }, "AR balance"), h("th", { className: "num" }, "Days out"), h("th", null, "Flag"))),
            h("tbody", null, collections.filter((c) => c.oldest > 30).slice(0, 10).map((c, i) => h("tr", { key: i, style: c.oldest > 60 ? { background: "rgba(217,79,71,.05)" } : null },
              h("td", { style: { fontWeight: 600 } }, c.name),
              h("td", { className: "num" }, M(c.total)),
              h("td", { className: "num", style: { fontWeight: 700, color: c.oldest > 90 ? "#d94f47" : c.oldest > 60 ? "#d97706" : "#475569" } }, c.oldest + "d"),
              h("td", null, c.oldest > 90 ? h("span", { style: { color: "#d94f47", fontWeight: 700, fontSize: 11 } }, "⚠ escalate") : c.oldest > 60 ? h("span", { style: { color: "#d97706", fontWeight: 700, fontSize: 11 } }, "follow up") : h("span", { style: { color: "#94a3b8", fontSize: 11 } }, "watch"))))))
            : h("div", { className: "pa-card-body", style: { color: "#6475a0", fontSize: 13 } }, "No customer past 30 days — collections are current.")),
        h(K.CFOCommentaryPanel, {
          title: "Working Capital",
          insights: [
            { type: "neutral", text: "Operating working capital is <b>" + M(wc) + "</b>.", detail: "AR " + M(arTotal) + " + inventory " + (inventory == null ? "n/a" : M(inventory)) + " − AP " + M(apTotal) + "." },
            { type: dso != null && dso > 60 ? "warning" : "neutral", text: "DSO is <b>" + days(dso) + "</b>" + (dso != null ? (dso <= 45 ? " — collections are healthy." : dso <= 60 ? " — slightly above benchmark." : " — collections need attention.") : "."), detail: dso != null && dso > 60 ? "Every 10 days of DSO ties up ≈ " + M(ltmRev / 36.5 || 0) + " of cash." : null },
          ].filter(Boolean),
          savings: dso != null && dso > 60
            ? "Prioritise the flagged 90+ day receivables — pulling DSO back to the 45-day benchmark would free roughly <b>" + M((dso - 45) * (ltmRev / 365) || 0) + "</b> of cash."
            : (dpo != null && dso != null && dpo < dso)
              ? "You pay vendors faster than you collect. Negotiating supplier terms toward your " + Math.round(dso) + "-day collection cycle would fund the gap from float instead of cash."
              : "Maintain the current cycle; snapshot period-end aging to chart the trend and catch slippage early.",
        })));
  }
  window.WorkingCapitalPage = Page;
})();
