/* SkyOps — Temporal Intelligence (AI-first multi-cycle comparison)
   Left: the brief (AI prose + findings + Q&A). Right: evidence canvas (map · transitions · forecast · actions)
   + panel lifetime film-strip. Every claim links to evidence. */

const { useState: useStateTI, useEffect: useEffectTI, useMemo: useMemoTI, useRef: useRefTI } = React;

const TI_KIND = {
  contagion:      { label: "Contagion",      icon: "activity",    color: "#8b5cf6" },
  forecast:       { label: "Forecast",       icon: "clock",       color: "#4a90d9" },
  cause:          { label: "Cause",          icon: "link",        color: "#f97316" },
  audit:          { label: "Action audit",   icon: "check_circle",color: "#22c55e" },
  counterfactual: { label: "Counterfactual", icon: "history",     color: "#ef4444" },
  quality:        { label: "Data quality",   icon: "shield",      color: "#eab308" },
};

function tiEur(n) { return "€" + Math.round(n).toLocaleString("en"); }

function buildFindings(D) {
  const { counts, pidSeries, pidRate, qToCliff, forecast, PID_THRESHOLD, audits, missed, missedCost, lowConfWarm } = D;
  return [
    { id: "F1", kind: "contagion", conf: 0.91, impact: (pidSeries[3]) * 1200,
      title: "PID is spreading outward from string S-01-09",
      body: `Affected panels grew ${pidSeries.join(" → ")} across four cycles — a radial front advancing ≈${(window.TM.PID_RADIUS[3] - window.TM.PID_RADIUS[2]).toFixed(1)} panels/quarter. Pattern matches potential-induced degradation propagating along a shared negative-pole string, not independent failures.`,
      ev: { mode: "map", t: 3, overlay: "rings" } },
    { id: "F2", kind: "forecast", conf: 0.78, impact: PID_THRESHOLD * 1200,
      title: qToCliff > 0 ? `Block S-01 hits the isolation threshold in ~${qToCliff} quarters` : "Block S-01 stays below isolation threshold through 2027",
      body: `At the current rate (+${pidRate.toFixed(1)} panels/qtr, accelerating), PID reaches ${PID_THRESHOLD} panels — the point where the inverter MPPT window drops strings — by ${qToCliff > 0 ? ["Q3 26","Q4 26","Q1 27","Q2 27","Q3 27","Q4 27"][qToCliff - 1] : "beyond Q4 27"}. Night-time anti-PID box on INV-01 would halt the front.`,
      ev: { mode: "forecast" } },
    { id: "F3", kind: "cause", conf: 0.88, impact: counts[2].bypass * 3000,
      title: "Bypass-diode cluster in rows 2–4 traces to the 14 Nov hailstorm",
      body: `${counts[2].bypass} bypass activations appeared between Q4 25 and Q1 26, all inside the band the IPMA storm cell crossed. Zero occurred elsewhere. Spatial overlap 94%, temporal fit: first cycle after event.`,
      ev: { mode: "map", t: 2, focus: "hail" } },
    { id: "F4", kind: "audit", conf: 0.84, impact: audits[0].recovered,
      title: "WO-118 wash worked — except rows 9–10, where runoff re-soils panels",
      body: `${audits[0].note} The 19 Mar rain event pooled at the same rows: this is a drainage defect, not a cleaning failure. A re-wash will not hold.`,
      ev: { mode: "map", t: 3, focus: "drain" } },
    { id: "F5", kind: "counterfactual", conf: 0.81, impact: missedCost,
      title: `Leaving ${missed.length} hot cells untreated cost ${tiEur(missedCost)}/yr`,
      body: `WO-131 fixed 6 of the hot cells flagged in Q1 26 and all 6 are clean now. The ${missed.length} not in scope persisted at 86% probability. Treating them in the same mobilisation would have cost ≈€${(missed.length * 1850 / 1000).toFixed(1)}k one-off against ${tiEur(missedCost)}/yr protected — payback under 13 months.`,
      ev: { mode: "actions" } },
    { id: "F6", kind: "quality", conf: 0.93, impact: 0,
      title: "The Q4 25 'warm module' spike is a sensor artifact, not degradation",
      body: `Low-confidence warm detections jumped ${lowConfWarm[0]} → ${lowConfWarm[1]} right after the M300 → M350 swap, then fell back to ${lowConfWarm[2]}. No correlated power loss in SCADA. Excluded from trend lines.`,
      spark: lowConfWarm,
      ev: { mode: "map", t: 1 } },
  ];
}

const TI_ACT = {
  F1: { act: "Energise anti-PID box on INV-01", when: "≤ 30 days", cost: 4800, rank: 1, dec: "DEC-T1" },
  F2: { act: "Re-inspect block S-01 next quarter", when: "Q3 26", monitor: true },
  F3: { act: "File hail insurance claim · rows 2–4", when: "≤ 60 days", rank: 4, dec: "DEC-T4" },
  F4: { act: "Re-grade swale at rows 9–10 before re-washing", when: "before summer", cost: 6500, rank: 2, dec: "DEC-T3" },
  F5: { act: "Replace the remaining hot-cell modules in one visit", when: "next mobilisation", rank: 3, dec: "DEC-T2" },
  F6: { act: "No action — mark Q4 25 warm spike as sensor artifact", when: "" },
};
function fallbackHeadline(F, D) {
  return `PID is the dominant risk: ${D.pidSeries[3]} panels affected and accelerating, with hail and drainage explaining most of the rest.`;
}
function fallbackSummary(F, D) {
  return `Across four cycles, three mechanisms explain most of the change at this site. PID is spreading radially from string S-01-09 and, at the current rate, will force string isolation in about ${D.qToCliff || 6} quarters [F1][F2]. The bypass cluster in rows 2–4 is storm damage from 14 Nov, not ageing [F3]. O&M is working but under-scoped: the east wash held everywhere except a drainage hotspot, and ${D.missed.length} untreated hot cells now cost ${tiEur(D.missedCost)}/yr [F4][F5]. Recommend: anti-PID box on INV-01, drainage fix rows 9–10, bundle remaining hot cells into next WO.`;
}

