/* INTEL - COD-style briefing cutscene + interactive board.
   Entering Intel plays a sequenced "mission briefing": secure uplink →
   target lock (reticle) → identity → classified lines typing out →
   known associates blipping in → PROCEED. Then it settles into the
   interactive crime board (subject centre, associates on red string,
   click to re-acquire). Exports: IntelView.                            */

function relVerb(a, b) {
  const D = window.DATA;
  const e = D.edges.find((x) => (x.a === a && x.b === b) || (x.a === b && x.b === a));
  const t = e ? e.type : "linked";
  return ({ funded: "FUNDS", unfunded: "TRACKS", account: "HANDLES", works_at: "EMPLOYS",
    has_project: "RUNS", relates: "LINKED", contact: "CONTACT", attended: "ATTENDED", linked: "ALLIED" })[t] || "LINKED";
}

function pickAssociates(subjectId) {
  const D = window.DATA;
  const ids = [...new Set(D.adj[subjectId] || [])];
  const rank = (id) => {
    const k = D.kindOf(id), e = D.byId[id];
    let s = 0;
    if (k === "person") s += 5;
    if (k === "org" && e.priority === "key") s += 4;
    if (k === "org" && e.priority === "need") s += 3;
    if (k === "org") s += 2;
    if (k === "funder") s += 6;
    if (k === "project") s += 1;
    return -s;
  };
  return ids.sort((a, b) => rank(a) - rank(b)).slice(0, 10);
}

/* ---- briefing helpers ---- */
function useRaf(active) {
  const [t, setT] = React.useState(0);
  React.useEffect(() => {
    if (!active) return;
    let raf, start = performance.now();
    const loop = (now) => { setT(now - start); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [active]);
  return t;
}
const typed = (str, startMs, t, cps = 46) => {
  if (t < startMs) return "";
  return (str || "").slice(0, Math.floor((t - startMs) * cps / 1000));
};
const typeDone = (str, startMs, t, cps = 46) => t >= startMs + ((str || "").length / cps) * 1000;
const fmtTC = (ms) => {
  const s = Math.floor(ms / 1000), f = Math.floor((ms % 1000) / 1000 * 24);
  return `00:${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}:${String(f).padStart(2, "0")}`;
};

function buildBrief(id) {
  const D = window.DATA;
  const k = D.kindOf(id), e = D.byId[id];
  const out = [];
  if (k === "org") {
    const g = D.orgGap(e); const mh = D.milestoneHealth(id);
    out.push(["PROFILE", e.mission]);
    out.push(["STATUS", `${e.relationship.toUpperCase()} · ${e.funded ? "FUNDED" : "NOT FUNDED"} · ${e.reach.toUpperCase()} REACH`]);
    out.push(["FUNDING", `${D.fmtMoney(e.funding.awarded)} / ${D.fmtMoney(e.funding.ask)}${g ? ` · GAP ${D.fmtMoney(g)}` : ""}`]);
    out.push(["MILESTONES", `${mh.verdict.toUpperCase().replace("-", " ")} - ${mh.met}/${mh.total} MET${mh.behind ? `, ${mh.behind} BEHIND` : ""}`]);
    out.push(["THREAT ASSESSMENT", `${e.risk.level.toUpperCase()} - ${e.risk.note}`]);
    out.push(["HANDLER", e.account ? D.byId[e.account].name : "-"]);
    if (e.contact) out.push(["FIELD CONTACT", D.byId[e.contact].name]);
  } else if (k === "person") {
    const org = e.org ? D.byId[e.org] : null;
    out.push(["ROLE", e.role || e.title]);
    out.push(["CLEARANCE", e.clearance || "EXTERNAL"]);
    out.push(["AFFILIATION", org ? org.name : (e.team ? "THE WELLSPRING FUND" : "INDEPENDENT")]);
    out.push(["COMMS", `${e.email}  ·  ${e.phone}`]);
    out.push(["NOTES", e.notes]);
  } else if (k === "project") {
    const org = D.byId[e.org];
    out.push(["BRIEF", e.notes]);
    out.push(["TYPE", `${e.type.toUpperCase()} · ${e.year}`]);
    out.push(["STATUS", e.status.toUpperCase()]);
    out.push(["PARENT ORG", org.name]);
    if (e.contact) out.push(["LEAD", D.byId[e.contact].name]);
  } else if (k === "event") {
    out.push(["BRIEF", e.notes]);
    out.push(["DATE", new Date(e.date).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }).toUpperCase()]);
    out.push(["PRESENT", `${(e.orgs || []).length} ORGS · ${(e.attendees || []).length} ATTENDEES`]);
  } else {
    out.push(["PROFILE", "Centre of the ecosystem. Every organisation connects here by a funding relationship."]);
    out.push(["FUNDED", `${D.counts.funded} / ${D.counts.orgs} ORGS`]);
    out.push(["OPEN GAP", `${D.fmtMoney(D.totalGap())} ACROSS ${D.needsFunding().length} ORGS`]);
  }
  return out;
}

