// StatsPage.jsx — season arc chart + per-meat sparklines from event history

const { useState: useStateStats, useMemo: useMemoStats } = React;

const StatsPage = () => {
  useReveal();
  const store = useEventsStore();
  const done = store.events.filter(e => e.done);

  // Group done events by year
  const monthOrder = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
  const monthKey = (e) => {
    const m = monthOrder.indexOf((e.date || '').slice(0, 3).toUpperCase());
    const d = parseInt((e.date || '').slice(4)) || 0;
    return (m >= 0 ? m : 99) * 100 + d;
  };
  const byYear = {};
  done.forEach(e => {
    const y = String(e.year || '—');
    (byYear[y] = byYear[y] || []).push(e);
  });
  Object.keys(byYear).forEach(y => byYear[y].sort((a, b) => monthKey(a) - monthKey(b)));
  const years = Object.keys(byYear).sort((a, b) => Number(a) - Number(b));

  // Year colors — flame, slime, blue cycle
  const yearColors = {};
  const palette = ['var(--wbbq-flame)', 'var(--wbbq-slime)', 'var(--wbbq-blue)', 'var(--wbbq-bone)'];
  years.forEach((y, i) => { yearColors[y] = palette[i % palette.length]; });

  // Year filter — defaults to most recent year with data
  const latestYear = years.length ? years[years.length - 1] : null;
  const [selectedYears, setSelectedYears] = useStateStats(latestYear ? [latestYear] : []);

  // Keep selection valid if underlying data changes
  const validSelected = selectedYears.filter(y => years.includes(y));
  const activeYears = (validSelected.length ? validSelected : (latestYear ? [latestYear] : []))
    .slice()
    .sort((a, b) => Number(a) - Number(b));

  const toggleYear = (y) => {
    setSelectedYears(prev => {
      const cur = prev.filter(x => years.includes(x));
      if (cur.includes(y)) {
        // don't allow removing the last one
        return cur.length === 1 ? cur : cur.filter(x => x !== y);
      }
      return [...cur, y];
    });
  };

  // ---- Summary cards per year (filtered) ----
  const summaries = activeYears.map(y => {
    const evs = byYear[y];
    const scores = evs.map(e => parseFloat(e.score)).filter(Number.isFinite);
    const places = evs.map(e => {
      const m = String(e.place || '').match(/^(\d+)/);
      return m ? parseInt(m[1]) : null;
    }).filter(p => p !== null);
    const avgScore = scores.length ? (scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(1) : '—';
    const avgPlace = places.length ? (places.reduce((a, b) => a + b, 0) / places.length).toFixed(1) : '—';
    const bestPlace = places.length ? Math.min(...places) : null;
    const bestScore = scores.length ? Math.max(...scores) : null;
    const podiums = places.filter(p => p <= 3).length +
      evs.filter(e => /rookie of/i.test(e.place || '')).length;
    return { year: y, count: evs.length, avgScore, avgPlace, bestPlace, bestScore, podiums };
  });

  if (done.length === 0) {
    return (
      <>
        <PageHero eyebrow="The Numbers" title={<>By The <span style={{ color: 'var(--wbbq-flame)' }}>Numbers.</span></>} subtitle="Scores, arcs, and the cold-hard story of our smoke." mascot="robot-cat-fish.svg" />
        <section className="section">
          <div className="wrap">
            <div style={{ padding: 40, textAlign: 'center', border: '2px dashed var(--border)', background: 'var(--wbbq-ink)', color: 'var(--fg2)' }}>
              No completed comps yet. Chart unlocks after the first cook.
            </div>
          </div>
        </section>
      </>
    );
  }

  return (
    <>
      <PageHero eyebrow="The Numbers" title={<>By The <span style={{ color: 'var(--wbbq-flame)' }}>Numbers.</span></>} subtitle="Scores, arcs, and the cold-hard story of our smoke." mascot="robot-cat-fish.svg" />

      <section className="section">
        <div className="wrap">
          {/* Year filter chips */}
          <div className="reveal" style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 32 }}>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 11, letterSpacing: '0.14em', color: 'var(--fg3)', textTransform: 'uppercase' }}>Seasons:</div>
            {years.map(y => {
              const isActive = activeYears.includes(y);
              const c = yearColors[y];
              return (
                <button
                  key={y}
                  onClick={() => toggleYear(y)}
                  style={{
                    fontFamily: 'var(--font-display)',
                    fontSize: 14,
                    letterSpacing: '0.08em',
                    padding: '8px 14px',
                    background: isActive ? c : 'transparent',
                    color: isActive ? 'var(--wbbq-black)' : c,
                    border: `2px solid ${c}`,
                    cursor: 'pointer',
                    transition: 'all 0.15s',
                  }}
                >
                  {y} <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, opacity: 0.7, marginLeft: 4 }}>({byYear[y].length})</span>
                </button>
              );
            })}
            {years.length > 1 && (
              <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)', marginLeft: 'auto' }}>
                Tap to compare years
              </div>
            )}
          </div>

          {/* Year summary cards */}
          <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(summaries.length, 4)}, 1fr)`, gap: 20, marginBottom: 56 }} className="stats-year-grid">
            {summaries.map(s => (
              <div key={s.year} className="reveal" style={{
                background: 'var(--wbbq-ink)',
                borderTop: `4px solid ${yearColors[s.year]}`,
                padding: '24px 22px',
              }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 16 }}>
                  <div style={{ fontFamily: 'var(--font-display)', fontSize: 32, color: yearColors[s.year], lineHeight: 1 }}>{s.year}</div>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)' }}>{s.count} COMPS</div>
                </div>
                <Stat label="Avg Score" value={s.avgScore} />
                <Stat label="Avg Place" value={s.avgPlace} />
                <Stat label="Best Finish" value={s.bestPlace !== null ? (window.formatPlace ? window.formatPlace(s.bestPlace) : s.bestPlace) : '—'} highlight />
                <Stat label="Podium Calls" value={s.podiums} />
              </div>
            ))}
          </div>

          {/* Skill Multiples — per-year trajectory, normalized by field size */}
          <div className="reveal" style={{ marginBottom: 64 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 20, marginBottom: 16 }}>
              <div className="eyebrow" style={{ color: 'var(--wbbq-flame)' }}>◉ Year Over Year</div>
              <h2 style={{ fontFamily: 'var(--font-display)', fontSize: 32, color: 'var(--wbbq-bone)', margin: 0, letterSpacing: '0.02em' }}>Are We <span style={{ color: 'var(--wbbq-flame)' }}>Climbing?</span></h2>
              <div style={{ flex: 1, height: 2, background: 'repeating-linear-gradient(90deg, var(--border) 0 8px, transparent 8px 14px)' }} />
            </div>
            <p style={{ color: 'var(--fg2)', fontSize: 14, marginTop: 0, marginBottom: 20, maxWidth: 720 }}>
              One row per year. Raw score on the left, field-beaten % on the right — the skill signal under the noise of raw scoring.
            </p>
            {window.SkillExplainer && <window.SkillExplainer />}
            <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              {activeYears.map(y => (
                window.SkillYearStrip ? <window.SkillYearStrip key={y} year={y} evs={byYear[y]} color={yearColors[y]} /> : null
              ))}
            </div>
          </div>

          {/* Per-meat sparklines */}
          <div className="reveal">
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 20, marginBottom: 16 }}>
              <div className="eyebrow" style={{ color: 'var(--wbbq-slime)' }}>◉ Meat By Meat</div>
              <h2 style={{ fontFamily: 'var(--font-display)', fontSize: 32, color: 'var(--wbbq-bone)', margin: 0, letterSpacing: '0.02em' }}>The Four <span style={{ color: 'var(--wbbq-slime)' }}>Masters.</span></h2>
              <div style={{ flex: 1, height: 2, background: 'repeating-linear-gradient(90deg, var(--border) 0 8px, transparent 8px 14px)' }} />
            </div>
            <p style={{ color: 'var(--fg2)', fontSize: 14, marginTop: 0, marginBottom: 24, maxWidth: 680 }}>
              Per-meat place trend — each dot is one comp. 1st sits at the top. Tinted bands = top-10 / podium. The ringed dot is the latest finish.
            </p>
            {(() => {
              const meats = ['chicken','ribs','pork','brisket'];
              let sharedMax = 10;
              activeYears.forEach(y => byYear[y].forEach(e => {
                meats.forEach(m => {
                  const r = e.results && e.results[m];
                  const p = r && parseInt(r.place);
                  if (Number.isFinite(p)) sharedMax = Math.max(sharedMax, p);
                });
              }));
              return (
                <div className="meat-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16 }}>
                  {meats.map(meat => (
                    <MeatSparkline key={meat} meat={meat} byYear={byYear} years={activeYears} yearColors={yearColors} sharedMax={sharedMax} />
                  ))}
                </div>
              );
            })()}
          </div>
        </div>
      </section>
    </>
  );
};

// ---------- Stat row inside the year card ----------
const Stat = ({ label, value, highlight }) => (
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '8px 0', borderTop: '1px dashed var(--border)' }}>
    <div style={{ fontFamily: 'var(--font-display)', fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--fg3)' }}>{label}</div>
    <div style={{ fontFamily: 'var(--font-display)', fontSize: highlight ? 20 : 16, color: highlight ? 'var(--wbbq-flame)' : 'var(--fg1)' }}>{value}</div>
  </div>
);

// ---------- Season Arc: multi-year overlaid line chart ----------
const SeasonArc = ({ byYear, years, yearColors }) => {
  const W = 900, H = 380, PAD = { t: 30, r: 30, b: 50, l: 60 };
  const innerW = W - PAD.l - PAD.r;
  const innerH = H - PAD.t - PAD.b;

  // X axis: months (JAN..DEC)
  const months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
  const xScale = (e) => {
    const m = months.indexOf((e.date || '').slice(0, 3).toUpperCase());
    const d = parseInt((e.date || '').slice(4)) || 0;
    const frac = (m >= 0 ? m : 0) + (d / 31);
    return (frac / 12) * innerW;
  };

  // Y axis: score domain, with padding
  const allScores = years.flatMap(y => byYear[y].map(e => parseFloat(e.score)).filter(Number.isFinite));
  if (allScores.length === 0) {
    return <div style={{ color: 'var(--fg3)', fontStyle: 'italic' }}>No scores recorded yet.</div>;
  }
  const minS = Math.min(...allScores), maxS = Math.max(...allScores);
  // Clean band: round min down / max up to nearest 10
  const yMin = Math.floor(minS / 10) * 10 - 10;
  const yMax = Math.ceil(maxS / 10) * 10 + 10;
  const yScale = (v) => innerH - ((v - yMin) / (yMax - yMin)) * innerH;

  // Build Y gridlines — every ~20pts
  const yStep = 20;
  const yLines = [];
  for (let v = Math.ceil(yMin / yStep) * yStep; v <= yMax; v += yStep) yLines.push(v);

  const [hover, setHover] = useStateStats(null);

  return (
    <div style={{ background: 'var(--wbbq-ink)', border: '1px solid var(--border)', padding: 24, position: 'relative' }}>
      {/* Legend */}
      <div style={{ display: 'flex', gap: 16, marginBottom: 12, flexWrap: 'wrap' }}>
        {years.map(y => (
          <div key={y} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <div style={{ width: 14, height: 4, background: yearColors[y] }} />
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 13, letterSpacing: '0.08em', color: yearColors[y] }}>{y}</div>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)' }}>({byYear[y].length})</div>
          </div>
        ))}
      </div>

      <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block', fontFamily: 'var(--font-mono)' }}>
        {/* Y gridlines */}
        {yLines.map(v => (
          <g key={v}>
            <line x1={PAD.l} y1={PAD.t + yScale(v)} x2={PAD.l + innerW} y2={PAD.t + yScale(v)} stroke="var(--border)" strokeWidth="1" strokeDasharray="2 4" />
            <text x={PAD.l - 10} y={PAD.t + yScale(v) + 4} textAnchor="end" fontSize="11" fill="var(--fg3)">{v}</text>
          </g>
        ))}
        {/* X month ticks */}
        {months.map((m, i) => (
          <g key={m}>
            <line x1={PAD.l + (i / 12) * innerW} y1={PAD.t + innerH} x2={PAD.l + (i / 12) * innerW} y2={PAD.t + innerH + 6} stroke="var(--border)" strokeWidth="1" />
            <text x={PAD.l + (i / 12) * innerW + (innerW / 12) / 2} y={PAD.t + innerH + 22} textAnchor="middle" fontSize="10" fill="var(--fg3)" letterSpacing="0.1em">{m}</text>
          </g>
        ))}
        {/* Axis label */}
        <text x={PAD.l - 45} y={PAD.t + innerH / 2} fontSize="10" fill="var(--fg3)" letterSpacing="0.14em" transform={`rotate(-90 ${PAD.l - 45} ${PAD.t + innerH / 2})`} textAnchor="middle">SCORE</text>

        {/* Year-to-year connectors */}
        {years.map((y, i) => {
          if (i === 0) return null;
          const prevY = years[i - 1];
          const prevEvs = byYear[prevY].filter(e => Number.isFinite(parseFloat(e.score)));
          const curEvs = byYear[y].filter(e => Number.isFinite(parseFloat(e.score)));
          if (!prevEvs.length || !curEvs.length) return null;
          const a = prevEvs[prevEvs.length - 1];
          const b = curEvs[0];
          return (
            <line key={`bridge-${y}`}
              x1={PAD.l + xScale(a)} y1={PAD.t + yScale(parseFloat(a.score))}
              x2={PAD.l + xScale(b)} y2={PAD.t + yScale(parseFloat(b.score))}
              stroke="var(--fg3)" strokeWidth="1.5" strokeDasharray="4 4" opacity="0.45" />
          );
        })}

        {/* Year lines */}
        {years.map(y => {
          const evs = byYear[y].filter(e => Number.isFinite(parseFloat(e.score)));
          if (evs.length === 0) return null;
          const points = evs.map(e => ({ e, x: PAD.l + xScale(e), y: PAD.t + yScale(parseFloat(e.score)) }));
          const path = points.map((p, i) => (i === 0 ? 'M' : 'L') + p.x.toFixed(1) + ' ' + p.y.toFixed(1)).join(' ');
          return (
            <g key={y}>
              <path d={path} fill="none" stroke={yearColors[y]} strokeWidth="3" strokeLinejoin="round" strokeLinecap="round" />
              {points.map((p, i) => (
                <g key={i} onMouseEnter={() => setHover({ ...p.e, x: p.x, y: p.y, color: yearColors[y] })} onMouseLeave={() => setHover(null)}>
                  <circle cx={p.x} cy={p.y} r="14" fill="transparent" style={{ cursor: 'pointer' }} />
                  <circle cx={p.x} cy={p.y} r="5" fill={yearColors[y]} stroke="var(--wbbq-ink)" strokeWidth="2" />
                </g>
              ))}
            </g>
          );
        })}

        {/* Tooltip */}
        {hover && (
          <g>
            <rect x={Math.min(hover.x + 12, W - 240)} y={Math.max(hover.y - 60, 4)} width="220" height="54" fill="var(--wbbq-black)" stroke={hover.color} strokeWidth="2" />
            <text x={Math.min(hover.x + 12, W - 240) + 10} y={Math.max(hover.y - 60, 4) + 18} fontSize="11" fill={hover.color} letterSpacing="0.08em" fontWeight="700">{hover.date} {hover.year}</text>
            <text x={Math.min(hover.x + 12, W - 240) + 10} y={Math.max(hover.y - 60, 4) + 34} fontSize="11" fill="var(--fg1)">{hover.name.slice(0, 30)}</text>
            <text x={Math.min(hover.x + 12, W - 240) + 10} y={Math.max(hover.y - 60, 4) + 48} fontSize="11" fill="var(--fg2)">
              {(window.formatPlace ? window.formatPlace(hover.place) : hover.place) || '—'}{hover.teams ? ` / ${hover.teams}` : ''} · {hover.score}
            </text>
          </g>
        )}
      </svg>
    </div>
  );
};

// ---------- Per-meat sparkline ----------
const MeatSparkline = ({ meat, byYear, years, yearColors, sharedMax }) => {
  const W = 320, H = 170, PAD = { t: 14, r: 10, b: 22, l: 30 };
  const innerW = W - PAD.l - PAD.r;
  const innerH = H - PAD.t - PAD.b;

  // Collect all data points for this meat across all years
  const allPoints = [];
  years.forEach(y => {
    byYear[y].forEach((e, idx) => {
      const r = e.results && e.results[meat];
      if (!r || !r.place) return;
      const place = parseInt(r.place);
      if (!Number.isFinite(place)) return;
      allPoints.push({ year: y, place, score: r.score, eventName: e.name, date: e.date, teams: e.teams });
    });
  });

  const cardStyle = { background: 'var(--wbbq-ink)', border: '1px solid var(--border)', padding: 18 };

  if (allPoints.length === 0) {
    return (
      <div style={cardStyle}>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 18, color: 'var(--wbbq-flame)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>{meat}</div>
        <div style={{ color: 'var(--fg3)', fontSize: 12, fontStyle: 'italic', marginTop: 12 }}>No data yet.</div>
      </div>
    );
  }

  // Y axis: place, inverted (1 at top), shared across cards
  const maxPlace = Math.max(sharedMax || 10, 10);
  const yScale = (v) => ((v - 1) / (maxPlace - 1 || 1)) * innerH;

  // X axis: chronological index across all years
  let globalIdx = 0;
  const yearSegments = years.map(y => {
    const pts = byYear[y].map((e, i) => {
      const r = e.results && e.results[meat];
      if (!r || !r.place) return null;
      const place = parseInt(r.place);
      if (!Number.isFinite(place)) return null;
      const x = globalIdx++;
      return { x, place, e, year: y };
    }).filter(Boolean);
    return { year: y, pts };
  });
  const totalPoints = globalIdx;
  const xScale = (i) => totalPoints > 1 ? (i / (totalPoints - 1)) * innerW : innerW / 2;

  // Headline stats
  const avgPlace = (allPoints.reduce((a, p) => a + p.place, 0) / allPoints.length).toFixed(1);
  const bestPlace = Math.min(...allPoints.map(p => p.place));
  const podiumCount = allPoints.filter(p => p.place <= 3).length;
  const callCount = allPoints.filter(p => p.place <= 10).length;
  const bestStr = window.formatPlace ? window.formatPlace(bestPlace) : String(bestPlace);

  // Identify the latest point (last point in the last active year that has data)
  let latestKey = null;
  for (let i = yearSegments.length - 1; i >= 0 && !latestKey; i--) {
    const seg = yearSegments[i];
    if (seg.pts.length) {
      const last = seg.pts[seg.pts.length - 1];
      latestKey = `${seg.year}:${last.x}`;
    }
  }

  const [hover, setHover] = useStateStats(null);
  const TIP_W = 150, TIP_H = 46;

  // Podium / Call band geometry
  const podiumTop = PAD.t + yScale(1);
  const podiumBot = PAD.t + yScale(Math.min(3, maxPlace));
  const callBot = PAD.t + yScale(Math.min(10, maxPlace));

  return (
    <div style={cardStyle}>
      {/* Header: meat name + promoted stats */}
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 10, marginBottom: 12, flexWrap: 'wrap' }}>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 18, color: 'var(--wbbq-flame)', textTransform: 'uppercase', letterSpacing: '0.1em', lineHeight: 1 }}>{meat}</div>
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 14 }}>
          <div style={{ textAlign: 'right' }}>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 9, letterSpacing: '0.18em', color: 'var(--fg3)', textTransform: 'uppercase' }}>Best</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 22, color: 'var(--wbbq-flame)', lineHeight: 1 }}>{bestStr}</div>
          </div>
          <div style={{ textAlign: 'right' }}>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 9, letterSpacing: '0.18em', color: 'var(--fg3)', textTransform: 'uppercase' }}>Avg</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 16, color: 'var(--fg1)', lineHeight: 1 }}>{avgPlace}</div>
          </div>
          <div style={{ textAlign: 'right' }}>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 9, letterSpacing: '0.18em', color: 'var(--fg3)', textTransform: 'uppercase' }}>Calls</div>
            <div style={{
              display: 'inline-block',
              fontFamily: 'var(--font-display)', fontSize: 14, lineHeight: 1,
              padding: '3px 8px',
              border: `1.5px solid ${callCount > 0 ? 'var(--wbbq-blue)' : 'var(--border)'}`,
              color: callCount > 0 ? 'var(--wbbq-blue)' : 'var(--fg3)',
            }}>{callCount}{podiumCount > 0 ? ` · ${podiumCount}🏆` : ''}</div>
          </div>
        </div>
      </div>

      <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block', overflow: 'visible' }}>
        {/* Call band (top-10) and Podium band (top-3) */}
        {callBot > podiumBot && (
          <rect x={PAD.l} y={podiumBot} width={innerW} height={callBot - podiumBot} fill="var(--wbbq-blue)" opacity="0.08" />
        )}
        <rect x={PAD.l} y={podiumTop} width={innerW} height={Math.max(0, podiumBot - podiumTop)} fill="var(--wbbq-slime)" opacity="0.15" />

        {/* Reference lines: 1st / 2nd / 3rd / 10th so each place reads distinctly */}
        <line x1={PAD.l} y1={PAD.t + yScale(1)} x2={PAD.l + innerW} y2={PAD.t + yScale(1)} stroke="var(--wbbq-flame)" strokeWidth="1.5" opacity="0.85" />
        {maxPlace >= 2 && (
          <line x1={PAD.l} y1={PAD.t + yScale(2)} x2={PAD.l + innerW} y2={PAD.t + yScale(2)} stroke="var(--wbbq-slime)" strokeWidth="1" strokeDasharray="1 4" opacity="0.4" />
        )}
        {maxPlace >= 3 && (
          <line x1={PAD.l} y1={PAD.t + yScale(3)} x2={PAD.l + innerW} y2={PAD.t + yScale(3)} stroke="var(--wbbq-slime)" strokeWidth="1" strokeDasharray="2 3" opacity="0.55" />
        )}
        {maxPlace > 10 && (
          <line x1={PAD.l} y1={callBot} x2={PAD.l + innerW} y2={callBot} stroke="var(--wbbq-blue)" strokeWidth="1" strokeDasharray="3 3" opacity="0.7" />
        )}

        {/* Left-edge place labels — only draw if there's vertical room */}
        {(() => {
          const stepPx = innerH / Math.max(maxPlace - 1, 1);
          const hasRoomFor2 = stepPx >= 14;
          const hasRoomFor3 = stepPx * 2 >= 14;
          const topY = PAD.t + yScale(1);
          return (
            <g>
              <text x={PAD.l - 6} y={topY + 4} textAnchor="end" fontSize="11" fill="var(--wbbq-flame)" fontFamily="var(--font-display)" letterSpacing="0.06em" fontWeight="900">1ST</text>
              {maxPlace >= 2 && hasRoomFor2 && (
                <text x={PAD.l - 6} y={PAD.t + yScale(2) + 3} textAnchor="end" fontSize="9" fill="var(--wbbq-slime)" fontFamily="var(--font-display)" letterSpacing="0.08em" fontWeight="700" opacity="0.75">2</text>
              )}
              {maxPlace >= 3 && hasRoomFor3 && (
                <text x={PAD.l - 6} y={PAD.t + yScale(3) + 3} textAnchor="end" fontSize="9" fill="var(--wbbq-slime)" fontFamily="var(--font-display)" letterSpacing="0.08em" fontWeight="700">3</text>
              )}
            </g>
          );
        })()}
        {maxPlace > 10 && (
          <text x={PAD.l - 6} y={callBot + 3} textAnchor="end" fontSize="9" fill="var(--wbbq-blue)" fontFamily="var(--font-display)" letterSpacing="0.08em" fontWeight="700">10</text>
        )}
        <text x={PAD.l - 6} y={PAD.t + yScale(maxPlace) + 4} textAnchor="end" fontSize="9" fill="var(--fg3)" fontFamily="var(--font-mono)">{maxPlace}</text>

        {/* Connector lines bridging consecutive years (muted, dashed) */}
        {yearSegments.map((seg, i) => {
          if (i === 0 || seg.pts.length === 0) return null;
          const prev = yearSegments[i - 1];
          if (!prev || prev.pts.length === 0) return null;
          const a = prev.pts[prev.pts.length - 1];
          const b = seg.pts[0];
          const ax = PAD.l + xScale(a.x), ay = PAD.t + yScale(a.place);
          const bx = PAD.l + xScale(b.x), by = PAD.t + yScale(b.place);
          return (
            <line key={`bridge-${seg.year}`} x1={ax} y1={ay} x2={bx} y2={by} stroke="var(--fg3)" strokeWidth="1.5" strokeDasharray="4 4" opacity="0.5" />
          );
        })}

        {/* Per-year segments */}
        {yearSegments.map(({ year, pts }) => {
          if (pts.length === 0) return null;
          const mapped = pts.map(p => ({ ...p, x: PAD.l + xScale(p.x), y: PAD.t + yScale(p.place) }));
          const path = mapped.map((p, i) => (i === 0 ? 'M' : 'L') + p.x.toFixed(1) + ' ' + p.y.toFixed(1)).join(' ');
          return (
            <g key={year}>
              <path d={path} fill="none" stroke={yearColors[year]} strokeWidth="2.5" strokeLinejoin="round" />
              {mapped.map((p, i) => {
                const isCall = p.place <= 10;
                const isHover = hover && hover.eventId === p.e.id && hover.year === year;
                const isLatest = `${year}:${pts[i].x}` === latestKey;
                // Size gradient by place: 1st biggest, then 2nd, 3rd, call, rest
                const sizeByPlace = p.place === 1 ? 7 : p.place === 2 ? 5.5 : p.place === 3 ? 4.25 : isCall ? 3 : 2.2;
                const baseR = sizeByPlace;
                const hoverR = sizeByPlace + 1.5;
                const opacity = isCall ? 1 : 0.45;
                return (
                  <g
                    key={i}
                    onMouseEnter={() => setHover({ eventId: p.e.id, year, x: p.x, y: p.y, color: yearColors[year], place: p.place, e: p.e })}
                    onMouseLeave={() => setHover(null)}
                    style={{ cursor: 'pointer' }}
                  >
                    <circle cx={p.x} cy={p.y} r="12" fill="transparent" />
                    {isLatest && (
                      <circle cx={p.x} cy={p.y} r={(isHover ? hoverR : baseR) + 3.5} fill="none" stroke={yearColors[year]} strokeWidth="1.5" opacity="0.9" />
                    )}
                    <circle cx={p.x} cy={p.y} r={isHover ? hoverR : baseR} fill={yearColors[year]} stroke="var(--wbbq-ink)" strokeWidth="1.5" opacity={opacity} />
                  </g>
                );
              })}
            </g>
          );
        })}

        {/* Tooltip */}
        {hover && (() => {
          const tx = Math.min(Math.max(hover.x + 10, PAD.l), W - TIP_W - 2);
          const ty = Math.max(hover.y - TIP_H - 8, 2);
          const placeStr = window.formatPlace ? window.formatPlace(hover.place) : hover.place;
          const teams = hover.e.teams ? ` / ${hover.e.teams}` : '';
          const name = hover.e.name.length > 22 ? hover.e.name.slice(0, 21) + '…' : hover.e.name;
          const mtier = window.tierFor ? window.tierFor(hover.place, 'meat') : null;
          const tailLabel = mtier ? ` · ${mtier.label}` : '';
          return (
            <g style={{ pointerEvents: 'none' }}>
              <rect x={tx} y={ty} width={TIP_W} height={TIP_H} fill="var(--wbbq-black)" stroke={hover.color} strokeWidth="1.5" />
              <text x={tx + 8} y={ty + 14} fontSize="9" fill={hover.color} fontFamily="var(--font-mono)" letterSpacing="0.08em" fontWeight="700">{hover.e.date} {hover.year}</text>
              <text x={tx + 8} y={ty + 27} fontSize="10" fill="var(--fg1)" fontFamily="var(--font-mono)">{name}</text>
              <text x={tx + 8} y={ty + 40} fontSize="10" fill="var(--fg2)" fontFamily="var(--font-mono)">{placeStr}{teams}{tailLabel}</text>
            </g>
          );
        })()}
      </svg>
    </div>
  );
};

window.StatsPage = StatsPage;

// ---------- Skill helpers (field-beaten %) ----------
const monthOrderSkill = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];

const summarizeYearSkill = (evs) => {
  const scores = evs.map(e => parseFloat(e.score)).filter(Number.isFinite);
  const places = evs.map(e => {
    const m = String(e.place || '').match(/^(\d+)/);
    return m ? parseInt(m[1]) : null;
  }).filter(p => p !== null);
  return {
    count: evs.length,
    avgScore: scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : null,
    bestScore: scores.length ? Math.max(...scores) : null,
    avgPlace: places.length ? places.reduce((a, b) => a + b, 0) / places.length : null,
    bestPlace: places.length ? Math.min(...places) : null,
    calls: places.filter(p => p <= 10).length,
    podiums: places.filter(p => p <= 3).length,
    gc: places.filter(p => p === 1).length,
  };
};

const nthSuffixSkill = (n) => {
  const s = ['th','st','nd','rd'];
  const v = n % 100;
  return s[(v - 20) % 10] || s[v] || s[0];
};

const SkillExplainer = () => (
  <div className="reveal" style={{ background: 'var(--wbbq-ink)', border: `1px solid var(--border)`, borderLeft: '4px solid var(--wbbq-slime)', padding: '20px 24px', marginBottom: 24 }}>
    <div style={{ fontFamily: 'var(--font-display)', fontSize: 11, letterSpacing: '0.18em', color: 'var(--wbbq-slime)', textTransform: 'uppercase', marginBottom: 10 }}>
      Read This First · What Is "Field Beaten %"?
    </div>
    <div style={{ fontFamily: 'var(--font-body)', color: 'var(--fg1)', fontSize: 15, lineHeight: 1.5, maxWidth: 780 }}>
      Place alone doesn't tell the story. <span style={{ color: 'var(--wbbq-flame)', fontWeight: 600 }}>A 5th in a 50-team comp is better than 2nd in a 5-team comp</span> — the first beat 45 teams, the second beat 3. "Field beaten %" normalizes across comp sizes so a strong result in a stacked field looks as good as it actually was.
    </div>
    <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)', marginTop: 10, letterSpacing: '0.04em' }}>
      Formula: <span style={{ color: 'var(--fg2)' }}>(teams beaten) ÷ (teams − 1)</span>. 1st always = 100%, last always = 0%, everything else scales in between.
    </div>
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: 14, marginTop: 18, maxWidth: 780 }}>
      {[
        { comp: 'Backyard Bash', place: 2, teams: 5, color: 'var(--wbbq-blue)' },
        { comp: 'State Championship', place: 5, teams: 50, color: 'var(--wbbq-slime)' },
      ].map(ex => {
        const beaten = ex.teams - ex.place;
        const pct = ex.teams > 1 ? Math.round((beaten / (ex.teams - 1)) * 100) : 100;
        return (
          <div key={ex.comp} style={{ background: 'var(--wbbq-black)', border: `1px solid var(--border)`, padding: '14px 16px' }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)', letterSpacing: '0.1em', textTransform: 'uppercase' }}>{ex.comp}</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 15, color: 'var(--fg1)', marginTop: 6 }}>
              {ex.place}{nthSuffixSkill(ex.place)} of {ex.teams} teams
            </div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginTop: 12 }}>
              <div style={{ fontFamily: 'var(--font-display)', fontSize: 38, color: ex.color, lineHeight: 1, fontWeight: 700 }}>{pct}%</div>
              <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)' }}>field beaten<br/>({beaten} of {ex.teams - 1} teams below us)</div>
            </div>
            <div style={{ height: 5, background: 'var(--border)', marginTop: 10, position: 'relative' }}>
              <div style={{ position: 'absolute', inset: 0, width: `${pct}%`, background: ex.color }} />
            </div>
          </div>
        );
      })}
    </div>
    <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg3)', marginTop: 14 }}>
      Higher % = stronger result against the field. 100% = 1st place (beat everyone below), 0% = last.
    </div>
  </div>
);

const SkillYearStrip = ({ year, evs, color }) => {
  const skillPts = evs.map(e => {
    const teams = parseInt(e.teams) || null;
    const m = String(e.place || '').match(/^(\d+)/);
    const place = m ? parseInt(m[1]) : null;
    if (!teams || !place || teams < place) return null;
    return { e, pct: teams > 1 ? (teams - place) / (teams - 1) : 1, teams, place };
  }).filter(Boolean);

  const scorePts = evs.map(e => ({ e, s: parseFloat(e.score) })).filter(p => Number.isFinite(p.s));
  const s = summarizeYearSkill(evs);
  const avgSkill = skillPts.length ? skillPts.reduce((a, p) => a + p.pct, 0) / skillPts.length : null;

  const W = 440, H = 140, PAD = { t: 12, r: 12, b: 22, l: 36 };
  const innerW = W - PAD.l - PAD.r;
  const innerH = H - PAD.t - PAD.b;
  const xScaleMo = (e) => {
    const m = monthOrderSkill.indexOf((e.date || '').slice(0, 3).toUpperCase());
    const d = parseInt((e.date || '').slice(4)) || 0;
    const frac = (m >= 0 ? m : 0) + (d / 31);
    return (frac / 12) * innerW;
  };

  const scoreMin = scorePts.length ? Math.floor(Math.min(...scorePts.map(p => p.s)) / 10) * 10 - 5 : 600;
  const scoreMax = scorePts.length ? Math.ceil(Math.max(...scorePts.map(p => p.s)) / 10) * 10 + 5 : 700;
  const yScore = (v) => innerH - ((v - scoreMin) / (scoreMax - scoreMin || 1)) * innerH;
  const scorePath = scorePts.map((p, i) => (i === 0 ? 'M' : 'L') + (PAD.l + xScaleMo(p.e)).toFixed(1) + ' ' + (PAD.t + yScore(p.s)).toFixed(1)).join(' ');

  const yPct = (v) => innerH - v * innerH;
  const pctPath = skillPts.map((p, i) => (i === 0 ? 'M' : 'L') + (PAD.l + xScaleMo(p.e)).toFixed(1) + ' ' + (PAD.t + yPct(p.pct)).toFixed(1)).join(' ');

  return (
    <div className="reveal skill-strip" style={{ background: 'var(--wbbq-ink)', border: '1px solid var(--border)', borderLeft: `4px solid ${color}`, display: 'grid', gridTemplateColumns: '130px 1fr 1fr', gap: 0 }}>
      <div style={{ padding: '16px 18px', borderRight: '1px dashed var(--border)' }}>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 36, color, lineHeight: 1 }}>{year}</div>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg3)', letterSpacing: '0.1em', textTransform: 'uppercase', marginTop: 4 }}>{s.count} comps</div>
        <div style={{ marginTop: 14, fontFamily: 'var(--font-display)', fontSize: 28, color: 'var(--wbbq-bone)', lineHeight: 1 }}>
          {avgSkill != null ? `${Math.round(avgSkill * 100)}%` : '—'}
        </div>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--fg3)', letterSpacing: '0.1em', textTransform: 'uppercase', marginTop: 2 }}>avg field beaten</div>
        <div style={{ display: 'flex', gap: 12, marginTop: 14, fontFamily: 'var(--font-mono)', fontSize: 11 }}>
          <span style={{ color: 'var(--wbbq-blue)' }}>{s.calls} call</span>
          <span style={{ color: 'var(--wbbq-slime)' }}>{s.podiums} pod</span>
          {s.gc > 0 && <span style={{ color: 'var(--wbbq-flame)' }}>{s.gc} GC</span>}
        </div>
      </div>

      <div style={{ padding: 6, borderRight: '1px dashed var(--border)' }}>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--fg3)', letterSpacing: '0.14em', textTransform: 'uppercase', padding: '4px 8px 0' }}>Raw Score</div>
        <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block' }}>
          <text x={PAD.l - 6} y={PAD.t + yScore(scoreMax) + 4} textAnchor="end" fontSize="9" fill="var(--fg3)" fontFamily="var(--font-mono)">{scoreMax}</text>
          <text x={PAD.l - 6} y={PAD.t + yScore(scoreMin) + 4} textAnchor="end" fontSize="9" fill="var(--fg3)" fontFamily="var(--font-mono)">{scoreMin}</text>
          {monthOrderSkill.map((m, i) => (
            <text key={m} x={PAD.l + (i / 12) * innerW + (innerW / 12) / 2} y={H - 6} textAnchor="middle" fontSize="8" fill="var(--fg3)" letterSpacing="0.1em" opacity="0.5">{m}</text>
          ))}
          {scorePath && <path d={scorePath} fill="none" stroke={color} strokeWidth="2" strokeLinejoin="round" opacity="0.55" />}
          {scorePts.map((p, i) => (
            <circle key={i} cx={PAD.l + xScaleMo(p.e)} cy={PAD.t + yScore(p.s)} r="3" fill={color} opacity="0.7" />
          ))}
        </svg>
      </div>

      <div style={{ padding: 6 }}>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--wbbq-slime)', letterSpacing: '0.14em', textTransform: 'uppercase', padding: '4px 8px 0' }}>Field Beaten %</div>
        <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block' }}>
          <rect x={PAD.l} y={PAD.t + yPct(1)} width={innerW} height={(PAD.t + yPct(0.9)) - (PAD.t + yPct(1))} fill="var(--wbbq-slime)" opacity="0.15" />
          <rect x={PAD.l} y={PAD.t + yPct(0.9)} width={innerW} height={(PAD.t + yPct(0.75)) - (PAD.t + yPct(0.9))} fill="var(--wbbq-blue)" opacity="0.08" />
          {[0, 0.5, 1].map(v => (
            <g key={v}>
              <line x1={PAD.l} y1={PAD.t + yPct(v)} x2={PAD.l + innerW} y2={PAD.t + yPct(v)} stroke="var(--border)" strokeWidth="1" strokeDasharray="2 4" />
              <text x={PAD.l - 6} y={PAD.t + yPct(v) + 4} textAnchor="end" fontSize="9" fill="var(--fg3)" fontFamily="var(--font-mono)">{(v * 100).toFixed(0)}</text>
            </g>
          ))}
          {avgSkill != null && (
            <line x1={PAD.l} y1={PAD.t + yPct(avgSkill)} x2={PAD.l + innerW} y2={PAD.t + yPct(avgSkill)} stroke={color} strokeWidth="1" strokeDasharray="3 3" opacity="0.45" />
          )}
          {monthOrderSkill.map((m, i) => (
            <text key={m} x={PAD.l + (i / 12) * innerW + (innerW / 12) / 2} y={H - 6} textAnchor="middle" fontSize="8" fill="var(--fg3)" letterSpacing="0.1em" opacity="0.5">{m}</text>
          ))}
          {pctPath && <path d={pctPath} fill="none" stroke={color} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" />}
          {skillPts.map((p, i) => (
            <circle key={i} cx={PAD.l + xScaleMo(p.e)} cy={PAD.t + yPct(p.pct)} r="4" fill={color} stroke="var(--wbbq-ink)" strokeWidth="1.5" />
          ))}
        </svg>
      </div>
    </div>
  );
};

window.SkillExplainer = SkillExplainer;
window.SkillYearStrip = SkillYearStrip;
