// Shared gameplay engine for Quick drills, Brain Ride and the landscape prototype.
const GP = () => window.GGPrefs || {};
const gmix = (c, p, base = 'transparent') => `color-mix(in srgb, ${c} ${p}%, ${base})`;
const PRAISE = ['Nice!', 'Spot on', 'Great catch', 'Correct'];

// Trial runner: spoken countdown → cue → response window → kind feedback.
function useTrials({ count, isTarget, baseMs, onCue, leadMs, onDone, labels, countdown = true, tapMode = false, showRT = false }) {
  const [st, setSt] = React.useState({ phase: countdown ? 'countdown' : 'trial', i: 0, fb: null, ready: false, paused: false, streak: 0 });
  const stRef = React.useRef(st); stRef.current = st;
  const pr = React.useRef(); pr.current = { count, isTarget, onCue, leadMs, onDone, labels, tapMode, showRT };
  const readyAt = React.useRef(0);
  const S = React.useRef({ correct: 0, total: 0, streak: 0, best: 0 });
  const win = React.useRef(baseMs);
  const tm = React.useRef([]); const fbT = React.useRef(null); const cueClean = React.useRef(null);
  const clear = () => { tm.current.forEach(clearTimeout); tm.current = []; if (cueClean.current) { cueClean.current(); cueClean.current = null; } };
  const answerRef = React.useRef();
  answerRef.current = chose => {
    const s = stRef.current; if (s.phase !== 'trial' || !s.ready || s.paused) return;
    clear();
    const p = pr.current; const tgt = p.isTarget(s.i);
    if (chose === null && p.tapMode) chose = false;
    const rt = (Date.now() - readyAt.current) / 1000;
    const k = chose === null ? 'none' : chose === tgt ? 'correct' : 'wrong';
    const R = S.current; R.total++;
    if (k === 'correct') { R.correct++; R.streak++; R.best = Math.max(R.best, R.streak); } else R.streak = 0;
    window.GGData.feedback(k);
    const fb = { k, t: k === 'correct' ? (p.showRT && chose ? rt.toFixed(2) + 's' : p.tapMode && !tgt ? 'Nice hold' : PRAISE[R.correct % PRAISE.length]) : k === 'none' ? 'No rush — next one coming' : p.tapMode ? (tgt ? 'Almost — tap for that one' : 'Almost — hold off on that one') : `Almost — that one was ${tgt ? p.labels.yes : p.labels.no}` };
    setSt(x => ({ ...x, phase: 'feedback', fb, ready: false, streak: R.streak, score: R.correct }));
    fbT.current = setTimeout(() => {
      if (s.i + 1 >= p.count) { setSt(x => ({ ...x, phase: 'done' })); p.onDone({ correct: R.correct, total: R.total, best: R.best }); }
      else setSt(x => ({ ...x, phase: 'trial', i: x.i + 1, fb: null }));
    }, k === 'correct' ? 700 : 1400);
  };
  const answer = React.useCallback(c => answerRef.current(c), []);
  React.useEffect(() => {
    if (st.phase !== 'trial' || st.paused) return;
    const p = pr.current;
    const c = p.onCue && p.onCue(st.i); if (typeof c === 'function') cueClean.current = c;
    const lead = p.leadMs ? p.leadMs(st.i) : 0;
    tm.current.push(setTimeout(() => { readyAt.current = Date.now(); setSt(x => ({ ...x, ready: true })); }, lead));
    tm.current.push(setTimeout(() => answerRef.current(null), lead + win.current));
    return clear;
  }, [st.phase, st.i, st.paused]);
  const pause = React.useCallback(() => { clear(); window.GGData.hush(); setSt(x => (x.phase === 'done' || x.paused ? x : { ...x, paused: true, ready: false })); }, []);
  const resume = React.useCallback(() => setSt(x => ({ ...x, paused: false })), []);
  const start = React.useCallback(() => setSt(x => ({ ...x, phase: 'trial' })), []);
  React.useEffect(() => () => { clear(); clearTimeout(fbT.current); }, []);
  return { ...st, answer, pause, resume, start, windowMs: win.current, running: st.phase === 'trial' && st.ready && !st.paused };
}

