// CustomerIntelligencePage — window.CustomerIntelligencePage
//
// Deep per-customer intelligence: revenue ranking, profitability, trends,
// payment behaviour and a full per-customer detail panel.
//
// HONEST DATA SOURCING (critical): gl_transactions carry NO customer/contact
// field, so customer-level revenue is NOT derivable from data.txns. Everything
// here is built from the AR subledger — data.dimensions.arInvoices (ar_invoices:
// invoice_no, issue_date, due_date, customer_name/id, terms, total, paid_amount,
// paid_date, balance, status) — with data.dimensions.customers (customers_master:
// acquisition_date, segment) for tenure. When the AR subledger is absent the page
// renders an honest empty state, never a fabricated customer.
//
// Per-customer COGS is NOT in the schema (sales_line_items has no customer key),
// so profitability allocates window COGS by each customer's revenue share and
// says so plainly. Days-to-pay is measured from paid_date − issue_date and
// benchmarked against the portfolio median (no invented "industry" number).

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

  // ── small utilities ────────────────────────────────────────────────────────
  const num = (v) => { const n = Number(v); return Number.isFinite(n) ? n : 0; };
  const ymOf = (d) => (d || "").slice(0, 7);
  const parseD = (d) => { const t = d ? +new Date(d) : 0; return Number.isFinite(t) ? t : 0; };
  const DAY = 86400000;
  const fmtMonYr = (d) => { try { return new Date(d).toLocaleDateString("en-US", { month: "short", year: "numeric" }); } catch (e) { return d || "—"; } };
  const fmtDay = (d) => { try { return new Date(d).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } catch (e) { return d || "—"; } };
  const fmtYM = (ym) => { try { const [y, m] = ym.split("-"); return new Date(+y, +m - 1, 1).toLocaleDateString("en-US", { month: "short", year: "2-digit" }); } catch (e) { return ym; } };

  // Inclusive list of "YYYY-MM" between two Dates (capped so a stray range can't
  // blow up the render).
  function monthsInRange(range) {
    const out = [];
    if (!range || !range.start || !range.end) return out;
    let y = range.start.getFullYear(), m = range.start.getMonth();
    const ey = range.end.getFullYear(), em = range.end.getMonth();
    while (y < ey || (y === ey && m <= em)) {
      out.push(y + "-" + String(m + 1).padStart(2, "0"));
      m++; if (m > 11) { m = 0; y++; }
      if (out.length > 60) break;
    }
    return out;
  }

  // Risk dot from a gross-margin %.
  function gpRisk(gpPct) {
    if (gpPct >= 35) return { dot: "🟢", label: "Healthy", color: "#059669" };
    if (gpPct >= 20) return { dot: "🟡", label: "Watch", color: "#d97706" };
    return { dot: "🔴", label: "Thin", color: "#DC2626" };
  }
  // Payment risk from avg days-to-pay vs portfolio median + trend.
  function payRisk(avgDtp, median, slower) {
    if (avgDtp == null) return { dot: "⚪", label: "No paid history", color: "#6475a0" };
    if (avgDtp > median + 15 || (slower && avgDtp > median)) return { dot: "🔴", label: "Slow payer", color: "#DC2626" };
    if (avgDtp > median + 5 || slower) return { dot: "🟡", label: "Watch", color: "#d97706" };
    return { dot: "🟢", label: "Prompt", color: "#059669" };
  }

  // ── the customer model — everything derived from the AR subledger ───────────
  function buildModel(invoices, custMaster, range, cmpRange, totalCOGS) {
    const start = +range.start, end = +range.end;
    const inWin = invoices.filter((v) => { const d = parseD(v.issue_date); return d >= start && d <= end; });

    // First-ever invoice per customer (across ALL loaded invoices, not just the
    // window) so "new this period" means a genuinely new logo, not just activity.
    const firstSeen = {};
    invoices.forEach((v) => { const k = v.customer_name || v.customer_id || "Unknown"; const d = v.issue_date || ""; if (!firstSeen[k] || d < firstSeen[k]) firstSeen[k] = d; });

    // Prior-window revenue per customer for the trend arrow.
    const priorByCust = {};
    if (cmpRange) {
      const ps = +cmpRange.start, pe = +cmpRange.end;
      invoices.forEach((v) => { const d = parseD(v.issue_date); if (d >= ps && d <= pe) { const k = v.customer_name || v.customer_id || "Unknown"; priorByCust[k] = (priorByCust[k] || 0) + num(v.total); } });
    }

    const now = Date.now();
    const by = {};
    inWin.forEach((v) => {
      const k = v.customer_name || v.customer_id || "Unknown";
      const rev = num(v.total);
      const o = by[k] || (by[k] = { name: k, revenue: 0, invoiceCount: 0, byMonth: {}, lastInvoice: null, openBalance: 0, overdue: 0, dtpSum: 0, dtpN: 0, invoices: [] });
      o.revenue += rev;
      o.invoiceCount += 1;
      const ym = ymOf(v.issue_date); if (ym) o.byMonth[ym] = (o.byMonth[ym] || 0) + rev;
      if (!o.lastInvoice || (v.issue_date || "") > o.lastInvoice) o.lastInvoice = v.issue_date;
      const bal = num(v.balance); o.openBalance += bal;
      if (bal > 0 && v.due_date && parseD(v.due_date) < now) o.overdue += bal;
      if (v.paid_date && v.issue_date) { const dtp = (parseD(v.paid_date) - parseD(v.issue_date)) / DAY; if (dtp >= 0 && dtp < 3650) { o.dtpSum += dtp; o.dtpN += 1; } }
      o.invoices.push(v);
    });

    const total = Object.values(by).reduce((s, c) => s + c.revenue, 0);
    const months = monthsInRange(range);

    let list = Object.values(by).map((c) => {
      const avgDtp = c.dtpN ? c.dtpSum / c.dtpN : null;
      // Payment trend: avg days-to-pay of the most recent 3 paid invoices vs the
      // 3 before that (getting slower = deteriorating).
      const paid = c.invoices.filter((v) => v.paid_date && v.issue_date).sort((a, b) => (a.paid_date || "").localeCompare(b.paid_date || ""));
      const dtpOf = (v) => (parseD(v.paid_date) - parseD(v.issue_date)) / DAY;
      let slower = false;
      if (paid.length >= 4) { const rec = paid.slice(-3).map(dtpOf); const pre = paid.slice(-6, -3).map(dtpOf); if (pre.length) { const ra = rec.reduce((a, b) => a + b, 0) / rec.length, pa = pre.reduce((a, b) => a + b, 0) / pre.length; slower = ra > pa + 3; } }
      const cogsAlloc = total > 0 ? totalCOGS * (c.revenue / total) : 0;
      const cm = custMaster[c.name] || null;
      // "New this period" keys off the authoritative acquisition date when the
      // customer master carries one, falling back to the first-ever invoice —
      // so a customer acquired earlier isn't mislabelled just because their
      // earliest loaded invoice happens to fall inside the window.
      const activeSince = (cm && cm.acquisition_date) || firstSeen[c.name] || null;
      return Object.assign({}, c, {
        pct: total > 0 ? c.revenue / total : 0,
        avgInvoice: c.invoiceCount ? c.revenue / c.invoiceCount : 0,
        avgDaysToPay: avgDtp,
        paymentSlower: slower,
        prior: priorByCust[c.name] || 0,
        trendPct: (priorByCust[c.name] > 0) ? (c.revenue - priorByCust[c.name]) / priorByCust[c.name] * 100 : null,
        isNew: !!activeSince && parseD(activeSince) >= start,
        activeSince: activeSince,
        segment: cm && cm.segment || null,
        cogsAlloc: cogsAlloc,
        gp: c.revenue - cogsAlloc,
        gpPct: c.revenue > 0 ? (c.revenue - cogsAlloc) / c.revenue * 100 : 0,
        spark: months.map((ym) => c.byMonth[ym] || 0),
        status: c.overdue > 0 ? "Overdue" : (c.openBalance > 0 ? "Open" : "Current"),
      });
    }).sort((a, b) => b.revenue - a.revenue);

    // HHI concentration (0–10000) + top shares.
    const hhi = list.reduce((s, c) => s + (c.pct * 100) * (c.pct * 100), 0);
    const top1 = list[0] ? list[0].pct : 0;
    const top3 = list.slice(0, 3).reduce((s, c) => s + c.pct, 0);
    const newCount = list.filter((c) => c.isNew).length;

    // Portfolio median days-to-pay (customers with paid history).
    const dtps = list.map((c) => c.avgDaysToPay).filter((v) => v != null).sort((a, b) => a - b);
    const medianDtp = dtps.length ? dtps[Math.floor(dtps.length / 2)] : null;

    return { list, total, count: list.length, months, hhi, top1, top3, newCount, medianDtp };
  }

  // ── stacked monthly revenue bars (top 5 customers + Other) ──────────────────
  function StackedBars(props) {
    const { months, list, colors, onSeg } = props;
    const K = window.PerduraPageKit;
    const top = list.slice(0, 5);
    const topNames = new Set(top.map((c) => c.name));
    // per-month totals for each series
    const series = top.map((c) => ({ name: c.name, data: months.map((ym) => c.byMonth[ym] || 0) }));
    const otherData = months.map((ym) => list.filter((c) => !topNames.has(c.name)).reduce((s, c) => s + (c.byMonth[ym] || 0), 0));
    const hasOther = otherData.some((v) => v > 0);
    const allSeries = series.concat(hasOther ? [{ name: "Other", data: otherData, other: true }] : []);
    const monthTotals = months.map((_, mi) => allSeries.reduce((s, se) => s + se.data[mi], 0));
    const maxV = Math.max.apply(null, monthTotals.concat([1]));

    const W = 820, H = 240, padL = 48, padR = 12, padT = 14, padB = 34;
    const chartW = W - padL - padR, chartH = H - padT - padB;
    const colW = chartW / Math.max(months.length, 1);
    const barW = Math.min(colW * 0.62, 46);
    const toY = (v) => padT + chartH * (1 - v / maxV);
    const fmtN = (v) => Math.abs(v) >= 1e6 ? (v / 1e6).toFixed(1) + "M" : Math.abs(v) >= 1e3 ? (v / 1e3).toFixed(0) + "K" : "" + Math.round(v);
    const els = [];
    [0, .25, .5, .75, 1].forEach((p, i) => { const y = padT + chartH * p; els.push(h("line", { key: "g" + i, x1: padL, x2: W - padR, y1: y, y2: y, stroke: "rgba(13,32,64,.07)" }), h("text", { key: "gl" + i, x: padL - 6, y: y + 3, textAnchor: "end", fontSize: 9, fill: "#94a3b8" }, fmtN(maxV * (1 - p)))); });
    months.forEach((ym, mi) => {
      const x = padL + mi * colW + (colW - barW) / 2;
      let acc = 0;
      allSeries.forEach((se, si) => {
        const v = se.data[mi]; if (!v) return;
        const yTop = toY(acc + v), yBot = toY(acc);
        const color = se.other ? "#c3ccdb" : colors[si % colors.length];
        els.push(h("rect", {
          key: "s" + mi + "-" + si, x: x, y: yTop, width: barW, height: Math.max(yBot - yTop, 1), fill: color, rx: 1.5,
          style: { cursor: se.other ? "default" : "pointer" },
          onClick: se.other ? undefined : () => onSeg && onSeg(se.name, ym),
        }, h("title", null, se.name + " · " + fmtYM(ym) + " · " + (K ? K.moneyStr(v, { compact: true }) : Math.round(v)))));
        acc += v;
      });
      if (mi % Math.ceil(months.length / 12) === 0) els.push(h("text", { key: "xl" + mi, x: padL + mi * colW + colW / 2, y: H - 6, textAnchor: "middle", fontSize: 9, fill: "#94a3b8" }, fmtYM(ym)));
    });
    return h("div", null,
      h("svg", { viewBox: "0 0 " + W + " " + H, style: { width: "100%", height: "auto", display: "block" } }, els),
      h("div", { style: { display: "flex", flexWrap: "wrap", gap: 12, marginTop: 8, fontSize: 11, color: "#6475a0" } },
        allSeries.map((se, si) => h("span", { key: si, style: { display: "inline-flex", alignItems: "center", gap: 5 } },
          h("span", { style: { width: 10, height: 10, borderRadius: 2, background: se.other ? "#c3ccdb" : colors[si % colors.length], display: "inline-block" } }),
          se.name))));
  }

  // ── CSV export for a single customer's invoices ─────────────────────────────
  function exportCustomerCsv(cust) {
    const headers = ["Invoice #", "Issue Date", "Due Date", "Amount", "Paid", "Balance", "Status", "Days to Pay"];
    const rows = cust.invoices.slice().sort((a, b) => (b.issue_date || "").localeCompare(a.issue_date || "")).map((v) => {
      const dtp = (v.paid_date && v.issue_date) ? Math.round((parseD(v.paid_date) - parseD(v.issue_date)) / DAY) : "";
      return [v.invoice_no || "", (v.issue_date || "").slice(0, 10), (v.due_date || "").slice(0, 10), num(v.total), num(v.paid_amount), num(v.balance), v.status || (num(v.balance) > 0 ? "Open" : "Paid"), dtp];
    });
    const meta = [
      ["PerduraCFO — Customer Report: " + cust.name],
      ["Revenue (period): " + Math.round(cust.revenue)],
      ["Invoices: " + cust.invoiceCount],
      ["Avg days to pay: " + (cust.avgDaysToPay != null ? Math.round(cust.avgDaysToPay) : "n/a")],
      ["Exported: " + new Date().toLocaleDateString()],
      [], headers, ...rows,
    ];
    const csv = meta.map((row) => row.map((cell) => { const s = String(cell == null ? "" : cell); return (s.includes(",") || s.includes("\"") || s.includes("\n")) ? "\"" + s.replace(/"/g, "\"\"") + "\"" : s; }).join(",")).join("\n");
    const blob = new Blob(["﻿" + csv], { type: "text/csv;charset=utf-8;" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = cust.name.replace(/[^a-z0-9]/gi, "_") + "_customer_report.csv";
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  // ── the page ────────────────────────────────────────────────────────────────
  function Page(props) {
    const K = window.PerduraPageKit;
    if (!K) return h("div", { className: "pc-page" }, "Loading…");
    const { data, companyProfile } = props;
    const MONO = K.MONO, INK = K.INK, MUTE = K.MUTE, POS = K.POS, NEG = K.NEG;
    const M = (v) => K.moneyStr(v, { compact: true });
    const Mf = (v) => K.moneyStr(v);

    const dims = (data && data.dimensions) || {};
    const invoices = (dims.arInvoices || data.arInvoices || []).filter((v) => v && v.issue_date);
    const custRows = dims.customers || data.customers || [];
    const custMaster = {};
    custRows.forEach((c) => { const nm = c.name || c.customer_name; if (nm) custMaster[nm] = c; });

    // Period state (persisted). arInvoices are loaded over ~24 months; anchor to
    // the latest data month from plHistory so relative periods line up with the
    // rest of the platform.
    const plH = (data && (data.plHistory || data.pl)) || { labels: [], revenue: [] };
    const anchor = K.anchorFromPlH(plH);
    const ps = K.usePeriodState("customer_intel", "ltm");
    const range = K.resolvePeriod(ps.mode, anchor, ps.custom);
    const cmpRange = K.comparePeriod(range, ps.cmp);
    const span = K.plIdxRange(plH, range);
    const totalCOGS = (plH && Array.isArray(plH.cogs)) ? K.sumIdx(plH.cogs, span) : 0;

    const [tab, setTab] = useState("overview");
    const [selected, setSelected] = useState(null); // customer name for detail
    const [rowLimit, setRowLimit] = useState(25);

    // ── honest empty state — no AR subledger ──────────────────────────────────
    if (!invoices.length) {
      return h(K.Shell, {
        hero: { eyebrow: "ANALYTICS", title: "Customer Intelligence", subtitle: "Per-customer revenue, profitability and payment behaviour" },
      },
        h(K.Card, { title: "No customer subledger connected" },
          h("div", { style: { padding: 24, color: MUTE, fontSize: 13, lineHeight: 1.7 } },
            "Customer-level revenue, profitability and payment behaviour are built from the ",
            h("b", null, "AR invoice subledger"), " (ar_invoices) — not the general ledger, whose income postings carry no payer. ",
            "Once invoices are synced (Wave / QuickBooks / CSV), this page lights up automatically with per-customer ranking, concentration risk, days-to-pay and a full invoice history per customer.")));
    }

    const model = buildModel(invoices, custMaster, range, cmpRange, totalCOGS);
    const { list, total, count, months, hhi, top1, top3, newCount, medianDtp } = model;
    const avgPerCust = count ? total / count : 0;
    const openCust = (name) => { setSelected(name); setTab("detail"); };

    // ── hero ──────────────────────────────────────────────────────────────────
    const hero = {
      eyebrow: "ANALYTICS",
      title: "Customer Intelligence",
      subtitle: count + " active customer" + (count === 1 ? "" : "s") + " · " + Mf(total) + " revenue · " + Mf(avgPerCust) + " avg per customer · " + range.label,
      controls: h(K.PeriodControls, ps),
    };

    // ── tab bar ───────────────────────────────────────────────────────────────
    const TABS = [["overview", "📊 Overview"], ["profitability", "💰 Profitability"], ["trends", "📈 Trends"], ["detail", "👤 Customer Detail"]];
    const tabBar = h("div", { style: { display: "flex", gap: 4, borderBottom: "2px solid rgba(13,32,64,.08)", marginBottom: 22, overflowX: "auto" } },
      TABS.map(([id, label]) => h("button", { key: id, onClick: () => setTab(id), style: { padding: "10px 18px", fontSize: 12, fontWeight: 700, cursor: "pointer", border: "none", borderBottom: tab === id ? "3px solid #0d2040" : "3px solid transparent", background: "transparent", color: tab === id ? "#0d2040" : "#6475a0", whiteSpace: "nowrap" } }, label)));

    // ── KPI tiles ─────────────────────────────────────────────────────────────
    const kpiRow = h("div", { className: "pa-kpi-row", style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 14, marginBottom: 22 } },
      h(K.Kpi, { label: "Total Revenue", value: M(total), sub: range.label, accent: "#1C4ED8" }),
      h(K.Kpi, { label: "Active Customers", value: String(count), sub: newCount + " new this period", accent: "#0891B2" }),
      h(K.Kpi, { label: "Avg Revenue / Customer", value: M(avgPerCust), accent: "#7C3AED" }),
      h(K.Kpi, { label: "Top Customer", value: (top1 * 100).toFixed(0) + "%", sub: list[0] ? list[0].name : "—", accent: top1 >= 0.3 ? "#DC2626" : "#059669", onClick: list[0] ? () => openCust(list[0].name) : undefined }),
      h(K.Kpi, { label: "Top 3 Concentration", value: (top3 * 100).toFixed(0) + "%", sub: "HHI " + Math.round(hhi).toLocaleString(), accent: top3 >= 0.6 ? "#d97706" : "#059669" }),
      h(K.Kpi, { label: "New Customers", value: String(newCount), sub: range.label, accent: "#18a867" }));

    // ── Tab 1 — Overview: revenue ranking ─────────────────────────────────────
    function overviewTab() {
      const shown = list.slice(0, rowLimit);
      const trendCell = (c) => {
        if (c.trendPct == null) return h("span", { style: { color: MUTE, fontSize: 11 } }, c.isNew ? "new" : "—");
        const up = c.trendPct >= 0;
        return h("span", { style: { color: up ? POS : NEG, fontWeight: 700, fontFamily: MONO, fontSize: 11 } }, (up ? "▲ " : "▼ ") + Math.abs(c.trendPct).toFixed(0) + "%");
      };
      return h("div", null,
        kpiRow,
        h(K.Card, { title: "Customer Revenue Ranking", sub: "Sorted by revenue · click a customer to open full detail", padding: 0 },
          h("div", { style: { overflowX: "auto" } },
            h("table", { className: "pa-table", style: { width: "100%" } },
              h("thead", null, h("tr", null,
                h("th", null, "#"), h("th", null, "Customer"),
                h("th", { className: "num" }, "Revenue"), h("th", { className: "num" }, "% of Total"),
                h("th", { className: "num" }, "Trend"), h("th", { className: "num" }, "Invoices"),
                h("th", { className: "num" }, "Avg Invoice"), h("th", null, "Last Invoice"), h("th", null, "Spark"))),
              h("tbody", null,
                shown.map((c, i) => h("tr", { key: c.name, style: { cursor: "pointer" }, onClick: () => openCust(c.name), title: "Open " + c.name + " detail" },
                  h("td", { style: { color: MUTE, fontFamily: MONO } }, i + 1),
                  h("td", { style: { fontWeight: 700, color: INK } }, c.name, c.isNew ? h("span", { style: { marginLeft: 6, fontSize: 9, fontWeight: 700, color: "#065f46", background: "#d1fae5", padding: "1px 6px", borderRadius: 10 } }, "NEW") : null),
                  h("td", { className: "num", style: { fontFamily: MONO, fontWeight: 700 } }, M(c.revenue)),
                  h("td", { className: "num", style: { fontFamily: MONO, color: c.pct >= 0.2 ? "#d97706" : MUTE } }, (c.pct * 100).toFixed(1) + "%"),
                  h("td", { className: "num" }, trendCell(c)),
                  h("td", { className: "num", style: { fontFamily: MONO } }, c.invoiceCount),
                  h("td", { className: "num", style: { fontFamily: MONO } }, M(c.avgInvoice)),
                  h("td", { style: { color: MUTE, fontSize: 11 } }, c.lastInvoice ? fmtMonYr(c.lastInvoice) : "—"),
                  h("td", null, h(K.Spark, { values: c.spark, color: "#1C4ED8", width: 68, height: 22 }))))))),
          list.length > rowLimit ? h("div", { style: { padding: 12, textAlign: "center" } },
            h("button", { onClick: () => setRowLimit((n) => n + 25), style: { padding: "7px 16px", background: "#f0f4ff", color: "#1C4ED8", border: "1px solid #dbeafe", borderRadius: 8, cursor: "pointer", fontSize: 12, fontWeight: 600 } }, "Show 25 more (" + (list.length - rowLimit) + " left)")) : null));
    }

    // ── Tab 2 — Profitability (revenue real, COGS allocated) ───────────────────
    function profitabilityTab() {
      const hasCOGS = totalCOGS > 0;
      const rec = top1 >= 0.35
        ? "Top customer is " + (list[0] ? list[0].name : "—") + " at " + (top1 * 100).toFixed(0) + "% of revenue — a material single-customer dependency. Diversify the book or secure a longer-term contract to de-risk it."
        : top3 >= 0.6
          ? "Your top 3 customers drive " + (top3 * 100).toFixed(0) + "% of revenue. Concentration is elevated but not critical — protect these relationships and grow the mid-tail."
          : "Revenue is well diversified (top customer " + (top1 * 100).toFixed(0) + "%, HHI " + Math.round(hhi).toLocaleString() + "). No single-customer dependency risk detected.";
      const hhiBand = hhi >= 2500 ? { t: "High concentration", c: NEG } : hhi >= 1500 ? { t: "Moderate concentration", c: "#d97706" } : { t: "Low concentration", c: POS };
      const shown = list.slice(0, rowLimit);
      return h("div", null,
        h("div", { style: { background: "#fff7ed", border: "1px solid #fed7aa", borderRadius: 10, padding: "12px 16px", marginBottom: 18, fontSize: 12.5, color: "#9a3412", lineHeight: 1.6 } },
          h("b", null, "How profitability is estimated: "),
          hasCOGS
            ? "Revenue is actual (per-invoice). Direct per-customer COGS is not in the schema (sales line items carry no customer key), so " + Mf(totalCOGS) + " of window COGS is allocated by each customer's revenue share. Treat gross-profit figures as directional until job-costed COGS is available."
            : "Revenue is actual (per-invoice), but no COGS is available for this window, so gross profit cannot be estimated. Connect a P&L with a COGS section to unlock per-customer profitability."),
        h("div", { style: { display: "grid", gridTemplateColumns: "1.6fr 1fr", gap: 16, alignItems: "start" } },
          h(K.Card, { title: "Profitability by Customer", sub: hasCOGS ? "Revenue actual · COGS allocated by revenue share" : "Revenue actual · COGS unavailable", padding: 0 },
            h("div", { style: { overflowX: "auto" } },
              h("table", { className: "pa-table", style: { width: "100%" } },
                h("thead", null, h("tr", null,
                  h("th", null, "Customer"), h("th", { className: "num" }, "Revenue"),
                  h("th", { className: "num" }, "COGS*"), h("th", { className: "num" }, "Gross Profit"),
                  h("th", { className: "num" }, "GP %"), h("th", { className: "num" }, "Risk"))),
                h("tbody", null,
                  shown.map((c) => { const r = gpRisk(c.gpPct); return h("tr", { key: c.name, style: { cursor: "pointer" }, onClick: () => openCust(c.name) },
                    h("td", { style: { fontWeight: 700, color: INK } }, c.name),
                    h("td", { className: "num", style: { fontFamily: MONO } }, M(c.revenue)),
                    h("td", { className: "num", style: { fontFamily: MONO, color: MUTE } }, hasCOGS ? M(c.cogsAlloc) : "—"),
                    h("td", { className: "num", style: { fontFamily: MONO, fontWeight: 700 } }, hasCOGS ? M(c.gp) : "—"),
                    h("td", { className: "num", style: { fontFamily: MONO, fontWeight: 700, color: r.color } }, hasCOGS ? c.gpPct.toFixed(1) + "%" : "—"),
                    h("td", { className: "num", title: r.label }, hasCOGS ? r.dot : "—")); })))),
            h("div", { style: { padding: "8px 14px", fontSize: 10.5, color: MUTE } }, "* COGS allocated by revenue share — not job-costed.")),
          h(K.Card, { title: "Concentration Risk" },
            h("div", { style: { padding: "6px 4px" } },
              h("div", { style: { fontSize: 34, fontWeight: 800, fontFamily: MONO, color: hhiBand.c, lineHeight: 1 } }, Math.round(hhi).toLocaleString()),
              h("div", { style: { fontSize: 12, fontWeight: 700, color: hhiBand.c, marginBottom: 12 } }, "HHI · " + hhiBand.t),
              h("div", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12.5, color: INK } },
                h("div", null, "├── Top customer: ", h("b", null, (top1 * 100).toFixed(0) + "%"), " of revenue"),
                h("div", null, "├── Top 3 customers: ", h("b", null, (top3 * 100).toFixed(0) + "%")),
                h("div", null, "└── Customers: ", h("b", null, String(count)))),
              h("div", { style: { marginTop: 14, background: "rgba(28,78,216,.05)", borderLeft: "3px solid #1C4ED8", borderRadius: 6, padding: "10px 12px", fontSize: 12, color: "#1a2540", lineHeight: 1.6 } },
                h("b", { style: { color: "#1C4ED8" } }, "Recommendation. "), rec)))));
    }

    // ── Tab 3 — Trends: stacked monthly + cohorts + payment behaviour ─────────
    function trendsTab() {
      const colors = K.RANK_COLORS;
      // cohort: new customers per quarter from activeSince
      const cohorts = {};
      list.forEach((c) => { if (!c.activeSince) return; const d = new Date(c.activeSince); if (isNaN(+d)) return; const q = d.getFullYear() + " Q" + (Math.floor(d.getMonth() / 3) + 1); cohorts[q] = (cohorts[q] || 0) + 1; });
      const cohortRows = Object.keys(cohorts).sort().slice(-8);
      const payRows = list.filter((c) => c.avgDaysToPay != null).sort((a, b) => b.revenue - a.revenue).slice(0, 15);
      const payTable = payRows.length
        ? h("div", { style: { overflowX: "auto" } },
            h("table", { className: "pa-table", style: { width: "100%" } },
              h("thead", null, h("tr", null, h("th", null, "Customer"), h("th", { className: "num" }, "Avg Days to Pay"), h("th", null, "Trend"), h("th", { className: "num" }, "Risk"))),
              h("tbody", null, payRows.map((c) => {
                const r = payRisk(c.avgDaysToPay, medianDtp || 30, c.paymentSlower);
                return h("tr", { key: c.name },
                  h("td", { style: { fontWeight: 600, color: INK } }, c.name),
                  h("td", { className: "num", style: { fontFamily: MONO, fontWeight: 700 } }, Math.round(c.avgDaysToPay) + " days"),
                  h("td", { style: { fontSize: 11, color: c.paymentSlower ? NEG : MUTE } }, c.paymentSlower ? "↑ Getting slower" : "→ Stable"),
                  h("td", { className: "num", title: r.label }, r.dot));
              }))))
        : h("div", { style: { padding: 16, color: MUTE, fontSize: 12.5 } }, "No paid invoices with dates yet — days-to-pay appears once invoices carry a paid_date.");
      const cohortTable = cohortRows.length
        ? h("table", { className: "pa-table", style: { width: "100%" } },
            h("thead", null, h("tr", null, h("th", null, "Quarter"), h("th", { className: "num" }, "New Customers"))),
            h("tbody", null, cohortRows.map((q) => h("tr", { key: q }, h("td", null, q), h("td", { className: "num", style: { fontFamily: MONO, fontWeight: 700 } }, cohorts[q])))))
        : h("div", { style: { padding: 16, color: MUTE, fontSize: 12.5 } }, "Acquisition dates not available — connect customers_master with acquisition_date to unlock cohorts.");
      return h("div", null,
        h(K.Card, { title: "Monthly Revenue by Customer", sub: "Top 5 customers + Other · click a segment to open that customer" },
          months.length ? h(StackedBars, { months, list, colors, onSeg: (name) => openCust(name) }) : h("div", { style: { padding: 16, color: MUTE } }, "No monthly invoice activity in this window.")),
        h("div", { style: { display: "grid", gridTemplateColumns: "1fr 1.4fr", gap: 16, marginTop: 16, alignItems: "start" } },
          h(K.Card, { title: "Customer Cohorts", sub: "New customers acquired per quarter" }, cohortTable),
          h(K.Card, { title: "Payment Behaviour", sub: medianDtp != null ? ("Days to pay · portfolio median " + Math.round(medianDtp) + " days") : "Days to pay", padding: 0 }, payTable)));
    }

    // ── Tab 4 — Customer Detail ───────────────────────────────────────────────
    function detailTab() {
      const c = list.find((x) => x.name === selected) || list[0];
      if (!c) return h(K.Card, { title: "Customer Detail" }, h("div", { style: { padding: 20, color: MUTE } }, "No customer selected."));
      const invs = c.invoices.slice().sort((a, b) => (b.issue_date || "").localeCompare(a.issue_date || ""));
      const monthBars = months.map((ym) => ({ ym, v: c.byMonth[ym] || 0 }));
      const maxBar = Math.max.apply(null, monthBars.map((m) => m.v).concat([1]));
      const benchTxt = (c.avgDaysToPay != null && medianDtp != null)
        ? Math.round(c.avgDaysToPay) + " (portfolio median: " + Math.round(medianDtp) + ")"
        : (c.avgDaysToPay != null ? Math.round(c.avgDaysToPay) + " days" : "n/a");
      // deterministic CFO commentary from the computed values
      const commentary = c.name + " generated " + Mf(c.revenue) + " over " + range.label
        + (c.trendPct != null ? ", " + (c.trendPct >= 0 ? "up " : "down ") + Math.abs(c.trendPct).toFixed(0) + "% vs the prior period" : (c.isNew ? " (new this period)" : ""))
        + ". They represent " + (c.pct * 100).toFixed(1) + "% of total revenue — "
        + (c.pct >= 0.2 ? "a material concentration to monitor." : "within a comfortable concentration range.")
        + (c.avgDaysToPay != null ? " Payment averages " + Math.round(c.avgDaysToPay) + " days" + (medianDtp != null ? (c.avgDaysToPay <= medianDtp ? ", faster than the portfolio median" : ", slower than the portfolio median") : "") + (c.paymentSlower ? " and has been trending slower — worth a nudge." : ".") : " No paid-invoice history is available yet to assess payment timing.")
        + (c.overdue > 0 ? " ⚠️ " + Mf(c.overdue) + " is currently overdue." : "");

      const stat = (label, value, sub) => h("div", { style: { padding: "10px 4px" } },
        h("div", { style: { fontSize: 9, fontWeight: 700, letterSpacing: "1px", textTransform: "uppercase", color: "#6475a0", fontFamily: MONO, marginBottom: 4 } }, label),
        h("div", { style: { fontSize: 17, fontWeight: 800, color: INK, fontFamily: MONO } }, value),
        sub ? h("div", { style: { fontSize: 10.5, color: MUTE, marginTop: 2 } }, sub) : null);

      // Build each section as a flat variable — deep inline nesting here silently
      // mis-paired parens once already, so keep the final return shallow.
      const picker = list.length > 1
        ? h("select", { value: c.name, onChange: (e) => setSelected(e.target.value), style: { padding: "6px 10px", border: "1px solid " + K.LINE, borderRadius: 7, fontSize: 12, fontWeight: 600, color: INK, cursor: "pointer" } }, list.slice(0, 200).map((x) => h("option", { key: x.name, value: x.name }, x.name)))
        : null;
      const header = h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14, gap: 12, flexWrap: "wrap" } },
        h("div", { style: { display: "flex", alignItems: "center", gap: 12 } },
          h(K.BackButton, { label: "all customers", onClick: () => setTab("overview") }),
          picker,
          h("div", { style: { fontSize: 20, fontWeight: 800, color: INK } }, c.name)),
        h("button", { onClick: () => exportCustomerCsv(c), style: { padding: "7px 14px", background: "#f0fdf4", color: "#065f46", border: "1px solid #bbf7d0", borderRadius: 8, cursor: "pointer", fontSize: 12, fontWeight: 600 } }, "⬇️ Export customer report"));

      const statsGrid = h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px,1fr))", gap: 8 } },
        stat("Revenue (" + range.label + ")", M(c.revenue), c.trendPct != null ? ((c.trendPct >= 0 ? "▲ " : "▼ ") + Math.abs(c.trendPct).toFixed(0) + "% vs prior") : (c.isNew ? "new this period" : null)),
        stat("Active since", c.activeSince ? fmtMonYr(c.activeSince) : "—", c.segment || null),
        stat("Avg invoice", M(c.avgInvoice), c.invoiceCount + " invoices"),
        stat("Days to pay", benchTxt, c.paymentSlower ? "trending slower" : null),
        stat("Last invoice", c.lastInvoice ? fmtDay(c.lastInvoice) : "—"),
        stat("Payment status", c.status, c.openBalance > 0 ? Mf(c.openBalance) + " open" : "no open balance"));
      const statsCard = h(K.Card, { padding: "6px 18px" }, statsGrid);

      const trendBars = h("div", { style: { display: "flex", alignItems: "flex-end", gap: 4, height: 90 } },
        monthBars.map((mb) => h("div", { key: mb.ym, title: fmtYM(mb.ym) + ": " + Mf(mb.v), style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 3 } },
          h("div", { style: { width: "100%", background: mb.v ? "#1C4ED8" : "#eef2f9", height: Math.max((mb.v / maxBar) * 70, 2), borderRadius: "3px 3px 0 0" } }),
          h("div", { style: { fontSize: 8, color: MUTE, whiteSpace: "nowrap" } }, fmtYM(mb.ym)))));
      const trendCard = h("div", { style: { marginTop: 16 } }, h(K.Card, { title: "Revenue Trend", sub: "Monthly invoiced revenue" }, trendBars));

      const invRows = invs.slice(0, 200).map((v, i) => {
        const dtp = (v.paid_date && v.issue_date) ? Math.round((parseD(v.paid_date) - parseD(v.issue_date)) / DAY) : null;
        const open = num(v.balance) > 0;
        const overdue = open && v.due_date && parseD(v.due_date) < Date.now();
        const st = overdue ? "Overdue" : open ? "Open" : "Paid";
        const stc = overdue ? NEG : open ? "#d97706" : POS;
        return h("tr", { key: (v.invoice_no || i) + "-" + i },
          h("td", { style: { fontFamily: MONO, fontWeight: 600 } }, v.invoice_no || "—"),
          h("td", { style: { color: MUTE } }, v.issue_date ? fmtDay(v.issue_date) : "—"),
          h("td", { className: "num", style: { fontFamily: MONO, fontWeight: 700 } }, Mf(num(v.total))),
          h("td", { style: { color: MUTE } }, v.due_date ? fmtDay(v.due_date) : "—"),
          h("td", null, h("span", { style: { fontSize: 11, fontWeight: 700, color: stc } }, st)),
          h("td", { className: "num", style: { fontFamily: MONO } }, dtp != null ? dtp + " days" : "—"));
      });
      const invTable = h("table", { className: "pa-table", style: { width: "100%" } },
        h("thead", null, h("tr", null,
          h("th", null, "Invoice #"), h("th", null, "Date"), h("th", { className: "num" }, "Amount"),
          h("th", null, "Due Date"), h("th", null, "Status"), h("th", { className: "num" }, "Days to Pay"))),
        h("tbody", null, invRows));
      const invoiceCard = h("div", { style: { marginTop: 16 } },
        h(K.Card, { title: "Invoice History", sub: c.invoiceCount + " invoices in " + range.label, padding: 0 },
          h("div", { style: { overflowX: "auto", maxHeight: 420 } }, invTable)),
        invs.length > 200 ? h("div", { style: { padding: 8, fontSize: 11, color: MUTE, textAlign: "center" } }, "Showing first 200 of " + invs.length + " invoices.") : null);

      const commentaryPanel = h(K.CFOCommentaryPanel ? K.CFOCommentaryPanel : "div", { title: c.name, insights: [{ type: c.overdue > 0 ? "warning" : "neutral", text: commentary }] });

      return h("div", null, header, statsCard, trendCard, invoiceCard, commentaryPanel);
    }

    const body = tab === "overview" ? overviewTab()
      : tab === "profitability" ? profitabilityTab()
      : tab === "trends" ? trendsTab()
      : detailTab();

    return h(K.Shell, { hero }, tabBar, body);
  }

  window.CustomerIntelligencePage = Page;
})();
