/* ============================================================
   gp-stages.jsx — RADAR (market scan) + SELECTION (asset picks)
   ============================================================ */

var { useState, useEffect, useMemo } = React;

// ---------- RADAR ----------
function RadarView({ onJump }) {
  const radar = window.GP.DATA.radar;
  const decisions = window.GP.DATA.decisions;
  const hasIndicators = radar.hasIndicators;
  const [sortKey, setSortKey] = useState(hasIndicators ? "adx" : "sym");
  const [sortDir, setSortDir] = useState(hasIndicators ? -1 : 1);

  // selection membership
  const selectedBy = useMemo(() => {
    const m = {};
    for (const d of decisions) {
      m[d.asset] = m[d.asset] || [];
      m[d.asset].push({ strat: d.strategy_id, id: d.id });
    }
    return m;
  }, [decisions]);

  // indicator columns only when the snapshot actually carries them (Phase 2)
  const indicatorCols = hasIndicators ? [
    { k: "adx", label: "ADX", w: "0.7fr", align: "right", fmt: (a) => a.adx.toFixed(1) },
    { k: "diPlus", label: "+DI", w: "0.7fr", align: "right", fmt: (a) => a.diPlus.toFixed(1), color: () => "var(--emerald)" },
    { k: "diMinus", label: "−DI", w: "0.7fr", align: "right", fmt: (a) => a.diMinus.toFixed(1), color: () => "var(--rose)" },
    { k: "rsi", label: "RSI", w: "0.7fr", align: "right", fmt: (a) => a.rsi.toFixed(0), color: (a) => a.rsi > 70 ? "var(--rose)" : a.rsi < 32 ? "var(--emerald)" : "var(--fg)" },
    { k: "bbPctB", label: "BB %b", w: "0.7fr", align: "right", fmt: (a) => a.bbPctB.toFixed(2) },
    { k: "regime", label: "Regime", w: "1.3fr", align: "left" },
  ] : [];
  const cols = [
    { k: "sym", label: "Asset", w: "1.4fr", align: "left", get: (a) => a.sym },
    { k: "px", label: "Price", w: "1fr", align: "right", fmt: (a) => window.GP.fmt.price(a.px) },
    { k: "chg", label: "24h", w: "0.8fr", align: "right", fmt: (a) => window.GP.fmt.signedPct(a.chg), color: (a) => a.chg > 0 ? "var(--emerald)" : "var(--rose)" },
    ...indicatorCols,
  ];

  const sorted = useMemo(() => {
    const arr = [...radar.assets];
    arr.sort((a, b) => {
      const va = a[sortKey], vb = b[sortKey];
      if (va == null && vb == null) return 0;
      if (typeof va === "string" || typeof vb === "string")
        return sortDir * String(va).localeCompare(String(vb));
      return sortDir * (va - vb);
    });
    return arr;
  }, [sortKey, sortDir, radar.assets]);

  const toggleSort = (k) => {
    if (k === sortKey) setSortDir((d) => -d);
    else { setSortKey(k); setSortDir(-1); }
  };

  const sourceNote = radar.scanned_at
    ? `universe.json · ${window.GP.fmt.time(radar.scanned_at)}Z · ${window.GP.fmt.ago(radar.scanned_at, window.GP.NOW)} ago${radar.fresh ? "" : " · stale"}`
    : "no universe snapshot on disk";

  return (
    <div style={{ height: "100%", overflowY: "auto", padding: "0" }}>
      <div style={{ padding: "18px 20px", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 14 }}>
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <span style={{ fontFamily: "var(--head)", fontSize: 20, fontWeight: 600, color: "var(--fg)" }}>Universe scan</span>
            {radar.count > 0
              ? <Pill kind={radar.fresh ? "verified" : "warn"} dot>{radar.fresh ? "snapshot fresh" : "snapshot stale"}</Pill>
              : <Pill kind="neutral">no snapshot</Pill>}
          </div>
          <Mono color="var(--fg-3)" size={10} style={{ marginTop: 6, display: "block" }}>
            /api/universe · {sourceNote}
          </Mono>
        </div>
        <div style={{ display: "flex", gap: 26 }}>
          {[["assets", radar.count, "var(--fg)"], ["selected", Object.keys(selectedBy).length, "var(--emerald)"]].map(([l, v, c]) => (
            <div key={l}>
              <Mono color={c} size={22} weight={600}>{v}</Mono>
              <div><Label>{l}</Label></div>
            </div>
          ))}
        </div>
      </div>

      {!hasIndicators && (
        <div style={{ padding: "10px 20px", borderBottom: "1px solid var(--line-soft)", background: "color-mix(in oklab, var(--amber) 6%, transparent)" }}>
          <Mono color="var(--amber)" size={10} style={{ lineHeight: 1.5 }}>
            ⚑ Indicator scan (ADX · RSI · regime · BB %b) is not in the universe artifact — symbols only. Per-asset indicators are a Phase-2 backend serializer change; none are fabricated here.
          </Mono>
        </div>
      )}

      {radar.count === 0 ? (
        <div style={{ padding: "60px 20px", display: "grid", placeItems: "center" }}>
          <div style={{ textAlign: "center", maxWidth: 420 }}>
            <Mono color="var(--fg-2)" size={12} weight={600} style={{ letterSpacing: "0.1em" }}>NO UNIVERSE SNAPSHOT</Mono>
            <Mono color="var(--fg-3)" size={11} style={{ marginTop: 10, display: "block", lineHeight: 1.6 }}>
              /api/universe returned no assets — the radar scan has not written <code style={{ color: "var(--cyan)" }}>data/universe.json</code> yet. The committed plan below (Selection / Plan) is unaffected.
            </Mono>
          </div>
        </div>
      ) : (
        <div style={{ padding: "0 20px 30px" }}>
          {/* header */}
          <div style={{ display: "grid", gridTemplateColumns: "40px " + cols.map((c) => c.w).join(" ") + " 1.4fr", gap: 10, padding: "12px 8px 8px", borderBottom: "1px solid var(--line)", position: "sticky", top: 0, background: "var(--bg-0)", zIndex: 1 }}>
            <span></span>
            {cols.map((c) => (
              <button key={c.k} onClick={() => toggleSort(c.k)} style={{
                background: "transparent", border: "none", cursor: "pointer", padding: 0,
                textAlign: c.align, justifySelf: c.align === "right" ? "end" : "start",
                fontFamily: "var(--mono)", fontSize: 9, letterSpacing: "0.12em", textTransform: "uppercase",
                color: sortKey === c.k ? "var(--cyan)" : "var(--fg-3)",
              }}>{c.label}{sortKey === c.k ? (sortDir < 0 ? " ↓" : " ↑") : ""}</button>
            ))}
            <Label style={{ textAlign: "left" }}>Selected by</Label>
          </div>
          {/* rows */}
          {sorted.map((a, i) => {
            const sels = selectedBy[a.sym];
            return (
              <div key={a.sym} style={{
                display: "grid", gridTemplateColumns: "40px " + cols.map((c) => c.w).join(" ") + " 1.4fr", gap: 10,
                padding: "8px", alignItems: "center", borderBottom: "1px solid var(--line-soft)",
                background: sels ? "color-mix(in oklab, var(--emerald) 5%, transparent)" : "transparent",
              }}>
                <Mono color="var(--fg-3)" size="9">{String(i + 1).padStart(2, "0")}</Mono>
                {cols.map((c) => {
                  if (c.k === "sym") return (
                    <div key={c.k} style={{ minWidth: 0 }}>
                      <Mono color="var(--fg)" size={12} weight={600}>{a.sym.split("/")[0]}</Mono>
                      <Mono color="var(--fg-3)" size={9} style={{ marginLeft: 6 }}>{a.name}</Mono>
                    </div>
                  );
                  if (c.k === "regime") return <div key={c.k}><RegimeTag regime={a.regime} small /></div>;
                  return <Mono key={c.k} color={c.color ? c.color(a) : "var(--fg)"} size={11} style={{ textAlign: "right", justifySelf: "end" }}>{c.fmt(a)}</Mono>;
                })}
                <div style={{ display: "flex", gap: 5 }}>
                  {sels ? sels.map((s) => (
                    <button key={s.id} onClick={() => onJump(s.id)} style={{ background: "transparent", border: "none", padding: 0, cursor: "pointer" }}>
                      <Pill kind={s.strat === "MEAN_REV" ? "accent" : "survivor"}>{s.strat === "MEAN_REV" ? "MEAN-REV" : "TREND"} →</Pill>
                    </button>
                  )) : <Mono color="var(--fg-3)" size={9}>—</Mono>}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ---------- SELECTION ----------
function SelectionView({ onJump }) {
  const selections = window.GP.DATA.selections;
  const decisions = window.GP.DATA.decisions;
  const decByAsset = (strat, sym) => decisions.find((d) => d.strategy_id === strat && d.asset === sym);
  // derived mode: no client-side fit (symbol-only universe) — picks come from
  // the engine's committed decisions, not a fabricated fit ranking.
  const derived = selections.some((s) => s.derived) ||
    selections.every((s) => s.picks.every((p) => p.fit == null));

  return (
    <div style={{ height: "100%", overflowY: "auto", padding: "18px 20px 30px" }}>
      <div style={{ marginBottom: 12 }}>
        <span style={{ fontFamily: "var(--head)", fontSize: 20, fontWeight: 600, color: "var(--fg)" }}>Asset selection</span>
        <Mono color="var(--fg-3)" size={10} style={{ marginLeft: 12 }}>
          {derived
            ? "the assets each strategy committed to the D5 sweep, drawn from the live briefing"
            : "each strategy ranks the scanned universe by regime fit and commits its top picks to the sweep queue"}
        </Mono>
      </div>
      {derived && (
        <div style={{ marginBottom: 16, padding: "8px 12px", borderRadius: 3, background: "color-mix(in oklab, var(--amber) 6%, transparent)" }}>
          <Mono color="var(--amber)" size={10} style={{ lineHeight: 1.5 }}>
            ⚑ Client-side fit estimate unavailable — the universe snapshot carries symbols only, so per-asset regime fit cannot be recomputed. Picks shown are the engine's committed selections (Phase-2 backend change surfaces fit/rank).
          </Mono>
        </div>
      )}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18 }}>
        {selections.map((sel) => {
          const strat = window.GP.STRATEGIES.find((s) => s.id === sel.strategy);
          if (!strat) return null;
          const fits = [...sel.picks, ...sel.alternatives].map((p) => p.fit).filter((f) => f != null);
          const maxFit = fits.length ? Math.max(...fits) : 0;
          const Row = ({ p, selected }) => {
            const d = selected ? decByAsset(strat.id, p.sym) : null;
            const r = d ? window.GP.verify.checkDecision(d) : null;
            return (
              <div onClick={() => d && onJump(d.id)} style={{
                display: "grid", gridTemplateColumns: "24px 1fr 90px auto", gap: 10, alignItems: "center",
                padding: "9px 10px", borderRadius: 3, cursor: d ? "pointer" : "default",
                background: selected ? "var(--bg-1)" : "transparent",
                border: "1px solid " + (selected ? "var(--line)" : "transparent"),
                opacity: selected ? 1 : 0.55, marginBottom: 4,
              }}>
                <Mono color={selected ? "var(--cyan)" : "var(--fg-3)"} size={11} weight={600}>{p.rank}</Mono>
                <div style={{ minWidth: 0 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <Mono color="var(--fg)" size={13} weight={600}>{p.sym.split("/")[0]}</Mono>
                    <RegimeTag regime={p.regime} small />
                    {r && <VerdictDot verdict={r.verdict} size={6} />}
                  </div>
                </div>
                <div>
                  {p.fit != null ? (
                    <>
                      <Bar frac={maxFit ? p.fit / maxFit : 0} color={selected ? "var(--cyan)" : "var(--bg-3)"} height={4} />
                      <Mono color="var(--fg-3)" size={9} style={{ marginTop: 3, display: "block" }}>fit {p.fit.toFixed(3)}</Mono>
                    </>
                  ) : (
                    <Mono color="var(--fg-3)" size={9}>fit —</Mono>
                  )}
                </div>
                <div style={{ textAlign: "right" }}>
                  {d ? <Mono color="var(--cyan)" size={9}>sweep →</Mono> : <Mono color="var(--fg-3)" size={9}>cut</Mono>}
                </div>
              </div>
            );
          };
          return (
            <Panel key={sel.strategy} style={{ padding: 0, overflow: "hidden" }}>
              <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--line)" }}>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                  <Mono color="var(--indigo)" size={13} weight={600}>{strat.label}</Mono>
                  <Pill kind="accent">{strat.id}</Pill>
                </div>
                <Mono color="var(--fg-3)" size={10} style={{ marginTop: 6, display: "block", lineHeight: 1.5 }}>
                  fits {strat.fits} · {sel.scanned ? `scanned ${sel.scanned} · ` : ""}floor {(strat.floor * 100).toFixed(0)}%
                </Mono>
              </div>
              <div style={{ padding: "12px 12px" }}>
                <Label style={{ display: "block", marginBottom: 8 }}>Committed · {sel.picks.length} {sel.picks.length === 1 ? "pick" : "picks"}</Label>
                {sel.picks.length
                  ? sel.picks.map((p) => <Row key={p.sym} p={p} selected />)
                  : <Mono color="var(--fg-3)" size={10}>no committed picks for this strategy today</Mono>}
                {sel.alternatives.length > 0 && (
                  <>
                    <div style={{ display: "flex", alignItems: "center", gap: 8, margin: "10px 0 8px" }}>
                      <div style={{ flex: 1, borderTop: "1px dashed var(--line-strong, #3f3f46)" }} />
                      <Label color="var(--fg-3)">selection cut line</Label>
                      <div style={{ flex: 1, borderTop: "1px dashed var(--line-strong, #3f3f46)" }} />
                    </div>
                    {sel.alternatives.map((p) => <Row key={p.sym} p={p} selected={false} />)}
                  </>
                )}
              </div>
            </Panel>
          );
        })}
      </div>
    </div>
  );
}

Object.assign(window, { RadarView, SelectionView });