/* ── Root ─────────────────────────────────────── */
function TemporalIntel({ siteId, go }) {
  const TM = window.TM;
  const D = TM.data;
  const site = (window.SITES || []).find(s => s.id === siteId) || (window.SITES || []).find(s => s.id === "pt-ev-03");
  const findings = useMemoTI(() => buildFindings(D).map(f => ({ ...f, ...(TI_ACT[f.id] || {}) })), []);
  const [range, setRange] = useStateTI([0, 3]);
  const [mode, setMode] = useStateTI("map");
  const [t, setT] = useStateTI(3);
  const [focus, setFocus] = useStateTI(null);
  const [overlay, setOverlay] = useStateTI({ rings: true, events: true });
  const [view, setView] = useStateTI("state");
  const [activeF, setActiveF] = useStateTI(null);
  const [panel, setPanel] = useStateTI(TM.PID_ORIGIN.r * TM.TM_COLS + TM.PID_ORIGIN.c + 2);

  const openEvidence = (f) => {
    setActiveF(f.id);
    setMode(f.ev.mode);
    if (f.ev.t != null) setT(f.ev.t);
    setFocus(f.ev.focus || null);
    if (f.ev.overlay) setOverlay(o => ({ ...o, [f.ev.overlay]: true }));
  };

  return (
    <div className="ti" data-screen-label="Temporal intelligence">
      <div className="ti-head">
        <div>
          <div className="kicker">Temporal intelligence · {site?.name || "Site"}</div>
          <h1>What changed, why, and what it will cost</h1>
          <div className="sub">{TM.TM_CYCLES.length} cycles · {D.N.toLocaleString()} panels tracked individually · {(D.N * TM.TM_CYCLES.length).toLocaleString()} panel-observations · {TM.TM_EVENTS.length} external events correlated</div>
        </div>
        <CycleRange range={range} setRange={setRange}/>
      </div>

      <div className="ti-body">
        <Brief findings={findings} D={D} activeF={activeF} onEvidence={openEvidence} range={range} panel={panel}/>
        <section className="ti-ev">
          <div className="ti-ev-tabs">
            {[["map","Panel map","grid"],["flow","Transitions","split"],["forecast","Forecast","clock"],["actions","Actions & cost","check_circle"]].map(([k,l,ic]) => (
              <button key={k} className={mode === k ? "on" : ""} onClick={() => { setMode(k); setActiveF(null); }}><Icon name={ic} size={12}/>{l}</button>
            ))}
            <span className="grow"/>
            {activeF && <span className="ti-ev-ctx"><span className="ti-cite">{activeF}</span> evidence</span>}
          </div>
          <div className="ti-ev-canvas">
            {mode === "map" && <EvidenceMap D={D} t={t} setT={setT} range={range} focus={focus} overlay={overlay} setOverlay={setOverlay} view={view} setView={setView} panel={panel} setPanel={setPanel}/>}
            {mode === "flow" && <EvidenceFlow D={D} range={range}/>}
            {mode === "forecast" && <EvidenceForecast D={D}/>}
            {mode === "actions" && <EvidenceActions D={D}/>}
          </div>
          <FilmStrip D={D} panel={panel} range={range}/>
        </section>
      </div>
    </div>
  );
}

function CycleRange({ range, setRange }) {
  const C = window.TM.TM_CYCLES;
  const click = (i) => {
    if (i < range[0]) setRange([i, range[1]]);
    else if (i > range[1]) setRange([range[0], i]);
    else if (i - range[0] < range[1] - i) setRange([Math.min(i, range[1] - 1), range[1]]);
    else setRange([range[0], Math.max(i, range[0] + 1)]);
  };
  return (
    <div className="ti-range">
      <span className="caps">Compare</span>
      <div className="ti-range-track">
        {C.map((c, i) => {
          const inR = i >= range[0] && i <= range[1];
          return (
            <React.Fragment key={c.id}>
              {i > 0 && <span className={`ti-range-link ${i > range[0] && i <= range[1] ? "on" : ""}`}/>}
              <button className={`ti-range-node ${inR ? "on" : ""} ${i === range[0] || i === range[1] ? "end" : ""}`} onClick={() => click(i)} title={c.id}>
                <span className="mono">{c.label}</span>
              </button>
            </React.Fragment>
          );
        })}
      </div>
      <span className="ti-range-n mono">{range[1] - range[0] + 1} cycles</span>
    </div>
  );
}

