/* global React, Panel, ROLES, MODELS, COUNCIL_TYPES, PROMPT_MODES, useStream */
// Portfolio Council + Council Overview page — replaces the duplicate parent
// /council route.  See /Users/rajatgupta/.claude/plans/we-can-use-council-frolicking-treasure.md
//
// Two sections on one page:
//   A · Council Overview — read-only status surface across Swing/Trade/Investment.
//   B · Portfolio Council — multi-symbol fan-out + cross-position synthesis.
//
// Hard rules echoed from the backend:
//   - Loud-fail surfaces: any 503/422 from the backend is shown in a red
//     banner with the exact `detail` text — never swallowed.
//   - No opinionated thresholds in this UI.  Correlations and concentrations
//     render as raw numbers; the Portfolio Chair handles judgement calls.

const { useState: _pcUseState, useEffect: _pcUseEffect, useMemo: _pcUseMemo,
        useCallback: _pcUseCallback, useRef: _pcUseRef } = React;

// ---- Tiny helpers ---------------------------------------------------------
function _fmtUsd(n) {
  const x = Number(n);
  if (!Number.isFinite(x)) return "—";
  if (x === 0) return "$0.00";
  if (x < 0.01) return "$" + x.toFixed(4);
  if (x < 1)    return "$" + x.toFixed(3);
  return "$" + x.toFixed(2);
}
function _fmtMs(n) {
  const x = Number(n);
  if (!Number.isFinite(x) || x === 0) return "—";
  if (x < 1000) return Math.round(x) + "ms";
  return (x / 1000).toFixed(1) + "s";
}
function _fmtPct(n, digits = 1) {
  const x = Number(n);
  if (!Number.isFinite(x)) return "—";
  return x.toFixed(digits) + "%";
}
function _shortRunId(id) {
  return id ? String(id).slice(0, 8) : "—";
}
function _actionTone(a) {
  const u = String(a || "").toUpperCase();
  if (u === "BUY" || u === "ACCUMULATE") return "up";
  if (u === "SELL" || u === "REDUCE")    return "down";
  if (u === "HOLD")                       return "amber";
  return "dim";
}
function _actionPillClass(a) {
  const t = _actionTone(a);
  return t === "up" ? "pill pill-up"
       : t === "down" ? "pill pill-down"
       : t === "amber" ? "pill pill-amber"
       : "pill pill-dim";
}
function _typeColor(t) {
  if (t === "swing")      return "var(--violet)";
  if (t === "trade")      return "var(--up)";
  if (t === "investment") return "var(--cyan)";
  return "var(--fg-dim)";
}
function _corrCell(v) {
  if (v == null || !Number.isFinite(Number(v))) {
    return { color: "var(--fg-faint)", bg: "transparent", text: "—" };
  }
  const x = Number(v);
  // Neutral diverging palette with NO threshold semantics — purely visual.
  // Negative -> down hue (red), positive -> cyan, zero -> grey.  We don't
  // colour ranges differently to imply "good"/"bad" — that's the chair's job.
  const intensity = Math.min(1, Math.abs(x));
  const r = x < 0 ? 255 : 60;
  const g = 200 - Math.round(150 * intensity);
  const b = x > 0 ? 240 : 60;
  const a = 0.05 + 0.20 * intensity;
  return {
    color: x === 1 ? "var(--fg-bright)" : "var(--fg-bright)",
    bg: `rgba(${r},${g},${b},${a})`,
    text: x.toFixed(2),
  };
}

