/* MonthEndIQ — Practice Portfolio (firm mode): multi-client month-end triage */

// ── Firm token — persisted per browser so the same firm's clients reload ──
function getFirmToken() {
  let t = localStorage.getItem("meiq_firm_token");
  if (!t) { t = crypto.randomUUID(); localStorage.setItem("meiq_firm_token", t); }
  return t;
}

const SECTORS = [
  "Accountancy", "Construction", "E-commerce", "Hospitality",
  "Manufacturing", "NHS GP Practice", "NHS PCN", "NHS Federation", "Professional services",
  "Property", "Retail", "SaaS", "Other",
];

// Maps display sector to the internal sector value used by the API
const SECTOR_VALUE_MAP = {
  "NHS GP Practice": "nhs_gp",
  "NHS PCN":         "nhs_pcn",
  "NHS Federation":  "nhs_federation",
};

// Sectors that already get a prominent pill badge in the row header — their
// internal identifier must never be printed as raw text alongside it.
const BADGED_SECTORS = new Set(["nhs_gp", "nhs_pcn", "nhs_federation"]);

// Human-readable label for an internal sector value. Never surface the raw
// identifier (e.g. "nhs_gp") in the UI — fall back to a title-cased version.
function sectorLabel(sector) {
  if (!sector) return "";
  const map = {
    nhs_gp: "GP Practice", nhs_pcn: "PCN", nhs_federation: "NHS Federation",
    general: "General", other: "Other",
  };
  const key = String(sector).toLowerCase();
  if (map[key]) return map[key];
  return String(sector).replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
}

const TIER = {
  action:  { label: "Action needed", color: "var(--adverse-text)",    bg: "var(--adverse-soft)",    border: "var(--adverse-border)",    icon: "alert-octagon" },
  watch:   { label: "Watch",         color: "var(--caution-text, #b45309)", bg: "var(--caution-soft, #fef3c7)", border: "var(--caution-border, #fcd34d)", icon: "eye" },
  healthy: { label: "Healthy",       color: "var(--favourable-text)", bg: "var(--favourable-soft)", border: "var(--favourable-border)", icon: "check-circle" },
};

const COMPARE_COLORS = ["var(--c-1)", "var(--c-5)", "var(--c-4)", "var(--c-7)"];

// NHS entity badge — one component for practice / PCN / federation, drawn from
// theme tokens so the NHS-blue tint inverts for dark mode instead of glaring.
function SectorBadge({ sector }) {
  const label = { nhs_gp: "GP Practice", nhs_pcn: "PCN",
                  nhs_federation: "NHS Federation" }[sector];
  if (!label) return null;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 4,
      font: "var(--text-label)", fontSize: 10, fontWeight: 700,
      textTransform: "uppercase", letterSpacing: ".04em",
      color: "var(--nhs-badge-text)", background: "var(--nhs-badge-bg)",
      border: "1px solid var(--nhs-badge-border)",
      borderRadius: 20, padding: "2px 8px", flexShrink: 0,
    }}>{label}</span>
  );
}