/* ── Brief (left) ─────────────────────────────── */
function Brief({ findings, D, activeF, onEvidence, range, panel }) {
  const key = "skyops_ti_headline_v3";
  const [summary, setSummary] = useStateTI(() => { try { return localStorage.getItem(key) || ""; } catch { return ""; } });
  const [loading, setLoading] = useStateTI(false);
  const [thread, setThread] = useStateTI([]);
  const [q, setQ] = useStateTI("");
  const [asking, setAsking] = useStateTI(false);
  const endRef = useRefTI();

  const ctx = () => JSON.stringify({
    site: "Évora Plana PV · 210.5 MW · Alentejo PT",
    cycles: window.TM.TM_CYCLES.map((c, i) => ({ ...c, counts: D.counts[i], lossEurYr: D.lossYr[i] })),
    events: window.TM.TM_EVENTS,
    findings: findings.map(f => ({ id: f.id, kind: f.kind, title: f.title, body: f.body, impactEurYr: Math.round(f.impact) })),
    selectedPanel: { id: window.TM.tmPanelId(panel % window.TM.TM_COLS, Math.floor(panel / window.TM.TM_COLS)), states: window.TM.TM_CYCLES.map((_, i) => D.grid[i][panel]) },
  });

  const generate = async () => {
    setLoading(true);
    try {
      const out = await window.claude.complete(
        "You are Argus, a terse PV asset-intelligence analyst. In ONE sentence of at most 22 words, state the single most important conclusion across these inspection cycles for an asset manager. No citations, no preamble. Data:\n" + ctx());
      const h = out.trim().replace(/\s*\[[^\]]+\]/g, "").split(/(?<=\.)\s/)[0].slice(0, 200);
      setSummary(h);
      try { localStorage.setItem(key, h); } catch {}
    } catch { setSummary(fallbackHeadline(findings, D)); }
    setLoading(false);
  };
  useEffectTI(() => { if (!summary) generate(); }, []);

  const ask = async (text) => {
    const question = (text || q).trim();
    if (!question || asking) return;
    setQ(""); setAsking(true);
    setThread(th => [...th, { role: "u", text: question }]);
    let ans;
    try {
      ans = await window.claude.complete(
        "You are Argus. Answer in ≤3 sentences using only this multi-cycle inspection data. Cite findings as [F1] or panels as [B-07-09]. Data:\n" + ctx() + "\n\nQuestion: " + question);
    } catch { ans = "I couldn't reach the model. The findings on the left cover the main changes — start with [F1] and [F5]."; }
    setThread(th => [...th, { role: "a", text: ans.trim() }]);
    setAsking(false);
  };
  useEffectTI(() => { endRef.current && endRef.current.scrollIntoView ? null : null; }, [thread]);

  const totalImpact = findings.reduce((a, f) => a + (f.impact || 0), 0);
  const nowAffected = Object.entries(D.counts[3]).filter(([k]) => k !== "clean").reduce((a, [, v]) => a + v, 0);
  const firstAffected = Object.entries(D.counts[0]).filter(([k]) => k !== "clean").reduce((a, [, v]) => a + v, 0);
  const f2 = findings.find(f => f.id === "F2");
  const todo = findings.filter(f => f.rank).sort((a, b) => a.rank - b.rank).slice(0, 4).map(f => f.id === "F1" && f2 ? { ...f, impact: f.impact + f2.impact, avoided: true } : f);

  const fMap = Object.fromEntries(findings.map(f => [f.id, f]));

  return (
    <section className="ti-brief">
      <div className="ti-sum">
        <div className="ti-sum-hd">
          <span className="ti-sum-ic"><Icon name="sparkles" size={13}/></span>
          <span>The brief</span>
          <span className="grow"/>
          <button className="btn btn-ghost btn-xs" onClick={generate} disabled={loading}><Icon name="refresh" size={11}/>{loading ? "Writing…" : "Rewrite"}</button>
        </div>
        {loading && !summary ? (
          <div className="ti-skel"><span/><span style={{ width: "70%" }}/></div>
        ) : (
          <p className="ti-headline">{summary || fallbackHeadline(findings, D)}</p>
        )}
        <div className="ti-kpis">
          <div><span>Exposure</span><b className="mono">{tiEur(totalImpact)}</b><em>per year</em></div>
          <div><span>Panels affected</span><b className="mono">{firstAffected} → {nowAffected}</b><em>Q3 25 → Q2 26</em></div>
          <div><span>PID threshold</span><b className="mono" style={{ color: "var(--sev-critical)" }}>{D.qToCliff > 0 ? "~" + D.qToCliff + " qtrs" : "> 6 qtrs"}</b><em>at current rate</em></div>
        </div>
        <div className="ti-do">
          <div className="ti-do-h">Do now</div>
          {todo.map((f, i) => (
            <div key={f.id} className="ti-do-r" role="button" tabIndex={0} onClick={() => onEvidence(f)}>
              <span className="ti-do-n mono">{i + 1}</span>
              <span className="ti-do-t"><b>{f.act}</b><em>{f.when}{f.cost ? " · cost " + tiEur(f.cost) : ""} · <span className="ti-cite-i">{f.id}</span></em></span>
              <span className="ti-do-v mono">{tiEur(f.impact)}<em>{f.dec === "DEC-T4" ? " recoverable" : "/yr" + (f.avoided ? " at stake" : "")}</em></span>
              <TiDecBtn id={f.dec}/>
            </div>
          ))}
        </div>
        <div className="ti-sum-foot">
          <span><b className="mono">{findings.length}</b> findings below</span>
          <span className="grow"/>
          <span className="mono muted">Argus · Q3 25 → Q2 26</span>
        </div>
      </div>

      <div className="ti-findings">
        {findings.map(f => <FindingCard key={f.id} f={f} active={activeF === f.id} onEvidence={() => onEvidence(f)}/>)}
      </div>

      {thread.length > 0 && (
        <div className="ti-thread">
          {thread.map((m, i) => (
            <div key={i} className={`ti-msg ti-msg-${m.role}`}>
              {m.role === "a" && <span className="ti-msg-ic"><Icon name="sparkles" size={10}/></span>}
              <div className="ti-msg-b"><Cited text={m.text} fMap={fMap} onCite={onEvidence}/></div>
            </div>
          ))}
          {asking && <div className="ti-msg ti-msg-a"><span className="ti-msg-ic"><Icon name="sparkles" size={10}/></span><div className="ti-dots"><span/><span/><span/></div></div>}
          <div ref={endRef}/>
        </div>
      )}

      <div className="ti-ask">
        <div className="ti-ask-sugg">
          {["Why did rows 9–10 re-soil after the wash?", "Which panels got worse every single cycle?", "What if we install the anti-PID box now?"].map(s => (
            <button key={s} onClick={() => ask(s)}>{s}</button>
          ))}
        </div>
        <div className="ti-ask-box">
          <Icon name="sparkles" size={13}/>
          <input value={q} onChange={e => setQ(e.target.value)} onKeyDown={e => e.key === "Enter" && ask()} placeholder="Ask about these cycles…"/>
          <button className="ti-ask-send" disabled={!q.trim() || asking} onClick={() => ask()}><Icon name="send" size={12}/></button>
        </div>
      </div>
    </section>
  );
}