// ---- Section A: Council Overview ------------------------------------------
function CouncilOverview() {
  const [summary, setSummary]   = _pcUseState(null);
  const [active,  setActive]    = _pcUseState({ single: [], portfolio: [] });
  const [recent,  setRecent]    = _pcUseState([]);
  const [overviewErr, setOverviewErr] = _pcUseState(null);

  const refresh = _pcUseCallback(async () => {
    try {
      const [sR, aS, aP, rR] = await Promise.all([
        fetch("/api/council/summary").then(r => r.json()),
        fetch("/api/council/runs/active").then(r => r.json()),
        fetch("/api/council/portfolio/runs/active").then(r => r.json()),
        fetch("/api/council/runs?limit=20").then(r => r.json()),
      ]);
      setSummary(sR);
      setActive({ single: aS.runs || [], portfolio: aP.runs || [] });
      setRecent(rR.runs || []);
      setOverviewErr(null);
    } catch (e) {
      setOverviewErr(String(e && e.message ? e.message : e));
    }
  }, []);

  _pcUseEffect(() => {
    refresh();
    const t = setInterval(refresh, 3000);
    return () => clearInterval(t);
  }, [refresh]);

  const today = summary?.today || {};
  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 320px", gap: 8 }}>
      {/* Active runs */}
      <Panel title="Active Now" tag={(active.single.length + active.portfolio.length) || ""} bodyFlush>
        <div style={{ maxHeight: 180, overflow: "auto" }}>
          {active.single.length === 0 && active.portfolio.length === 0 ? (
            <div className="faint" style={{ padding: 10, fontSize: 10 }}>nothing running.</div>
          ) : (
            <table className="tbl tbl-compact" style={{ width: "100%" }}>
              <thead>
                <tr>
                  <th style={{ width: 70 }}>Type</th>
                  <th>Symbol(s)</th>
                  <th style={{ width: 70 }}>Status</th>
                  <th style={{ width: 100 }}>Started</th>
                </tr>
              </thead>
              <tbody>
                {active.portfolio.map(r => (
                  <tr key={"p:" + r.run_id}>
                    <td><span className="pill pill-amber" style={{ fontSize: 9 }}>PORT</span></td>
                    <td className="truncate">{(r.symbols || []).join(", ") || _shortRunId(r.run_id)}</td>
                    <td className="dim">{r.status}</td>
                    <td className="faint tnum" style={{ fontSize: 10 }}>{(r.created_at || "").slice(11, 19)}</td>
                  </tr>
                ))}
                {active.single.map(r => (
                  <tr key={"s:" + r.run_id}>
                    <td>
                      <span className="pill pill-dim" style={{
                        fontSize: 9,
                        color: _typeColor(r.council_type || "swing"),
                        borderColor: _typeColor(r.council_type || "swing"),
                      }}>
                        {String(r.council_type || "swing").toUpperCase().slice(0, 4)}
                      </span>
                    </td>
                    <td className="bright">{r.symbol}</td>
                    <td className="dim">{r.status}</td>
                    <td className="faint tnum" style={{ fontSize: 10 }}>{(r.created_at || "").slice(11, 19)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </Panel>

      {/* Recent verdicts */}
      <Panel title="Recent Verdicts" tag={recent.length || ""} bodyFlush>
        <div style={{ maxHeight: 180, overflow: "auto" }}>
          {recent.length === 0 ? (
            <div className="faint" style={{ padding: 10, fontSize: 10 }}>no runs yet.</div>
          ) : (
            <table className="tbl tbl-compact" style={{ width: "100%" }}>
              <thead>
                <tr>
                  <th style={{ width: 60 }}>Type</th>
                  <th>Symbol</th>
                  <th style={{ width: 70 }}>Action</th>
                  <th style={{ width: 50 }}>Conv</th>
                  <th className="num" style={{ width: 70 }}>Cost</th>
                  <th className="num" style={{ width: 60 }}>Dur</th>
                  <th style={{ width: 80 }}>Time</th>
                </tr>
              </thead>
              <tbody>
                {recent.map(r => {
                  const cs = r.chair_summary || {};
                  return (
                    <tr key={r.run_id}>
                      <td>
                        <span className="pill pill-dim" style={{
                          fontSize: 9,
                          color: _typeColor(r.council_type || "swing"),
                          borderColor: _typeColor(r.council_type || "swing"),
                        }}>
                          {String(r.council_type || "—").toUpperCase().slice(0, 4)}
                        </span>
                      </td>
                      <td className="bright">{r.symbol}</td>
                      <td>
                        {cs.action ? (
                          <span className={_actionPillClass(cs.action)} style={{ fontSize: 9 }}>{cs.action}</span>
                        ) : (
                          <span className="faint" style={{ fontSize: 10 }}>{r.status}</span>
                        )}
                      </td>
                      <td className="tnum dim">{cs.conviction != null ? cs.conviction : "—"}</td>
                      <td className="num tnum dim">{_fmtUsd(r.cost_usd)}</td>
                      <td className="num tnum dim">{_fmtMs(r.duration_ms)}</td>
                      <td className="faint tnum" style={{ fontSize: 10 }}>{(r.created_at || "").slice(11, 19)}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>
      </Panel>

      {/* Today's totals */}
      <Panel title="Today" bodyFlush>
        <div style={{ padding: 10, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6, fontSize: 11 }}>
          <div>
            <div className="h-xxs">Runs</div>
            <div className="bright tnum" style={{ fontSize: 16 }}>{today.runs ?? "—"}</div>
          </div>
          <div>
            <div className="h-xxs">Cost (USD)</div>
            <div className="bright tnum" style={{ fontSize: 16 }}>{_fmtUsd(today.cost_usd)}</div>
          </div>
          <div>
            <div className="h-xxs">Avg Duration</div>
            <div className="bright tnum" style={{ fontSize: 14 }}>{_fmtMs(today.avg_duration_ms)}</div>
          </div>
          <div>
            <div className="h-xxs">Cache Hit</div>
            <div className="bright tnum" style={{ fontSize: 14 }}>
              {today.cache_hit_pct == null ? "—" : _fmtPct(today.cache_hit_pct, 1)}
            </div>
          </div>
        </div>
        <div className="faint" style={{ padding: "0 10px 10px", fontSize: 9, lineHeight: 1.5 }}>
          Outcome tracking (win-rate by horizon) not yet implemented. Field will surface here once the post-verdict price audit job is wired up.
          {overviewErr ? <div className="down" style={{ marginTop: 4 }}>error: {overviewErr}</div> : null}
        </div>
      </Panel>
    </div>
  );
}

// ---- Section B form: pick source + roles + run ---------------------------
function PortfolioCouncilForm({ onLaunch, busy }) {
  const [source, setSource]           = _pcUseState("watchlist");
  const [holdings, setHoldings]       = _pcUseState(null);
  const [holdingsErr, setHoldingsErr] = _pcUseState(null);
  const [watchlist, setWatchlist]     = _pcUseState([]);
  const [picked, setPicked]           = _pcUseState(() => new Set());
  const [councilType, setCouncilType] = _pcUseState("swing");
  const [mode, setMode]               = _pcUseState("balanced");
  const [lookback, setLookback]       = _pcUseState(60);
  const [enabledRoles, setEnabledRoles] = _pcUseState(() => {
    const cfg = (typeof COUNCIL_TYPES !== "undefined" && COUNCIL_TYPES.swing) || null;
    return new Set(cfg ? cfg.default_roles : (typeof ROLES !== "undefined" ? ROLES.map(r => r.id) : []));
  });
  const [roleModels, setRoleModels] = _pcUseState(() => {
    const m = {};
    if (typeof ROLES !== "undefined") {
      ROLES.forEach(r => { m[r.id] = r.default_model; });
    }
    return m;
  });
  const [submitErr, setSubmitErr] = _pcUseState(null);

  // Load holdings when source = holdings.
  _pcUseEffect(() => {
    if (source !== "holdings") return;
    let cancelled = false;
    setHoldings(null); setHoldingsErr(null);
    fetch("/api/account/holdings_enriched")
      .then(async r => {
        if (!r.ok) throw new Error(await r.text());
        return r.json();
      })
      .then(j => { if (!cancelled) setHoldings(j); })
      .catch(e => { if (!cancelled) setHoldingsErr(String(e && e.message || e)); });
    return () => { cancelled = true; };
  }, [source]);

  // Load watchlist for symbol picker.
  _pcUseEffect(() => {
    fetch("/api/watchlist")
      .then(r => r.ok ? r.json() : null)
      .then(j => {
        if (!j) return;
        const list = j.items || j.watchlist || j.symbols || [];
        setWatchlist(list);
      })
      .catch(() => {});
  }, []);

  const togglePick = (sym) => {
    setPicked(prev => {
      const next = new Set(prev);
      if (next.has(sym)) next.delete(sym); else next.add(sym);
      return next;
    });
  };

  const togglePickRole = (rid) => {
    setEnabledRoles(prev => {
      const next = new Set(prev);
      if (next.has(rid)) next.delete(rid); else next.add(rid);
      return next;
    });
  };

  const submit = async () => {
    setSubmitErr(null);
    const body = {
      source,
      symbols: source === "watchlist" ? Array.from(picked) : undefined,
      council_type: councilType,
      mode,
      roles: Array.from(enabledRoles),
      role_models: roleModels,
      lookback_days: Number(lookback) || 60,
    };
    if (source === "watchlist" && (!body.symbols || body.symbols.length < 2)) {
      setSubmitErr("pick at least 2 symbols.");
      return;
    }
    if (body.roles.length === 0) {
      setSubmitErr("enable at least one role.");
      return;
    }
    try {
      const r = await fetch("/api/council/portfolio/runs", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      const j = await r.json();
      if (!r.ok || !j.ok) {
        const detail = j.detail || j.error || JSON.stringify(j);
        throw new Error("[" + r.status + "] " + detail);
      }
      onLaunch(j);
    } catch (e) {
      setSubmitErr(String(e && e.message || e));
    }
  };

  return (
    <Panel title="Portfolio Council" tag={source.toUpperCase()} bodyFlush>
      <div style={{ padding: 10, display: "grid", gridTemplateColumns: "260px 1fr", gap: 12 }}>
        {/* Left: source + horizon + lookback */}
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          <div>
            <div className="h-xxs">Source</div>
            <div style={{ display: "flex", gap: 4, marginTop: 3 }}>
              {["holdings", "watchlist"].map(s => (
                <button key={s} className={`chip ${source === s ? "active" : ""}`}
                        onClick={() => setSource(s)} style={{ flex: 1 }}>{s}</button>
              ))}
            </div>
          </div>
          <div>
            <div className="h-xxs">Council Type</div>
            <div style={{ display: "flex", gap: 4, marginTop: 3 }}>
              {["swing", "trade", "investment"].map(t => (
                <button key={t} className={`chip ${councilType === t ? "active" : ""}`}
                        onClick={() => setCouncilType(t)} style={{ flex: 1 }}>{t}</button>
              ))}
            </div>
          </div>
          <div>
            <div className="h-xxs">Mode</div>
            <select className="select" value={mode} onChange={e => setMode(e.target.value)}>
              {(typeof PROMPT_MODES !== "undefined" ? PROMPT_MODES : [{id:"balanced",label:"Balanced"}]).map(m =>
                <option key={m.id} value={m.id}>{m.label}</option>)}
            </select>
          </div>
          <div>
            <div className="h-xxs">Lookback (daily bars)</div>
            <input className="input tnum" type="number" min={10} max={750}
                   value={lookback} onChange={e => setLookback(e.target.value)}/>
            <div className="faint" style={{ fontSize: 9, marginTop: 2 }}>
              correlation window — symbols with fewer bars surface in `data_gaps`.
            </div>
          </div>
          <button className="btn btn-primary" disabled={busy}
                  onClick={submit} style={{ marginTop: 4 }}>
            {busy ? "◉ RUNNING…" : "▸ CONVENE PORTFOLIO COUNCIL"}
          </button>
          {submitErr ? (
            <div style={{ color: "var(--down)", fontSize: 10, padding: 6, border: "1px solid var(--down)" }}>
              {submitErr}
            </div>
          ) : null}
        </div>

        {/* Right: symbols + roles */}
        <div style={{ display: "flex", flexDirection: "column", gap: 8, minHeight: 0 }}>
          {source === "holdings" ? (
            <div>
              <div className="h-xxs">Holdings</div>
              {holdingsErr ? (
                <div style={{ color: "var(--down)", fontSize: 10, padding: 8, border: "1px solid var(--down)", marginTop: 4 }}>
                  holdings fetch failed: {holdingsErr}
                </div>
              ) : !holdings ? (
                <div className="faint" style={{ fontSize: 10, marginTop: 4 }}>loading…</div>
              ) : (
                <div style={{ marginTop: 4 }}>
                  {holdings.unmapped && holdings.unmapped.length > 0 ? (
                    <div style={{ color: "var(--amber)", fontSize: 10, padding: 6, border: "1px solid var(--amber)", marginBottom: 6 }}>
                      unmapped sectors ({holdings.unmapped.length}): {holdings.unmapped.join(", ")} — add them
                      to your watchlist before running, otherwise the request will be rejected (422).
                    </div>
                  ) : null}
                  <table className="tbl tbl-compact">
                    <thead>
                      <tr><th>Symbol</th><th>Sector</th><th className="num">Qty</th><th className="num">LTP</th></tr>
                    </thead>
                    <tbody>
                      {(holdings.holdings || []).map((h, i) => (
                        <tr key={i}>
                          <td className="bright">{h.symbol}</td>
                          <td className={h.sector ? "" : "down"}>{h.sector || "UNMAPPED"}</td>
                          <td className="num tnum">{h.totalQty || h.net_qty || h.quantity || "—"}</td>
                          <td className="num tnum">{h.lastTradedPrice || h.ltp || "—"}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
            </div>
          ) : (
            <div>
              <div className="h-xxs">Pick symbols ({picked.size})</div>
              <div style={{ marginTop: 4, maxHeight: 160, overflow: "auto",
                            display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(110px, 1fr))",
                            gap: 4, padding: 4, border: "1px solid var(--border)" }}>
                {(watchlist || []).map(w => {
                  const sym = (w.symbol || w.sym || "").toUpperCase();
                  if (!sym) return null;
                  const on = picked.has(sym);
                  return (
                    <button key={sym} onClick={() => togglePick(sym)}
                            className={`chip ${on ? "active" : ""}`}
                            style={{ textAlign: "left" }}>
                      {sym}
                      <span className="faint" style={{ fontSize: 8, marginLeft: 4 }}>
                        {w.sector || ""}
                      </span>
                    </button>
                  );
                })}
                {watchlist.length === 0 ? <span className="faint" style={{ fontSize: 10 }}>no watchlist symbols.</span> : null}
              </div>
            </div>
          )}

          <div>
            <div className="h-xxs">Roles ({enabledRoles.size}/{(typeof ROLES !== "undefined" ? ROLES.length : 0)})</div>
            <div style={{ marginTop: 4, display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))",
                          gap: 4 }}>
              {(typeof ROLES !== "undefined" ? ROLES : []).map(r => (
                <label key={r.id} style={{ display: "flex", alignItems: "center", gap: 6,
                                            padding: "3px 5px", border: "1px solid var(--border)",
                                            background: enabledRoles.has(r.id) ? "var(--bg-0)" : "transparent",
                                            cursor: "pointer", fontSize: 10 }}>
                  <input type="checkbox" className="checkbox"
                         checked={enabledRoles.has(r.id)}
                         onChange={() => togglePickRole(r.id)}/>
                  <span style={{ color: r.color }}>{r.icon}</span>
                  <span className={enabledRoles.has(r.id) ? "bright" : "dim"} style={{ fontSize: 10 }}>{r.name}</span>
                </label>
              ))}
            </div>
          </div>
        </div>
      </div>
    </Panel>
  );
}

// ---- Section B view: per-symbol cards + correlation + sectors + chair ----
function PortfolioRunView({ portfolioRunId, onClear }) {
  const [snapshot, setSnapshot] = _pcUseState(null);
  const [perSymbol, setPerSymbol] = _pcUseState({});       // child_run_id -> {symbol, status, verdict}
  const [stages, setStages] = _pcUseState([]);
  const [streamErr, setStreamErr] = _pcUseState(null);
  const symByChild = _pcUseRef({});                         // child_run_id -> symbol

  // Initial run snapshot — gives us correlation/sectors/chair when reattaching.
  _pcUseEffect(() => {
    if (!portfolioRunId) return;
    let cancelled = false;
    fetch(`/api/council/portfolio/runs/${portfolioRunId}`)
      .then(r => r.ok ? r.json() : Promise.reject("HTTP " + r.status))
      .then(j => {
        if (cancelled || !j.ok) return;
        setSnapshot(j.run);
        const map = {};
        const childMap = {};
        const childIds = j.run.child_run_ids || [];
        const symbols  = j.run.symbols || [];
        // child_ids order matches the symbols order from launch.
        for (let i = 0; i < childIds.length; i++) {
          childMap[childIds[i]] = symbols[i];
          map[childIds[i]] = {
            run_id: childIds[i],
            symbol: symbols[i],
            status: "queued",
            verdict: null,
            error: null,
          };
        }
        // Overlay any persisted child statuses.
        (j.run.children || []).forEach(c => {
          if (map[c.run_id]) {
            map[c.run_id].status = c.status;
            map[c.run_id].error = c.error;
          }
        });
        setPerSymbol(map);
        symByChild.current = childMap;
      })
      .catch(e => setStreamErr(String(e && e.message || e)));
    return () => { cancelled = true; };
  }, [portfolioRunId]);

  // SSE — append events; route child_* events into per-symbol buckets.
  const streamUrl = portfolioRunId ? `/api/council/portfolio/runs/${portfolioRunId}/stream` : null;
  const { lastEvent, status: streamStatus, error: streamHookErr } =
    useStream(streamUrl, { bufferSize: 500, enabled: !!portfolioRunId });

  _pcUseEffect(() => {
    if (!lastEvent) return;
    const t = lastEvent.type || "";
    if (t === "portfolio_correlation_done") {
      setSnapshot(prev => ({ ...(prev || {}), correlation: lastEvent.correlation }));
      setStages(prev => prev.concat({ t: Date.now(), kind: "corr", failed: lastEvent.failed_symbols || [] }));
      return;
    }
    if (t === "portfolio_sector_done") {
      setSnapshot(prev => ({ ...(prev || {}), sectors: lastEvent.sectors }));
      return;
    }
    if (t === "portfolio_chair_done") {
      setSnapshot(prev => ({ ...(prev || {}), chair: lastEvent.chair }));
      return;
    }
    if (t === "complete") {
      setSnapshot(prev => ({ ...(prev || {}), ...(lastEvent.result || {}), status: "complete" }));
      return;
    }
    if (t === "error") {
      setStreamErr(lastEvent.error || "stream error");
      return;
    }
    if (t.startsWith("child_")) {
      const cid = lastEvent.child_run_id;
      if (!cid) return;
      const sym = symByChild.current[cid];
      const inner = t.slice("child_".length);
      setPerSymbol(prev => {
        const cur = prev[cid] || { run_id: cid, symbol: sym, status: "running", verdict: null };
        let nextStatus = cur.status;
        let verdict = cur.verdict;
        let error = cur.error;
        if (inner === "chain_done") {
          // intentionally ignored — chair_done carries the final verdict
        }
        if (inner === "chair_done") {
          verdict = ((lastEvent.chair || {}).verdict) || verdict;
        }
        if (inner === "complete") {
          nextStatus = "complete";
          const v = ((lastEvent.result || {}).chair || {}).verdict;
          if (v) verdict = v;
        }
        if (inner === "error") {
          nextStatus = "error";
          error = lastEvent.error;
        }
        return { ...prev, [cid]: { ...cur, status: nextStatus, verdict, error } };
      });
      return;
    }
    if (t === "portfolio_stage") {
      setStages(prev => prev.concat({ t: Date.now(), kind: "stage", payload: lastEvent }));
    }
  }, [lastEvent]);

  if (!portfolioRunId) return null;

  const correlation = (snapshot && snapshot.correlation) || null;
  const sectors     = (snapshot && snapshot.sectors)     || null;
  const chair       = (snapshot && snapshot.chair)       || null;
  const dataGaps    = (snapshot && snapshot.data_gaps)   || [];
  const symbols     = correlation && correlation.stats
                       ? correlation.stats.map(s => s.symbol).filter(s => s !== correlation.benchmark)
                       : Object.values(perSymbol).map(p => p.symbol);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
      {(streamErr || streamHookErr) ? (
        <div style={{ color: "var(--down)", fontSize: 11, padding: 8, border: "1px solid var(--down)" }}>
          stream error: {streamErr || streamHookErr}
        </div>
      ) : null}

      {/* Per-symbol cards */}
      <Panel title={`Per-Symbol Verdicts · run ${_shortRunId(portfolioRunId)}`}
             tag={(snapshot && snapshot.status) || streamStatus} bodyFlush>
        <div style={{ padding: 8, display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 6 }}>
          {Object.values(perSymbol).map(p => {
            const v = p.verdict || {};
            return (
              <div key={p.run_id} style={{
                border: "1px solid var(--border)",
                background: "var(--bg-1)",
                padding: 8,
                display: "flex", flexDirection: "column", gap: 4,
              }}>
                <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
                  <span className="bright" style={{ fontSize: 12, fontWeight: 600 }}>{p.symbol}</span>
                  <span className="faint" style={{ fontSize: 9 }}>{_shortRunId(p.run_id)}</span>
                </div>
                <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  {v.action ? (
                    <span className={_actionPillClass(v.action)} style={{ fontSize: 10 }}>{v.action}</span>
                  ) : (
                    <span className="pill pill-dim" style={{ fontSize: 10 }}>{p.status || "—"}</span>
                  )}
                  {v.conviction != null ? (
                    <span className="dim tnum" style={{ fontSize: 11 }}>conv {v.conviction}</span>
                  ) : null}
                  {v.horizon ? <span className="faint" style={{ fontSize: 10 }}>{v.horizon}</span> : null}
                </div>
                {v.price_target != null || v.stop_loss != null ? (
                  <div className="faint tnum" style={{ fontSize: 10 }}>
                    {v.price_target != null ? <>tgt ₹{Number(v.price_target).toFixed(2)} </> : null}
                    {v.stop_loss != null ? <>· stop ₹{Number(v.stop_loss).toFixed(2)}</> : null}
                  </div>
                ) : null}
                {p.error ? (
                  <div className="down" style={{ fontSize: 10, marginTop: 2 }}>error: {p.error}</div>
                ) : null}
                <a href={`/council/${(snapshot && snapshot.council_type) || "swing"}?run=${p.run_id}`}
                   style={{ fontSize: 9, color: "var(--cyan)", marginTop: 2 }}>
                  open full council →
                </a>
              </div>
            );
          })}
        </div>
      </Panel>

      {/* Correlation matrix */}
      <Panel title="Correlation Matrix" tag={correlation ? `lookback ${correlation.lookback_bars}` : "—"} bodyFlush>
        <div style={{ padding: 8, overflow: "auto" }}>
          {!correlation ? (
            <div className="faint" style={{ fontSize: 11 }}>
              {dataGaps.length > 0 ? `correlation skipped — ${dataGaps.filter(g => g.startsWith("history")).length} symbol(s) missing history.` : "computing…"}
            </div>
          ) : (
            <table style={{ borderCollapse: "collapse", fontSize: 10 }}>
              <thead>
                <tr>
                  <th style={{ position: "sticky", left: 0, background: "var(--bg-2)", padding: "3px 6px", border: "1px solid var(--border)" }}></th>
                  {symbols.map(s => (
                    <th key={s} style={{ padding: "3px 6px", border: "1px solid var(--border)", color: "var(--fg-bright)" }}>{s}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {symbols.map(row => (
                  <tr key={row}>
                    <td className="bright" style={{ position: "sticky", left: 0, background: "var(--bg-2)",
                                                     padding: "3px 6px", border: "1px solid var(--border)" }}>{row}</td>
                    {symbols.map(col => {
                      const v = correlation.correlation && correlation.correlation[row]
                                 ? correlation.correlation[row][col]
                                 : null;
                      const c = _corrCell(v);
                      return (
                        <td key={col} className="tnum" style={{
                          padding: "3px 6px", border: "1px solid var(--border)",
                          background: c.bg, color: c.color, textAlign: "center",
                        }}>{c.text}</td>
                      );
                    })}
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {correlation && correlation.stats ? (
            <div style={{ marginTop: 10 }}>
              <div className="h-xxs">Per-symbol stats</div>
              <table className="tbl tbl-compact" style={{ marginTop: 3 }}>
                <thead><tr><th>Symbol</th><th className="num">Total Ret %</th><th className="num">Daily Vol %</th><th className="num">Beta</th><th className="num">Rel Strength %</th></tr></thead>
                <tbody>
                  {correlation.stats.map(s => (
                    <tr key={s.symbol}>
                      <td className="bright">{s.symbol}</td>
                      <td className={"num tnum " + (s.total_return_pct >= 0 ? "up" : "down")}>{s.total_return_pct}</td>
                      <td className="num tnum dim">{s.daily_vol_pct}</td>
                      <td className="num tnum dim">{s.beta == null ? "—" : s.beta}</td>
                      <td className={"num tnum " + (s.rel_strength_pct == null ? "dim" : (s.rel_strength_pct >= 0 ? "up" : "down"))}>
                        {s.rel_strength_pct == null ? "—" : s.rel_strength_pct}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          ) : null}
        </div>
      </Panel>

      {/* Sector breakdown */}
      <Panel title="Sector Breakdown"
             tag={sectors ? (sectors.weights_known ? "weighted" : "weights unknown") : "—"} bodyFlush>
        <div style={{ padding: 8 }}>
          {!sectors ? (
            <div className="faint" style={{ fontSize: 11 }}>computing…</div>
          ) : (
            <table className="tbl tbl-compact">
              <thead>
                <tr>
                  <th>Sector</th>
                  <th>Symbols</th>
                  <th className="num">Weight %</th>
                  <th>Agreement</th>
                </tr>
              </thead>
              <tbody>
                {(sectors.sectors || []).map(b => (
                  <tr key={b.sector}>
                    <td className="bright">{b.sector}</td>
                    <td className="dim" style={{ fontSize: 10 }}>{(b.symbols || []).join(", ")}</td>
                    <td className="num tnum">{b.weight_pct == null ? "—" : b.weight_pct.toFixed(2)}</td>
                    <td className="dim" style={{ fontSize: 10 }}>
                      {Object.entries(b.agreement || {})
                        .filter(([, v]) => v > 0)
                        .map(([k, v]) => `${k}:${v}`)
                        .join("  ")}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {sectors && !sectors.weights_known ? (
            <div className="faint" style={{ fontSize: 9, marginTop: 6 }}>
              weights not computed (source = {sectors.weights_source}). watchlist mode does not infer
              equal-weight; chair receives null weights.
            </div>
          ) : null}
        </div>
      </Panel>

      {/* Portfolio Chair */}
      <Panel title="Portfolio Chair" tag={chair && chair.ok ? "OK" : (chair ? "ERROR" : "—")} bodyFlush>
        <div style={{ padding: 10 }}>
          {!chair ? (
            <div className="faint" style={{ fontSize: 11 }}>awaiting synthesis…</div>
          ) : !chair.ok ? (
            <div className="down" style={{ fontSize: 11 }}>chair failed: {chair.error || "unknown"}</div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              {chair.verdict?.summary ? (
                <div className="bright" style={{ fontSize: 12, lineHeight: 1.5 }}>
                  {chair.verdict.summary}
                </div>
              ) : null}
              {Array.isArray(chair.verdict?.agreement_clusters) && chair.verdict.agreement_clusters.length > 0 ? (
                <div>
                  <div className="h-xxs">Agreement Clusters</div>
                  {chair.verdict.agreement_clusters.map((c, i) => (
                    <div key={i} style={{ padding: 6, border: "1px solid var(--border)", marginTop: 4 }}>
                      <div>
                        <span className={_actionPillClass(c.action)} style={{ fontSize: 9 }}>{c.action || "—"}</span>
                        <span className="bright" style={{ fontSize: 10, marginLeft: 6 }}>{(c.symbols || []).join(", ")}</span>
                      </div>
                      <div className="faint" style={{ fontSize: 10, marginTop: 3 }}>{c.rationale}</div>
                    </div>
                  ))}
                </div>
              ) : null}
              {Array.isArray(chair.verdict?.conflict_pairs) && chair.verdict.conflict_pairs.length > 0 ? (
                <div>
                  <div className="h-xxs">Conflict Pairs</div>
                  {chair.verdict.conflict_pairs.map((c, i) => (
                    <div key={i} style={{ padding: 6, border: "1px solid var(--down)", marginTop: 4 }}>
                      <div className="bright" style={{ fontSize: 11 }}>{(c.symbols || []).join(" ↔ ")}</div>
                      <div className="faint" style={{ fontSize: 10, marginTop: 3 }}>{c.rationale}</div>
                    </div>
                  ))}
                </div>
              ) : null}
              {Array.isArray(chair.verdict?.concentration_findings) && chair.verdict.concentration_findings.length > 0 ? (
                <div>
                  <div className="h-xxs">Concentration Findings</div>
                  {chair.verdict.concentration_findings.map((c, i) => (
                    <div key={i} style={{ padding: 6, border: "1px solid var(--amber)", marginTop: 4 }}>
                      <span className="amber" style={{ fontSize: 10, fontWeight: 600 }}>{c.scope || "—"}</span>
                      <div className="bright" style={{ fontSize: 11, marginTop: 2 }}>{c.detail}</div>
                      <div className="faint" style={{ fontSize: 10, marginTop: 3 }}>{c.evidence}</div>
                    </div>
                  ))}
                </div>
              ) : null}
              {Array.isArray(chair.verdict?.hedge_suggestions) && chair.verdict.hedge_suggestions.length > 0 ? (
                <div>
                  <div className="h-xxs">Hedge Suggestions</div>
                  {chair.verdict.hedge_suggestions.map((c, i) => (
                    <div key={i} style={{ padding: 6, border: "1px solid var(--cyan)", marginTop: 4 }}>
                      <div>
                        <span className="cyan" style={{ fontSize: 10, fontWeight: 600 }}>{c.action}</span>
                        <span className="bright" style={{ fontSize: 10, marginLeft: 6 }}>{(c.symbols || []).join(", ")}</span>
                      </div>
                      <div className="faint" style={{ fontSize: 10, marginTop: 3 }}>{c.rationale}</div>
                    </div>
                  ))}
                </div>
              ) : null}
              {Array.isArray(chair.verdict?.data_gaps) && chair.verdict.data_gaps.length > 0 ? (
                <div>
                  <div className="h-xxs">Data Gaps (Chair-acknowledged)</div>
                  <ul className="faint" style={{ fontSize: 10, paddingLeft: 16, marginTop: 3 }}>
                    {chair.verdict.data_gaps.map((g, i) => <li key={i}>{g}</li>)}
                  </ul>
                </div>
              ) : null}
            </div>
          )}
          {dataGaps.length > 0 ? (
            <div style={{ marginTop: 10, padding: 8, border: "1px solid var(--amber)" }}>
              <div className="h-xxs">Backend Data Gaps</div>
              <ul className="faint" style={{ fontSize: 10, paddingLeft: 16, marginTop: 3 }}>
                {dataGaps.map((g, i) => <li key={i}>{g}</li>)}
              </ul>
            </div>
          ) : null}
        </div>
      </Panel>

      <button className="btn btn-ghost" style={{ alignSelf: "flex-start" }} onClick={onClear}>
        ← clear run
      </button>
    </div>
  );
}

// ---- Top-level page ------------------------------------------------------
function PortfolioCouncilPage({ app, quotes }) {
  const [activeRunId, setActiveRunId] = _pcUseState(null);
  const [busy, setBusy] = _pcUseState(false);

  const onLaunch = (j) => {
    setActiveRunId(j.portfolio_run_id);
    setBusy(true);
  };

  // When run completes via SSE, busy auto-resets via PortfolioRunView prop... we don't track here.
  // Use a poll on the run's status to release busy.
  _pcUseEffect(() => {
    if (!activeRunId) { setBusy(false); return; }
    let cancelled = false;
    const t = setInterval(async () => {
      try {
        const r = await fetch(`/api/council/portfolio/runs/${activeRunId}`);
        if (!r.ok) return;
        const j = await r.json();
        if (cancelled) return;
        if (j.run && (j.run.status === "complete" || j.run.status === "error")) {
          setBusy(false);
        }
      } catch (e) {}
    }, 2000);
    return () => { cancelled = true; clearInterval(t); };
  }, [activeRunId]);

  return (
    <div style={{ height: "100%", overflow: "auto", padding: 8,
                  display: "flex", flexDirection: "column", gap: 8 }}>
      <CouncilOverview/>
      <PortfolioCouncilForm onLaunch={onLaunch} busy={busy}/>
      {activeRunId ? (
        <PortfolioRunView portfolioRunId={activeRunId}
                          onClear={() => { setActiveRunId(null); setBusy(false); }}/>
      ) : null}
    </div>
  );
}

window.PortfolioCouncilPage = PortfolioCouncilPage;