/* ============================ CUTSCENE ============================ */
function BriefingCutscene({ subjectId, assoc, onProceed, onSkip }) {
  const D = window.DATA;
  const k = D.kindOf(subjectId);
  const e = D.byId[subjectId];
  const m = typeMeta(k);
  const lines = React.useMemo(() => buildBrief(subjectId), [subjectId]);
  const classif = (k === "org" ? e.classification : k === "person" ? e.clearance : null) || "RESTRICTED";

  // timeline (ms)
  const T = {};
  T.uplinkEnd = 1200;
  T.header = 1250;
  T.lockStart = 1650; T.lockEnd = 2550;
  T.name = 2600; T.code = 3250;
  T.lines = 3750; T.linePer = 560;
  T.linesEnd = T.lines + lines.length * T.linePer;
  T.assoc = T.linesEnd + 250; T.assocPer = 130;
  T.assocEnd = T.assoc + assoc.length * T.assocPer;
  T.complete = T.assocEnd + 500;

  const t = useRaf(true);

  // auto-proceed safeguard
  React.useEffect(() => {
    if (t >= T.complete + 9000) onProceed();
  }, [t >= T.complete + 9000]); // eslint-disable-line

  const lockP = Math.max(0, Math.min(1, (t - T.lockStart) / (T.lockEnd - T.lockStart)));
  const bootLines = [
    ["> ESTABLISHING SECURE UPLINK", 0],
    ["> AUTHENTICATING CREDENTIALS … OK", 350],
    ["> DECRYPTING DOSSIER PACKET", 700],
  ];
  const progress = Math.min(100, (t / T.uplinkEnd) * 100);
  const scanY = (t / 22) % 100;
  const blink = Math.floor(t / 500) % 2 === 0;

  const showHeader = t >= T.header;
  const showId = t >= T.lockStart;
  const showLines = t >= T.lines;
  const showAssoc = t >= T.assoc;
  const showProceedBtn = t >= T.complete;

  // reticle bracket offset (converges)
  const off = 64 - lockP * 50; // 64 → 14
  const bsize = 96; // glyph frame
  const Bracket = ({ corner }) => {
    const s = { top: { top: -off, borderBottom: "none", borderRight: corner === "tl" ? "none" : undefined } };
    const base = { position: "absolute", width: 20, height: 20, borderColor: "var(--i-cyan)", borderStyle: "solid", opacity: 0.4 + lockP * 0.6 };
    const map = {
      tl: { top: -off, left: -off, borderWidth: "2px 0 0 2px" },
      tr: { top: -off, right: -off, borderWidth: "2px 2px 0 0" },
      bl: { bottom: -off, left: -off, borderWidth: "0 0 2px 2px" },
      br: { bottom: -off, right: -off, borderWidth: "0 2px 2px 0" },
    };
    return <span style={{ ...base, ...map[corner] }} />;
  };

  return (
    <div className="intel-root cod-topo" style={{ display: "flex", flexDirection: "column" }}>
      <div className="cod-noise" />
      {/* moving scanline */}
      <div style={{ position: "absolute", left: 0, right: 0, top: scanY + "%", height: 2, background: "linear-gradient(90deg, transparent, rgba(79,209,224,0.5), transparent)", pointerEvents: "none", zIndex: 2 }} />
      <div className="intel-scan" style={{ zIndex: 1 }} />

      {/* HUD top */}
      <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 46, display: "flex", alignItems: "center", gap: 14, padding: "0 18px", zIndex: 10, borderBottom: "1px solid var(--i-line)", background: "rgba(10,12,18,0.5)" }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
          <span style={{ width: 9, height: 9, borderRadius: 999, background: "var(--i-string)", opacity: blink ? 1 : 0.25 }} />
          <span className="intel-mono" style={{ fontSize: 11, letterSpacing: "0.16em", color: "var(--i-text)" }}>REC</span>
        </span>
        <span className="intel-mono" style={{ fontSize: 11, color: "var(--i-cyan)", letterSpacing: "0.1em" }}>{fmtTC(t)}</span>
        <span className="intel-mono" style={{ fontSize: 10.5, color: "var(--i-faint)", letterSpacing: "0.1em" }}>SAT-LINK // CH-07</span>
        <div style={{ flex: 1 }} />
        <span className="intel-stamp">EYES ONLY</span>
        <button onClick={onSkip} className="focusable intel-mono" style={{ fontSize: 11, letterSpacing: "0.12em", color: "var(--i-muted)", background: "transparent", border: "1px solid var(--i-line)", borderRadius: 6, padding: "6px 11px", cursor: "pointer" }}>SKIP ▸</button>
      </div>

      {/* body */}
      <div style={{ position: "absolute", inset: "46px 0 0 0", overflow: "auto", display: "flex", justifyContent: "center", zIndex: 4 }}>
        <div style={{ width: 660, maxWidth: "92%", padding: "30px 0 60px" }}>

          {/* UPLINK boot */}
          <div style={{ marginBottom: 22 }}>
            {bootLines.map(([txt, st], i) => (
              <div key={i} className="intel-mono" style={{ fontSize: 12, color: i === 2 ? "var(--i-amber)" : "var(--i-muted)", lineHeight: 1.9, letterSpacing: "0.04em" }}>
                {typed(txt, st, t, 60)}{!typeDone(txt, st, t, 60) && t > st ? "▍" : (typeDone(txt, st, t, 60) ? "  ✓" : "")}
              </div>
            ))}
            <div style={{ marginTop: 8, height: 6, background: "var(--i-panel)", border: "1px solid var(--i-line)", borderRadius: 3, overflow: "hidden", maxWidth: 320 }}>
              <div style={{ width: progress + "%", height: "100%", background: "linear-gradient(90deg, var(--i-cyan), var(--i-amber))" }} />
            </div>
          </div>

          {/* HEADER slam */}
          {showHeader && (
            <div style={{ borderTop: "1px solid var(--i-line)", paddingTop: 20, marginBottom: 22, transform: t < T.header + 180 ? "scale(1.04)" : "none", transition: "transform .18s" }}>
              <div className="intel-mono" style={{ fontSize: 11, letterSpacing: "0.2em", color: "var(--i-string-soft)", marginBottom: 6 }}>// INCOMING BRIEFING - PRIORITY ALPHA</div>
              <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                <span className={"intel-stamp rot " + (classif === "SECRET" ? "" : classif === "CONFIDENTIAL" ? "amber" : "cyan")}>{classif}</span>
                <span className="intel-mono" style={{ fontSize: 11, color: "var(--i-faint)" }}>FILE {(e.idcode || subjectId).toUpperCase()}</span>
              </div>
            </div>
          )}

          {/* IDENTITY + reticle */}
          {showId && (
            <div style={{ display: "flex", gap: 26, alignItems: "center", marginBottom: 26 }}>
              <div style={{ position: "relative", width: bsize, height: bsize, flex: "none", display: "grid", placeItems: "center" }}>
                {/* crosshair */}
                <span style={{ position: "absolute", left: "50%", top: -off - 6, width: 1, height: off + 6, background: "var(--i-cyan)", opacity: 0.5 * lockP }} />
                <span style={{ position: "absolute", left: "50%", bottom: -off - 6, width: 1, height: off + 6, background: "var(--i-cyan)", opacity: 0.5 * lockP }} />
                <Bracket corner="tl" /><Bracket corner="tr" /><Bracket corner="bl" /><Bracket corner="br" />
                <div style={{ transform: `scale(${0.7 + lockP * 0.3})`, opacity: 0.5 + lockP * 0.5 }}>
                  {k === "person" && e.photo
                    ? <img src={e.photo} alt={e.name} style={{ width: 70, height: 70, borderRadius: 6, objectFit: "cover", filter: `grayscale(${1 - lockP}) contrast(1.05)`, boxShadow: "0 0 0 1px var(--i-cyan)" }} />
                    : <EntityGlyph kind={k} size={70} color={m.color} />}
                </div>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="intel-mono" style={{ fontSize: 11, letterSpacing: "0.16em", color: lockP >= 1 ? "var(--i-cyan)" : "var(--i-amber)", marginBottom: 6 }}>
                  {lockP < 1 ? "ACQUIRING TARGET…" : "● TARGET ACQUIRED"}
                </div>
                <div style={{ fontSize: 30, fontWeight: 800, letterSpacing: "-0.02em", lineHeight: 1.05, color: "var(--i-text)" }}>
                  {typed(e.name, T.name, t, 34)}{!typeDone(e.name, T.name, t, 34) && t > T.name ? <span style={{ color: "var(--i-cyan)" }}>▍</span> : ""}
                </div>
                {e.codename && t >= T.code && (
                  <div className="intel-mono" style={{ fontSize: 14, color: "var(--i-amber)", marginTop: 6, letterSpacing: "0.1em" }}>
                    CODENAME “{typed(e.codename, T.code, t, 22)}”{!typeDone(e.codename, T.code, t, 22) ? "▍" : ""}
                  </div>
                )}
                <div className="intel-mono" style={{ fontSize: 11, color: "var(--i-muted)", marginTop: 5, textTransform: "uppercase", letterSpacing: "0.12em" }}>{m.label}</div>
              </div>
            </div>
          )}

          {/* LINES typing */}
          {showLines && (
            <div style={{ borderTop: "1px solid var(--i-line)", paddingTop: 18, display: "flex", flexDirection: "column", gap: 13 }}>
              {lines.map((ln, i) => {
                const st = T.lines + i * T.linePer;
                if (t < st) return null;
                const v = typed(ln[1], st, t, 64);
                const cur = !typeDone(ln[1], st, t, 64);
                return (
                  <div key={i} style={{ display: "flex", gap: 14 }}>
                    <div className="intel-mono" style={{ width: 150, flex: "none", fontSize: 10, color: "var(--i-cyan)", letterSpacing: "0.1em", paddingTop: 2 }}>{ln[0]}</div>
                    <div style={{ flex: 1, fontSize: 13.5, color: "var(--i-text)", lineHeight: 1.5 }}>{v}{cur ? <span style={{ color: "var(--i-cyan)" }}>▍</span> : ""}</div>
                  </div>
                );
              })}
            </div>
          )}

          {/* ASSOCIATES */}
          {showAssoc && (
            <div style={{ marginTop: 26, borderTop: "1px solid var(--i-line)", paddingTop: 18 }}>
              <div className="intel-mono" style={{ fontSize: 11, letterSpacing: "0.16em", color: "var(--i-string-soft)", marginBottom: 12 }}>
                KNOWN ASSOCIATES - {assoc.length} LINKED ENTITIES
              </div>
              <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
                {assoc.map((id, i) => {
                  const at = T.assoc + i * T.assocPer;
                  if (t < at) return null;
                  const ak = D.kindOf(id), ae = D.byId[id], am = typeMeta(ak);
                  return (
                    <span key={id} style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "6px 10px", border: "1px solid var(--i-line)", borderRadius: 7, background: "var(--i-panel)", transform: t < at + 140 ? "translateY(3px)" : "none", transition: "transform .14s" }}>
                      <EntityGlyph kind={ak} size={16} color={am.color} />
                      <span style={{ fontSize: 12, fontWeight: 600, color: "var(--i-text)" }}>{ae.name}</span>
                      <span className="intel-mono" style={{ fontSize: 8.5, color: "var(--i-faint)", textTransform: "uppercase" }}>{relVerb(subjectId, id)}</span>
                    </span>
                  );
                })}
              </div>
            </div>
          )}

          {/* PROCEED */}
          <div style={{ marginTop: 30, display: "flex", alignItems: "center", gap: 12, opacity: showProceedBtn ? 1 : 0.25 }}>
            <button onClick={onProceed} disabled={!showProceedBtn} className="focusable intel-mono"
              style={{ display: "inline-flex", alignItems: "center", gap: 8, padding: "11px 18px", borderRadius: 8, cursor: showProceedBtn ? "pointer" : "default", fontSize: 12.5, fontWeight: 700, letterSpacing: "0.12em", background: showProceedBtn ? "var(--i-string)" : "var(--i-panel)", color: showProceedBtn ? "#fff" : "var(--i-faint)", border: "1px solid " + (showProceedBtn ? "transparent" : "var(--i-line)") }}>
              PROCEED TO BOARD ▸
            </button>
            {showProceedBtn && <span className="intel-mono" style={{ fontSize: 10.5, color: "var(--i-faint)" }}>OR PRESS SKIP</span>}
          </div>
        </div>
      </div>
      <div className="intel-vignette" style={{ position: "absolute", inset: 0, zIndex: 6 }} />
    </div>
  );
}