function TiDecBtn({ id }) {
  const m = window.useCoreModel ? window.useCoreModel() : { dec: [] };
  const d = m.dec.find(x => x.id === id);
  const st = d ? d.state : (window.coreStore?.v.dec || {})[id];
  if (!d && !st) return <span className="ti-do-st muted">—</span>;
  if (st && st.st === "done") return <button className="ti-do-st ok" onClick={e => { e.stopPropagation(); window.__inNav && window.__inNav.go(/^(WC|IC)/.test(st.ref || "") ? "warranty" : "workorders"); }}><Icon name="check" size={10}/>{st.ref || "Done"}</button>;
  if (st && st.st === "dismissed") return <span className="ti-do-st muted">Dismissed</span>;
  return <button className="btn btn-primary btn-xs" onClick={e => { e.stopPropagation(); (window.coreExecuteDec || window.coreExecute)(d); }}>{d.action.type === "insurance" ? "Draft claim" : "Create WO"}</button>;
}

function Cited({ text, fMap, onCite }) {
  const parts = text.split(/(\[[^\]]+\])/g);
  return parts.map((p, i) => {
    const m = p.match(/^\[([^\]]+)\]$/);
    if (!m) return <React.Fragment key={i}>{p}</React.Fragment>;
    const f = fMap[m[1]];
    return <button key={i} className="ti-cite" onClick={() => f && onCite(f)} title={f ? f.title : m[1]}>{m[1]}</button>;
  });
}

function FindingCard({ f, active, onEvidence }) {
  const k = TI_KIND[f.kind];
  return (
    <article className={`ti-f ${active ? "on" : ""}`} style={{ "--k": k.color }} onClick={onEvidence}>
      <div className="ti-f-hd">
        <span className="ti-f-id mono">{f.id}</span>
        <span className="ti-f-kind"><Icon name={k.icon} size={10}/>{k.label}</span>
        <span className="grow"/>
        {f.impact > 0 && <span className="ti-f-imp mono">{tiEur(f.impact)}/yr</span>}
      </div>
      <h3 className="ti-f-t">{f.title}</h3>
      {active && <p className="ti-f-b">{f.body}</p>}
      {active && f.act && <div className="ti-f-act"><Icon name="arrow_right" size={10}/><b>{f.act}</b>{f.when && <em>{f.when}</em>}</div>}
      {active && f.spark && (
        <div className="ti-f-spark">
          {f.spark.map((v, i) => (
            <div key={i} className="ti-f-spark-col">
              <span style={{ height: `${(v / Math.max(...f.spark)) * 100}%`, background: i === 1 ? "var(--sev-medium)" : "var(--border-strong)" }}/>
              <em className="mono">{window.TM.TM_CYCLES[i].label}</em>
            </div>
          ))}
        </div>
      )}
      <div className="ti-f-ft">
        <span className="ti-f-conf"><span style={{ width: `${f.conf * 100}%` }}/></span>
        <span className="mono muted">{Math.round(f.conf * 100)}% conf.</span>
        <span className="grow"/>
        <span className="ti-f-ev">{active ? "Evidence on map" : "Details"} <Icon name={active ? "arrow_right" : "chevron_down"} size={10}/></span>
      </div>
    </article>
  );
}

