// ============================================================
// Charts — Bankroll curve, Calibration reliability, Daily P/L
// Tufte: no chartjunk, direct labels, light grid, layered detail
// ============================================================

// ---- Bankroll curve (Kelly backtest) -------------------------
function BankrollChart({ growth, startBR = 100, variants = null, height = 220, showFooterStats = true }) {
  const wrapRef = useRef(null);
  const [w, setW] = useState(640);
  const [hover, setHover] = useState(null);

  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 (!growth || growth.length < 2) {
    return <div style={{ color: colors.fg3, fontSize: 12, padding: 12 }}>No bankroll data.</div>;
  }

  const pad = { l: 40, r: 60, t: 14, b: 28 };
  const innerW = Math.max(100, w - pad.l - pad.r);
  const innerH = height - pad.t - pad.b;

  const series = growth.map((d, i) => ({ ...d, i }));
  const ys = series.map(d => d.bankroll);
  const yMin = Math.min(...ys, startBR);
  const yMax = Math.max(...ys, startBR);
  const yPad = (yMax - yMin) * 0.12 || 1;
  const y0 = yMin - yPad, y1 = yMax + yPad;
  const xScale = (i) => pad.l + (i / (series.length - 1)) * innerW;
  const yScale = (v) => pad.t + (1 - (v - y0) / (y1 - y0)) * innerH;

  const path = series.map((d, i) =>
    (i === 0 ? 'M' : 'L') + xScale(d.i).toFixed(2) + ',' + yScale(d.bankroll).toFixed(2)
  ).join(' ');

  const last = series[series.length - 1];
  const final = last.bankroll;
  const ret = final - startBR;
  const retPct = (ret / startBR) * 100;
  const lineColor = ret >= 0 ? colors.green : colors.red;

  // y-ticks
  const yTicks = [];
  const range = y1 - y0;
  const step = range > 50 ? 25 : range > 20 ? 10 : 5;
  const yFloor = Math.ceil(y0 / step) * step;
  for (let v = yFloor; v <= y1; v += step) yTicks.push(v);

  // peak & trough annotations
  const peak = series.reduce((a, b) => b.bankroll > a.bankroll ? b : a, series[0]);
  const trough = series.reduce((a, b) => b.bankroll < a.bankroll ? b : a, series[0]);

  function onMove(e) {
    const rect = e.currentTarget.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const idx = Math.max(0, Math.min(series.length - 1, Math.round((mx - pad.l) / innerW * (series.length - 1))));
    setHover({ idx, clientX: e.clientX, clientY: e.clientY });
  }

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <svg width={w} height={height} className="chart-svg" onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
        {/* baseline reference */}
        <line x1={pad.l} x2={pad.l + innerW} y1={yScale(startBR)} y2={yScale(startBR)} className="reference-line" />
        <text x={pad.l + innerW + 4} y={yScale(startBR) + 4} className="label-text" fill={colors.fg3}>
          start ${startBR.toFixed(0)}
        </text>

        {/* y ticks (sparse) */}
        {yTicks.map((v, i) => (
          <g key={i}>
            <text x={pad.l - 6} y={yScale(v) + 3} textAnchor="end" className="tick-text">{v.toFixed(0)}</text>
          </g>
        ))}

        {/* x labels: first & last date only (Tufte: don't repeat what label-text says) */}
        <text x={pad.l} y={height - 8} className="tick-text">{fmt.date(series[0].date)}</text>
        <text x={pad.l + innerW} y={height - 8} textAnchor="end" className="tick-text">{fmt.date(last.date)}</text>

        {/* area + line */}
        <path
          d={path + ` L${xScale(last.i).toFixed(2)},${yScale(startBR).toFixed(2)} L${xScale(0).toFixed(2)},${yScale(startBR).toFixed(2)} Z`}
          fill={lineColor} opacity="0.10"
        />
        <path d={path} fill="none" stroke={lineColor} strokeWidth="1.75" strokeLinejoin="round" />

        {/* peak / trough annotations */}
        {peak.i > 0 && peak.i < series.length - 1 && (
          <g>
            <circle cx={xScale(peak.i)} cy={yScale(peak.bankroll)} r="2.5" fill={colors.green} />
            <text x={xScale(peak.i)} y={yScale(peak.bankroll) - 6} textAnchor="middle" className="label-text" fill={colors.fg2}>
              ${peak.bankroll.toFixed(0)}
            </text>
          </g>
        )}
        {trough.i > 0 && trough.i < series.length - 1 && trough.bankroll < startBR && (
          <g>
            <circle cx={xScale(trough.i)} cy={yScale(trough.bankroll)} r="2.5" fill={colors.red} />
            <text x={xScale(trough.i)} y={yScale(trough.bankroll) + 14} textAnchor="middle" className="label-text" fill={colors.fg2}>
              ${trough.bankroll.toFixed(0)}
            </text>
          </g>
        )}

        {/* end value */}
        <circle cx={xScale(last.i)} cy={yScale(last.bankroll)} r="3.5" fill={lineColor} />
        <text x={xScale(last.i) + 6} y={yScale(last.bankroll) + 4} className="label-text" fill={lineColor} style={{ fontWeight: 600 }}>
          ${last.bankroll.toFixed(1)}
        </text>

        {/* hover crosshair */}
        {hover && (
          <g>
            <line x1={xScale(hover.idx)} x2={xScale(hover.idx)} y1={pad.t} y2={pad.t + innerH} stroke={colors.fg3} strokeWidth="1" opacity="0.4" strokeDasharray="2 2" />
            <circle cx={xScale(hover.idx)} cy={yScale(series[hover.idx].bankroll)} r="3.5" fill={lineColor} stroke={colors.surface} strokeWidth="1.5" />
          </g>
        )}
      </svg>

      {/* Summary chips, below chart, integrated */}
      {showFooterStats && (
        <div style={{ display: 'flex', gap: 18, fontSize: 11, color: colors.fg2, fontFamily: 'var(--sb-font-mono)', marginTop: 6, flexWrap: 'wrap' }}>
          <span>Return <strong style={{ color: ret >= 0 ? colors.green : colors.red, fontWeight: 600 }}>{ret >= 0 ? '+' : ''}{retPct.toFixed(2)}%</strong></span>
          <span>Peak <strong style={{ color: colors.fg1, fontWeight: 600 }}>${peak.bankroll.toFixed(1)}</strong></span>
          <span>Max DD <strong style={{ color: colors.red, fontWeight: 600 }}>{variants?.full_kelly?.max_drawdown_pct?.toFixed(1) ?? '—'}%</strong></span>
          <span>Bets sized <strong style={{ color: colors.fg1, fontWeight: 600 }}>{variants?.full_kelly?.n_bets_sized ?? '—'}</strong></span>
        </div>
      )}

      {hover && (
        <ChartTooltip x={hover.clientX} y={hover.clientY}>
          <div className="tip-label">{fmt.date(series[hover.idx].date)}</div>
          <div className="tip-row">
            <span>Bankroll</span><span className="tip-val">${series[hover.idx].bankroll.toFixed(2)}</span>
          </div>
          <div className="tip-row">
            <span>vs start</span><span className="tip-val" style={{ color: (series[hover.idx].bankroll - startBR) >= 0 ? colors.green : colors.red }}>
              {(series[hover.idx].bankroll - startBR >= 0 ? '+' : '')}{(series[hover.idx].bankroll - startBR).toFixed(2)}u
            </span>
          </div>
        </ChartTooltip>
      )}
    </div>
  );
}


