/* =============================================================================
   SheetHead — Statistics screen
   Reads aggregates from the stats store (src/stats.js): it never loads raw game history into React. Charts are hand-drawn SVG
   (no chart library) driven by pointer events, so hover and touch scrubbing share one code path. Exposes window.SH.ui.StatisticsScreen.
============================================================================= */
(function () {
  const { useState, useEffect, useMemo, useRef, useCallback } = React;
  const S = window.SH.stats;
  const PR = window.SH.presenter;
  const { cx, Button, Frame, Pip, Icon } = window.SH.ui;

  const RANGES = [['m1', 'Last month'], ['m2', 'Last 2 months'], ['m6', 'Last 6 months'], ['y1', 'Last year'], ['all', 'All time']];
  const TYPES = [['all', 'All games'], ['solo', 'Solo'], ['online', 'Online'], ['friends', 'Friends'], ['tournament', 'Tournaments']];
  const CAT_LABEL = { solo: 'Solo', online: 'Online', friends: 'Friends', tournament: 'Tournament' };
  const RANK_ORDER = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
  const filterKey = () => 'sheethead:stats:filters:' + S.owner(), seenKey = () => 'sheethead:stats:seen:' + S.owner();
  const MIN_TREND_GAMES = 5, MIN_TREND_POINTS = 3, MIN_PLACES = 3, MIN_FLIPS = 3;

  // ---- formatting -------------------------------------------------------------------------------------------------------
  const pct = (v) => (v == null ? '—' : Math.round(v * 100) + '%');
  const num = (v) => (v == null ? '—' : Number(v).toLocaleString('en-US'));
  const dec = (v, d) => (v == null ? '—' : Number(v).toFixed(d == null ? 1 : d));
  function dur(ms) {
    if (ms == null) return '—';
    const s = Math.round(ms / 1000), m = Math.floor(s / 60), h = Math.floor(m / 60);
    return h ? h + 'h ' + String(m % 60).padStart(2, '0') + 'm' : m ? m + 'm ' + String(s % 60).padStart(2, '0') + 's' : s + 's';
  }
  const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  function bucketLabel(key, gran) {
    if (gran === 'month') { const [y, m] = key.split('-'); return MON[+m - 1] + " '" + y.slice(2); }
    const [, m, d] = key.split('-'); return MON[+m - 1] + ' ' + (+d);
  }
  const rankName = (r) => (r === 'JOKER' ? 'Joker' : r);

  // ---- small hooks -------------------------------------------------------------------------------------------------------
  function useWidth(ref) {
    const [w, setW] = useState(0);
    useEffect(() => {
      const el = ref.current; if (!el) return undefined;
      const read = () => setW(Math.round(el.clientWidth));
      read();
      if (typeof ResizeObserver === 'undefined') { window.addEventListener('resize', read); return () => window.removeEventListener('resize', read); }
      const ro = new ResizeObserver(read); ro.observe(el); return () => ro.disconnect();
    }, []);
    return w;
  }
  // Count-up for headline numbers; jumps straight to the value under reduced motion.
  function useCount(target, rm) {
    const [v, setV] = useState(target == null ? 0 : target), from = useRef(target == null ? 0 : target);
    useEffect(() => {
      if (target == null) return undefined;
      if (rm) { setV(target); from.current = target; return undefined; }
      const a = from.current, b = target, t0 = performance.now(), D = 650; let raf = 0;
      const step = (t) => { const k = Math.min(1, (t - t0) / D), e = 1 - Math.pow(1 - k, 3); setV(a + (b - a) * e); if (k < 1) raf = requestAnimationFrame(step); else from.current = b; };
      raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf);
    }, [target, rm]);
    return target == null ? null : v;
  }
  function Count({ value, fmt, rm }) { const v = useCount(value, rm); return <span>{v == null ? '—' : fmt ? fmt(v) : Math.round(v).toLocaleString('en-US')}</span>; }

  // ---- chart primitives ----------------------------------------------------------------------------------------------------
  // Tooltip shared by every chart. `tip` = { x, y, lines: [..] } in container pixels.
  function Tip({ tip, w }) {
    if (!tip) return null;
    const left = Math.max(8, Math.min(w - 8, tip.x));
    return <div className="ch-tip" role="status" style={{ left, top: tip.y }} data-testid="chart-tip">{tip.lines.map((l, i) => <span key={i} className={i === 0 ? 'h' : undefined}>{l}</span>)}</div>;
  }
  function useScrub(w, n, pad, setHi) {
    return useMemo(() => {
      const pick = (e) => {
        const r = e.currentTarget.getBoundingClientRect(), px = e.clientX - r.left, iw = Math.max(1, w - pad.l - pad.r);
        setHi(Math.max(0, Math.min(n - 1, Math.round(((px - pad.l) / iw) * Math.max(1, n - 1)))));
      };
      return { onPointerDown: pick, onPointerMove: pick, onPointerLeave: (e) => { if (e.pointerType === 'mouse') setHi(null); } };
    }, [w, n]);
  }

  // Line + area over categorical points (value 0..1). tipFor(i) -> lines.
  function AreaChart({ data, tipFor, height, rm }) {
    const ref = useRef(null), w = useWidth(ref), [hi, setHi] = useState(null), H = height || 200;
    const pad = { l: 38, r: 14, t: 14, b: 28 }, iw = Math.max(10, w - pad.l - pad.r), ih = H - pad.t - pad.b, n = data.length;
    const X = (i) => pad.l + (n === 1 ? iw / 2 : (i * iw) / (n - 1)), Y = (v) => pad.t + ih * (1 - v);
    const scrub = useScrub(w, n, pad, setHi);
    let d = '';
    data.forEach((p, i) => { const x = X(i), y = Y(p.value); if (!i) d = 'M' + x + ' ' + y; else { const px = X(i - 1), py = Y(data[i - 1].value), mx = (px + x) / 2; d += ' C' + mx + ' ' + py + ',' + mx + ' ' + y + ',' + x + ' ' + y; } });
    const area = d ? d + ' L' + X(n - 1) + ' ' + Y(0) + ' L' + X(0) + ' ' + Y(0) + ' Z' : '';
    const ticks = []; const step = Math.max(1, Math.ceil(n / Math.max(2, Math.floor(iw / 78)))); for (let i = 0; i < n; i += step) ticks.push(i);
    return (
      <div className={cx('ch', rm && 'rm')} ref={ref} style={{ height: H }}>
        {w > 0 && (
          <svg width={w} height={H} role="img" aria-label="Win rate over time" style={{ touchAction: 'pan-y' }} {...scrub}>
            <defs><linearGradient id="ag" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#b81e2d" stopOpacity=".34" /><stop offset="1" stopColor="#b81e2d" stopOpacity="0" /></linearGradient></defs>
            {[0, 0.5, 1].map(v => <g key={v}><line x1={pad.l} x2={w - pad.r} y1={Y(v)} y2={Y(v)} className="gl" /><text x={pad.l - 8} y={Y(v) + 4} textAnchor="end" className="ax">{Math.round(v * 100)}%</text></g>)}
            {ticks.map(i => <text key={i} x={X(i)} y={H - 8} textAnchor="middle" className="ax">{data[i].label}</text>)}
            <path d={area} fill="url(#ag)" className="area" />
            <path d={d} fill="none" pathLength="1" className="line" />
            {data.map((p, i) => <circle key={i} cx={X(i)} cy={Y(p.value)} r={hi === i ? 6 : 3 + Math.min(3, Math.sqrt(p.n) * 0.6)} className={cx('dot', hi === i && 'on')} />)}
            {hi != null && <line x1={X(hi)} x2={X(hi)} y1={pad.t} y2={pad.t + ih} className="cursor" />}
          </svg>
        )}
        <Tip w={w} tip={hi != null && w ? { x: X(hi), y: Math.max(0, Y(data[hi].value) - 62), lines: tipFor(hi) } : null} />
      </div>
    );
  }

  function BarChart({ data, tipFor, height, color, rm, label }) {
    const ref = useRef(null), w = useWidth(ref), [hi, setHi] = useState(null), H = height || 190;
    const pad = { l: 30, r: 10, t: 12, b: 28 }, iw = Math.max(10, w - pad.l - pad.r), ih = H - pad.t - pad.b, n = data.length;
    const max = Math.max(1, ...data.map(x => x.value)), nice = max <= 4 ? max : Math.ceil(max / 4) * 4, slot = iw / Math.max(1, n), bw = Math.max(3, Math.min(34, slot * 0.68));
    const scrub = useScrub(w, n, { l: pad.l + slot / 2 - 0, r: pad.r + slot / 2 }, setHi);
    const step = Math.max(1, Math.ceil(n / Math.max(2, Math.floor(iw / 64))));
    return (
      <div className={cx('ch', rm && 'rm')} ref={ref} style={{ height: H }}>
        {w > 0 && (
          <svg width={w} height={H} role="img" aria-label={label} style={{ touchAction: 'pan-y' }} {...scrub}>
            {[0, 0.5, 1].map(v => <g key={v}><line x1={pad.l} x2={w - pad.r} y1={pad.t + ih * (1 - v)} y2={pad.t + ih * (1 - v)} className="gl" /><text x={pad.l - 6} y={pad.t + ih * (1 - v) + 4} textAnchor="end" className="ax">{Math.round(nice * v)}</text></g>)}
            {data.map((p, i) => {
              const h = (p.value / nice) * ih, x = pad.l + slot * i + (slot - bw) / 2;
              return <g key={p.key}><rect x={x} y={pad.t + ih - h} width={bw} height={Math.max(p.value ? 2 : 0, h)} rx={Math.min(4, bw / 3)} className={cx('bar', hi === i && 'on')} fill={p.color || color || '#b81e2d'} style={{ animationDelay: Math.min(500, i * 14) + 'ms' }} />
                {i % step === 0 && <text x={x + bw / 2} y={H - 8} textAnchor="middle" className="ax">{p.label}</text>}</g>;
            })}
          </svg>
        )}
        <Tip w={w} tip={hi != null && w ? { x: pad.l + slot * hi + slot / 2, y: Math.max(0, pad.t + ih - (data[hi].value / nice) * ih - 60), lines: tipFor(hi) } : null} />
      </div>
    );
  }

  // Horizontal proportional bars with a tooltip per row (special-card activity, game types).
  function HBars({ rows, empty, rm }) {
    const [hi, setHi] = useState(null), max = Math.max(1, ...rows.map(r => r.value));
    return (
      <ul className={cx('hb', rm && 'rm')} onPointerLeave={(e) => { if (e.pointerType === 'mouse') setHi(null); }}>
        {rows.map((r, i) => (
          <li key={r.key} className={cx(hi === i && 'on', r.dim && 'dim')} onPointerDown={() => setHi(hi === i ? null : i)} onPointerEnter={(e) => { if (e.pointerType === 'mouse') setHi(i); }} data-key={r.key}>
            <span className="hb-l">{r.icon}{r.label}</span>
            <span className="hb-t"><i style={{ width: (r.value / max) * 100 + '%', background: r.color || '#b81e2d', animationDelay: i * 60 + 'ms' }} /></span>
            <b className="hb-v">{r.text != null ? r.text : num(r.value)}</b>
            {hi === i && r.tip && <span className="hb-tip" role="status">{r.tip}</span>}
          </li>
        ))}
        {!rows.length && <li className="hb-empty">{empty}</li>}
      </ul>
    );
  }

  function Ring({ value, rm, size }) {
    const R = 46, C = 2 * Math.PI * R, v = value == null ? 0 : value;
    return (
      <svg className={cx('st-ring', rm && 'rm')} viewBox="0 0 120 120" width={size || 132} height={size || 132} role="img" aria-label={'Win rate ' + pct(value)}>
        <circle cx="60" cy="60" r={R} className="ring-bg" /><circle cx="60" cy="60" r={R} className="ring-fg" strokeDasharray={C} strokeDashoffset={C * (1 - v)} transform="rotate(-90 60 60)" style={{ '--c': C }} />
      </svg>
    );
  }

  const Sheet = ({ title, sub, children, className, right, id }) => (
    <section className={cx('sheet st-sheet', className)} aria-labelledby={id}>
      <header className="st-sh"><div><h2 id={id} className="st-h">{title}</h2>{sub && <p className="st-sub">{sub}</p>}</div>{right}</header>
      {children}
    </section>
  );
  const Need = ({ children }) => <div className="st-need" data-testid="needs-more"><Pip suit="S" size={16} /><span>{children}</span></div>;
  function Tile({ label, value, hint, wide, need, testid }) {
    return <div className={cx('tile', wide && 'wide', need && 'need')} data-testid={testid}><span className="tl">{label}</span><b className="tv">{need ? '—' : value}</b><span className="th">{need || hint || ' '}</span></div>;
  }

  // ---- empty state -----------------------------------------------------------------------------------------------------------
  function Empty({ onPlay }) {
    return (
      <section className="st-empty sheet" data-testid="stats-empty" aria-labelledby="st-empty-h">
        <div className="se-cards" aria-hidden="true"><i>A</i><i>K</i><i>★</i></div>
        <h2 id="st-empty-h" className="se-h">Your record starts at the first deal</h2>
        <p className="se-p">Every game you finish is written down here: wins, streaks, burns, Jokers, tournaments and how often you dodge the poop hat. Play a game and this page fills itself in.</p>
        <div className="se-chips" aria-hidden="true"><span>Win rate</span><span>Finishing places</span><span>Burns &amp; Jokers</span><span>Tournaments</span></div>
        <Button variant="primary" onClick={onPlay} icon={<Icon name="play" size={16} />}>Play a game</Button>
      </section>
    );
  }

  // ---- milestones ---------------------------------------------------------------------------------------------------------------
  function Milestones({ items, fresh, rm }) {
    return (
      <div className="ms-grid" data-testid="milestones">
        {items.map(m => {
          const isNew = fresh.includes(m.id), nxt = m.next, frac = nxt ? Math.max(0, Math.min(1, m.value / nxt)) : 1;
          return (
            <article key={m.id} className={cx('ms', m.top > 0 && 'got', isNew && 'new', rm && 'rm')} data-milestone={m.id} data-top={m.top}>
              <div className="ms-badge" aria-hidden="true">{m.icon}{isNew && <i className="ms-spark" />}</div>
              <div className="ms-body">
                <h3>{m.label}</h3>
                <div className="ms-val"><b>{num(m.value)}</b>{nxt ? <span> / {num(nxt)}</span> : <span> maxed</span>}</div>
                <div className="ms-bar" aria-hidden="true"><i style={{ width: frac * 100 + '%' }} /></div>
                <div className="ms-steps">{m.steps.map(s => <span key={s} className={cx(m.reached.includes(s) && 'on')} title={s + ' ' + m.label}>{s}</span>)}</div>
              </div>
            </article>
          );
        })}
      </div>
    );
  }

  // ---- Friends (Cloud Stats, Phase 7) --------------------------------------------------------------------------------------------
  // Verified stats for real Friends multiplayer games, computed server-side from immutable trusted results (never from anything
  // local). Deliberately its OWN fetch/state, entirely separate from `store`/`agg`/`sum` above: it must never be merged with, or
  // computed from, local/SOLO data. One round-trip per screen visit (no realtime, no polling, no local cache) -- switching tabs
  // away and back re-fetches, which is exactly how a just-finished game's result becomes visible here.
  function CloudStatsPanel({ auth, user, rm }) {
    const guest = !auth || (user && user.provider === 'guest');
    const [state, setState] = useState(guest ? 'guest' : 'loading');
    const [data, setData] = useState(null);
    const load = useCallback(() => {
      if (guest) { setState('guest'); return; }
      setState('loading');
      auth.cloudStats().then((d) => { setData(d); setState('ready'); }).catch(() => setState('error'));
    }, [guest, auth]);
    useEffect(() => { load(); }, [load]);

    if (guest) return (
      <section className="st-empty sheet" data-testid="cloud-stats-guest">
        <h2 className="se-h">Friends stats need an account</h2>
        <p className="se-p">Sign in to see your verified record from Friends multiplayer games.</p>
      </section>
    );
    if (state === 'loading') return (
      <section className="st-empty sheet" data-testid="cloud-stats-loading" aria-busy="true">
        <span className="spin" aria-hidden="true" />
        <p className="se-p">Loading your Friends stats…</p>
      </section>
    );
    if (state === 'error') return (
      <section className="st-empty sheet" data-testid="cloud-stats-error" role="alert">
        <h2 className="se-h">Couldn't load your Friends stats</h2>
        <p className="se-p">Check your connection and try again.</p>
        <Button variant="ghost-dark" onClick={load} data-testid="cloud-stats-retry">Try again</Button>
      </section>
    );
    const n = data.normal, tr = data.tournament;
    if (n.gamesPlayed === 0 && tr.played === 0) return (
      <section className="st-empty sheet" data-testid="cloud-stats-empty">
        <h2 className="se-h">No Friends games yet</h2>
        <p className="se-p">Play a Friends multiplayer game with other people and your verified record will show up here.</p>
      </section>
    );
    return (
      <div className={cx('st-page', !rm && 'enter')} data-testid="cloud-stats-ready">
        <Sheet id="cs-normal" title="Friends games" sub="Verified results from your real Friends multiplayer games">
          <div className="tiles" data-testid="cs-normal-tiles">
            <Tile testid="cs-games" label="Games played" value={num(n.gamesPlayed)} />
            <Tile testid="cs-wins" label="Wins" value={num(n.wins)} />
            <Tile testid="cs-winrate" label="Win rate" value={pct(n.winRate)} />
            <Tile testid="cs-avgplace" label="Average placement" value={dec(n.avgPlacement, 2)} />
            <Tile testid="cs-p1" label="1st place" value={num(n.placementCounts['1'])} />
            <Tile testid="cs-p2" label="2nd place" value={num(n.placementCounts['2'])} />
            <Tile testid="cs-p3" label="3rd place" value={num(n.placementCounts['3'])} />
            <Tile testid="cs-sheethead" label="SheetHead" value={num(n.sheetHeadCount)} />
          </div>
        </Sheet>
        <Sheet id="cs-tournament" title="Friends tournaments" sub="Overall Tournament results and individual round outcomes">
          <div className="tiles" data-testid="cs-tournament-tiles">
            <Tile testid="cs-t-played" label="Tournaments played" value={num(tr.played)} />
            <Tile testid="cs-t-won" label="Tournaments won" value={num(tr.won)} />
            <Tile testid="cs-t-winrate" label="Tournament win rate" value={pct(tr.winRate)} />
            <Tile testid="cs-t-roundsplayed" label="Rounds played" value={num(tr.roundsPlayed)} />
            <Tile testid="cs-t-roundswon" label="Rounds won" value={num(tr.roundsWon)} />
            <Tile testid="cs-t-roundwinrate" label="Round win rate" value={pct(tr.roundWinRate)} />
            <Tile testid="cs-t-p1" label="1st place finishes" value={num(tr.finalPlacementCounts['1'])} />
            <Tile testid="cs-t-p2" label="2nd place finishes" value={num(tr.finalPlacementCounts['2'])} />
            <Tile testid="cs-t-p3" label="3rd place finishes" value={num(tr.finalPlacementCounts['3'])} />
            <Tile testid="cs-t-p4" label="4th place finishes" value={num(tr.finalPlacementCounts['4'])} />
          </div>
        </Sheet>
      </div>
    );
  }

  // ---- the screen ---------------------------------------------------------------------------------------------------------------
  function loadJSON(k, d) { try { return JSON.parse(localStorage.getItem(k)) || d; } catch (e) { return d; } }
  function StatisticsScreen({ back, settings, setScreen, statsStore, statsNow, persist, auth, user }) {
    const rm = !!(settings && (settings.reducedMotion || settings.animations === false));
    const store = statsStore || S.local(), now = useMemo(() => statsNow || Date.now(), [statsNow]);
    const saved = useMemo(() => (persist === false ? {} : loadJSON(filterKey(), {})), []);
    const [rid, setRid] = useState(RANGES.some(r => r[0] === saved.rid) ? saved.rid : 'all');
    const [type, setType] = useState(TYPES.some(t => t[0] === saved.type) ? saved.type : 'all');
    const [tab, setTab] = useState('overview');
    useEffect(() => { if (persist !== false) { try { localStorage.setItem(filterKey(), JSON.stringify({ rid, type })); } catch (e) { /* ignore */ } } }, [rid, type]);

    const range = useMemo(() => S.rangeFor(rid, now), [rid, now]);
    // aggregates only: rollups for closed months, raw records for the current / boundary months (see stats.js)
    const life = useMemo(() => store.query({ from: null, to: now }, 'all', now), [store, now]);
    const agg = useMemo(() => store.query(range, type, now), [store, range, type, now]);
    const aggAll = useMemo(() => (type === 'all' ? agg : store.query(range, 'all', now)), [store, range, type, now, agg]);
    const sum = useMemo(() => S.summarize(agg), [agg]);
    const lifeSum = useMemo(() => S.summarize(life), [life]);
    const tourList = useMemo(() => store.tournaments(range), [store, range, now]);
    const tour = useMemo(() => S.tournamentStats(tourList), [tourList]);
    const tourGames = useMemo(() => S.summarize(store.query(range, 'tournament', now)), [store, range, now]);
    const tourLife = useMemo(() => S.tournamentStats(store.tournaments({ from: null, to: now })), [store, now]);
    const gran = S.granularityFor(rid), trend = useMemo(() => S.series(agg, gran), [agg, gran]);
    const ms = useMemo(() => S.milestones(lifeSum, tourLife), [lifeSum, tourLife]);

    // milestones reached since the last visit are celebrated once
    const [fresh, setFresh] = useState([]);
    useEffect(() => {
      if (persist === false || !life.games) return undefined;
      const seen = loadJSON(seenKey(), null), first = seen === null, cur = {}; ms.forEach(m => { cur[m.id] = m.top; });
      const nw = first ? [] : ms.filter(m => m.top > (seen[m.id] || 0)).map(m => m.id);
      setFresh(nw);
      try { localStorage.setItem(seenKey(), JSON.stringify(cur)); } catch (e) { /* ignore */ }
      return undefined;
    }, [life.games]);

    const goPlay = () => { if (setScreen) setScreen('lobby'); };
    // Named distinctly from the module-scope filterKey() above (the localStorage persistence key function): a
    // same-named local here used to shadow it for the whole component (var-hoisted under this project's Babel
    // preset-env transpilation target, so the SHADOWED STRING value -- not the outer function -- was in scope by
    // the time the `saved`/persistence useEffect above actually ran, crashing with "filterKey is not a function"
    // on every real render of this screen).
    const pageKey = rid + '|' + type;
    const noneEver = life.games === 0 && tourLife.played === 0;
    const rangeLabel = RANGES.find(r => r[0] === rid)[1], typeLabel = TYPES.find(t => t[0] === type)[1];
    const emptyFiltered = !noneEver && agg.games === 0;

    const trendPoints = trend.map(b => ({ key: b.key, value: b.rate || 0, n: b.g, label: bucketLabel(b.key, gran), b }));
    const trendOk = agg.games >= MIN_TREND_GAMES && trend.length >= MIN_TREND_POINTS;
    const placeTotal = sum.normalGames, placeSegs = [['1st', sum.firsts, 'p1', 'Winner'], ['2nd', sum.seconds, 'p2', 'Second out'], ['3rd', sum.thirds, 'p3', 'Third out'], ['SheetHead', sum.sheetHeads, 'p4', 'Last one holding cards']];
    const catRows = ['solo', 'online', 'friends', 'tournament'].map(k => { const c = aggAll.cat[k] || { games: 0, wins: 0 }; return { key: k, label: CAT_LABEL[k], value: c.games ? c.wins / c.games : 0, text: c.games ? pct(c.wins / c.games) : '—', dim: !c.games, color: k === 'tournament' ? '#e3b75a' : '#b81e2d', tip: c.games ? c.wins + ' wins in ' + c.games + ' games' : 'No games yet' }; });
    const special = [
      ['Jokers played', sum.jokers, '#7b4bff'], ['10 burns', sum.tenBurns, '#e3b75a'], ['Four-of-a-kind burns', sum.quadBurns, '#b81e2d'], ['Sticks', sum.sticks, '#19a58e'],
      ['8 skips', sum.skips, '#c2661e'], ['4 bursts', sum.bursts, '#8c1521'], ['Interrupts', sum.interrupts, '#5a3416'], ['Extra turns', sum.extraTurns, '#43322a']
    ].map(([label, value, color]) => ({ key: label, label, value, color }));
    const rankBars = RANK_ORDER.concat(['JOKER']).map(r => ({ key: r, label: r === 'JOKER' ? '★' : r, value: r === 'JOKER' ? sum.jokers : (sum.ranks[r] || 0), color: r === 'JOKER' ? '#7b4bff' : ['2', '3', '4', '7', '8', '10', 'Q'].includes(r) ? '#e3b75a' : '#b81e2d', name: rankName(r) }));

    const Filters = (
      <div className="st-filters" data-testid="stats-filters">
        <div className="st-row" role="group" aria-label="Time period">
          {RANGES.map(([id, label]) => <button key={id} type="button" className={cx('chip', rid === id && 'on')} aria-pressed={rid === id} data-testid={'range-' + id} onClick={() => setRid(id)}>{label}</button>)}
        </div>
        {tab === 'overview' && (
          <div className="st-row" role="group" aria-label="Game type">
            {TYPES.map(([id, label]) => <button key={id} type="button" className={cx('chip alt', type === id && 'on')} aria-pressed={type === id} data-testid={'type-' + id} onClick={() => setType(id)}>{label}</button>)}
          </div>
        )}
        <div className="st-now" aria-live="polite" data-testid="filter-summary">{tab === 'overview' ? rangeLabel + ' · ' + typeLabel : rangeLabel}<span>{tab === 'overview' ? num(agg.games) + ' games' : num(tour.played) + ' tournaments'}</span></div>
      </div>
    );

    return (
      <Frame title="Statistics" kicker="Your record" onBack={back} wide>
        <div className="stats" data-testid="stats-screen" data-rid={rid} data-type={type}>
          <div className="st-tabs" role="tablist" aria-label="Statistics sections">
            {[['overview', 'Overview'], ['tournaments', 'Tournaments'], ['milestones', 'Milestones'], ['friends', 'Friends']].map(([id, label]) => (
              <button key={id} role="tab" type="button" aria-selected={tab === id} className={cx('st-tab', tab === id && 'on')} data-testid={'tab-' + id} onClick={() => setTab(id)}>{label}{id === 'milestones' && fresh.length > 0 && <i className="st-dot" aria-label="new" />}</button>
            ))}
          </div>
          {/* "Friends" is real server-verified multiplayer data (Phase 7 Cloud Stats) -- a completely separate fetch
              from everything else on this screen, which is all local/SOLO and driven by `store` (stats.js). It must
              never be merged into or computed from the local aggregates above, and renders even when the local
              history is empty (noneEver), since a player can have Friends results with zero local/SOLO games. */}
          {tab === 'friends' ? <CloudStatsPanel auth={auth} user={user} rm={rm} /> : noneEver ? <Empty onPlay={goPlay} /> : (
            <>
              {tab !== 'milestones' && Filters}

              {tab === 'overview' && (
                <div key={pageKey} className={cx('st-page', !rm && 'enter')}>
                  {emptyFiltered ? (
                    <section className="sheet st-none" data-testid="stats-none">
                      <h2 className="st-h">No games here yet</h2>
                      <p className="st-sub">Nothing recorded for <b>{rangeLabel}</b> in <b>{typeLabel}</b>. {life.games ? 'You have ' + num(life.games) + ' games in total.' : ''}</p>
                      <Button variant="ghost-dark" onClick={() => { setRid('all'); setType('all'); }}>Reset filters</Button>
                    </section>
                  ) : (
                    <>
                      <section className="st-hero" aria-label="Summary" data-testid="stats-hero">
                        <div className="hero-ring"><Ring value={sum.winRate} rm={rm} /><div className="hr-c"><b data-testid="stat-winrate"><Count value={sum.winRate == null ? null : sum.winRate * 100} fmt={(v) => Math.round(v) + '%'} rm={rm} /></b><span>Win rate</span></div></div>
                        <dl className="hero-nums">
                          <div><dt>Games played</dt><dd data-testid="stat-games"><Count value={sum.games} rm={rm} /></dd></div>
                          <div><dt>Wins</dt><dd data-testid="stat-wins"><Count value={sum.wins} rm={rm} /></dd></div>
                          <div><dt>Losses</dt><dd data-testid="stat-losses"><Count value={sum.losses} rm={rm} /></dd></div>
                          <div><dt>Current streak</dt><dd data-testid="stat-streak"><Count value={sum.curStreak} rm={rm} /></dd></div>
                          <div><dt>Best streak</dt><dd data-testid="stat-beststreak"><Count value={sum.bestStreak} rm={rm} /></dd></div>
                          <div className="poop"><dt>SheetHead</dt><dd data-testid="stat-sheethead">{sum.normalGames ? <Count value={sum.sheetHeads} rm={rm} /> : '—'}</dd></div>
                        </dl>
                        <p className="hero-note">Winner = first player out. SheetHead = last player still holding cards. Tournament games count a win for the game point ({num(sum.tourGames)} played{sum.tourGames ? ', ' + num(sum.tourGameWins) + ' won' : ''}).</p>
                      </section>

                      <div className="st-grid">
                        <Sheet id="c-trend" title="Win rate over time" sub={'Per ' + (gran === 'day' ? 'day' : gran === 'week' ? 'week' : 'month') + ' · tap or hover a point'}>
                          {trendOk ? <AreaChart data={trendPoints} rm={rm} tipFor={(i) => { const b = trendPoints[i].b; return [trendPoints[i].label, pct(b.rate) + ' win rate', b.w + ' wins in ' + b.g + ' games']; }} /> : <Need>{agg.games < MIN_TREND_GAMES ? 'Play ' + (MIN_TREND_GAMES - agg.games) + ' more game' + (MIN_TREND_GAMES - agg.games === 1 ? '' : 's') + ' in this period to see a trend (' + agg.games + '/' + MIN_TREND_GAMES + ').' : 'Play on a few more days to see a trend (' + trend.length + '/' + MIN_TREND_POINTS + ' time slots so far).'}</Need>}
                        </Sheet>
                        <Sheet id="c-activity" title="Games played" sub={'Activity per ' + (gran === 'day' ? 'day' : gran === 'week' ? 'week' : 'month')}>
                          {trend.length ? <BarChart label="Games played over time" data={trend.map(b => ({ key: b.key, value: b.g, label: bucketLabel(b.key, gran) }))} rm={rm} color="#8c1521" tipFor={(i) => { const b = trend[i]; return [bucketLabel(b.key, gran), b.g + ' games', b.w + ' won']; }} /> : <Need>No activity in this period.</Need>}
                        </Sheet>
                        <Sheet id="c-places" title="Finishing positions" sub="Normal games: 1st out wins, the last player holding cards is the SheetHead">
                          {placeTotal >= MIN_PLACES ? (
                            <div className={cx('places', rm && 'rm')} data-testid="places">
                              <div className="pl-bar" role="img" aria-label="Finishing position distribution">
                                {placeSegs.map(([l, n, cl, tip]) => n > 0 && <span key={l} className={cl} style={{ flexGrow: n }} title={l + ': ' + n + ' (' + pct(n / placeTotal) + ') · ' + tip} tabIndex={0}>{n / placeTotal > 0.09 ? pct(n / placeTotal) : ''}</span>)}
                              </div>
                              <ul className="pl-legend">
                                {placeSegs.map(([l, n, cl, tip]) => <li key={l} className={cl} data-testid={'place-' + cl}><i /><span>{l === 'SheetHead' ? '💩 SheetHead' : l}</span><b>{num(n)}</b><em>{pct(n / placeTotal)}</em></li>)}
                              </ul>
                            </div>
                          ) : <Need>Finish {MIN_PLACES - placeTotal} more normal game{MIN_PLACES - placeTotal === 1 ? '' : 's'} to see your finishing positions ({placeTotal}/{MIN_PLACES}).</Need>}
                        </Sheet>
                        <Sheet id="c-types" title="Game type performance" sub="Win rate by where you played (time period only)">
                          <HBars rm={rm} rows={catRows} empty="No games yet" />
                        </Sheet>
                        <Sheet id="c-special" title="Special-card activity" sub="Jokers, burns, sticks, bursts and more">
                          {sum.cards ? <HBars rm={rm} rows={special} /> : <Need>Special-card activity is counted from your next game on.</Need>}
                        </Sheet>
                        <Sheet id="c-ranks" title="Cards you play" sub="Ranks played (gold = special cards, purple = Jokers)">
                          {sum.cards ? <BarChart label="Ranks played" data={rankBars} rm={rm} height={170} tipFor={(i) => { const r = rankBars[i]; return [r.name, r.value + ' played']; }} /> : <Need>Card counts appear after your next game.</Need>}
                        </Sheet>
                      </div>

                      <Sheet id="c-nums" title="SheetHead numbers" sub="Counted from the game's own events, not guessed from the screen">
                        <div className="tiles" data-testid="tiles">
                          <Tile testid="t-cards" label="Cards played" value={num(sum.cards)} hint={sum.topRank ? 'Favourite rank: ' + sum.topRank.rank + ' (' + sum.topRank.n + ')' : ''} />
                          <Tile testid="t-jokers" label="Jokers played" value={num(sum.jokers)} hint={sum.jokerTop ? 'Most often as ' + sum.jokerTop.rank + ' (' + sum.jokerTop.n + '×)' : 'None yet'} />
                          <Tile testid="t-ten" label="10 burns" value={num(sum.tenBurns)} />
                          <Tile testid="t-quad" label="Four-of-a-kind burns" value={num(sum.quadBurns)} />
                          <Tile testid="t-stick" label="Successful sticks" value={num(sum.sticks)} />
                          <Tile testid="t-burst" label="4 bursts" value={num(sum.bursts)} hint={sum.interrupts ? num(sum.interrupts) + ' interrupts' : ''} />
                          <Tile testid="t-skips" label="Players skipped by 8" value={num(sum.skips)} />
                          <Tile testid="t-pickups" label="Piles picked up" value={num(sum.pickups)} hint={sum.games ? dec(sum.pickupsPerGame, 1) + ' per game · biggest ' + sum.maxPickup : ''} />
                          <Tile testid="t-large" label="Wins after a big pickup" value={num(sum.largePickupWins)} hint={'Won after taking ' + S.LARGE_PICKUP + '+ cards'} />
                          <Tile testid="t-fd" label="Face-down success" value={pct(sum.fdRate)} need={sum.fdFlips < MIN_FLIPS ? 'Needs ' + MIN_FLIPS + '+ blind flips (' + sum.fdFlips + ')' : null} hint={sum.fdFlips + ' flips'} />
                          <Tile testid="t-avgplace" label="Average finish" value={dec(sum.avgPlace, 2)} need={sum.normalGames < MIN_PLACES ? 'Needs ' + MIN_PLACES + '+ normal games' : null} hint="1 = always first out" />
                          <Tile testid="t-shrate" label="SheetHead rate" value={pct(sum.sheetHeadRate)} need={sum.normalGames < MIN_PLACES ? 'Needs ' + MIN_PLACES + '+ normal games' : null} hint={sum.sheetHeads + ' of ' + sum.normalGames + ' normal games'} />
                          <Tile testid="t-safe" label="Longest SheetHead-free run" value={num(sum.safeStreak)} hint="Normal games in a row" />
                          <Tile testid="t-avgdur" label="Average game length" value={dur(sum.avgDur)} />
                          <Tile testid="t-fast" label="Fastest win" value={dur(sum.fastestWin)} />
                          <Tile testid="t-long" label="Longest game" value={dur(sum.longest)} />
                          <Tile testid="t-fav" label="Favourite special card" value={sum.topSpecial ? rankName(sum.topSpecial.rank) : '—'} hint={sum.topSpecial ? sum.topSpecial.n + ' played' : ''} />
                        </div>
                      </Sheet>
                    </>
                  )}
                </div>
              )}

              {tab === 'tournaments' && (
                <div key={'t' + rid} className={cx('st-page', !rm && 'enter')} data-testid="tournament-stats">
                  {tourList.length === 0 ? (
                    <section className="sheet st-none" data-testid="tour-none">
                      <h2 className="st-h">No tournaments in this period</h2>
                      <p className="st-sub">{tourLife.played ? 'You have played ' + tourLife.played + ' in total. Try All time.' : 'Finish a Tournament (first to 3, 5, 7 or 10 game wins) and its story shows up here.'}</p>
                      {tourLife.played > 0 && <Button variant="ghost-dark" onClick={() => setRid('all')}>Show all time</Button>}
                    </section>
                  ) : (
                    <>
                      <section className="st-hero t" aria-label="Tournament summary">
                        <div className="hero-ring"><Ring value={tour.rate} rm={rm} /><div className="hr-c"><b data-testid="tour-rate"><Count value={tour.rate == null ? null : tour.rate * 100} fmt={(v) => Math.round(v) + '%'} rm={rm} /></b><span>Tournament win rate</span></div></div>
                        <dl className="hero-nums">
                          <div><dt>Tournaments played</dt><dd data-testid="tour-played"><Count value={tour.played} rm={rm} /></dd></div>
                          <div><dt>Tournaments won</dt><dd data-testid="tour-won"><Count value={tour.won} rm={rm} /></dd></div>
                          <div><dt>Game wins in them</dt><dd data-testid="tour-gamewins"><Count value={tourGames.tourGameWins} rm={rm} /></dd></div>
                          <div><dt>Avg final score</dt><dd data-testid="tour-avg">{dec(tour.avgScore, 1)}</dd></div>
                          <div><dt>Win streak</dt><dd data-testid="tour-streak">{tour.curStreak}<small> best {tour.bestStreak}</small></dd></div>
                          <div><dt>Games played</dt><dd>{num(tour.games)}</dd></div>
                        </dl>
                        <p className="hero-note">A Tournament is won by reaching its target of game wins first.</p>
                      </section>
                      <div className="st-grid">
                        <Sheet id="t-hist" title="Tournament history" sub="Bar = your score as a share of the target. Gold = you won it. Tap a bar for details.">
                          <TourHistory list={tour.list} rm={rm} />
                        </Sheet>
                        <Sheet id="t-target" title="By target" sub="Performance in first-to-3, 5, 7 and 10">
                          <table className="t-table" data-testid="tour-targets"><thead><tr><th>Format</th><th>Played</th><th>Won</th><th>Win rate</th><th>Avg score</th></tr></thead>
                            <tbody>{[3, 5, 7, 10].map(k => { const r = tour.per[k]; return <tr key={k} className={cx(!r.played && 'dim')} data-target={k}><th>First to {k}</th><td>{r.played}</td><td>{r.won}</td><td>{r.played ? pct(r.won / r.played) : '—'}</td><td>{dec(r.avg, 1)}</td></tr>; })}</tbody></table>
                        </Sheet>
                      </div>
                      <div className="tiles two">
                        <Tile testid="tour-dom" label="Most dominant victory" value={tour.dominant ? tour.dominant.my + '–' + (tour.dominant.my - tour.dominant.margin) : '—'} hint={tour.dominant ? 'First to ' + tour.dominant.target + ' · ' + bucketLabel(S.dayKey(tour.dominant.at), 'day') : 'Win a tournament first'} wide />
                        <Tile testid="tour-best" label="Best tournament" value={tour.best ? 'First to ' + tour.best.target : '—'} hint={tour.best ? 'Won ' + tour.best.my + '–' + (tour.best.my - tour.best.margin) + ' · the biggest one you have won' : 'Win a tournament first'} wide />
                      </div>
                    </>
                  )}
                </div>
              )}

              {tab === 'milestones' && (
                <div className={cx('st-page', !rm && 'enter')}>
                  {fresh.length > 0 && <div className="ms-toast" role="status" data-testid="ms-toast">Milestone unlocked: {ms.filter(m => fresh.includes(m.id)).map(m => m.top + ' ' + m.label.toLowerCase()).join(', ')}</div>}
                  <Sheet id="m-all" title="Milestones" sub="Lifetime achievements, independent of the filters"><Milestones items={ms} fresh={fresh} rm={rm} /></Sheet>
                </div>
              )}
            </>
          )}
        </div>
      </Frame>
    );
  }

  function TourHistory({ list, rm }) {
    const [hi, setHi] = useState(null), ref = useRef(null);
    const items = list.slice(-24);
    return (
      <div className={cx('th', rm && 'rm')} ref={ref} data-testid="tour-history">
        <div className="th-scroll">
          {items.map((t, i) => (
            <button key={t.id} type="button" className={cx('th-col', t.won && 'won', hi === i && 'on')} onPointerEnter={(e) => { if (e.pointerType === 'mouse') setHi(i); }} onPointerLeave={(e) => { if (e.pointerType === 'mouse') setHi(null); }} onClick={() => setHi(hi === i ? null : i)} aria-label={'First to ' + t.target + ', you scored ' + t.my + (t.won ? ', won' : ', lost')}>
              <span className="th-bar"><i style={{ height: Math.max(6, Math.min(1, t.my / t.target) * 100) + '%', animationDelay: i * 30 + 'ms' }} /></span>
              <span className="th-x">{t.target}</span>
            </button>
          ))}
        </div>
        <div className="th-tip" role="status" aria-live="polite">
          {hi != null && items[hi] ? <><b>{bucketLabel(S.dayKey(items[hi].at), 'day')} · First to {items[hi].target}</b> You {items[hi].my}, best rival {items[hi].my - items[hi].margin} · {items[hi].won ? 'Won' : 'Lost'}</> : <span>Tap a bar. The number under it is the target.</span>}
        </div>
      </div>
    );
  }

  window.SH.ui.StatisticsScreen = StatisticsScreen;
})();
