// ============================================================
// Slices — by-market, bias slices, bias flags, park bias, recent days
// Tufte: dot plots, direct labeling, reference lines, no chartjunk
// ============================================================

// ---- By-market summary ---------------------------------------
function ByMarket({ roi, kpropRoi, batterHr, totalsStatus }) {
  const by = roi?.by_type || {};
  // Map raw types to nice rows
  const ml = {
    name: 'Moneyline',
    bets: (by.ml_away?.count || 0) + (by.ml_home?.count || 0),
    wins: (by.ml_away?.wins || 0) + (by.ml_home?.wins || 0),
    profit: (by.ml_away?.profit || 0) + (by.ml_home?.profit || 0),
  };
  const totals = {
    name: 'Totals (O/U)',
    bets: (by.over?.count || 0) + (by.under?.count || 0),
    wins: (by.over?.wins || 0) + (by.under?.wins || 0),
    profit: (by.over?.profit || 0) + (by.under?.profit || 0),
  };
  const kp = kpropRoi?.buckets || [];
  const kSum = kp.reduce((a, b) => ({
    bets: a.bets + b.n,
    wins: a.wins + b.hits,
    profit: a.profit + b.profit_units,
  }), { bets: 0, wins: 0, profit: 0 });
  const krow = { name: 'K-Props (trusted)', bets: kSum.bets, wins: kSum.wins, profit: kSum.profit };

  const hr = batterHr?.scalars;
  const hrRow = hr ? {
    name: 'HR Props',
    bets: hr.n,
    wins: Math.round((hr.n || 0) * (hr.base_hr_rate ?? 0)),
    profit: null,
    note: `Brier ${hr.brier?.toFixed(3)} vs ${hr.brier_baseline?.toFixed(3)} baseline`,
  } : null;

  const rows = [ml, totals, krow];
  if (hrRow) rows.push(hrRow);

  return (
    <div className="bymarket">
      <div className="bm-row">
        <div className="bm-cell header">Market</div>
        <div className="bm-cell header" style={{ textAlign: 'right' }}>Bets</div>
        <div className="bm-cell header" style={{ textAlign: 'right' }}>Hit rate</div>
        <div className="bm-cell header" style={{ textAlign: 'right' }}>Profit</div>
        <div className="bm-cell header" style={{ textAlign: 'right' }}>ROI</div>
      </div>
      {rows.map((r, i) => {
        const hit = r.bets > 0 ? r.wins / r.bets : null;
        const roiPct = r.profit != null && r.bets > 0 ? r.profit / r.bets : null;
        return (
          <div key={i} className="bm-row">
            <div className="bm-cell name">
              {r.name}
              {r.note && <div style={{ fontSize: 10, color: colors.fg3, fontWeight: 400, marginTop: 2 }}>{r.note}</div>}
            </div>
            <div className="bm-cell" style={{ textAlign: 'right', color: colors.fg2 }}>{r.bets || '—'}</div>
            <div className="bm-cell" style={{ textAlign: 'right' }}>
              {hit == null ? <span style={{ color: colors.fg3 }}>—</span> : (
                <span style={{ color: hit >= 0.524 ? colors.green : hit >= 0.5 ? colors.amber : colors.red }}>
                  {(hit*100).toFixed(1)}%
                </span>
              )}
            </div>
            <div className="bm-cell" style={{ textAlign: 'right' }}>
              {r.profit == null ? <span style={{ color: colors.fg3 }}>—</span> : (
                <span style={{ color: r.profit >= 0 ? colors.green : colors.red, fontWeight: 600 }}>
                  {r.profit >= 0 ? '+' : ''}{r.profit.toFixed(1)}u
                </span>
              )}
            </div>
            <div className="bm-cell" style={{ textAlign: 'right' }}>
              {roiPct == null ? <span style={{ color: colors.fg3 }}>—</span> : (
                <span style={{ color: roiPct >= 0 ? colors.green : colors.red, fontWeight: 600 }}>
                  {roiPct >= 0 ? '+' : ''}{(roiPct*100).toFixed(1)}%
                </span>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}


// ---- Slice diagnostics: by_spread, by_edge, by_total, by_lineup ----
// Tufte dot plot: each slice = one row with sample size + dot positioned
// relative to a break-even reference line.
function SliceDotPlot({ rows, metric = 'ml_accuracy', vmin = 0.40, vmax = 0.65, refValue = 0.524, refLabel = 'break-even', formatValue, height = null, onSelect, selected, minSample = 0 }) {
  const wrapRef = useRef(null);
  const [w, setW] = useState(420);
  useEffect(() => {
    const onResize = () => { if (wrapRef.current) setW(wrapRef.current.clientWidth); };
    onResize();
    const obs = new ResizeObserver(onResize); if (wrapRef.current) obs.observe(wrapRef.current);
    return () => obs.disconnect();
  }, []);

  if (!rows || rows.length === 0) {
    return <div style={{ color: colors.fg3, fontSize: 12 }}>No slice data.</div>;
  }

  const validRows = rows.filter(r => r.n >= minSample);
  const maxN = Math.max(...validRows.map(r => r.n));
  const dotR = (n) => 3 + Math.sqrt(n / maxN) * 4;
  const fmtVal = formatValue || ((v) => (v*100).toFixed(1) + '%');

  const labelCol = 110;
  const nCol = 36;
  const valCol = 60;
  const plotW = Math.max(140, w - labelCol - nCol - valCol - 20);

  const xScale = (v) => ((v - vmin) / (vmax - vmin)) * plotW;

  return (
    <div ref={wrapRef}>
      {/* Header row */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: `${labelCol}px ${nCol}px 1fr ${valCol}px`,
        gap: 10,
        fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)',
        textTransform: 'uppercase', letterSpacing: '0.04em',
        padding: '0 6px 6px',
        borderBottom: '1px solid var(--sb-border)',
        marginBottom: 4,
      }}>
        <span></span>
        <span style={{ textAlign: 'right' }}>n</span>
        <span style={{ position: 'relative' }}>
          <span style={{ position: 'absolute', left: 0 }}>{(vmin*100).toFixed(0)}%</span>
          <span style={{ position: 'absolute', left: `${((refValue-vmin)/(vmax-vmin))*100}%`, transform: 'translateX(-50%)', color: colors.fg2 }}>
            {refLabel} {(refValue*100).toFixed(1)}%
          </span>
          <span style={{ position: 'absolute', right: 0 }}>{(vmax*100).toFixed(0)}%</span>
        </span>
        <span style={{ textAlign: 'right' }}>value</span>
      </div>

      {validRows.map((r, i) => {
        const v = r[metric];
        if (v == null) return null;
        const isSelected = selected === r.slice;
        const cx = Math.max(0, Math.min(plotW, xScale(v)));
        const aboveRef = v > refValue;
        return (
          <div key={i}
            className={'slice-row ' + (isSelected ? 'selected' : '')}
            style={{
              gridTemplateColumns: `${labelCol}px ${nCol}px 1fr ${valCol}px`,
            }}
            onClick={() => onSelect && onSelect(r)}>
            <div className="slice-name" title={r.slice}>{r.slice}</div>
            <div className="slice-n">{r.n}</div>
            <div style={{ position: 'relative', height: 16 }}>
              <svg width="100%" height="16" style={{ position: 'absolute', inset: 0, overflow: 'visible' }} preserveAspectRatio="none" viewBox={`0 0 ${plotW} 16`}>
                <line x1={0} x2={plotW} y1={8} y2={8} stroke={colors.border} strokeWidth="1" />
                <line x1={xScale(refValue)} x2={xScale(refValue)} y1={2} y2={14}
                  stroke={colors.fg3} strokeWidth="1" strokeDasharray="2 2" opacity="0.6" />
                <circle cx={cx} cy={8} r={dotR(r.n)}
                  fill={aboveRef ? colors.green : colors.red}
                  opacity={r.n < 10 ? 0.45 : 0.85} />
              </svg>
            </div>
            <div className="slice-val" style={{ color: aboveRef ? colors.green : colors.red }}>
              {fmtVal(v)}
            </div>
          </div>
        );
      })}
    </div>
  );
}


// ---- Bias flags panel ----------------------------------------
// Shows the most extreme over/under-performers
function BiasFlags({ flags }) {
  if (!flags || flags.length === 0) return <div style={{ color: colors.fg3, fontSize: 12 }}>No bias flags.</div>;

  // Group by direction
  const top = flags.slice(0, 12);

  return (
    <div>
      <div style={{ display: 'grid', gridTemplateColumns: '90px 1fr 50px 60px 70px', gap: 8,
        fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)',
        textTransform: 'uppercase', letterSpacing: '0.04em',
        padding: '0 0 6px', borderBottom: '1px solid var(--sb-border)', marginBottom: 4 }}>
        <span>Dimension</span>
        <span>Value</span>
        <span style={{ textAlign: 'right' }}>n</span>
        <span style={{ textAlign: 'right' }}>Acc</span>
        <span style={{ textAlign: 'right' }}>Δ</span>
      </div>
      {top.map((f, i) => {
        const dev = f.deviation;
        const dim = f.dimension.replace(/_/g, ' ').replace('away', 'Away').replace('home', 'Home').replace('park', 'Park').replace('name', '').trim();
        return (
          <div key={i} style={{
            display: 'grid', gridTemplateColumns: '90px 1fr 50px 60px 70px',
            gap: 8, fontSize: 12, padding: '5px 0',
            borderBottom: i < top.length - 1 ? '1px solid rgba(45,52,68,0.4)' : 'none',
            alignItems: 'baseline',
          }}>
            <span style={{ color: colors.fg3, fontSize: 10, fontFamily: 'var(--sb-font-mono)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
              {dim}
            </span>
            <span style={{ color: colors.fg1, fontWeight: 500 }}>{f.value}</span>
            <span style={{ textAlign: 'right', fontFamily: 'var(--sb-font-mono)', color: colors.fg3, fontVariantNumeric: 'tabular-nums' }}>{f.n}</span>
            <span style={{ textAlign: 'right', fontFamily: 'var(--sb-font-mono)', fontVariantNumeric: 'tabular-nums' }}>
              {(f.accuracy*100).toFixed(0)}%
            </span>
            <span style={{ textAlign: 'right', fontFamily: 'var(--sb-font-mono)', fontVariantNumeric: 'tabular-nums', fontWeight: 600,
              color: dev >= 0 ? colors.green : colors.red }}>
              {dev >= 0 ? '+' : ''}{(dev*100).toFixed(0)}pp
            </span>
          </div>
        );
      })}
    </div>
  );
}


// ---- Park bias dot plot --------------------------------------
function ParkBias({ parks }) {
  if (!parks || parks.length === 0) return null;
  // Filter to parks with >= 5 games
  const valid = parks.filter(p => p.n >= 5);
  const maxAbs = Math.max(...valid.map(p => Math.abs(p.runs_bias)));
  const vmin = -Math.ceil(maxAbs);
  const vmax = Math.ceil(maxAbs);

  // Sort by abs bias descending
  const sorted = [...valid].sort((a, b) => Math.abs(b.runs_bias) - Math.abs(a.runs_bias));
  const top = sorted.slice(0, 10);

  const wrapRef = useRef(null);
  const [w, setW] = useState(420);
  useEffect(() => {
    const onResize = () => { if (wrapRef.current) setW(wrapRef.current.clientWidth); };
    onResize();
    const obs = new ResizeObserver(onResize); if (wrapRef.current) obs.observe(wrapRef.current);
    return () => obs.disconnect();
  }, []);

  const labelCol = 130;
  const nCol = 32;
  const valCol = 56;
  const plotW = Math.max(120, w - labelCol - nCol - valCol - 20);
  const xScale = (v) => ((v - vmin) / (vmax - vmin)) * plotW;

  return (
    <div ref={wrapRef}>
      <div style={{
        display: 'grid', gridTemplateColumns: `${labelCol}px ${nCol}px 1fr ${valCol}px`,
        gap: 10, fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)',
        textTransform: 'uppercase', letterSpacing: '0.04em',
        padding: '0 6px 6px', borderBottom: '1px solid var(--sb-border)', marginBottom: 4,
      }}>
        <span>Park</span>
        <span style={{ textAlign: 'right' }}>n</span>
        <span style={{ position: 'relative' }}>
          <span style={{ position: 'absolute', left: 0 }}>−{Math.abs(vmin)}r</span>
          <span style={{ position: 'absolute', left: '50%', transform: 'translateX(-50%)' }}>actual − predicted</span>
          <span style={{ position: 'absolute', right: 0 }}>+{vmax}r</span>
        </span>
        <span style={{ textAlign: 'right' }}>bias</span>
      </div>

      {top.map((p, i) => {
        const v = p.runs_bias;
        const cx = xScale(v);
        const colorBias = Math.abs(v) < 1 ? colors.fg2 : v > 0 ? colors.green : colors.red;
        return (
          <div key={i} className="slice-row"
            style={{ gridTemplateColumns: `${labelCol}px ${nCol}px 1fr ${valCol}px` }}>
            <div className="slice-name" title={p.park}>
              {p.park} {p.is_dome ? <span style={{ fontSize: 10, color: colors.fg3 }}>·dome</span> : null}
            </div>
            <div className="slice-n">{p.n}</div>
            <div style={{ position: 'relative', height: 16 }}>
              <svg width="100%" height="16" style={{ position: 'absolute', inset: 0 }} preserveAspectRatio="none" viewBox={`0 0 ${plotW} 16`}>
                <line x1={0} x2={plotW} y1={8} y2={8} stroke={colors.border} strokeWidth="1" />
                <line x1={xScale(0)} x2={xScale(0)} y1={2} y2={14} stroke={colors.fg3} strokeDasharray="2 2" opacity="0.6" />
                <circle cx={cx} cy={8} r={3 + Math.sqrt(p.n / 30) * 4} fill={colorBias} opacity={0.85} />
              </svg>
            </div>
            <div className="slice-val" style={{ color: colorBias }}>
              {v >= 0 ? '+' : ''}{v.toFixed(2)}r
            </div>
          </div>
        );
      })}
    </div>
  );
}


// ---- Team performance dot plot (top over/under-performers) ---
function TeamSlices({ teams, onSelect, selected }) {
  if (!teams || teams.length === 0) return null;
  // Sort by deviation from break-even, take extremes
  const sorted = [...teams]
    .filter(t => t.n >= 8)
    .sort((a, b) => Math.abs(b.ml_accuracy - 0.524) - Math.abs(a.ml_accuracy - 0.524));
  const top = sorted.slice(0, 12);

  return (
    <SliceDotPlot
      rows={top}
      metric="ml_accuracy"
      vmin={0.30} vmax={0.75}
      refValue={0.524}
      refLabel="b/e"
      onSelect={onSelect}
      selected={selected}
    />
  );
}


// ---- Recent days strip — last 10 days, P/L + ML hitrate ------
function RecentDaysStrip({ daily, growth }) {
  const deltas = useMemo(() => dailyDeltas(growth), [growth]);
  // join daily + deltas
  const dailyByDate = useMemo(() => {
    const m = {};
    for (const d of (daily || [])) m[d.date] = d;
    return m;
  }, [daily]);

  const last = deltas.slice(-10);
  if (!last.length) {
    return <div style={{ color: colors.fg3, fontSize: 12, padding: 8 }}>No recent days yet.</div>;
  }

  return (
    <div className="days-strip">
      {last.map((d, i) => {
        const dd = dailyByDate[d.date];
        const cls = d.delta > 0.01 ? 'pos' : d.delta < -0.01 ? 'neg' : 'zero';
        return (
          <div key={i} className="day-card">
            <div className="day-date">{fmt.date(d.date)}</div>
            <div className={'day-pl ' + cls}>
              {Math.abs(d.delta) < 0.005 ? '0.00u' : (d.delta >= 0 ? '+' : '') + d.delta.toFixed(2) + 'u'}
            </div>
            <div className="day-meta">
              {dd ? `${dd.ml_correct}/${dd.games} ML` : '—'}
            </div>
            <div className="day-meta" style={{ color: dd && Math.abs(dd.total_bias) > 1 ? colors.amber : colors.fg3 }}>
              {dd ? `${dd.total_bias >= 0 ? '+' : ''}${dd.total_bias.toFixed(1)}r tot` : ''}
            </div>
          </div>
        );
      })}
    </div>
  );
}


// ---- HR-prop calibration summary ----------------------------
function HrPropSummary({ hr }) {
  if (!hr || !hr.scalars) return null;
  const s = hr.scalars;
  const baselineEdge = s.brier_baseline - s.brier;
  const baselineEdgePct = (baselineEdge / s.brier_baseline);
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
      <div>
        <div style={{ fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>Sample</div>
        <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 15, fontWeight: 700, marginTop: 2 }}>{s.n}</div>
      </div>
      <div>
        <div style={{ fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>Brier</div>
        <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 15, fontWeight: 700, marginTop: 2, color: baselineEdge > 0 ? colors.green : colors.red }}>
          {s.brier.toFixed(4)}
        </div>
        <div style={{ fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)' }}>vs {s.brier_baseline.toFixed(4)} base</div>
      </div>
      <div>
        <div style={{ fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>ECE</div>
        <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 15, fontWeight: 700, marginTop: 2, color: s.ece < 0.05 ? colors.green : s.ece < 0.10 ? colors.amber : colors.red }}>
          {(s.ece*100).toFixed(1)}%
        </div>
      </div>
      <div>
        <div style={{ fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>Base / Model</div>
        <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 13, fontWeight: 600, marginTop: 2 }}>
          {(s.base_hr_rate*100).toFixed(1)}% / {(s.avg_model_prob*100).toFixed(1)}%
        </div>
      </div>
    </div>
  );
}


Object.assign(window, {
  ByMarket, SliceDotPlot, BiasFlags, ParkBias, TeamSlices,
  RecentDaysStrip, HrPropSummary,
});
