/* FP&A Copilot — main variance dashboard (real API data) */
const { useState: useStateDash, useEffect: useEffectDash, useRef: useRefDash } = React;

/* ── ClassificationDrawer ───────────────────────────────────────────────────
   Review + correct how each revenue account is mapped to an NHS income stream.
   Overrides are per-account and persist server-side, so benchmarks and the
   income breakdown respect the correction on every subsequent load. */
function ClassificationDrawer({ sessionId, onClose, onSaved }) {
  const { Icon } = window;
  const [payload, setPayload] = useStateDash(null);
  const [status, setStatus]   = useStateDash("loading"); // loading | ready | error
  const [savingAcct, setSavingAcct] = useStateDash(null);
  const [tab, setTab] = useStateDash("income"); // income | workforce
  const fmt = v => window.fmtCurrency(v, { compact: true });

  const load = React.useCallback(() => {
    fetch(apiUrl(`/api/session/${sessionId}/classifications`))
      .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
      .then(d => { setPayload(d); setStatus("ready"); })
      .catch(() => setStatus("error"));
  }, [sessionId]);

  useEffectDash(() => { load(); }, [load]);
  useEffectDash(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [onClose]);

  // Save either an income-stream override (kind="income") or a workforce-role
  // override (kind="workforce"); both refresh the drawer + dashboard.
  async function saveOverride(kind, accountName, value) {
    setSavingAcct(accountName);
    try {
      const path = kind === "workforce"
        ? `/api/session/${sessionId}/workforce-classifications`
        : `/api/session/${sessionId}/classifications`;
      const body = kind === "workforce"
        ? { account_name: accountName, role: value }
        : { account_name: accountName, stream_key: value };
      const r = await fetch(apiUrl(path), {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      if (r.ok) { setPayload(await r.json()); onSaved && onSaved(); }
    } finally { setSavingAcct(null); }
  }

  const SOURCE = {
    override:     { label: "Manual",   color: "var(--primary-text)" },
    keyword:      { label: "Auto",     color: "var(--fg-3)" },
    unclassified: { label: "Unmatched",color: "var(--adverse-text, #b91c1c)" },
  };

  const wf = payload?.workforce || {};
  const isWf = tab === "workforce";
  const rows    = isWf ? (wf.rows || [])  : (payload?.rows || []);
  const options = isWf ? (wf.roles || []) : (payload?.streams || []);
  const valueKey = isWf ? "role" : "stream_key";
  const unmatched = isWf ? (wf.unclassified_count || 0) : (payload?.unclassified_count || 0);

  const TabBtn = ({ id, label, badge }) => (
    <button onClick={() => setTab(id)} style={{
      padding: "6px 12px", borderRadius: "var(--radius-sm)", cursor: "pointer",
      font: "var(--text-body-strong)", fontSize: 12.5,
      border: tab === id ? "1px solid var(--primary)" : "1px solid var(--border-strong)",
      background: tab === id ? "var(--primary-soft, #eff6ff)" : "var(--surface)",
      color: tab === id ? "var(--primary-text)" : "var(--fg-2)",
      display: "inline-flex", alignItems: "center", gap: 6,
    }}>
      {label}
      {badge > 0 && (
        <span style={{ font: "600 10px var(--font-display)", color: "var(--adverse-text, #b91c1c)",
          background: "var(--adverse-soft, #fee2e2)", borderRadius: 9, padding: "0 6px" }}>{badge}</span>
      )}
    </button>
  );

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1100,
      display: "flex", justifyContent: "flex-end",
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", width: "100%", maxWidth: 560, height: "100%",
        boxShadow: "var(--shadow-hover)", overflow: "auto", padding: "24px 24px 40px",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>
            Review classifications
          </h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>

        <div style={{ display: "flex", gap: 8, marginBottom: 14 }}>
          <TabBtn id="income" label="Income streams" badge={payload?.unclassified_count} />
          <TabBtn id="workforce" label="Workforce roles" badge={wf.unclassified_count} />
        </div>

        <p style={{ margin: "0 0 16px", font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-3)" }}>
          {isWf
            ? "Each staff account is matched to a workforce role by keyword. Correct any that are wrong — the change flows into the workforce breakdown."
            : "Each revenue account is matched to an income stream by keyword. Correct any that are wrong — the change flows into the benchmarks."}
          {unmatched > 0 && (
            <span style={{ color: "var(--adverse-text, #b91c1c)", fontWeight: 600 }}> {unmatched} unmatched.</span>
          )}
        </p>

        {status === "loading" && <div style={{ color: "var(--fg-3)", fontSize: 13 }}>Loading…</div>}
        {status === "error" && <div style={{ color: "var(--adverse-text, #b91c1c)", fontSize: 13 }}>Could not load classifications.</div>}

        {status === "ready" && rows.length === 0 && (
          <div style={{ color: "var(--fg-3)", fontSize: 13 }}>No accounts to review here.</div>
        )}

        {status === "ready" && rows.map((r) => {
          const src = SOURCE[r.source] || SOURCE.keyword;
          return (
            <div key={r.name} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 0",
              borderBottom: "1px solid var(--border)" }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ font: "var(--text-body)", fontSize: 12.5, color: "var(--ink)",
                  whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                  {r.name}
                </div>
                <div style={{ font: "var(--text-caption)", fontSize: 10.5, color: src.color }}>
                  {src.label}{r.matched_keyword ? ` · “${r.matched_keyword}”` : ""} · {fmt(r.value)}
                </div>
              </div>
              <select value={r[valueKey]} disabled={savingAcct === r.name}
                onChange={e => saveOverride(isWf ? "workforce" : "income", r.name, e.target.value)}
                style={{ font: "var(--text-body)", fontSize: 12, padding: "5px 8px",
                  borderRadius: "var(--radius-xs)", border: "1px solid var(--border-strong)",
                  background: "var(--surface)", color: "var(--ink)", maxWidth: 180 }}>
                {options.map(o => (
                  <option key={o.key} value={o.key}>{o.label}</option>
                ))}
              </select>
            </div>
          );
        })}
      </div>
    </div>
  );
}


/* ── AnomalyPanel ───────────────────────────────────────────────────────── */
function AnomalyPanel({ sessionId, selectedPeriod, periodMode }) {
  const { Icon } = window;
  const [result,   setResult]   = React.useState(null);
  const [loading,  setLoading]  = React.useState(true);
  const [sigma,    setSigma]    = React.useState(1.5);
  const [expanded, setExpanded] = React.useState(false);

  React.useEffect(() => {
    if (!sessionId) return;
    setLoading(true);
    const params = new URLSearchParams({
      period: selectedPeriod || "",
      mode:   periodMode || "monthly",
      sigma,
    });
    fetch(apiUrl(`/api/anomalies/${sessionId}?${params}`))
      .then((r) => r.json())
      .then(setResult)
      .catch(() => setResult(null))
      .finally(() => setLoading(false));
  }, [sessionId, selectedPeriod, periodMode, sigma]);

  const fmtGBP = (v) => window.fmtCurrency(v, { compact: true });

  const anomalies = result?.anomalies || [];
  const note      = result?.note;

  if (result === null && !loading) return null;

  if (note) return (
    <div style={{
      marginBottom: 20, padding: "12px 18px", borderRadius: "var(--radius-md)",
      border: "1px solid var(--border)", background: "var(--surface-2)",
      display: "flex", alignItems: "center", gap: 10,
    }}>
      <Icon name="zap" size={15} color="var(--fg-3)" />
      <span style={{ font: "var(--text-body)", fontSize: 13, color: "var(--fg-3)" }}>
        Statistical anomalies — {note}
      </span>
    </div>
  );

  const visible = expanded ? anomalies : anomalies.slice(0, 3);
  const sigmaColor = (z) =>
    z >= 3 ? "var(--adverse-text)" : z >= 2 ? "var(--caution-text)" : "var(--fg-2)";
  const sigmaLabel = (z) =>
    z >= 3 ? "High" : z >= 2 ? "Moderate" : "Low";

  return (
    <div style={{
      marginBottom: 20,
      borderRadius: "var(--radius-md)",
      border: "1px solid var(--border)",
      background: "var(--surface)",
      overflow: "hidden",
    }}>
      {/* Header */}
      <div style={{
        display: "flex", alignItems: "center", gap: 10, padding: "14px 18px",
        borderBottom: loading || !anomalies.length ? "none" : "1px solid var(--border)",
        background: "var(--surface-2)",
      }}>
        <span style={{
          width: 30, height: 30, borderRadius: "50%", flexShrink: 0,
          background: anomalies.length ? "var(--caution-soft)" : "var(--surface-3)",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>
          <Icon name="zap" size={15} color={anomalies.length ? "var(--caution)" : "var(--fg-3)"} />
        </span>
        <div style={{ flex: 1 }}>
          <span style={{ font: "var(--text-body-strong)", fontSize: 14, color: "var(--ink)" }}>
            Statistical anomalies
          </span>
          <span style={{ marginLeft: 10, font: "var(--text-caption)", fontSize: 12, color: "var(--fg-3)" }}>
            {loading ? "Scanning…"
              : anomalies.length
                ? `${anomalies.length} account${anomalies.length > 1 ? "s" : ""} deviate from historical pattern · ${result?.period}`
                : `No unusual movements detected · ${result?.period}`}
          </span>
        </div>
        {/* Sensitivity selector */}
        <div style={{ display: "flex", alignItems: "center", gap: 6, flexShrink: 0 }}>
          <span style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>Sensitivity</span>
          {[[1.5, "High"], [2.0, "Med"], [2.5, "Low"]].map(([s, lbl]) => (
            <button key={s} onClick={() => setSigma(s)}
              style={{
                padding: "3px 9px", borderRadius: "var(--radius-pill)", cursor: "pointer",
                border: sigma === s ? "1.5px solid var(--primary)" : "1px solid var(--border-strong)",
                background: sigma === s ? "var(--primary-soft)" : "transparent",
                color: sigma === s ? "var(--primary-text)" : "var(--fg-3)",
                font: "var(--text-label)", fontSize: 10.5,
              }}>{lbl}</button>
          ))}
        </div>
      </div>

      {/* Empty state — no anomalies at this sensitivity */}
      {!loading && anomalies.length === 0 && (
        <div style={{ padding: "14px 18px", font: "var(--text-body)", fontSize: 13, color: "var(--fg-3)" }}>
          No unusual movements detected at this sensitivity level.
        </div>
      )}

      {/* Anomaly cards */}
      {!loading && anomalies.length > 0 && (
        <div style={{ padding: "12px 18px", display: "flex", flexDirection: "column", gap: 8 }}>
          {visible.map((a, i) => (
            <div key={i} style={{
              display: "flex", alignItems: "center", gap: 12, padding: "10px 14px",
              borderRadius: "var(--radius-sm)",
              background: a.is_fav ? "var(--favourable-soft)" : "var(--adverse-soft)",
              border: `1px solid ${a.is_fav ? "var(--favourable-border)" : "var(--adverse-border)"}`,
            }}>
              {/* Sigma badge */}
              <div
                title={`${a.z_score.toFixed(1)} standard deviations from the historical average\n\nσ (sigma) measures how unusual this value is compared to the account's typical range.`}
                style={{
                  flexShrink: 0, textAlign: "center", minWidth: 44,
                  padding: "4px 8px", borderRadius: "var(--radius-sm)",
                  background: "var(--surface-3)", cursor: "help",
                }}
              >
                <div style={{ font: "700 13px var(--font-mono)", color: sigmaColor(a.z_score) }}>
                  {a.z_score.toFixed(1)}σ
                </div>
                <div style={{ font: "var(--text-label)", fontSize: 9, textTransform: "uppercase", color: sigmaColor(a.z_score) }}>
                  {sigmaLabel(a.z_score)}
                </div>
              </div>

              {/* Account info */}
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                  {a.account}
                </div>
                <div style={{ font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)", marginTop: 2 }}>
                  {a.category} ·{" "}
                  {a.change_pct != null
                    ? `${a.change_pct > 0 ? "+" : ""}${a.change_pct.toFixed(1)}% vs avg`
                    : "unusual value"}
                  {" "}· avg {fmtGBP(a.historical_mean)}
                </div>
              </div>

              {/* Current vs mean */}
              <div style={{ textAlign: "right", flexShrink: 0 }}>
                <div style={{ font: "600 13px var(--font-mono)", color: a.is_fav ? "var(--favourable-text)" : "var(--adverse-text)", fontVariantNumeric: "tabular-nums" }}>
                  {fmtGBP(a.current_value)}
                </div>
                <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 2 }}>
                  {a.is_fav ? "Favourable" : "Adverse"}
                </div>
              </div>
            </div>
          ))}

          {anomalies.length > 3 && (
            <button
              onClick={() => setExpanded(!expanded)}
              style={{
                alignSelf: "flex-start", padding: "4px 12px", borderRadius: "var(--radius-pill)",
                border: "1px solid var(--border-strong)", background: "transparent",
                color: "var(--fg-3)", font: "var(--text-body-strong)", fontSize: 12.5, cursor: "pointer",
              }}>
              {expanded
                ? "Show fewer"
                : `Show ${anomalies.length - 3} more anomal${anomalies.length - 3 > 1 ? "ies" : "y"}`}
            </button>
          )}
        </div>
      )}
    </div>
  );
}

/* ── ForecastPanel ──────────────────────────────────────────────────────── */
function ForecastPanel({ sessionId, periodMode }) {
  const { Icon, Card, Chip, TrendChart } = window;
  const [fcData,   setFcData]   = React.useState(null);
  const [lookback, setLookback] = React.useState(3);
  const [loading,  setLoading]  = React.useState(false);
  const [error,    setError]    = React.useState(null);

  React.useEffect(() => {
    if (!sessionId || periodMode === "ytd") return;
    setLoading(true);
    setError(null);
    fetch(apiUrl(`/api/forecast/${sessionId}?lookback=${lookback}&mode=${periodMode}`))
      .then((r) => {
        if (!r.ok) {
          const code = r.status;
          if (code === 401 || code === 403) throw new Error("Authentication failed — check your API key.");
          if (code === 404) throw new Error("Session expired — please re-upload your file.");
          if (code === 429) throw new Error("Rate limited — wait a moment and try again.");
          if (code >= 500) throw new Error("Server error — try again shortly.");
          throw new Error(`Request failed (HTTP ${code}).`);
        }
        return r.json();
      })
      .then(setFcData)
      .catch((e) => setError(e.message))
      .finally(() => setLoading(false));
  }, [sessionId, lookback, periodMode]);

  const fmtGBP = (v) => window.fmtCurrency(v);

  const combined     = fcData?.combined || [];
  const forecastFrom = fcData ? (fcData.actuals || []).length : 0;
  const hasProjection = (fcData?.forecast || []).length > 0;

  return (
    <Card
      title="Rolling forecast"
      sub={loading ? "Loading…" : fcData ? `${fcData.lookback_used}-period trailing average · ${(fcData.forecast||[]).length} periods projected` : ""}
      action={<Chip tone="info" icon="trending-up">Forecast</Chip>}
    >
      {/* Lookback selector */}
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 14 }}>
        <span style={{ font: "var(--text-body-strong)", fontSize: 12.5, color: "var(--fg-2)" }}>
          Trailing average:
        </span>
        {[3, 6, 12].map((n) => (
          <button key={n} onClick={() => setLookback(n)}
            style={{
              padding: "4px 12px", borderRadius: "var(--radius-pill)",
              border: lookback === n ? "1.5px solid var(--primary)" : "1px solid var(--border-strong)",
              background: lookback === n ? "var(--primary-soft)" : "var(--surface-2)",
              color: lookback === n ? "var(--primary-text)" : "var(--fg-2)",
              font: "var(--text-body-strong)", fontSize: 12.5, cursor: "pointer",
            }}>
            {n}m
          </button>
        ))}
        <span style={{ font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)", marginLeft: 4 }}>
          {loading ? "Loading…" : error ? "Error — is the session still active?" : ""}
        </span>
      </div>

      {/* Chart */}
      {!loading && combined.length > 1 && (
        <TrendChart
          data={combined}
          series={[
            { key: "revenue", label: "Revenue", color: "var(--c-1)" },
            { key: "costs",   label: "Costs",   color: "var(--c-6)" },
            { key: "profit",  label: "Profit",  color: "var(--c-5)" },
          ]}
          forecastFrom={forecastFrom}
        />
      )}

      {/* Projection summary table */}
      {!loading && hasProjection && (
        <div style={{ marginTop: 16, overflowX: "auto" }}>
          <div style={{
            font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase",
            letterSpacing: ".06em", color: "var(--fg-3)", marginBottom: 8,
          }}>Projected periods (trailing {lookback}m avg)</div>
          <table className="var">
            <thead>
              <tr>
                <th className="l">Period</th>
                <th>Revenue</th>
                <th>Costs</th>
                <th>Profit</th>
              </tr>
            </thead>
            <tbody>
              {(fcData.forecast || []).map((r, i) => (
                <tr key={i}>
                  <td className="l" style={{ color: "var(--fg-2)", fontStyle: "italic" }}>{r.full}</td>
                  <td>{fmtGBP(r.revenue)}</td>
                  <td>{fmtGBP(r.costs)}</td>
                  <td style={{ color: r.profit >= 0 ? "var(--favourable-text)" : "var(--adverse-text)" }}>
                    {fmtGBP(r.profit)}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          <div style={{ marginTop: 8, font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>
            * Projections use a simple trailing average — not a statistical model. Review assumptions before using in management packs.
          </div>
        </div>
      )}

      {!loading && !hasProjection && combined.length > 0 && (
        <div style={{ textAlign: "center", padding: "12px 0", font: "var(--text-caption)", fontSize: 12, color: "var(--fg-3)" }}>
          All months in the current year already have actuals — no forecast periods needed.
        </div>
      )}
    </Card>
  );
}

const CHART_COLORS = ["var(--c-1)","var(--c-2)","var(--c-3)","var(--c-4)","var(--c-5)","var(--c-6)","var(--c-7)","var(--c-8)"];

/* ── SmartInsights — auto-computed data highlights (no API call) ───────────── */
function SmartInsights({ movements, kpis, isBvA }) {
  const { Icon } = window;
  const all  = movements || [];
  const favM = all.filter(m => m.is_fav  && m.variance !== 0);
  const advM = all.filter(m => !m.is_fav && m.variance !== 0);
  const revM = all.filter(m => m.category === "Revenue");

  const fmt  = (v) => window.fmtCurrency(v, { compact: true });
  const fmtS = (v) => window.fmtCurrency(v, { compact: true, signed: true });

  const items = [];

  const profKpi = (kpis || []).find(k => k.icon === "wallet");
  if (profKpi?.variance != null) {
    items.push({
      icon: profKpi.is_fav ? "trending-up" : "trending-down", tone: profKpi.is_fav ? "fav" : "adv",
      // use the account's own name so NHS sessions say "Net surplus", not "Operating profit"
      label: profKpi.label || "Operating profit",
      text: `${fmtS(profKpi.variance)} vs ${isBvA ? "budget" : "prior"}`,
    });
  }

  if (favM.length + advM.length > 0) {
    const more = favM.length >= advM.length;
    items.push({
      icon: more ? "check-circle" : "alert-circle", tone: more ? "fav" : "adv",
      label: "Movement split",
      text: `${favM.length} favourable · ${advM.length} adverse`,
    });
  }

  if (all.length > 0) {
    const top = all.reduce((a, b) => Math.abs(a.variance||0) > Math.abs(b.variance||0) ? a : b);
    if (Math.abs(top.variance||0) > 0) {
      // Strip a leading Xero-style account code ("200 Digital Services Revenue")
      // before truncating, so the chip keeps the meaningful words.
      const clean = top.account.replace(/^\d{2,4}\s*[-–·]?\s*/, "");
      const nm = clean.length > 26 ? clean.slice(0, 24) + "…" : clean;
      items.push({
        icon: "zap", tone: top.is_fav ? "fav" : "adv",
        label: "Biggest movement",
        text: `${nm}: ${fmtS(top.variance)}`,
      });
    }
  }

  const catAdv = {};
  advM.filter(m => m.category !== "Revenue").forEach(m => {
    catAdv[m.category] = (catAdv[m.category] || 0) + Math.abs(m.variance || 0);
  });
  const [topCat, topAmt] = Object.entries(catAdv).sort((a, b) => b[1] - a[1])[0] || [];
  if (topCat) {
    items.push({
      icon: "alert-triangle", tone: "adv",
      label: "Most pressure",
      text: `${topCat}: ${fmt(topAmt)} adverse`,
    });
  }

  if (!isBvA && revM.length >= 3) {
    const total = revM.reduce((s, m) => s + Math.abs(m.value || 0), 0);
    if (total > 0) {
      const top3pct = Math.round(
        [...revM].sort((a, b) => Math.abs(b.value) - Math.abs(a.value)).slice(0, 3)
          .reduce((s, m) => s + Math.abs(m.value || 0), 0) / total * 100
      );
      items.push({
        icon: "pie-chart", tone: "info",
        label: "Revenue concentration",
        text: `Top 3 accounts: ${top3pct}% of total`,
      });
    }
  }

  if (items.length === 0) return null;

  const toneCol = (t) => t === "fav" ? "var(--favourable-text)" : t === "adv" ? "var(--adverse-text)" : "var(--primary)";
  const toneBg  = (t) => t === "fav" ? "var(--favourable-soft)" : t === "adv" ? "var(--adverse-soft)" : "var(--primary-soft)";

  return (
    <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(195px, 1fr))", gap: 12, marginBottom: 20 }}>
      {items.map((item, i) => (
        <div key={i} className="card" style={{ padding: "14px 16px", display: "flex", alignItems: "flex-start", gap: 10 }}>
          <div style={{
            width: 32, height: 32, borderRadius: "var(--radius-sm)", flexShrink: 0,
            background: toneBg(item.tone), display: "flex", alignItems: "center", justifyContent: "center",
          }}>
            <Icon name={item.icon} size={15} color={toneCol(item.tone)} />
          </div>
          <div>
            <div style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".06em", color: "var(--fg-3)", marginBottom: 3 }}>
              {item.label}
            </div>
            <div style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)", lineHeight: 1.3 }}>
              {item.text}
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

/* ── SpotlightModal ─────────────────────────────────────────────────────── */
function SpotlightModal({ spotlight, onClose }) {
  const { Icon, Delta } = window;
  const { period, loading, data, error } = spotlight;

  React.useEffect(() => {
    const handler = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", handler);
    return () => document.removeEventListener("keydown", handler);
  }, []);

  const fmt  = (v) => window.fmtCurrency(v, { compact: true });
  const fmtS = (v) => window.fmtCurrency(v, { signed: true });
  const fmtPct = (v) => (v == null || isNaN(v)) ? "—"
    : (v > 0 ? "+" : v < 0 ? "-" : "") + Math.abs(v).toFixed(1) + "%";

  const movements = data?.movements || [];
  const kpis      = (data?.kpis || []).filter(k => !k.pct_only);
  const favRows   = movements.filter(m =>  m.is_fav && m.variance !== 0)
    .sort((a, b) => Math.abs(b.variance) - Math.abs(a.variance)).slice(0, 5);
  const advRows   = movements.filter(m => !m.is_fav && m.variance !== 0)
    .sort((a, b) => Math.abs(b.variance) - Math.abs(a.variance)).slice(0, 5);

  const movCol = (rows, tone) => {
    const fav = tone === "fav";
    return (
      <div>
        <div style={{
          font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase",
          letterSpacing: ".06em", marginBottom: 8,
          color: fav ? "var(--favourable-text)" : "var(--adverse-text)",
          display: "flex", alignItems: "center", gap: 6,
        }}>
          <Icon name={fav ? "trending-up" : "trending-down"} size={12}
            color={fav ? "var(--favourable)" : "var(--adverse)"} />
          {fav ? "Top favourable" : "Top adverse"}
        </div>
        {rows.length === 0 && (
          <div style={{ font: "var(--text-body)", fontSize: 13, color: "var(--fg-3)", padding: "4px 0" }}>None</div>
        )}
        {rows.map((m, i) => (
          <div key={i} style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            padding: "7px 10px", marginBottom: 4, borderRadius: "var(--radius-sm)",
            background: fav ? "var(--favourable-soft)" : "var(--adverse-soft)",
          }}>
            <div style={{ minWidth: 0 }}>
              <div style={{ font: "var(--text-body-strong)", fontSize: 12.5, color: "var(--ink)",
                overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {m.account}
              </div>
              <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>
                {m.category}
              </div>
            </div>
            <span style={{ font: "600 12px var(--font-mono)", fontVariantNumeric: "tabular-nums",
              color: fav ? "var(--favourable-text)" : "var(--adverse-text)",
              flexShrink: 0, marginLeft: 8 }}>
              {fmtS(m.variance)}
            </span>
          </div>
        ))}
      </div>
    );
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{ width: 680, maxWidth: "calc(100vw - 48px)" }}
        onClick={e => e.stopPropagation()}>
        <div className="modal-h">
          <div>
            <h3>{period}</h3>
            <div className="sub">Period spotlight · movements vs prior period</div>
          </div>
          <button className="x" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>

        <div className="modal-b" style={{ maxHeight: "70vh", overflowY: "auto" }}>
          {loading && (
            <div className="loading" style={{ height: 160 }}>
              <div className="spinner" />Loading {period}…
            </div>
          )}
          {error && (
            <div style={{ color: "var(--adverse-text)", font: "var(--text-body)", fontSize: 13 }}>
              {error}
            </div>
          )}
          {data && !loading && (
            <>
              {/* KPI row */}
              <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 20 }}>
                {kpis.map(k => (
                  <div key={k.label} className={`card kpi${k.icon === "wallet" ? " kpi-hero" : ""}`}
                    style={{ padding: "14px 16px" }}>
                    <div className="kpi-top">
                      <span className="lbl">{k.label}</span>
                      <div className="kpi-ic"><Icon name={k.icon} size={16} /></div>
                    </div>
                    <div className="val" style={{ fontSize: 20, margin: "8px 0 6px" }}>{fmt(k.value)}</div>
                    <div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
                      <span style={{ font: "var(--text-caption)", fontSize: 11,
                        color: k.icon === "wallet" ? "var(--fg-on-dark-2)" : "var(--fg-3)" }}>
                        Prior: {fmt(k.prior)}
                      </span>
                      <Delta fav={k.is_fav} up={k.variance >= 0}>
                        {fmtS(k.variance)} · {fmtPct(k.pct)}
                      </Delta>
                    </div>
                  </div>
                ))}
              </div>

              {/* Top movements */}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
                {movCol(favRows, "fav")}
                {movCol(advRows, "adv")}
              </div>
            </>
          )}
        </div>

        <div className="modal-f">
          <button className="btn secondary" onClick={onClose}>Close</button>
        </div>
      </div>
    </div>
  );
}

/* ── Cash & Runway ───────────────────────────────────────────────────────────
   The headline SMB metric. Burn is estimated from the operating-result trend;
   true runway needs an actual cash balance, which the user enters (persisted)
   or — when connected — is prefilled from Xero's balance sheet. We label the
   basis honestly rather than presenting a P&L approximation as real cash. */
function CashRunway({ trend, periodMode, sessionId, isBvA, xeroCash }) {
  const { Icon, Card } = window;
  const KEY = `monthendiq_cash_${sessionId || "x"}`;

  const [cash, setCash]       = React.useState(() => {
    try { const v = localStorage.getItem(KEY); return v != null && v !== "" ? Number(v) : null; } catch { return null; }
  });
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft]     = React.useState("");

  // Prefill from Xero balance sheet if we have it and the user hasn't set one
  React.useEffect(() => {
    if (xeroCash != null && cash == null) {
      setCash(xeroCash);
      try { localStorage.setItem(KEY, String(xeroCash)); } catch {}
    }
  }, [xeroCash]);

  const results = (trend || [])
    .map(t => isBvA ? (t.actual_profit != null ? t.actual_profit : t.profit) : t.profit)
    .filter(v => typeof v === "number" && !isNaN(v));

  if (results.length < 2 || periodMode === "ytd") return null;

  const avgPeriod   = results.reduce((a, b) => a + b, 0) / results.length;
  const perMonth    = periodMode === "quarterly" ? avgPeriod / 3 : avgPeriod;
  const burning     = perMonth < 0;
  const monthlyBurn = Math.abs(perMonth);
  const nPeriods    = results.length;

  const fmtMoney = (v) => window.fmtCurrency(v, { compact: true });

  const saveCash = () => {
    const n = Number(String(draft).replace(/[^0-9.\-]/g, ""));
    if (!isNaN(n) && draft.trim() !== "") { setCash(n); try { localStorage.setItem(KEY, String(n)); } catch {} }
    else if (draft.trim() === "") { setCash(null); try { localStorage.removeItem(KEY); } catch {} }
    setEditing(false);
  };

  const runwayMonths = (burning && cash != null && cash > 0) ? cash / monthlyBurn : null;
  const runoutDate = runwayMonths != null ? (() => {
    const d = new Date(); d.setMonth(d.getMonth() + Math.floor(runwayMonths));
    return d.toLocaleDateString("en-GB", { month: "short", year: "numeric" });
  })() : null;

  const runwayTone = runwayMonths == null ? "neutral"
    : runwayMonths < 6 ? "adv" : runwayMonths < 12 ? "caution" : "fav";
  const toneColor = { adv: "var(--adverse-text)", caution: "var(--caution-text, #b45309)", fav: "var(--favourable-text)", neutral: "var(--fg-2)" }[runwayTone];

  const startEdit = () => { setDraft(cash != null ? String(cash) : ""); setEditing(true); };

  const tile = { padding: "14px 16px", background: "var(--surface-2)", borderRadius: "var(--radius-sm)", border: "1px solid var(--border)" };
  const tileLbl = { font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", marginBottom: 6 };

  return (
    <Card title="Cash & Runway"
      sub={`Operating ${burning ? "burn" : "surplus"} from the last ${nPeriods} ${periodMode === "quarterly" ? "quarters" : "months"}`}
      action={<span className="ai-badge" style={{ background: burning ? "var(--adverse-soft)" : "var(--favourable-soft)", color: burning ? "var(--adverse-text)" : "var(--favourable-text)" }}>
        <Icon name={burning ? "trending-down" : "trending-up"} size={12} />{burning ? "Burning" : "Generative"}</span>}>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
        {/* Cash on hand — editable */}
        <div style={tile}>
          <div style={tileLbl}>Cash on hand</div>
          {editing ? (
            <div style={{ display: "flex", gap: 4 }}>
              <input autoFocus type="text" value={draft}
                onChange={e => setDraft(e.target.value)}
                onKeyDown={e => { if (e.key === "Enter") saveCash(); if (e.key === "Escape") setEditing(false); }}
                placeholder="e.g. 250000"
                style={{ width: "100%", padding: "5px 8px", font: "var(--text-data)", fontSize: 15, color: "var(--ink)", background: "var(--surface)", border: "1px solid var(--primary)", borderRadius: "var(--radius-xs)", outline: "none" }} />
              <button onClick={saveCash} style={{ flexShrink: 0, padding: "0 8px", borderRadius: "var(--radius-xs)", border: "none", background: "var(--primary)", color: "var(--on-primary)", cursor: "pointer" }}>
                <Icon name="check" size={14} />
              </button>
            </div>
          ) : (
            <div onClick={startEdit} style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }} title="Click to edit your current cash balance">
              <span style={{ font: "var(--text-metric)", fontSize: 22, fontVariantNumeric: "tabular-nums", color: cash != null ? "var(--ink)" : "var(--fg-3)" }}>
                {cash != null ? fmtMoney(cash) : "Add →"}
              </span>
              <Icon name="pencil" size={12} color="var(--fg-3)" />
            </div>
          )}
          {cash != null && xeroCash != null && Math.round(cash) === Math.round(xeroCash) && (
            <div style={{ font: "var(--text-caption)", fontSize: 10, color: "var(--favourable-text)", marginTop: 3 }}>From Xero balance sheet</div>
          )}
        </div>

        {/* Avg monthly result */}
        <div style={tile}>
          <div style={tileLbl}>Avg monthly {burning ? "burn" : "surplus"}</div>
          <div style={{ font: "var(--text-metric)", fontSize: 22, fontVariantNumeric: "tabular-nums", color: burning ? "var(--adverse-text)" : "var(--favourable-text)" }}>
            {burning ? "-" : "+"}{fmtMoney(monthlyBurn)}
          </div>
        </div>

        {/* Runway */}
        <div style={tile}>
          <div style={tileLbl}>Cash runway</div>
          {!burning ? (
            <div style={{ font: "var(--text-body-strong)", fontSize: 15, color: "var(--favourable-text)", paddingTop: 4 }}>
              Cash generative
            </div>
          ) : runwayMonths == null ? (
            <div onClick={startEdit} style={{ cursor: "pointer", font: "var(--text-body-strong)", fontSize: 14, color: "var(--primary-text)", paddingTop: 4 }}>
              Add cash to see runway →
            </div>
          ) : (
            <div>
              <div style={{ font: "var(--text-metric)", fontSize: 22, fontVariantNumeric: "tabular-nums", color: toneColor }}>
                {runwayMonths < 1 ? "<1" : Math.floor(runwayMonths)} mo
              </div>
              <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 1 }}>
                depleted ~{runoutDate}
              </div>
            </div>
          )}
        </div>
      </div>
      <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 10, lineHeight: 1.5 }}>
        <Icon name="info" size={11} /> {burning ? "Burn" : "Surplus"} estimated from the operating result over the last {nPeriods} {periodMode === "quarterly" ? "quarters" : "months"} — excludes capex, financing and working-capital movements. Enter your actual cash balance for an accurate runway.
      </div>
    </Card>
  );
}

