// FPnADriversTab — window.FPnADriversTab — the business-driver grid inside FP&A.
//
// A P&L line moving is not the story. The story is WHY it moved:
//
//     "Salaries up 12% — but headcount is up 15%, so cost per head is DOWN 3%."
//
// You cannot tell that from the ledger, because the ledger does not know how many
// people you employ. This grid is where the owner supplies the denominators the GL
// cannot know — and only those:
//
//   MANUAL (stored in company_drivers, migration 79)
//     headcount       average employees on payroll that month
//     location_count  stores / locations trading that month
//     sqft            occupied square footage that month
//
//   AUTO-DERIVED (never stored — computed live from data we already hold)
//     Active SKUs / channels     ← sales_line_items
//     Active customers           ← ar_invoices
//     GL transactions            ← gl_transactions
//
// Auto drivers are deliberately NOT in this grid. Storing a copy of something the
// subledger already knows creates a second source of truth that silently drifts.
// They light up on their own the moment the tenant has the subledger for them.
//
// HONESTY: a blank cell is MISSING, not zero. It is not saved, and downstream the
// normalized figure for that month renders blank — never 0 (which would read as an
// infinite cost per head) and never interpolated from its neighbours. A driver the
// owner has not entered is simply not offered as a "Normalize by" option anywhere.
//
// The driver list is DATA (K.DRIVER_REGISTRY), not hardcoded here — this component
// renders whatever manual drivers the registry declares, so adding one is a
// registry entry and not a change to this file.

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

  const MONS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

  // Parse an input cell. "" / whitespace / junk → null (MISSING), never 0.
  // A driver is a denominator; a 0 that slipped through here would divide a line
  // into infinity. The table CHECKs value > 0 as a second line of defence.
  const cellNum = (v) => {
    const s = String(v == null ? "" : v).trim();
    if (!s) return null;
    const n = Number(s.replace(/[^0-9.]/g, ""));
    return Number.isFinite(n) && n > 0 ? n : null;
  };

  function Tab(props) {
    const { K, tenantId, data, drivers, onSaved } = props;
    const MONO = K.MONO, INK = K.INK, MUTE = K.MUTE, LINE = K.LINE;

    const manualDefs = (K.DRIVER_REGISTRY || []).filter((d) => d.source === "manual");
    const autoDefs = (K.DRIVER_REGISTRY || []).filter((d) => d.source === "auto");

    // Years the ledger covers, newest first — the years worth entering drivers for.
    const years = useMemo(() => {
      const s = new Set();
      for (const t of ((data && data.txns) || [])) {
        const y = String(t.posted_date || "").slice(0, 4);
        if (/^\d{4}$/.test(y)) s.add(Number(y));
      }
      const list = Array.from(s).sort((a, b) => b - a);
      return list.length ? list : [new Date().getFullYear()];
    }, [data && data.txns]);

    const [fy, setFy] = useState(years[0]);
    const [grid, setGrid] = useState({});      // driver_key -> [12] strings
    const [saving, setSaving] = useState(false);
    const [msg, setMsg] = useState(null);

    // Hydrate the grid from what is already saved, so re-opening a year shows the
    // owner what they entered rather than a blank slate they might overwrite.
    useEffect(() => {
      const g = {};
      manualDefs.forEach((d) => {
        g[d.key] = Array.from({ length: 12 }, (_, i) => {
          const mk = fy + "-" + String(i + 1).padStart(2, "0");
          const v = drivers && drivers.manual && drivers.manual[d.key] && drivers.manual[d.key][mk];
          return v == null ? "" : String(v);
        });
      });
      setGrid(g);
      setMsg(null);
    }, [fy, drivers && drivers.manual]);

    const setCell = (key, i, v) => setGrid((g) => {
      const row = (g[key] || Array(12).fill("")).slice();
      row[i] = v;
      return Object.assign({}, g, { [key]: row });
    });
    // Fill the rest of the year from the first value entered — most owners' headcount
    // and location counts are flat, and typing the same number 12 times is friction.
    const fillAcross = (key) => setGrid((g) => {
      const row = (g[key] || Array(12).fill("")).slice();
      const first = row.filter((v) => cellNum(v) != null)[0];
      if (first == null) return g;
      return Object.assign({}, g, { [key]: row.map((v) => (cellNum(v) == null ? first : v)) });
    });

    const entered = useMemo(() => {
      let n = 0;
      Object.keys(grid).forEach((k) => (grid[k] || []).forEach((v) => { if (cellNum(v) != null) n++; }));
      return n;
    }, [grid]);

    const save = async () => {
      const db = window.supabaseClient;
      if (!db || !tenantId) { setMsg({ bad: true, t: "Not connected — can't save." }); return; }
      setSaving(true);
      setMsg(null);
      try {
        const rows = [];
        manualDefs.forEach((d) => {
          (grid[d.key] || []).forEach((v, i) => {
            const n = cellNum(v);
            if (n == null) return;                 // blank = MISSING; simply not written
            rows.push({
              company_id: tenantId, driver_key: d.key,
              period_month: fy + "-" + String(i + 1).padStart(2, "0") + "-01",
              value: n,
            });
          });
        });

        // Clear this year's manual rows, then insert what the grid now holds. Scoped
        // to THIS company × THIS year × the MANUAL keys only — so clearing a cell
        // actually clears it (an upsert alone would leave the old value behind), and
        // no other year, tenant or auto driver is touched.
        const { error: dErr } = await db.from("company_drivers")
          .delete()
          .eq("company_id", tenantId)
          .in("driver_key", manualDefs.map((d) => d.key))
          .gte("period_month", fy + "-01-01")
          .lte("period_month", fy + "-12-01");
        if (dErr) throw new Error(dErr.message);

        if (rows.length) {
          const { error: iErr } = await db.from("company_drivers").insert(rows);
          if (iErr) throw new Error(iErr.message);
        }
        setMsg({ bad: false, t: rows.length
          ? "Saved " + rows.length + " driver value" + (rows.length === 1 ? "" : "s") + " for " + fy + ". They are now available as “Normalize by” options."
          : "Cleared all driver values for " + fy + "." });
        if (onSaved) onSaved();
      } catch (err) {
        // Fail closed and say why. Migration 79 may not be applied yet, in which
        // case the table does not exist — say that plainly rather than "failed".
        const m = String(err && err.message || err);
        setMsg({ bad: true, t: /relation .*company_drivers.* does not exist|schema cache/i.test(m)
          ? "Can't save — the company_drivers table isn't in the database yet (migration 79 has not been applied). Nothing was written."
          : "Could not save: " + m });
      } finally {
        setSaving(false);
      }
    };

    const th = (txt, align) => h("th", { key: txt, style: { textAlign: align || "right", padding: "9px 10px", fontSize: 9.5,
      fontWeight: 700, letterSpacing: 0.5, textTransform: "uppercase", color: "rgba(255,255,255,.85)", whiteSpace: "nowrap" } }, txt);

    const btn = (label, onClick, primary, disabled) => h("button", {
      onClick: disabled ? undefined : onClick, disabled: !!disabled,
      style: { padding: "8px 16px", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: disabled ? "not-allowed" : "pointer",
        border: primary ? "none" : "1px solid " + LINE, background: primary ? (disabled ? "#9aacbf" : "#1C4ED8") : "#fff",
        color: primary ? "#fff" : "#4B5563", opacity: disabled ? 0.7 : 1 },
    }, label);

    // What the tenant's own data already supports, with no entry at all.
    const autoLive = autoDefs.map((d) => {
      // Probe the trailing 12 months of the selected year — a driver is "live" if
      // the subledger behind it actually has activity.
      const range = { start: new Date(fy, 0, 1), end: new Date(fy, 11, 31) };
      const w = K.driverWindowValue(d.key, data, {}, range);
      return Object.assign({}, d, { live: w.value != null && w.value > 0, value: w.value });
    });

    return h("div", null,
      h(K.Card, {
        title: "BUSINESS DRIVERS — " + fy,
        sub: "The denominators the ledger cannot know. Enter a month and every P&L line can be shown per employee / per location / per sq ft — and a variance split into volume vs rate. A blank cell means MISSING, not zero: it is never saved and never fabricated downstream.",
        padding: 0,
      },
        h("div", { style: { display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap", padding: "12px 16px", borderBottom: "1px solid " + LINE } },
          h("span", { style: { fontSize: 10, fontWeight: 800, color: MUTE, textTransform: "uppercase", letterSpacing: ".07em" } }, "Fiscal year"),
          h("select", {
            value: fy, onChange: (e) => setFy(Number(e.target.value)),
            style: { padding: "5px 10px", borderRadius: 999, border: "1px solid " + LINE, background: "#F7F8FB",
              fontSize: 12, fontWeight: 700, color: "#4B5563", cursor: "pointer" },
          }, years.map((y) => h("option", { key: y, value: y }, y))),
          h("div", { style: { flex: 1 } }),
          h("span", { style: { fontSize: 11.5, color: MUTE } }, entered + " of " + (manualDefs.length * 12) + " cells filled"),
          btn(saving ? "Saving…" : "Save drivers", save, true, saving)),

        h("div", { style: { overflowX: "auto" } },
          h("table", { className: "pa-table", style: { width: "100%", minWidth: 900 } },
            h("thead", null, h("tr", { style: { background: "#0d2040" } },
              th("Driver", "left"),
              MONS.map((m) => th(m)),
              th(""))),
            h("tbody", null, manualDefs.map((d) => h("tr", { key: d.key, style: { borderTop: "1px solid #f1f3f7" } },
              h("td", { style: { padding: "8px 12px" } },
                h("div", { style: { fontSize: 12.5, fontWeight: 700, color: INK } }, d.label),
                h("div", { style: { fontSize: 10.5, color: MUTE, marginTop: 2 } }, d.hint)),
              (grid[d.key] || Array(12).fill("")).map((v, i) => h("td", { key: i, style: { padding: "4px 3px" } },
                h("input", {
                  type: "number", min: "0", step: "any", value: v,
                  placeholder: "—",
                  onChange: (e) => setCell(d.key, i, e.target.value),
                  title: cellNum(v) == null ? "Blank = not known. Nothing is saved and no per-unit figure is computed for this month." : d.label + " for " + MONS[i] + " " + fy,
                  style: { width: 58, padding: "5px 6px", border: "1px solid " + LINE, borderRadius: 6, fontSize: 11.5,
                    fontFamily: MONO, textAlign: "right", color: INK, background: cellNum(v) == null ? "#FBFCFE" : "#fff" },
                }))),
              h("td", { style: { padding: "4px 8px", textAlign: "right" } },
                h("button", {
                  onClick: () => fillAcross(d.key),
                  title: "Copy the first value entered across the rest of the year",
                  style: { padding: "4px 8px", fontSize: 10.5, fontWeight: 700, border: "1px solid " + LINE,
                    borderRadius: 6, background: "#fff", color: "#1C4ED8", cursor: "pointer", whiteSpace: "nowrap" },
                }, "Fill →"))))))),

        msg ? h("div", { style: { margin: "12px 16px", padding: "10px 14px", borderRadius: 8, fontSize: 12.5, lineHeight: 1.6,
          background: msg.bad ? "rgba(217,79,71,.08)" : "rgba(24,168,103,.08)",
          border: "1px solid " + (msg.bad ? "rgba(217,79,71,.3)" : "rgba(24,168,103,.3)"),
          color: msg.bad ? "#991b1b" : "#065f46" } }, msg.t) : null),

      h("div", { style: { height: 18 } }),

      // Auto-derived drivers — shown so the owner can SEE what they already have
      // without entering anything, and see honestly what they don't.
      h(K.Card, {
        title: "AUTO-DERIVED DRIVERS",
        sub: "Computed live from your own subledgers — nothing to enter, nothing stored. A driver is offered downstream only where the data behind it actually exists.",
      },
        h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(210px,1fr))", gap: 12 } },
          autoLive.map((d) => h("div", { key: d.key, style: { border: "1px solid " + LINE, borderRadius: 10, padding: "12px 14px",
            background: d.live ? "#fff" : "#FBFCFE" } },
            h("div", { style: { fontSize: 11, fontWeight: 800, color: MUTE, textTransform: "uppercase", letterSpacing: 0.4 } }, d.label),
            h("div", { style: { fontSize: 22, fontWeight: 800, fontFamily: MONO, color: d.live ? INK : "#c8d6e8", marginTop: 4 } },
              d.live ? Math.round(d.value).toLocaleString() : "—"),
            h("div", { style: { fontSize: 10.5, color: MUTE, marginTop: 4, lineHeight: 1.5 } },
              d.live ? (d.hint + " · " + fy)
                     : ("Awaiting data — " + d.table + " has no rows for " + fy + ", so this driver isn't offered."))))))
    );
  }

  window.FPnADriversTab = Tab;
})();