/* ===================== BOARD (interactive) ===================== */
function boardRot(id) { let h = 0; for (const c of id) h = (h * 31 + c.charCodeAt(0)) & 0xffff; return (h % 64) / 10 - 3.2; }
function cardHalfH(kind, subject) {
  if (kind === "person") return subject ? 116 : 92;
  return subject ? 60 : 50;
}

/* dark tactical cards (real photo for people) */
function AssocCard({ id, onClick }) {
  const D = window.DATA;
  const k = D.kindOf(id), e = D.byId[id], m = typeMeta(k);
  const sub = k === "person" ? (e.codename ? "“" + e.codename + "”" : e.title)
    : k === "org" ? (e.codename ? "“" + e.codename + "”" : "")
    : k === "project" ? e.type
    : k === "event" ? new Date(e.date).toLocaleDateString("en-GB", { month: "short", year: "numeric" }) : "";
  return (
    <button onClick={onClick} className="intel-card-anim focusable"
      style={{ width: 200, textAlign: "left", cursor: "pointer", background: "var(--i-panel)", border: "1px solid var(--i-line)", borderRadius: 10, boxShadow: "0 10px 30px -12px rgba(0,0,0,0.8)", padding: 0, overflow: "hidden", color: "var(--i-text)" }}>
      <div style={{ height: 3, background: m.color, opacity: 0.85 }} />
      <div style={{ display: "flex", gap: 10, alignItems: "center", padding: "9px 10px" }}>
        {k === "person" && e.photo
          ? <img src={e.photo} alt={e.name} style={{ width: 36, height: 36, borderRadius: 8, objectFit: "cover", flex: "none", border: "1px solid var(--i-line)" }} />
          : <span style={{ position: "relative", flex: "none" }} className="intel-pulse"><EntityGlyph kind={k} size={30} color={m.color} /></span>}
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 12.5, fontWeight: 650, lineHeight: 1.15, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{e.name}</div>
          <div className="intel-mono" style={{ fontSize: 9.5, color: "var(--i-muted)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", textTransform: "uppercase" }}>{m.label}{sub ? " · " + sub : ""}</div>
        </div>
      </div>
    </button>
  );
}