// ---- Calibration Reliability Plot ---------------------------
// x = model probability, y = actual win rate, dot area = sample
// Diagonal = perfect calibration
function CalibrationPlot({ buckets, height = 240 }) {
  const wrapRef = useRef(null);
  const [w, setW] = useState(420);
  const [hover, setHover] = useState(null);

  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 (!buckets || buckets.length === 0) {
    return <div style={{ color: colors.fg3, fontSize: 12, padding: 12 }}>No calibration data.</div>;
  }

  const valid = buckets.filter(b => b.count > 0);
  const pad = { l: 36, r: 16, t: 14, b: 32 };
  const innerW = Math.max(100, w - pad.l - pad.r);
  const innerH = height - pad.t - pad.b;

  // Determine domain - bracket around the actual buckets
  const vmin = 0.45, vmax = 0.80;
  const xScale = (v) => pad.l + (v - vmin) / (vmax - vmin) * innerW;
  const yScale = (v) => pad.t + (1 - (v - vmin) / (vmax - vmin)) * innerH;

  // ticks
  const ticks = [0.5, 0.6, 0.7, 0.8];
  const maxN = Math.max(...valid.map(b => b.count));
  const dotR = (n) => 4 + Math.sqrt(n / maxN) * 16;

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <svg width={w} height={height} className="chart-svg">
        {/* grid */}
        {ticks.map(t => (
          <g key={t}>
            <line x1={xScale(t)} x2={xScale(t)} y1={pad.t} y2={pad.t + innerH} className="gridline dashed" />
            <line x1={pad.l} x2={pad.l + innerW} y1={yScale(t)} y2={yScale(t)} className="gridline dashed" />
            <text x={xScale(t)} y={height - 12} textAnchor="middle" className="tick-text">{(t*100).toFixed(0)}%</text>
            <text x={pad.l - 6} y={yScale(t) + 3} textAnchor="end" className="tick-text">{(t*100).toFixed(0)}%</text>
          </g>
        ))}

        {/* perfect calibration diagonal */}
        <line x1={xScale(vmin)} y1={yScale(vmin)} x2={xScale(vmax)} y2={yScale(vmax)} stroke={colors.fg3} strokeWidth="1" strokeDasharray="3 3" opacity="0.7" />
        <text x={xScale(0.78)} y={yScale(0.78) - 6} className="label-text" fill={colors.fg3} textAnchor="end">perfect</text>

        {/* connecting line through bucket centers */}
        <path
          d={valid.map((b, i) => (i === 0 ? 'M' : 'L') + xScale(b.avg_model_prob).toFixed(2) + ',' + yScale(b.actual_win_rate).toFixed(2)).join(' ')}
          fill="none" stroke={colors.blue} strokeWidth="1.25" opacity="0.5"
        />

        {/* dots */}
        {valid.map((b, i) => {
          const ce = b.calibration_error;
          const color = Math.abs(ce) < 0.05 ? colors.green : Math.abs(ce) < 0.1 ? colors.amber : colors.red;
          const cx = xScale(b.avg_model_prob);
          const cy = yScale(b.actual_win_rate);
          const r = dotR(b.count);
          const isHover = hover && hover.i === i;
          return (
            <g key={i}
              onMouseEnter={(e) => setHover({ i, b, clientX: e.clientX, clientY: e.clientY })}
              onMouseLeave={() => setHover(null)}
              onMouseMove={(e) => setHover({ i, b, clientX: e.clientX, clientY: e.clientY })}
              style={{ cursor: 'pointer' }}>
              {/* error bar to diagonal */}
              <line x1={cx} y1={cy} x2={xScale(b.avg_model_prob)} y2={yScale(b.avg_model_prob)}
                stroke={color} strokeWidth="1" strokeDasharray="2 2" opacity="0.5" />
              <circle cx={cx} cy={cy} r={r} fill={color} opacity={isHover ? 0.7 : 0.35} stroke={color} strokeWidth="1.5" />
              <text x={cx} y={cy + 3} textAnchor="middle" fontSize="9" fontFamily="var(--sb-font-mono)" fill={colors.fg1} style={{ pointerEvents: 'none', fontWeight: 600 }}>
                {b.count}
              </text>
            </g>
          );
        })}

        {/* axis titles */}
        <text x={pad.l + innerW/2} y={height - 1} textAnchor="middle" className="axis-title">Model probability →</text>
        <text x={10} y={pad.t + innerH/2} className="axis-title" transform={`rotate(-90 10 ${pad.t + innerH/2})`} textAnchor="middle">Actual win rate →</text>
      </svg>

      {hover && (
        <ChartTooltip x={hover.clientX} y={hover.clientY}>
          <div className="tip-label">{hover.b.bucket} bucket</div>
          <div className="tip-row"><span>Sample</span><span className="tip-val">{hover.b.count} games</span></div>
          <div className="tip-row"><span>Predicted</span><span className="tip-val">{fmt.pct(hover.b.avg_model_prob, 1)}</span></div>
          <div className="tip-row"><span>Actual</span><span className="tip-val">{fmt.pct(hover.b.actual_win_rate, 1)}</span></div>
          <div className="tip-row">
            <span>Gap</span>
            <span className="tip-val" style={{ color: Math.abs(hover.b.calibration_error) < 0.05 ? colors.green : Math.abs(hover.b.calibration_error) < 0.1 ? colors.amber : colors.red }}>
              {hover.b.calibration_error >= 0 ? '+' : ''}{(hover.b.calibration_error * 100).toFixed(1)}pp
            </span>
          </div>
        </ChartTooltip>
      )}
    </div>
  );
}


