/* NBA PREDICTOR — components. Exported to window for the main app script. */

const { useState, useEffect, useMemo, useRef } = React;

/* ---- small helpers ---------------------------------------------------------*/
function pct(x) { return Math.round(x * 100); }
function signed(x, d = 1) { return (x >= 0 ? "+" : "−") + Math.abs(x).toFixed(d); }
function readable(hex) {
  // pick black/white text for a given bg
  const h = hex.replace("#", "");
  const r = parseInt(h.substr(0, 2), 16), g = parseInt(h.substr(2, 2), 16), b = parseInt(h.substr(4, 2), 16);
  return (r * 299 + g * 587 + b * 114) / 1000 > 150 ? "#16150F" : "#FBF8F1";
}

/* ---- Masthead --------------------------------------------------------------*/
function Masthead({ asOf, live }) {
  return (
    <header className="masthead">
      <div className="masthead-rule" />
      <div className="masthead-row">
        <span className="masthead-meta">EST. 2026 · WIN-PROBABILITY DESK</span>
        <span className="masthead-meta">
          <span style={{ color: live ? "#2E8B57" : "var(--ink-soft)", letterSpacing: ".1em" }}>
            {live ? "● LIVE" : "○ DEMO"}
          </span>
          {" · DATA: "}{asOf}
        </span>
      </div>
      <h1 className="masthead-title">The Hardwood Index</h1>
      <div className="masthead-sub">A basketball analytics terminal — net rating, positional matchups &amp; context, distilled to one number.</div>
      <div className="masthead-rule thick" />
    </header>
  );
}