function SubjectNode({ id }) {
  const D = window.DATA;
  const k = D.kindOf(id), e = D.byId[id], m = typeMeta(k);
  return (
    <div style={{ position: "relative", width: 240, background: "var(--i-panel-2)", border: "1.5px solid " + m.color, borderRadius: 14, boxShadow: "0 0 0 4px rgba(255,77,77,0.10), 0 18px 50px -14px rgba(0,0,0,0.9)", overflow: "hidden" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "7px 10px", background: "rgba(255,77,77,0.10)", borderBottom: "1px solid var(--i-line)" }}>
        <span className="intel-mono" style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: "0.14em", color: "var(--i-string-soft)" }}>● SUBJECT</span>
        <span className="intel-mono" style={{ fontSize: 9, color: "var(--i-faint)" }}>{(e.idcode || id).toUpperCase()}</span>
      </div>
      <div style={{ padding: "12px 12px", display: "flex", gap: 12, alignItems: "center" }}>
        {k === "person" && e.photo
          ? <img src={e.photo} alt={e.name} style={{ width: 50, height: 50, borderRadius: 9, objectFit: "cover", flex: "none", border: "1px solid var(--i-line)" }} />
          : <span style={{ position: "relative", flex: "none" }} className="intel-pulse"><EntityGlyph kind={k} size={42} color={m.color} /></span>}
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 15.5, fontWeight: 700, letterSpacing: "-0.01em", lineHeight: 1.15 }}>{e.name}</div>
          {e.codename && <div className="intel-mono" style={{ fontSize: 10.5, color: "var(--i-amber)", marginTop: 2, letterSpacing: "0.08em" }}>“{e.codename}”</div>}
          <div className="intel-mono" style={{ fontSize: 9.5, color: "var(--i-muted)", marginTop: 3, textTransform: "uppercase" }}>{m.label}</div>
        </div>
      </div>
    </div>
  );
}