// ---- Daily P/L bars (small multiple, last 30 days) -----------
function DailyPL({ growth, daily, height = 130 }) {
  const wrapRef = useRef(null);
  const [w, setW] = useState(420);
  const [hover, setHover] = useState(null);

  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 deltas = useMemo(() => dailyDeltas(growth), [growth]);
  // Match each delta to its daily ML accuracy
  const dailyByDate = useMemo(() => {
    const m = {};
    for (const d of (daily || [])) m[d.date] = d;
    return m;
  }, [daily]);

  if (!deltas.length) return null;

  const pad = { l: 8, r: 8, t: 8, b: 26 };
  const innerW = Math.max(100, w - pad.l - pad.r);
  const innerH = height - pad.t - pad.b;
  const ys = deltas.map(d => d.delta);
  const yMax = Math.max(...ys, 0);
  const yMin = Math.min(...ys, 0);
  const yRange = yMax - yMin || 1;
  const yZero = pad.t + (yMax / yRange) * innerH;

  const barW = innerW / deltas.length;
  const barGap = Math.min(1.5, barW * 0.15);
  const drawW = barW - barGap;

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <svg width={w} height={height} className="chart-svg" onMouseLeave={() => setHover(null)}>
        {/* zero baseline */}
        <line x1={pad.l} x2={pad.l + innerW} y1={yZero} y2={yZero} stroke={colors.fg3} strokeWidth="1" opacity="0.7" />

        {deltas.map((d, i) => {
          const x = pad.l + i * barW;
          const h = Math.abs(d.delta) / yRange * innerH;
          const y = d.delta >= 0 ? yZero - h : yZero;
          const isHover = hover && hover.i === i;
          return (
            <rect key={i} x={x + barGap/2} y={y} width={drawW} height={Math.max(h, 0.5)}
              fill={d.delta >= 0 ? colors.green : colors.red}
              opacity={isHover ? 1 : 0.78}
              onMouseEnter={(e) => setHover({ i, d, clientX: e.clientX, clientY: e.clientY })}
              onMouseMove={(e) => setHover({ i, d, clientX: e.clientX, clientY: e.clientY })}
              style={{ cursor: 'pointer' }} />
          );
        })}
        {/* sparse date labels: first, middle, last */}
        {[0, Math.floor(deltas.length/2), deltas.length-1].map(idx => (
          <text key={idx}
            x={pad.l + idx * barW + barW/2}
            y={height - 8}
            textAnchor={idx === 0 ? 'start' : idx === deltas.length-1 ? 'end' : 'middle'}
            className="tick-text">
            {fmt.date(deltas[idx].date)}
          </text>
        ))}
      </svg>

      {hover && (
        <ChartTooltip x={hover.clientX} y={hover.clientY}>
          <div className="tip-label">{fmt.date(hover.d.date)}</div>
          <div className="tip-row"><span>P/L</span>
            <span className="tip-val" style={{ color: hover.d.delta >= 0 ? colors.green : colors.red }}>
              {hover.d.delta >= 0 ? '+' : ''}{hover.d.delta.toFixed(2)}u
            </span>
          </div>
          <div className="tip-row"><span>Bankroll</span><span className="tip-val">${hover.d.bankroll.toFixed(2)}</span></div>
          {dailyByDate[hover.d.date] && (
            <div className="tip-row"><span>ML</span>
              <span className="tip-val">{dailyByDate[hover.d.date].ml_correct}/{dailyByDate[hover.d.date].games} ({fmt.pct(dailyByDate[hover.d.date].ml_accuracy, 0)})</span>
            </div>
          )}
        </ChartTooltip>
      )}
    </div>
  );
}