/* ── Evidence: panel map w/ time scrubber ───────── */
function EvidenceMap({ D, t, setT, range, focus, overlay, setOverlay, view, setView, panel, setPanel }) {
  const TM = window.TM;
  const [playing, setPlaying] = useStateTI(false);
  useEffectTI(() => {
    if (!playing) return;
    const id = setInterval(() => setT(x => (x >= range[1] ? range[0] : x + 1)), 1100);
    return () => clearInterval(id);
  }, [playing, range]);
  useEffectTI(() => { if (t < range[0] || t > range[1]) setT(range[1]); }, [range]);

  const S = 22, G = 3, W = TM.TM_COLS * S, H = TM.TM_ROWS * S;
  const pers = useMemoTI(() => D.persistence(range[0], range[1]), [range]);
  const k = range[1] - range[0] + 1;
  const persColor = (n) => n === 0 ? "#16261f" : `color-mix(in oklab, #ef4444 ${Math.round((n / k) * 85)}%, #2a2f3a)`;
  const evBetween = TM.TM_EVENTS.filter(e => e.after === t - 1);

  return (
    <div className="ti-map">
      <div className="ti-map-bar">
        <div className="seg">
          <button className={view === "state" ? "on" : ""} onClick={() => setView("state")}>State at cycle</button>
          <button className={view === "pers" ? "on" : ""} onClick={() => setView("pers")}>Persistence</button>
        </div>
        <label className="ti-chk"><input type="checkbox" checked={overlay.rings} onChange={e => setOverlay(o => ({ ...o, rings: e.target.checked }))}/>Contagion fronts</label>
        <label className="ti-chk"><input type="checkbox" checked={overlay.events} onChange={e => setOverlay(o => ({ ...o, events: e.target.checked }))}/>Event zones</label>
        <span className="grow"/>
        <div className="ti-legend">
          {view === "state"
            ? TM.TM_STATES.map(s => <span key={s}><i style={{ background: TM.TM_STATE_META[s].color }}/>{TM.TM_STATE_META[s].label}</span>)
            : <span className="ti-legend-grad"><em>0</em><i/><em>{k} of {k} cycles defective</em></span>}
        </div>
      </div>

      <div className="ti-map-stage">
        <svg viewBox={`-6 -6 ${W + 12} ${H + 12}`} className="ti-map-svg">
          {D.grid[t].map((st, i) => {
            const c = i % TM.TM_COLS, r = Math.floor(i / TM.TM_COLS);
            const fill = view === "state" ? TM.TM_STATE_META[st].color : persColor(pers[i]);
            const wasRepaired = t === 3 && D.repaired.has(i);
            return (
              <g key={i} onClick={() => setPanel(i)} style={{ cursor: "pointer" }}>
                <rect x={c * S} y={r * S} width={S - G} height={S - G} rx="2" fill={fill} className="ti-cell"
                  stroke={i === panel ? "#fff" : "transparent"} strokeWidth="1.6"/>
                {wasRepaired && view === "state" && <circle cx={c * S + (S - G) / 2} cy={r * S + (S - G) / 2} r="3" fill="none" stroke="#22c55e" strokeWidth="1.4"/>}
              </g>
            );
          })}
          {/* string separators */}
          {[1,2,3].map(n => <line key={n} x1={n * 6 * S - G / 2} x2={n * 6 * S - G / 2} y1={-4} y2={H} stroke="var(--border-strong)" strokeDasharray="2 3"/>)}
          {overlay.rings && TM.PID_RADIUS.slice(range[0], t + 1).map((rad, j) => {
            const idx = range[0] + j;
            const cx = TM.PID_ORIGIN.c * S + (S - G) / 2, cy = TM.PID_ORIGIN.r * S + (S - G) / 2;
            const cur = idx === t;
                const a = (-150 + j * 24) * Math.PI / 180;
                const R = (TM.PID_RADIUS[3] + 1.1) * S;
                const rr = (rad + 0.5) * S;
                const lx = cx + R * Math.cos(a), ly = cy + (R / 1.2) * Math.sin(a);
                const px = cx + rr * Math.cos(a), py = cy + (rr / 1.2) * Math.sin(a);
                return (
              <g key={idx}>
                <ellipse cx={cx} cy={cy} rx={rr} ry={rr / 1.2} fill="none" stroke="#a78bfa" strokeWidth={cur ? 1.8 : 1} strokeDasharray={cur ? "" : "3 3"} opacity={cur ? 1 : 0.55} className={cur ? "ti-ring-cur" : ""}/>
                {cur && <line x1={px} y1={py} x2={lx} y2={ly} stroke="#a78bfa" strokeWidth="0.7" opacity="0.7"/>}
                {cur && <circle cx={px} cy={py} r="1.6" fill="#c4b5fd"/>}
                {cur && <text x={lx} y={ly - 2} textAnchor="middle" fontSize="8.5" fontWeight={cur ? 700 : 500} fill="#ddd6fe" fontFamily="var(--font-mono)" paintOrder="stroke" stroke="#0b1018" strokeWidth="3">{TM.TM_CYCLES[idx].label}</text>}
              </g>
            );
          })}
          {overlay.events && t >= 2 && (
            <g>
              <rect x={13 * S - 3} y={1 * S - 3} width={8 * S + 3} height={3 * S + 3} fill="none" stroke="#f97316" strokeDasharray="5 3" rx="4" className={focus === "hail" ? "ti-focus" : ""}/>
              <text x={13 * S} y={1 * S - 7} fontSize="9" fill="#fdba74" fontFamily="var(--font-mono)">WX-07 hail path · 14 Nov</text>
            </g>
          )}
          {overlay.events && t >= 3 && (
            <g>
              <rect x={18 * S - 3} y={8 * S - 3} width={6 * S + 3} height={2 * S + 3} fill="none" stroke="#eab308" strokeDasharray="5 3" rx="4" className={focus === "drain" ? "ti-focus" : ""}/>
              <text x={18 * S} y={10 * S + 12} fontSize="9" fill="#fde047" fontFamily="var(--font-mono)">WX-09 runoff pooling</text>
            </g>
          )}
        </svg>
      </div>

      <div className="ti-scrub">
        <button className="ti-play" onClick={() => setPlaying(p => !p)}><Icon name={playing ? "pause" : "play"} size={12}/></button>
        <div className="ti-scrub-track">
          {TM.TM_CYCLES.map((c, i) => {
            const inR = i >= range[0] && i <= range[1];
            const evs = TM.TM_EVENTS.filter(e => e.after === i - 1);
            return (
              <React.Fragment key={c.id}>
                {i > 0 && (
                  <div className="ti-scrub-gap">
                    {evs.map(e => <span key={e.id} className={`ti-scrub-ev ti-scrub-ev-${e.kind}`} title={`${e.date} · ${e.title}`}/>)}
                  </div>
                )}
                <button disabled={!inR} className={`ti-scrub-n ${i === t ? "on" : ""}`} onClick={() => setT(i)}>
                  <span className="mono">{c.label}</span>
                  <em className="mono">{D.lossYr[i] ? tiEur(D.lossYr[i]) : ""}</em>
                </button>
              </React.Fragment>
            );
          })}
        </div>
        <div className="ti-scrub-info">
          {evBetween.length > 0
            ? evBetween.map(e => <span key={e.id}><b className="mono">{e.id}</b> {e.title}</span>)
            : <span className="muted">No external events before this cycle</span>}
        </div>
      </div>
    </div>
  );
}

