// ARAPAnalytics — banker-grade AR / AP / Working Capital analytics
//
// window.ARAPAnalytics. Three tabs: AR Analysis, AP Analysis, Working Capital.
// Computes current aging, concentration, watchlist, and banker-adjusted AR for
// real from the ar_invoices / ap_bills subledgers (queried directly via
// window.supabaseClient + scopedCompanyId). Where a view genuinely depends on
// data we do not have (e.g. stored monthly aging snapshots for a 16-month
// historical trend, or per-customer 12-month averages), it shows an honest
// "awaiting data" placeholder rather than fabricating numbers — consistent with
// the platform "no fake numbers" rule.
//
// Props: { data, companyProfile, scopedCompanyId, periodTotals, setPage }
// Styling matches the dashboard design system (white cards, #1C4ED8 accent).

(function () {
  const { useState, useEffect, useMemo } = React;
  const h = React.createElement;

  // ── design tokens ───────────────────────────────────────────────────────────
  const C = {
    bg: "#F7F8FB", card: "#FFFFFF", border: "#E4E8F0", accent: "#1C4ED8", accentSoft: "#EEF2FF",
    text1: "#1A2233", text2: "#475569", muted: "#6B7A99",
    green: "#059669", greenSoft: "#DCFCE7", amber: "#B45309", amberSoft: "#FEF3C7",
    red: "#DC2626", redSoft: "#FEE2E2", slate: "#94A3B8",
  };
  const CARD = { background: C.card, border: "1px solid " + C.border, boxShadow: "0 1px 4px rgba(0,0,0,0.06)", borderRadius: 10 };
  const BUCKET_COLORS = ["#1C4ED8", "#0891B2", "#F59E0B", "#F97316", "#DC2626"]; // current,1-30,31-60,61-90,91+
  const BUCKET_LABELS = ["Current", "1-30", "31-60", "61-90", "91+"];

  const money = (v, opts) => {
    if (v == null || !isFinite(v)) return "—";
    if (window.PerduraFormat && window.PerduraFormat.money) return window.PerduraFormat.money(v, opts);
    const n = Math.round(v);
    return (n < 0 ? "-$" : "$") + Math.abs(n).toLocaleString();
  };
  const pct = (v, d) => (v == null || !isFinite(v)) ? "—" : v.toFixed(d == null ? 1 : d) + "%";
  const days = (v) => (v == null || !isFinite(v)) ? "—" : Math.round(v) + " days";

  // Bucket an open-item list into [current,1-30,31-60,61-90,91+] by days past due.
  function ageItems(items, asOf, dueField, amtField, nameField) {
    const buckets = [0, 0, 0, 0, 0];
    const counts = [0, 0, 0, 0, 0];
    const byName = new Map();
    const credits = [];
    for (const it of items) {
      const bal = Number(it[amtField]) || 0;
      if (bal === 0) continue;
      const name = (it[nameField] || "(unnamed)").trim();
      if (bal < 0) { credits.push({ name, balance: bal, note: it.status || it.memo || "Credit / overpayment" }); }
      const due = it[dueField] ? new Date(it[dueField]) : null;
      const dpd = due ? Math.floor((asOf - due) / 86400000) : 0;
      let bi = 0;
      if (dpd <= 0) bi = 0; else if (dpd <= 30) bi = 1; else if (dpd <= 60) bi = 2; else if (dpd <= 90) bi = 3; else bi = 4;
      const pos = Math.max(0, bal); // aging buckets count positive balances; credits handled separately
      buckets[bi] += pos; counts[bi] += pos ? 1 : 0;
      const cur = byName.get(name) || { name, total: 0, b: [0, 0, 0, 0, 0], count: 0 };
      cur.total += bal; cur.b[bi] += pos; cur.count += 1;
      byName.set(name, cur);
    }
    const total = buckets.reduce((a, b) => a + b, 0);
    return { buckets, counts, total, byName, credits };
  }

  // Trend-chart bucket colors (per task spec) and labels.
  const TREND_COLORS = ["#18a867", "#b8921e", "#f59e0b", "#ef4444", "#dc2626"]; // current,1-30,31-60,61-90,91+
  const NAVY = "#1C3A6E";

  // Aggregate snapshot rows (ar_/ap_aging_snapshots) into per-month bucket totals.
  // Returns [{ month:Date, key:'YYYY-MM', label, buckets:[5], total, byName:Map }] oldest→newest.
  function aggregateSnaps(rows, nameField) {
    if (!rows || !rows.length) return [];
    const byMonth = new Map();
    for (const r of rows) {
      const key = String(r.snapshot_month).slice(0, 7); // YYYY-MM
      let m = byMonth.get(key);
      if (!m) { m = { key, month: new Date(String(r.snapshot_month)), buckets: [0, 0, 0, 0, 0], total: 0, byName: new Map() }; byMonth.set(key, m); }
      const b = [Number(r.current_balance) || 0, Number(r.days_1_30) || 0, Number(r.days_31_60) || 0, Number(r.days_61_90) || 0, Number(r.days_91_plus) || 0];
      const t = Number(r.total_balance) || b.reduce((a, x) => a + x, 0);
      for (let i = 0; i < 5; i++) m.buckets[i] += b[i];
      m.total += t;
      const nm = (r[nameField] || "(unnamed)").trim();
      const cur = m.byName.get(nm) || { name: nm, total: 0, b: [0, 0, 0, 0, 0] };
      cur.total += t; for (let i = 0; i < 5; i++) cur.b[i] += b[i];
      m.byName.set(nm, cur);
    }
    const out = Array.from(byMonth.values()).sort((a, b) => a.month - b.month).slice(-16);
    out.forEach((m) => { m.label = m.month.toLocaleDateString("en-US", { month: "short", year: "2-digit" }); });
    return out;
  }

  // ── small UI atoms ──────────────────────────────────────────────────────────
  function Card({ title, subtitle, right, children, pad }) {
    return h("div", { style: { ...CARD, marginBottom: 16 } },
      (title || right) ? h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", padding: "14px 16px 0" } },
        h("div", null,
          title ? h("div", { style: { fontSize: 14, fontWeight: 700, color: C.text1 } }, title) : null,
          subtitle ? h("div", { style: { fontSize: 12, color: C.muted, marginTop: 2 } }, subtitle) : null),
        right || null) : null,
      h("div", { style: { padding: pad == null ? 16 : pad } }, children));
  }

  function Placeholder({ label }) {
    return h("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", textAlign: "center", minHeight: 120, padding: "18px 20px", border: "1px dashed " + C.border, borderRadius: 8, color: C.muted, fontSize: 12.5, lineHeight: 1.55, background: "#FCFDFF" } }, label);
  }

  const STATUS = { green: { fg: C.green, bg: C.greenSoft }, amber: { fg: C.amber, bg: C.amberSoft }, red: { fg: C.red, bg: C.redSoft }, neutral: { fg: C.text2, bg: "#F1F5F9" } };
  function Tile({ label, value, sub, status, delta }) {
    const s = STATUS[status || "neutral"];
    return h("div", { style: { ...CARD, padding: 14, flex: 1, minWidth: 150 } },
      h("div", { style: { fontSize: 11, color: C.muted, fontWeight: 600, marginBottom: 6 } }, label),
      h("div", { style: { fontSize: 21, fontWeight: 700, color: C.text1, letterSpacing: -0.4 } }, value),
      h("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 5 } },
        status ? h("span", { style: { fontSize: 10, fontWeight: 700, color: s.fg, background: s.bg, borderRadius: 999, padding: "2px 8px", textTransform: "uppercase", letterSpacing: 0.3 } }, status === "green" ? "Healthy" : status === "amber" ? "Watch" : status === "red" ? "Concern" : "") : null,
        sub ? h("span", { style: { fontSize: 11, color: C.muted } }, sub) : null,
        delta ? h("span", { style: { fontSize: 11, fontWeight: 600, color: delta.up ? C.green : C.red } }, (delta.up ? "▲ " : "▼ ") + delta.text) : null));
  }

  // Stacked horizontal aging bar for a single period.
  function StackedAgingBar({ buckets, total }) {
    if (!total) return h(Placeholder, { label: "No open balance to age." });
    return h("div", null,
      h("div", { style: { display: "flex", height: 30, borderRadius: 6, overflow: "hidden", border: "1px solid " + C.border } },
        buckets.map((v, i) => v > 0 ? h("div", { key: i, title: BUCKET_LABELS[i] + ": " + money(v), style: { width: (v / total * 100) + "%", background: BUCKET_COLORS[i] } }) : null)),
      h("div", { style: { display: "flex", flexWrap: "wrap", gap: 14, marginTop: 10 } },
        buckets.map((v, i) => h("div", { key: i, style: { display: "flex", alignItems: "center", gap: 6, fontSize: 11.5, color: C.text2 } },
          h("span", { style: { width: 10, height: 10, borderRadius: 2, background: BUCKET_COLORS[i] } }),
          h("span", { style: { fontWeight: 600 } }, BUCKET_LABELS[i]), h("span", null, money(v) + " · " + pct(total ? v / total * 100 : 0, 0))))));
  }

  // Concentration horizontal bar (top-N share of total).
  function ConcentrationBar({ tiers, total }) {
    return h("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
      tiers.map((t) => h("div", { key: t.label },
        h("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 11.5, color: C.text2, marginBottom: 3 } },
          h("span", { style: { fontWeight: 600 } }, t.label), h("span", null, money(t.value) + " · " + pct(total ? t.value / total * 100 : 0, 0))),
        h("div", { style: { height: 8, background: "#EEF2F7", borderRadius: 999, overflow: "hidden" } },
          h("div", { style: { width: (total ? Math.min(100, t.value / total * 100) : 0) + "%", height: "100%", background: C.accent, borderRadius: 999 } })))));
  }

  // Stacked vertical bar trend with an optional metric-line overlay on a right axis.
  // months: [{label, buckets:[5], total}]; line: optional [{label, value}] (e.g. DSO/DPO)
  // benchmark: optional number drawn as a dashed line on the right axis.
  function StackedTrendChart({ months, line, lineLabel, benchmark }) {
    if (!months || !months.length) return h(Placeholder, { label: "No snapshot history yet." });
    const W = 900, H = 200, padL = 8, padR = 44, padT = 12, padB = 26;
    const plotW = W - padL - padR, plotH = H - padT - padB;
    const maxTotal = Math.max(1, ...months.map((m) => m.total));
    const n = months.length;
    const slot = plotW / n;
    const bw = Math.min(46, slot * 0.62);
    const lineVals = (line || []).map((p) => (p && isFinite(p.value)) ? p.value : null);
    const lineMax = Math.max(benchmark || 0, ...lineVals.filter((v) => v != null), 1) * 1.15;
    const xCenter = (i) => padL + slot * i + slot / 2;
    const yBar = (v) => padT + plotH - (v / maxTotal) * plotH;
    const yLine = (v) => padT + plotH - (v / lineMax) * plotH;
    const hasLine = lineVals.some((v) => v != null);
    let linePath = "", pts = [];
    if (hasLine) {
      lineVals.forEach((v, i) => { if (v != null) { const x = xCenter(i), y = yLine(v); pts.push([x, y]); linePath += (linePath ? " L" : "M") + x + " " + y; } });
    }
    return h("div", { style: { overflowX: "auto" } },
      h("svg", { viewBox: "0 0 " + W + " " + H, width: "100%", height: 200, preserveAspectRatio: "none", style: { display: "block" } },
        // horizontal gridlines
        [0.25, 0.5, 0.75, 1].map((f, i) => h("line", { key: "g" + i, x1: padL, x2: W - padR, y1: padT + plotH * (1 - f), y2: padT + plotH * (1 - f), stroke: "#EEF2F7", strokeWidth: 1 })),
        // benchmark dashed line (right axis)
        (benchmark != null && hasLine) ? h("line", { x1: padL, x2: W - padR, y1: yLine(benchmark), y2: yLine(benchmark), stroke: NAVY, strokeWidth: 1, strokeDasharray: "5 4", opacity: 0.5 }) : null,
        // stacked bars
        months.map((m, i) => {
          const x = xCenter(i) - bw / 2;
          let acc = 0;
          return h("g", { key: i },
            m.buckets.map((v, bi) => {
              if (v <= 0) return null;
              const segH = (v / maxTotal) * plotH;
              const y = padT + plotH - acc - segH;
              acc += segH;
              return h("rect", { key: bi, x, y, width: bw, height: Math.max(0.5, segH), fill: TREND_COLORS[bi], title: BUCKET_LABELS[bi] + ": " + money(v) });
            }),
            h("text", { x: xCenter(i), y: H - 8, textAnchor: "middle", fontSize: 9, fill: C.muted }, (n > 12 && i % 2 === 1) ? "" : m.label));
        }),
        // metric line overlay
        hasLine ? h("path", { d: linePath, fill: "none", stroke: NAVY, strokeWidth: 2 }) : null,
        hasLine ? pts.map((p, i) => h("circle", { key: "c" + i, cx: p[0], cy: p[1], r: 2.6, fill: NAVY })) : null,
        // right axis labels for line
        hasLine ? [0, 0.5, 1].map((f, i) => h("text", { key: "ra" + i, x: W - padR + 6, y: padT + plotH * (1 - f) + 3, fontSize: 9, fill: NAVY }, Math.round(lineMax * f))) : null),
      h("div", { style: { display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8 } },
        BUCKET_LABELS.map((l, i) => h("div", { key: i, style: { display: "flex", alignItems: "center", gap: 5, fontSize: 11, color: C.text2 } },
          h("span", { style: { width: 10, height: 10, borderRadius: 2, background: TREND_COLORS[i] } }), l)),
        hasLine ? h("div", { style: { display: "flex", alignItems: "center", gap: 5, fontSize: 11, color: C.text2 } },
          h("span", { style: { width: 14, height: 2, background: NAVY } }), (lineLabel || "DSO") + " (right axis)") : null,
        (benchmark != null && hasLine) ? h("div", { style: { display: "flex", alignItems: "center", gap: 5, fontSize: 11, color: C.muted } },
          h("span", { style: { width: 14, height: 0, borderTop: "1px dashed " + NAVY } }), benchmark + "-day benchmark") : null));
  }

  // Multi-line trend chart (e.g. DSO/DPO/DIO over months). series: [{label,color,values:[]}].
  function MultiLineChart({ months, series }) {
    if (!months || !months.length) return h(Placeholder, { label: "No working-capital history yet." });
    const W = 900, H = 200, padL = 8, padR = 36, padT = 12, padB = 26;
    const plotW = W - padL - padR, plotH = H - padT - padB;
    const all = series.flatMap((s) => s.values).filter((v) => v != null && isFinite(v));
    const maxV = Math.max(1, ...all) * 1.12;
    const n = months.length;
    const x = (i) => padL + (n === 1 ? plotW / 2 : (plotW * i) / (n - 1));
    const y = (v) => padT + plotH - (v / maxV) * plotH;
    return h("div", { style: { overflowX: "auto" } },
      h("svg", { viewBox: "0 0 " + W + " " + H, width: "100%", height: 200, preserveAspectRatio: "none", style: { display: "block" } },
        [0.25, 0.5, 0.75, 1].map((f, i) => h("line", { key: "g" + i, x1: padL, x2: W - padR, y1: y(maxV * f), y2: y(maxV * f), stroke: "#EEF2F7", strokeWidth: 1 })),
        [0, 0.5, 1].map((f, i) => h("text", { key: "ya" + i, x: W - padR + 4, y: y(maxV * f) + 3, fontSize: 9, fill: C.muted }, Math.round(maxV * f))),
        series.map((s, si) => {
          let d = "";
          s.values.forEach((v, i) => { if (v != null && isFinite(v)) d += (d ? " L" : "M") + x(i) + " " + y(v); });
          return h("g", { key: si },
            h("path", { d, fill: "none", stroke: s.color, strokeWidth: 2 }),
            s.values.map((v, i) => (v != null && isFinite(v)) ? h("circle", { key: i, cx: x(i), cy: y(v), r: 2.4, fill: s.color }) : null));
        }),
        months.map((m, i) => h("text", { key: "x" + i, x: x(i), y: H - 8, textAnchor: "middle", fontSize: 9, fill: C.muted }, (n > 12 && i % 2 === 1) ? "" : m.label))),
      h("div", { style: { display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8 } },
        series.map((s, i) => h("div", { key: i, style: { display: "flex", alignItems: "center", gap: 5, fontSize: 11, color: C.text2 } },
          h("span", { style: { width: 14, height: 2, background: s.color } }), s.label))));
  }

  // Navy executive stat-band: a horizontal strip of dark metrics with optional
  // per-value status color (green/amber/red) and a small sub line. Uses the
  // platform .pa-stat-band classes (defined in app.html) with inline fallbacks.
  const BAND_FG = { green: "#34d399", amber: "#fbbf24", red: "#f87171", neutral: "#eef2ff" };
  function StatBand({ items }) {
    return h("div", { className: "pa-stat-band", style: { background: "#0d2040", borderRadius: 12, padding: "18px 24px", display: "flex", gap: 32, marginBottom: 16, overflowX: "auto", color: "#FFFFFF" } },
      items.map((it, i) => h("div", { key: i, className: "pa-stat-band-item", onClick: it.onClick || undefined, title: it.onClick ? "Click to drill in" : undefined, style: { flexShrink: 0, cursor: it.onClick ? "pointer" : "default" } },
        h("div", { className: "pa-stat-band-label", style: { fontSize: 9, color: "rgba(174,184,210,0.7)", textTransform: "uppercase", letterSpacing: 1.5, marginBottom: 4 } }, it.label),
        h("div", { className: "pa-stat-band-value", style: { fontSize: 20, fontWeight: 700, color: BAND_FG[it.status || "neutral"], fontFamily: "'JetBrains Mono',ui-monospace,monospace" } }, it.value),
        it.sub ? h("div", { className: "pa-stat-band-sub", style: { fontSize: 10, color: "rgba(174,184,210,0.6)", marginTop: 2 } }, it.sub) : null)));
  }

  // ExceptionCard-style finding: colored left border by severity + sev pill + text.
  const SEV = { HIGH: "#dc2626", MEDIUM: "#f59e0b", WATCH: "#0891b2", INFO: "#18a867" };
  function FindingsList({ findings }) {
    if (!findings || !findings.length) return null;
    const K = window.PerduraPageKit;
    return h("div", { style: { display: "flex", flexDirection: "column", gap: 10 } },
      findings.map((f, i) => {
        const col = SEV[f.sev] || SEV.INFO;
        if (K && K.ExceptionCard) {
          return h(K.ExceptionCard, { key: i, num: i + 1, title: f.sev, detail: f.text, severity: f.sev === "INFO" ? "Low" : f.sev === "MEDIUM" ? "Medium" : f.sev === "WATCH" ? "Low" : "High", category: "AR/AP" });
        }
        return h("div", { key: i, style: { ...CARD, borderLeft: "4px solid " + col, padding: "12px 14px", display: "flex", alignItems: "flex-start", gap: 10 } },
          h("span", { style: { fontSize: 10, fontWeight: 800, color: "#FFFFFF", background: col, borderRadius: 999, padding: "2px 8px", letterSpacing: 0.4, flexShrink: 0 } }, f.sev),
          h("span", { style: { fontSize: 12.5, color: C.text2, lineHeight: 1.55 } }, f.text));
      }));
  }

  // ── findings generators ───────────────────────────────────────────────────────
  function generateARFindings(ctx) {
    const { top1Pct, top5Pct, topCustName, currentPct, pct91, dsoReported, arMom, credCount } = ctx;
    const out = [];
    if (top1Pct != null && topCustName) {
      if (top1Pct > 40) out.push({ sev: "HIGH", text: `Concentration risk: ${topCustName} is ${pct(top1Pct, 0)} of AR (top 5 = ${pct(top5Pct, 0)}) — a single-name exposure that drives collections.` });
      else if (top1Pct > 25) out.push({ sev: "MEDIUM", text: `${topCustName} is ${pct(top1Pct, 0)} of AR (top 5 = ${pct(top5Pct, 0)}) — monitor this concentration.` });
      else out.push({ sev: "INFO", text: `Customer base is diversified: top customer ${pct(top1Pct, 0)}, top 5 = ${pct(top5Pct, 0)} of AR.` });
    }
    if (pct91 > 15) out.push({ sev: "HIGH", text: `Severely aged: ${pct(pct91, 0)} of AR is 91+ days — escalate, reserve, or pursue write-off.` });
    else out.push({ sev: "INFO", text: `Aging is healthy: ${pct(currentPct, 0)} current, ${pct(pct91, 0)} in 91+ days.` });
    if (dsoReported != null) {
      if (dsoReported > 60) out.push({ sev: "HIGH", text: `DSO of ${Math.round(dsoReported)} days is well above the 45-day benchmark (by ${Math.round(dsoReported - 45)} days).` });
      else if (dsoReported > 45) out.push({ sev: "MEDIUM", text: `DSO of ${Math.round(dsoReported)} days is above the 45-day benchmark by ${Math.round(dsoReported - 45)} days.` });
      else out.push({ sev: "INFO", text: `DSO of ${Math.round(dsoReported)} days is inside the 45-day benchmark.` });
    }
    if (arMom && arMom.prevTotal && arMom.curTotal < arMom.prevTotal) out.push({ sev: "MEDIUM", text: `AR dropped from a recent peak of ${money(arMom.prevTotal, { compact: true })} (${arMom.prevLabel}) to ${money(arMom.curTotal, { compact: true })} — confirm whether collections or revenue softening.` });
    if (credCount > 0) out.push({ sev: "WATCH", text: `${credCount} customer${credCount > 1 ? "s carry" : " carries"} a net credit balance — verify refunds or unapplied payments.` });
    return out;
  }

  function generateAPFindings(ctx) {
    const { top1VendPct, top5VendPct, top1VendName, currentPct, pct91, dpoReported, apMom, pastDue61, apTotal } = ctx;
    const out = [];
    if (top1VendPct != null && top1VendName) {
      if (top1VendPct > 40) out.push({ sev: "HIGH", text: `Supplier dependency: ${top1VendName} is ${pct(top1VendPct, 0)} of AP (top 5 = ${pct(top5VendPct, 0)}) — secure a backup source.` });
      else if (top1VendPct > 25) out.push({ sev: "MEDIUM", text: `${top1VendName} is ${pct(top1VendPct, 0)} of AP (top 5 = ${pct(top5VendPct, 0)}) — monitor this dependency.` });
      else out.push({ sev: "INFO", text: `Vendor base is spread: top vendor ${pct(top1VendPct, 0)}, top 5 = ${pct(top5VendPct, 0)} of AP.` });
    }
    if (pct91 > 15) out.push({ sev: "HIGH", text: `${pct(pct91, 0)} of AP is 91+ days past due — clear to protect supplier terms and avoid late fees.` });
    else out.push({ sev: "INFO", text: `Payables aging is healthy: ${pct(currentPct, 0)} current, ${pct(pct91, 0)} in 91+ days.` });
    if (dpoReported != null) {
      if (dpoReported < 20) out.push({ sev: "MEDIUM", text: `DPO of ${Math.round(dpoReported)} days is short — you may be paying faster than necessary; extend toward terms to free cash.` });
      else out.push({ sev: "INFO", text: `DPO of ${Math.round(dpoReported)} days is reasonable.` });
    }
    if (pastDue61 > 0) out.push({ sev: "MEDIUM", text: `${money(pastDue61, { compact: true })} (${pct(apTotal ? pastDue61 / apTotal * 100 : 0, 0)} of AP) is 61+ days past due — stretching risk on supplier relationships.` });
    if (apMom && apMom.prevTotal && apMom.curTotal > apMom.prevTotal) out.push({ sev: "WATCH", text: `AP rose from ${money(apMom.prevTotal, { compact: true })} (${apMom.prevLabel}) to ${money(apMom.curTotal, { compact: true })} — confirm intentional term-stretching vs. cash strain.` });
    return out;
  }

  function CfoPanel({ verdict, lines }) {
    const vs = verdict === "Healthy" ? STATUS.green : verdict === "Watch" ? STATUS.amber : verdict === "Concern" ? STATUS.red : STATUS.neutral;
    return h("div", { style: { ...CARD, marginBottom: 16, borderLeft: "3px solid " + vs.fg } },
      h("div", { style: { padding: "14px 16px" } },
        h("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 10 } },
          h("div", { style: { fontSize: 13, fontWeight: 800, color: C.accent, textTransform: "uppercase", letterSpacing: 0.4 } }, "CFO Intelligence"),
          verdict ? h("span", { style: { fontSize: 11, fontWeight: 700, color: vs.fg, background: vs.bg, borderRadius: 999, padding: "3px 10px" } }, verdict) : null),
        lines.map((l, i) => h("div", { key: i, style: { fontSize: 12.5, color: C.text2, lineHeight: 1.6, marginBottom: 5 } },
          l.label ? h("strong", { style: { color: C.text1 } }, l.label + ": ") : null, l.text))));
  }

  const riskFlag = (agedPct) => agedPct > 25 ? { fg: C.red, bg: C.redSoft, label: "HIGH", dot: "🔴" } : agedPct >= 10 ? { fg: C.amber, bg: C.amberSoft, label: "MED", dot: "🟡" } : { fg: C.green, bg: C.greenSoft, label: "OK", dot: "🟢" };

  // ── main component ────────────────────────────────────────────────────────────
  function ARAPAnalytics(props) {
    const { data, companyProfile, scopedCompanyId, periodTotals, setPage } = props || {};
    const [tab, setTab] = useState("ar");
    const [arRows, setArRows] = useState(null); // null=loading, []=none
    const [apRows, setApRows] = useState(null);
    const [arSnaps, setArSnaps] = useState(null); // monthly AR aging snapshots (null=loading, []=none)
    const [apSnaps, setApSnaps] = useState(null); // monthly AP aging snapshots
    const [wcSnaps, setWcSnaps] = useState(null); // working-capital snapshots
    const [reserves, setReserves] = useState([0, 5, 25, 50, 100]); // editable % per bucket
    const [sortKey, setSortKey] = useState("total");
    const [search, setSearch] = useState("");
    const [showAllCust, setShowAllCust] = useState(false);

    useEffect(() => {
      let cancelled = false;
      const db = window.supabaseClient;
      if (!db || !scopedCompanyId) { setArRows([]); setApRows([]); setArSnaps([]); setApSnaps([]); setWcSnaps([]); return; }
      setArRows(null); setApRows(null); setArSnaps(null); setApSnaps(null); setWcSnaps(null);
      db.from("ar_invoices").select("invoice_no,issue_date,due_date,customer_name,total,paid_amount,balance,status,terms").eq("company_id", scopedCompanyId).gt("balance", 0).limit(5000)
        .then(({ data: d }) => { if (!cancelled) setArRows(d || []); }, () => { if (!cancelled) setArRows([]); });
      db.from("ap_bills").select("bill_no,issue_date,due_date,vendor_name,category,amount,open_balance,terms").eq("company_id", scopedCompanyId).gt("open_balance", 0).limit(5000)
        .then(({ data: d }) => { if (!cancelled) setApRows(d || []); }, () => { if (!cancelled) setApRows([]); });
      // Monthly snapshot tables — last ~16 months, oldest→newest.
      const cutoff = new Date(); cutoff.setMonth(cutoff.getMonth() - 17); cutoff.setDate(1);
      const cutoffStr = cutoff.toISOString().slice(0, 10);
      db.from("ar_aging_snapshots").select("snapshot_month,customer_name,current_balance,days_1_30,days_31_60,days_61_90,days_91_plus,total_balance").eq("company_id", scopedCompanyId).gte("snapshot_month", cutoffStr).order("snapshot_month", { ascending: true }).limit(5000)
        .then(({ data: d }) => { if (!cancelled) setArSnaps(d || []); }, () => { if (!cancelled) setArSnaps([]); });
      db.from("ap_aging_snapshots").select("snapshot_month,vendor_name,current_balance,days_1_30,days_31_60,days_61_90,days_91_plus,total_balance").eq("company_id", scopedCompanyId).gte("snapshot_month", cutoffStr).order("snapshot_month", { ascending: true }).limit(5000)
        .then(({ data: d }) => { if (!cancelled) setApSnaps(d || []); }, () => { if (!cancelled) setApSnaps([]); });
      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").eq("company_id", scopedCompanyId).gte("period_end", cutoffStr).order("period_end", { ascending: true }).limit(2000)
        .then(({ data: d }) => { if (!cancelled) setWcSnaps(d || []); }, () => { if (!cancelled) setWcSnaps([]); });
      return () => { cancelled = true; };
    }, [scopedCompanyId]);

    const asOf = useMemo(() => new Date(), []);
    const ltmRevenue = useMemo(() => {
      const r = (data && data.plHistory && data.plHistory.revenue) || (data && data.pl && data.pl.revenue) || [];
      const last12 = r.slice(-12);
      const s = last12.reduce((a, b) => a + (Number(b) || 0), 0);
      return s > 0 ? s : null;
    }, [data]);
    const ltmCogs = useMemo(() => {
      const r = (data && data.plHistory && data.plHistory.cogs) || (data && data.pl && data.pl.cogs) || [];
      const s = r.slice(-12).reduce((a, b) => a + (Number(b) || 0), 0);
      return s > 0 ? s : null;
    }, [data]);

    // Monthly LTM revenue run-rate (avg of last 12 months) for per-month DSO on the trend.
    const monthlyRevRunRate = useMemo(() => {
      const r = (data && data.plHistory && data.plHistory.revenue) || (data && data.pl && data.pl.revenue) || [];
      const last12 = r.slice(-12).map((v) => Number(v) || 0).filter((v) => v > 0);
      if (!last12.length) return null;
      return last12.reduce((a, b) => a + b, 0) / last12.length;
    }, [data]);
    // COGS monthly run-rate for per-month DPO.
    const monthlyCogsRunRate = useMemo(() => {
      const r = (data && data.plHistory && data.plHistory.cogs) || (data && data.pl && data.pl.cogs) || [];
      const last12 = r.slice(-12).map((v) => Number(v) || 0).filter((v) => v > 0);
      if (!last12.length) return null;
      return last12.reduce((a, b) => a + b, 0) / last12.length;
    }, [data]);

    // Aggregated monthly snapshot series.
    const arMonths = useMemo(() => aggregateSnaps(arSnaps, "customer_name"), [arSnaps]);
    const apMonths = useMemo(() => aggregateSnaps(apSnaps, "vendor_name"), [apSnaps]);
    const wcMonths = useMemo(() => {
      if (!wcSnaps || !wcSnaps.length) return [];
      return wcSnaps.slice(-16).map((r) => ({
        label: new Date(String(r.period_end)).toLocaleDateString("en-US", { month: "short", year: "2-digit" }),
        period_end: r.period_end,
        dso: r.dso_days != null ? Number(r.dso_days) : null,
        dpo: r.dpo_days != null ? Number(r.dpo_days) : null,
        dio: r.dio_days != null ? Number(r.dio_days) : null,
        ccc: r.cash_conversion_cycle != null ? Number(r.cash_conversion_cycle) : null,
        wc: r.working_capital != null ? Number(r.working_capital) : null,
        ar: r.ar_total != null ? Number(r.ar_total) : null,
        ap: r.ap_total != null ? Number(r.ap_total) : null,
        inv: r.inventory_total != null ? Number(r.inventory_total) : null,
      }));
    }, [wcSnaps]);

    // Month-over-month bucket deltas from two most recent AR/AP snapshot months.
    function momDelta(monthsArr) {
      if (!monthsArr || monthsArr.length < 2) return null;
      const cur = monthsArr[monthsArr.length - 1], prev = monthsArr[monthsArr.length - 2];
      return { total: cur.total - prev.total, prevTotal: prev.total, curTotal: cur.total, prevLabel: prev.label };
    }
    const arMom = useMemo(() => momDelta(arMonths), [arMonths]);
    const apMom = useMemo(() => momDelta(apMonths), [apMonths]);

    // Fallback to pre-computed data.aging buckets if subledger query returns nothing.
    const fallbackAr = (data && data.aging && data.aging.ar) || null;
    const fallbackAp = (data && data.aging && data.aging.ap) || null;

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

    const loading = arRows === null || apRows === null;
    const hasAr = ar && ar.total > 0;
    const hasAp = ap && ap.total > 0;

    // Archetype-specific DSO/DPO benchmark bands from the declarative catalog —
    // drives the exec-strip coloring + context (no hardcoded 45/30-day cutoffs).
    const KC = window.KPICatalog || {};
    const archetype = (companyProfile && (companyProfile.archetype || (companyProfile.industry && companyProfile.industry.archetype))) || "default";
    const dsoBench = (KC.getBenchmark && KC.getBenchmark("dso_days", archetype)) || { p25: 25, p50: 40, p75: 55 };
    const dpoBench = (KC.getBenchmark && KC.getBenchmark("dpo_days", archetype)) || { p25: 20, p50: 32, p75: 48 };

    // ── derived AR analytics ───────────────────────────────────────────────────
    const arTotal = hasAr ? ar.total : (fallbackAr ? (fallbackAr.buckets || []).reduce((s, b) => s + b.value, 0) : 0);
    const arBuckets = hasAr ? ar.buckets : (fallbackAr ? mapFallbackBuckets(fallbackAr) : [0, 0, 0, 0, 0]);
    const reserveDollar = arBuckets.reduce((s, v, i) => s + v * (reserves[i] || 0) / 100, 0);
    const adjustedAr = arTotal - reserveDollar;
    const dsoReported = ltmRevenue ? arTotal / ltmRevenue * 365 : null;
    const dsoAdjusted = ltmRevenue ? adjustedAr / ltmRevenue * 365 : null;

    const customers = useMemo(() => {
      if (!hasAr) return [];
      const arr = Array.from(ar.byName.values()).map((c) => {
        const aged61 = c.b[3] + c.b[4];
        const agedPct = c.total > 0 ? aged61 / c.total * 100 : 0;
        return { ...c, aged61, agedPct };
      });
      arr.sort((a, b) => b.total - a.total);
      let cum = 0;
      arr.forEach((c) => { cum += c.total; c.cum = arTotal ? cum / arTotal * 100 : 0; c.share = arTotal ? c.total / arTotal * 100 : 0; });
      return arr;
    }, [ar, hasAr, arTotal]);

    const concentration = useMemo(() => {
      const topN = (n) => customers.slice(0, n).reduce((s, c) => s + c.total, 0);
      return [{ label: "Top 1", value: topN(1) }, { label: "Top 3", value: topN(3) }, { label: "Top 5", value: topN(5) }, { label: "Top 10", value: topN(10) }, { label: "Top 20", value: topN(20) }];
    }, [customers]);
    const top5Pct = arTotal ? concentration[2].value / arTotal * 100 : null;

    // ── derived AP analytics ───────────────────────────────────────────────────
    const apTotal = hasAp ? ap.total : (fallbackAp ? (fallbackAp.buckets || []).reduce((s, b) => s + b.value, 0) : 0);
    const apBuckets = hasAp ? ap.buckets : (fallbackAp ? mapFallbackBuckets(fallbackAp) : [0, 0, 0, 0, 0]);
    const dpoReported = ltmCogs ? apTotal / ltmCogs * 365 : null;
    const vendors = useMemo(() => {
      if (!hasAp) return [];
      const arr = Array.from(ap.byName.values()).map((v) => ({ ...v, aged61: v.b[3] + v.b[4] }));
      arr.sort((a, b) => b.total - a.total);
      return arr;
    }, [ap, hasAp]);

    // ── render ──────────────────────────────────────────────────────────────────
    return h("div", { style: { background: C.bg, minHeight: "100%", padding: "4px 2px" } },
      h("div", { onClick: () => setPage && setPage("overview"), style: { display: "inline-flex", alignItems: "center", gap: 6, marginBottom: 10, fontSize: 12, fontWeight: 600, color: C.muted, cursor: "pointer", userSelect: "none", transition: "color 0.15s" }, onMouseEnter: (e) => { e.currentTarget.style.color = C.accent; }, onMouseLeave: (e) => { e.currentTarget.style.color = C.muted; } },
        "← Back to AR / AP Analytics"),
      h("div", { className: "pa-hero", style: { borderRadius: 14, marginBottom: 18 } },
        h("div", { className: "pa-hero-eyebrow" }, "WORKING CAPITAL"),
        h("div", { className: "pa-hero-title" }, "AR / AP Analytics"),
        h("div", { className: "pa-hero-subtitle" }, "Aging, concentration, DSO/DPO trends, and collectibility analysis")),
      // tab bar
      h("div", { style: { display: "flex", gap: 4, marginBottom: 18, borderBottom: "1px solid " + C.border } },
        [["ar", "AR Analysis"], ["ap", "AP Analysis"], ["wc", "Working Capital"]].map(([id, label]) =>
          h("button", { key: id, onClick: () => setTab(id), style: { background: "transparent", border: "none", cursor: "pointer", padding: "9px 16px", fontSize: 13.5, fontWeight: 600, color: tab === id ? C.accent : C.muted, borderBottom: tab === id ? "2px solid " + C.accent : "2px solid transparent", marginBottom: -1 } }, label))),
      loading ? h("div", { style: { display: "flex", justifyContent: "center", padding: 60 } }, h("div", { style: { width: 28, height: 28, border: "2px solid " + C.border, borderTopColor: C.accent, borderRadius: "50%", animation: "pc-spin 0.7s linear infinite" } }))
        : tab === "ar" ? renderAR() : tab === "ap" ? renderAP() : renderWC());

    // ════════════ AR TAB ════════════
    function renderAR() {
      if (!hasAr && !(fallbackAr && arTotal > 0)) {
        return h(Card, { title: "AR Analysis" }, h(Placeholder, { label: "Awaiting AR subledger data — connect your accounting system to unlock aging, concentration, watchlist, and collectibility analysis." }));
      }
      const overduePct = arTotal ? (arBuckets[1] + arBuckets[2] + arBuckets[3] + arBuckets[4]) / arTotal * 100 : 0;
      const currentPct = arTotal ? arBuckets[0] / arTotal * 100 : 0;
      const aged61Pct = arTotal ? (arBuckets[3] + arBuckets[4]) / arTotal * 100 : 0;
      // DSO color: red >60, amber 45-60, green <45. 91+ %: red >15%, amber 5-15%, green <5%.
      const dsoStatus = dsoReported == null ? "neutral" : dsoReported < dsoBench.p50 ? "green" : dsoReported <= dsoBench.p75 ? "amber" : "red";
      const dsoContext = dsoReported == null ? archetype + " p50 " + Math.round(dsoBench.p50) + "d"
        : (dsoReported <= dsoBench.p50 ? "within" : "above") + " " + archetype + " p50 of " + Math.round(dsoBench.p50) + "d";
      const concStatus = top5Pct == null ? "neutral" : top5Pct < 40 ? "green" : top5Pct < 60 ? "amber" : "red";
      const pct91 = arTotal ? arBuckets[4] / arTotal * 100 : 0;
      const p91Status = pct91 > 15 ? "red" : pct91 >= 5 ? "amber" : "green";
      const verdict = aged61Pct > 20 || (top5Pct || 0) > 60 ? "Concern" : overduePct > 30 || (top5Pct || 0) > 40 ? "Watch" : "Healthy";
      const top1Pct = arTotal ? (concentration[0].value / arTotal * 100) : null;
      const topCustName = customers.length ? customers[0].name : null;

      // MoM delta tile (from two most recent AR snapshot months).
      const arDeltaTile = arMom ? (() => {
        const pctChg = arMom.prevTotal ? (arMom.total / arMom.prevTotal * 100) : 0;
        return { up: arMom.total >= 0, text: money(Math.abs(arMom.total), { compact: true }) + " vs " + arMom.prevLabel };
      })() : null;

      // Per-month DSO overlay for the 16-month trend (month total / monthly rev run-rate * 365 / 12 days/mo basis).
      // DSO = (AR balance / annual revenue) * 365 = (AR / (monthlyRev*12)) * 365.
      const arTrendLine = (arMonths.length && monthlyRevRunRate) ? arMonths.map((m) => ({ label: m.label, value: m.total / (monthlyRevRunRate * 12) * 365 })) : null;

      // S2 — Key Findings (ExceptionCards by severity)
      const arFindings = generateARFindings({
        top1Pct, top5Pct, topCustName, currentPct, pct91, dsoReported, arMom,
        credCount: ar && ar.credits ? new Set(ar.credits.map((c) => c.name)).size : 0,
      });

      return h("div", null,
        // Section 1 — Executive Summary (navy stat-band)
        (function () {
          const drillAR = () => window.openAccountDetail && window.openAccountDetail({ name: "Accounts Receivable", category: "Accounts Receivable", page: "ar-ap" });
          return h(StatBand, { items: [
            { label: "Total AR", value: money(arTotal, { compact: true }), sub: arDeltaTile ? ((arDeltaTile.up ? "▲ " : "▼ ") + arDeltaTile.text) : (hasAr ? (customers.length + " customers") : "from rollup"), onClick: drillAR },
            { label: "DSO", value: days(dsoReported), status: dsoStatus, sub: dsoContext, onClick: drillAR },
            { label: "Current %", value: pct(currentPct, 0), status: currentPct > 75 ? "green" : currentPct >= 60 ? "amber" : "red", sub: "of AR current", onClick: drillAR },
            { label: "91+ %", value: pct(pct91, 0), status: pct91 < 5 ? "green" : pct91 <= 15 ? "amber" : "red", sub: money(arBuckets[4], { compact: true }) + " aged", onClick: drillAR },
            { label: "Top Customer %", value: pct(top1Pct, 0), status: top1Pct == null ? "neutral" : top1Pct > 40 ? "red" : top1Pct > 25 ? "amber" : "neutral", sub: topCustName || "of AR", onClick: drillAR },
            { label: "Reserve Estimate", value: money(reserveDollar, { compact: true }), status: (arTotal && reserveDollar / arTotal > 0.15) ? "red" : (arTotal && reserveDollar / arTotal > 0.07) ? "amber" : "green", sub: pct(arTotal ? reserveDollar / arTotal * 100 : 0, 0) + " of AR", onClick: drillAR },
          ] });
        })(),

        // Section 2 — Key Findings
        arFindings.length ? h(Card, { title: "Key Findings", subtitle: "Auto-generated exceptions ranked by severity" },
          h(FindingsList, { findings: arFindings })) : null,

        // Section 1b — 16-Month AR Aging Trend (real snapshots)
        arMonths.length ? h(Card, { title: "16-Month AR Aging Trend", subtitle: "Stacked monthly aging from stored snapshots" + (arTrendLine ? ", with DSO overlay vs 45-day benchmark" : "") },
          h(StackedTrendChart, { months: arMonths, line: arTrendLine, lineLabel: "DSO", benchmark: arTrendLine ? Math.round(dsoBench.p50) : null }),
          (() => {
            const first = arMonths[0], last = arMonths[arMonths.length - 1];
            const totChg = first.total ? (last.total - first.total) / first.total * 100 : 0;
            const last91 = last.total ? last.buckets[4] / last.total * 100 : 0;
            const first91 = first.total ? first.buckets[4] / first.total * 100 : 0;
            return h("div", { style: { marginTop: 12, fontSize: 12.5, color: C.text2, lineHeight: 1.6, background: C.accentSoft, padding: "10px 14px", borderRadius: 8 } },
              h("strong", { style: { color: C.accent } }, "Key takeaway: "),
              `Total AR ${totChg >= 0 ? "rose" : "fell"} ${pct(Math.abs(totChg), 0)} over ${arMonths.length} months to ${money(last.total, { compact: true })}; the 91+ share ${last91 >= first91 ? "widened" : "narrowed"} from ${pct(first91, 0)} to ${pct(last91, 0)}.` +
              (arTrendLine ? ` DSO is tracking ~${Math.round(arTrendLine[arTrendLine.length - 1].value)} days against the 45-day benchmark.` : " (Add revenue history to overlay DSO.)"));
          })()) : null,

        // Section 2 — Aging trend (current + honest note on history)
        h(Card, { title: "AR Aging — Current Period", subtitle: "16-month trend requires stored monthly snapshots; showing current aging from open invoices" },
          h(StackedAgingBar, { buckets: arBuckets, total: arTotal }),
          h("table", { style: tableStyle, },
            h("thead", null, h("tr", null, h("th", { style: thL }, "Bucket"), BUCKET_LABELS.map((l, i) => h("th", { key: i, style: thR }, l)))),
            h("tbody", null,
              h("tr", null, h("td", { style: tdL }, "Amount"), arBuckets.map((v, i) => h("td", { key: i, style: tdR }, money(v, { compact: true })))),
              h("tr", null, h("td", { style: tdL }, "% Mix"), arBuckets.map((v, i) => {
                const p = arTotal ? v / arTotal * 100 : 0;
                const col = i === 0 ? C.green : i >= 3 ? C.red : C.amber;
                return h("td", { key: i, style: { ...tdR, color: col, fontWeight: 600 } }, pct(p, 0));
              })))),
          h("div", { style: { marginTop: 12, fontSize: 12.5, color: C.text2, lineHeight: 1.6, background: C.accentSoft, padding: "10px 14px", borderRadius: 8 } },
            h("strong", { style: { color: C.accent } }, "CFO read: "),
            `Aging mix is ${currentPct >= 80 ? "strong" : currentPct >= 70 ? "stable" : "soft"} at ${pct(currentPct, 0)} current. `,
            dsoReported != null ? `DSO of ${Math.round(dsoReported)} days is ${dsoReported <= 45 ? "inside" : "above"} the 45-day benchmark. ` : "",
            aged61Pct > 15 ? `${pct(aged61Pct, 0)} of AR is 61+ days and needs collection focus.` : `Severely aged balances (61+) are contained at ${pct(aged61Pct, 0)}.`),
          arMonths.length ? null : h(Placeholder, { label: "16-month historical aging trend — awaiting stored monthly AR aging snapshots. Connect a system that retains period-end aging, or enable snapshotting, to chart the trend with a DSO overlay." })),

        // Section 3 — Customer master & concentration
        hasAr ? h(Card, { title: "Customer Master & Concentration", subtitle: customers.length + " customers ranked by AR balance",
          right: h("input", { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search customer…", style: { padding: "6px 10px", fontSize: 12.5, border: "1px solid " + C.border, borderRadius: 7, outline: "none" } }) },
          h("div", { style: { marginBottom: 16 } }, h(ConcentrationBar, { tiers: concentration, total: arTotal })),
          (() => {
            const filtered = customers.filter((c) => !search || c.name.toLowerCase().includes(search.toLowerCase()));
            const shown = showAllCust ? filtered : filtered.slice(0, 20);
            return h("div", null,
              h("div", { style: { overflowX: "auto" } }, h("table", { style: tableStyle },
                h("thead", null, h("tr", null,
                  ["Rank", "Customer", "Total AR", "Current", "1-30", "31-60", "61-90", "91+", "% of Total", "Cum %", "Aged %", "Risk"].map((c, i) =>
                    h("th", { key: i, onClick: () => i === 2 && setSortKey("total"), style: i <= 1 ? thL : thR }, c)))),
                h("tbody", null, shown.map((c, idx) => {
                  const rf = riskFlag(c.agedPct);
                  return h("tr", { key: c.name, style: { cursor: "pointer" }, title: "View account detail", onClick: () => window.openAccountDetail && window.openAccountDetail({ name: c.name, category: "Accounts Receivable", page: "ar-ap" }) },
                    h("td", { style: tdL }, idx + 1), h("td", { style: { ...tdL, fontWeight: 600, color: C.text1 } }, c.name),
                    h("td", { style: tdR }, money(c.total, { compact: true })),
                    c.b.map((v, i) => h("td", { key: i, style: tdR }, v ? money(v, { compact: true }) : "—")),
                    h("td", { style: tdR }, pct(c.share, 1)), h("td", { style: tdR }, pct(c.cum, 0)),
                    h("td", { style: { ...tdR, color: rf.fg, fontWeight: 600 } }, pct(c.agedPct, 0)),
                    h("td", { style: { ...tdR } }, h("span", { style: { fontSize: 10.5, fontWeight: 700, color: rf.fg, background: rf.bg, borderRadius: 999, padding: "2px 8px" } }, rf.dot + " " + rf.label)));
                })))),
              filtered.length > 20 ? h("div", { style: { marginTop: 10 } },
                h("button", { onClick: () => setShowAllCust((v) => !v), style: { background: C.accentSoft, border: "1px solid " + C.border, color: C.accent, fontSize: 12, fontWeight: 600, padding: "6px 12px", borderRadius: 7, cursor: "pointer" } },
                  showAllCust ? "Show top 20" : ("Show all " + filtered.length + " customers"))) : null);
          })()) : null,

        // Section 4 — Problem accounts watchlist
        hasAr ? renderWatchlist() : null,

        // Section 5 — Banker-Adjusted AR (editable reserves)
        h(Card, { title: "Banker-Adjusted AR", subtitle: "Standard collectibility reserves — edit any % to recalculate live" },
          h("div", { style: { overflowX: "auto" } }, h("table", { style: tableStyle },
            h("thead", null, h("tr", null, h("th", { style: thL }, "Bucket"), h("th", { style: thR }, "Balance"), h("th", { style: thR }, "Reserve %"), h("th", { style: thR }, "Reserve $"), h("th", { style: thR }, "ADJ BALANCE"))),
            h("tbody", null, BUCKET_LABELS.map((l, i) => {
              const bal = arBuckets[i]; const r = reserves[i] || 0; const res = bal * r / 100;
              return h("tr", { key: i },
                h("td", { style: tdL }, h("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } }, h("span", { style: { width: 9, height: 9, borderRadius: 2, background: BUCKET_COLORS[i] } }), l)),
                h("td", { style: tdR }, money(bal, { compact: true })),
                h("td", { style: tdR }, h("input", { type: "number", min: 0, max: 100, value: r, onChange: (e) => setReserves((prev) => prev.map((x, j) => j === i ? Math.max(0, Math.min(100, Number(e.target.value) || 0)) : x)), style: { width: 64, padding: "4px 6px", fontSize: 12.5, border: "1px solid " + C.border, borderRadius: 6, textAlign: "right" } })),
                h("td", { style: { ...tdR, color: C.red } }, money(res, { compact: true })),
                h("td", { style: { ...tdR, fontWeight: 600 } }, money(bal - res, { compact: true })));
            }),
            h("tr", { style: { borderTop: "2px solid " + C.border, fontWeight: 700 } },
              h("td", { style: tdL }, "Total"), h("td", { style: tdR }, money(arTotal, { compact: true })), h("td", { style: tdR }, pct(arTotal ? reserveDollar / arTotal * 100 : 0, 0)), h("td", { style: { ...tdR, color: C.red } }, money(reserveDollar, { compact: true })), h("td", { style: tdR }, money(adjustedAr, { compact: true })))))),
          (() => {
            const mult = (companyProfile && Number(companyProfile.ev_ebitda_multiple)) || 5;
            return h("div", { style: { marginTop: 12, fontSize: 12.5, color: C.text2, lineHeight: 1.6, background: C.redSoft, padding: "10px 14px", borderRadius: 8, borderLeft: "3px solid " + C.red } },
              h("strong", { style: { color: C.red } }, "Working capital impact: "),
              `Adjusted AR reduces working capital by ${money(reserveDollar)} (≈ ${money(reserveDollar * mult)} EV impact at ${mult}x EBITDA).`);
          })(),
          h("div", { style: { display: "flex", flexWrap: "wrap", gap: 12, marginTop: 14 } },
            h(Tile, { label: "Reported AR", value: money(arTotal, { compact: true }) }),
            h(Tile, { label: "Adjusted AR", value: money(adjustedAr, { compact: true }) }),
            h(Tile, { label: "Working-Capital Impact", value: money(-reserveDollar, { compact: true }), sub: "reserve write-down", status: "red" }),
            h(Tile, { label: "DSO Reported → Adjusted", value: days(dsoReported) + " → " + days(dsoAdjusted) })),
          arMonths.length ? null : h(Placeholder, { label: "16-month adjusted-AR trend chart — awaiting stored monthly aging snapshots." })),

        // Section 6 — CFO AR intelligence
        h(CfoPanel, { verdict, lines: [
          { label: "Verdict", text: `Overall AR health is ${verdict.toLowerCase()}. ${pct(currentPct, 0)} of AR is current and ${pct(aged61Pct, 0)} is 61+ days.` },
          { label: "Concentration", text: top5Pct == null ? "Customer concentration unavailable." : `Top-5 customers are ${pct(top5Pct, 0)} of AR${top5Pct > 50 ? " — a meaningful single-customer risk." : " — reasonably diversified."}` },
          { label: "Collectibility", text: `Standard reserves imply a ${money(reserveDollar, { compact: true })} write-down (${pct(arTotal ? reserveDollar / arTotal * 100 : 0, 0)}), leaving ${money(adjustedAr, { compact: true })} collectible.` },
          { label: "Actions", text: aged61Pct > 15 ? "Prioritise collection calls on the watchlist below; escalate or reserve the worst-aged accounts." : "Maintain current cadence; no urgent collection action indicated." },
        ] }),
        // BLOCK D — standardised shared CFO panel (window.CFOCommentaryPanel)
        (function () {
          const Panel = window.CFOCommentaryPanel;
          if (!Panel) return null;
          const M = (v) => money(v, { compact: true });
          const ar61 = arTotal ? arTotal * (aged61Pct || 0) / 100 : 0;
          const insights = [
            { type: dsoReported == null ? "neutral" : dsoReported > dsoBench.p75 ? "warning" : "neutral",
              text: `DSO is <b>${dsoReported == null ? "—" : Math.round(dsoReported) + " days"}</b> — ${dsoReported == null ? "" : (dsoReported <= dsoBench.p50 ? "inside" : "above")} the ${archetype} benchmark of <b>${Math.round(dsoBench.p50)} days</b>.`,
              detail: dsoContext },
            (top1Pct != null && top1Pct > 30) ? { type: "warning", text: `Top customer <b>${topCustName || "—"}</b> is <b>${pct(top1Pct, 0)}</b> of AR — concentration risk.`, detail: "Consider a customer AR limit and a backup of the relationship." } : null,
            (aged61Pct > 15) ? { type: "warning", text: `<b>${pct(aged61Pct, 0)}</b> of AR is 61+ days overdue — <b>${M(ar61)}</b> at risk.`, detail: "Prioritise collections on the oldest balances before they age into write-off territory." } : null,
          ].filter(Boolean);
          const savings = (dsoReported != null && ltmRevenue && dsoReported > dsoBench.p50)
            ? `Pulling DSO from <b>${Math.round(dsoReported)}</b> to the ${archetype} median (<b>${Math.round(dsoBench.p50)} days</b>) would release roughly <b>${M((dsoReported - dsoBench.p50) * (ltmRevenue / 365))}</b> of cash.`
            : null;
          return h(Panel, { title: "AR / AP Intelligence", insights, savings });
        })());
    }

    function renderWatchlist() {
      const worst = customers.filter((c) => c.total > 5000 && c.agedPct > 25).slice(0, 25);
      const credits = (ar.credits || []).reduce((m, c) => { const e = m.get(c.name) || { name: c.name, balance: 0, note: c.note }; e.balance += c.balance; m.set(c.name, e); return m; }, new Map());
      const creditList = Array.from(credits.values()).filter((c) => c.balance < 0);
      const action = (c) => {
        const p91 = c.total ? c.b[4] / c.total * 100 : 0;
        if (p91 > 50) return "Write-off candidate";
        if (c.agedPct > 40) return "Escalate";
        if (c.b[2] + c.b[3] > c.b[4]) return "Collection call";
        return "Reserve";
      };
      return h(Card, { title: "Problem Accounts Watchlist", subtitle: "Actionable accounts requiring collections attention" },
        h("div", { style: { fontSize: 12.5, fontWeight: 700, color: C.text1, marginBottom: 8 } }, "a) Worst Aged (>$5K AR, >25% aged 61+)"),
        worst.length ? h("div", { style: { overflowX: "auto", marginBottom: 18 } }, h("table", { style: tableStyle },
          h("thead", null, h("tr", null, ["Customer", "Total AR", "61-90", "91+", "Aged $", "Aged %", "Recommended Action"].map((c, i) => h("th", { key: i, style: i === 0 ? thL : thR }, c)))),
          h("tbody", null, worst.map((c) => h("tr", { key: c.name, style: { cursor: "pointer" }, title: "View account detail", onClick: () => window.openAccountDetail && window.openAccountDetail({ name: c.name, category: "Accounts Receivable", page: "ar-ap" }) },
            h("td", { style: { ...tdL, fontWeight: 600 } }, c.name), h("td", { style: tdR }, money(c.total, { compact: true })),
            h("td", { style: tdR }, money(c.b[3], { compact: true })), h("td", { style: tdR }, money(c.b[4], { compact: true })),
            h("td", { style: { ...tdR, color: C.red } }, money(c.aged61, { compact: true })), h("td", { style: { ...tdR, color: C.red, fontWeight: 600 } }, pct(c.agedPct, 0)),
            h("td", { style: tdR }, h("span", { style: { fontSize: 11, fontWeight: 600, color: C.accent } }, action(c)))))))) : h(Placeholder, { label: "No accounts over $5K with more than 25% aged 61+ days. Clean aged book." }),

        h("div", { style: { fontSize: 12.5, fontWeight: 700, color: C.text1, margin: "4px 0 8px" } }, "b) Credit Balances (negative AR)"),
        creditList.length ? h("table", { style: tableStyle },
          h("thead", null, h("tr", null, h("th", { style: thL }, "Customer"), h("th", { style: thR }, "Balance"), h("th", { style: thL }, "Note"))),
          h("tbody", null, creditList.map((c) => h("tr", { key: c.name }, h("td", { style: tdL }, c.name), h("td", { style: { ...tdR, color: C.green } }, money(c.balance, { compact: true })), h("td", { style: { ...tdL, color: C.muted } }, c.note || "Credit / overpayment"))))) : h(Placeholder, { label: "No customer credit balances." }),

        h("div", { style: { fontSize: 12.5, fontWeight: 700, color: C.text1, margin: "16px 0 8px" } }, "c) Deteriorating Accounts (AR grew and aging worsened over 12 months)"),
        h(Placeholder, { label: "Awaiting 12-month per-customer AR history — needs stored monthly snapshots to detect balances that grew while aging worsened." }));
    }

    // ════════════ AP TAB ════════════
    function renderAP() {
      if (!hasAp && !(fallbackAp && apTotal > 0)) {
        return h(Card, { title: "AP Analysis" }, h(Placeholder, { label: "Awaiting AP subledger data — connect your accounting system to unlock payables aging, vendor concentration, and DPO analysis." }));
      }
      const pastDue61 = apBuckets[3] + apBuckets[4];
      const lateFeeEst = pastDue61 * 0.015; // ~1.5% indicative late-fee exposure
      // DPO: longer = more cash retained, so green at/above p50, amber within
      // p25–p50, red below p25 (paying suppliers faster than peers).
      const dpoStatus = dpoReported == null ? "neutral" : dpoReported >= dpoBench.p50 ? "green" : dpoReported >= dpoBench.p25 ? "amber" : "red";
      const dpoContext = dpoReported == null ? archetype + " p50 " + Math.round(dpoBench.p50) + "d"
        : (dpoReported >= dpoBench.p50 ? "at/above" : "below") + " " + archetype + " p50 of " + Math.round(dpoBench.p50) + "d";
      const top5VendPct = apTotal ? vendors.slice(0, 5).reduce((s, v) => s + v.total, 0) / apTotal * 100 : null;
      const top1VendPct = apTotal && vendors.length ? vendors[0].total / apTotal * 100 : null;
      const top1VendName = vendors.length ? vendors[0].name : null;
      const verdict = pastDue61 / (apTotal || 1) > 0.2 ? "Concern" : pastDue61 > 0 ? "Watch" : "Healthy";
      const apDeltaTile = apMom ? { up: apMom.total >= 0, text: money(Math.abs(apMom.total), { compact: true }) + " vs " + apMom.prevLabel } : null;
      const apTrendLine = (apMonths.length && monthlyCogsRunRate) ? apMonths.map((m) => ({ label: m.label, value: m.total / (monthlyCogsRunRate * 12) * 365 })) : null;
      const apCurrentPct = apTotal ? apBuckets[0] / apTotal * 100 : 0;
      const apPct91 = apTotal ? apBuckets[4] / apTotal * 100 : 0;
      const apFindings = generateAPFindings({ top1VendPct, top5VendPct, top1VendName, currentPct: apCurrentPct, pct91: apPct91, dpoReported, apMom, pastDue61, apTotal });
      return h("div", null,
        // Section 1 — Executive Summary (navy stat-band)
        (function () {
          const drillAP = () => window.openAccountDetail && window.openAccountDetail({ name: "Accounts Payable", category: "Accounts Payable", page: "ar-ap" });
          return h(StatBand, { items: [
            { label: "Total AP", value: money(apTotal, { compact: true }), sub: apDeltaTile ? ((apDeltaTile.up ? "▲ " : "▼ ") + apDeltaTile.text) : (hasAp ? vendors.length + " vendors" : "from rollup"), onClick: drillAP },
            { label: "DPO", value: days(dpoReported), status: dpoStatus, sub: dpoContext, onClick: drillAP },
            { label: "Current %", value: pct(apCurrentPct, 0), status: apCurrentPct > 75 ? "green" : apCurrentPct >= 60 ? "amber" : "red", sub: "of AP current", onClick: drillAP },
            { label: "91+ %", value: pct(apPct91, 0), status: apPct91 < 5 ? "green" : apPct91 <= 15 ? "amber" : "red", sub: money(apBuckets[4], { compact: true }) + " aged", onClick: drillAP },
            { label: "Top Vendor %", value: pct(top1VendPct, 0), status: top1VendPct == null ? "neutral" : top1VendPct > 40 ? "red" : top1VendPct > 25 ? "amber" : "neutral", sub: top1VendName || "of AP", onClick: drillAP },
            { label: "Stretching Risk", value: money(pastDue61, { compact: true }), status: pastDue61 > 0 ? (apPct91 > 15 ? "red" : "amber") : "green", sub: pct(apTotal ? pastDue61 / apTotal * 100 : 0, 0) + " 61+ past due", onClick: drillAP },
          ] });
        })(),

        // Section 2 — Key Findings
        apFindings.length ? h(Card, { title: "Key Findings", subtitle: "Auto-generated exceptions ranked by severity" },
          h(FindingsList, { findings: apFindings })) : null,

        apMonths.length ? h(Card, { title: "16-Month AP Aging Trend", subtitle: "Stacked monthly payables aging from stored snapshots" + (apTrendLine ? ", with DPO overlay" : "") },
          h(StackedTrendChart, { months: apMonths, line: apTrendLine, lineLabel: "DPO", benchmark: apTrendLine ? Math.round(dpoBench.p50) : null }),
          (() => {
            const first = apMonths[0], last = apMonths[apMonths.length - 1];
            const totChg = first.total ? (last.total - first.total) / first.total * 100 : 0;
            return h("div", { style: { marginTop: 12, fontSize: 12.5, color: C.text2, lineHeight: 1.6, background: C.accentSoft, padding: "10px 14px", borderRadius: 8 } },
              h("strong", { style: { color: C.accent } }, "Key takeaway: "),
              `Total AP ${totChg >= 0 ? "rose" : "fell"} ${pct(Math.abs(totChg), 0)} over ${apMonths.length} months to ${money(last.total, { compact: true })}.` +
              (apTrendLine ? ` DPO is tracking ~${Math.round(apTrendLine[apTrendLine.length - 1].value)} days.` : ""));
          })()) : null,
        h(Card, { title: "AP Aging — Current Period", subtitle: "Current aging from open bills" + (apMonths.length ? "" : "; 16-month trend requires stored monthly snapshots") },
          h(StackedAgingBar, { buckets: apBuckets, total: apTotal }),
          apMonths.length ? null : h(Placeholder, { label: "16-month AP aging trend — awaiting stored monthly snapshots." })),
        hasAp ? h(Card, { title: "Vendor Concentration", subtitle: vendors.length + " vendors by open balance" },
          h(ConcentrationBar, { tiers: [1, 3, 5, 10].map((n) => ({ label: "Top " + n, value: vendors.slice(0, n).reduce((s, v) => s + v.total, 0) })), total: apTotal })) : null,
        hasAp ? h(Card, { title: "Past-Due AP (61+ days)", subtitle: "Vendors with aged balances and indicative late-fee exposure" },
          (() => { const pd = vendors.filter((v) => v.aged61 > 0).slice(0, 25); return pd.length ? h("table", { style: tableStyle },
            h("thead", null, h("tr", null, ["Vendor", "Open Balance", "61-90", "91+", "Aged $", "Late-Fee Est."].map((c, i) => h("th", { key: i, style: i === 0 ? thL : thR }, c)))),
            h("tbody", null, pd.map((v) => h("tr", { key: v.name, style: { cursor: "pointer" }, title: "View account detail", onClick: () => window.openAccountDetail && window.openAccountDetail({ name: v.name, category: "Accounts Payable", page: "ar-ap" }) }, h("td", { style: { ...tdL, fontWeight: 600 } }, v.name), h("td", { style: tdR }, money(v.total, { compact: true })), h("td", { style: tdR }, money(v.b[3], { compact: true })), h("td", { style: tdR }, money(v.b[4], { compact: true })), h("td", { style: { ...tdR, color: C.red } }, money(v.aged61, { compact: true })), h("td", { style: { ...tdR, color: C.amber } }, money(v.aged61 * 0.015, { compact: true })))))) : h(Placeholder, { label: "No payables aged 61+ days. Vendor terms are current." }); })()) : null,
        h(Card, { title: "Early-Payment Discount Opportunities", subtitle: "Bills with discount terms worth capturing" },
          h(Placeholder, { label: "Awaiting vendor discount-term data (e.g. 2/10 net 30) on bills — capture terms in the bill feed to surface early-payment savings." })),
        h(CfoPanel, { verdict, lines: [
          { label: "Verdict", text: `Payables health is ${verdict.toLowerCase()}. DPO is ${days(dpoReported)}${dpoReported != null && dpoReported < 30 ? " — you may be paying faster than necessary." : "."}` },
          { label: "Past due", text: pastDue61 > 0 ? `${money(pastDue61, { compact: true })} is 61+ days past due with ~${money(lateFeeEst, { compact: true })} indicative late-fee exposure.` : "No materially past-due payables." },
          (top1VendPct != null && top1VendPct > 30)
            ? { label: "Vendor dependency", text: `${top1VendName} is ${pct(top1VendPct, 0)} of total AP — a single-supplier dependency; secure a backup source and negotiate terms from a position of strength.` }
            : { label: "Concentration", text: top5VendPct == null ? "Vendor concentration unavailable." : `Top-5 vendors are ${pct(top5VendPct, 0)} of AP — ${(top5VendPct || 0) > 60 ? "concentrated" : "reasonably spread"}.` },
          { label: "Actions", text: pastDue61 > 0 ? "Clear 61+ day balances to avoid late fees and protect supplier terms." : "Consider extending DPO toward terms to free working capital without straining suppliers." },
        ] }));
    }

    // ════════════ WORKING CAPITAL TAB ════════════
    function renderWC() {
      const cash = data && data.cash ? data.cash : null;
      const cashOnHand = cash && isFinite(cash.endCash) ? cash.endCash : null;
      // Prefer the latest stored snapshot DSO/DPO/DIO when available; else fall back to computed.
      const lastWc = wcMonths.length ? wcMonths[wcMonths.length - 1] : null;
      const dso = (lastWc && lastWc.dso != null) ? lastWc.dso : dsoReported;
      const dpo = (lastWc && lastWc.dpo != null) ? lastWc.dpo : dpoReported;
      const dio = lastWc && lastWc.dio != null ? lastWc.dio : null;
      const ccc = (lastWc && lastWc.ccc != null) ? lastWc.ccc : ((dso != null && dpo != null) ? (dso + (dio || 0) - dpo) : null);
      const netWC = (arTotal || 0) - (apTotal || 0); // inventory unavailable -> AR - AP partial
      const wcPctRev = ltmRevenue ? netWC / ltmRevenue * 100 : null;
      const cccHasDio = dio != null;
      return h("div", null,
        h("div", { style: { display: "flex", flexWrap: "wrap", gap: 12, marginBottom: 16 } },
          h(Tile, { label: "DSO", value: days(dso), status: dso == null ? "neutral" : dso < dsoBench.p50 ? "green" : dso <= dsoBench.p75 ? "amber" : "red", sub: archetype + " p50 " + Math.round(dsoBench.p50) + "d" }),
          h(Tile, { label: "DPO", value: days(dpo), status: dpo == null ? "neutral" : dpo >= dpoBench.p50 ? "green" : dpo >= dpoBench.p25 ? "amber" : "red", sub: archetype + " p50 " + Math.round(dpoBench.p50) + "d" }),
          h(Tile, { label: "DIO", value: dio != null ? days(dio) : "—", sub: dio != null ? "days inventory" : "no inventory feed" }),
          h(Tile, { label: "Cash Conversion Cycle", value: ccc != null ? (days(ccc) + (cccHasDio ? "" : "*")) : "—", sub: cccHasDio ? "DSO + DIO − DPO" : "*DSO − DPO (no DIO)" }),
          h(Tile, { label: "Cash on Hand", value: money(cashOnHand, { compact: true }), sub: cash && cash.runwayDays ? Math.round(cash.runwayDays) + "d runway" : null })),

        // DSO / DPO / DIO trend from working_capital_snapshots
        wcMonths.length ? h(Card, { title: "Working-Capital Cycle Trend", subtitle: "DSO, DPO and DIO over " + wcMonths.length + " stored period-ends" },
          h(MultiLineChart, { months: wcMonths, series: [
            { label: "DSO", color: "#1C4ED8", values: wcMonths.map((m) => m.dso) },
            { label: "DPO", color: "#059669", values: wcMonths.map((m) => m.dpo) },
            { label: "DIO", color: "#F59E0B", values: wcMonths.map((m) => m.dio) },
          ] }),
          h("div", { style: { marginTop: 12, fontSize: 12.5, color: C.text2, lineHeight: 1.6, background: C.accentSoft, padding: "10px 14px", borderRadius: 8 } },
            h("strong", { style: { color: C.accent } }, "Key takeaway: "),
            ccc != null ? `The cash conversion cycle currently runs ~${Math.round(ccc)} days${cccHasDio ? "" : " (excl. inventory)"}.` : "Snapshots present but DSO/DPO not yet populated.")) : null,

        // Working-capital bridge from the two most recent snapshots
        (wcMonths.length >= 2) ? (() => {
          const cur = wcMonths[wcMonths.length - 1], prev = wcMonths[wcMonths.length - 2];
          const wcOf = (m) => (m.wc != null) ? m.wc : ((m.ar || 0) - (m.ap || 0) + (m.inv || 0));
          const prevWC = wcOf(prev), curWC = wcOf(cur);
          const dAR = (cur.ar || 0) - (prev.ar || 0);
          const dAP = (cur.ap || 0) - (prev.ap || 0);
          const dInv = (cur.inv || 0) - (prev.inv || 0);
          const step = (label, val, sign) => h(Tile, { label, value: (sign ? (val >= 0 ? "+" : "−") : "") + money(sign ? Math.abs(val) : val, { compact: true }), status: sign ? (val >= 0 ? "green" : "red") : undefined });
          return h(Card, { title: "Working Capital Bridge", subtitle: prev.label + " → " + cur.label + " (ΔAR + ΔInventory − ΔAP)" },
            h("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 } },
              step("WC " + prev.label, prevWC, false),
              step("Δ Receivables", dAR, true),
              step("Δ Inventory", dInv, true),
              step("Δ Payables", -dAP, true),
              step("WC " + cur.label, curWC, false)));
        })() : h(Card, { title: "Working Capital Bridge", subtitle: "AR + Inventory − AP = Net Working Capital" },
          h("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 } },
            h(Tile, { label: "Accounts Receivable", value: money(arTotal, { compact: true }) }),
            h(Tile, { label: "Inventory", value: lastWc && lastWc.inv != null ? money(lastWc.inv, { compact: true }) : "—", sub: lastWc && lastWc.inv != null ? "from snapshot" : "awaiting inventory feed" }),
            h(Tile, { label: "Accounts Payable", value: money(apTotal, { compact: true }) }),
            h(Tile, { label: "Net Working Capital*", value: money(netWC, { compact: true }), sub: "*AR − AP (excl. inventory)" })),
          h(Placeholder, { label: "Prior-period bridge — awaiting at least two stored working-capital snapshots to chart the month-over-month ΔAR / ΔAP / ΔInventory movement." })),
        (() => {
          const cr = wcSnaps && wcSnaps.length ? wcSnaps[wcSnaps.length - 1].current_ratio : null;
          const qr = wcSnaps && wcSnaps.length ? wcSnaps[wcSnaps.length - 1].quick_ratio : null;
          const haveRatios = cr != null || qr != null;
          return h(Card, { title: "Liquidity Metrics", subtitle: "Short-term solvency" },
            h("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 } },
              h(Tile, { label: "Working Capital % of Revenue", value: pct(wcPctRev, 1), sub: ltmRevenue ? "LTM basis" : "no revenue" }),
              h(Tile, { label: "Current Ratio", value: cr != null ? Number(cr).toFixed(2) : "—", sub: cr != null ? "from snapshot" : "awaiting balance sheet", status: cr == null ? "neutral" : cr >= 1.5 ? "green" : cr >= 1 ? "amber" : "red" }),
              h(Tile, { label: "Quick Ratio", value: qr != null ? Number(qr).toFixed(2) : "—", sub: qr != null ? "from snapshot" : "awaiting balance sheet", status: qr == null ? "neutral" : qr >= 1 ? "green" : qr >= 0.7 ? "amber" : "red" })),
            haveRatios ? null : h(Placeholder, { label: "Current and quick ratios require full current-asset and current-liability balances from the balance sheet — connect the GL balance-sheet feed to compute." }));
        })(),
        h(CfoPanel, { verdict: (dso != null && dpo != null) ? ((ccc || 0) > 45 ? "Watch" : "Healthy") : "Watch", lines: [
          { label: "Cycle", text: (dso != null && dpo != null) ? `Receivables collect in ~${Math.round(dso)} days against ~${Math.round(dpo)} days to pay suppliers${cccHasDio ? ` and ~${Math.round(dio)} days of inventory` : ""}, a ${Math.round(ccc)}-day cash conversion cycle.` : "DSO/DPO require revenue and COGS history to compute." },
          { label: "Position", text: `AR of ${money(arTotal, { compact: true })} against AP of ${money(apTotal, { compact: true })} is a ${netWC >= 0 ? "net asset" : "net liability"} working-capital position of ${money(netWC, { compact: true })} before inventory.` },
          { label: "Actions", text: (dso || 0) > 50 ? "Tighten collections to shrink the cash gap; the AR watchlist shows where to start." : "Working-capital cycle is reasonable; consider extending payables toward terms to free cash." },
        ] }));
    }
  }

  function mapFallbackBuckets(side) {
    // data.aging.{ar,ap}.buckets: [{label,value}] roughly Current/1-30/31-60/61-90/90+
    const out = [0, 0, 0, 0, 0];
    (side.buckets || []).forEach((b, i) => { if (i < 5) out[i] = Number(b.value) || 0; });
    return out;
  }

  // shared table styles
  const tableStyle = { width: "100%", borderCollapse: "collapse", fontSize: 12.5, marginTop: 14 };
  const thBase = { padding: "8px 10px", borderBottom: "1px solid " + C.border, color: C.muted, fontWeight: 600, fontSize: 11, whiteSpace: "nowrap", cursor: "default" };
  const thL = { ...thBase, textAlign: "left" };
  const thR = { ...thBase, textAlign: "right" };
  const tdBase = { padding: "7px 10px", borderBottom: "1px solid #F1F5F9", color: C.text2, whiteSpace: "nowrap" };
  const tdL = { ...tdBase, textAlign: "left" };
  const tdR = { ...tdBase, textAlign: "right", fontVariantNumeric: "tabular-nums" };

  window.ARAPAnalytics = ARAPAnalytics;
})();