// ---- Totals model status — compact view ---------------------
function TotalsStatus({ status }) {
  if (!status) return null;
  const months = status.by_month || [];
  const breakeven = status.breakeven_pct ?? 52.4;
  return (
    <div>
      <div className="callout" style={{ marginBottom: 12 }}>
        <span className="callout-icon">!</span>
        <div>
          <strong>Totals signal weak.</strong>{' '}
          <span style={{ color: colors.fg2 }}>
            Direction-correct rate <strong style={{ color: colors.amber }}>{status.overall.direction_correct_pct}%</strong> trails the <strong>{breakeven}%</strong> needed for break-even. Bets gated to ≥{status.gate_min_edge_pct}% edge until signal develops.
          </span>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 0 }}>
        {months.map(m => {
          const dc = m.direction_correct_pct;
          const ok = dc >= breakeven;
          return (
            <div key={m.month} style={{
              borderRight: '1px solid var(--sb-border)',
              padding: '10px 14px',
              borderBottom: '1px solid var(--sb-border)',
            }}>
              <div style={{ fontSize: 10, color: colors.fg3, fontFamily: 'var(--sb-font-mono)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 4 }}>
                {m.month} <span style={{ color: colors.fg3 }}>· {m.n} games</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, fontFamily: 'var(--sb-font-mono)', fontVariantNumeric: 'tabular-nums' }}>
                <div>
                  <div style={{ fontSize: 9, color: colors.fg3, textTransform: 'uppercase', letterSpacing: '0.04em' }}>Dir.correct</div>
                  <div style={{ fontSize: 18, fontWeight: 700, color: ok ? colors.green : colors.amber }}>{dc}%</div>
                </div>
                <div>
                  <div style={{ fontSize: 9, color: colors.fg3, textTransform: 'uppercase', letterSpacing: '0.04em' }}>Bias</div>
                  <div style={{ fontSize: 13, color: Math.abs(m.bias_runs) < 0.3 ? colors.fg1 : colors.amber }}>
                    {m.bias_runs >= 0 ? '+' : ''}{m.bias_runs.toFixed(2)}r
                  </div>
                </div>
                <div>
                  <div style={{ fontSize: 9, color: colors.fg3, textTransform: 'uppercase', letterSpacing: '0.04em' }}>MAE</div>
                  <div style={{ fontSize: 13, color: colors.fg2 }}>{m.mae_runs.toFixed(2)}r</div>
                </div>
              </div>
              {/* mini bar showing vs breakeven */}
              <svg width="100%" height="6" style={{ marginTop: 8, display: 'block' }} preserveAspectRatio="none" viewBox="0 0 100 6">
                <rect x={0} y={2} width={100} height={2} fill={colors.border} />
                <rect x={0} y={2} width={dc} height={2} fill={ok ? colors.green : colors.amber} />
                <line x1={breakeven} x2={breakeven} y1={0} y2={6} stroke={colors.fg2} strokeWidth="0.5" strokeDasharray="1 1" />
              </svg>
            </div>
          );
        })}
      </div>
    </div>
  );
}


Object.assign(window, { BankrollChart, CalibrationPlot, DailyPL, TotalsStatus });