/* ── Evidence: multi-cycle Sankey ──────────────── */
function EvidenceFlow({ D, range }) {
  const TM = window.TM;
  const cols = []; for (let i = range[0]; i <= range[1]; i++) cols.push(i);
  const W = 640, H = 340, NW = 14, PAD = 6;
  const colX = (j) => 40 + j * ((W - 80 - NW) / Math.max(1, cols.length - 1));
  const scale = (H - PAD * (TM.TM_STATES.length - 1)) / D.N;
  const nodePos = (ci) => {
    let y = 0; const o = {};
    TM.TM_STATES.forEach(s => { const h = D.counts[ci][s] * scale; o[s] = { y, h }; y += h + PAD; });
    return o;
  };
  const pos = cols.map(nodePos);
  const links = [];
  for (let j = 0; j < cols.length - 1; j++) {
    const tr = D.transitions(cols[j], cols[j + 1]);
    const outOff = {}, inOff = {};
    TM.TM_STATES.forEach(s => { outOff[s] = 0; inOff[s] = 0; });
    TM.TM_STATES.forEach(a => TM.TM_STATES.forEach(b => {
      const n = tr[a][b]; if (!n) return;
      const h = n * scale;
      const y0 = pos[j][a].y + outOff[a], y1 = pos[j + 1][b].y + inOff[b];
      outOff[a] += h; inOff[b] += h;
      links.push({ j, a, b, n, h, y0, y1 });
    }));
  }
  const [hover, setHover] = useStateTI(null);
  return (
    <div className="ti-flow">
      <div className="ti-flow-hd">
        <span>How every panel moved between states, cycle to cycle</span>
        <span className="grow"/>
        {hover ? <span className="mono"><b>{hover.n}</b> panels · {TM.TM_STATE_META[hover.a].label} → {TM.TM_STATE_META[hover.b].label} · {TM.TM_CYCLES[cols[hover.j]].label}→{TM.TM_CYCLES[cols[hover.j + 1]].label}</span>
               : <span className="muted">Hover a band</span>}
      </div>
      <svg viewBox={`0 -18 ${W + 100} ${H + 30}`} className="ti-flow-svg">
        {links.map((l, i) => {
          const x0 = colX(l.j) + NW, x1 = colX(l.j + 1), mx = (x0 + x1) / 2;
          const same = l.a === l.b;
          const d = `M${x0},${l.y0} C${mx},${l.y0} ${mx},${l.y1} ${x1},${l.y1} L${x1},${l.y1 + l.h} C${mx},${l.y1 + l.h} ${mx},${l.y0 + l.h} ${x0},${l.y0 + l.h} Z`;
          const col = l.a === "clean" && l.b === "clean" ? "#1f3a2e" : TM.TM_STATE_META[l.b === "clean" ? l.a : l.b].color;
          const dim = hover && !(hover.j === l.j && hover.a === l.a && hover.b === l.b);
          return <path key={i} d={d} fill={col} opacity={dim ? 0.12 : same ? 0.28 : 0.62} onMouseEnter={() => setHover(l)} onMouseLeave={() => setHover(null)} className="ti-flow-link"/>;
        })}
        {cols.map((ci, j) => (
          <g key={ci}>
            <text x={colX(j) + NW / 2} y={-6} textAnchor="middle" fontSize="10" fill="var(--fg-secondary)" fontFamily="var(--font-mono)">{TM.TM_CYCLES[ci].label}</text>
            {TM.TM_STATES.map(s => pos[j][s].h > 0 && (
              <g key={s}>
                <rect x={colX(j)} y={pos[j][s].y} width={NW} height={Math.max(1, pos[j][s].h)} fill={TM.TM_STATE_META[s].color} rx="2"/>
                {j === cols.length - 1 && <text x={colX(j) + NW + 6} y={pos[j][s].y + Math.max(8, pos[j][s].h / 2 + 3)} fontSize="9" fill="var(--fg-secondary)">{TM.TM_STATE_META[s].label} · {D.counts[ci][s]}</text>}
              </g>
            ))}
          </g>
        ))}
      </svg>
      <div className="ti-flow-note">Bands into <b style={{ color: "#a3e635" }}>Clean</b> are repairs or recoveries; bands out of Clean are new failures. The widening violet band is the PID front [F1]; the orange band appearing at Q1 26 is the hail event [F3].</div>
    </div>
  );
}