// One half of the screen = one answer. Fires on touch-down so a bumpy tap still counts.
function AnswerZone({ target, color, label, sub, lit, marker, interactive = true, onAnswer, align = 'end' }) {
  const { Icon } = window.GG;
  const p = GP(); const hc = p.highContrast; const lg = p.largeText;
  const [down, setDown] = React.useState(false);
  const on = lit || down;
  const bg = hc ? (target ? (on ? '#fff' : color) : (on ? '#333' : '#000'))
    : target ? (on ? color : `linear-gradient(180deg, ${gmix(color, 10, '#0c0e14')} 0%, ${gmix(color, 30, '#0c0e14')} 100%)`) : (on ? '#2a2c3a' : 'var(--surface-card)');
  const txt = target && (hc || on) ? '#050608' : '#fff';
  return (
    <div role="button" aria-label={label}
      onPointerDown={() => { if (!interactive) return; setDown(true); onAnswer && onAnswer(!!target); }}
      onPointerUp={() => setDown(false)} onPointerLeave={() => setDown(false)}
      style={{ position: 'relative', flex: 1, minWidth: 0, borderRadius: 28, display: 'flex', flexDirection: 'column', justifyContent: align === 'center' ? 'center' : 'flex-end', alignItems: 'center', gap: 4, padding: align === 'center' ? '0 8px' : '0 8px 26px', boxSizing: 'border-box',
        cursor: interactive ? 'pointer' : 'default', userSelect: 'none', WebkitUserSelect: 'none', touchAction: 'manipulation', textAlign: 'center', background: bg, color: txt,
        border: hc ? `3px solid ${target ? color : '#fff'}` : `2px solid ${target ? color : 'var(--gg-line-strong)'}`,
        boxShadow: !hc && target ? `0 0 ${on ? 40 : 20}px ${gmix(color, on ? 55 : 25)}` : 'none', transform: on ? 'scale(0.985)' : 'none', transition: 'all 140ms var(--ease)' }}>
      {marker && <span style={{ position: 'absolute', left: '50%', bottom: align === 'center' ? 'auto' : (lg ? 128 : 108), top: align === 'center' ? 24 : 'auto', color: txt, display: 'flex', animation: 'gg-tap 0.9s ease-in-out infinite' }}><Icon name="touch_app" size={64} fill /></span>}
      <span style={{ fontSize: lg ? 32 : 26, fontWeight: 800, letterSpacing: '-0.01em', lineHeight: 1.1 }}>{label}</span>
      {sub && <span style={{ fontSize: lg ? 15 : 13, fontWeight: 800, letterSpacing: '0.06em', textTransform: 'uppercase', opacity: 0.8 }}>{sub}</span>}
    </div>
  );
}

function CueCard({ cue, cueSize = 140, caption, fb, color, running, windowMs, runKey, forceVisual, style }) {
  const { ResponseWindow, Icon } = window.GG;
  const p = GP(); const hc = p.highContrast; const lg = p.largeText;
  const hide = false;
  const fbc = !fb ? color : fb.k === 'correct' ? 'var(--gg-domain-composure)' : fb.k === 'wrong' ? 'var(--gg-domain-speed)' : fb.k === 'demo' ? color : '#fff';
  return (
    <div style={{ pointerEvents: 'none', borderRadius: 26, padding: '18px 16px 16px', textAlign: 'center', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', justifyContent: 'center',
      background: hc ? '#000' : 'rgba(14,15,24,0.94)', border: hc ? '3px solid #fff' : '1px solid var(--gg-line-strong)', boxShadow: hc ? 'none' : '0 16px 40px rgba(0,0,0,0.55)', ...style }}>
      <div style={{ minHeight: lg ? 176 : 150, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: (lg ? 1.15 : 1) * cueSize, fontWeight: 800, lineHeight: 1, letterSpacing: '-0.04em', fontVariantNumeric: 'tabular-nums', color: '#fff', textShadow: hc ? 'none' : `0 0 40px ${gmix(fbc, 50)}` }}>
        {hide ? <span style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, color: hc ? '#fff' : color }}><Icon name="hearing" size={88} /><span style={{ fontSize: lg ? 22 : 19, fontWeight: 800, letterSpacing: 0 }}>Listen</span></span> : cue}
      </div>
      <div style={{ minHeight: lg ? 64 : 56, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {fb ? <span style={{ padding: '8px 16px', borderRadius: 9999, fontSize: lg ? 21 : 18, fontWeight: 800, lineHeight: 1.25, color: hc ? '#000' : fbc, background: hc ? fbc : gmix(fbc, 16), border: hc ? 'none' : `1px solid ${gmix(fbc, 40)}` }}>{fb.t}</span>
          : caption && !hide ? <span style={{ fontSize: lg ? 21 : 18, fontWeight: 700, lineHeight: 1.3, color: hc ? '#fff' : 'var(--text-secondary)' }}>{caption}</span> : null}
      </div>
      <ResponseWindow ms={windowMs || 1000} runKey={runKey} running={!!running} color={hc ? '#fff' : color} />
    </div>
  );
}

