// PayrollPage (Operations) — window.PayrollPage
//
// Payroll Summary: headcount & cost intelligence from a payroll_summary table
// (one row per department per period_end). The most recent period_end forms the
// current snapshot; all period_ends drive the headcount and cost trend charts.
// Live data only — honest empty state when no rows are present, never fabricates
// numbers. A client-side CSV upload lets the user seed / extend the table.
//
// Props: { data, companyProfile, scopedCompanyId, globalPeriod, setPage }
// data.plHistory monthly arrays (incl. revenue) drive the payroll-%-of-revenue
// overlay on the cost trend chart.

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

  const MON3 = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
  const MON_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  const nz = (v) => Number(v) || 0;

  // Stacked cost-component palette (Gross / Contributions / Bonus / Other).
  const COST_KEYS = [
    { key: "gross_salary", label: "Gross Salary", color: "#1C4ED8" },
    { key: "employer_contributions", label: "Employer Contributions", color: "#009fa0" },
    { key: "bonus", label: "Bonus", color: "#b8921e" },
    { key: "other_costs", label: "Other", color: "#7c3aed" },
  ];

  // period_end → "MON 'YY" short label.
  function periodLabel(pe) {
    if (!pe) return "—";
    const d = new Date(pe);
    if (isNaN(+d)) return String(pe).slice(0, 7);
    return MON_ABBR[d.getUTCMonth()].toUpperCase() + " '" + String(d.getUTCFullYear()).slice(-2);
  }
  function fiscalYearOf(pe) { const d = pe ? new Date(pe) : null; return d && !isNaN(+d) ? d.getUTCFullYear() : null; }

  // Turnover status dot: green < 8%, amber 8–15%, red > 15%.
  function turnoverDot(p) {
    if (p == null || !isFinite(p)) return "⚪";
    return p < 8 ? "🟢" : p <= 15 ? "🟡" : "🔴";
  }

  // ── CSV parsing (header-driven, quote-aware) ───────────────────────────────
  function parseCsv(text) {
    const rows = [];
    let field = "", row = [], inQ = false;
    for (let i = 0; i < text.length; i++) {
      const c = text[i];
      if (inQ) {
        if (c === '"') { if (text[i + 1] === '"') { field += '"'; i++; } else inQ = false; }
        else field += c;
      } else if (c === '"') inQ = true;
      else if (c === ",") { row.push(field); field = ""; }
      else if (c === "\n" || c === "\r") {
        if (c === "\r" && text[i + 1] === "\n") i++;
        if (field !== "" || row.length) { row.push(field); rows.push(row); row = []; field = ""; }
      } else field += c;
    }
    if (field !== "" || row.length) { row.push(field); rows.push(row); }
    if (!rows.length) return [];
    const head = rows[0].map((x) => String(x || "").trim().toLowerCase());
    return rows.slice(1).filter((r) => r.some((x) => String(x || "").trim() !== "")).map((r) => {
      const o = {};
      head.forEach((k, i) => { o[k] = r[i] != null ? String(r[i]).trim() : ""; });
      return o;
    });
  }
  const num = (v) => { const n = parseFloat(String(v == null ? "" : v).replace(/[$,%\s]/g, "")); return isFinite(n) ? n : 0; };

  // Sample CSV — exact column order the parser expects, with a few example rows.
  function downloadPayrollTemplate() {
    const csv = [
      "period_end,department,headcount_current,headcount_prior,fte,turnover_pct,gross_salary,employer_contributions,bonus,other_costs",
      "2025-12-31,Engineering,12,11,12,8.5,480000,96000,24000,6000",
      "2025-12-31,Sales,8,8,7.5,15.0,320000,64000,48000,4000",
      "2025-12-31,Operations,5,5,5,5.0,200000,40000,10000,3000",
    ].join("\n");
    const a = document.createElement("a");
    a.href = "data:text/csv;charset=utf-8," + encodeURIComponent(csv);
    a.download = "payroll_template.csv";
    a.click();
  }

  // ── CSV upload card ────────────────────────────────────────────────────────
  function UploadCard(props) {
    const { K, scopedCompanyId, onInserted } = props;
    const [preview, setPreview] = useState(null);
    const [fileName, setFileName] = useState("");
    const [status, setStatus] = useState(null); // {kind, msg}
    const [busy, setBusy] = useState(false);

    const REQUIRED = ["period_end", "department", "headcount_current", "headcount_prior", "fte", "turnover_pct", "gross_salary", "employer_contributions", "bonus", "other_costs"];

    function handleFile(e) {
      const f = e.target.files && e.target.files[0];
      if (!f) return;
      setFileName(f.name); setStatus(null);
      const reader = new FileReader();
      reader.onload = () => {
        try {
          const parsed = parseCsv(String(reader.result || ""));
          if (!parsed.length) { setStatus({ kind: "err", msg: "No data rows found in the file." }); setPreview(null); return; }
          const headers = Object.keys(parsed[0]);
          const missing = REQUIRED.filter((c) => headers.indexOf(c) < 0);
          if (missing.length) { setStatus({ kind: "err", msg: "Missing required column(s): " + missing.join(", ") }); setPreview(null); return; }
          const rows = parsed.map((r) => {
            const gross = num(r.gross_salary), contrib = num(r.employer_contributions), bonus = num(r.bonus), other = num(r.other_costs);
            const fte = num(r.fte);
            const total = gross + contrib + bonus + other;
            return {
              company_id: scopedCompanyId,
              period_end: r.period_end,
              department: r.department || "—",
              headcount_current: Math.round(num(r.headcount_current)),
              headcount_prior: Math.round(num(r.headcount_prior)),
              fte: fte,
              turnover_pct: num(r.turnover_pct),
              gross_salary: gross,
              employer_contributions: contrib,
              bonus: bonus,
              other_costs: other,
              total_payroll_cost: total,
              avg_cost_per_fte: fte ? total / fte : 0,
            };
          });
          setPreview(rows); setStatus(null);
        } catch (err) { setStatus({ kind: "err", msg: "Could not parse file: " + (err && err.message ? err.message : "unknown error") }); setPreview(null); }
      };
      reader.readAsText(f);
    }

    function confirmInsert() {
      if (!preview || !preview.length) return;
      const db = window.supabaseClient;
      if (!db || !scopedCompanyId) { setStatus({ kind: "err", msg: "No data connection — cannot save. Sign in to a company first." }); return; }
      setBusy(true); setStatus(null);
      // Idempotent replace: the table has no unique constraint on
      // (company_id, period_end, department), so an upsert/onConflict would
      // error. Instead clear this company's rows for the uploaded period_ends,
      // then insert — re-uploading a period replaces it rather than duplicating.
      const periods = Array.from(new Set(preview.map((r) => r.period_end).filter(Boolean)));
      db.from("payroll_summary").delete().eq("company_id", scopedCompanyId).in("period_end", periods)
        .then(({ error }) => {
          if (error) throw new Error(error.message);
          return db.from("payroll_summary").insert(preview);
        })
        .then(({ error }) => {
          setBusy(false);
          if (error) { setStatus({ kind: "err", msg: "Save failed: " + (error.message || "database error") }); return; }
          setStatus({ kind: "ok", msg: "Saved " + preview.length + " row(s)." });
          window.logAuditEvent && window.logAuditEvent("data_upload", { category: "data", page: "payroll", detail: "Payroll data uploaded: " + preview.length + " rows" });
          setPreview(null); setFileName("");
          if (onInserted) onInserted();
        })
        .catch((err) => { setBusy(false); setStatus({ kind: "err", msg: "Save failed: " + (err && err.message ? err.message : "database error") }); });
    }

    const M = (v) => K.moneyStr(v, { compact: true });
    return h(K.Card, { title: "UPLOAD PAYROLL CSV", sub: "Columns: period_end, department, headcount_current, headcount_prior, fte, turnover_pct, gross_salary, employer_contributions, bonus, other_costs" },
      h("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 12, marginBottom: 12 } },
        h("button", { onClick: downloadPayrollTemplate, style: { display: "inline-flex", alignItems: "center", gap: 6, cursor: "pointer", background: "#fff", color: "#1C4ED8", fontWeight: 700, fontSize: 12.5, padding: "7px 14px", borderRadius: 8, border: "1px solid #1C4ED8" } }, "⭳ Download sample CSV"),
        h("input", { type: "file", accept: ".csv", onChange: handleFile, style: { fontSize: 12.5, color: "#475569" } }),
        fileName ? h("span", { style: { fontSize: 12, color: "#6475a0" } }, fileName) : null),
      status ? h("div", { style: { fontSize: 12.5, fontWeight: 600, marginBottom: 10, color: status.kind === "ok" ? "#18a867" : "#d94f47" } }, (status.kind === "ok" ? "✓ " : "⚠ ") + status.msg) : null,
      preview ? h("div", null,
        h("div", { style: { fontSize: 12, color: "#6475a0", marginBottom: 8 } }, "Preview — " + preview.length + " row(s). total_payroll_cost and avg_cost_per_fte are computed on save."),
        h("div", { style: { overflowX: "auto", border: "1px solid #E4E8F0", borderRadius: 8 } },
          h("table", { className: "pa-table", style: { width: "100%", fontSize: 11.5 } },
            h("thead", null, h("tr", null,
              ["Period", "Department", "HC Cur", "HC Prior", "FTE", "Turnover %", "Gross", "Contrib", "Bonus", "Other", "Total"].map((c, i) =>
                h("th", { key: i, className: i >= 2 ? "num" : null }, c)))),
            h("tbody", null, preview.slice(0, 12).map((r, i) => h("tr", { key: i },
              h("td", null, r.period_end),
              h("td", null, r.department),
              h("td", { className: "num" }, r.headcount_current),
              h("td", { className: "num" }, r.headcount_prior),
              h("td", { className: "num" }, r.fte),
              h("td", { className: "num" }, r.turnover_pct.toFixed(1) + "%"),
              h("td", { className: "num" }, M(r.gross_salary)),
              h("td", { className: "num" }, M(r.employer_contributions)),
              h("td", { className: "num" }, M(r.bonus)),
              h("td", { className: "num" }, M(r.other_costs)),
              h("td", { className: "num", style: { fontWeight: 700 } }, M(r.total_payroll_cost))))))),
        preview.length > 12 ? h("div", { style: { fontSize: 11, color: "#94a3b8", marginTop: 6 } }, "+ " + (preview.length - 12) + " more row(s) not shown") : null,
        h("div", { style: { marginTop: 12 } },
          h("button", { onClick: confirmInsert, disabled: busy,
            style: { padding: "8px 16px", fontSize: 13, fontWeight: 700, borderRadius: 8, border: "1px solid #1C4ED8", background: busy ? "#94a3b8" : "#1C4ED8", color: "#fff", cursor: busy ? "default" : "pointer" } },
            busy ? "Saving…" : "Confirm & Save")))
        : h("div", { style: { fontSize: 12.5, color: "#6475a0" } }, "Choose a CSV file to preview the parsed rows before saving."));
  }

  // ── main page ──────────────────────────────────────────────────────────────
  function Page(props) {
    const K = window.PerduraPageKit;
    if (!K) return h("div", { className: "pa-page" }, "Loading…");
    const { data, scopedCompanyId } = props || {};
    const M = (v) => K.moneyStr(v, { compact: true });
    const plH = (data && (data.plHistory || data.pl)) || { labels: [], years: [], revenue: [] };

    const [rows, setRows] = useState(null); // null = loading
    const [reloadKey, setReloadKey] = useState(0);

    useEffect(() => {
      let cancelled = false;
      const db = window.supabaseClient;
      if (!db || !scopedCompanyId) { setRows([]); return; }
      setRows(null);
      db.from("payroll_summary").select("*").eq("company_id", scopedCompanyId).order("period_end", { ascending: true })
        .then(({ data: d }) => { if (!cancelled) setRows(d || []); }, () => { if (!cancelled) setRows([]); });
      return () => { cancelled = true; };
    }, [scopedCompanyId, reloadKey]);

    const reload = () => setReloadKey((k) => k + 1);

    // Distinct period_ends ascending, latest snapshot rows, and per-period aggregates.
    const model = useMemo(() => {
      const all = rows || [];
      const periods = Array.from(new Set(all.map((r) => r.period_end).filter(Boolean))).sort();
      const latest = periods.length ? periods[periods.length - 1] : null;
      const latestRows = latest ? all.filter((r) => r.period_end === latest) : [];

      // Per-period aggregate (summed across departments) for trend charts.
      const byPeriod = periods.map((pe) => {
        const pr = all.filter((r) => r.period_end === pe);
        const sum = (k) => pr.reduce((s, r) => s + nz(r[k]), 0);
        const totalCost = pr.reduce((s, r) => s + (r.total_payroll_cost != null ? nz(r.total_payroll_cost) : nz(r.gross_salary) + nz(r.employer_contributions) + nz(r.bonus) + nz(r.other_costs)), 0);
        return {
          period_end: pe,
          headcount: sum("headcount_current"),
          fte: sum("fte"),
          gross_salary: sum("gross_salary"),
          employer_contributions: sum("employer_contributions"),
          bonus: sum("bonus"),
          other_costs: sum("other_costs"),
          total: totalCost,
        };
      });
      return { all, periods, latest, latestRows, byPeriod };
    }, [rows]);

    // Publish latest-snapshot totals for any cross-page consumer. Note: the KPI
    // Scorecard already reads payroll_summary directly from the DB (so headcount
    // flows the moment a CSV is saved); this global simply mirrors the page's
    // computed totals for lightweight consumers that prefer the in-memory value.
    useEffect(() => {
      const lr = model.latestRows;
      if (!lr.length) { window._payrollTotals = null; return; }
      const sum = (k) => lr.reduce((s, r) => s + nz(r[k]), 0);
      const payrollTotal = lr.reduce((s, r) => s + (r.total_payroll_cost != null ? nz(r.total_payroll_cost) : nz(r.gross_salary) + nz(r.employer_contributions) + nz(r.bonus) + nz(r.other_costs)), 0);
      window._payrollTotals = { headcount: sum("headcount_current"), fte: sum("fte"), payroll_total: payrollTotal, period_end: model.latest };
    }, [model]);

    // Monthly revenue keyed by YYYYMM from plHistory, for payroll % of revenue.
    const revByYm = useMemo(() => {
      const m = {}; const labels = plH.labels || [], years = plH.years || [], rev = plH.revenue || [];
      for (let i = 0; i < labels.length; i++) {
        const mi = MON3.indexOf(String(labels[i] || "").slice(0, 3).toLowerCase());
        if (mi >= 0 && years[i]) m[years[i] * 100 + (mi + 1)] = nz(rev[i]);
      }
      return m;
    }, [plH]);
    const monthlyRevFor = (pe) => { if (!pe) return null; const d = new Date(pe); if (isNaN(+d)) return null; const k = d.getUTCFullYear() * 100 + (d.getUTCMonth() + 1); return revByYm[k] != null ? revByYm[k] : null; };
    const ltmRevenue = (plH.revenue || []).slice(-12).reduce((s, v) => s + nz(v), 0);

    // ── loading / empty states ────────────────────────────────────────────────
    if (rows === null) {
      return h(K.Shell, { hero: { eyebrow: "OPERATIONS", title: "Payroll Summary", subtitle: "Headcount & cost intelligence" } },
        h(K.Card, { title: "Payroll Summary" }, h("div", { style: { padding: 18, fontSize: 13, color: "#6475a0" } }, "Loading payroll data…")));
    }
    if (!model.all.length) {
      return h(K.Shell, { hero: { eyebrow: "OPERATIONS", title: "Payroll Summary", subtitle: "Headcount & cost intelligence" } },
        h(K.Card, { title: "Payroll Summary" },
          h("div", { style: { padding: 20, fontSize: 13.5, color: "#6475a0", lineHeight: 1.6 } },
            h("b", { style: { color: "#0d2040" } }, "No payroll data — upload a CSV to begin. "),
            "This page reads from your payroll_summary records (headcount, FTE and cost by department for each period-end). Once data is present it builds the headcount-by-department roster, the payroll cost breakdown, and 12-month headcount and cost trends. Use the upload card below to seed the table.")),
        h(UploadCard, { K: K, scopedCompanyId: scopedCompanyId, onInserted: reload }));
    }

    // ── latest-snapshot aggregates → KPI tiles ────────────────────────────────
    const lr = model.latestRows;
    const sumLatest = (k) => lr.reduce((s, r) => s + nz(r[k]), 0);
    const totalHC = sumLatest("headcount_current");
    const totalHCPrior = sumLatest("headcount_prior");
    const totalFTE = sumLatest("fte");
    const totalCost = lr.reduce((s, r) => s + (r.total_payroll_cost != null ? nz(r.total_payroll_cost) : nz(r.gross_salary) + nz(r.employer_contributions) + nz(r.bonus) + nz(r.other_costs)), 0);
    const avgCostPerFte = totalFTE ? totalCost / totalFTE : null;
    // Payroll % of revenue: prefer the snapshot month's revenue, else LTM revenue.
    const monthRev = monthlyRevFor(model.latest);
    const revBasis = monthRev != null && monthRev > 0 ? monthRev : (ltmRevenue > 0 ? ltmRevenue : null);
    const payrollPctRev = revBasis ? totalCost / revBasis * 100 : null;
    const revBasisLabel = monthRev != null && monthRev > 0 ? "of latest-month revenue" : (ltmRevenue > 0 ? "of LTM revenue" : "revenue n/a");

    const kpis = [
      { label: "Total Headcount", value: totalHC ? String(totalHC) : "—", valueColor: "navy", sub: "as of " + periodLabel(model.latest) },
      { label: "FTE", value: totalFTE ? (Math.round(totalFTE * 10) / 10).toString() : "—", valueColor: "blue", sub: "full-time equivalents" },
      { label: "Total Payroll Cost", value: M(totalCost), valueColor: "navy", sub: "latest period · all departments" },
      { label: "Payroll % of Revenue", value: payrollPctRev == null ? "—" : payrollPctRev.toFixed(1) + "%", valueColor: payrollPctRev == null ? "navy" : payrollPctRev > 45 ? "red" : payrollPctRev > 30 ? "amber" : "green", sub: revBasisLabel },
      { label: "Avg Cost per FTE", value: avgCostPerFte == null ? "—" : M(avgCostPerFte), valueColor: "teal", sub: "annualized loaded cost" },
    ];

    // ── Section 1: headcount by department ────────────────────────────────────
    const deptRows = lr.slice().sort((a, b) => nz(b.headcount_current) - nz(a.headcount_current));
    const totTenureW = lr.reduce((s, r) => s + nz(r.avg_tenure_years) * nz(r.fte), 0);
    const avgTenureOverall = totalFTE ? totTenureW / totalFTE : null;
    const totTurnoverW = lr.reduce((s, r) => s + nz(r.turnover_pct) * nz(r.headcount_current), 0);
    const avgTurnoverOverall = totalHC ? totTurnoverW / totalHC : null;

    const deltaCell = (cur, prior) => {
      const d = nz(cur) - nz(prior);
      const color = d > 0 ? "#18a867" : d < 0 ? "#d94f47" : "#94a3b8";
      return h("td", { className: "num", style: { fontWeight: 700, color: color } }, d > 0 ? "+" + d : d < 0 ? "−" + Math.abs(d) : "0");
    };
    // Per-department total cost & cost/FTE (fall back to component sum when the
    // stored total is absent).
    const deptTotal = (r) => r.total_payroll_cost != null ? nz(r.total_payroll_cost) : nz(r.gross_salary) + nz(r.employer_contributions) + nz(r.bonus) + nz(r.other_costs);
    const deptPerFte = (r) => { const t = deptTotal(r), f = nz(r.fte); return f ? t / f : null; };

    const headcountSection = h(K.Card, { title: "HEADCOUNT BY DEPARTMENT", sub: model.latest ? "Snapshot · " + periodLabel(model.latest) : null, padding: 0 },
      h("div", { style: { overflowX: "auto" } },
        h("table", { className: "pa-table", style: { width: "100%" } },
          h("thead", null, h("tr", null,
            h("th", null, "Department"),
            h("th", { className: "num" }, "HC Current"),
            h("th", { className: "num" }, "HC Prior"),
            h("th", { className: "num" }, "Δ HC"),
            h("th", { className: "num" }, "FTE"),
            h("th", { className: "num" }, "Avg Tenure"),
            h("th", { className: "num" }, "Turnover %"),
            h("th", { className: "num" }, "Total Cost"),
            h("th", { className: "num" }, "Avg/FTE"),
            h("th", null, "Status"))),
          h("tbody", null,
            deptRows.map((r, i) => h("tr", { key: i },
              h("td", { style: { fontWeight: 600 } }, r.department || "—"),
              h("td", { className: "num" }, Math.round(nz(r.headcount_current))),
              h("td", { className: "num" }, Math.round(nz(r.headcount_prior))),
              deltaCell(r.headcount_current, r.headcount_prior),
              h("td", { className: "num" }, (Math.round(nz(r.fte) * 10) / 10)),
              h("td", { className: "num" }, r.avg_tenure_years != null ? nz(r.avg_tenure_years).toFixed(1) + "y" : "—"),
              h("td", { className: "num" }, r.turnover_pct != null ? nz(r.turnover_pct).toFixed(1) + "%" : "—"),
              h("td", { className: "num", style: { fontWeight: 600 } }, M(deptTotal(r))),
              h("td", { className: "num" }, deptPerFte(r) == null ? "—" : M(deptPerFte(r))),
              h("td", null, turnoverDot(r.turnover_pct != null ? nz(r.turnover_pct) : null)))),
            h("tr", { style: { borderTop: "2px solid #0d2040", background: "rgba(13,32,64,.05)", fontWeight: 800 } },
              h("td", { style: { fontWeight: 800, color: "#0d2040" } }, "TOTAL"),
              h("td", { className: "num", style: { fontWeight: 800 } }, totalHC),
              h("td", { className: "num", style: { fontWeight: 800 } }, totalHCPrior),
              deltaCell(totalHC, totalHCPrior),
              h("td", { className: "num", style: { fontWeight: 800 } }, Math.round(totalFTE * 10) / 10),
              h("td", { className: "num", style: { fontWeight: 800 } }, avgTenureOverall == null ? "—" : avgTenureOverall.toFixed(1) + "y"),
              h("td", { className: "num", style: { fontWeight: 800 } }, avgTurnoverOverall == null ? "—" : avgTurnoverOverall.toFixed(1) + "%"),
              h("td", { className: "num", style: { fontWeight: 800 } }, M(totalCost)),
              h("td", { className: "num", style: { fontWeight: 800 } }, avgCostPerFte == null ? "—" : M(avgCostPerFte)),
              h("td", null, turnoverDot(avgTurnoverOverall)))))));

    // ── Section 2: payroll cost breakdown ─────────────────────────────────────
    // FY YTD = sum across period_ends within the latest snapshot's fiscal year.
    const fy = fiscalYearOf(model.latest);
    const fyPeriods = model.byPeriod.filter((p) => fiscalYearOf(p.period_end) === fy);
    const sumFy = (k) => fyPeriods.reduce((s, p) => s + nz(p[k]), 0);
    const curOf = (k) => lr.reduce((s, r) => s + nz(r[k]), 0);
    // FY budget: only if a budget column genuinely exists on the rows — else "—".
    const haveBudget = (model.all || []).some((r) => r.fy_budget != null || r.budget != null);
    const budgetOf = (k) => haveBudget ? (model.all || []).filter((r) => r.period_end === model.latest).reduce((s, r) => s + (nz(r[k + "_budget"]) || 0), 0) : null;

    const costComponents = COST_KEYS.map((c) => {
      const cur = curOf(c.key), ytd = sumFy(c.key);
      const budget = budgetOf(c.key);
      const varPct = (budget != null && budget !== 0) ? (ytd - budget) / budget * 100 : null;
      const perFte = totalFTE ? cur / totalFTE : null;
      return { label: c.label, cur, ytd, budget, varPct, perFte };
    });
    const totCur = costComponents.reduce((s, c) => s + c.cur, 0);
    const totYtd = costComponents.reduce((s, c) => s + c.ytd, 0);
    const totBudget = haveBudget ? costComponents.reduce((s, c) => s + (c.budget || 0), 0) : null;
    const totVarPct = (totBudget != null && totBudget !== 0) ? (totYtd - totBudget) / totBudget * 100 : null;
    const totPerFte = totalFTE ? totCur / totalFTE : null;

    const costRow = (c, bold) => h("tr", { key: c.label, style: bold ? { borderTop: "2px solid #0d2040", background: "rgba(13,32,64,.05)", fontWeight: 800 } : null },
      h("td", { style: { fontWeight: bold ? 800 : 600, color: bold ? "#0d2040" : "#1a2540" } }, c.label),
      h("td", { className: "num", style: { fontWeight: bold ? 800 : 400 } }, M(c.cur)),
      h("td", { className: "num", style: { fontWeight: bold ? 800 : 400 } }, totCur ? (c.cur / totCur * 100).toFixed(1) + "%" : "—"),
      h("td", { className: "num" }, M(c.ytd)),
      h("td", { className: "num" }, c.budget == null ? "—" : M(c.budget)),
      h("td", { className: "num", style: { color: c.varPct == null ? "#94a3b8" : c.varPct > 0 ? "#d94f47" : "#18a867", fontWeight: 700 } }, c.varPct == null ? "—" : (c.varPct > 0 ? "+" : "") + c.varPct.toFixed(1) + "%"),
      h("td", { className: "num" }, c.perFte == null ? "—" : M(c.perFte)));

    const costSection = h(K.Card, { title: "PAYROLL COST BREAKDOWN", sub: model.latest ? "Current period · " + periodLabel(model.latest) + (fy ? " · FY " + fy + " YTD" : "") : null, padding: 0 },
      h("div", { style: { overflowX: "auto" } },
        h("table", { className: "pa-table", style: { width: "100%" } },
          h("thead", null, h("tr", null,
            h("th", null, "Cost Component"),
            h("th", { className: "num" }, "Current Period"),
            h("th", { className: "num" }, "% of Total"),
            h("th", { className: "num" }, "FY YTD"),
            h("th", { className: "num" }, "FY Budget"),
            h("th", { className: "num" }, "Var %"),
            h("th", { className: "num" }, "Avg/FTE"))),
          h("tbody", null,
            costComponents.map((c) => costRow(c, false)),
            costRow({ label: "TOTAL", cur: totCur, ytd: totYtd, budget: totBudget, varPct: totVarPct, perFte: totPerFte }, true)))));

    // ── Section 3: headcount trend (HC line + FTE line) ───────────────────────
    const trendPeriods = model.byPeriod.slice(-12);
    const trendLabels = trendPeriods.map((p) => periodLabel(p.period_end));
    const hcTrend = trendPeriods.length >= 2 ? h(React.Fragment, null,
      h(K.MultiSeriesBarChart, { months: trendLabels, height: 200, series: [
        { type: "line", color: "#0d2040", data: trendPeriods.map((p) => p.headcount) },
        { type: "dashed-line", color: "#009fa0", data: trendPeriods.map((p) => Math.round(p.fte * 10) / 10) },
      ] }),
      h("div", { style: { display: "flex", gap: 16, marginTop: 8, fontSize: 10.5, color: "#475569", fontWeight: 600 } },
        h("span", null, h("span", { style: { display: "inline-block", width: 14, height: 3, background: "#0d2040", borderRadius: 2, marginRight: 5, verticalAlign: "middle" } }), "Headcount"),
        h("span", null, h("span", { style: { display: "inline-block", width: 14, borderTop: "2px dashed #009fa0", marginRight: 5, verticalAlign: "middle" } }), "FTE")),
      h(K.KeyTakeaway, { text: "Headcount is <b>" + totalHC + "</b> (" + (Math.round(totalFTE * 10) / 10) + " FTE) at " + periodLabel(model.latest) + "." + (totalHC - totalHCPrior !== 0 ? " Net " + (totalHC - totalHCPrior > 0 ? "added " + (totalHC - totalHCPrior) : "reduced " + Math.abs(totalHC - totalHCPrior)) + " head(s) vs the prior period." : " Headcount held flat vs the prior period.") }))
      : h("div", { style: { padding: 16, fontSize: 13, color: "#6475a0" } }, h("b", null, "Building headcount history — "), "the trend line populates once at least two period-ends are present.");
    const headcountTrend = h(K.Card, { title: "HEADCOUNT TREND", sub: "Total headcount & FTE by period-end (last 12)" }, hcTrend);

    // ── Section 4: payroll cost trend (stacked bars + revenue overlay) ────────
    const costTrendPeriods = model.byPeriod.slice(-12);
    const ctLabels = costTrendPeriods.map((p) => periodLabel(p.period_end));
    const revOverlay = costTrendPeriods.map((p) => { const r = monthlyRevFor(p.period_end); return r != null && r > 0 ? p.total / r * 100 : null; });
    const haveRevOverlay = revOverlay.some((v) => v != null);
    // Stacked bars: PageKit MultiSeriesBarChart groups bars side-by-side, so we
    // render the four cost components as grouped bars and overlay payroll % of
    // revenue as a secondary line (own axis).
    const costSeries = COST_KEYS.map((c) => ({ type: "bar", color: c.color, data: costTrendPeriods.map((p) => nz(p[c.key])) }));
    if (haveRevOverlay) costSeries.push({ type: "line", color: "#d94f47", secondary: true, data: revOverlay });

    const costTrend = costTrendPeriods.length >= 1 ? h(React.Fragment, null,
      h(K.MultiSeriesBarChart, { months: ctLabels, height: 210, series: costSeries }),
      h("div", { style: { display: "flex", flexWrap: "wrap", gap: 14, marginTop: 8, fontSize: 10.5, color: "#475569", fontWeight: 600 } },
        COST_KEYS.map((c, i) => h("span", { key: i }, h("span", { style: { display: "inline-block", width: 10, height: 10, background: c.color, borderRadius: 2, marginRight: 5, verticalAlign: "middle" } }), c.label)),
        haveRevOverlay ? h("span", null, h("span", { style: { display: "inline-block", width: 14, borderTop: "2px solid #d94f47", marginRight: 5, verticalAlign: "middle" } }), "Payroll % of Revenue") : null),
      payrollPctRev != null ? h(K.KeyTakeaway, { text: "Latest payroll cost is <b>" + M(totalCost) + "</b>, about <b>" + payrollPctRev.toFixed(1) + "%</b> " + revBasisLabel + "." + (!haveRevOverlay ? " Connect P&L revenue history to chart the payroll-%-of-revenue trend." : "") }) : null)
      : h("div", { style: { padding: 16, fontSize: 13, color: "#6475a0" } }, "Awaiting cost history.");
    const costTrendCard = h(K.Card, { title: "PAYROLL COST TREND", sub: "Cost components by period-end" + (haveRevOverlay ? " · revenue overlay" : "") }, costTrend);

    // ── render ────────────────────────────────────────────────────────────────
    return h(K.Shell, { hero: {
      eyebrow: "OPERATIONS", title: "Payroll Summary",
      subtitle: "Headcount & cost intelligence · " + (model.latest ? "as of " + periodLabel(model.latest) : "no period"),
    } },
      h("div", { className: "pa-kpi-strip pa-kpi-strip-5" }, kpis.map((k, i) => h(K.Kpi, Object.assign({ key: i, animDelay: i * 0.05, onClick: () => window.__perduraSetPage && window.__perduraSetPage("opex_intelligence") }, k)))),
      headcountSection,
      costSection,
      headcountTrend,
      costTrendCard,
      h(UploadCard, { K: K, scopedCompanyId: scopedCompanyId, onInserted: reload }));
  }

  window.PayrollPage = Page;
})();