function Dossier({ id, onOpenRecord }) {
  const D = window.DATA;
  const k = D.kindOf(id), e = D.byId[id], m = typeMeta(k);
  const Row = ({ label, children }) => (
    <div style={{ display: "flex", gap: 10, padding: "8px 0", borderBottom: "1px dashed var(--i-line)" }}>
      <div className="intel-mono" style={{ width: 92, flex: "none", fontSize: 9.5, color: "var(--i-faint)", textTransform: "uppercase", paddingTop: 1 }}>{label}</div>
      <div style={{ flex: 1, fontSize: 12.5, color: "var(--i-text)", minWidth: 0 }}>{children}</div>
    </div>
  );
  const classif = (k === "org" ? e.classification : k === "person" ? e.clearance : null) || "RESTRICTED";
  let body = null;
  if (k === "org") {
    const g = D.orgGap(e); const mh = D.milestoneHealth(id);
    const mv = { met: ["ALL MET", "var(--i-cyan)"], "on-track": ["ON TRACK", "var(--i-amber)"], "at-risk": ["AT RISK", "var(--i-amber)"], behind: ["BEHIND", "var(--i-string-soft)"] }[mh.verdict];
    body = (<React.Fragment>
      <Row label="What they do">{e.mission}</Row>
      <Row label="Status">{e.relationship} · {e.funded ? "Funded" : "Not funded"} · {e.reach} reach</Row>
      <Row label="Funding"><span className="intel-mono">{D.fmtMoney(e.funding.awarded)}</span> of <span className="intel-mono">{D.fmtMoney(e.funding.ask)}</span>{g > 0 && <span style={{ color: "var(--i-amber)" }}> · gap <span className="intel-mono">{D.fmtMoney(g)}</span></span>}</Row>
      <Row label="Milestones"><span style={{ color: mv[1], fontWeight: 650 }}>{mv[0]}</span><span style={{ color: "var(--i-muted)" }}> - {mh.met}/{mh.total} met{mh.behind ? `, ${mh.behind} behind` : ""}</span>
        <div style={{ marginTop: 6, display: "flex", flexDirection: "column", gap: 4 }}>{mh.list.map((ms, i) => { const c = { met: "var(--i-cyan)", "on-track": "var(--i-amber)", behind: "var(--i-string-soft)", pending: "var(--i-faint)" }[ms[1]]; return <div key={i} style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 11.5 }}><span style={{ width: 7, height: 7, borderRadius: 999, background: c, flex: "none" }} /><span style={{ flex: 1, color: "var(--i-muted)" }}>{ms[0]}</span><span className="intel-mono" style={{ fontSize: 9, color: c, textTransform: "uppercase" }}>{ms[1]}</span></div>; })}</div>
      </Row>
      <Row label="Risk"><span style={{ color: e.risk.level === "high" ? "var(--i-string-soft)" : e.risk.level === "medium" ? "var(--i-amber)" : "var(--i-cyan)", fontWeight: 650, textTransform: "uppercase" }}>{e.risk.level}</span><span style={{ color: "var(--i-muted)" }}> - {e.risk.note}</span></Row>
      <Row label="Handler">{e.account ? D.byId[e.account].name : "-"}</Row>
      <Row label="Contact">{e.contact ? D.byId[e.contact].name : "-"}</Row>
    </React.Fragment>);
  } else if (k === "person") {
    const org = e.org ? D.byId[e.org] : null;
    body = (<React.Fragment>
      <Row label="Role">{e.role || e.title}</Row>
      <Row label="Clearance"><span className="intel-mono" style={{ color: "var(--i-amber)" }}>{e.clearance || "EXTERNAL"}</span></Row>
      <Row label="Affiliation">{org ? org.name : (e.team ? "The Wellspring Fund" : "Independent")}</Row>
      <Row label="Comms"><span className="intel-mono" style={{ fontSize: 11 }}>{e.email}</span><br /><span className="intel-mono" style={{ fontSize: 11 }}>{e.phone}</span></Row>
      <Row label="Notes">{e.notes}</Row>
    </React.Fragment>);
  } else if (k === "project") {
    const org = D.byId[e.org];
    body = (<React.Fragment>
      <Row label="Brief">{e.notes}</Row><Row label="Type">{e.type} · {e.year}</Row><Row label="Status">{e.status}</Row><Row label="Parent">{org.name}</Row><Row label="Lead">{e.contact ? D.byId[e.contact].name : "-"}</Row>
    </React.Fragment>);
  } else if (k === "event") {
    body = (<React.Fragment>
      <Row label="Brief">{e.notes}</Row><Row label="Date"><span className="intel-mono">{new Date(e.date).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" })}</span></Row><Row label="Orgs">{(e.orgs || []).length} present</Row><Row label="Attendees">{(e.attendees || []).length} logged</Row>
    </React.Fragment>);
  } else {
    body = (<React.Fragment>
      <Row label="Brief">Centre of the ecosystem. Every organisation connects here by a funding relationship.</Row><Row label="Funded">{D.counts.funded} of {D.counts.orgs} orgs</Row><Row label="Open gap"><span className="intel-mono" style={{ color: "var(--i-amber)" }}>{D.fmtMoney(D.totalGap())}</span> across {D.needsFunding().length} orgs</Row>
    </React.Fragment>);
  }
  return (
    <div style={{ width: 358, flex: "none", display: "flex", flexDirection: "column", background: "var(--i-panel)", borderRight: "1px solid var(--i-line)", overflow: "auto" }}>
      <div style={{ padding: "16px 18px", borderBottom: "1px solid var(--i-line)" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
          <span className="intel-mono" style={{ fontSize: 10, color: "var(--i-faint)", letterSpacing: "0.14em" }}>DOSSIER // {(e.idcode || id).toUpperCase()}</span>
          <span className={"intel-stamp rot " + (classif === "SECRET" ? "" : classif === "CONFIDENTIAL" ? "amber" : "cyan")}>{classif}</span>
        </div>
        <div style={{ display: "flex", gap: 13, alignItems: "center" }}>
          <span style={{ flex: "none" }}><EntityGlyph kind={k} size={52} color={m.color} /></span>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 20, fontWeight: 750, letterSpacing: "-0.02em", lineHeight: 1.1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{e.name}</div>
            {e.codename && <div className="intel-mono" style={{ fontSize: 11.5, color: "var(--i-amber)", marginTop: 4, letterSpacing: "0.07em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>CODENAME “{e.codename}”</div>}
            <div className="intel-mono" style={{ fontSize: 10, color: "var(--i-muted)", marginTop: 4, textTransform: "uppercase", letterSpacing: "0.1em" }}>{m.label}</div>
          </div>
        </div>
      </div>
      <div style={{ padding: "6px 18px 18px" }}>{body}</div>
      <div style={{ marginTop: "auto", padding: 16, borderTop: "1px solid var(--i-line)" }}>
        <button onClick={() => onOpenRecord(id)} className="focusable" style={{ width: "100%", justifyContent: "center", display: "inline-flex", alignItems: "center", gap: 8, padding: "10px", borderRadius: 9, cursor: "pointer", font: "inherit", fontSize: 13, fontWeight: 600, background: "transparent", color: "var(--i-text)", border: "1px solid var(--i-line)" }}>
          <Icon name="external" size={15} /> Open full record
        </button>
      </div>
    </div>
  );
}

function IntelBoard({ subjectId, recenter, onReplay, onOpenRecord, onExit }) {
  const D = window.DATA;
  const wrapRef = React.useRef(null);
  const [size, setSize] = React.useState({ w: 1000, h: 700 });
  const [flash, setFlash] = React.useState(0);
  React.useEffect(() => {
    const el = wrapRef.current; if (!el) return;
    const update = () => { const r = el.getBoundingClientRect(); setSize({ w: r.width, h: r.height }); };
    update(); const ro = new ResizeObserver(update); ro.observe(el); return () => ro.disconnect();
  }, []);
  const assoc = React.useMemo(() => pickAssociates(subjectId), [subjectId]);
  const cx = size.w * 0.5, cy = size.h * 0.46;
  const baseR = Math.max(172, Math.min(size.w, size.h) * 0.33);
  const positions = assoc.map((id, i) => {
    const ang = -Math.PI / 2 + (i / assoc.length) * Math.PI * 2;
    const r = baseR * (i % 2 ? 1.16 : 0.9);
    return { id, x: cx + Math.cos(ang) * r * 1.22, y: cy + Math.sin(ang) * r * 0.82 };
  });
  const go = (id) => { recenter(id); setFlash((f) => f + 1); };
  const subject = D.byId[subjectId];
  return (
    <div className="intel-root" style={{ display: "flex", flexDirection: "column" }}>
      <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 56, display: "flex", alignItems: "center", gap: 14, padding: "0 18px", borderBottom: "1px solid var(--i-line)", background: "rgba(10,12,18,0.72)", zIndex: 10 }}>
        <button onClick={onExit} className="focusable" style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "8px 12px", borderRadius: 8, cursor: "pointer", font: "inherit", fontSize: 13, fontWeight: 600, background: "transparent", color: "var(--i-text)", border: "1px solid var(--i-line)" }}>
          <Icon name="chevronLeft" size={15} /> Exit Intel
        </button>
        <span className="intel-rec" />
        <span className="intel-mono" style={{ fontSize: 12, fontWeight: 700, letterSpacing: "0.16em" }}>INTEL BOARD</span>
        <span className="intel-mono" style={{ fontSize: 11, color: "var(--i-faint)" }}>// {subject.codename ? "“" + subject.codename + "”" : subject.name.toUpperCase()}</span>
        <div style={{ flex: 1 }} />
        <button onClick={onReplay} className="focusable" style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "8px 12px", borderRadius: 8, cursor: "pointer", font: "inherit", fontSize: 12.5, fontWeight: 600, background: "transparent", color: "var(--i-amber)", border: "1px solid var(--i-line)" }}>
          <Icon name="bolt" size={14} /> Replay briefing
        </button>
        <span className="intel-mono" style={{ fontSize: 10.5, color: "var(--i-faint)" }}>ASSOCIATES: <span style={{ color: "var(--i-amber)" }}>{assoc.length}</span></span>
      </div>
      <div style={{ position: "absolute", top: 56, left: 0, right: 0, bottom: 0, display: "flex" }}>
        <Dossier id={subjectId} onOpenRecord={onOpenRecord} />
        <div ref={wrapRef} className="intel-board" style={{ position: "relative", flex: 1, overflow: "hidden" }}>
          <div className="intel-scan" />
          <svg width="100%" height="100%" style={{ position: "absolute", inset: 0, pointerEvents: "none" }}>
            <defs><filter id="intel-glow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="2.4" result="b" /><feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge></filter></defs>
            {positions.map((p) => {
              const o = D.byId[p.id]; const funded = D.kindOf(p.id) === "org" ? o.funded : true;
              return <g key={p.id + "-" + flash}>
                <line x1={cx} y1={cy} x2={p.x} y2={p.y} stroke="var(--i-string)" strokeWidth="1.6" strokeLinecap="round" strokeDasharray={funded ? "none" : "6 5"} opacity={funded ? 0.62 : 0.42} filter="url(#intel-glow)" />
                <circle cx={p.x} cy={p.y} r="3.2" fill="var(--i-string-soft)" filter="url(#intel-glow)" />
                <circle cx={cx} cy={cy} r="3.2" fill="var(--i-string-soft)" filter="url(#intel-glow)" />
              </g>;
            })}
          </svg>
          {positions.map((p) => { const mx = (cx + p.x) / 2, my = (cy + p.y) / 2; return (
            <div key={"lbl" + p.id} className="intel-mono" style={{ position: "absolute", left: mx, top: my, transform: "translate(-50%,-50%)", fontSize: 8.5, letterSpacing: "0.1em", color: "var(--i-faint)", background: "var(--i-bg)", padding: "1px 5px", borderRadius: 4, border: "1px solid var(--i-line)", pointerEvents: "none", whiteSpace: "nowrap" }}>{relVerb(subjectId, p.id)}</div>
          ); })}
          {positions.map((p) => (
            <div key={p.id + "-card-" + flash} style={{ position: "absolute", left: p.x, top: p.y, transform: "translate(-50%,-50%)", zIndex: 4 }}>
              <AssocCard id={p.id} onClick={() => go(p.id)} />
            </div>
          ))}
          <div key={"subj-" + subjectId} style={{ position: "absolute", left: cx, top: cy, transform: "translate(-50%,-50%)", zIndex: 6 }}>
            <SubjectNode id={subjectId} />
            <div className={"intel-flash " + (flash ? "on" : "")} key={flash} />
          </div>
          <div style={{ position: "absolute", left: 16, bottom: 16, zIndex: 8, display: "flex", gap: 14, alignItems: "center", padding: "9px 13px", background: "rgba(17,21,31,0.82)", border: "1px solid var(--i-line)", borderRadius: 10 }}>
            {["funder", "org", "person", "project", "event"].map((kk) => { const mm = typeMeta(kk); return <span key={kk} style={{ display: "inline-flex", alignItems: "center", gap: 6 }}><EntityGlyph kind={kk} size={18} color={mm.color} /><span className="intel-mono" style={{ fontSize: 9.5, color: "var(--i-muted)", textTransform: "uppercase" }}>{mm.label}</span></span>; })}
          </div>
          <div className="intel-vignette" style={{ position: "absolute", inset: 0, zIndex: 7 }} />
        </div>
      </div>
    </div>
  );
}

/* ===================== ROOT ===================== */
function IntelView({ subjectId, setSubjectId, onExit, onOpenRecord }) {
  const [phase, setPhase] = React.useState("briefing");
  const assoc = React.useMemo(() => pickAssociates(subjectId), [subjectId]);
  // replay briefing whenever we explicitly enter a new subject via briefing
  if (phase === "briefing") {
    return (
      <BriefingCutscene key={subjectId} subjectId={subjectId} assoc={assoc}
        onProceed={() => setPhase("board")} onSkip={() => setPhase("board")} />
    );
  }
  return (
    <IntelBoard subjectId={subjectId}
      recenter={(id) => setSubjectId(id)}
      onReplay={() => setPhase("briefing")}
      onOpenRecord={onOpenRecord} onExit={onExit} />
  );
}

window.IntelView = IntelView;