/* ── Evidence: forecast ───────────────────────── */
function EvidenceForecast({ D }) {
  const labels = ["Q3 25","Q4 25","Q1 26","Q2 26","Q3 26","Q4 26","Q1 27","Q2 27","Q3 27","Q4 27"];
  const vals = [...D.pidSeries, ...D.forecast];
  const W = 620, H = 260, x0 = 44, y0 = 16;
  const max = Math.max(D.PID_THRESHOLD * 1.25, ...vals);
  const X = i => x0 + (i / (labels.length - 1)) * (W - x0 - 16);
  const Y = v => y0 + (1 - v / max) * (H - y0 - 30);
  const act = D.pidSeries.map((v, i) => `${X(i)},${Y(v)}`).join(" ");
  const fc = [3, 4, 5, 6, 7, 8, 9].map(i => `${X(i)},${Y(vals[i])}`).join(" ");
  const band = [3,4,5,6,7,8,9].map(i => `${X(i)},${Y(vals[i] * (1 + 0.06 * (i - 3)))}`).join(" ") + " " +
               [9,8,7,6,5,4,3].map(i => `${X(i)},${Y(vals[i] * (1 - 0.06 * (i - 3)))}`).join(" ");
  const cliffI = D.qToCliff > 0 ? 3 + D.qToCliff : null;
  return (
    <div className="ti-fc">
      <div className="ti-fc-kpis">
        <div><span className="caps">Velocity</span><b className="mono">+{D.pidRate.toFixed(1)}</b><em>panels / qtr</em></div>
        <div><span className="caps">Time to threshold</span><b className="mono" style={{ color: "var(--sev-critical)" }}>{D.qToCliff > 0 ? `${D.qToCliff} qtrs` : "> 6 qtrs"}</b><em>{cliffI ? labels[cliffI] : "—"}</em></div>
        <div><span className="caps">Loss at threshold</span><b className="mono">{tiEur(D.PID_THRESHOLD * 1200 + 4 * 6200)}</b><em>/yr incl. string trips</em></div>
        <div><span className="caps">Mitigation</span><b className="mono" style={{ color: "var(--sev-low)" }}>€4.8k</b><em>anti-PID box INV-01</em></div>
      </div>
      <svg viewBox={`0 0 ${W} ${H}`} className="ti-fc-svg">
        {[0, 0.25, 0.5, 0.75, 1].map(p => <g key={p}><line x1={x0} x2={W - 16} y1={Y(max * p)} y2={Y(max * p)} stroke="var(--border-base)" strokeDasharray="2 3"/><text x={x0 - 6} y={Y(max * p) + 3} fontSize="9" textAnchor="end" fill="var(--fg-muted)" fontFamily="var(--font-mono)">{Math.round(max * p)}</text></g>)}
        <line x1={x0} x2={W - 16} y1={Y(D.PID_THRESHOLD)} y2={Y(D.PID_THRESHOLD)} stroke="var(--sev-critical)" strokeWidth="1.2" strokeDasharray="6 3"/>
        <text x={x0 + 4} y={Y(D.PID_THRESHOLD) - 5} fontSize="9" textAnchor="start" fill="#fca5a5" fontFamily="var(--font-mono)">String-isolation threshold · {D.PID_THRESHOLD} panels</text>
        <rect x={X(3)} y={y0} width={X(9) - X(3)} height={H - y0 - 30} fill="color-mix(in oklab, var(--accent-primary) 5%, transparent)"/>
        <polygon points={band} fill="color-mix(in oklab, #8b5cf6 22%, transparent)"/>
        <polyline points={fc} fill="none" stroke="#a78bfa" strokeWidth="2" strokeDasharray="5 4"/>
        <polyline points={act} fill="none" stroke="#a78bfa" strokeWidth="2.5"/>
        {D.pidSeries.map((v, i) => <circle key={i} cx={X(i)} cy={Y(v)} r="3.5" fill="#a78bfa"/>)}
        {cliffI && <g><circle cx={X(cliffI)} cy={Y(vals[cliffI])} r="6" fill="none" stroke="var(--sev-critical)" strokeWidth="2"/><text x={X(cliffI)} y={Y(vals[cliffI]) - 10} fontSize="10" textAnchor="middle" fill="#fca5a5" fontWeight="600">cliff</text></g>}
        {labels.map((l, i) => <text key={l} x={X(i)} y={H - 10} fontSize="9" textAnchor="middle" fill={i <= 3 ? "var(--fg-secondary)" : "var(--fg-muted)"} fontFamily="var(--font-mono)">{l}</text>)}
        <text x={X(3) + 6} y={y0 + 12} fontSize="9" fill="var(--fg-muted)" fontFamily="var(--font-mono)">FORECAST →</text>
      </svg>
      <div className="ti-fc-note">PID affected panels in block S-01. Model: observed radial front + quadratic acceleration, ±6%/qtr uncertainty. Validated against 11 similar Iberian sites with the same module BOM.</div>
    </div>
  );
}