function Dashboard({ sessionId, initialData, periodMode, controlledPeriod, onDataChange, onModeChange, analysisType }) {
  const { Icon, Card, Button, Delta, Chip, TrendChart, Donut, WaterfallChart, RagBadge } = window;
  const [data, setData]             = useStateDash(initialData);
  const [loading, setLoading]       = useStateDash(false);
  const [activeTab, setActiveTab]   = useStateDash("movements");
  const [spotlight, setSpotlight]   = useStateDash(null);
  const [commentaryCopied, setCommentaryCopied] = useStateDash(false);
  const [, setRagRev] = useStateDash(0);
  const [localComm, setLocalComm]   = useStateDash(null);
  const [commSaveState, setCommSaveState] = useStateDash("idle"); // "idle"|"saving"|"saved"|"error"
  const commTimer = useRefDash(null);
  const [nhsSettingsOpen, setNhsSettingsOpen] = useStateDash(false);
  const [nhsSettingsDraft, setNhsSettingsDraft] = useStateDash(null);
  const [nhsSettingsSaving, setNhsSettingsSaving] = useStateDash(false);
  const [classifyOpen, setClassifyOpen] = useStateDash(false);

  // Track what we last fetched so we don't re-fetch on initial mount
  const lastFetched = useRefDash({ period: initialData?.selected_period, mode: periodMode });

  /* ── formatters ── */
  const fmtGBP       = (v) => window.fmtCurrency(v);
  const fmtSignedGBP = (v) => window.fmtCurrency(v, { signed: true });
  const fmtPct = (v) => {
    if (v === null || v === undefined || isNaN(v)) return "—";
    return (v > 0 ? "+" : v < 0 ? "-" : "") + Math.abs(v).toFixed(1) + "%";
  };

  /* ── data fetching ── */
  const fetchPeriod = async (period, mode) => {
    setLoading(true);
    try {
      const params = new URLSearchParams({ period, mode });
      const res = await fetch(apiUrl(`/api/data/${sessionId}?${params}`));
      if (res.ok) {
        const newData = await res.json();
        setData(newData);
        onDataChange && onDataChange(newData);
        lastFetched.current = { period: newData.selected_period, mode };
      }
    } finally {
      setLoading(false);
    }
  };

  /* ── ARRS declared WTE (reconciliation) ── */
  const saveArrsDeclared = async (roleKey, raw) => {
    const wte = raw === "" || raw == null ? 0 : parseFloat(raw);
    try {
      const r = await fetch(apiUrl(`/api/session/${sessionId}/arrs-declared`), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ role_key: roleKey, wte: isFinite(wte) ? wte : 0 }),
      });
      if (r.ok) fetchPeriod(data.selected_period, periodMode);
    } catch (e) { /* non-fatal — leave the field as typed */ }
  };

  /* ── trend spotlight ── */
  const openSpotlight = async (trendPoint) => {
    const period = trendPoint.full;
    setSpotlight({ period, loading: true, data: null, error: null });
    try {
      const params = new URLSearchParams({ period, mode: periodMode || "monthly" });
      const res = await fetch(apiUrl(`/api/data/${sessionId}?${params}`));
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const d = await res.json();
      setSpotlight({ period, loading: false, data: d, error: null });
    } catch (e) {
      setSpotlight(prev => ({ ...prev, loading: false, error: e.message }));
    }
  };

  // Re-render when user updates KPI thresholds in Settings
  useEffectDash(() => {
    const h = () => setRagRev(v => v + 1);
    window.addEventListener("meiq:thresholds-updated", h);
    return () => window.removeEventListener("meiq:thresholds-updated", h);
  }, []);

  // React to TopBar period change (controlledPeriod prop)
  useEffectDash(() => {
    if (!controlledPeriod) return;
    if (controlledPeriod === lastFetched.current.period && periodMode === lastFetched.current.mode) return;
    fetchPeriod(controlledPeriod, periodMode);
  }, [controlledPeriod, periodMode]);

  /* ── Commentary editor ──────────────────────────────────────────────── */
  // Re-initialise local bullets whenever the fetched period data changes
  useEffectDash(() => {
    if (!data?.commentary) return;
    const stripHtml = (h) => (h || "")
      .replace(/<[^>]+>/g, "")
      .replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">")
      .replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
    setLocalComm(
      data.commentary.map(c => ({
        text:   stripHtml(c.html),
        icon:   c.icon  || "circle",
        fav:    c.fav !== false,
        source: c.source || null,
        edited: !!c.edited,
      }))
    );
    setCommSaveState("idle");
  }, [data?.selected_period, sessionId, periodMode]);

  const _saveComm = (bullets) => {
    clearTimeout(commTimer.current);
    setCommSaveState("saving");
    commTimer.current = setTimeout(() => {
      const texts = bullets.filter(b => b.text.trim()).map(b => b.text.trim());
      fetch(apiUrl(`/api/sessions/${sessionId}/commentary`), {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ commentary: texts, period: data?.selected_period || "" }),
      }).then(r => {
        if (r.ok) {
          setCommSaveState("saved");
          setTimeout(() => setCommSaveState("idle"), 1500);
        } else setCommSaveState("error");
      }).catch(() => setCommSaveState("error"));
    }, 700);
  };

  const _updateBullet = (i, text) => {
    const next = (localComm || []).map((b, idx) => idx === i ? { ...b, text, edited: true } : b);
    setLocalComm(next);
    _saveComm(next);
  };
  const _deleteBullet = (i) => {
    const next = (localComm || []).filter((_, idx) => idx !== i);
    setLocalComm(next);
    _saveComm(next);
  };
  const _addBullet = () => {
    setLocalComm(prev => [...(prev || []), { text: "", icon: "edit-3", fav: true, source: null, edited: true }]);
  };
  const _resetComm = () => {
    fetch(apiUrl(`/api/sessions/${sessionId}/commentary`), {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ commentary: [], period: data?.selected_period || "", clear: true }),
    }).then(() => fetchPeriod(data?.selected_period, periodMode));
  };

  if (!data) return <div className="loading"><div className="spinner" />Loading…</div>;

  const isBvA = (analysisType || data.analysis_type) === "budget_vs_actual";
  const { kpis, trend, revenue_split, expense_split, movements, commentary, waterfall, period, selected_period } = data;

  // Effective commentary: local edits take precedence over server data
  const stripHtml = (h) => (h || "").replace(/<[^>]+>/g, "")
    .replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
  const commItems = localComm ?? (commentary || []).map(c => ({
    text: stripHtml(c.html), icon: c.icon || "circle", fav: c.fav !== false,
    source: c.source || null, edited: !!c.edited,
  }));
  const hasEdits = commItems.some(b => b.edited);

  // Short month labels for waterfall x-axis  e.g. "August 2025" → "Aug"
  const shortLabel = (lbl) => lbl ? lbl.split(" ")[0].slice(0, 3) : "";

  React.useEffect(() => {
    if (waterfall && !waterfall.reconciles) {
      console.warn("[MonthEndIQ] Waterfall does not reconcile — check account categorisation");
    }
  }, [waterfall]);

  // Donut colours
  const revSplit = (revenue_split || []).map((d, i) => ({ ...d, c: CHART_COLORS[i % 8] }));
  const expSplit = (expense_split || []).map((d, i) => ({ ...d, c: CHART_COLORS[i % 8] }));
  const revTotal = revSplit.reduce((a, b) => a + b.value, 0);
  const expTotal = expSplit.reduce((a, b) => a + b.value, 0);

  const tabs = ["movements", "revenue", "costs", "commentary"];
  const ragThresholds = window.loadRagThresholds ? window.loadRagThresholds() : {};

  // ── BvA KPI helpers ────────────────────────────────────────────────
  const bvaRevKpi  = isBvA ? (kpis || []).find(k => !k.pct_only && k.icon === "trending-up")  : null;
  const bvaProfKpi = isBvA ? (kpis || []).find(k => !k.pct_only && k.icon === "wallet")        : null;

  // ── BvA account-level profit waterfall bars ─────────────────────
  // Profit impact: Revenue variance improves profit; cost overspend reduces it.
  let bvaWaterfallBars  = null;
  let bvaWaterfallReady = false;
  if (isBvA && bvaProfKpi && bvaProfKpi.prior != null && bvaProfKpi.value != null) {
    const budgetProfit = bvaProfKpi.prior;
    const actualProfit = bvaProfKpi.value;
    const netVariance  = actualProfit - budgetProfit;

    const rawBars = (movements || [])
      .filter(m => m.variance != null && m.variance !== 0)
      .map(m => ({
        label:  m.account,
        impact: m.category === "Revenue" ? Number(m.variance) : -Number(m.variance),
        fav:    m.is_fav,
      }))
      .sort((a, b) => Math.abs(b.impact) - Math.abs(a.impact))
      .slice(0, 8);

    // Residual: keep waterfall balanced
    const explained = rawBars.reduce((s, b) => s + b.impact, 0);
    const residual  = Math.round(netVariance - explained);
    if (Math.abs(residual) > 1) {
      rawBars.push({ label: "Other", impact: residual, fav: residual > 0 });
    }

    bvaWaterfallBars  = rawBars;
    bvaWaterfallReady = true;
  }

  return (
    <div className="content-inner reveal" style={{ opacity: loading ? 0.6 : 1, transition: "opacity .2s" }}>

      {/* ── BvA editorial headline (matches MoM style) ────────────── */}
      {isBvA && (() => {
        const profKpi = (kpis || []).find(k => k.icon === "wallet");
        if (!profKpi || profKpi.variance == null) return null;
        const bvaPeriodLabel = (() => {
          const sp = data.selected_bva_period;
          if (!sp || sp === "full_year") return "Full Year";
          try { return new Date(sp + "T00:00:00").toLocaleDateString("en-GB", { month: "long", year: "numeric" }); }
          catch (_) { return sp; }
        })();
        return (
          <div className="editorial-header">
            <div className="editorial-eyebrow">
              <span className="e-label">Budget vs Actual</span>
              <span className="e-sep" />
              <span className="e-period">{bvaPeriodLabel}</span>
            </div>
            <h2 className="editorial-h1">
              Operating profit{" "}
              <span style={{ color: profKpi.is_fav ? "var(--favourable-text)" : "var(--adverse-text)" }}>
                {fmtGBP(Math.abs(profKpi.variance))}
              </span>
              {" "}{profKpi.variance >= 0 ? "above" : "below"} budget.
            </h2>
          </div>
        );
      })()}

      {/* Editorial headline — MoM view only */}
      {!isBvA && (() => {
        const profKpi = (kpis || []).find(k => k.icon === "wallet");
        if (!profKpi || profKpi.variance == null) return null;
        return (
          <div className="editorial-header">
            <div className="editorial-eyebrow">
              <span className="e-label">Variance report</span>
              <span className="e-sep" />
              <span className="e-period">{period?.label || selected_period}</span>
              {/xero/i.test(data?.file_name || "") && (
                <span title="This session was imported directly from Xero" style={{
                  display: "inline-flex", alignItems: "center", gap: 5, marginLeft: 10,
                  font: "600 10.5px var(--font-display)", textTransform: "uppercase",
                  letterSpacing: ".05em", color: "var(--xero-text)",
                  background: "var(--xero-bg)", border: "1px solid var(--xero-border)",
                  borderRadius: 20, padding: "2px 9px",
                }}>
                  <Icon name="refresh-cw" size={10} /> Synced from Xero
                </span>
              )}
            </div>
            <h2 className="editorial-h1">
              {profKpi.value != null && profKpi.value < 0 ? (
                // Loss-making period: never celebrate "profit up" over a loss —
                // frame the movement honestly as the loss narrowing or widening.
                <React.Fragment>
                  {/net surplus/i.test(profKpi.label) ? "Net deficit" : "Operating loss"}{" "}
                  {profKpi.variance >= 0 ? "narrowed" : "widened"} by{" "}
                  <span style={{ color: profKpi.is_fav ? "var(--favourable-text)" : "var(--adverse-text)" }}>
                    {fmtGBP(Math.abs(profKpi.variance))}
                  </span>
                  {" "}to {fmtGBP(profKpi.value)} this period.
                </React.Fragment>
              ) : (
                <React.Fragment>
                  {profKpi.label || "Operating profit"} {profKpi.is_fav ? "up" : "down"}{" "}
                  <span style={{ color: profKpi.is_fav ? "var(--favourable-text)" : "var(--adverse-text)" }}>
                    {fmtSignedGBP(profKpi.variance)}
                  </span>
                  {" "}this period.
                </React.Fragment>
              )}
            </h2>
          </div>
        );
      })()}

      {/* KPI row */}
      <div className="grid-kpi">
        {(kpis || []).map((k) => {
          // Map KPI label → trend series key for sparkline
          const lc = k.label.toLowerCase();
          const trendKey = !isBvA && trend && trend.length > 2
            ? (lc.includes("revenue") || lc.includes("turnover") || lc.includes("sales") ? "revenue"
              : lc.includes("profit") || lc.includes("ebitda") || lc.includes("operating") ? "profit"
              : lc.includes("cost") || lc.includes("expense") ? "costs"
              : null)
            : null;
          const ragKey = k.icon === "trending-up" ? "revenue_var_pct"
                       : k.icon === "wallet"       ? "profit_var_pct"
                       : k.icon === "percent"      ? "op_margin"
                       : k.icon === "users"        ? "payroll_pct"
                       : null;
          const ragSt = ragKey && window.ragStatus ? window.ragStatus(k.pct, ragThresholds[ragKey]) : null;
          return (
            <div key={k.label} className={`card kpi${k.icon === "wallet" ? " kpi-hero" : ""}`}>
              <div className="kpi-top">
                <div className="lbl">{k.label}</div>
                <span className="kpi-ic"><Icon name={k.icon} size={16} /></span>
              </div>
              <div className="val">{k.pct_only ? fmtPct(k.pct) : fmtGBP(k.value)}</div>
              {!k.pct_only && k.variance !== null && (
                <Delta fav={k.is_fav} up={k.variance >= 0}>
                  {fmtSignedGBP(k.variance)}{k.pct != null ? ` · ${fmtPct(k.pct)}` : ""}
                </Delta>
              )}
              {k.pct_only && k.variance != null && (
                <Delta fav={k.is_fav} up={k.variance >= 0}>
                  {`${k.variance >= 0 ? "+" : ""}${k.variance.toFixed(1)} pts vs ${period?.prior || "prior"}`}
                </Delta>
              )}
              {/* TrendSparkline, not Sparkline — Movements.jsx overwrites
                  window.Sparkline with an unrelated component of the same name */}
              {trendKey && window.TrendSparkline && (
                <div style={{ marginTop: 10, marginBottom: -2 }}>
                  <TrendSparkline data={trend} valueKey={trendKey} height={26} />
                </div>
              )}
              {ragSt && (
                <div style={{ marginTop: 6 }}>
                  <RagBadge status={ragSt} />
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* NHS GP per-patient KPI row */}
      {data.sector === "nhs_gp" && (data.nhs_kpi_cards || []).length > 0 && (
        <div style={{ marginTop: 4 }}>
          <div style={{ font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", color: "var(--fg-3)", marginBottom: 8, display: "flex", alignItems: "center", gap: 6, justifyContent: "space-between" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <Icon name="heart-pulse" size={12} color="var(--fg-3)" />
              NHS GP metrics
            </div>
            <button
              onClick={() => {
                if (!nhsSettingsOpen) {
                  setNhsSettingsDraft({
                    list_size: data.list_size || 0,
                    wte_partners: data.nhs_kpis?.list_size ? "" : "",
                    arrs_allocation: "",
                    qof_entitlement: "",
                    partner_drawings: "",
                  });
                }
                setNhsSettingsOpen(v => !v);
              }}
              style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--primary-text)", background: "none", border: "none", cursor: "pointer", padding: 0, display: "flex", alignItems: "center", gap: 4 }}
            >
              <Icon name="settings" size={10} color="var(--primary)" />
              {nhsSettingsOpen ? "close" : "edit settings"}
            </button>
          </div>
          {nhsSettingsOpen && (() => {
            const draft = nhsSettingsDraft || {};
            const setField = (k, v) => setNhsSettingsDraft(d => ({ ...d, [k]: v }));
            const save = async () => {
              setNhsSettingsSaving(true);
              try {
                const body = {};
                if (draft.list_size !== "" && draft.list_size !== undefined) body.list_size = parseInt(draft.list_size) || 0;
                if (draft.wte_partners !== "" && draft.wte_partners !== undefined) body.wte_partners = parseFloat(draft.wte_partners) || null;
                if (draft.arrs_allocation !== "" && draft.arrs_allocation !== undefined) body.arrs_allocation = parseFloat(draft.arrs_allocation) || null;
                if (draft.qof_entitlement !== "" && draft.qof_entitlement !== undefined) body.qof_entitlement = parseFloat(draft.qof_entitlement) || null;
                if (draft.partner_drawings !== "" && draft.partner_drawings !== undefined) body.partner_drawings = parseFloat(draft.partner_drawings) || null;
                const res = await fetch(apiUrl(`/api/session/${sessionId}/settings`), {
                  method: "PATCH",
                  headers: { "Content-Type": "application/json" },
                  body: JSON.stringify(body),
                });
                if (res.ok) {
                  setNhsSettingsOpen(false);
                  // Refresh period data to recompute NHS KPIs
                  if (data.selected_period) fetchPeriod(data.selected_period, periodMode);
                }
              } finally {
                setNhsSettingsSaving(false);
              }
            };
            const fieldStyle = { font: "var(--text-body)", fontSize: 12.5, padding: "5px 8px", borderRadius: "var(--radius-xs)", border: "1.5px solid var(--border)", background: "var(--surface)", color: "var(--ink)", width: "100%", boxSizing: "border-box" };
            const labelStyle = { font: "var(--text-label)", fontSize: 10.5, color: "var(--fg-3)", textTransform: "uppercase", letterSpacing: ".05em", display: "block", marginBottom: 4 };
            return (
              <div style={{ background: "var(--surface-2)", border: "1.5px solid var(--border)", borderRadius: "var(--radius-sm)", padding: 14, marginBottom: 10 }}>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
                  <div>
                    <label style={labelStyle}>List size (patients)</label>
                    <input type="number" style={fieldStyle} placeholder={data.list_size || "e.g. 9200"} value={draft.list_size ?? ""} onChange={e => setField("list_size", e.target.value)} />
                  </div>
                  <div>
                    <label style={labelStyle}>WTE GP partners</label>
                    <input type="number" style={fieldStyle} placeholder="e.g. 3.0" step="0.5" value={draft.wte_partners ?? ""} onChange={e => setField("wte_partners", e.target.value)} />
                  </div>
                  <div>
                    <label style={labelStyle}>ARRS allocation (£)</label>
                    <input type="number" style={fieldStyle} placeholder="e.g. 125000" value={draft.arrs_allocation ?? ""} onChange={e => setField("arrs_allocation", e.target.value)} />
                  </div>
                  <div>
                    <label style={labelStyle}>QOF entitlement (£)</label>
                    <input type="number" style={fieldStyle} placeholder="e.g. 188000" value={draft.qof_entitlement ?? ""} onChange={e => setField("qof_entitlement", e.target.value)} />
                    {(() => {
                      const q = data?.nhs_utilisation?.qof;
                      if (!q) return null;
                      if (q.estimated) return (
                        <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--caution-text, #b45309)", marginTop: 4 }}>
                          Leaving blank estimates ≈ £{Math.round(q.entitlement).toLocaleString()} from list size.
                        </div>
                      );
                      return (
                        <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 4 }}>
                          Update whenever the practice's QOF aspiration changes in-year.
                        </div>
                      );
                    })()}
                  </div>
                </div>
                <div style={{ marginTop: 10, display: "flex", gap: 8, justifyContent: "flex-end" }}>
                  <button onClick={() => setNhsSettingsOpen(false)} style={{ font: "var(--text-body)", fontSize: 12.5, padding: "5px 12px", borderRadius: "var(--radius-xs)", border: "1.5px solid var(--border)", background: "transparent", color: "var(--fg-2)", cursor: "pointer" }}>Cancel</button>
                  <button onClick={save} disabled={nhsSettingsSaving} style={{ font: "var(--text-body-strong)", fontSize: 12.5, padding: "5px 14px", borderRadius: "var(--radius-xs)", border: "none", background: "var(--primary)", color: "var(--on-primary)", cursor: nhsSettingsSaving ? "wait" : "pointer", opacity: nhsSettingsSaving ? 0.7 : 1 }}>
                    {nhsSettingsSaving ? "Saving…" : "Update"}
                  </button>
                </div>
              </div>
            );
          })()}
          <div className="grid-kpi">
            {(data.nhs_kpi_cards || []).map(k => (
              <div key={k.label} className="card kpi">
                <div className="kpi-top">
                  <div className="lbl">{k.label}</div>
                  <span className="kpi-ic"><Icon name={k.icon} size={16} /></span>
                </div>
                <div className="val">
                  {k.fmt === "pct"    ? `${k.value?.toFixed(1)}%`
                  : k.fmt === "number" ? k.value?.toLocaleString()
                  : fmtGBP(k.value)}
                </div>
                {k.hint && (
                  <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 3 }}>
                    {k.hint}
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>
      )}

      {/* NHS income stream breakdown */}
      {(data.sector === "nhs_gp" || data.sector === "nhs_pcn") && (data.nhs_income_breakdown || []).length > 0 && (() => {
        const streams = data.nhs_income_breakdown;
        // Token palette, so the mix re-steps for the dark surface instead of
        // sitting at light-mode saturation on navy.
        const STREAM_COLORS = {
          core_contract:     "var(--c-1)",
          qof:               "var(--c-3)",
          enhanced_services: "var(--c-5)",
          pcn_des:           "var(--c-2)",
          premises:          "var(--c-4)",
          private:           "var(--c-8)",
          other_nhs:         "var(--c-6)",
          unclassified:      "var(--adverse)",
        };
        const STREAM_FALLBACK = "var(--c-8)";
        const fmtCompact = v => window.fmtCurrency(v, { compact: true });
        const unclassifiedCount = (streams.find(s => s.key === "unclassified")?.accounts || []).length;
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
              <Icon name="layers" size={14} color="var(--fg-3)" />
              <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>
                Income streams
              </span>
              {unclassifiedCount > 0 && (
                <span title={`${unclassifiedCount} account${unclassifiedCount === 1 ? "" : "s"} not matched to a stream`}
                  style={{ font: "600 10px var(--font-display)", color: "var(--adverse-text, #b91c1c)",
                    background: "var(--adverse-soft, #fee2e2)", borderRadius: 10, padding: "1px 7px" }}>
                  {unclassifiedCount} to review
                </span>
              )}
              <button onClick={() => setClassifyOpen(true)}
                style={{ marginLeft: unclassifiedCount > 0 ? 0 : "auto", font: "var(--text-caption)", fontSize: 11,
                  color: "var(--primary-text)", background: "none", border: "none", cursor: "pointer", padding: "2px 4px" }}>
                Review
              </button>
              <span style={{ marginLeft: unclassifiedCount > 0 ? "auto" : 0, font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>
                {period?.label || selected_period || ""}
              </span>
            </div>

            {/* Stacked proportional bar */}
            <div style={{ display: "flex", height: 10, borderRadius: 5, overflow: "hidden", marginBottom: 12 }}>
              {streams.map(s => (
                <div key={s.key} title={`${s.label}: ${fmtGBP(s.value)} (${s.pct}%)`}
                  style={{ width: `${s.pct}%`, background: STREAM_COLORS[s.key] || STREAM_FALLBACK,
                           flexShrink: 0, minWidth: s.pct > 0 ? 3 : 0 }} />
              ))}
            </div>

            {/* Stream rows — compact table style */}
            {streams.map(s => (
              <React.Fragment key={s.key}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 0",
                  borderBottom: "1px solid var(--border)" }}>
                  <span style={{ width: 9, height: 9, borderRadius: 2, flexShrink: 0,
                    background: STREAM_COLORS[s.key] || STREAM_FALLBACK, display: "inline-block" }} />
                  <span style={{ flex: 1, font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-1)" }}>
                    {s.label}
                    {s.accounts && s.accounts.length > 1 && (
                      <span style={{ font: "var(--text-caption)", fontSize: 10.5, color: "var(--fg-3)", marginLeft: 5 }}>
                        {s.accounts.length} lines
                      </span>
                    )}
                  </span>
                  <span style={{ font: "600 12px var(--font-mono)", color: "var(--ink)",
                    fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" }}>
                    {fmtCompact(s.value)}
                  </span>
                  <span style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)",
                    width: 38, textAlign: "right", flexShrink: 0 }}>
                    {s.pct}%
                  </span>
                </div>
                {s.accounts && s.accounts.length > 1 && (
                  <div style={{ paddingLeft: 17, paddingBottom: 2 }}>
                    {s.accounts.map((a, i) => {
                      const delta = a.prior_value != null ? a.value - a.prior_value : null;
                      const fav   = delta != null && delta >= 0;
                      return (
                        <div key={i} style={{ display: "flex", justifyContent: "space-between",
                          padding: "2px 0", font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>
                          <span>{a.name}</span>
                          <span style={{ display: "flex", gap: 6, fontVariantNumeric: "tabular-nums" }}>
                            {fmtCompact(a.value)}
                            {delta != null && (
                              <span style={{ color: fav ? "var(--favourable-text, #15803d)" : "var(--adverse-text, #b91c1c)" }}>
                                {fav ? "+" : ""}{fmtCompact(delta)}
                              </span>
                            )}
                          </span>
                        </div>
                      );
                    })}
                  </div>
                )}
              </React.Fragment>
            ))}
          </div>
        );
      })()}

      {/* NHS GP workforce breakdown */}
      {data.sector === "nhs_gp" && data.workforce_breakdown && (() => {
        const wb = data.workforce_breakdown;
        const roles = [
          { key: "clinical",   label: "Clinical",    icon: "stethoscope",  color: "var(--favourable-text)" },
          { key: "locum",      label: "Locum",       icon: "user-x",       color: "var(--caution-text, #b45309)" },
          { key: "management", label: "Management",  icon: "briefcase",    color: "var(--primary-text)" },
          { key: "admin",      label: "Admin",       icon: "clipboard",    color: "var(--fg-2)" },
        ].filter(r => wb[r.key] && wb[r.key].total !== 0);
        if (!roles.length) return null;
        const total = roles.reduce((s, r) => s + Math.abs(wb[r.key].total), 0);
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <Icon name="users" size={14} color="var(--fg-3)" />
              <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>Workforce cost breakdown</span>
            </div>
            {roles.map(r => {
              const amt = Math.abs(wb[r.key].total);
              const pct = total ? (amt / total * 100) : 0;
              return (
                <div key={r.key} style={{ marginBottom: 8 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 3 }}>
                    <span style={{ font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-1)" }}>{r.label}</span>
                    <span style={{ font: "600 12px var(--font-mono)", color: r.color }}>{fmtGBP(amt)} <span style={{ fontWeight: 400, color: "var(--fg-3)", fontSize: 11 }}>({pct.toFixed(0)}%)</span></span>
                  </div>
                  <div style={{ height: 4, borderRadius: 2, background: "var(--surface-2)", overflow: "hidden" }}>
                    <div style={{ height: "100%", width: `${pct}%`, background: r.color, borderRadius: 2, transition: "width .3s" }} />
                  </div>
                </div>
              );
            })}
          </div>
        );
      })()}

      {/* ARRS headcount tracker */}
      {(data.sector === "nhs_gp" || data.sector === "nhs_pcn") && (data.arrs_headcount?.roles || []).length > 0 && (() => {
        const hc = data.arrs_headcount;
        const alloc = hc.arrs_allocation || 0;
        const totalYtd = hc.total_ytd_spend || 0;
        const utilPct = alloc > 0 ? Math.min(100, totalYtd / alloc * 100) : null;
        const statusColor = pct => pct == null ? "var(--fg-3)" : pct >= 80 ? "var(--favourable-text, #15803d)" : pct >= 50 ? "var(--caution-text, #b45309)" : "var(--adverse-text, #b91c1c)";
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <Icon name="users" size={14} color="var(--fg-3)" />
                <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>ARRS headcount tracker</span>
                <span style={{ font: "600 10px var(--font-display)", color: "var(--fg-3)", textTransform: "uppercase", letterSpacing: ".04em" }}>YTD • {hc.months_elapsed}m</span>
              </div>
              {utilPct != null && (
                <span style={{ font: "600 12px var(--font-mono)", color: statusColor(utilPct) }}>
                  {utilPct.toFixed(0)}% of allocation
                </span>
              )}
            </div>
            {alloc > 0 && (
              <div style={{ height: 6, borderRadius: 3, background: "var(--surface-2)", overflow: "hidden", marginBottom: 12 }}>
                <div style={{ height: "100%", width: `${Math.min(100, utilPct || 0)}%`, background: statusColor(utilPct), borderRadius: 3, transition: "width .3s" }} />
              </div>
            )}
            <div style={{ display: "grid", gridTemplateColumns: "1fr auto auto auto auto", gap: "4px 12px", alignItems: "center" }}>
              <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)" }}>Role</span>
              <span title="Estimated from spend ÷ NHSE cap — an indicator, not an audit-grade headcount" style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", textAlign: "right", cursor: "help" }}>WTE (est.)</span>
              <span title="Declared WTE from the practice's HR record" style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", textAlign: "right", cursor: "help" }}>Declared</span>
              <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", textAlign: "right" }}>Monthly avg</span>
              <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", textAlign: "right" }}>% cap</span>
              {hc.roles.map(r => {
                const pct = r.pct_of_cap;
                const col = statusColor(pct);
                // One full-width divider per row (spanning all columns) instead of
                // per-cell borders, which were ragged under auto-sized columns.
                const cellTop = { paddingTop: 6 };
                return [
                  <div key={r.key + "_div"} style={{ gridColumn: "1 / -1", borderTop: "1px solid var(--border)" }} />,
                  <span key={r.key + "_name"} style={{ font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-1)", ...cellTop, display: "flex", alignItems: "center", gap: 6 }}>
                    {r.role}
                    {r.divergent && (
                      <span title={`Estimate differs from declared by ${Math.abs(r.wte_variance).toFixed(2)} WTE`}
                        style={{ font: "600 9px var(--font-display)", color: "var(--adverse-text, #b91c1c)", background: "var(--adverse-soft, #fee2e2)", borderRadius: 8, padding: "0 5px" }}>
                        ⚠ {r.wte_variance > 0 ? "+" : ""}{r.wte_variance.toFixed(1)}
                      </span>
                    )}
                  </span>,
                  <span key={r.key + "_wte"} style={{ font: "600 12px var(--font-mono)", color: "var(--fg-1)", textAlign: "right", ...cellTop }}>
                    {r.wte_est != null ? r.wte_est.toFixed(2) : "—"}
                  </span>,
                  <span key={r.key + "_decl"} style={{ textAlign: "right", ...cellTop }}>
                    {r.wte_est != null ? (
                      <input type="number" step="0.1" min="0" defaultValue={r.wte_declared ?? ""}
                        placeholder="—"
                        onBlur={e => saveArrsDeclared(r.key, e.target.value)}
                        onKeyDown={e => { if (e.key === "Enter") e.target.blur(); }}
                        style={{ width: 52, font: "600 12px var(--font-mono)", textAlign: "right",
                          padding: "2px 5px", borderRadius: "var(--radius-xs)",
                          border: "1px solid var(--border)", background: "var(--surface)",
                          color: r.divergent ? "var(--adverse-text, #b91c1c)" : "var(--fg-1)" }} />
                    ) : <span style={{ color: "var(--fg-3)" }}>—</span>}
                  </span>,
                  <span key={r.key + "_avg"} style={{ font: "600 12px var(--font-mono)", color: "var(--fg-2)", textAlign: "right", ...cellTop }}>
                    {window.fmtCurrency ? window.fmtCurrency(r.monthly_avg, { compact: true }) : `£${(r.monthly_avg||0).toLocaleString()}`}
                  </span>,
                  <span key={r.key + "_pct"} style={{ font: "600 12px var(--font-mono)", color: col, textAlign: "right", ...cellTop }}>
                    {pct != null ? `${pct.toFixed(0)}%` : "—"}
                  </span>,
                ];
              })}
            </div>
            <div style={{ marginTop: 8, font: "var(--text-body)", fontSize: 10.5, color: "var(--fg-3)", lineHeight: 1.5 }}>
              WTE is estimated from spend ÷ NHSE reimbursement cap — an indicator, not audit-grade. Enter declared WTE from your HR record to reconcile; a ⚠ flags a gap over 0.5 WTE.
            </div>
            {alloc > 0 && (
              <div style={{ marginTop: 10, display: "flex", justifyContent: "flex-end", gap: 16 }}>
                <span style={{ font: "var(--text-body)", fontSize: 11, color: "var(--fg-3)" }}>
                  Spent YTD: <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>£{totalYtd.toLocaleString()}</b>
                </span>
                <span style={{ font: "var(--text-body)", fontSize: 11, color: "var(--fg-3)" }}>
                  Allocation: <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>£{alloc.toLocaleString()}</b>
                </span>
              </div>
            )}
          </div>
        );
      })()}

      {/* Year-end funding position — projected ARRS/QOF claim vs ceiling */}
      {(data.sector === "nhs_gp" || data.sector === "nhs_pcn") && data.nhs_forecast &&
        (data.nhs_forecast.arrs || data.nhs_forecast.qof) && (() => {
        const fc = data.nhs_forecast;
        const STATUS = {
          good:    { color: "var(--favourable-text)", bg: "var(--favourable-soft)", border: "var(--favourable-border)", label: "On track" },
          watch:   { color: "var(--caution-text)",    bg: "var(--caution-soft)",    border: "var(--caution-border)",    label: "Watch" },
          concern: { color: "var(--adverse-text)",    bg: "var(--adverse-soft)",    border: "var(--adverse-border)",    label: "At risk" },
          // QOF can't be read as "unclaimed" from a ledger — see _qof_position
          timing:  { color: "var(--fg-2)",            bg: "var(--surface-2)",       border: "var(--border)",            label: "Timing" },
          review:  { color: "var(--caution-text)",    bg: "var(--caution-soft)",    border: "var(--caution-border)",    label: "Review" },
        };
        const fmtC = v => `£${Math.round(v || 0).toLocaleString()}`;
        const Row = ({ name, b }) => {
          const st = STATUS[b.status] || STATUS.good;
          const pct = Math.min((b.projected / (b.ceiling || 1)) * 100, 100);
          return (
            <div style={{ padding: "12px 0", borderTop: "1px solid var(--border)" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
                <span style={{ font: "var(--text-body-strong)", fontSize: 12.5, color: "var(--fg-1)" }}>{name}</span>
                <span style={{ font: "600 10px var(--font-display)", textTransform: "uppercase", letterSpacing: ".05em",
                  padding: "2px 8px", borderRadius: 20, background: st.bg, color: st.color, border: `1px solid ${st.border}` }}>
                  {st.label}
                </span>
                {b.estimated_entitlement && (
                  <span style={{ font: "var(--text-caption)", fontSize: 10, color: "var(--fg-3)" }}>entitlement estimated from list size</span>
                )}
              </div>
              <div style={{ font: "var(--text-body)", fontSize: 13, color: "var(--fg-1)", lineHeight: 1.5, marginBottom: 8 }}>
                {b.headline}
              </div>
              {/* projection bar: YTD solid, projected remainder hatched-ish (lower opacity), vs ceiling track */}
              <div style={{ height: 6, borderRadius: 3, background: "var(--surface-2)", overflow: "hidden", display: "flex" }}>
                <span style={{ width: `${Math.min((b.ytd / (b.ceiling || 1)) * 100, 100)}%`, background: st.color, opacity: .9 }} />
                {!b.final && (
                  <span style={{ width: `${Math.max(pct - (b.ytd / (b.ceiling || 1)) * 100, 0)}%`, background: st.color, opacity: .35 }} />
                )}
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", marginTop: 6, font: "var(--text-body)", fontSize: 11, color: "var(--fg-3)" }}>
                <span>YTD <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>{fmtC(b.ytd)}</b>
                  {b.opportunity && !b.final && <React.Fragment> · projected <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>{fmtC(b.projected)}</b></React.Fragment>}
                  {b.opportunity === false && b.shortfall > 0 && <React.Fragment> · <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>{fmtC(b.shortfall)}</b> below entitlement to date</React.Fragment>}
                </span>
                <span>
                  {b.required_monthly != null && b.months_remaining > 0 && b.projected_unclaimed > 0 && (
                    <React.Fragment>needs <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>{fmtC(b.required_monthly)}/mo</b> for {b.months_remaining} remaining mo · </React.Fragment>
                  )}
                  ceiling <b style={{ color: "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>{fmtC(b.ceiling)}</b>
                </span>
              </div>
            </div>
          );
        };
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
              <Icon name="calendar-clock" size={14} color="var(--fg-3)" />
              <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>Year-end funding position</span>
              <span style={{ font: "600 10px var(--font-display)", color: "var(--fg-3)", textTransform: "uppercase", letterSpacing: ".04em" }}>
                {fc.arrs?.final || fc.qof?.final ? "contract year complete" : "straight-line projection from YTD pace"}
              </span>
            </div>
            {fc.arrs && <Row name="ARRS reimbursement" b={fc.arrs} />}
            {fc.qof && <Row name="QOF income" b={fc.qof} />}
            <div style={{ marginTop: 8, font: "var(--text-body)", fontSize: 10.5, color: "var(--fg-3)", lineHeight: 1.5 }}>
              ARRS is a reimbursement ceiling, so allocation not spent is genuinely unclaimed — projected at the YTD monthly pace against the allocation you entered. QOF is not a ceiling: achievement is clinical and the balance settles after year-end, so its position is shown for review, never as "unclaimed".
            </div>
          </div>
        );
      })()}

      {/* NHS Practice Benchmarks */}
      {(data.sector === "nhs_gp" || data.sector === "nhs_pcn") && (data.nhs_benchmarks?.items || []).length > 0 && (() => {
        const items = data.nhs_benchmarks.items;
        const STATUS = {
          good:    { bg: "var(--favourable-soft, #dcfce7)",  color: "var(--favourable-text, #15803d)",  label: "Good" },
          watch:   { bg: "var(--caution-soft, #fef3c7)",     color: "var(--caution-text, #b45309)",     label: "Watch" },
          concern: { bg: "var(--adverse-soft, #fee2e2)",      color: "var(--adverse-text, #b91c1c)",     label: "Concern" },
        };
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <Icon name="bar-chart-2" size={14} color="var(--fg-3)" />
              <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>Practice benchmarks</span>
              <span style={{ font: "600 10px var(--font-display)", color: "var(--fg-3)", textTransform: "uppercase", letterSpacing: ".04em" }}>
                Annualised · {data.nhs_benchmarks.months_elapsed}m YTD
              </span>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr auto auto", gap: "3px 10px", alignItems: "center" }}>
              <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)" }}>Metric</span>
              <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", textAlign: "right" }}>Your figure</span>
              <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", textAlign: "right" }}>Benchmark</span>
              {items.map(b => {
                const s = STATUS[b.status] || STATUS.watch;
                return [
                  <div key={b.key+"_div"} style={{ gridColumn: "1 / -1", borderTop: "1px solid var(--border)" }} />,
                  <span key={b.key+"_l"} title={b.hint} style={{ font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-1)", paddingTop: 5, cursor: "help" }}>
                    {b.label}
                  </span>,
                  <span key={b.key+"_v"} style={{ font: "600 12px var(--font-mono)", color: "var(--fg-1)", textAlign: "right", paddingTop: 5, whiteSpace: "nowrap" }}>
                    {b.value_str}
                  </span>,
                  <span key={b.key+"_b"} style={{ textAlign: "right", paddingTop: 5 }}>
                    <span style={{ display: "inline-block", padding: "1px 7px", borderRadius: 10, background: s.bg, color: s.color, font: "600 10.5px var(--font-display)", whiteSpace: "nowrap" }}>
                      {s.label}
                    </span>
                  </span>,
                ];
              })}
            </div>
            <div style={{ marginTop: 8, font: "var(--text-body)", fontSize: 10.5, color: "var(--fg-3)" }}>
              Sources: AISMA 2024/25, NHS England GP contract data. Hover metric for detail.
            </div>
          </div>
        );
      })()}

      {/* Year-end tax reserve panel */}
      {data.sector === "nhs_gp" && data.nhs_benchmarks && data.wte_partners > 0 && (() => {
        const me = data.nhs_benchmarks.months_elapsed || 12;
        const annProfit = data.nhs_benchmarks.ann_profit || 0;
        const ytdDraw   = data.ytd_drawings || 0;
        const annDraw   = Math.round(ytdDraw / me * 12);
        const wte       = data.wte_partners;
        const profPerPartner   = wte ? Math.round(annProfit / wte) : null;
        const drawPerPartner   = wte ? Math.round(annDraw   / wte) : null;
        const taxReserve       = profPerPartner != null ? Math.round(profPerPartner * 0.20) : null;
        const pensionRate      = 0.1438;  // NHS employer pension rate (14.38%)
        const netAfterTax      = profPerPartner != null && taxReserve != null ? profPerPartner - taxReserve : null;
        if (profPerPartner == null) return null;
        const fmtK = v => v != null ? `${v < 0 ? "-" : ""}£${Math.abs(v).toLocaleString()}` : "—";
        const Row = ({ label, value, bold, color, borderTop }) => (
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
            padding: "5px 0", borderTop: borderTop ? "1px solid var(--border)" : "none" }}>
            <span style={{ font: bold ? "var(--text-body-strong)" : "var(--text-body)", fontSize: 12.5, color: "var(--fg-1)" }}>{label}</span>
            <span style={{ font: `600 12px var(--font-mono)`, color: color || "var(--fg-1)", fontVariantNumeric: "tabular-nums" }}>{value}</span>
          </div>
        );
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <Icon name="calculator" size={14} color="var(--fg-3)" />
              <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>Year-end tax reserve</span>
              <span style={{ font: "600 10px var(--font-display)", color: "var(--fg-3)", textTransform: "uppercase", letterSpacing: ".04em" }}>Per WTE partner · annualised</span>
            </div>
            <Row label="Annualised practice surplus" value={fmtK(annProfit)} />
            <Row label="÷ WTE partners" value={`${wte.toFixed(1)}`} />
            <Row label="= Surplus per partner" value={fmtK(profPerPartner)} bold />
            {drawPerPartner != null && <Row label="Less: annualised drawings" value={`(${fmtK(drawPerPartner)})`} color="var(--fg-2)" />}
            {taxReserve != null && (
              <Row label="Suggested tax reserve (20%)" value={`(${fmtK(taxReserve)})`} color="var(--adverse-text, #b91c1c)" borderTop />
            )}
            {netAfterTax != null && (
              <Row label="Balance after tax reserve" value={fmtK(netAfterTax)}
                bold color={netAfterTax >= 0 ? "var(--favourable-text, #15803d)" : "var(--adverse-text, #b91c1c)"}
                borderTop />
            )}
            <div style={{ marginTop: 10, font: "var(--text-body)", fontSize: 10.5, color: "var(--fg-3)", lineHeight: 1.5 }}>
              Tax reserve estimate only — based on 20% effective rate. Actual liability depends on individual partner drawings, pension contributions, and other income. Always confirm with your tax adviser.
            </div>
          </div>
        );
      })()}

      {/* Partner drawings tracker */}
      {data.sector === "nhs_gp" && (() => {
        const drawTrend = (data.trend || []).filter(t => t.drawings > 0);
        if (!drawTrend.length) return null;
        const maxProfit = Math.max(...drawTrend.map(t => Math.abs(t.profit || 0)), 1);
        const maxDraw   = Math.max(...drawTrend.map(t => t.drawings), 1);
        const scale     = Math.max(maxProfit, maxDraw);
        const ytdDraw   = drawTrend.reduce((s, t) => s + t.drawings, 0);
        const overdraw  = drawTrend.filter(t => t.drawings > (t.profit || 0));
        return (
          <div className="card" style={{ marginTop: 18, padding: "16px 18px" }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <Icon name="briefcase" size={14} color="var(--fg-3)" />
                <span style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--fg-1)" }}>Partner drawings</span>
              </div>
              <span style={{ font: "600 12px var(--font-mono)", color: "var(--fg-2)" }}>
                YTD: {window.fmtCurrency ? window.fmtCurrency(ytdDraw, { compact: true }) : `£${ytdDraw.toLocaleString()}`}
              </span>
            </div>
            {overdraw.length > 0 && (
              <div style={{ background: "var(--adverse-soft)", border: "1px solid var(--adverse)", borderRadius: 6, padding: "6px 10px", marginBottom: 10, font: "var(--text-body)", fontSize: 12, color: "var(--adverse-text, #b91c1c)" }}>
                Drawings exceeded surplus in {overdraw.length} period{overdraw.length > 1 ? "s" : ""}: {overdraw.map(t => t.m).join(", ")}
              </div>
            )}
            <div style={{ display: "flex", alignItems: "flex-end", gap: 4, height: 60 }}>
              {drawTrend.map(t => {
                const drawH  = Math.round((t.drawings / scale) * 54);
                const profH  = Math.round((Math.abs(t.profit || 0) / scale) * 54);
                const over   = t.drawings > (t.profit || 0);
                return (
                  <div key={t.m} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 2 }}>
                    <div style={{ width: "100%", display: "flex", alignItems: "flex-end", gap: 1, height: 54 }}>
                      <div title={`Surplus: £${(t.profit||0).toLocaleString()}`} style={{ flex: 1, height: profH, background: "var(--favourable, #16a34a)", borderRadius: "2px 2px 0 0", opacity: 0.75 }} />
                      <div title={`Drawings: £${t.drawings.toLocaleString()}`} style={{ flex: 1, height: drawH, background: over ? "var(--adverse, #dc2626)" : "var(--primary, #2F62E8)", borderRadius: "2px 2px 0 0", opacity: 0.85 }} />
                    </div>
                    <span style={{ font: "600 9px var(--font-display)", color: "var(--fg-3)", textAlign: "center" }}>{t.m}</span>
                  </div>
                );
              })}
            </div>
            <div style={{ display: "flex", gap: 16, marginTop: 8, justifyContent: "flex-end" }}>
              <span style={{ display: "flex", alignItems: "center", gap: 4, font: "var(--text-body)", fontSize: 11, color: "var(--fg-3)" }}>
                <span style={{ width: 10, height: 10, borderRadius: 2, background: "var(--favourable, #16a34a)", opacity: 0.75, display: "inline-block" }} />Surplus
              </span>
              <span style={{ display: "flex", alignItems: "center", gap: 4, font: "var(--text-body)", fontSize: 11, color: "var(--fg-3)" }}>
                <span style={{ width: 10, height: 10, borderRadius: 2, background: "var(--primary, #2F62E8)", opacity: 0.85, display: "inline-block" }} />Drawings
              </span>
            </div>
          </div>
        );
      })()}

      {/* Smart highlights */}
      <SmartInsights movements={movements} kpis={kpis} isBvA={isBvA} />

      {/* Trend + AI commentary */}
      <div className="grid-2">
        {isBvA && trend && trend.length > 1 && (
          <Card
            title="Actual vs Budget trend"
            sub={`${trend.length} periods · Actual (solid) vs Budget (dashed)`}
            action={<Chip tone="info" icon="line-chart">Trend</Chip>}>
            <div style={{ marginBottom: 16 }}>
              <div style={{ font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", marginBottom: 6 }}>
                Revenue — Actual vs Budget
              </div>
              <TrendChart data={trend} series={[
                { key: "actual_revenue", label: "Actual", color: "var(--c-1)" },
                { key: "budget_revenue", label: "Budget", color: "var(--c-1)", dashed: true },
              ]} />
            </div>
            <div>
              <div style={{ font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", marginBottom: 6 }}>
                Profit — Actual vs Budget
              </div>
              <TrendChart data={trend} series={[
                { key: "actual_profit", label: "Actual", color: "var(--c-5)" },
                { key: "budget_profit", label: "Budget", color: "var(--c-5)", dashed: true },
              ]} />
            </div>
          </Card>
        )}
        {!isBvA && (
          <Card
            title="Revenue vs costs vs profit"
            sub={`Last ${(trend || []).length} periods · ${periodMode}`}
            action={<Chip tone="info" icon="line-chart">Trend</Chip>}>
            {trend && trend.length > 1 ? (
              <TrendChart data={trend} series={[
                { key: "revenue", label: "Revenue", color: "var(--c-1)" },
                { key: "costs",   label: "Costs",   color: "var(--c-6)" },
                { key: "profit",  label: "Profit",  color: "var(--c-5)" },
              ]} onPointClick={openSpotlight} />
            ) : (
              <div className="loading">Not enough periods for a trend chart</div>
            )}
          </Card>
        )}
        <Card
          title="AI commentary"
          className="card-ai"
          sub={isBvA ? "Budget vs Actual · figures from your ledger" : `${period?.label || selected_period} · figures from your ledger`}
          action={
            <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
              {hasEdits && (
                <button onClick={_resetComm} title="Restore AI-generated commentary"
                  style={{
                    display: "inline-flex", alignItems: "center", gap: 4,
                    background: "none", border: "1px solid var(--border-strong)",
                    borderRadius: "var(--radius-xs)", padding: "3px 8px", cursor: "pointer",
                    font: "var(--text-label)", fontSize: 11, color: "var(--fg-3)",
                  }}>
                  <Icon name="rotate-ccw" size={10} /> Reset
                </button>
              )}
              {commSaveState === "saving" && (
                <span style={{ font: "var(--text-label)", fontSize: 11, color: "var(--fg-3)" }}>Saving…</span>
              )}
              {commSaveState === "saved" && (
                <span style={{ font: "var(--text-label)", fontSize: 11, color: "var(--favourable-text)" }}>✓ Saved</span>
              )}
              {hasEdits && commSaveState === "idle" && (
                <span style={{
                  font: "var(--text-label)", fontSize: 10, color: "var(--primary-text)",
                  background: "var(--primary-soft)", borderRadius: 20, padding: "2px 8px",
                }}>Edited</span>
              )}
              {commItems.length > 0 && commSaveState === "idle" && (
                <button
                  onClick={() => {
                    const text = commItems.map(b => "• " + b.text).join("\n");
                    navigator.clipboard.writeText(text).catch(() => {});
                    setCommentaryCopied(true);
                    setTimeout(() => setCommentaryCopied(false), 2000);
                  }}
                  title="Copy all commentary bullets"
                  style={{
                    display: "inline-flex", alignItems: "center", gap: 5,
                    background: "none", border: "1px solid var(--border-strong)",
                    borderRadius: "var(--radius-xs)", padding: "3px 8px", cursor: "pointer",
                    font: "var(--text-label)", fontSize: 11, color: "var(--fg-2)",
                  }}
                >
                  <Icon name={commentaryCopied ? "check" : "copy"} size={11} />
                  {commentaryCopied ? "Copied" : "Copy"}
                </button>
              )}
              <span className="ai-badge"><Icon name="sparkles" size={13} />AI</span>
            </div>
          }>
          <ul className="ai-list">
            {commItems.map((b, i) => (
              <li key={i} className="comm-row">
                <span className="ic">
                  <Icon name={b.icon} size={16} color={b.fav ? "var(--favourable)" : "var(--adverse)"} />
                </span>
                <textarea
                  className="comm-textarea"
                  value={b.text}
                  rows={1}
                  ref={el => { if (el) { el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } }}
                  onChange={e => _updateBullet(i, e.target.value)}
                  onInput={e => { e.target.style.height = "auto"; e.target.style.height = e.target.scrollHeight + "px"; }}
                  placeholder="Enter commentary…"
                />
                {b.source && !b.edited && (() => {
                  const src = b.source;
                  const tip = [
                    src.account,
                    `${src.actual_label || "Actual"}: ${fmtGBP(src.actual)}`,
                    src.comparison != null ? `${src.comparison_label || "Prior"}: ${fmtGBP(src.comparison)}` : null,
                    src.variance != null ? `Variance: ${fmtSignedGBP(src.variance)}${src.pct != null ? ` (${fmtPct(src.pct)})` : ""}` : null,
                    "— traced to your imported P&L",
                  ].filter(Boolean).join("\n");
                  return (
                    <span title={tip} style={{
                      flexShrink: 0, cursor: "help",
                      display: "inline-flex", alignItems: "center", gap: 3,
                      font: "var(--text-label)", fontSize: 9.5, textTransform: "uppercase", letterSpacing: ".04em",
                      color: "var(--favourable-text)", background: "var(--favourable-soft)",
                      border: "1px solid var(--favourable-border)", borderRadius: 20, padding: "1px 6px 1px 4px",
                      marginLeft: 6, verticalAlign: "middle",
                    }}>
                      <Icon name="shield-check" size={10} /> Source
                    </span>
                  );
                })()}
                <button className="comm-del" onClick={() => _deleteBullet(i)} title="Delete bullet">
                  <Icon name="x" size={12} color="var(--fg-3)" />
                </button>
              </li>
            ))}
          </ul>
          <button className="comm-add" onClick={_addBullet}>
            <Icon name="plus" size={12} color="var(--fg-3)" /> Add bullet
          </button>
        </Card>
      </div>

      {/* ── BvA Profit Variance Waterfall ──────────────────────────── */}
      {isBvA && bvaWaterfallReady && (
        <Card
          title="Profit Variance Waterfall"
          sub="How Budget Profit becomes Actual Profit — top account drivers"
          action={<Chip tone="info" icon="bar-chart-2">Waterfall</Chip>}
        >
          {/* Summary row */}
          <div style={{
            display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap",
            paddingBottom: 14, marginBottom: 14,
            borderBottom: "1px solid var(--border)",
          }}>
            <div>
              <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", fontSize: 10.5, marginBottom: 3 }}>
                Budget Profit
              </div>
              <div style={{ font: "var(--text-metric)", fontSize: 21, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>
                {fmtGBP(bvaProfKpi.prior)}
              </div>
            </div>
            <div style={{ color: "var(--fg-3)", fontSize: 20, flexShrink: 0 }}>→</div>
            <div>
              <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", fontSize: 10.5, marginBottom: 3 }}>
                Actual Profit
              </div>
              <div style={{ font: "var(--text-metric)", fontSize: 21, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>
                {fmtGBP(bvaProfKpi.value)}
              </div>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 4, marginLeft: 4 }}>
              <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", fontSize: 10.5 }}>Net Variance</div>
              <Delta fav={bvaProfKpi.variance >= 0} up={bvaProfKpi.variance >= 0}>
                {fmtSignedGBP(bvaProfKpi.variance)}
              </Delta>
            </div>
          </div>

          {/* Account-level waterfall chart */}
          <WaterfallChart
            prior={bvaProfKpi.prior}
            current={bvaProfKpi.value}
            bars={bvaWaterfallBars}
            priorLabel="Budget"
            currentLabel="Actual"
          />

          {/* Account legend — scrollable list with fav/adv colour coding */}
          <div style={{ marginTop: 16, display: "flex", flexDirection: "column", gap: 5 }}>
            {bvaWaterfallBars.map((b, i) => (
              <div key={i} style={{
                display: "flex", alignItems: "center", gap: 10,
                padding: "5px 10px", borderRadius: "var(--radius-sm)",
                background: b.fav ? "var(--favourable-soft)" : "var(--adverse-soft)",
              }}>
                <span style={{ flexShrink: 0 }}>
                  <Icon
                    name={b.fav ? "trending-up" : "trending-down"}
                    size={13}
                    color={b.fav ? "var(--favourable)" : "var(--adverse)"}
                  />
                </span>
                <span style={{ flex: 1, font: "var(--text-body)", fontSize: 13, color: "var(--ink)" }}>
                  {b.label}
                </span>
                <span style={{
                  font: "600 13px var(--font-mono)", fontVariantNumeric: "tabular-nums",
                  color: b.fav ? "var(--favourable-text)" : "var(--adverse-text)",
                  whiteSpace: "nowrap",
                }}>
                  {fmtSignedGBP(b.impact)}
                </span>
              </div>
            ))}
          </div>
        </Card>
      )}

      {/* Revenue + Expense splits */}
      <div className="grid-2b">
        <Card title="Revenue split" sub={period?.label || selected_period}>
          <div className="donut-wrap" style={{ display: "flex", gap: 22, alignItems: "center" }}>
            <Donut data={revSplit} total={fmtGBP(revTotal)} label="revenue" />
            <div className="legend" style={{ flex: 1, minWidth: 0 }}>
              {revSplit.map((it, i) => (
                <div className="lg" key={`${it.name}-${i}`}>
                  <span className="sw" style={{ background: it.c }} />
                  <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{it.name}</span>
                  <span className="v">{fmtGBP(it.value)}</span>
                  <span className="pct">{revTotal > 0 ? ((it.value / revTotal) * 100).toFixed(0) : 0}%</span>
                </div>
              ))}
            </div>
          </div>
        </Card>
        <Card title="Expense split" sub={period?.label || selected_period}>
          <div className="donut-wrap" style={{ display: "flex", gap: 22, alignItems: "center" }}>
            <Donut data={expSplit} total={fmtGBP(expTotal)} label="costs" />
            <div className="legend" style={{ flex: 1, minWidth: 0 }}>
              {expSplit.map((it, i) => (
                <div className="lg" key={`${it.name}-${i}`}>
                  <span className="sw" style={{ background: it.c }} />
                  <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{it.name}</span>
                  <span className="v">{fmtGBP(it.value)}</span>
                  <span className="pct">{expTotal > 0 ? ((it.value / expTotal) * 100).toFixed(0) : 0}%</span>
                </div>
              ))}
            </div>
          </div>
        </Card>
      </div>

      {/* ── Profit Drivers / Waterfall ─────────────────────────── */}
      {waterfall && (
        <Card
          title={isBvA ? "Budget vs Actual Profit Drivers" : "Profit Drivers"}
          sub={isBvA
            ? "What drove the difference between Actual and Budget profit?"
            : `${period?.label || selected_period} · vs ${period?.prior || "prior period"} · what drove the change?`}
          action={<Chip tone="info" icon="bar-chart-2">Waterfall</Chip>}
        >
          {/* ── Summary row ── */}
          <div style={{
            display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap",
            paddingBottom: 14, marginBottom: 14,
            borderBottom: "1px solid var(--border)",
          }}>
            {/* Prior profit */}
            <div>
              <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", fontSize: 10.5, marginBottom: 3 }}>
                {isBvA ? "Budget profit" : (period?.prior || "Prior period")}
              </div>
              <div style={{ font: "var(--text-metric)", fontSize: 21, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>
                {fmtGBP(waterfall.prior_profit)}
              </div>
            </div>

            <div style={{ color: "var(--fg-3)", fontSize: 20, flexShrink: 0 }}>→</div>

            {/* Current / Actual profit */}
            <div>
              <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", fontSize: 10.5, marginBottom: 3 }}>
                {isBvA ? "Actual profit" : (period?.label || selected_period)}
              </div>
              <div style={{ font: "var(--text-metric)", fontSize: 21, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>
                {fmtGBP(waterfall.current_profit)}
              </div>
            </div>

            {/* Net change delta */}
            <div style={{ display: "flex", flexDirection: "column", gap: 4, marginLeft: 4 }}>
              <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", fontSize: 10.5 }}>Net change</div>
              <Delta fav={waterfall.net_change >= 0} up={waterfall.net_change >= 0}>
                {fmtSignedGBP(waterfall.net_change)}
              </Delta>
            </div>

            {/* Largest driver pills */}
            <div style={{ marginLeft: "auto", display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
              {waterfall.largest_positive && (
                <span style={{
                  font: "var(--text-body-strong)", fontSize: 12, padding: "4px 11px",
                  borderRadius: "var(--radius-pill)",
                  background: "var(--favourable-soft)", color: "var(--favourable-text)",
                  display: "inline-flex", alignItems: "center", gap: 5, whiteSpace: "nowrap",
                }}>
                  <Icon name="trending-up" size={12} />
                  {waterfall.largest_positive.label}: {fmtSignedGBP(waterfall.largest_positive.impact)}
                </span>
              )}
              {waterfall.largest_negative && (
                <span style={{
                  font: "var(--text-body-strong)", fontSize: 12, padding: "4px 11px",
                  borderRadius: "var(--radius-pill)",
                  background: "var(--adverse-soft)", color: "var(--adverse-text)",
                  display: "inline-flex", alignItems: "center", gap: 5, whiteSpace: "nowrap",
                }}>
                  <Icon name="trending-down" size={12} />
                  {waterfall.largest_negative.label}: {fmtSignedGBP(waterfall.largest_negative.impact)}
                </span>
              )}
            </div>
          </div>

          {/* ── Category impact table + Waterfall chart ── */}
          <div style={{ display: "grid", gridTemplateColumns: "240px 1fr", gap: 24, alignItems: "start" }}>
            {/* Left: category impact table */}
            <div>
              {/* Section title + helper text */}
              <div style={{ marginBottom: 12 }}>
                <div style={{ font: "var(--text-label)", textTransform: "uppercase", letterSpacing: ".06em", color: "var(--fg-3)", fontSize: 10.5, marginBottom: 4 }}>
                  Impact on Profit
                </div>
                <div style={{ font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)", lineHeight: 1.4 }}>
                  Positive values improved profit.{" "}
                  Negative values reduced profit.
                </div>
              </div>

              <table style={{ width: "100%", borderCollapse: "collapse" }}>
                <tbody>
                  {waterfall.bars.map((b, i) => {
                    const det = (waterfall.category_detail || []).find(d => d.category === b.label);
                    const priorLbl   = period?.prior  || "Prior";
                    const currentLbl = period?.label  || "Current";

                    // ── Rich tooltip ──────────────────────────────────────
                    let tooltip = null;
                    if (det) {
                      const rawAbs   = Math.abs(Math.round(det.raw_variance));
                      const rawDir   = det.raw_variance >= 0 ? "increased" : "decreased";
                      if (det.is_revenue) {
                        tooltip = [
                          b.label,
                          `${priorLbl}: ${fmtGBP(det.prior)}`,
                          `${currentLbl}: ${fmtGBP(det.current)}`,
                          `Movement: revenue ${rawDir} by ${fmtGBP(rawAbs)}`,
                          `Profit impact: ${fmtSignedGBP(b.impact)}`,
                        ].join("\n");
                      } else {
                        const costDir  = det.raw_variance >= 0 ? "increased" : "decreased";
                        const effLabel = b.impact >= 0
                          ? "lower costs improved profit"
                          : "higher costs reduced profit";
                        tooltip = [
                          b.label,
                          `${priorLbl}: ${fmtGBP(det.prior)}`,
                          `${currentLbl}: ${fmtGBP(det.current)}`,
                          `Movement: costs ${costDir} by ${fmtGBP(rawAbs)}`,
                          `Profit impact: ${fmtSignedGBP(b.impact)} (${effLabel})`,
                        ].join("\n");
                      }
                    }

                    // ── Sub-label (plain-English explanation) ─────────────
                    let subLabel = null;
                    if (isBvA) {
                      if (b.label === "Revenue") {
                        subLabel = b.fav ? "Revenue above budget" : "Revenue below budget";
                      } else if (b.label === "Other Movements") {
                        subLabel = b.fav ? "Other items above budget (net)" : "Other items below budget (net)";
                      } else {
                        subLabel = b.fav ? "Costs below budget (favourable)" : "Costs above budget (adverse)";
                      }
                    } else if (b.label === "Revenue") {
                      subLabel = b.fav ? "Higher revenue improved profit" : "Lower revenue reduced profit";
                    } else if (b.label === "Other Movements") {
                      subLabel = b.fav ? "Net other movements improved profit" : "Net other movements reduced profit";
                    } else {
                      subLabel = b.fav ? "Lower costs improved profit" : "Higher costs reduced profit";
                    }

                    return (
                      <tr key={i} style={{ borderBottom: "1px solid var(--border)" }}
                          title={tooltip || undefined}>
                        <td style={{ padding: "8px 0 6px", cursor: tooltip ? "help" : "default" }}>
                          <div style={{ font: "var(--text-body)", fontSize: 13, color: "var(--fg-2)" }}>
                            {b.label}
                          </div>
                          <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 1, lineHeight: 1.3 }}>
                            {subLabel}
                          </div>
                        </td>
                        <td style={{
                          padding: "8px 0 6px", textAlign: "right", verticalAlign: "top",
                          font: "600 13px var(--font-mono)", fontVariantNumeric: "tabular-nums",
                          color: b.fav ? "var(--favourable-text)" : "var(--adverse-text)",
                          cursor: tooltip ? "help" : "default",
                          whiteSpace: "nowrap",
                        }}>
                          {fmtSignedGBP(b.impact)}
                        </td>
                      </tr>
                    );
                  })}

                  {/* Net total row */}
                  <tr>
                    <td style={{ padding: "10px 0 2px" }}>
                      <div style={{ font: "var(--text-body-strong)", fontSize: 13, color: "var(--ink)" }}>
                        Net profit impact
                      </div>
                    </td>
                    <td style={{
                      padding: "10px 0 2px", textAlign: "right",
                      font: "700 13.5px var(--font-mono)", fontVariantNumeric: "tabular-nums",
                      color: waterfall.net_change >= 0 ? "var(--favourable-text)" : "var(--adverse-text)",
                      borderTop: "1px solid var(--border-strong)",
                    }}>
                      {fmtSignedGBP(waterfall.net_change)}
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>

            {/* Right: SVG waterfall */}
            <div>
              <WaterfallChart
                prior={waterfall.prior_profit}
                current={waterfall.current_profit}
                bars={waterfall.bars}
                priorLabel={shortLabel(period?.prior)}
                currentLabel={shortLabel(period?.label)}
              />
            </div>
          </div>

          {/* ── AI commentary ── */}
          <div style={{
            marginTop: 14,
            padding: "11px 15px",
            background: "var(--primary-soft)",
            borderLeft: "3px solid var(--primary)",
            borderRadius: "var(--radius-sm)",
            display: "flex", gap: 10, alignItems: "flex-start",
            font: "var(--text-body)", fontSize: 13.5, color: "var(--fg-2)", lineHeight: 1.6,
          }}>
            <span style={{ flexShrink: 0, marginTop: 1 }}><Icon name="sparkles" size={14} color="var(--primary)" /></span>
            <span>{waterfall.commentary}</span>
          </div>
        </Card>
      )}

      {/* ── Variance movements table — tabbed ─────────────────── */}
      <Card
        title="Variance movements"
        sub={isBvA ? "Actual vs Budget · sorted by absolute variance" : `${period?.label || selected_period} vs prior period`}>
        <div style={{
          display: "flex", gap: 6, marginBottom: 16,
          borderBottom: "1px solid var(--border)", paddingBottom: 0,
        }}>
          {tabs.map((t) => (
            <button key={t} onClick={() => setActiveTab(t)}
              style={{
                font: "var(--text-body-strong)", fontSize: 13, padding: "8px 14px",
                border: "none", background: "transparent",
                color: activeTab === t ? "var(--primary-text)" : "var(--fg-3)",
                borderBottom: activeTab === t ? "2px solid var(--primary)" : "2px solid transparent",
                cursor: "pointer", textTransform: "capitalize",
              }}>
              {t === "commentary" ? "Commentary"
                : t === "revenue" ? "Revenue"
                : t === "costs"   ? "Costs"
                : "Top movements"}
            </button>
          ))}
        </div>

        {activeTab !== "commentary" && (
          <div style={{ overflowX: "auto" }}>
            <table className="var">
              <thead>
                <tr>
                  <th className="l">Account</th>
                  <th className="l">Category</th>
                  <th>{isBvA ? "Actual" : "Current"}</th>
                  <th>{isBvA ? "Budget" : "Prior"}</th>
                  <th>Variance</th>
                  <th>Var %</th>
                </tr>
              </thead>
              <tbody>
                {(() => {
                const rows = (movements || [])
                  .filter((m) => {
                    if (activeTab === "revenue") return m.category === "Revenue";
                    if (activeTab === "costs")   return m.category !== "Revenue";
                    return true;
                  })
                  .slice(0, 15);
                const maxAbsVar = Math.max(...rows.map(r => Math.abs(r.variance || 0)), 1);
                return rows.map((m, i) => (
                    <tr key={i}>
                      <td className="l">
                        {m.account}
                        {m.is_locum && (
                          <span style={{ marginLeft: 6, font: "var(--text-label)", fontSize: 9.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".05em", padding: "1px 5px", borderRadius: 10, background: "var(--caution-soft, #fff8e1)", color: "var(--caution-text, #b45309)", border: "1px solid var(--caution, #f59e0b)", whiteSpace: "nowrap" }}>Locum</span>
                        )}
                      </td>
                      <td className="l">
                        {(() => {
                          const chip = window.catChip && window.catChip(m.category);
                          return chip ? (
                            <span style={{
                              display: "inline-flex", alignItems: "center",
                              font: "var(--text-label)", fontSize: 10.5, fontWeight: 600,
                              padding: "2px 8px", borderRadius: 20,
                              background: chip.bg, color: chip.color, whiteSpace: "nowrap",
                            }}>{m.category}</span>
                          ) : m.category;
                        })()}
                      </td>
                      <td>{fmtGBP(m.value)}</td>
                      <td>{fmtGBP(m.prior_value)}</td>
                      <td className={m.is_fav ? "fav" : m.variance !== 0 ? "adv" : ""}>
                        <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
                          {/* impact bar: magnitude relative to the largest movement shown */}
                          <span aria-hidden="true" style={{ width: 44, height: 4, borderRadius: 2,
                            background: "var(--surface-2)", overflow: "hidden", flexShrink: 0 }}>
                            <span style={{ display: "block", height: "100%", borderRadius: 2,
                              width: `${Math.max(Math.round(Math.abs(m.variance || 0) / maxAbsVar * 100), m.variance ? 4 : 0)}%`,
                              background: m.is_fav ? "var(--favourable)" : "var(--adverse)",
                              opacity: 0.75 }} />
                          </span>
                          {fmtSignedGBP(m.variance)}
                        </span>
                      </td>
                      <td className={m.is_fav ? "fav" : m.variance !== 0 ? "adv" : ""}>
                        {fmtPct(m.variance_pct)}
                      </td>
                    </tr>
                  ));
                })()}
              </tbody>
            </table>
          </div>
        )}

        {activeTab === "commentary" && (
          <>
            <ul className="ai-list">
              {commItems.map((b, i) => (
                <li key={i} className="comm-row">
                  <span className="ic">
                    <Icon name={b.icon} size={16} color={b.fav ? "var(--favourable)" : "var(--adverse)"} />
                  </span>
                  <textarea
                    className="comm-textarea"
                    value={b.text}
                    rows={1}
                    ref={el => { if (el) { el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } }}
                    onChange={e => _updateBullet(i, e.target.value)}
                    onInput={e => { e.target.style.height = "auto"; e.target.style.height = e.target.scrollHeight + "px"; }}
                    placeholder="Enter commentary…"
                  />
                  <button className="comm-del" onClick={() => _deleteBullet(i)} title="Delete bullet">
                    <Icon name="x" size={12} color="var(--fg-3)" />
                  </button>
                </li>
              ))}
            </ul>
            <button className="comm-add" onClick={_addBullet}>
              <Icon name="plus" size={12} color="var(--fg-3)" /> Add bullet
            </button>
          </>
        )}
      </Card>

      {/* ── Rolling Forecast ─────────────────────────────────── */}
      {!isBvA && periodMode !== "ytd" && (trend || []).length > 2 && (
        <ForecastPanel sessionId={sessionId} periodMode={periodMode} />
      )}

      {/* ── Statistical anomaly detection (secondary — requires history) ── */}
      {!isBvA && periodMode !== "ytd" && (
        <AnomalyPanel
          sessionId={sessionId}
          selectedPeriod={selected_period}
          periodMode={periodMode}
        />
      )}

      {/* ── Cash & Runway (supplementary — requires cash balance input) ── */}
      <CashRunway
        trend={trend}
        periodMode={periodMode}
        sessionId={sessionId}
        isBvA={isBvA}
        xeroCash={data.xero_cash}
      />

      {/* ── Period spotlight modal ── */}
      {spotlight && (
        <SpotlightModal spotlight={spotlight} onClose={() => setSpotlight(null)} />
      )}

      {/* ── Income-stream classification review drawer ── */}
      {classifyOpen && (
        <ClassificationDrawer
          sessionId={sessionId}
          onClose={() => setClassifyOpen(false)}
          onSaved={() => fetchPeriod(data.selected_period, periodMode)}
        />
      )}
    </div>
  );
}
Object.assign(window, { Dashboard });