// Portrait stage: both halves of the screen are answers; the cue floats over the top (taps pass through).
function TrialStage({ color, cue, cueSize, caption, no, yes, noSub, yesSub, onAnswer, fb, running, windowMs, runKey, lit, marker, interactive = true, forceVisual }) {
  return (
    <div style={{ position: 'relative', flex: 1, minHeight: 300, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
      <AnswerZone color={color} label={no} sub={noSub} lit={lit === 'no'} marker={marker === 'no'} interactive={interactive} onAnswer={onAnswer} />
      <AnswerZone target color={color} label={yes} sub={yesSub} lit={lit === 'yes'} marker={marker === 'yes'} interactive={interactive} onAnswer={onAnswer} />
      <div style={{ position: 'absolute', left: 12, right: 12, top: 12 }}>
        <CueCard cue={cue} cueSize={cueSize} caption={caption} fb={fb} color={color} running={running} windowMs={windowMs} runKey={runKey} forceVisual={forceVisual} />
      </div>
    </div>
  );
}

function TapStage({ color, cue, cueSize, caption, yes, onAnswer, fb, running, windowMs, runKey, lit, marker, interactive = true, forceVisual }) {
  return (
    <div style={{ position: 'relative', flex: 1, minHeight: 300, display: 'flex' }}>
      <AnswerZone target color={color} label={yes || 'Tap anywhere'} lit={lit === 'yes'} marker={marker === 'yes'} interactive={interactive} onAnswer={() => onAnswer && onAnswer(true)} />
      <div style={{ position: 'absolute', left: 12, right: 12, top: 12 }}>
        <CueCard cue={cue} cueSize={cueSize} caption={caption} fb={fb} color={color} running={running} windowMs={windowMs} runKey={runKey} forceVisual={forceVisual} />
      </div>
    </div>
  );
}
function Stage(props) { return props.mode === 'tap' ? <TapStage {...props} /> : <TrialStage {...props} />; }

function ExitButton({ onClick }) {
  return <button type="button" aria-label="End" onClick={onClick} style={{ width: 56, height: 56, borderRadius: '50%', border: '2px solid var(--gg-line-strong)', background: 'var(--gg-fill-glass)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flex: 'none' }}><span className="material-symbols-outlined" style={{ fontSize: 28 }}>close</span></button>;
}

// Starts immediately: spoken 3-2-1-Go with the rule on screen.
function Countdown({ color, title, rule, onDone }) {
  const { speak, tone, haptic } = window.GGData;
  const p = GP(); const lg = p.largeText; const hc = p.highContrast;
  const [n, setN] = React.useState(null);
  const go = true;
  React.useEffect(() => {
    if (!go) return;
    const seq = ['3', '2', '1', 'Go']; const ts = [];
    seq.forEach((v, k) => ts.push(setTimeout(() => { setN(v); speak(v, p.voice !== false, 1); tone(v === 'Go' ? 880 : 560, 'sine', v === 'Go' ? 0.25 : 0.12); haptic(v === 'Go' ? 'go' : 'count'); }, k * 950)));
    ts.push(setTimeout(onDone, seq.length * 950 + 250));
    return () => ts.forEach(clearTimeout);
  }, [go]);
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 30, background: hc ? '#000' : 'rgba(7,7,10,0.94)', backdropFilter: 'blur(6px)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 14, padding: 24, textAlign: 'center', borderRadius: 'inherit' }}>
      <Meta color={hc ? '#fff' : color}>Get ready</Meta>
      <div style={{ fontSize: lg ? 32 : 28, fontWeight: 800, letterSpacing: '-0.02em', lineHeight: 1.1 }}>{title}</div>
      {rule && <div style={{ fontSize: lg ? 22 : 19, lineHeight: 1.4, fontWeight: 600, color: hc ? '#fff' : 'var(--text-secondary)', maxWidth: 330, textWrap: 'pretty' }}>{rule}</div>}
      <div style={{ width: 120, height: 120, borderRadius: '50%', marginTop: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', border: `4px solid ${hc ? '#fff' : color}`, boxShadow: hc ? 'none' : `0 0 30px ${gmix(color, 45)}`, fontSize: n === 'Go' ? 44 : 64, fontWeight: 800, color: hc ? '#fff' : color }}>
        {n || <span className="material-symbols-outlined" style={{ fontSize: 44 }}>volume_up</span>}
      </div>
    </div>
  );
}

// Portrait gameplay chrome: exit button, title, one optional right-hand readout.
function GameFrame({ T, color, title, right, rule, onEnd, children }) {
  const lg = GP().largeText;
  return (
    <div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '56px minmax(0,1fr) auto', alignItems: 'center', gap: 8, padding: '6px 0 10px' }}>
        <ExitButton onClick={() => { window.GGData.hush(); onEnd(); }} />
        <div style={{ textAlign: 'center', fontSize: lg ? 19 : 16, fontWeight: 800, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{title}</div>
        <div style={{ minWidth: 56, display: 'flex', justifyContent: 'flex-end' }}>{right}</div>
      </div>
      {rule && <p style={{ margin: '0 0 10px', fontSize: lg ? 19 : 16, lineHeight: 1.35, fontWeight: 600, color: 'var(--text-secondary)', textAlign: 'center', textWrap: 'pretty' }}>{rule}</p>}
      <div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex' }}>
        {children}
        {T.phase === 'countdown' && !T.paused && <Countdown color={color} title={title} rule={rule} onDone={T.start} />}
      </div>
    </div>
  );
}

// Practice Demo: reads the rule, then plays example rounds on a loop until the rider taps "Got it". Toggle skips demos everywhere.
function PracticeDemo({ Frame, title, color, rule, no, yes, trials, onPlay, onBack, mode: kind = 'lr' }) {
  const tapMode = kind === 'tap';
  const { BackButton, Toggle, Button, Icon } = window.GG;
  const { speak, tone, haptic, hush } = window.GGData;
  const p = GP(); const lg = p.largeText;
  const [k, setK] = React.useState(0);
  const mode = 'demo';
  const [started, setStarted] = React.useState(false);
  React.useEffect(() => { say(rule); const id = setTimeout(() => setStarted(true), Math.min(4000, 1200 + String(rule).length * 55)); return () => clearTimeout(id); }, []);
  const [lit, setLit] = React.useState(null);
  const [fb, setFb] = React.useState(null);
  const lock = React.useRef(false);
  const t = trials[k % trials.length];
  const say = x => speak(String(x).replace(/→/g, ','), p.voice !== false);
  React.useEffect(() => {
    const ts = [];
    setLit(null);
    if (!started) return;
    if (mode === 'demo') {
      setFb(t.caption ? null : { k: 'demo', t: 'Listen…' });
      t.spoken && say(t.spoken); t.tone ? tone(...t.tone) : tone(540, 'sine', 0.08);
      ts.push(setTimeout(() => { setLit(t.isTrue ? 'yes' : tapMode ? null : 'no'); haptic(t.isTrue ? 'correct' : 'tap'); tone(t.isTrue ? 740 : 520, 'triangle', 0.1); setFb({ k: 'demo', t: t.explain }); say(t.explain); }, 1500));
      ts.push(setTimeout(() => { setK(x => x + 1); }, 4400));
    } else {
      lock.current = false;
      setFb(k === trials.length ? { k: 'demo', t: 'Your turn — try one!' } : null);
      if (k === trials.length) say('Your turn. Try one.');
      const lead = k === trials.length ? 1300 : 200;
      ts.push(setTimeout(() => { t.spoken && say(t.spoken); t.tone ? tone(...t.tone) : tone(540, 'sine', 0.08); }, lead));
      if (tapMode) ts.push(setTimeout(() => tryIt(false), lead + 3500));
    }
    return () => ts.forEach(clearTimeout);
  }, [k, started]);
  const tryIt = chose => {
    if (mode !== 'you' || lock.current) return; lock.current = true;
    const ok = chose === t.isTrue;
    haptic(ok ? 'correct' : 'wrong'); ok ? tone(880, 'sine', 0.15) : tone(240, 'triangle', 0.25);
    setFb({ k: ok ? 'correct' : 'wrong', t: ok ? (tapMode && !t.isTrue ? 'Nice hold — you got it!' : 'Nice — you got it!') : tapMode ? (t.isTrue ? 'Almost — tap for that one' : 'Almost — hold off on that one') : `Almost — that one was ${t.isTrue ? (t.yes || yes) : (t.no || no)}` });
    setTimeout(() => setK(x => x + 1), 1500);
  };
  return (
    <Frame>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 0' }}>
        <BackButton onClick={() => { hush(); onBack(); }} />
        <span style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: lg ? 16 : 14, fontWeight: 700, color: 'var(--text-secondary)' }}>Skip Practice Demos<Toggle checked={!!p.skipRobot} onChange={v => p.set && p.set({ skipRobot: v })} label="Skip Practice Demos" /></span>
      </div>
      <div style={{ display: 'flex', gap: 12, alignItems: 'center', marginTop: 4 }}>
        <span style={{ width: 52, height: 52, flex: 'none', borderRadius: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', color, background: gmix(color, 15), border: `1px solid ${gmix(color, 35)}` }}><Icon name={mode === 'demo' ? 'play_circle' : 'front_hand'} size={30} /></span>
        <div style={{ minWidth: 0 }}>
          <Meta color={color}>{mode === 'demo' ? 'Practice Demo · watch how it works' : 'Your turn · practice, not scored'}</Meta>
          <div style={{ fontSize: lg ? 27 : 24, fontWeight: 800, letterSpacing: '-0.02em', lineHeight: 1.1, marginTop: 2 }}>{title}</div>
        </div>
      </div>
      <p style={{ margin: '10px 0 12px', fontSize: lg ? 21 : 18, lineHeight: 1.4, fontWeight: 600, color: 'var(--text-secondary)', textWrap: 'pretty' }}>{rule}</p>
      <Stage mode={kind} color={color} cue={t.cue} cueSize={t.cueSize} caption={t.caption} no={t.no || no} yes={t.yes || yes} lit={lit} marker={mode === 'demo' ? lit : null} interactive={mode === 'you'} onAnswer={tryIt} fb={fb} running={false} forceVisual={t.forceVisual} />
      <div style={{ paddingTop: 14 }}><Button onClick={() => { hush(); onPlay(); }}>Got it, let's play for real</Button></div>
    </Frame>
  );
}

function PlainFrame({ children }) {
  const { PhoneShell } = window.GG;
  const hc = GP().highContrast;
  return (
    <PhoneShell background={hc ? '#000' : 'var(--bg-page)'}>
      {!hc && <Glows />}
      <div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', padding: '0 16px 8px' }}>{children}</div>
    </PhoneShell>
  );
}

// Generic gameplay shell: optional background layer, respects high contrast. portrait forces portrait padding.
function Shell({ bg, layer, portrait, children }) {
  const { PhoneShell } = window.GG;
  const hc = GP().highContrast;
  return (
    <PhoneShell background={hc ? '#000' : (bg || 'var(--bg-page)')}>
      {!hc && layer}
      <div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', padding: '0 16px 8px', animation: 'gg-fadein .4s var(--ease, ease) both' }}>{children}</div>
    </PhoneShell>
  );
}

function HydrationCard({ final }) {
  const { Icon } = window.GG;
  const lg = GP().largeText;
  return (
    <div style={{ borderRadius: 'var(--radius-card)', padding: 16, display: 'flex', gap: 14, alignItems: 'center', background: 'linear-gradient(135deg, color-mix(in srgb, var(--gg-cyan) 14%, #0c0e14), #0c0e14)', border: '1px solid color-mix(in srgb, var(--gg-cyan) 35%, transparent)' }}>
      <span style={{ width: 52, height: 52, flex: 'none', borderRadius: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--gg-cyan)', background: 'color-mix(in srgb, var(--gg-cyan) 15%, transparent)' }}><Icon name="water_drop" size={30} fill /></span>
      <div style={{ minWidth: 0 }}>
        <div style={{ fontSize: lg ? 21 : 18, fontWeight: 800 }}>Take a sip of water</div>
        <div style={{ fontSize: lg ? 17 : 15, lineHeight: 1.4, color: 'var(--text-secondary)', marginTop: 2 }}>{final ? 'Then ease into a 2-minute easy spin to cool down.' : 'Keep pedaling easy while the next drill loads.'}</div>
      </div>
    </div>
  );
}

Object.assign(window, { GP, useTrials, AnswerZone, CueCard, TrialStage, TapStage, Stage, ExitButton, Countdown, GameFrame, PracticeDemo, PlainFrame, Shell, HydrationCard });