/* ── Evidence: actions & counterfactual ──────── */
function EvidenceActions({ D }) {
  const TM = window.TM;
  return (
    <div className="ti-act">
      <div className="ti-act-grid">
        {D.audits.map(a => {
          const roi = a.recovered / a.cost;
          return (
            <div key={a.id} className={`ti-act-card ti-act-${a.verdict}`}>
              <div className="ti-act-hd"><span className="mono">{a.id}</span><span className={`ti-verdict ${a.verdict}`}>{a.verdict === "effective" ? "Effective" : "Partially effective"}</span></div>
              <div className="ti-act-t">{a.title}</div>
              <div className="ti-act-ba">
                <div><span className="caps">Before</span><b className="mono">{a.before}</b></div>
                <Icon name="arrow_right" size={14}/>
                <div><span className="caps">After</span><b className="mono" style={{ color: "var(--sev-low)" }}>{a.after}</b></div>
                <span className="muted" style={{ fontSize: 11 }}>{a.unit}</span>
              </div>
              <div className="ti-act-roi">
                <span>Cost <b className="mono">{tiEur(a.cost)}</b></span>
                <span>Protected <b className="mono">{tiEur(a.recovered)}/yr</b></span>
                <span>Payback <b className="mono">{(12 / roi).toFixed(1)} mo</b></span>
              </div>
              <p>{a.note}</p>
            </div>
          );
        })}
      </div>
      <div className="ti-cf">
        <div className="ti-cf-l">
          <div className="caps">Counterfactual · missed-action ledger</div>
          <div className="ti-cf-big mono">{tiEur(D.missedCost)}<em>/yr</em></div>
          <p>Revenue still leaking from {D.missed.length} hot cells that were detected in Q1 26 but left out of WO-131. They were on the same strings, reachable in the same mobilisation.</p>
          <button className="btn btn-primary btn-xs"><Icon name="plus" size={11}/>Draft WO-140 with these {D.missed.length} modules</button>
        </div>
        <div className="ti-cf-r">
          {D.missed.slice(0, 8).map(i => {
            const c = i % TM.TM_COLS, r = Math.floor(i / TM.TM_COLS);
            return <div key={i} className="ti-cf-row"><span className="mono">{TM.tmPanelId(c, r)}</span><span className="muted mono">{TM.tmStringId(c, r)}</span><span className="grow"/><span className="ti-cf-strip">{TM.TM_CYCLES.map((_, t) => <i key={t} style={{ background: TM.TM_STATE_META[D.grid[t][i]].color }}/>)}</span></div>;
          })}
        </div>
      </div>
    </div>
  );
}

/* ── Panel lifetime film-strip ───────────────── */
function FilmStrip({ D, panel, range }) {
  const TM = window.TM;
  const c = panel % TM.TM_COLS, r = Math.floor(panel / TM.TM_COLS);
  const states = TM.TM_CYCLES.map((_, t) => D.grid[t][panel]);
  const story = useMemoTI(() => {
    const changes = [];
    states.forEach((s, t) => { if (t === 0 || s !== states[t - 1]) changes.push(`${TM.TM_STATE_META[s].label.toLowerCase()} (${TM.TM_CYCLES[t].label})`); });
    let why = "";
    if (states.includes("pid")) why = " Inside the advancing PID front from S-01-09.";
    else if (states.includes("bypass")) why = " First bypass activation directly follows the 14 Nov hailstorm.";
    else if (D.repaired.has(panel)) why = " Module replaced under WO-131; now clean.";
    else if (states[3] === "soiling" && c >= 18 && (r === 8 || r === 9)) why = " Re-soiled after the wash — sits in the runoff pooling zone.";
    else if (states.filter(s => s !== "clean").length === 0) why = " No defects in any cycle.";
    return changes.join(" → ") + "." + why;
  }, [panel]);
  return (
    <div className="ti-film">
      <div className="ti-film-hd">
        <span className="caps">Panel lifetime</span>
        <b className="mono">{TM.tmPanelId(c, r)}</b>
        <span className="muted mono">{TM.tmStringId(c, r)} · INV-{String(Math.floor(c / 6) + 1).padStart(2, "0")}</span>
        <span className="grow"/>
        <span className="muted" style={{ fontSize: 10 }}>Click any panel on the map</span>
      </div>
      <div className="ti-film-row">
        {TM.TM_CYCLES.map((cy, t) => {
          const st = states[t];
          const evs = TM.TM_EVENTS.filter(e => e.after === t - 1);
          return (
            <React.Fragment key={cy.id}>
              {t > 0 && (
                <div className="ti-film-gap">
                  {evs.map(e => <span key={e.id} className={`ti-film-ev ti-scrub-ev-${e.kind}`} title={e.title}><Icon name={e.kind === "weather" ? "cloud" : e.kind === "om" ? "settings" : "camera"} size={9}/></span>)}
                </div>
              )}
              <div className={`ti-film-f ${t < range[0] || t > range[1] ? "out" : ""}`}>
                <div className="ti-film-img" style={{ "--st": TM.TM_STATE_META[st].color }}>
                  <span className={`ti-film-hot ti-film-hot-${st}`}/>
                </div>
                <div className="ti-film-lbl"><span className="mono">{cy.label}</span><b style={{ color: st === "clean" ? "var(--fg-secondary)" : TM.TM_STATE_META[st].color }}>{TM.TM_STATE_META[st].label}</b></div>
              </div>
            </React.Fragment>
          );
        })}
      </div>
      <div className="ti-film-story"><Icon name="sparkles" size={11}/><span>{story}</span></div>
    </div>
  );
}

window.TemporalIntel = TemporalIntel;