/* ---- Team picker -----------------------------------------------------------*/
function TeamSelect({ label, teams, value, onChange, accent, align }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const team = teams.find((t) => t.id === value);
  useEffect(() => {
    function h(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);
  return (
    <div className={"team-select" + (align ? " " + align : "")} ref={ref}>
      <div className="team-select-label">{label}</div>
      <button className={"team-select-box" + (open ? " open" : "")} style={{ "--accent": accent }}
        onClick={() => setOpen((o) => !o)} aria-haspopup="listbox" aria-expanded={open}>
        <span className="team-dot" style={{ background: accent }} />
        <span className="team-select-name">{team.city} {team.name}</span>
        <span className="team-select-rec">{team.record.w}–{team.record.l} · {team.streak}</span>
        <span className="team-caret">▾</span>
      </button>
      {open && (
        <ul className="team-menu" role="listbox">
          {teams.map((tm) => (
            <li key={tm.id} role="option" aria-selected={tm.id === value}
              className={tm.id === value ? "sel" : ""}
              onClick={() => { onChange(tm.id); setOpen(false); }}>
              <span className="team-dot" style={{ background: tm.colors.primary }} />
              <span className="tm-name">{tm.city} {tm.name}</span>
              <span className="tm-rec">{tm.record.w}–{tm.record.l}</span>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

function MatchupControls({ teams, homeId, awayId, setHome, setAway, onSwap }) {
  const home = teams.find((t) => t.id === homeId);
  const away = teams.find((t) => t.id === awayId);
  return (
    <section className="matchup-controls">
      <TeamSelect label="AWAY" teams={teams} value={awayId} onChange={setAway} accent={away.colors.primary} />
      <button className="swap-btn" onClick={onSwap} title="Swap home / away">
        <span className="at-sign">@</span>
        <span className="swap-hint">swap</span>
      </button>
      <TeamSelect label="HOME" teams={teams} value={homeId} onChange={setHome} accent={home.colors.primary} align="right" />
    </section>
  );
}

/* ---- Series ribbon (marquee matchups only) --------------------------------*/
function SeriesRibbon({ series }) {
  if (!series) return null;
  return (
    <section className="series-ribbon">
      <div className="series-main">
        <span className="series-title">{series.title}</span>
        <span className="series-game">{series.game} · {series.venue} · {series.date}</span>
        <span className="series-format">{series.format}</span>
      </div>
      <div className="series-note">{series.note}</div>
    </section>
  );
}

/* ---- The verdict (headline) ------------------------------------------------*/
function Verdict({ r }) {
  const fav = r.fav;
  const favColor = fav.colors.primary;
  const homeWin = r.margin >= 0;
  const spread = Math.abs(r.score.home - r.score.away);
  return (
    <section className="verdict">
      <div className="verdict-left">
        <div className="kicker">PROJECTED WINNER</div>
        <div className="verdict-team" style={{ color: favColor }}>
          <span className="verdict-city">{fav.city}</span>
          <span className="verdict-name">{fav.name}</span>
        </div>
        <div className="verdict-line">
          {fav.abbr} by <strong>{spread}</strong> · projected {r.score.home > r.score.away ? `${r.score.home}–${r.score.away}` : `${r.score.away}–${r.score.home}`}
        </div>
      </div>
      <div className="verdict-right">
        <div className="kicker right">WIN PROBABILITY</div>
        <div className="bigprob" style={{ color: favColor }}>
          {pct(r.favProb)}<span className="bigprob-pct">%</span>
        </div>
        <div className="verdict-line right">{fav.abbr} · spread {signed(homeWin ? r.margin : -r.margin, 1)} (home)</div>
      </div>
    </section>
  );
}

/* ---- Win-probability split bar --------------------------------------------*/
function WinBar({ r }) {
  const { away, home } = r;
  const hp = pct(r.pHome);
  return (
    <div className="winbar">
      <div className="winbar-head">
        <span><b>{away.abbr}</b> {pct(r.pAway)}%</span>
        <span className="winbar-mid">WIN PROBABILITY</span>
        <span>{pct(r.pHome)}% <b>{home.abbr}</b></span>
      </div>
      <div className="winbar-track">
        <div className="winbar-fill away" style={{ width: (100 - hp) + "%", background: away.colors.primary }} />
        <div className="winbar-fill home" style={{ width: hp + "%", background: home.colors.primary }} />
        <div className="winbar-tick" />
      </div>
    </div>
  );
}

/* ---- Category edges (diverging bars) --------------------------------------*/
function CategoryEdges({ r }) {
  const { home, away } = r;
  return (
    <section className="panel">
      <div className="panel-head">
        <h2>Edge by category</h2>
        <span className="panel-key">
          <i style={{ background: away.colors.primary }} />{away.abbr}
          <i style={{ background: home.colors.primary, marginLeft: 14 }} />{home.abbr}
        </span>
      </div>
      <div className="catlist">
        {r.categories.map((c) => {
          const homeFav = c.edge >= 0;
          const w = Math.abs(c.edge) * 50; // half-width %
          const col = homeFav ? home.colors.primary : away.colors.primary;
          return (
            <div className="catrow" key={c.key}>
              <div className="catlabel">
                <span className="catname">{c.key}</span>
                <span className="catsub">{c.sub}</span>
              </div>
              <div className="catval away">{c.av}</div>
              <div className="cattrack">
                <div className="cataxis" />
                <div className="catfill" style={{
                  width: w + "%", background: col,
                  left: homeFav ? "50%" : "auto", right: homeFav ? "auto" : "50%",
                }} />
              </div>
              <div className="catval home">{c.hv}</div>
            </div>
          );
        })}
      </div>
    </section>
  );
}

/* ---- Lens breakdown (how we got here) -------------------------------------*/
function LensBreakdown({ r }) {
  const { home, away } = r;
  const w = r.opts.statsWeight;
  const rows = [
    { k: "Team net rating", sub: `weighted ${pct(w)}%`, v: r.lenses.statMargin },
    { k: "Starter matchups", sub: `weighted ${pct(1 - w)}%`, v: r.lenses.matchupMargin },
  ];
  if (Math.abs(r.lenses.benchMargin) > 0.01) rows.push({ k: "Bench / depth", sub: "second unit", v: r.lenses.benchMargin });
  rows.push({ k: "Home court", sub: "context", v: r.lenses.homeCtx });
  if (Math.abs(r.lenses.restAdj) > 0.01) rows.push({ k: "Rest / schedule", sub: "context", v: r.lenses.restAdj });
  return (
    <section className="panel">
      <div className="panel-head"><h2>How we got here</h2><span className="panel-key">points of margin →</span></div>
      <div className="lenslist">
        {rows.map((row) => {
          const homeFav = row.v >= 0;
          return (
            <div className="lensrow" key={row.k}>
              <div className="lenslabel"><span>{row.k}</span><span className="catsub">{row.sub}</span></div>
              <div className="lensval" style={{ color: homeFav ? home.colors.primary : away.colors.primary }}>
                {signed(row.v, 1)} <span className="lensteam">{homeFav ? home.abbr : away.abbr}</span>
              </div>
            </div>
          );
        })}
        <div className="lensrow total">
          <div className="lenslabel"><span>Projected margin</span></div>
          <div className="lensval">{signed(r.margin, 1)} <span className="lensteam">{r.margin >= 0 ? home.abbr : away.abbr}</span></div>
        </div>
      </div>
    </section>
  );
}

/* ---- Player mini stat line -------------------------------------------------*/
function StatLine({ p, align }) {
  return (
    <div className={"statline " + align}>
      <span><em>TS</em>{(p.ts * 100).toFixed(1)}</span>
      <span><em>PER</em>{p.per.toFixed(1)}</span>
      <span><em>BPM</em>{signed(p.bpm, 1)}</span>
      <span><em>USG</em>{p.usg.toFixed(1)}</span>
    </div>
  );
}

/* ---- Editable starter name (click to swap who starts at this position) -----*/
function StarterSelect({ team, side, pos, current, onSet, align }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    function h(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);
  const roster = (team.roster || []).slice().sort((a, b) => b.mpg - a.mpg);
  const note = current.note && <span className="injtag">{current.note}</span>;
  return (
    <div className={"starter-select" + (align === "right" ? " right" : "")} ref={ref}>
      <button className={"mname mname-btn" + (open ? " open" : "")} onClick={() => setOpen((o) => !o)}
        title="Click to change the starter at this position">
        {align === "right" && note}
        <span className="mname-txt">{current.name}</span>
        {align !== "right" && note}
        <span className="mname-caret">▾</span>
      </button>
      {open && (
        <ul className="starter-menu" role="listbox">
          <li className="sm-auto" onClick={() => { onSet(side, pos, null); setOpen(false); }}>↺ Auto (by minutes)</li>
          {roster.map((p) => (
            <li key={p.name} className={p.name === current.name ? "sel" : ""}
              onClick={() => { onSet(side, pos, p.name); setOpen(false); }}>
              <span className="sm-pos">{p.pos}</span>
              <span className="sm-name">{p.name}</span>
              <span className="sm-mpg">{p.mpg.toFixed(0)}m</span>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

/* ---- Head-to-head matchup rows --------------------------------------------*/
function MatchupCards({ r, onSetStarter }) {
  const { home, away } = r;
  const max = Math.max(...r.matchups.map((m) => Math.abs(m.edge)), 6);
  return (
    <section className="panel">
      <div className="panel-head"><h2>Head-to-head matchups</h2><span className="panel-key">tap a name to swap · adv. →</span></div>
      <div className="mlist">
        {r.matchups.map((m) => {
          const homeFav = m.edge >= 0;
          const w = (Math.abs(m.edge) / max) * 50;
          const col = homeFav ? home.colors.primary : away.colors.primary;
          const dim = (p) => (p.avail === 0 ? " dimmed" : "");
          return (
            <div className="mrow" key={m.pos}>
              <div className={"mplayer away" + dim(m.away)}>
                <StarterSelect team={away} side="away" pos={m.pos} current={m.away} onSet={onSetStarter} align="left" />
                <StatLine p={m.away} align="left" />
              </div>
              <div className="mcenter">
                <div className="mpos">{m.pos}</div>
                <div className="medge">
                  <div className="medge-axis" />
                  <div className="medge-fill" style={{ width: w + "%", background: col, left: homeFav ? "50%" : "auto", right: homeFav ? "auto" : "50%" }} />
                </div>
                <div className="medge-num" style={{ color: col }}>{signed(m.edge, 1)}</div>
              </div>
              <div className={"mplayer home" + dim(m.home)}>
                <StarterSelect team={home} side="home" pos={m.pos} current={m.home} onSet={onSetStarter} align="right" />
                <StatLine p={m.home} align="right" />
              </div>
            </div>
          );
        })}
      </div>
    </section>
  );
}

/* ---- Bench & depth ---------------------------------------------------------*/
function BenchPanel({ r, onSetStarter }) {
  const { home, away, bench } = r;
  if (!bench) return null;
  const homeDeeper = bench.edge >= 0;
  const max = Math.max(Math.abs(bench.edge), 2);
  const w = (Math.abs(bench.edge) / max) * 50;
  const col = homeDeeper ? home.colors.primary : away.colors.primary;
  const reserve = (p, side) => (
    <li key={p.name} className={"benchplayer" + (p.avail === 0 ? " dimmed" : "")}>
      <span className="bp-pos">{p.pos}</span>
      <span className="bp-name">{p.name}{p.note && <span className="injtag">{p.note}</span>}</span>
      <span className="bp-stat"><em>MPG</em>{p.mpg.toFixed(1)}</span>
      <span className="bp-stat"><em>IMP</em>{signed(p.impactRaw, 1)}</span>
      <button className="bp-start" title={`Start ${p.name} at ${p.pos}`}
        onClick={() => onSetStarter(side, p.pos, p.name)}>↑ start</button>
    </li>
  );
  const empty = <li className="benchplayer thin">No rotation reserves available</li>;
  return (
    <section className="panel">
      <div className="panel-head">
        <h2>Bench &amp; depth</h2>
        <span className="panel-key">
          <i style={{ background: away.colors.primary }} />{away.abbr}
          <i style={{ background: home.colors.primary, marginLeft: 14 }} />{home.abbr}
        </span>
      </div>
      <div className="benchrow">
        <div className="benchside away">
          <div className="benchscore">{signed(bench.away.score, 1)}</div>
          <div className="benchcap">{away.abbr} second unit</div>
        </div>
        <div className="mcenter">
          <div className="mpos">DEPTH</div>
          <div className="medge">
            <div className="medge-axis" />
            <div className="medge-fill" style={{ width: w + "%", background: col, left: homeDeeper ? "50%" : "auto", right: homeDeeper ? "auto" : "50%" }} />
          </div>
          <div className="medge-num" style={{ color: col }}>{signed(bench.edge, 1)}</div>
        </div>
        <div className="benchside home">
          <div className="benchscore">{signed(bench.home.score, 1)}</div>
          <div className="benchcap">{home.abbr} second unit</div>
        </div>
      </div>
      <div className="benchcols">
        <ul className="benchcol away">{bench.away.players.length ? bench.away.players.map((p) => reserve(p, "away")) : empty}</ul>
        <ul className="benchcol home">{bench.home.players.length ? bench.home.players.map((p) => reserve(p, "home")) : empty}</ul>
      </div>
    </section>
  );
}

/* ---- Simulation & series odds ---------------------------------------------*/
function SimPanel({ r }) {
  const { home, away, seriesOdds } = r;
  const [sim, setSim] = useState(null);
  const N = 10000, SD = 13.2;

  function run() {
    const mean = r.margin;
    const total = r.score.home + r.score.away;
    const nb = 25, lo = -37.5, step = 3; // margin buckets −37.5…+37.5
    const buckets = new Array(nb).fill(0);
    const margins = new Float64Array(N);
    let homeWins = 0;
    for (let i = 0; i < N; i++) {
      const u1 = Math.random() || 1e-9, u2 = Math.random();
      const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
      const mg = mean + SD * z;
      margins[i] = mg;
      if (mg > 0) homeWins++;
      const bi = Math.max(0, Math.min(nb - 1, Math.floor((mg - lo) / step)));
      buckets[bi]++;
    }
    margins.sort();
    const q = (p) => margins[Math.floor(p * N)];
    setSim({ homeWinPct: homeWins / N, buckets, peak: Math.max(...buckets), p25: q(0.25), p75: q(0.75), total });
  }

  const sh = pct(seriesOdds.homeWin), sa = 100 - sh;
  const scoreBand = (m) => {
    const total = sim.total; const h = Math.round((total + m) / 2), a = Math.round((total - m) / 2);
    return m >= 0 ? `${home.abbr} ${h}–${a}` : `${away.abbr} ${a}–${h}`;
  };
  return (
    <section className="panel simpanel">
      <div className="panel-head">
        <h2>Simulation &amp; series</h2>
        <span className="panel-key">σ {SD} · Bo7 2-2-1-1-1</span>
      </div>

      <div className="series-odds">
        <div className="winbar-head">
          <span><b>{away.abbr}</b> {sa}%</span>
          <span className="winbar-mid">BEST-OF-7 SERIES</span>
          <span>{sh}% <b>{home.abbr}</b></span>
        </div>
        <div className="winbar-track">
          <div className="winbar-fill away" style={{ width: sa + "%", background: away.colors.primary }} />
          <div className="winbar-fill home" style={{ width: sh + "%", background: home.colors.primary }} />
          <div className="winbar-tick" />
        </div>
        <div className="series-sub">
          Home court swings the game: {away.abbr} hosting → {home.abbr} {pct(seriesOdds.pAtAway)}% · {home.abbr} hosting → {home.abbr} {pct(seriesOdds.pAtHome)}%
        </div>
      </div>

      {!sim ? (
        <button className="sim-run" onClick={run}>▶ Run {N.toLocaleString()} game simulations</button>
      ) : (
        <div className="sim-out">
          <div className="sim-stats">
            <div className="sim-stat"><span className="sim-num" style={{ color: away.colors.primary }}>{pct(1 - sim.homeWinPct)}%</span><span className="sim-cap">{away.abbr} win</span></div>
            <div className="sim-stat mid"><span className="sim-num">{scoreBand(sim.p25)} … {scoreBand(sim.p75)}</span><span className="sim-cap">middle 50% of outcomes</span></div>
            <div className="sim-stat right"><span className="sim-num" style={{ color: home.colors.primary }}>{pct(sim.homeWinPct)}%</span><span className="sim-cap">{home.abbr} win</span></div>
          </div>
          <div className="hist">
            {sim.buckets.map((b, i) => {
              const m = -37.5 + i * 3 + 1.5;
              return <div key={i} className="hist-bar" title={`${scoreBand(m)} region`}
                style={{ height: (b / sim.peak * 100) + "%", background: m >= 0 ? home.colors.primary : away.colors.primary, opacity: .35 + .65 * (b / sim.peak) }} />;
            })}
            <div className="hist-axis" />
          </div>
          <div className="sim-foot">
            <span>← {away.city} wins</span>
            <button className="sim-rerun" onClick={run}>↻ re-run</button>
            <span>{home.city} wins →</span>
          </div>
        </div>
      )}
    </section>
  );
}

/* ---- Track this prediction (slim action bar) ------------------------------*/
function TrackBar({ tracked, onTrack, record }) {
  const haveRec = record && record.graded > 0;
  return (
    <div className="trackbar">
      <button className="track-btn" disabled={tracked} onClick={onTrack}>
        {tracked ? "✓ Prediction tracked" : "📌 Track this prediction"}
      </button>
      {haveRec && (
        <span className="track-rec">
          Desk record <b>{record.correct}–{record.graded - record.correct}</b>
          {" · "}{pct(record.winPct)}% on picks{record.brier != null && <> · Brier <b>{record.brier.toFixed(3)}</b></>}
        </span>
      )}
    </div>
  );
}

/* ---- Calibration chart (predicted prob vs. actual win rate) ----------------*/
function CalibrationChart({ buckets }) {
  const W = 300, H = 200, pad = 30;
  const x = (p) => pad + (p - 0.5) / 0.5 * (W - pad - 8);   // 50%→left, 100%→right
  const y = (p) => H - pad - p * (H - pad - 8);              // 0%→bottom, 100%→top
  const pts = buckets.filter((b) => b.n > 0);
  return (
    <svg className="calib" viewBox={`0 0 ${W} ${H}`} role="img" aria-label="Calibration curve">
      {/* perfect-calibration diagonal */}
      <line x1={x(0.5)} y1={y(0.5)} x2={x(1)} y2={y(1)} className="calib-diag" />
      {[0.5, 0.75, 1].map((g) => <line key={"v" + g} x1={x(g)} y1={y(0.5)} x2={x(g)} y2={y(1)} className="calib-grid" />)}
      {[0.5, 0.75, 1].map((g) => <line key={"h" + g} x1={x(0.5)} y1={y(g)} x2={x(1)} y2={y(g)} className="calib-grid" />)}
      {/* connecting line through points */}
      {pts.length > 1 && <polyline className="calib-line" points={pts.map((b) => `${x(b.predAvg)},${y(b.actualRate)}`).join(" ")} />}
      {pts.map((b, i) => (
        <circle key={i} cx={x(b.predAvg)} cy={y(b.actualRate)} r={Math.min(9, 3 + Math.sqrt(b.n))} className="calib-pt">
          <title>{`${pct(b.predAvg)}% predicted → ${pct(b.actualRate)}% actual (n=${b.n})`}</title>
        </circle>
      ))}
      <text x={x(0.75)} y={H - 6} className="calib-axis" textAnchor="middle">predicted win %</text>
      <text x={10} y={y(0.75)} className="calib-axis" transform={`rotate(-90 10 ${y(0.75)})`} textAnchor="middle">actual win %</text>
    </svg>
  );
}

/* ---- Equity curve (cumulative skill units over time) ----------------------*/
function EquityCurve({ timeline }) {
  if (!timeline || timeline.length < 2) return null;
  const W = 300, H = 110, pad = 18;
  const n = timeline.length;
  const us = timeline.map((d) => d.units);
  const lo = Math.min(0, ...us), hi = Math.max(0, ...us);
  const span = Math.max(1, hi - lo);
  const x = (i) => pad + (i / (n - 1)) * (W - pad - 6);
  const y = (u) => H - pad - ((u - lo) / span) * (H - pad - 6);
  const end = us[n - 1];
  const col = end >= 0 ? "#2E8B57" : "#B43B2E";
  const path = timeline.map((d, i) => `${i ? "L" : "M"}${x(i).toFixed(1)},${y(d.units).toFixed(1)}`).join(" ");
  const area = `${path} L${x(n - 1).toFixed(1)},${y(0).toFixed(1)} L${x(0).toFixed(1)},${y(0).toFixed(1)} Z`;
  return (
    <svg className="equity" viewBox={`0 0 ${W} ${H}`} role="img" aria-label="Cumulative units over time">
      <line x1={pad} y1={y(0)} x2={W - 6} y2={y(0)} className="eq-zero" />
      <path d={area} fill={col} opacity=".10" />
      <polyline points={timeline.map((d, i) => `${x(i)},${y(d.units)}`).join(" ")} className="eq-line" style={{ stroke: col }} />
      <circle cx={x(n - 1)} cy={y(end)} r="3.5" style={{ fill: col }} />
      <text x={x(n - 1) - 2} y={y(end) - 7} className="eq-end" style={{ fill: col }} textAnchor="end">{end >= 0 ? "+" : ""}{end}u</text>
    </svg>
  );
}

/* ---- Baselines: model vs. naive rules -------------------------------------*/
function BaselineBar({ baselines }) {
  if (!baselines || baselines.model.winPct == null) return null;
  const rows = [
    { k: "The model", v: baselines.model.winPct, hero: true },
    { k: "Always pick home", v: baselines.home.winPct },
    { k: "Coin flip", v: 0.5 },
  ];
  const edge = (baselines.model.winPct - baselines.home.winPct) * 100;
  return (
    <div className="trk-bl">
      <div className="trk-subhead">Model vs. naive baselines</div>
      {rows.map((r) => (
        <div className={"bl" + (r.hero ? " hero" : "")} key={r.k}>
          <span className="bl-k">{r.k}</span>
          <span className="bl-track"><span className="bl-fill" style={{ width: pct(r.v) + "%" }} /></span>
          <span className="bl-v">{pct(r.v)}%</span>
        </div>
      ))}
      <div className="bl-edge">{edge >= 0 ? "+" : ""}{edge.toFixed(1)} pts vs. always-home{edge >= 0 ? " — the model adds signal" : ""}</div>
    </div>
  );
}

/* ---- Desk record & calibration --------------------------------------------*/
function TrackRecord({ record, onBackfill, busy }) {
  if (!record) return null;
  const badge = (s) => s === "correct" ? "ok" : s === "incorrect" ? "miss" : "pend";
  const label = (s) => s === "correct" ? "✓ hit" : s === "incorrect" ? "✗ miss" : "· pending";
  return (
    <section className="panel xpanel trackpanel">
      <div className="panel-head"><h2>Desk record &amp; calibration</h2><span className="panel-key">model accountability →</span></div>
      {record.graded > 0 ? (
        <div className="trk-grid">
          <div className="trk-stats">
            <div className="trk-big">{record.correct}<span className="trk-slash">–</span>{record.graded - record.correct}</div>
            <div className="trk-cap">picks · {pct(record.winPct)}% correct</div>
            <div className="trk-metric"><span>Brier score</span><b>{record.brier.toFixed(3)}</b></div>
            <div className="trk-metric"><span>Games graded</span><b>{record.graded}</b></div>
            <div className="trk-note">Closer the dots hug the diagonal, the better-calibrated the model.</div>
          </div>
          <CalibrationChart buckets={record.buckets} />
        </div>
      ) : (
        <div className="trk-empty">No graded games yet. Track predictions for real matchups and they auto-grade against final scores (via ESPN) — then this fills with a hit-rate and a calibration curve. Or backfill recent games below.</div>
      )}

      {record.graded > 0 && (
        <div className="trk-deep">
          <div className="trk-deep-col">
            <BaselineBar baselines={record.baselines} />
            {record.segments && record.segments.some((s) => s.n > 0) && (
              <div className="trk-segs">
                <div className="trk-subhead">Where the model is strong</div>
                {record.segments.filter((s) => s.n > 0).map((s) => (
                  <div className="seg" key={s.name}>
                    <span className="seg-name">{s.name}</span>
                    <span className="seg-rec">{s.correct}–{s.n - s.correct} · {pct(s.winPct)}%</span>
                    <span className="seg-brier">Brier {s.brier.toFixed(3)}</span>
                  </div>
                ))}
              </div>
            )}
          </div>
          {record.timeline && record.timeline.length > 1 && (
            <div className="trk-deep-col">
              <div className="trk-subhead">Cumulative skill · +1 hit / −1 miss</div>
              <EquityCurve timeline={record.timeline} />
            </div>
          )}
        </div>
      )}
      {record.recent && record.recent.length > 0 && (
        <ul className="trk-recent">
          {record.recent.map((p) => (
            <li key={p.id}>
              <span className={"trk-badge " + badge(p.status)}>{label(p.status)}</span>
              <span className="trk-match">{p.away} @ {p.home}</span>
              <span className="trk-pick">{p.pick} {pct(p.pPick)}%</span>
              <span className="trk-final">{p.result ? `${p.result.awayScore}–${p.result.homeScore}` : "—"}</span>
            </li>
          ))}
        </ul>
      )}
      <div className="trk-foot">
        <span>Graded against real final scores · ESPN</span>
        <button onClick={onBackfill} disabled={busy}>{busy ? "backfilling…" : "⟲ Backfill recent real games"}</button>
      </div>
    </section>
  );
}

/* ---- X-factors -------------------------------------------------------------*/
function XFactors({ r }) {
  const { home, away } = r;
  if (!r.xfactors.length) return null;
  const colFor = (t) => (t === "home" ? home.colors.primary : t === "away" ? away.colors.primary : "var(--ink-soft)");
  return (
    <section className="panel xpanel">
      <div className="panel-head"><h2>X-factors &amp; reads</h2></div>
      <ul className="xlist">
        {r.xfactors.map((x, i) => (
          <li key={i}>
            <span className="xtag" style={{ color: colFor(x.team), borderColor: colFor(x.team) }}>{x.tag}</span>
            <span className="xtext">{x.text}</span>
          </li>
        ))}
      </ul>
    </section>
  );
}

Object.assign(window, {
  Masthead, MatchupControls, TeamSelect, SeriesRibbon, Verdict, WinBar,
  CategoryEdges, LensBreakdown, MatchupCards, StarterSelect, BenchPanel, SimPanel,
  TrackBar, CalibrationChart, EquityCurve, BaselineBar, TrackRecord, XFactors,
});