// ── Add-client modal ───────────────────────────────────────────────────────
function AddClientModal({ firmToken, onClose, onAdded }) {
  const { Icon } = window;
  const [name, setName]       = React.useState("");
  const [sector, setSector]   = React.useState("Other");
  const [listSize, setListSize] = React.useState("");
  const [odsCode, setOdsCode] = React.useState("");
  const [cash, setCash]       = React.useState("");
  const [file, setFile]       = React.useState(null);
  const [status, setStatus]   = React.useState("idle"); // idle | uploading | error
  const [errMsg, setErrMsg]   = React.useState("");
  const fileRef               = React.useRef();

  const isNhsGp    = sector === "NHS GP Practice";
  const isNhsBadged = ["NHS GP Practice", "NHS PCN", "NHS Federation"].includes(sector);
  const canSubmit  = name.trim() && file && status !== "uploading";
  const apiSector  = SECTOR_VALUE_MAP[sector] || sector.toLowerCase().replace(/\s+/g, "_");

  async function submit(e) {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("uploading");
    setErrMsg("");
    const fd = new FormData();
    fd.append("firm_token",   firmToken);
    fd.append("name",         name.trim());
    fd.append("sector",       apiSector);
    fd.append("cash_balance", cash ? parseFloat(cash) : 0);
    fd.append("list_size",    isNhsGp && listSize ? parseInt(listSize, 10) || 0 : 0);
    if (odsCode.trim()) fd.append("ods_code", odsCode.trim());
    fd.append("file",         file);
    try {
      const r = await fetch(apiUrl("/api/portfolio/clients"), { method: "POST", body: fd });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error(window.apiErrorText(j, `Error ${r.status}`));
      }
      const client = await r.json();
      onAdded(client);
    } catch (ex) {
      setErrMsg(ex.message);
      setStatus("error");
    }
  }

  // Close on Escape
  React.useEffect(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [onClose]);

  const inputStyle = {
    width: "100%", padding: "9px 12px", fontSize: 13.5,
    border: "1px solid var(--border-strong)", borderRadius: "var(--radius-sm)",
    background: "var(--surface)", color: "var(--ink)", outline: "none",
    boxSizing: "border-box",
  };
  const labelStyle = { font: "var(--text-label)", fontSize: 11, fontWeight: 600,
    textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)",
    display: "block", marginBottom: 5 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20, padding: "28px 28px 24px",
        width: "100%", maxWidth: 480, boxShadow: "var(--shadow-hover)",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 22 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>Add client</h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>

        <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div>
            <label style={labelStyle}>Client name *</label>
            <input value={name} onChange={e => setName(e.target.value)}
              placeholder="e.g. Harbour Retail Ltd" style={inputStyle} autoFocus />
          </div>

          <div>
            <label style={labelStyle}>Sector</label>
            <select value={sector} onChange={e => setSector(e.target.value)} style={inputStyle}>
              {SECTORS.map(s => <option key={s} value={s}>{s}</option>)}
            </select>
          </div>

          {isNhsBadged && (
            <div>
              <label style={labelStyle}>ODS code (optional)</label>
              <input value={odsCode} onChange={e => setOdsCode(e.target.value.toUpperCase())}
                placeholder="e.g. A81001" maxLength={9} style={inputStyle} />
              <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 4 }}>
                The organisation code the ICB, PCSE and NHS Digital use for this
                practice or PCN. Lets reports be reconciled against their data.
              </div>
            </div>
          )}

          {isNhsGp && (
            <div>
              <label style={labelStyle}>Weighted list size (registered patients)</label>
              <input type="number" min="0" step="100" value={listSize} onChange={e => setListSize(e.target.value)}
                placeholder="e.g. 8500" style={inputStyle} />
              <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 4 }}>
                Used for per-patient benchmarking. Leave blank if unknown.
              </div>
            </div>
          )}

          <div>
            <label style={labelStyle}>Cash balance (optional — for runway)</label>
            <input type="number" min="0" step="1000" value={cash} onChange={e => setCash(e.target.value)}
              placeholder="e.g. 250000" style={inputStyle} />
            <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 4 }}>
              Used to calculate months of runway. Leave blank if unknown.
            </div>
          </div>

          <div>
            <label style={labelStyle}>P&amp;L file * (Excel or CSV, MoM format)</label>
            <div
              onClick={() => fileRef.current?.click()}
              onDragOver={e => e.preventDefault()}
              onDrop={e => { e.preventDefault(); setFile(e.dataTransfer.files[0]); }}
              style={{
                border: `2px dashed ${file ? "var(--primary)" : "var(--border-strong)"}`,
                borderRadius: "var(--radius-sm)", padding: "18px 16px", textAlign: "center",
                cursor: "pointer", background: file ? "var(--primary-soft)" : "var(--surface-2)",
                transition: "all .15s",
              }}
            >
              <Icon name={file ? "check-circle" : "upload-cloud"} size={20}
                style={{ color: file ? "var(--primary-text)" : "var(--fg-3)", marginBottom: 6 }} />
              <div style={{ font: "var(--text-body)", fontSize: 13, color: file ? "var(--primary-text)" : "var(--fg-2)" }}>
                {file ? file.name : "Click or drag file here"}
              </div>
              {!file && <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 3 }}>
                .xlsx, .xls, .csv
              </div>}
            </div>
            <input ref={fileRef} type="file" accept=".csv,.xlsx,.xls" style={{ display: "none" }}
              onChange={e => setFile(e.target.files[0])} />
          </div>

          {errMsg && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", font: "var(--text-body)", fontSize: 12.5, lineHeight: 1.5,
              whiteSpace: "pre-line", maxHeight: 200, overflowY: "auto" }}>
              {errMsg}
            </div>
          )}

          <div style={{ display: "flex", gap: 10, justifyContent: "flex-end", marginTop: 4 }}>
            <button type="button" onClick={onClose} style={{
              padding: "9px 18px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", font: "var(--text-body)", fontSize: 13.5, cursor: "pointer",
            }}>Cancel</button>
            <button type="submit" disabled={!canSubmit} style={{
              padding: "9px 20px", borderRadius: "var(--radius-sm)", border: "none",
              background: canSubmit ? "var(--primary)" : "var(--border-strong)",
              color: canSubmit ? "#fff" : "var(--fg-3)",
              font: "var(--text-body-strong)", fontSize: 13.5, cursor: canSubmit ? "pointer" : "default",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              {status === "uploading"
                ? <React.Fragment><div className="spinner" style={{ width: 14, height: 14 }} /> Analysing…</React.Fragment>
                : <React.Fragment><Icon name="plus" size={14} /> Add client</React.Fragment>}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ── Update-cash modal (lightweight re-upload or cash edit) ─────────────────
function UpdateCashModal({ client, firmToken, onClose, onUpdated }) {
  const { Icon } = window;
  const [cash, setCash]     = React.useState(client.cash ? String(client.cash) : "");
  const [file, setFile]     = React.useState(null);
  const [status, setStatus] = React.useState("idle");
  const [errMsg, setErrMsg] = React.useState("");
  const fileRef             = React.useRef();

  async function submit(e) {
    e.preventDefault();
    setStatus("uploading"); setErrMsg("");
    const fd = new FormData();
    fd.append("firm_token",   firmToken);
    fd.append("cash_balance", cash ? parseFloat(cash) : 0);
    if (file) fd.append("file", file);
    try {
      const r = await fetch(apiUrl(`/api/portfolio/clients/${client.session_id}`), { method: "PUT", body: fd });
      if (!r.ok) { const j = await r.json().catch(() => ({})); throw new Error(window.apiErrorText(j, `Error ${r.status}`)); }
      onUpdated(await r.json());
    } catch (ex) { setErrMsg(ex.message); setStatus("error"); }
  }

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

  const inputStyle = { width: "100%", padding: "9px 12px", fontSize: 13.5,
    border: "1px solid var(--border-strong)", borderRadius: "var(--radius-sm)",
    background: "var(--surface)", color: "var(--ink)", outline: "none", boxSizing: "border-box" };
  const labelStyle = { font: "var(--text-label)", fontSize: 11, fontWeight: 600,
    textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", display: "block", marginBottom: 5 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20, padding: "28px 28px 24px",
        width: "100%", maxWidth: 400, boxShadow: "var(--shadow-hover)",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 22 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>
            Update — {client.name}
          </h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>
        <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div>
            <label style={labelStyle}>Cash balance</label>
            <input type="number" min="0" step="1000" value={cash} onChange={e => setCash(e.target.value)}
              placeholder="e.g. 250000" style={inputStyle} autoFocus />
          </div>
          <div>
            <label style={labelStyle}>New P&amp;L file (optional — replaces current)</label>
            <div onClick={() => fileRef.current?.click()}
              onDragOver={e => e.preventDefault()}
              onDrop={e => { e.preventDefault(); setFile(e.dataTransfer.files[0]); }}
              style={{
                border: `2px dashed ${file ? "var(--primary)" : "var(--border-strong)"}`,
                borderRadius: "var(--radius-sm)", padding: "14px 16px", textAlign: "center",
                cursor: "pointer", background: file ? "var(--primary-soft)" : "var(--surface-2)",
              }}>
              <div style={{ font: "var(--text-body)", fontSize: 13, color: file ? "var(--primary-text)" : "var(--fg-2)" }}>
                {file ? file.name : "Drop new file here to re-analyse"}
              </div>
            </div>
            <input ref={fileRef} type="file" accept=".csv,.xlsx,.xls" style={{ display: "none" }}
              onChange={e => setFile(e.target.files[0])} />
          </div>
          {errMsg && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", fontSize: 12.5, lineHeight: 1.5,
              whiteSpace: "pre-line", maxHeight: 200, overflowY: "auto" }}>
              {errMsg}
            </div>
          )}
          <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
            <button type="button" onClick={onClose} style={{
              padding: "9px 18px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", fontSize: 13.5, cursor: "pointer",
            }}>Cancel</button>
            <button type="submit" disabled={status === "uploading"} style={{
              padding: "9px 20px", borderRadius: "var(--radius-sm)", border: "none",
              background: "var(--primary)", color: "var(--on-primary)", fontSize: 13.5, cursor: "pointer",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              {status === "uploading"
                ? <React.Fragment><div className="spinner" style={{ width: 14, height: 14 }} /> Saving…</React.Fragment>
                : <React.Fragment><Icon name="save" size={14} /> Save</React.Fragment>}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ── Create Neighbourhood modal ─────────────────────────────────────────────
function CreateNeighbourhoodModal({ firmToken, nhsClients, onClose, onCreated }) {
  const { Icon } = window;
  const [name, setName]       = React.useState("");
  const [selected, setSelected] = React.useState(new Set());
  const [kind, setKind]       = React.useState("neighbourhood"); // neighbourhood | locality | place | icb
  const [status, setStatus]   = React.useState("idle");
  const [errMsg, setErrMsg]   = React.useState("");

  const isGrouping = kind !== "neighbourhood"; // grouping tiers hold sub-groups, not practices directly

  const toggle = (id) => setSelected(prev => {
    const next = new Set(prev);
    next.has(id) ? next.delete(id) : next.add(id);
    return next;
  });

  // A neighbourhood needs practices; a grouping tier (locality/place/ICB) can be
  // created empty and have sub-groups nested under it afterwards.
  const canSubmit = name.trim() && (isGrouping || selected.size >= 1) && status !== "loading";

  async function submit(e) {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("loading"); setErrMsg("");
    try {
      const r = await fetch(apiUrl("/api/portfolio/neighbourhoods"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          firm_token: firmToken, name: name.trim(),
          client_ids: isGrouping ? [] : [...selected],
          level: isGrouping ? kind : null,
        }),
      });
      if (!r.ok) { const j = await r.json().catch(() => ({})); throw new Error(j.detail || `Error ${r.status}`); }
      onCreated(await r.json());
    } catch (ex) { setErrMsg(ex.message); setStatus("error"); }
  }

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

  const inputStyle = { width: "100%", padding: "9px 12px", fontSize: 13.5,
    border: "1px solid var(--border-strong)", borderRadius: "var(--radius-sm)",
    background: "var(--surface)", color: "var(--ink)", outline: "none", boxSizing: "border-box" };
  const labelStyle = { font: "var(--text-label)", fontSize: 11, fontWeight: 600,
    textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", display: "block", marginBottom: 5 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20, padding: "28px 28px 24px",
        width: "100%", maxWidth: 500, boxShadow: "var(--shadow-hover)", maxHeight: "90vh", overflow: "auto",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 22 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>
            Create neighbourhood
          </h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>

        <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div>
            <label style={labelStyle}>Type</label>
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
              {[
                { k: "neighbourhood", label: "Neighbourhood" },
                { k: "locality",      label: "Locality" },
                { k: "place",         label: "Place" },
                { k: "icb",           label: "ICB" },
              ].map(opt => (
                <button type="button" key={opt.k} onClick={() => setKind(opt.k)}
                  style={{ padding: "6px 12px", borderRadius: "var(--radius-sm)", fontSize: 12.5, cursor: "pointer",
                    border: kind === opt.k ? "1px solid var(--primary)" : "1px solid var(--border-strong)",
                    background: kind === opt.k ? "var(--primary-soft,#eff6ff)" : "var(--surface)",
                    color: kind === opt.k ? "var(--primary-text)" : "var(--fg-2)" }}>
                  {opt.label}
                </button>
              ))}
            </div>
            <div style={{ fontSize: 11, color: "var(--fg-3)", marginTop: 5 }}>
              {isGrouping
                ? "A grouping tier — create it empty, then nest neighbourhoods (or lower tiers) under it to build an ICB-level rollup."
                : "A neighbourhood groups practices directly for borough-level reporting."}
            </div>
          </div>

          <div>
            <label style={labelStyle}>{isGrouping ? "Name *" : "Neighbourhood name *"}</label>
            <input value={name} onChange={e => setName(e.target.value)}
              placeholder={isGrouping ? "e.g. North East London ICB" : "e.g. North Islington Neighbourhood"} style={inputStyle} autoFocus />
          </div>

          {!isGrouping && (
          <div>
            <label style={labelStyle}>Select PCNs * ({selected.size} selected)</label>
            <div style={{ border: "1px solid var(--border-strong)", borderRadius: "var(--radius-sm)",
                          maxHeight: 200, overflowY: "auto" }}>
              {nhsClients.length === 0 && (
                <div style={{ padding: "14px 16px", color: "var(--fg-3)", fontSize: 13 }}>
                  No NHS GP clients uploaded yet. Add NHS GP Practice clients first.
                </div>
              )}
              {nhsClients.map((c, i) => (
                <label key={c.session_id} style={{
                  display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", cursor: "pointer",
                  borderBottom: i < nhsClients.length - 1 ? "1px solid var(--border)" : "none",
                  background: selected.has(c.session_id) ? "var(--primary-soft,#eff6ff)" : "transparent",
                }}>
                  <input type="checkbox" checked={selected.has(c.session_id)} onChange={() => toggle(c.session_id)}
                    style={{ width: 15, height: 15, accentColor: "var(--primary)" }} />
                  <span style={{ flex: 1, font: "500 13px/1.3 var(--font-display)", color: "var(--ink)" }}>
                    {c.name}
                  </span>
                  {c.list_size > 0 && (
                    <span style={{ fontSize: 12, color: "var(--fg-3)" }}>{c.list_size.toLocaleString()} pts</span>
                  )}
                </label>
              ))}
            </div>
            <div style={{ fontSize: 11, color: "var(--fg-3)", marginTop: 5 }}>
              Each PCN keeps its own books — data is never merged.
            </div>
          </div>
          )}

          {errMsg && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", fontSize: 12.5 }}>
              {errMsg}
            </div>
          )}

          <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
            <button type="button" onClick={onClose} style={{
              padding: "9px 18px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", fontSize: 13.5, cursor: "pointer",
            }}>Cancel</button>
            <button type="submit" disabled={!canSubmit} style={{
              padding: "9px 20px", borderRadius: "var(--radius-sm)", border: "none",
              background: canSubmit ? "var(--primary)" : "var(--border-strong)",
              color: canSubmit ? "#fff" : "var(--fg-3)",
              fontSize: 13.5, cursor: canSubmit ? "pointer" : "default",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              {status === "loading"
                ? <React.Fragment><div className="spinner" style={{ width: 14, height: 14 }} /> Creating…</React.Fragment>
                : <React.Fragment><Icon name="map-pin" size={14} /> Create</React.Fragment>}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}


// ── Bulk QOF entitlement import ───────────────────────────────────────────
// Lets a federation paste "practice, entitlement" lines to maintain many QOF
// entitlements in one action instead of editing each practice individually.
function BulkQofModal({ firmToken, nhsClients, onClose, onDone }) {
  const { Icon } = window;
  const [text, setText]     = React.useState("");
  const [status, setStatus] = React.useState("idle"); // idle | loading | done | error
  const [errMsg, setErrMsg] = React.useState("");
  const [result, setResult] = React.useState(null);

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

  // Parse "Name, 123456" / "Name<tab>123456" lines. Last numeric field is the
  // entitlement; everything before it is the practice name.
  function parseRows() {
    const rows = [];
    for (const raw of text.split("\n")) {
      const line = raw.trim();
      if (!line) continue;
      const parts = line.split(/[,\t]/).map(s => s.trim());
      if (parts.length < 2) continue;
      const val = parseFloat(parts[parts.length - 1].replace(/[£,\s]/g, ""));
      const name = parts.slice(0, -1).join(", ").trim();
      if (!name || !isFinite(val) || val < 0) continue;
      rows.push({ name, qof_entitlement: val });
    }
    return rows;
  }

  const rows = parseRows();
  const canSubmit = rows.length > 0 && status !== "loading";

  async function submit(e) {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("loading"); setErrMsg("");
    try {
      const r = await fetch(apiUrl("/api/portfolio/qof-bulk"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ firm_token: firmToken, rows }),
      });
      if (!r.ok) { const j = await r.json().catch(() => ({})); throw new Error(j.detail || `Error ${r.status}`); }
      const res = await r.json();
      setResult(res); setStatus("done");
      onDone(res);
    } catch (ex) { setErrMsg(ex.message); setStatus("error"); }
  }

  const inputStyle = { width: "100%", padding: "9px 12px", fontSize: 13, fontFamily: "var(--font-mono)",
    border: "1px solid var(--border-strong)", borderRadius: "var(--radius-sm)",
    background: "var(--surface)", color: "var(--ink)", outline: "none", boxSizing: "border-box", resize: "vertical" };
  const labelStyle = { font: "var(--text-label)", fontSize: 11, fontWeight: 600,
    textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", display: "block", marginBottom: 5 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20, padding: "28px 28px 24px",
        width: "100%", maxWidth: 520, boxShadow: "var(--shadow-hover)", maxHeight: "90vh", overflow: "auto",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>
            Bulk QOF entitlements
          </h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>
        <p style={{ margin: "0 0 16px", font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-3)" }}>
          Paste one practice per line as <code>Practice name, entitlement</code>. Rows are matched
          to practices by name (case-insensitive).
        </p>

        <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <div>
            <label style={labelStyle}>Practice, entitlement (£)</label>
            <textarea value={text} onChange={e => setText(e.target.value)} rows={7}
              placeholder={"Riverside Surgery, 188000\nOakwood Medical Centre, 152500"}
              style={inputStyle} autoFocus />
            <div style={{ fontSize: 11, color: "var(--fg-3)", marginTop: 5 }}>
              {rows.length} valid row{rows.length === 1 ? "" : "s"} detected
              {nhsClients?.length ? ` · ${nhsClients.length} NHS GP practices in portfolio` : ""}
            </div>
          </div>

          {result && status === "done" && (
            <div style={{ padding: "10px 14px", background: "var(--favourable-soft)", border: "1px solid var(--favourable-border)",
              borderRadius: "var(--radius-sm)", color: "var(--favourable-text)", fontSize: 12.5 }}>
              Updated {result.updated} of {result.total}.
              {result.unmatched?.length > 0 && (
                <span style={{ color: "var(--adverse-text)" }}> Unmatched: {result.unmatched.join(", ")}.</span>
              )}
            </div>
          )}
          {errMsg && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", fontSize: 12.5 }}>
              {errMsg}
            </div>
          )}

          <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
            <button type="button" onClick={onClose} style={{
              padding: "9px 18px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", fontSize: 13.5, cursor: "pointer",
            }}>{status === "done" ? "Close" : "Cancel"}</button>
            <button type="submit" disabled={!canSubmit} style={{
              padding: "9px 20px", borderRadius: "var(--radius-sm)", border: "none",
              background: canSubmit ? "var(--primary)" : "var(--border-strong)",
              color: canSubmit ? "#fff" : "var(--fg-3)",
              fontSize: 13.5, cursor: canSubmit ? "pointer" : "default",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              {status === "loading"
                ? <React.Fragment><div className="spinner" style={{ width: 14, height: 14 }} /> Applying…</React.Fragment>
                : <React.Fragment><Icon name="upload" size={14} /> Apply</React.Fragment>}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}


// ── Bulk multi-file client upload ─────────────────────────────────────────
// Onboard many practices in one drag-and-drop: one client per file. PCSE
// payment statements are auto-detected server-side and converted.
function BulkUploadModal({ firmToken, onClose, onDone }) {
  const { Icon } = window;
  const [files, setFiles]   = React.useState([]);
  const [sector, setSector] = React.useState("nhs_gp");
  const [status, setStatus] = React.useState("idle"); // idle | uploading | done | error
  const [result, setResult] = React.useState(null);
  const [errMsg, setErrMsg] = React.useState("");

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

  const canSubmit = files.length > 0 && status !== "uploading";

  async function submit(e) {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("uploading"); setErrMsg("");
    try {
      const fd = new FormData();
      fd.append("firm_token", firmToken);
      fd.append("sector", sector);
      for (const f of files) fd.append("files", f);
      const r = await fetch(apiUrl("/api/portfolio/clients/bulk"), { method: "POST", body: fd });
      if (!r.ok) { const j = await r.json().catch(() => ({})); throw new Error(j.detail || `Error ${r.status}`); }
      const res = await r.json();
      setResult(res); setStatus("done");
      onDone(res);
    } catch (ex) { setErrMsg(ex.message); setStatus("error"); }
  }

  const labelStyle = { font: "var(--text-label)", fontSize: 11, fontWeight: 600,
    textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", display: "block", marginBottom: 5 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20, padding: "28px 28px 24px",
        width: "100%", maxWidth: 520, boxShadow: "var(--shadow-hover)", maxHeight: "90vh", overflow: "auto",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>
            Bulk upload practices
          </h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>
        <p style={{ margin: "0 0 16px", font: "var(--text-body)", fontSize: 12.5, color: "var(--fg-3)" }}>
          One client per file — the practice name comes from the filename. CSV/Excel P&Ls and
          PCSE payment statements are both accepted (PCSE is detected automatically).
        </p>

        <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <div>
            <label style={labelStyle}>Sector</label>
            <select value={sector} onChange={e => setSector(e.target.value)}
              style={{ width: "100%", padding: "9px 12px", fontSize: 13.5, borderRadius: "var(--radius-sm)",
                border: "1px solid var(--border-strong)", background: "var(--surface)", color: "var(--ink)" }}>
              <option value="nhs_gp">NHS GP Practice</option>
              <option value="nhs_pcn">NHS PCN</option>
              <option value="Other">Other</option>
            </select>
          </div>
          <div>
            <label style={labelStyle}>Files ({files.length} selected)</label>
            <input type="file" multiple accept=".csv,.xlsx,.xls"
              onChange={e => setFiles(Array.from(e.target.files || []))}
              style={{ width: "100%", fontSize: 13, color: "var(--fg-2)" }} />
            {files.length > 0 && (
              <div style={{ marginTop: 8, maxHeight: 140, overflowY: "auto", border: "1px solid var(--border)", borderRadius: "var(--radius-sm)" }}>
                {files.map((f, i) => (
                  <div key={i} style={{ padding: "6px 12px", fontSize: 12, color: "var(--fg-2)",
                    borderBottom: i < files.length - 1 ? "1px solid var(--border)" : "none" }}>
                    {f.name}
                  </div>
                ))}
              </div>
            )}
          </div>

          {result && status === "done" && (
            <div style={{ padding: "10px 14px", background: "var(--favourable-soft)", border: "1px solid var(--favourable-border)",
              borderRadius: "var(--radius-sm)", color: "var(--favourable-text)", fontSize: 12.5 }}>
              Added {result.added_count} practice{result.added_count === 1 ? "" : "s"}.
              {result.failed_count > 0 && (
                <span style={{ color: "var(--adverse-text)" }}> {result.failed_count} failed.</span>
              )}
            </div>
          )}
          {/* Per-file diagnostics for failed uploads — say WHICH row/column is broken */}
          {result && status === "done" && result.failed_count > 0 && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", fontSize: 12.5,
              maxHeight: 180, overflowY: "auto" }}>
              {result.failed.map((f, i) => (
                <div key={i} style={{ marginBottom: i < result.failed.length - 1 ? 8 : 0 }}>
                  <div style={{ fontWeight: 600 }}>{f.file} — {f.error}</div>
                  {(f.issues || []).slice(0, 4).map((it, k) => (
                    <div key={k} style={{ marginTop: 2, paddingLeft: 10, lineHeight: 1.5 }}>
                      {it.severity === "error" ? "✕" : "⚠"} {it.message}
                    </div>
                  ))}
                </div>
              ))}
            </div>
          )}
          {errMsg && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", fontSize: 12.5,
              whiteSpace: "pre-line" }}>
              {errMsg}
            </div>
          )}

          <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
            <button type="button" onClick={onClose} style={{
              padding: "9px 18px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", fontSize: 13.5, cursor: "pointer",
            }}>{status === "done" ? "Close" : "Cancel"}</button>
            <button type="submit" disabled={!canSubmit} style={{
              padding: "9px 20px", borderRadius: "var(--radius-sm)", border: "none",
              background: canSubmit ? "var(--primary)" : "var(--border-strong)",
              color: canSubmit ? "#fff" : "var(--fg-3)",
              fontSize: 13.5, cursor: canSubmit ? "pointer" : "default",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              {status === "uploading"
                ? <React.Fragment><div className="spinner" style={{ width: 14, height: 14 }} /> Uploading…</React.Fragment>
                : <React.Fragment><Icon name="upload" size={14} /> Upload {files.length || ""}</React.Fragment>}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}


// ── Side-by-side comparison modal ─────────────────────────────────────────
function ClientCompareModal({ data, onClose }) {
  const { Icon, TrendChart } = window;
  React.useEffect(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [onClose]);

  if (!data || !data.practices || data.practices.length === 0) return null;
  const practices = data.practices;
  const shortMonths = (practices[0]?.months || []).map(m => m.split(" ")[0]);

  const buildChartData = (key) =>
    shortMonths.map((m, i) => {
      const row = { m };
      practices.forEach((p, pi) => { row[`p${pi}`] = p[key][i] || 0; });
      return row;
    });

  const series = practices.map((p, i) => ({
    key: `p${i}`, label: p.name.split(" ")[0], color: COMPARE_COLORS[i],
  }));

  const fmtFull = v => v == null ? "—" : `£${Math.round(v).toLocaleString()}`;
  const fmtPct  = v => v == null ? "—" : `${v.toFixed(1)}%`;

  return (
    <div onClick={e => e.target === e.currentTarget && onClose()}
      style={{ position:"fixed", inset:0, background:"rgba(0,0,0,.46)",
        display:"flex", alignItems:"center", justifyContent:"center",
        zIndex:1200, padding:16 }}>
      <div style={{ background:"var(--surface)", borderRadius:16,
        width:"min(960px,100%)", maxHeight:"92vh", overflowY:"auto",
        boxShadow:"0 24px 64px rgba(0,0,0,.22)" }}>

        {/* Header */}
        <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between",
          padding:"18px 24px", borderBottom:"1px solid var(--border)",
          position:"sticky", top:0, background:"var(--surface)", zIndex:1 }}>
          <div>
            <h3 style={{ margin:0, font:"700 16px/1.2 var(--font-display)", color: "var(--ink)" }}>
              Side-by-side comparison
            </h3>
            <p style={{ margin:"3px 0 0", font:"var(--text-caption)", fontSize:12, color: "var(--fg-3)" }}>
              {practices.length} practices · 12-month trends
            </p>
          </div>
          <button onClick={onClose}
            style={{ background:"none", border:"none", cursor:"pointer", color: "var(--fg-2)", padding:4 }}>
            <Icon name="x" size={18} />
          </button>
        </div>

        {/* Legend pills */}
        <div style={{ display:"flex", gap:8, flexWrap:"wrap", padding:"14px 24px",
          borderBottom:"1px solid var(--border)" }}>
          {practices.map((p, i) => (
            <span key={i} style={{
              display:"inline-flex", alignItems:"center", gap:8,
              padding:"5px 14px", borderRadius:20,
              background: COMPARE_COLORS[i] + "18",
              border: `1px solid ${COMPARE_COLORS[i]}50`,
              font:"500 13px var(--font-display)", color: COMPARE_COLORS[i],
            }}>
              <span style={{ width:8, height:8, borderRadius:"50%",
                background: COMPARE_COLORS[i], display:"inline-block", flexShrink:0 }} />
              {p.name}
            </span>
          ))}
        </div>

        {/* KPI table */}
        <div style={{ padding:"18px 24px", borderBottom:"1px solid var(--border)", overflowX:"auto" }}>
          <div style={{ font:"600 11px var(--font-display)", textTransform:"uppercase",
            letterSpacing:".05em", color: "var(--fg-3)", marginBottom:10 }}>
            Latest period
          </div>
          <table style={{ width:"100%", borderCollapse:"collapse", minWidth:400 }}>
            <thead>
              <tr>
                <th style={{ width:110, padding:"8px 0", textAlign:"left",
                  font:"var(--text-label)", fontSize:11, color: "var(--fg-3)",
                  borderBottom:"1px solid var(--border)" }} />
                {practices.map((p, i) => (
                  <th key={i} style={{ padding:"8px 12px", textAlign:"right",
                    font:"600 13px var(--font-display)", color: COMPARE_COLORS[i],
                    borderBottom:`2px solid ${COMPARE_COLORS[i]}` }}>
                    {p.name}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {[
                { label:"Revenue",      key:"revenue",    fmt: fmtFull },
                { label:"Total costs",  key:"total_cost", fmt: fmtFull, alwaysRed: true },
                { label:"Op surplus",   key:"op_profit",  fmt: fmtFull },
                { label:"Margin",       key:"margin",     fmt: fmtPct  },
              ].map(row => (
                <tr key={row.label}>
                  <td style={{ padding:"10px 0", font:"var(--text-caption)", fontSize:12.5,
                    color: "var(--fg-3)", borderBottom:"1px solid var(--border)" }}>
                    {row.label}
                  </td>
                  {practices.map((p, i) => {
                    const v = p.kpis[row.key];
                    const isAdverse = row.alwaysRed || (typeof v === "number" && v < 0);
                    return (
                      <td key={i} style={{ padding:"10px 12px", textAlign:"right",
                        font:"var(--text-data)", fontSize:15, fontVariantNumeric:"tabular-nums",
                        color: isAdverse ? "var(--adverse-text)" : "var(--ink)",
                        borderBottom:"1px solid var(--border)" }}>
                        {row.fmt(v)}
                      </td>
                    );
                  })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        {/* Revenue chart */}
        <div style={{ padding:"18px 24px", borderBottom:"1px solid var(--border)" }}>
          <div style={{ font:"600 11px var(--font-display)", textTransform:"uppercase",
            letterSpacing:".05em", color: "var(--fg-3)", marginBottom:10 }}>
            Revenue — 12-month trend
          </div>
          <TrendChart data={buildChartData("revenue")} series={series} />
        </div>

        {/* Costs chart */}
        <div style={{ padding:"18px 24px", borderBottom:"1px solid var(--border)" }}>
          <div style={{ font:"600 11px var(--font-display)", textTransform:"uppercase",
            letterSpacing:".05em", color: "var(--fg-3)", marginBottom:10 }}>
            Total costs — 12-month trend
          </div>
          <TrendChart data={buildChartData("costs")} series={series} />
        </div>

        {/* Surplus chart */}
        <div style={{ padding:"18px 24px" }}>
          <div style={{ font:"600 11px var(--font-display)", textTransform:"uppercase",
            letterSpacing:".05em", color: "var(--fg-3)", marginBottom:10 }}>
            Operating surplus — 12-month trend
          </div>
          <TrendChart data={buildChartData("surplus")} series={series} />
        </div>

      </div>
    </div>
  );
}


// ── Firm account modal (sign in / create account) ─────────────────────────
// The firm token stays the working credential; an account makes it recoverable
// (sign in from any device) and claims the anonymous token minted locally.
function FirmAccountModal({ firmToken, onClose }) {
  const { Icon } = window;
  const [tab, setTab]           = React.useState("register"); // register | login | forgot
  const [firmName, setFirmName] = React.useState("");
  const [email, setEmail]       = React.useState("");
  const [password, setPassword] = React.useState("");
  const [status, setStatus]     = React.useState("idle");
  const [errMsg, setErrMsg]     = React.useState("");
  const [notice, setNotice]     = React.useState("");

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

  const canSubmit = status !== "loading" && email.trim() &&
    (tab === "forgot" ||
     (password && (tab === "login" || (firmName.trim() && password.length >= 8))));

  async function submit(e) {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("loading"); setErrMsg(""); setNotice("");
    if (tab === "forgot") {
      try {
        const r = await fetch(apiUrl("/api/auth/forgot"), {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email: email.trim() }),
        });
        const j = await r.json().catch(() => ({}));
        if (!r.ok) throw new Error(window.apiErrorText(j, `Error ${r.status}`));
        setNotice(j.message || "If an account exists for that address, a reset link is on its way.");
        setStatus("idle");
      } catch (ex) { setErrMsg(ex.message); setStatus("error"); }
      return;
    }
    try {
      const isReg = tab === "register";
      const r = await fetch(apiUrl(isReg ? "/api/auth/register" : "/api/auth/login"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(isReg
          ? { email: email.trim(), password, firm_name: firmName.trim(),
              existing_firm_token: firmToken }
          : { email: email.trim(), password }),
      });
      if (!r.ok) { const j = await r.json().catch(() => ({})); throw new Error(window.apiErrorText(j, `Error ${r.status}`)); }
      const acc = await r.json();
      try {
        localStorage.setItem("meiq_firm_token", acc.firm_token);
        if (acc.firm_name) localStorage.setItem("meiq_firm_name", acc.firm_name);
      } catch {}
      // Reload so every component picks up the (possibly different) token
      window.location.reload();
    } catch (ex) { setErrMsg(ex.message); setStatus("error"); }
  }

  const inputStyle = { width: "100%", padding: "9px 12px", fontSize: 13.5,
    border: "1px solid var(--border-strong)", borderRadius: "var(--radius-sm)",
    background: "var(--surface)", color: "var(--ink)", outline: "none", boxSizing: "border-box" };
  const labelStyle = { font: "var(--text-label)", fontSize: 11, fontWeight: 600,
    textTransform: "uppercase", letterSpacing: ".05em", color: "var(--fg-3)", display: "block", marginBottom: 5 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 20, padding: "28px 28px 24px",
        width: "100%", maxWidth: 400, boxShadow: "var(--shadow-hover)",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 6 }}>
          <h3 style={{ margin: 0, font: "700 17px/1.2 var(--font-display)", color: "var(--ink)" }}>
            {tab === "register" ? "Secure your portfolio" : tab === "forgot" ? "Reset your password" : "Sign in"}
          </h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 4 }}>
            <Icon name="x" size={17} />
          </button>
        </div>
        <p style={{ margin: "0 0 16px", font: "var(--text-caption)", fontSize: 12.5, color: "var(--fg-3)", lineHeight: 1.55 }}>
          {tab === "register"
            ? "Your portfolio is currently stored under an anonymous key on this device. Creating an account keeps it, and lets you sign in from any device."
            : tab === "forgot"
            ? "Enter your account email and we'll send you a link to choose a new password."
            : "Signing in loads your firm's portfolio on this device."}
        </p>

        {/* Tab switch */}
        {tab !== "forgot" && (
        <div style={{ display: "inline-flex", background: "var(--surface-2)", borderRadius: "var(--radius-sm)",
          padding: 3, marginBottom: 16 }}>
          {[["register", "Create account"], ["login", "Sign in"]].map(([k, lbl]) => (
            <button key={k} type="button" onClick={() => { setTab(k); setErrMsg(""); setNotice(""); }} style={{
              padding: "5px 13px", borderRadius: "calc(var(--radius-sm) - 2px)", border: "none",
              background: tab === k ? "var(--surface)" : "transparent",
              boxShadow: tab === k ? "0 1px 3px rgba(0,0,0,.12)" : "none",
              color: tab === k ? "var(--primary-text)" : "var(--fg-3)",
              font: "var(--text-body)", fontSize: 12.5, fontWeight: tab === k ? 600 : 400, cursor: "pointer",
            }}>{lbl}</button>
          ))}
        </div>
        )}

        <form onSubmit={submit} style={{ display: "grid", gap: 14 }}>
          {tab === "register" && (
            <div>
              <label style={labelStyle}>Firm / practice name</label>
              <input style={inputStyle} value={firmName} onChange={(e) => setFirmName(e.target.value)}
                placeholder="e.g. Mersey Medical Accountants" autoFocus />
            </div>
          )}
          <div>
            <label style={labelStyle}>Email</label>
            <input style={inputStyle} type="email" value={email} onChange={(e) => setEmail(e.target.value)}
              placeholder="you@firm.co.uk" autoFocus={tab === "login"} />
          </div>
          {tab !== "forgot" && (
          <div>
            <label style={labelStyle}>Password {tab === "register" && <span style={{ textTransform: "none", fontWeight: 400 }}>(min 8 characters)</span>}</label>
            <input style={inputStyle} type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
            {tab === "login" && (
              <button type="button" onClick={() => { setTab("forgot"); setErrMsg(""); setNotice(""); }} style={{
                background: "none", border: "none", padding: 0, marginTop: 7, cursor: "pointer",
                color: "var(--primary-text)", fontSize: 12, textDecoration: "underline",
              }}>Forgot password?</button>
            )}
          </div>
          )}
          {tab === "forgot" && (
            <button type="button" onClick={() => { setTab("login"); setErrMsg(""); setNotice(""); }} style={{
              background: "none", border: "none", padding: 0, cursor: "pointer", justifySelf: "start",
              color: "var(--fg-3)", fontSize: 12, textDecoration: "underline",
            }}>← Back to sign in</button>
          )}
          {notice && (
            <div style={{ padding: "10px 14px", background: "var(--favourable-soft)", border: "1px solid var(--favourable-border)",
              borderRadius: "var(--radius-sm)", color: "var(--favourable-text)", fontSize: 12.5, whiteSpace: "pre-line" }}>
              {notice}
            </div>
          )}
          {errMsg && (
            <div style={{ padding: "10px 14px", background: "var(--adverse-soft)", border: "1px solid var(--adverse-border)",
              borderRadius: "var(--radius-sm)", color: "var(--adverse-text)", fontSize: 12.5, whiteSpace: "pre-line" }}>
              {errMsg}
            </div>
          )}
          <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
            <button type="button" onClick={onClose} style={{
              padding: "9px 18px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", fontSize: 13.5, cursor: "pointer",
            }}>Cancel</button>
            <button type="submit" disabled={!canSubmit} style={{
              padding: "9px 20px", borderRadius: "var(--radius-sm)", border: "none",
              background: canSubmit ? "var(--primary)" : "var(--border-strong)",
              color: canSubmit ? "#fff" : "var(--fg-3)", fontSize: 13.5,
              cursor: canSubmit ? "pointer" : "default",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              {status === "loading"
                ? <React.Fragment><div className="spinner" style={{ width: 14, height: 14 }} /> Working…</React.Fragment>
                : (tab === "register" ? "Create account" : tab === "forgot" ? "Send reset link" : "Sign in")}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}


// ── Main Portfolio component ───────────────────────────────────────────────

/* ── Adoption panel ────────────────────────────────────────────────────────
   Built for the person who has to evidence the benefit of their own
   investment at post-implementation review. Reads the audit log, which was
   already recording exactly this and was previously write-only. */
function AdoptionPanel() {
  const { Icon } = window;
  const [data, setData]   = React.useState(null);
  const [open, setOpen]   = React.useState(false);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    if (!open || data) return;
    fetch(apiUrl("/api/portfolio/adoption?days=90"))
      .then(r => r.ok ? r.json() : Promise.reject(new Error(`Failed (${r.status})`)))
      .then(setData)
      .catch(e => setError(e.message));
  }, [open, data]);

  const fmtDate = (iso) => {
    if (!iso) return "never";
    try { return new Date(iso).toLocaleDateString("en-GB", { day: "numeric", month: "short" }); }
    catch { return "—"; }
  };

  const stat = (value, label) => (
    <div>
      <div style={{ font: "var(--text-metric)", fontSize: 22, color: "var(--ink)" }}>{value}</div>
      <div style={{ font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)" }}>{label}</div>
    </div>
  );

  return (
    <div className="card" style={{ marginTop: 22, padding: "14px 18px" }}>
      <button
        onClick={() => setOpen(o => !o)}
        aria-expanded={open}
        style={{ display: "flex", alignItems: "center", gap: 9, width: "100%",
          background: "none", border: "none", padding: 0, cursor: "pointer", textAlign: "left" }}>
        <Icon name="activity" size={14} color="var(--fg-3)" />
        <span style={{ font: "var(--text-body-strong)", fontSize: 13.5, color: "var(--ink)" }}>
          Adoption
        </span>
        <span style={{ font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)" }}>
          Last 90 days — for your post-implementation review
        </span>
        <span style={{ marginLeft: "auto", display: "inline-flex" }}>
          <Icon name={open ? "chevron-up" : "chevron-down"} size={15} color="var(--fg-3)" />
        </span>
      </button>

      {open && error && (
        <div style={{ marginTop: 12, font: "var(--text-body)", fontSize: 12.5, color: "var(--adverse-text)" }}>
          {error}
        </div>
      )}

      {open && data && (
        <div style={{ marginTop: 14 }}>
          <div style={{ display: "flex", gap: 30, flexWrap: "wrap", marginBottom: 16 }}>
            {stat(`${data.clients_active} / ${data.clients_total}`, "Clients with activity")}
            {stat(data.sign_ins, "Sign-ins")}
            {stat(data.outputs_produced, "Packs & exports produced")}
            {stat(data.total_events, "Recorded actions")}
          </div>

          {data.clients_active < data.clients_total && (
            <div style={{ padding: "9px 12px", marginBottom: 14, borderRadius: "var(--radius-sm)",
              background: "var(--caution-soft)", border: "1px solid var(--caution-border)",
              font: "var(--text-body)", fontSize: 12.5, color: "var(--caution-text)" }}>
              {data.clients_total - data.clients_active} of {data.clients_total} clients
              have had no recorded activity in this window.
            </div>
          )}

          <table className="var" style={{ width: "100%", borderCollapse: "collapse" }}>
            <caption style={{ captionSide: "top", textAlign: "left", paddingBottom: 6,
              font: "var(--text-caption)", fontSize: 11, textTransform: "uppercase",
              letterSpacing: ".06em", color: "var(--fg-3)" }}>
              Last activity per client
            </caption>
            <thead>
              <tr>
                <th scope="col" style={{ textAlign: "left", font: "var(--text-label)", fontSize: 11,
                  color: "var(--fg-3)", padding: "6px 0" }}>Client</th>
                <th scope="col" style={{ textAlign: "right", font: "var(--text-label)", fontSize: 11,
                  color: "var(--fg-3)", padding: "6px 0" }}>Last activity</th>
              </tr>
            </thead>
            <tbody>
              {data.clients.map(c => (
                <tr key={c.client_id}>
                  <td style={{ padding: "6px 0", font: "var(--text-body)", fontSize: 12.5,
                    color: "var(--fg-1)", borderTop: "1px solid var(--border)" }}>{c.name}</td>
                  <td style={{ padding: "6px 0", textAlign: "right", font: "var(--text-data)",
                    fontSize: 12, borderTop: "1px solid var(--border)",
                    color: c.last_activity ? "var(--fg-2)" : "var(--fg-3)" }}>
                    {fmtDate(c.last_activity)}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

function Portfolio({ onOpenClient, onToast }) {
  const { Icon, RagBadge } = window;
  const firmToken = React.useMemo(() => getFirmToken(), []);

  const [mode, setMode]           = React.useState("real"); // "real" | "demo"
  const [status, setStatus]       = React.useState("loading");
  const [data, setData]           = React.useState(null);
  const [showAdd, setShowAdd]     = React.useState(false);
  const [updating, setUpdating]   = React.useState(null); // client object being updated
  const [deleting, setDeleting]   = React.useState(null); // client_id being confirmed
  const [briefStatus, setBriefStatus] = React.useState("idle"); // idle | loading | done | error
  const [briefs, setBriefs]           = React.useState({});     // {session_id: text}
  const [copiedLink, setCopiedLink]   = React.useState(null);  // session_id of last copied card
  const [showWorkings, setShowWorkings] = React.useState(false); // funding-number drill-down
  const [emailedLink, setEmailedLink] = React.useState(null); // session_id of last emailed card
  const [search, setSearch]           = React.useState("");
  const [tierFilter, setTierFilter]   = React.useState("all"); // "all" | "action" | "watch" | "healthy"
  const [sortBy, setSortBy]           = React.useState("triage"); // "triage" | "opportunity"
  const [typeFilter, setTypeFilter]   = React.useState("all"); // "all" | "nhs_gp" | "nhs_pcn"
  const [, setRagRev]                 = React.useState(0);
  const [neighbourhoods, setNeighbourhoods] = React.useState([]);
  const [showNeighModal, setShowNeighModal] = React.useState(false);
  const [showBulkQof, setShowBulkQof]       = React.useState(false);
  const [showBulkUpload, setShowBulkUpload] = React.useState(false);
  const [compareIds, setCompareIds]         = React.useState(new Set());
  const [compareData, setCompareData]       = React.useState(null);
  const [showCompare, setShowCompare]       = React.useState(false);
  const [compareLoading, setCompareLoading] = React.useState(false);
  const [neighShareStates, setNeighShareStates] = React.useState({}); // {id: "idle"|"loading"|"copied"}
  const [xeroSyncing, setXeroSyncing]           = React.useState({}); // {client_id: bool}
  const [xeroSyncPeriod, setXeroSyncPeriod]     = React.useState({}); // {client_id: "rolling_12"|"financial_year"}
  const [account, setAccount]                   = React.useState(null); // null | {registered, email?, firm_name?}
  const [showAccount, setShowAccount]           = React.useState(false);
  const [showAccountMenu, setShowAccountMenu]   = React.useState(false);

  // Signed-in state: does an account own this firm token?
  React.useEffect(() => {
    fetch(apiUrl(`/api/auth/whoami?firm_token=${encodeURIComponent(firmToken)}`))
      .then(r => r.ok ? r.json() : { registered: false })
      .then(setAccount)
      .catch(() => setAccount({ registered: false }));
  }, [firmToken]);

  // Sign out: forget the account's token and start a fresh anonymous session.
  // The account and its saved data remain on the server — signing back in
  // restores them; only this browser reverts to anonymous mode.
  const signOut = React.useCallback(() => {
    try {
      localStorage.removeItem("meiq_firm_token");
      localStorage.removeItem("meiq_firm_name");
    } catch {}
    window.location.reload();
  }, []);

  const load = React.useCallback((m) => {
    const which = m ?? mode;
    setStatus("loading");
    const url = which === "demo"
      ? apiUrl("/api/portfolio/demo")
      : apiUrl(`/api/portfolio/clients?firm_token=${encodeURIComponent(firmToken)}`);
    fetch(url)
      .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
      .then(d => {
        setData(d);
        setStatus("done");
        if (which === "demo") setNeighbourhoods(d.neighbourhoods || []);
      })
      .catch(() => setStatus("error"));
  }, [mode, firmToken]);

  React.useEffect(() => { load(); }, [load]);

  const loadNeighbourhoods = React.useCallback((demoData) => {
    if (mode === "demo") {
      // Demo neighbourhoods come bundled in the portfolio/demo response
      setNeighbourhoods(demoData || []);
      return;
    }
    fetch(apiUrl(`/api/portfolio/neighbourhoods?firm_token=${encodeURIComponent(firmToken)}`))
      .then(r => r.json())
      .then(d => setNeighbourhoods(d.neighbourhoods || []))
      .catch(() => {});
  }, [mode, firmToken]);

  React.useEffect(() => { loadNeighbourhoods(); }, [loadNeighbourhoods]);

  React.useEffect(() => {
    const h = () => setRagRev(v => v + 1);
    window.addEventListener("meiq:thresholds-updated", h);
    return () => window.removeEventListener("meiq:thresholds-updated", h);
  }, []);

  function switchMode(m) { setMode(m); load(m); }

  function handleAdded(client) {
    setShowAdd(false);
    if (mode === "real") {
      setData(prev => {
        const clients = [client, ...(prev?.clients || [])].sort((a, b) => b.score - a.score);
        return { clients, summary: buildSummary(clients) };
      });
    }
  }

  function handleUpdated(updated) {
    setUpdating(null);
    setData(prev => {
      const clients = (prev?.clients || []).map(c => c.session_id === updated.session_id ? updated : c)
        .sort((a, b) => b.score - a.score);
      return { clients, summary: buildSummary(clients) };
    });
  }

  // The exit plan, as a button. A buyer assessing a small vendor needs leaving
  // to be one click, in a format they can read without us.
  async function exportEverything() {
    try {
      const r = await fetch(apiUrl("/api/portfolio/export-all"));
      if (!r.ok) throw new Error(`Export failed (${r.status})`);
      const blob = await r.blob();
      const url  = URL.createObjectURL(blob);
      const a    = document.createElement("a");
      a.href = url;
      a.download = `monthendiq-export-${new Date().toISOString().slice(0, 10)}.json`;
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(url);
      onToast && onToast("Exported everything held for this firm");
    } catch (e) {
      onToast && onToast(e.message || "Export failed");
    }
  }

  async function exportClientData(c) {
    try {
      const r = await fetch(
        apiUrl(`/api/portfolio/clients/${c.session_id}/export?firm_token=${encodeURIComponent(firmToken)}`)
      );
      if (!r.ok) throw new Error();
      const blob = new Blob([JSON.stringify(await r.json(), null, 2)], { type: "application/json" });
      const url  = URL.createObjectURL(blob);
      const a    = document.createElement("a");
      a.href = url;
      a.download = `${(c.name || "client").replace(/[^\w.-]+/g, "_")}_export.json`;
      a.click();
      URL.revokeObjectURL(url);
      onToast?.(`Exported data for ${c.name}`);
    } catch {
      onToast?.("Could not export client data. Please try again.");
    }
  }

  async function confirmDelete(clientId) {
    setDeleting(null);
    try {
      const r = await fetch(
        apiUrl(`/api/portfolio/clients/${clientId}?firm_token=${encodeURIComponent(firmToken)}`),
        { method: "DELETE" }
      );
      if (!r.ok) throw new Error();
      setData(prev => {
        const clients = (prev?.clients || []).filter(c => c.session_id !== clientId);
        return { clients, summary: buildSummary(clients) };
      });
    } catch {
      onToast?.("Could not delete client. Please try again.");
    }
  }

  function buildSummary(clients) {
    return {
      total:         clients.length,
      action:        clients.filter(c => c.tier === "action").length,
      watch:         clients.filter(c => c.tier === "watch").length,
      healthy:       clients.filter(c => c.tier === "healthy").length,
      total_revenue: clients.reduce((s, c) => s + (c.revenue || 0), 0),
      burning:       clients.filter(c => c.burning).length,
    };
  }

  async function generateBriefing() {
    setBriefStatus("loading");
    setBriefs({});
    try {
      const r = await fetch(
        apiUrl(`/api/portfolio/briefing?firm_token=${encodeURIComponent(firmToken)}&currency=${encodeURIComponent((() => { try { return localStorage.getItem("meiq_currency_sym") || "£"; } catch { return "£"; } })())}`),
        { method: "POST" }
      );
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error(j.detail || `Error ${r.status}`);
      }
      const { briefs: b } = await r.json();
      setBriefs(b || {});
      setBriefStatus("done");
    } catch (ex) {
      console.error("[Briefing]", ex);
      setBriefStatus("error");
    }
  }

  function toggleCompare(sid) {
    setCompareIds(prev => {
      const next = new Set(prev);
      if (next.has(sid)) next.delete(sid);
      else if (next.size < 4) next.add(sid);
      return next;
    });
  }

  async function openCompare() {
    setCompareLoading(true);
    try {
      const ids = [...compareIds].join(",");
      const r = await fetch(apiUrl(`/api/portfolio/compare?session_ids=${encodeURIComponent(ids)}`));
      if (!r.ok) throw new Error(r.status);
      setCompareData(await r.json());
      setShowCompare(true);
    } catch { /* silent */ } finally { setCompareLoading(false); }
  }

  async function syncFromXero(clientId) {
    setXeroSyncing(prev => ({ ...prev, [clientId]: true }));
    try {
      const pm = xeroSyncPeriod[clientId] || "rolling_12";
      const r = await fetch(apiUrl(`/api/portfolio/clients/${clientId}/xero-sync?period_mode=${pm}`), { method: "POST" });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error(j.detail || `Error ${r.status}`);
      }
      const updated = await r.json();
      setData(prev => {
        const clients = (prev?.clients || []).map(c =>
          c.session_id === clientId ? { ...c, ...updated } : c
        ).sort((a, b) => b.score - a.score);
        return { ...prev, clients };
      });
      onToast?.("Synced from Xero");
    } catch (ex) {
      onToast?.(ex.message || "Xero sync failed");
    } finally {
      setXeroSyncing(prev => ({ ...prev, [clientId]: false }));
    }
  }

  function fmtGBP(v) { return window.fmtCurrency(v, { compact: true }); }

  function fmtDate(iso) {
    if (!iso) return "";
    try { return new Date(iso).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }); }
    catch { return ""; }
  }

  function fmtSyncedAgo(iso) {
    if (!iso) return "";
    const mins = Math.floor((Date.now() - new Date(iso).getTime()) / 60000);
    if (mins < 2)   return "just now";
    if (mins < 60)  return `${mins}m ago`;
    const hrs = Math.floor(mins / 60);
    if (hrs < 24)   return `${hrs}h ago`;
    return `${Math.floor(hrs / 24)}d ago`;
  }

  function dataFreshness(iso) {
    if (!iso) return null;
    const days = Math.floor((Date.now() - new Date(iso).getTime()) / 86400000);
    if (days <= 35)  return { label: "Data current", color: "var(--favourable-text)", bg: "var(--favourable-soft)", border: "var(--favourable-border, #86efac)" };
    if (days <= 65)  return { label: "Due for update", color: "var(--caution-text, #b45309)", bg: "var(--caution-soft, #fef3c7)", border: "var(--caution-border, #fcd34d)" };
    return { label: "Data overdue", color: "var(--adverse-text)", bg: "var(--adverse-soft)", border: "var(--adverse-border)" };
  }

  const ragThresholds = window.loadRagThresholds ? window.loadRagThresholds() : {};

  const isDemo = mode === "demo";
  const hasClients = data?.clients?.length > 0;

  const visibleClients = React.useMemo(() => {
    let list = data?.clients || [];
    if (tierFilter !== "all") list = list.filter(c => c.tier === tierFilter);
    if (typeFilter !== "all") list = list.filter(c => c.sector === typeFilter);
    const q = search.trim().toLowerCase();
    if (q) list = list.filter(c =>
      c.name.toLowerCase().includes(q) || (c.sector || "").toLowerCase().includes(q)
    );
    if (sortBy === "opportunity") {
      // Rank by ARRS only — QOF's ledger gap is timing, not recoverable money
      const opp = c => (c.arrs_unclaimed || 0);
      list = [...list].sort((a, b) => opp(b) - opp(a));
    }
    return list;
  }, [data, tierFilter, typeFilter, search, sortBy]);

  return (
    <div className="content">
      <div className="content-inner reveal">
        {/* Header */}
        <div style={{ marginBottom: 20, display: "flex", alignItems: "flex-end",
          justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
          <div>
            <h2 style={{ font: "600 22px/1.2 var(--font-display)", color: "var(--ink)", margin: 0 }}>
              Practice Portfolio
            </h2>
            <p style={{ font: "var(--text-body)", fontSize: 13.5, color: "var(--fg-3)", margin: "4px 0 0" }}>
              Month-end triage across your clients — sorted by who needs attention first
            </p>
          </div>
          <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
            {/* Real / Demo toggle */}
            <div style={{
              display: "inline-flex", background: "var(--surface-2)",
              borderRadius: "var(--radius-sm)", padding: 3, border: "1px solid var(--border)",
            }}>
              {["real", "demo"].map(m => (
                <button key={m} onClick={() => switchMode(m)} style={{
                  padding: "5px 13px", borderRadius: "calc(var(--radius-sm) - 2px)", border: "none",
                  background: mode === m ? "var(--surface)" : "transparent",
                  boxShadow: mode === m ? "0 1px 3px rgba(0,0,0,.12)" : "none",
                  color: mode === m ? "var(--primary-text)" : "var(--fg-3)",
                  font: "var(--text-body)", fontSize: 12.5, fontWeight: mode === m ? 600 : 400,
                  cursor: "pointer", transition: "all .15s",
                }}>
                  {m === "real" ? "My clients" : "Demo"}
                </button>
              ))}
            </div>
            {/* Firm account — signed-in menu or secure-portfolio prompt */}
            {!isDemo && account && (
              account.registered ? (
                <div style={{ position: "relative" }}>
                  <button onClick={() => setShowAccountMenu(v => !v)}
                    title={`Signed in as ${account.email}`} style={{
                    display: "inline-flex", alignItems: "center", gap: 6,
                    font: "var(--text-caption)", fontSize: 12, color: "var(--favourable-text)",
                    background: "var(--favourable-soft)", border: "1px solid var(--favourable-border)",
                    borderRadius: "var(--radius-sm)", padding: "6px 12px", maxWidth: 220,
                    cursor: "pointer",
                  }}>
                    <Icon name="shield-check" size={13} />
                    <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {account.firm_name || account.email}
                    </span>
                    <Icon name="chevron-down" size={12} />
                  </button>
                  {showAccountMenu && (
                    <React.Fragment>
                      {/* click-away backdrop */}
                      <div onClick={() => setShowAccountMenu(false)}
                        style={{ position: "fixed", inset: 0, zIndex: 40 }} />
                      <div style={{
                        position: "absolute", top: "calc(100% + 6px)", right: 0, zIndex: 41,
                        minWidth: 220, background: "var(--surface)", borderRadius: "var(--radius-sm)",
                        border: "1px solid var(--border)", boxShadow: "var(--shadow-hover)",
                        padding: 6, textAlign: "left",
                      }}>
                        <div style={{ padding: "8px 12px 10px", borderBottom: "1px solid var(--border)" }}>
                          <div style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase",
                            letterSpacing: ".05em", color: "var(--fg-3)", marginBottom: 3 }}>Signed in as</div>
                          <div style={{ font: "var(--text-body)", fontSize: 13, color: "var(--ink)",
                            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                            {account.email}
                          </div>
                          {account.firm_name && (
                            <div style={{ font: "var(--text-caption)", fontSize: 12, color: "var(--fg-3)", marginTop: 1 }}>
                              {account.firm_name}
                            </div>
                          )}
                        </div>
                        <button onClick={signOut} style={{
                          display: "flex", alignItems: "center", gap: 8, width: "100%", marginTop: 4,
                          padding: "8px 12px", border: "none", background: "none", cursor: "pointer",
                          borderRadius: "var(--radius-sm)", color: "var(--fg-1)",
                          font: "var(--text-body)", fontSize: 13, textAlign: "left",
                        }}
                          onMouseEnter={e => e.currentTarget.style.background = "var(--surface-2)"}
                          onMouseLeave={e => e.currentTarget.style.background = "none"}>
                          <Icon name="log-out" size={14} /> Sign out
                        </button>
                      </div>
                    </React.Fragment>
                  )}
                </div>
              ) : (
                <button onClick={() => setShowAccount(true)} style={{
                  display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 14px",
                  borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
                  background: "var(--surface)", color: "var(--fg-2)",
                  font: "var(--text-body-strong)", fontSize: 13, cursor: "pointer",
                }}>
                  <Icon name="lock" size={13} /> Sign in
                </button>
              )
            )}
            {/* Board pack — federation-wide PDF / XLSX (real mode, has clients) */}
            {!isDemo && hasClients && (() => {
              const download = (fmt) => {
                const firm = (() => { try { return localStorage.getItem("meiq_firm_name") || ""; } catch { return ""; } })();
                const cur  = (() => { try { return localStorage.getItem("meiq_currency_sym") || "£"; } catch { return "£"; } })();
                const qs = new URLSearchParams({ firm_token: firmToken, firm, currency: cur, fmt });
                window.open(apiUrl(`/api/portfolio/board-pack?${qs}`), "_blank");
              };
              return (
                <div style={{ display: "inline-flex", borderRadius: "var(--radius-sm)",
                  border: "1px solid var(--border-strong)", overflow: "hidden" }}>
                  <button onClick={() => download("pdf")} title="Download board pack (PDF)" style={{
                    display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 14px",
                    border: "none", borderRight: "1px solid var(--border-strong)",
                    background: "var(--surface)", color: "var(--fg-2)",
                    font: "var(--text-body-strong)", fontSize: 13, cursor: "pointer",
                  }}>
                    <Icon name="file-text" size={14} /> Board pack
                  </button>
                  <button onClick={() => download("xlsx")} title="Download as spreadsheet (XLSX)" style={{
                    padding: "7px 10px", border: "none", background: "var(--surface)",
                    color: "var(--fg-3)", font: "var(--text-body-strong)", fontSize: 11.5, cursor: "pointer" }}>
                    XLSX
                  </button>
                </div>
              );
            })()}
            {/* Morning briefing (real mode, has clients) */}
            {!isDemo && hasClients && (
              <button
                onClick={generateBriefing}
                disabled={briefStatus === "loading"}
                style={{
                  display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 14px",
                  borderRadius: "var(--radius-sm)", border: "1px solid var(--primary)",
                  background: briefStatus === "done" ? "var(--primary-soft)" : "var(--surface)",
                  color: "var(--primary-text)",
                  font: "var(--text-body-strong)", fontSize: 13,
                  cursor: briefStatus === "loading" ? "default" : "pointer",
                  opacity: briefStatus === "loading" ? .7 : 1,
                }}
              >
                {briefStatus === "loading"
                  ? <React.Fragment><div className="spinner" style={{ width: 13, height: 13, borderColor: "var(--primary)", borderTopColor: "transparent" }} /> Generating…</React.Fragment>
                  : <React.Fragment><Icon name="sun" size={14} /> Morning briefing</React.Fragment>}
              </button>
            )}
            {/* Add client (real mode only) */}
            {!isDemo && (
              <button onClick={() => setShowAdd(true)} style={{
                display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 14px",
                borderRadius: "var(--radius-sm)", border: "none",
                background: "var(--primary)", color: "var(--on-primary)",
                font: "var(--text-body-strong)", fontSize: 13, cursor: "pointer",
              }}>
                <Icon name="plus" size={14} /> Add client
              </button>
            )}
            {!isDemo && (
              <button onClick={() => setShowBulkUpload(true)} title="Onboard many practices at once — PCSE statements auto-detected" style={{
                display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 12px",
                borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
                background: "var(--surface)", color: "var(--fg-2)",
                font: "var(--text-body-strong)", fontSize: 13, cursor: "pointer",
              }}>
                <Icon name="upload" size={14} /> Bulk upload
              </button>
            )}
            {!isDemo && (
              <button onClick={exportEverything}
                title="Download everything held for this firm — clients, NHS parameters, budget holders, saved layouts and the audit log"
                style={{
                  display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 12px",
                  borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
                  background: "var(--surface)", color: "var(--fg-2)",
                  font: "var(--text-body-strong)", fontSize: 13, cursor: "pointer",
                }}>
                <Icon name="hard-drive-download" size={14} /> Export all data
              </button>
            )}
            <button onClick={() => load()} title="Refresh" aria-label="Refresh client list" style={{
              display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 12px",
              borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
              background: "var(--surface)", color: "var(--fg-2)", fontSize: 13, cursor: "pointer",
            }}>
              <Icon name="refresh-cw" size={14} />
            </button>
          </div>
        </div>

        {status === "loading" && (
          <div style={{ padding: "60px 0", textAlign: "center" }}>
            <div className="spinner" style={{ margin: "0 auto 12px" }} />
            <div style={{ font: "var(--text-body)", fontSize: 13, color: "var(--fg-3)" }}>
              {isDemo ? "Building demo portfolio…" : "Loading your clients…"}
            </div>
          </div>
        )}

        {status === "error" && (
          <div style={{ padding: "16px 18px", background: "var(--adverse-soft)",
            borderRadius: "var(--radius-sm)", border: "1px solid var(--adverse-border)",
            color: "var(--adverse-text)", fontSize: 13 }}>
            Could not load the portfolio.{" "}
            <button onClick={() => load()} style={{ marginLeft: 8, textDecoration: "underline",
              background: "none", border: "none", color: "inherit", cursor: "pointer" }}>Retry</button>
          </div>
        )}

        {status === "done" && data && (
          <React.Fragment>
            {/* Empty state — real mode, no clients yet */}
            {!isDemo && !hasClients && (
              <div style={{
                padding: "52px 24px", textAlign: "center",
                background: "var(--surface-2)", borderRadius: 16,
                border: "2px dashed var(--border-strong)",
              }}>
                <div style={{ fontSize: 32, marginBottom: 12 }}>📂</div>
                <h3 style={{ font: "600 17px/1.3 var(--font-display)", color: "var(--ink)", margin: "0 0 8px" }}>
                  No clients yet
                </h3>
                <p style={{ font: "var(--text-body)", fontSize: 13.5, color: "var(--fg-3)",
                  maxWidth: 360, margin: "0 auto 20px", lineHeight: 1.6 }}>
                  Upload a client's month-on-month P&amp;L and MonthEndIQ will triage them into
                  Action&nbsp;/ Watch&nbsp;/ Healthy — automatically, every time you refresh.
                </p>
                <button onClick={() => setShowAdd(true)} style={{
                  display: "inline-flex", alignItems: "center", gap: 7, padding: "10px 22px",
                  borderRadius: "var(--radius-sm)", border: "none",
                  background: "var(--primary)", color: "var(--on-primary)",
                  font: "var(--text-body-strong)", fontSize: 14, cursor: "pointer",
                }}>
                  <Icon name="plus" size={15} /> Add first client
                </button>
                <div style={{ marginTop: 14, font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)" }}>
                  Or switch to <button onClick={() => switchMode("demo")} style={{
                    background: "none", border: "none", color: "var(--primary-text)",
                    cursor: "pointer", fontSize: 11.5, padding: 0, textDecoration: "underline",
                  }}>Demo</button> to see how the triage works with sample data.
                </div>
              </div>
            )}

            {/* Summary stat strip */}
            {hasClients && (
              <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: 12, marginBottom: 22 }}>
                {[
                  { label: "Clients",         value: data.summary.total,          tone: "var(--ink)" },
                  { label: "Need action",     value: data.summary.action,         tone: "var(--adverse-text)" },
                  { label: "Watch",           value: data.summary.watch,          tone: "var(--caution-text, #b45309)" },
                  { label: "Healthy",         value: data.summary.healthy,        tone: "var(--favourable-text)" },
                  { label: "Revenue managed", value: fmtGBP(data.summary.total_revenue), tone: "var(--ink)" },
                  { label: "Burning cash",    value: data.summary.burning,        tone: data.summary.burning ? "var(--adverse-text)" : "var(--fg-2)" },
                ].map((s) => (
                  <div key={s.label} className="card" style={{ padding: "14px 16px" }}>
                    <div style={{ font: "var(--text-label)", fontSize: 10.5, textTransform: "uppercase",
                      letterSpacing: ".05em", color: "var(--fg-3)", marginBottom: 6 }}>{s.label}</div>
                    <div style={{ font: "var(--text-metric)", fontSize: 24,
                      fontVariantNumeric: "tabular-nums", color: s.tone }}>{s.value}</div>
                  </div>
                ))}
              </div>
            )}

            {/* Anonymous nudge — this browser has real clients but no account,
                so the data is only reachable from here. Offer to save it. */}
            {!isDemo && hasClients && account && !account.registered && (
              <div style={{
                display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
                padding: "12px 16px", marginBottom: 18, borderRadius: "var(--radius-sm)",
                background: "var(--primary-soft)",
                border: "1px solid var(--primary-border, rgba(var(--primary-rgb,79,70,229),.2))",
              }}>
                <Icon name="info" size={16} style={{ color: "var(--primary-text)", flexShrink: 0 }} />
                <span style={{ flex: 1, minWidth: 180, font: "var(--text-body)", fontSize: 13,
                  color: "var(--fg-1)", lineHeight: 1.5 }}>
                  This portfolio is only saved on this device. Create a free account to keep it and open it from anywhere.
                </span>
                <button onClick={() => setShowAccount(true)} style={{
                  flexShrink: 0, padding: "7px 16px", borderRadius: "var(--radius-sm)", border: "none",
                  background: "var(--primary)", color: "var(--on-primary)", font: "var(--text-body-strong)",
                  fontSize: 13, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6,
                }}>
                  <Icon name="shield-check" size={14} /> Save my portfolio
                </button>
              </div>
            )}

            {/* Unclaimed-opportunity headline — the "money left on the table" across the patch */}
            {hasClients && (() => {
              const opp = data.opportunity;
              if (!opp || (opp.arrs_unclaimed_total <= 0 && opp.qof_gap_total <= 0)) return null;
              const money = v => "£" + Math.round(v).toLocaleString();
              const items = [
                { show: opp.arrs_unclaimed_total > 0, big: money(opp.arrs_unclaimed_total),
                  label: "ARRS funding unclaimed", sub: `across ${opp.arrs_practices} of ${opp.practice_count} practices`,
                  icon: "users" },
                { show: opp.qof_gap_total > 0, big: money(opp.qof_gap_total),
                  label: "QOF income below entitlement",
                  sub: `${opp.qof_practices} of ${opp.practice_count} practices · timing-affected, review`,
                  icon: "award" },
              ].filter(i => i.show);
              return (
                <div className="card" style={{ padding: "16px 20px", marginBottom: 22,
                  borderLeft: "3px solid var(--adverse-text, #b91c1c)" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
                    <Icon name="target" size={15} color="var(--adverse-text, #b91c1c)" />
                    <span style={{ font: "var(--text-body-strong)", fontSize: 13.5, color: "var(--ink)" }}>
                      Funding opportunity across your portfolio
                    </span>
                  </div>
                  <div style={{ display: "grid", gridTemplateColumns: `repeat(${items.length}, 1fr)`, gap: 20 }}>
                    {items.map(i => (
                      <div key={i.label} style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
                        <div style={{ marginTop: 3 }}><Icon name={i.icon} size={18} color="var(--fg-3)" /></div>
                        <div>
                          <div style={{ font: "var(--text-metric)", fontSize: 26, fontVariantNumeric: "tabular-nums",
                            color: "var(--adverse-text, #b91c1c)", lineHeight: 1.1 }}>{i.big}</div>
                          <div style={{ font: "var(--text-body-strong)", fontSize: 12.5, color: "var(--ink)", marginTop: 3 }}>{i.label}</div>
                          <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>{i.sub}</div>
                        </div>
                      </div>
                    ))}
                  </div>
                  {/* ARRS claiming-pace momentum vs last month */}
                  {(opp.arrs_improving > 0 || opp.arrs_worsening > 0) && (
                    <div style={{ marginTop: 12, display: "flex", gap: 16, alignItems: "center",
                      font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-2)" }}>
                      <span style={{ font: "var(--text-label)", fontSize: 10, textTransform: "uppercase",
                        letterSpacing: ".05em", color: "var(--fg-3)" }}>Claiming pace vs last month</span>
                      {opp.arrs_improving > 0 && (
                        <span style={{ color: "var(--favourable-text, #15803d)", fontWeight: 600 }}>
                          ▲ {opp.arrs_improving} improving
                        </span>
                      )}
                      {opp.arrs_worsening > 0 && (
                        <span style={{ color: "var(--adverse-text, #b91c1c)", fontWeight: 600 }}>
                          ▼ {opp.arrs_worsening} slowing
                        </span>
                      )}
                    </div>
                  )}
                  <div style={{ marginTop: 10, font: "var(--text-caption)", fontSize: 10.5, color: "var(--fg-3)" }}>
                    YTD, annualised where applicable. ARRS is a reimbursement ceiling, so allocation not drawn is genuinely unclaimed. QOF is not — achievement is clinical and the balance settles after year-end, so the QOF figure is a review prompt, not recoverable money, and is excluded from the funding total.
                    {(opp.breakdown || []).length > 0 && (
                      <React.Fragment>
                        {" "}
                        <button onClick={() => setShowWorkings(w => !w)} style={{
                          background: "none", border: "none", padding: 0, cursor: "pointer",
                          color: "var(--primary-text)", font: "inherit", textDecoration: "underline",
                        }}>{showWorkings ? "Hide workings" : "Show workings"}</button>
                      </React.Fragment>
                    )}
                    Pace = annualised ARRS run-rate vs the prior month.
                  </div>
                  {showWorkings && (opp.breakdown || []).length > 0 && (
                    <div style={{ marginTop: 14, overflowX: "auto" }}>
                      <table className="var" style={{ fontSize: 12 }}>
                        <thead>
                          <tr>
                            <th className="l">Entity</th>
                            <th>ARRS allocation</th>
                            <th>Spent YTD</th>
                            <th>Unclaimed</th>
                            <th>QOF entitlement</th>
                            <th>QOF income YTD</th>
                            <th className="l">Basis</th>
                          </tr>
                        </thead>
                        <tbody>
                          {opp.breakdown.map(b => (
                            <tr key={b.session_id}>
                              <td className="l">{b.name}</td>
                              <td>{b.arrs_allocation ? money(b.arrs_allocation) : "—"}</td>
                              <td>{b.arrs_allocation ? money(b.arrs_spend_ytd) : "—"}</td>
                              <td className={b.arrs_unclaimed > 0 ? "adv" : ""}>
                                {b.arrs_allocation ? money(b.arrs_unclaimed) : "—"}
                              </td>
                              <td>{b.qof_entitlement ? money(b.qof_entitlement) : "—"}</td>
                              <td>{b.qof_entitlement ? money(b.qof_income_ytd) : "—"}</td>
                              <td className="l" style={{ font: "var(--text-caption)", fontSize: 10.5, color: "var(--fg-3)" }}>
                                {b.months_elapsed ? `${b.months_elapsed}m YTD · ` : ""}
                                allocation {b.allocation_set_at
                                  ? `entered ${new Date(b.allocation_set_at).toLocaleDateString("en-GB")}`
                                  : "entered manually"}
                                {b.qof_estimated ? " · QOF entitlement estimated from list size" : ""}
                                {b.list_size_basis && b.list_size_basis !== "unspecified"
                                  ? ` · ${b.list_size_basis} list` : ""}
                              </td>
                            </tr>
                          ))}
                          <tr>
                            <td className="l" style={{ fontWeight: 700 }}>Total</td>
                            <td style={{ fontWeight: 700 }}>
                              {money(opp.breakdown.reduce((a, b) => a + (b.arrs_allocation || 0), 0))}
                            </td>
                            <td style={{ fontWeight: 700 }}>
                              {money(opp.breakdown.reduce((a, b) => a + (b.arrs_spend_ytd || 0), 0))}
                            </td>
                            <td className="adv" style={{ fontWeight: 700 }}>{money(opp.headline_total)}</td>
                            <td colSpan={3}></td>
                          </tr>
                        </tbody>
                      </table>
                      <div style={{ marginTop: 8, font: "var(--text-caption)", fontSize: 10.5, color: "var(--fg-3)" }}>
                        Every ceiling above was entered by your firm, not supplied by NHS England — the unclaimed figure is only as good as those entries. Check them against the PCN's ARRS allocation letter before taking this to a Board.
                      </div>
                    </div>
                  )}
                </div>
              );
            })()}

            {/* Search + tier filter */}
            {hasClients && (() => {
              const allClients = data?.clients || [];
              const hasGp  = allClients.some(c => c.sector === "nhs_gp");
              const hasPcn = allClients.some(c => c.sector === "nhs_pcn");
              const showTypeFilter = hasGp || hasPcn;
              return (
                <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 14 }}>
                  <div style={{ display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center" }}>
                    <div style={{
                      display: "flex", alignItems: "center", gap: 8,
                      flex: "1 1 200px", padding: "7px 12px",
                      background: "var(--surface)", border: "1px solid var(--border-strong)",
                      borderRadius: "var(--radius-sm)",
                    }}>
                      <Icon name="search" size={14} color="var(--fg-3)" style={{ flexShrink: 0 }} />
                      <input
                        value={search}
                        onChange={e => setSearch(e.target.value)}
                        placeholder="Search clients…"
                        style={{
                          flex: 1, border: "none", outline: "none",
                          font: "var(--text-body)", fontSize: 13, color: "var(--ink)",
                          background: "transparent",
                        }}
                      />
                      {search && (
                        <button onClick={() => setSearch("")} style={{
                          background: "none", border: "none", cursor: "pointer",
                          color: "var(--fg-3)", padding: 0, display: "flex", alignItems: "center",
                        }}>
                          <Icon name="x" size={13} />
                        </button>
                      )}
                    </div>
                    <div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
                      {[
                        { key: "all",     label: "All" },
                        { key: "action",  label: "Action", color: "var(--adverse-text)",         bg: "var(--adverse-soft)",    border: "var(--adverse-border)" },
                        { key: "watch",   label: "Watch",  color: "var(--caution-text, #b45309)",bg: "var(--caution-soft, #fef3c7)", border: "var(--caution-border, #fcd34d)" },
                        { key: "healthy", label: "Healthy",color: "var(--favourable-text)",      bg: "var(--favourable-soft)", border: "var(--favourable-border)" },
                      ].map(f => {
                        const active = tierFilter === f.key;
                        return (
                          <button key={f.key} onClick={() => setTierFilter(f.key)} style={{
                            padding: "5px 12px", borderRadius: 20, cursor: "pointer",
                            font: "var(--text-label)", fontSize: 11.5, fontWeight: 600,
                            border: `1px solid ${active && f.border ? f.border : "var(--border-strong)"}`,
                            background: active && f.bg ? f.bg : (active ? "var(--surface-2)" : "var(--surface)"),
                            color: active && f.color ? f.color : (active ? "var(--ink)" : "var(--fg-3)"),
                            transition: "all .12s",
                          }}>
                            {f.label}
                          </button>
                        );
                      })}
                    </div>
                    {/* Sort — triage score vs unclaimed funding opportunity */}
                    {allClients.some(c => c.arrs_unclaimed > 0 || c.qof_gap > 0) && (
                      <div style={{ display: "inline-flex", background: "var(--surface-2)", flexShrink: 0,
                        borderRadius: "var(--radius-sm)", padding: 2, border: "1px solid var(--border)", gap: 2 }}>
                        {[
                          { key: "triage",      label: "Triage" },
                          { key: "opportunity", label: "Opportunity" },
                        ].map(o => {
                          const active = sortBy === o.key;
                          return (
                            <button key={o.key} onClick={() => setSortBy(o.key)} title={o.key === "opportunity" ? "Rank by unclaimed ARRS + QOF gap" : "Rank by triage score"}
                              style={{ padding: "5px 11px", borderRadius: "var(--radius-xs)", cursor: "pointer",
                                font: "var(--text-label)", fontSize: 11.5, fontWeight: 600, border: "none",
                                background: active ? "var(--surface)" : "transparent",
                                color: active ? "var(--ink)" : "var(--fg-3)",
                                boxShadow: active ? "var(--shadow-card)" : "none" }}>
                              {o.label}
                            </button>
                          );
                        })}
                      </div>
                    )}
                  </div>
                  {/* Type filter — only shown when both GP practices and PCNs are present */}
                  {showTypeFilter && (
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <span style={{ font: "var(--text-label)", fontSize: 11, color: "var(--fg-3)",
                        textTransform: "uppercase", letterSpacing: ".05em", flexShrink: 0 }}>
                        View
                      </span>
                      <div style={{ display: "inline-flex", background: "var(--surface-2)",
                        borderRadius: "var(--radius-sm)", padding: 2, border: "1px solid var(--border)", gap: 2 }}>
                        {[
                          { key: "all",     label: "All clients" },
                          { key: "nhs_gp",  label: "GP Practices" },
                          { key: "nhs_pcn", label: "PCNs" },
                        ].map(t => {
                          const active = typeFilter === t.key;
                          return (
                            <button key={t.key} onClick={() => setTypeFilter(t.key)} style={{
                              padding: "4px 13px", borderRadius: "calc(var(--radius-sm) - 2px)", border: "none",
                              background: active ? "var(--surface)" : "transparent",
                              boxShadow: active ? "0 1px 3px rgba(0,0,0,.12)" : "none",
                              color: active ? "var(--primary-text)" : "var(--fg-3)",
                              font: "var(--text-body)", fontSize: 12, fontWeight: active ? 600 : 400,
                              cursor: "pointer", transition: "all .12s",
                            }}>
                              {t.label}
                            </button>
                          );
                        })}
                      </div>
                    </div>
                  )}
                </div>
              );
            })()}

            {/* Triage list */}
            {hasClients && (
              <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                {/* Floating compare bar */}
                {compareIds.size >= 2 && (
                  <div style={{
                    position:"sticky", top:0, zIndex:10,
                    display:"flex", alignItems:"center", gap:10,
                    background:"var(--primary)", color: "#fff",
                    borderRadius:"var(--radius-sm)", padding:"10px 16px",
                    boxShadow:"0 4px 16px rgba(79,70,229,.35)",
                  }}>
                    <Icon name="bar-chart-2" size={15} />
                    <span style={{ flex:1, font:"600 13px var(--font-display)" }}>
                      {compareIds.size} practice{compareIds.size > 1 ? "s" : ""} selected for comparison
                    </span>
                    <button onClick={openCompare} disabled={compareLoading} style={{
                      padding:"6px 16px", borderRadius:"var(--radius-sm)", border:"none",
                      background:"#fff", color: "var(--primary-text)",
                      font:"600 13px var(--font-display)",
                      cursor: compareLoading ? "default" : "pointer",
                      opacity: compareLoading ? .75 : 1,
                    }}>
                      {compareLoading ? "Loading…" : "Compare side by side →"}
                    </button>
                    <button onClick={() => setCompareIds(new Set())} style={{
                      background:"rgba(255,255,255,.2)", border:"none",
                      borderRadius:"var(--radius-sm)", padding:"6px 8px",
                      cursor:"pointer", color: "#fff", display:"flex", alignItems:"center",
                    }}>
                      <Icon name="x" size={13} />
                    </button>
                  </div>
                )}

                {visibleClients.length === 0 && (
                  <div style={{ padding: "28px 16px", textAlign: "center",
                    font: "var(--text-body)", fontSize: 13, color: "var(--fg-3)" }}>
                    No clients match your filter.
                  </div>
                )}
                {visibleClients.map((c) => {
                  const t = TIER[c.tier] || TIER.healthy;
                  const isConfirmingDelete = deleting === c.session_id;
                  const ragSt = window.ragStatus ? window.ragStatus(c.margin, ragThresholds.op_margin) : null;
                  return (
                    <div key={c.session_id} className="card portfolio-row" style={{
                      padding: "16px 18px", display: "flex", alignItems: "center", gap: 16,
                      borderLeft: `3px solid ${t.color}`, position: "relative",
                    }}>
                      {/* Tier badge */}
                      <div style={{ flexShrink: 0, width: 116 }}>
                        <span style={{
                          display: "inline-flex", alignItems: "center", gap: 5,
                          font: "var(--text-label)", fontSize: 10.5, fontWeight: 700,
                          textTransform: "uppercase", letterSpacing: ".04em",
                          color: t.color, background: t.bg, border: `1px solid ${t.border}`,
                          borderRadius: 20, padding: "3px 9px",
                        }}>
                          <Icon name={t.icon} size={11} /> {t.label}
                        </span>
                      </div>

                      {/* Name + sector + reasons + brief */}
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                          <span style={{ font: "var(--text-body-strong)", fontSize: 14.5, color: "var(--fg-1)" }}>
                            {c.name}
                          </span>
                          <SectorBadge sector={c.sector} />
                          {c.ods_code && (
                            <span title="ODS organisation code — how the ICB, PCSE and NHS Digital identify this organisation"
                              style={{ font: "var(--text-data)", fontSize: 11, letterSpacing: ".03em",
                                color: "var(--fg-3)", background: "var(--surface-2)",
                                border: "1px solid var(--border)", borderRadius: 5,
                                padding: "1px 6px", flexShrink: 0 }}>
                              {c.ods_code}
                            </span>
                          )}
                        </div>
                        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap",
                          font: "var(--text-caption)", fontSize: 11.5, color: "var(--fg-3)", marginBottom: 4 }}>
                          {/* Only show a sector label when there's no badge above; never the raw id */}
                          {!BADGED_SECTORS.has(c.sector) && <span>{sectorLabel(c.sector)}</span>}
                          {c.xero_connected && c.xero_synced_at
                            ? <span style={{ display: "inline-flex", alignItems: "center", gap: 4,
                                color: "var(--xero-text)", fontWeight: 600 }}>
                                · Xero · synced {fmtSyncedAgo(c.xero_synced_at)}
                              </span>
                            : c.updated_at && (
                              <span style={{ opacity: .7 }}>· updated {fmtDate(c.updated_at)}</span>
                            )
                          }
                          {(() => {
                            const f = dataFreshness(c.updated_at);
                            if (!f) return null;
                            return (
                              <span style={{
                                display: "inline-flex", alignItems: "center", gap: 4,
                                font: "var(--text-label)", fontSize: 10, fontWeight: 700,
                                textTransform: "uppercase", letterSpacing: ".04em",
                                color: f.color, background: f.bg, border: `1px solid ${f.border}`,
                                borderRadius: 20, padding: "2px 7px",
                              }}>
                                <Icon name="circle" size={7} style={{ fill: f.color }} /> {f.label}
                              </span>
                            );
                          })()}
                        </div>
                        <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                          {(c.reasons || []).map((r, i) => (
                            <span key={i} style={{
                              font: "var(--text-caption)", fontSize: 11,
                              color: c.tier === "healthy" ? "var(--favourable-text)" : t.color,
                              background: c.tier === "healthy" ? "var(--favourable-soft)" : t.bg,
                              border: `1px solid ${t.border}`, borderRadius: 6, padding: "2px 8px",
                            }}>{r}</span>
                          ))}
                        </div>
                        {/* Unclaimed-funding chips — where the money is, per practice */}
                        {(c.arrs_unclaimed > 0 || c.qof_gap > 0) && (
                          <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 }}>
                            {c.arrs_unclaimed > 0 && (() => {
                              // Claiming-pace momentum vs last month (annualised utilisation)
                              const tr = c.arrs_trend;
                              const arrow = tr === "improving"
                                ? { ch: "▲", col: "var(--favourable-text, #15803d)", tip: `Claiming pace improving (${c.arrs_util_pct}% vs ${c.arrs_util_pct_prev}% last month)` }
                                : tr === "worsening"
                                ? { ch: "▼", col: "var(--adverse-text, #b91c1c)", tip: `Claiming pace slowing (${c.arrs_util_pct}% vs ${c.arrs_util_pct_prev}% last month)` }
                                : null;
                              return (
                                <span title="ARRS allocation not yet drawn (YTD)" style={{
                                  font: "var(--text-caption)", fontSize: 11, cursor: "help",
                                  color: "var(--adverse-text, #b91c1c)", background: "var(--adverse-soft, #fee2e2)",
                                  border: "1px solid var(--adverse-border, #fecaca)", borderRadius: 6, padding: "2px 8px",
                                  display: "inline-flex", alignItems: "center", gap: 4,
                                }}>
                                  £{Math.round(c.arrs_unclaimed).toLocaleString()} ARRS unclaimed
                                  {arrow && <span title={arrow.tip} style={{ color: arrow.col, fontWeight: 700 }}>{arrow.ch}</span>}
                                </span>
                              );
                            })()}
                            {c.qof_gap > 0 && (
                              <span title={`QOF income still short of ${c.qof_gap_estimated ? "estimated " : ""}entitlement (YTD)`} style={{
                                font: "var(--text-caption)", fontSize: 11, cursor: "help",
                                color: "var(--caution-text, #b45309)", background: "var(--caution-soft, #fef3c7)",
                                border: "1px solid var(--caution-border, #fcd34d)", borderRadius: 6, padding: "2px 8px",
                              }}>£{Math.round(c.qof_gap).toLocaleString()} QOF gap{c.qof_gap_estimated ? " (est.)" : ""}</span>
                            )}
                          </div>
                        )}
                        {/* Morning brief — shown when generated */}
                        {briefStatus === "loading" && !briefs[c.session_id] && (
                          <div style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 7,
                            font: "var(--text-caption)", fontSize: 12, color: "var(--fg-3)" }}>
                            <div className="spinner" style={{ width: 12, height: 12, flexShrink: 0 }} />
                            Generating brief…
                          </div>
                        )}
                        {briefs[c.session_id] && (
                          <div style={{
                            marginTop: 10, padding: "9px 12px",
                            background: "var(--primary-soft)", borderRadius: 8,
                            border: "1px solid var(--primary-border, rgba(var(--primary-rgb,79,70,229),.2))",
                            animation: "fadeIn .35s ease",
                          }}>
                            <div style={{ font: "var(--text-caption)", fontSize: 10.5, fontWeight: 700,
                              textTransform: "uppercase", letterSpacing: ".05em",
                              color: "var(--primary-text)", marginBottom: 4 }}>
                              AI Brief
                            </div>
                            <p style={{ margin: 0, font: "var(--text-body)", fontSize: 13, lineHeight: 1.6,
                              color: "var(--fg-1)" }}>
                              {briefs[c.session_id]}
                            </p>
                            <button
                              onClick={() => navigator.clipboard?.writeText(briefs[c.session_id])}
                              style={{ marginTop: 6, background: "none", border: "none", cursor: "pointer",
                                font: "var(--text-caption)", fontSize: 11, color: "var(--primary-text)",
                                padding: 0, display: "inline-flex", alignItems: "center", gap: 4 }}
                            >
                              <Icon name="copy" size={11} /> Copy
                            </button>
                          </div>
                        )}
                      </div>

                      {/* Figures */}
                      <div className="portfolio-row-figures" style={{ display: "flex", gap: 22, flexShrink: 0 }}>
                        {/* Cross-upload profit timeline — builds a point per uploaded month */}
                        {Array.isArray(c.history) && c.history.length >= 2 && (() => {
                          const pts  = c.history.filter(h => typeof h.op_profit === "number");
                          if (pts.length < 2) return null;
                          const last = pts[pts.length - 1], prev = pts[pts.length - 2];
                          const d    = last.op_profit - prev.op_profit;
                          const up   = d >= 0;
                          return (
                            <div style={{ textAlign: "right", minWidth: 72, cursor: "help" }}
                              title={`Profit ${up ? "up" : "down"} £${Math.abs(Math.round(d)).toLocaleString()} vs ${prev.period} · ${pts.length} months tracked (${pts[0].period} → ${last.period})`}>
                              <div style={{ font: "var(--text-label)", fontSize: 9.5, textTransform: "uppercase",
                                letterSpacing: ".04em", color: "var(--fg-3)", marginBottom: 2 }}>Trend</div>
                              <TrendSparkline data={pts} valueKey="op_profit" height={20} />
                              <div style={{ font: "var(--text-caption)", fontSize: 10.5, fontVariantNumeric: "tabular-nums",
                                color: up ? "var(--favourable-text, #15803d)" : "var(--adverse-text, #b91c1c)" }}>
                                {up ? "▲" : "▼"} {fmtGBP(Math.abs(d))}
                              </div>
                            </div>
                          );
                        })()}
                        {[
                          { k: "Revenue",   v: fmtGBP(c.revenue),   color: "var(--ink)" },
                          { k: "Costs",     v: (c.revenue != null && c.op_profit != null) ? fmtGBP(c.revenue - c.op_profit) : "—", color: "var(--fg-2)" },
                          { k: "Op profit", v: fmtGBP(c.op_profit), color: c.op_profit != null && c.op_profit < 0 ? "var(--adverse-text)" : "var(--ink)" },
                          { k: "Margin",    v: c.margin != null ? `${c.margin.toFixed(1)}%` : "—", color: "var(--fg-2)" },
                        ].map((f) => (
                          <div key={f.k} style={{ textAlign: "right", minWidth: 64 }}>
                            <div style={{ font: "var(--text-label)", fontSize: 9.5, textTransform: "uppercase",
                              letterSpacing: ".04em", color: "var(--fg-3)", marginBottom: 2 }}>{f.k}</div>
                            <div style={{ font: "var(--text-data)", fontSize: 14,
                              fontVariantNumeric: "tabular-nums", color: f.color }}>{f.v}</div>
                          </div>
                        ))}
                      </div>

                      {/* Margin RAG badge — hidden on mobile via .portfolio-row-rag */}
                      {ragSt && (
                        <div className="portfolio-row-rag" style={{ flexShrink: 0, alignSelf: "center" }}>
                          <RagBadge status={ragSt} />
                        </div>
                      )}

                      {/* Actions */}
                      <div style={{ flexShrink: 0, display: "flex", gap: 8, alignItems: "center" }}>
                        {!isDemo && !isConfirmingDelete && (
                          <React.Fragment>
                            {/* Xero sync — period toggle + sync button for connected clients */}
                            {c.xero_connected && (
                              <React.Fragment>
                                {/* Period selector: Last 12 months vs NHS financial year */}
                                <div style={{
                                  display: "inline-flex", borderRadius: "var(--radius-sm)",
                                  border: "1px solid var(--xero-border)", overflow: "hidden", flexShrink: 0,
                                }}>
                                  {[
                                    { v: "rolling_12",    label: "12M" },
                                    { v: "financial_year", label: "FY"  },
                                  ].map(opt => {
                                    const active = (xeroSyncPeriod[c.session_id] || "rolling_12") === opt.v;
                                    return (
                                      <button
                                        key={opt.v}
                                        title={opt.v === "rolling_12" ? "Last 12 months" : "NHS financial year (Apr–Mar)"}
                                        onClick={() => setXeroSyncPeriod(prev => ({ ...prev, [c.session_id]: opt.v }))}
                                        style={{
                                          padding: "5px 9px", border: "none",
                                          background: active ? "var(--xero-solid)" : "var(--xero-bg)",
                                          color: active ? "var(--xero-on-solid)" : "var(--xero-text)",
                                          font: "600 10.5px var(--font-display)",
                                          cursor: "pointer", transition: "all .1s",
                                        }}
                                      >
                                        {opt.label}
                                      </button>
                                    );
                                  })}
                                </div>
                                <button
                                  title={c.xero_synced_at ? `Last synced ${fmtSyncedAgo(c.xero_synced_at)}` : "Sync from Xero"}
                                  onClick={() => syncFromXero(c.session_id)}
                                  disabled={!!xeroSyncing[c.session_id]}
                                  style={{
                                    display: "inline-flex", alignItems: "center", gap: 5,
                                    padding: "6px 10px", borderRadius: "var(--radius-sm)",
                                    border: "1px solid var(--xero-border)",
                                    background: "var(--xero-bg)",
                                    color: "var(--xero-text)",
                                    font: "600 11.5px var(--font-display)", cursor: "pointer",
                                    opacity: xeroSyncing[c.session_id] ? .7 : 1,
                                    transition: "all .12s",
                                  }}
                                >
                                  {xeroSyncing[c.session_id]
                                    ? <React.Fragment><div className="spinner" style={{ width: 11, height: 11, borderColor: "var(--xero-text)", borderTopColor: "transparent", flexShrink: 0 }} /> Syncing…</React.Fragment>
                                    : <React.Fragment>
                                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 4v4l3 3-1.41 1.41L11 12.83V6h1z"/></svg>
                                        Sync Xero
                                      </React.Fragment>}
                                </button>
                              </React.Fragment>
                            )}
                            <button onClick={() => setUpdating(c)} title="Update" style={{
                              background: "var(--surface-2)", border: "1px solid var(--border)",
                              borderRadius: "var(--radius-sm)", padding: "6px 8px",
                              cursor: "pointer", color: "var(--fg-2)", display: "flex", alignItems: "center",
                            }}>
                              <Icon name="refresh-cw" size={13} />
                            </button>
                            <button
                              title="Copy share link"
                              onClick={() => {
                                const firm = (() => { try { return localStorage.getItem("meiq_firm_name") || ""; } catch { return ""; } })();
                                const cur  = (() => { try { return localStorage.getItem("meiq_currency_sym") || ""; } catch { return ""; } })();
                                const ps   = [firm ? "firm=" + encodeURIComponent(firm) : "", cur && cur !== "£" ? "cur=" + encodeURIComponent(cur) : "", c.share_token ? "share=" + encodeURIComponent(c.share_token) : ""].filter(Boolean).join("&");
                                const base = `${window.location.origin}/view/${c.session_id}`;
                                const url  = ps ? `${base}?${ps}` : base;
                                navigator.clipboard?.writeText(url);
                                setCopiedLink(c.session_id);
                                setTimeout(() => setCopiedLink(null), 2000);
                              }}
                              style={{
                                background: copiedLink === c.session_id ? "var(--favourable-soft)" : "var(--surface-2)",
                                border: `1px solid ${copiedLink === c.session_id ? "var(--favourable-border)" : "var(--border)"}`,
                                borderRadius: "var(--radius-sm)", padding: "6px 8px",
                                cursor: "pointer",
                                color: copiedLink === c.session_id ? "var(--favourable-text)" : "var(--fg-2)",
                                display: "flex", alignItems: "center",
                                transition: "all .15s",
                              }}
                            >
                              <Icon name={copiedLink === c.session_id ? "check" : "share-2"} size={13} />
                            </button>
                            <button
                              title="Email share link to client"
                              onClick={() => {
                                const firm = (() => { try { return localStorage.getItem("meiq_firm_name") || ""; } catch { return ""; } })();
                                const cur  = (() => { try { return localStorage.getItem("meiq_currency_sym") || ""; } catch { return ""; } })();
                                const ps   = [firm ? "firm=" + encodeURIComponent(firm) : "", cur && cur !== "£" ? "cur=" + encodeURIComponent(cur) : "", c.share_token ? "share=" + encodeURIComponent(c.share_token) : ""].filter(Boolean).join("&");
                                const base = `${window.location.origin}/view/${c.session_id}`;
                                const url  = ps ? `${base}?${ps}` : base;
                                const sign = firm ? `Kind regards,\n${firm}` : "Kind regards";
                                const body = `Hi,\n\nYour latest management pack is ready to view online:\n\n${url}\n\nYou can browse by period and download a PDF copy directly from the link.\n\n${sign}`;
                                const mailto = `mailto:?subject=${encodeURIComponent(`Your management pack — ${c.name}`)}&body=${encodeURIComponent(body)}`;
                                window.location.href = mailto;
                                setEmailedLink(c.session_id);
                                setTimeout(() => setEmailedLink(null), 2500);
                              }}
                              style={{
                                background: emailedLink === c.session_id ? "var(--primary-soft)" : "var(--surface-2)",
                                border: `1px solid ${emailedLink === c.session_id ? "var(--primary)" : "var(--border)"}`,
                                borderRadius: "var(--radius-sm)", padding: "6px 8px",
                                cursor: "pointer",
                                color: emailedLink === c.session_id ? "var(--primary-text)" : "var(--fg-2)",
                                display: "flex", alignItems: "center",
                                transition: "all .15s",
                              }}
                            >
                              <Icon name={emailedLink === c.session_id ? "check" : "mail"} size={13} />
                            </button>
                            <button onClick={() => exportClientData(c)} title="Export all data (SAR / portability)" style={{
                              background: "var(--surface-2)", border: "1px solid var(--border)",
                              borderRadius: "var(--radius-sm)", padding: "6px 8px",
                              cursor: "pointer", color: "var(--fg-2)", display: "flex", alignItems: "center",
                            }}>
                              <Icon name="download" size={13} />
                            </button>
                            <button onClick={() => setDeleting(c.session_id)} title="Erase client (hard delete)" style={{
                              background: "var(--surface-2)", border: "1px solid var(--border)",
                              borderRadius: "var(--radius-sm)", padding: "6px 8px",
                              cursor: "pointer", color: "var(--adverse-text)", display: "flex", alignItems: "center",
                            }}>
                              <Icon name="trash-2" size={13} />
                            </button>
                          </React.Fragment>
                        )}
                        {!isDemo && isConfirmingDelete && (
                          <React.Fragment>
                            <span style={{ fontSize: 12, color: "var(--adverse-text)", fontWeight: 600 }}>Remove?</span>
                            <button onClick={() => confirmDelete(c.session_id)} style={{
                              padding: "5px 12px", borderRadius: "var(--radius-sm)", border: "none",
                              background: "var(--adverse-text)", color: "#fff", fontSize: 12, cursor: "pointer",
                            }}>Yes</button>
                            <button onClick={() => setDeleting(null)} style={{
                              padding: "5px 10px", borderRadius: "var(--radius-sm)",
                              border: "1px solid var(--border-strong)", background: "var(--surface)",
                              color: "var(--fg-2)", fontSize: 12, cursor: "pointer",
                            }}>No</button>
                          </React.Fragment>
                        )}
                        {/* Compare toggle */}
                        <button
                          title={compareIds.has(c.session_id) ? "Remove from comparison" : compareIds.size >= 4 ? "Max 4 selected" : "Compare"}
                          onClick={() => toggleCompare(c.session_id)}
                          style={{
                            background: compareIds.has(c.session_id) ? "var(--primary-soft)" : "var(--surface-2)",
                            border: `1px solid ${compareIds.has(c.session_id) ? "var(--primary)" : "var(--border)"}`,
                            borderRadius: "var(--radius-sm)", padding: "6px 9px",
                            cursor: compareIds.size >= 4 && !compareIds.has(c.session_id) ? "not-allowed" : "pointer",
                            color: compareIds.has(c.session_id) ? "var(--primary-text)" : "var(--fg-2)",
                            display: "flex", alignItems: "center", gap: 5,
                            opacity: compareIds.size >= 4 && !compareIds.has(c.session_id) ? .35 : 1,
                            transition: "all .12s",
                            font: "600 11.5px var(--font-display)",
                          }}
                        >
                          <Icon name={compareIds.has(c.session_id) ? "check-square" : "layout-template"} size={13} />
                          {compareIds.has(c.session_id) ? "Selected" : "Compare"}
                        </button>
                        <button onClick={() => onOpenClient && onOpenClient(c.session_id, c.name)} style={{
                          display: "inline-flex", alignItems: "center", gap: 6,
                          padding: "8px 14px", borderRadius: "var(--radius-sm)", border: "none",
                          background: "var(--primary)", color: "var(--on-primary)",
                          font: "var(--text-body-strong)", fontSize: 13, cursor: "pointer",
                        }}>
                          Open <Icon name="arrow-right" size={14} />
                        </button>
                      </div>
                    </div>
                  );
                })}
              </div>
            )}

            {/* NHS GP Benchmarking table */}
            {(() => {
              const gpPractices = (data?.clients || []).filter(
                c => c.sector === "nhs_gp" && c.list_size > 0
              );
              if (gpPractices.length < 2) return null;

              const rows = gpPractices.map(c => {
                const rev  = c.revenue    || 0;
                const cost = rev - (c.op_profit || 0);
                const nonClinical = cost * 0.6; // approx overhead share
                return {
                  name:             c.name,
                  list_size:        c.list_size,
                  income_per_pt:    c.list_size > 0 ? rev  / c.list_size : null,
                  cost_per_pt:      c.list_size > 0 ? cost / c.list_size : null,
                  overhead_pct:     rev > 0 ? (nonClinical / rev * 100) : null,
                  revenue:          rev,
                  total_cost:       cost,
                  // Quartile within this firm's own cohort, computed server-side.
                  q_income:         (c.cohort_quartile || {}).income_per_patient ?? null,
                };
              });
              const cohort = data?.cohort?.income_per_patient || null;

              // Sort by income per patient descending
              rows.sort((a, b) => (b.income_per_pt || 0) - (a.income_per_pt || 0));

              // Federation totals row
              const totRev  = rows.reduce((s, r) => s + r.revenue, 0);
              const totCost = rows.reduce((s, r) => s + r.total_cost, 0);
              const totLS   = rows.reduce((s, r) => s + r.list_size, 0);
              const fedRow  = {
                name:          "Federation totals",
                list_size:     totLS,
                income_per_pt: totLS > 0 ? totRev  / totLS : null,
                cost_per_pt:   totLS > 0 ? totCost / totLS : null,
                overhead_pct:  totRev > 0 ? ((totCost * 0.6) / totRev * 100) : null,
                revenue:       totRev,
                total_cost:    totCost,
                isFedTotal:    true,
              };

              const fmt2 = v => v != null ? `£${v.toFixed(2)}` : "—";
              const fmtP = v => v != null ? `${v.toFixed(1)}%` : "—";
              const thStyle = {
                font: "var(--text-label)", fontSize: 10.5, fontWeight: 700,
                textTransform: "uppercase", letterSpacing: ".05em",
                color: "var(--fg-3)", padding: "8px 12px",
                textAlign: "right", borderBottom: "1px solid var(--border)",
                whiteSpace: "nowrap",
              };
              const tdStyle = {
                padding: "10px 12px", font: "var(--text-body)", fontSize: 13,
                color: "var(--ink)", borderBottom: "1px solid var(--border)",
                fontVariantNumeric: "tabular-nums", textAlign: "right",
              };
              const tdNameStyle = { ...tdStyle, textAlign: "left", fontWeight: 500 };
              const fedStyle = { background: "var(--surface-2)", fontWeight: 700 };

              // Cohort-quartile badge: Q4 = top of this firm's own practices.
              const QUARTILE = {
                4: { bg: "var(--favourable-soft)", color: "var(--favourable-text)", label: "Q4 · top" },
                3: { bg: "var(--favourable-soft)", color: "var(--favourable-text)", label: "Q3" },
                2: { bg: "var(--caution-soft, #fef3c7)", color: "var(--caution-text, #b45309)", label: "Q2" },
                1: { bg: "var(--adverse-soft)", color: "var(--adverse-text)", label: "Q1 · bottom" },
              };
              const quartileBadge = q => {
                const s = QUARTILE[q];
                if (!s) return <span style={{ color: "var(--fg-3)" }}>—</span>;
                return (
                  <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>
                );
              };

              return (
                <div style={{ marginTop: 28 }}>
                  <div style={{ marginBottom: 12, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12 }}>
                    <div>
                      <h3 style={{ font: "600 15px/1.2 var(--font-display)", color: "var(--ink)", margin: "0 0 3px" }}>
                        NHS GP Benchmarking
                      </h3>
                      <p style={{ font: "var(--text-caption)", fontSize: 12, color: "var(--fg-3)", margin: 0 }}>
                        Per-patient metrics across {gpPractices.length} NHS GP practices
                        {cohort && <> · cohort median £{cohort.median.toFixed(2)}/patient</>}
                      </p>
                    </div>
                    {mode === "real" && (
                      <button onClick={() => setShowBulkQof(true)} style={{
                        flexShrink: 0, padding: "6px 12px", borderRadius: "var(--radius-sm)",
                        border: "1px solid var(--border-strong)", background: "var(--surface)",
                        color: "var(--fg-2)", fontSize: 12.5, cursor: "pointer",
                        display: "inline-flex", alignItems: "center", gap: 6,
                      }}>
                        <Icon name="upload" size={13} /> Bulk QOF
                      </button>
                    )}
                  </div>
                  <div className="card" style={{ padding: 0, overflow: "hidden" }}>
                    <table style={{ width: "100%", borderCollapse: "collapse" }}>
                      <thead>
                        <tr>
                          <th style={{ ...thStyle, textAlign: "left" }}>Practice</th>
                          <th style={thStyle}>List size</th>
                          <th style={thStyle}>Income / patient</th>
                          {cohort && <th style={thStyle} title="Quartile within this portfolio's own practices">Cohort rank</th>}
                          <th style={thStyle}>Cost / patient</th>
                          <th style={thStyle}>Overhead %</th>
                        </tr>
                      </thead>
                      <tbody>
                        {rows.map((r, i) => (
                          <tr key={r.name}>
                            <td style={tdNameStyle}>
                              {i === 0 && (
                                <span style={{
                                  display: "inline-block", marginRight: 6,
                                  font: "var(--text-label)", fontSize: 9.5, fontWeight: 700,
                                  textTransform: "uppercase", letterSpacing: ".04em",
                                  color: "var(--favourable-text)", background: "var(--favourable-soft)",
                                  border: "1px solid var(--favourable-border)", borderRadius: 20,
                                  padding: "1px 6px",
                                }}>Top</span>
                              )}
                              {r.name}
                            </td>
                            <td style={tdStyle}>{r.list_size.toLocaleString()}</td>
                            <td style={tdStyle}>{fmt2(r.income_per_pt)}</td>
                            {cohort && <td style={tdStyle}>{quartileBadge(r.q_income)}</td>}
                            <td style={tdStyle}>{fmt2(r.cost_per_pt)}</td>
                            <td style={tdStyle}>{fmtP(r.overhead_pct)}</td>
                          </tr>
                        ))}
                        {/* Federation totals row */}
                        <tr style={fedStyle}>
                          <td style={{ ...tdNameStyle, ...fedStyle, borderBottom: "none", borderTop: "2px solid var(--border)" }}>
                            Federation totals
                          </td>
                          <td style={{ ...tdStyle, ...fedStyle, borderBottom: "none", borderTop: "2px solid var(--border)" }}>
                            {fedRow.list_size.toLocaleString()}
                          </td>
                          <td style={{ ...tdStyle, ...fedStyle, borderBottom: "none", borderTop: "2px solid var(--border)" }}>
                            {fmt2(fedRow.income_per_pt)}
                          </td>
                          {cohort && (
                            <td style={{ ...tdStyle, ...fedStyle, borderBottom: "none", borderTop: "2px solid var(--border)" }}>—</td>
                          )}
                          <td style={{ ...tdStyle, ...fedStyle, borderBottom: "none", borderTop: "2px solid var(--border)" }}>
                            {fmt2(fedRow.cost_per_pt)}
                          </td>
                          <td style={{ ...tdStyle, ...fedStyle, borderBottom: "none", borderTop: "2px solid var(--border)" }}>
                            {fmtP(fedRow.overhead_pct)}
                          </td>
                        </tr>
                      </tbody>
                    </table>
                  </div>
                  <div style={{ marginTop: 6, font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)" }}>
                    Overhead % approximated as 60% of total costs ÷ revenue. Income and cost per patient based on weighted list size.
                    {cohort && <> Cohort rank is each practice's income-per-patient quartile within this portfolio (Q4 = top 25%).</>}
                  </div>
                </div>
              );
            })()}

            {/* Neighbourhoods */}
            {(() => {
              const nhsClients = (data?.clients || []).filter(c => c.sector === "nhs_gp");

              const fmt = v => v == null ? "—" : "£" + Math.round(v).toLocaleString();
              const pct = v => v == null ? "—" : v.toFixed(1) + "%";
              const pctColor = (v, low, high) => {
                if (v == null) return "var(--fg-3)";
                if (v >= high) return "var(--favourable-text,#15803d)";
                if (v >= low) return "var(--caution-text,#b45309)";
                return "var(--adverse-text,#b91c1c)";
              };

              async function generateShare(n) {
                setNeighShareStates(p => ({ ...p, [n.id]: "loading" }));
                try {
                  const r = await fetch(
                    apiUrl(`/api/portfolio/neighbourhoods/${n.id}/share?firm_token=${encodeURIComponent(firmToken)}`),
                    { method: "POST" }
                  );
                  if (!r.ok) throw new Error();
                  const { share_token } = await r.json();
                  const url = `${window.location.origin}/portal/neighbourhood/${share_token}`;
                  await navigator.clipboard?.writeText(url);
                  setNeighbourhoods(prev => prev.map(x => x.id === n.id ? { ...x, has_share: true, share_token } : x));
                  setNeighShareStates(p => ({ ...p, [n.id]: "copied" }));
                  setTimeout(() => setNeighShareStates(p => ({ ...p, [n.id]: "idle" })), 2500);
                } catch { setNeighShareStates(p => ({ ...p, [n.id]: "idle" })); }
              }

              async function copyShare(n) {
                const url = `${window.location.origin}/portal/neighbourhood/${n.share_token}`;
                await navigator.clipboard?.writeText(url);
                setNeighShareStates(p => ({ ...p, [n.id]: "copied" }));
                setTimeout(() => setNeighShareStates(p => ({ ...p, [n.id]: "idle" })), 2500);
              }

              async function revokeShare(n) {
                await fetch(
                  apiUrl(`/api/portfolio/neighbourhoods/${n.id}/share?firm_token=${encodeURIComponent(firmToken)}`),
                  { method: "DELETE" }
                );
                setNeighbourhoods(prev => prev.map(x => x.id === n.id ? { ...x, has_share: false, share_token: null } : x));
              }

              async function deleteNeigh(id) {
                await fetch(
                  apiUrl(`/api/portfolio/neighbourhoods/${id}?firm_token=${encodeURIComponent(firmToken)}`),
                  { method: "DELETE" }
                );
                setNeighbourhoods(prev => prev.filter(x => x.id !== id));
              }

              async function setParent(id, parentId) {
                const r = await fetch(
                  apiUrl(`/api/portfolio/neighbourhoods/${id}/parent`),
                  { method: "PATCH", headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({ firm_token: firmToken, parent_id: parentId || null }) }
                );
                if (r.ok) loadNeighbourhoods();
                else if (onToast) onToast((await r.json().catch(() => ({}))).detail || "Could not nest group");
              }

              // Order the flat list into a depth-first tree so children render
              // indented under their parent (PCN → locality → ICB).
              const byId = Object.fromEntries(neighbourhoods.map(n => [n.id, n]));
              const roots = neighbourhoods.filter(n => !n.parent_id || !byId[n.parent_id]);
              const ordered = [];
              const walk = (node, depth) => {
                ordered.push({ node, depth });
                neighbourhoods.filter(c => c.parent_id === node.id)
                  .forEach(c => walk(c, depth + 1));
              };
              roots.forEach(r => walk(r, 0));
              const LEVEL_BADGE = { icb: "ICB", place: "Place", locality: "Locality" };

              return (
                <div style={{ marginTop: 32 }}>
                  {!isDemo && <AdoptionPanel />}
                  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14, marginTop: 22 }}>
                    <div>
                      <h3 style={{ margin: "0 0 2px", font: "700 15px/1.2 var(--font-display)", color: "var(--ink)" }}>
                        Neighbourhoods
                      </h3>
                      <p style={{ margin: 0, font: "var(--text-caption)", fontSize: 12, color: "var(--fg-3)" }}>
                        Borough-level reporting across PCNs &mdash; individual books stay separate
                      </p>
                    </div>
                    {!isDemo && (
                      <button
                        onClick={() => setShowNeighModal(true)}
                        style={{
                          display: "inline-flex", alignItems: "center", gap: 6,
                          padding: "8px 14px", borderRadius: "var(--radius-sm)", border: "none",
                          background: "var(--primary)", color: "var(--on-primary)",
                          font: "var(--text-body-strong)", fontSize: 12.5, cursor: "pointer", whiteSpace: "nowrap",
                        }}
                      >
                        <Icon name="plus" size={13} /> New
                      </button>
                    )}
                  </div>

                  {neighbourhoods.length === 0 && (
                    <div style={{ padding: "18px 20px", border: "1px dashed var(--border-strong)", borderRadius: 12,
                                  textAlign: "center", color: "var(--fg-3)", fontSize: 13 }}>
                      No neighbourhoods yet. Create one to generate a borough-level portal link for external stakeholders.
                    </div>
                  )}

                  <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
                    {ordered.map(({ node: n, depth }) => {
                      const agg = n.aggregate || {};
                      const shareState = neighShareStates[n.id] || "idle";
                      const levelLabel = LEVEL_BADGE[n.level];
                      // Candidate parents: any group that isn't this node (cycle
                      // prevention is enforced server-side too).
                      const parentOptions = neighbourhoods.filter(o => o.id !== n.id);
                      return (
                        <div key={n.id} style={{
                          border: "1px solid var(--border)", borderRadius: 14,
                          overflow: "hidden", background: "var(--surface)",
                          marginLeft: depth * 22,
                        }}>
                          {/* Neighbourhood header */}
                          <div style={{ padding: "14px 18px", display: "flex", alignItems: "flex-start",
                                        justifyContent: "space-between", gap: 12,
                                        borderBottom: "1px solid var(--border)", background: "var(--surface-2,#f8fafc)" }}>
                            <div>
                              <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
                                <Icon name={levelLabel ? "git-branch" : "map-pin"} size={14} style={{ color: "var(--primary,#2563eb)" }} />
                                <span style={{ font: "700 14px/1 var(--font-display)", color: "var(--ink)" }}>
                                  {n.name}
                                </span>
                                {levelLabel && (
                                  <span style={{ font: "600 9.5px var(--font-display)", color: "var(--primary-text)",
                                    background: "var(--primary-soft,#eff6ff)", borderRadius: 10, padding: "1px 7px",
                                    textTransform: "uppercase", letterSpacing: ".04em" }}>{levelLabel}</span>
                                )}
                              </div>
                              <div style={{ marginTop: 4, fontSize: 12, color: "var(--fg-3)" }}>
                                {agg.pcn_count} practice{agg.pcn_count !== 1 ? "s" : ""}&nbsp;&middot;&nbsp;
                                {agg.total_list_size ? agg.total_list_size.toLocaleString() + " patients" : ""}
                                {n.child_ids?.length ? ` · ${n.child_ids.length} sub-group${n.child_ids.length !== 1 ? "s" : ""}` : ""}
                              </div>
                            </div>
                            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                              {!isDemo && parentOptions.length > 0 && (
                                <select value={n.parent_id || ""} onChange={e => setParent(n.id, e.target.value)}
                                  title="Nest this group under a parent (locality / ICB)"
                                  style={{ font: "var(--text-body)", fontSize: 11.5, padding: "5px 7px",
                                    borderRadius: "var(--radius-sm)", border: "1px solid var(--border-strong)",
                                    background: "var(--surface)", color: "var(--fg-2)", maxWidth: 150 }}>
                                  <option value="">Top level</option>
                                  {parentOptions.map(o => (
                                    <option key={o.id} value={o.id}>Under: {o.name}</option>
                                  ))}
                                </select>
                              )}
                              {/* Share link controls — real mode only */}
                              {!isDemo && n.has_share ? (
                                <React.Fragment>
                                  <button onClick={() => copyShare(n)} style={{
                                    display: "inline-flex", alignItems: "center", gap: 5,
                                    padding: "6px 12px", borderRadius: "var(--radius-sm)",
                                    border: "1px solid var(--primary-border,rgba(37,99,235,.25))",
                                    background: shareState === "copied" ? "var(--primary)" : "var(--primary-soft,#eff6ff)",
                                    color: shareState === "copied" ? "#fff" : "var(--primary)",
                                    fontSize: 12, cursor: "pointer", whiteSpace: "nowrap",
                                  }}>
                                    <Icon name={shareState === "copied" ? "check" : "copy"} size={12} />
                                    {shareState === "copied" ? "Copied!" : "Copy portal link"}
                                  </button>
                                  <button onClick={() => revokeShare(n)} title="Revoke link" style={{
                                    padding: "6px 8px", borderRadius: "var(--radius-sm)",
                                    border: "1px solid var(--adverse-border,#fecaca)",
                                    background: "var(--adverse-soft,#fef2f2)", color: "var(--adverse-text,#b91c1c)",
                                    fontSize: 12, cursor: "pointer",
                                  }}>
                                    <Icon name="link-off" size={12} />
                                  </button>
                                </React.Fragment>
                              ) : !isDemo ? (
                                <button onClick={() => generateShare(n)} style={{
                                  display: "inline-flex", alignItems: "center", gap: 5,
                                  padding: "6px 12px", borderRadius: "var(--radius-sm)",
                                  border: "1px solid var(--border-strong)",
                                  background: "var(--surface)", color: "var(--fg-2)",
                                  fontSize: 12, cursor: "pointer", whiteSpace: "nowrap",
                                }}>
                                  {shareState === "loading"
                                    ? <React.Fragment><div className="spinner" style={{ width: 10, height: 10 }} /> Generating…</React.Fragment>
                                    : <React.Fragment><Icon name="share-2" size={12} /> Generate portal link</React.Fragment>}
                                </button>
                              ) : null}
                              {!isDemo && (
                                <button onClick={() => deleteNeigh(n.id)} title="Delete neighbourhood" style={{
                                  padding: "6px 8px", borderRadius: "var(--radius-sm)",
                                  border: "1px solid var(--border-strong)", background: "none",
                                  color: "var(--fg-3)", cursor: "pointer",
                                }}>
                                  <Icon name="trash-2" size={12} />
                                </button>
                              )}
                            </div>
                          </div>

                          {/* Aggregate KPI row */}
                          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(140px,1fr))",
                                        gap: 0, padding: "12px 18px 14px" }}>
                            {[
                              { label: "Income/patient",  value: fmt(agg.income_per_patient) },
                              { label: "Surplus/patient", value: fmt(agg.surplus_per_patient) },
                              { label: "ARRS util.",
                                value: pct(agg.avg_arrs_utilisation_pct),
                                color: pctColor(agg.avg_arrs_utilisation_pct, 50, 80) },
                              { label: "QOF ach.",
                                value: pct(agg.avg_qof_achievement_pct),
                                color: pctColor(agg.avg_qof_achievement_pct, 80, 95) },
                            ].map(kpi => (
                              <div key={kpi.label} style={{ padding: "4px 0" }}>
                                <div style={{ fontSize: 10.5, color: "var(--fg-3)", textTransform: "uppercase",
                                              letterSpacing: ".05em", fontWeight: 600, marginBottom: 2 }}>
                                  {kpi.label}
                                </div>
                                <div style={{ font: "700 15px/1 var(--font-display)", color: kpi.color || "var(--ink)" }}>
                                  {kpi.value}
                                </div>
                              </div>
                            ))}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            })()}

            {/* Briefing error */}
            {briefStatus === "error" && (
              <div style={{ marginTop: 14, padding: "10px 14px", background: "var(--adverse-soft)",
                border: "1px solid var(--adverse-border)", borderRadius: "var(--radius-sm)",
                font: "var(--text-body)", fontSize: 13, color: "var(--adverse-text)" }}>
                Briefing failed. Make sure <code>OPENAI_API_KEY</code> is set on the server.{" "}
                <button onClick={generateBriefing} style={{ marginLeft: 8, textDecoration: "underline",
                  background: "none", border: "none", color: "inherit", cursor: "pointer" }}>Retry</button>
              </div>
            )}

            {/* Copy all briefs to clipboard */}
            {briefStatus === "done" && Object.keys(briefs).length > 0 && (
              <div style={{ marginTop: 12, display: "flex", alignItems: "center", justifyContent: "space-between",
                padding: "10px 14px", background: "var(--primary-soft)",
                borderRadius: "var(--radius-sm)", border: "1px solid var(--primary-border, rgba(79,70,229,.2))" }}>
                <span style={{ font: "var(--text-body)", fontSize: 13, color: "var(--primary-text)" }}>
                  <Icon name="check-circle" size={14} style={{ verticalAlign: "middle", marginRight: 6 }} />
                  Morning briefing ready — {Object.keys(briefs).length} client{Object.keys(briefs).length !== 1 ? "s" : ""}
                </span>
                <button
                  onClick={() => {
                    const lines = (data?.clients || [])
                      .filter(c => briefs[c.session_id])
                      .map(c => `${c.name}\n${briefs[c.session_id]}`);
                    navigator.clipboard?.writeText(lines.join("\n\n"));
                  }}
                  style={{ background: "none", border: "none", cursor: "pointer",
                    font: "var(--text-body-strong)", fontSize: 12.5, color: "var(--primary-text)",
                    display: "inline-flex", alignItems: "center", gap: 5 }}
                >
                  <Icon name="copy" size={13} /> Copy all
                </button>
              </div>
            )}

            {isDemo && (
              <div style={{ font: "var(--text-caption)", fontSize: 11, color: "var(--fg-3)", marginTop: 16, lineHeight: 1.5 }}>
                <Icon name="info" size={11} /> Demo portfolio with synthetic data. Switch to{" "}
                <button onClick={() => switchMode("real")} style={{
                  background: "none", border: "none", color: "var(--primary-text)",
                  cursor: "pointer", fontSize: 11, padding: 0, textDecoration: "underline",
                }}>My clients</button>{" "}
                and upload real P&amp;Ls to build your live practice triage.
              </div>
            )}
          </React.Fragment>
        )}
      </div>

      {/* Modals */}
      {showAccount && (
        <FirmAccountModal firmToken={firmToken} onClose={() => setShowAccount(false)} />
      )}
      {showAdd && (
        <AddClientModal firmToken={firmToken} onClose={() => setShowAdd(false)} onAdded={handleAdded} />
      )}
      {updating && (
        <UpdateCashModal client={updating} firmToken={firmToken}
          onClose={() => setUpdating(null)} onUpdated={handleUpdated} />
      )}
      {showNeighModal && (
        <CreateNeighbourhoodModal
          firmToken={firmToken}
          nhsClients={(data?.clients || []).filter(c => c.sector === "nhs_gp")}
          onClose={() => setShowNeighModal(false)}
          onCreated={(n) => {
            setNeighbourhoods(prev => [n, ...prev]);
            setShowNeighModal(false);
          }}
        />
      )}
      {showCompare && compareData && (
        <ClientCompareModal
          data={compareData}
          onClose={() => { setShowCompare(false); setCompareData(null); }}
        />
      )}
      {showBulkQof && (
        <BulkQofModal
          firmToken={firmToken}
          nhsClients={(data?.clients || []).filter(c => c.sector === "nhs_gp")}
          onClose={() => setShowBulkQof(false)}
          onDone={(res) => {
            load();
            if (onToast) onToast(`QOF updated for ${res.updated} practice${res.updated === 1 ? "" : "s"}`);
          }}
        />
      )}
      {showBulkUpload && (
        <BulkUploadModal
          firmToken={firmToken}
          onClose={() => setShowBulkUpload(false)}
          onDone={(res) => {
            load();
            if (onToast && res.added_count) onToast(`Added ${res.added_count} practice${res.added_count === 1 ? "" : "s"}`);
          }}
        />
      )}
    </div>
  );
}

Object.assign(window, { Portfolio });
