/* ============================================================
   DRAWING TELEPHONE — draw → guess → draw chain of chaos
   ============================================================ */
const { useState: useT, useRef: useRT, useEffect: useET, useCallback: useCT, useMemo: useM } = React;

const TEL_SESSION_KEY = 'party:telephone:v2';
const GUESS_SECS_DEFAULT = 20;
const BRUSH_SIZES = [5, 10, 18];
const AWARDS = [
  { key: 'cursed', label: 'Most cursed derailment', emoji: '💀' },
  { key: 'close', label: 'Closest to the start', emoji: '🎯' },
  { key: 'art', label: 'Best drawing', emoji: '🎨' },
];

function chainLength(n) { return n % 2 === 0 ? n : n + 1; }

function chainCountForMode(mode) {
  if (mode === 'double') return 2;
  return 1;
}

function getTurnInfo(mode, order, turnIndex) {
  const n = order.length;
  const chains = chainCountForMode(mode);
  const lengths = Array.from({ length: chains }, () => chainLength(n));
  const total = lengths.reduce((a, b) => a + b, 0);
  let rem = turnIndex;
  for (let c = 0; c < chains; c++) {
    const len = lengths[c];
    if (rem < len) {
      return {
        chainIdx: c,
        step: rem,
        player: order[(c + rem) % n],
        action: rem % 2 === 0 ? 'draw' : 'guess',
        total,
        chains,
        chainLen: len,
        isLapEnd: rem === n - 1 && rem < len - 1,
      };
    }
    rem -= len;
  }
  return { chainIdx: 0, step: 0, player: order[0], action: 'draw', total, chains, chainLen: chainLength(n), isLapEnd: false };
}

function avgSecPerTurn(drawSecs, guessSecs) { return drawSecs * 0.55 + guessSecs * 0.45; }

function saveSession(state) {
  try {
    const lean = { ...state, phase: state.phase === 'draw' || state.phase === 'guess' ? 'pass' : state.phase };
    sessionStorage.setItem(TEL_SESSION_KEY, JSON.stringify(lean));
  } catch {}
}

function loadSession() {
  try {
    const raw = sessionStorage.getItem(TEL_SESSION_KEY);
    return raw ? JSON.parse(raw) : null;
  } catch { return null; }
}

function clearSession() {
  try { sessionStorage.removeItem(TEL_SESSION_KEY); } catch {}
}

/* ---------- drawing pad ---------- */
function DrawPad({ prompt, subPrompt, drawSecs, collapsed, onTogglePrompt, onDone, onBack }) {
  const canvasRef = useRT(null);
  const wrapRef = useRT(null);
  const strokes = useRT([]);
  const strokeIdx = useRT(-1);
  const drawing = useRT(false);
  const last = useRT(null);
  const sized = useRT(false);
  const [hasInk, setHasInk] = useT(false);
  const [strokeCount, setStrokeCount] = useT(0);
  const [running, setRunning] = useT(true);
  const [brush, setBrush] = useT(1);
  const [eraser, setEraser] = useT(false);
  const [rem] = useCountdown(drawSecs, running, () => setRunning(false));

  const replay = useCT((ctx, w, h) => {
    ctx.fillStyle = '#FFF6E6';
    ctx.fillRect(0, 0, w, h);
    const list = strokes.current.slice(0, strokeIdx.current + 1);
    list.forEach(st => {
      ctx.save();
      if (st.eraser) ctx.globalCompositeOperation = 'destination-out';
      else ctx.globalCompositeOperation = 'source-over';
      ctx.strokeStyle = st.eraser ? 'rgba(0,0,0,1)' : '#1A1330';
      ctx.lineWidth = st.size;
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
      ctx.beginPath();
      st.points.forEach((p, i) => (i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y)));
      ctx.stroke();
      ctx.restore();
    });
    setHasInk(strokeIdx.current >= 0);
  }, []);

  const initCanvas = useCT(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap || sized.current) return;
    const rect = wrap.getBoundingClientRect();
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = Math.floor(rect.width * dpr);
    canvas.height = Math.floor(rect.height * dpr);
    canvas.style.width = rect.width + 'px';
    canvas.style.height = rect.height + 'px';
    const ctx = canvas.getContext('2d');
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    replay(ctx, rect.width, rect.height);
    sized.current = true;
  }, [replay]);

  useET(() => {
    initCanvas();
    const t = setTimeout(initCanvas, 120);
    return () => clearTimeout(t);
  }, [initCanvas]);

  const pos = (e) => {
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const t = e.touches?.[0] || e.changedTouches?.[0];
    const cx = t ? t.clientX : e.clientX;
    const cy = t ? t.clientY : e.clientY;
    return { x: cx - rect.left, y: cy - rect.top };
  };

  const activeStroke = useRT(null);

  const start = (e) => {
    e.preventDefault();
    const canvas = canvasRef.current;
    if (canvas?.setPointerCapture && e.pointerId != null) {
      try { canvas.setPointerCapture(e.pointerId); } catch {}
    }
    drawing.current = true;
    const p = pos(e);
    last.current = p;
    activeStroke.current = { points: [p], size: BRUSH_SIZES[brush], eraser };
    strokes.current = strokes.current.slice(0, strokeIdx.current + 1);
  };

  const move = (e) => {
    if (!drawing.current || !activeStroke.current) return;
    e.preventDefault();
    const p = pos(e);
    const st = activeStroke.current;
    const prev = st.points[st.points.length - 1];
    st.points.push(p);
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const rect = canvas.getBoundingClientRect();
    ctx.save();
    if (st.eraser) ctx.globalCompositeOperation = 'destination-out';
    ctx.strokeStyle = st.eraser ? 'rgba(0,0,0,1)' : '#1A1330';
    ctx.lineWidth = st.size;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    ctx.moveTo(prev.x, prev.y);
    ctx.lineTo(p.x, p.y);
    ctx.stroke();
    ctx.restore();
    setHasInk(true);
    last.current = p;
  };

  const end = () => {
    if (activeStroke.current?.points.length > 1) {
      strokeIdx.current += 1;
      strokes.current[strokeIdx.current] = activeStroke.current;
      setStrokeCount(strokeIdx.current + 1);
      setHasInk(true);
    }
    drawing.current = false;
    activeStroke.current = null;
    last.current = null;
  };

  const undo = () => {
    if (strokeIdx.current < 0) return;
    strokeIdx.current -= 1;
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    replay(canvas.getContext('2d'), rect.width, rect.height);
    setStrokeCount(strokeIdx.current + 1);
  };

  const clear = () => {
    strokeIdx.current = -1;
    strokes.current = [];
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    replay(canvas.getContext('2d'), rect.width, rect.height);
    setStrokeCount(0);
  };

  const submit = () => {
    if (!hasInk) return;
    onDone(canvasRef.current.toDataURL('image/png', 0.82));
  };

  return (
    <div className="app screen tel-draw-screen">
      <TopBar onBack={onBack} title="Draw it" color="var(--lime)"
        right={<span className="mono" style={{ fontSize: 11, color: rem <= 10 ? 'var(--coral)' : '#9082BE' }}>{fmtTime(rem)}</span>}/>
      <TimerBar remaining={rem} total={drawSecs}/>
      {!running && (
        <div className="tel-timeup">time's up — finish up!</div>
      )}
      {prompt && !collapsed && (
        <div className="tel-prompt card pad" style={{ paddingTop: 10, paddingBottom: 12, textAlign: 'center' }}>
          <div className="kicker" style={{ marginBottom: 4 }}>draw this</div>
          <div className="secret-word" style={{ fontSize: subPrompt ? 24 : 30 }}>{prompt}</div>
          {subPrompt && <p className="muted" style={{ fontSize: 12, marginTop: 6, color: 'var(--ink)' }}>{subPrompt}</p>}
        </div>
      )}
      {prompt && (
        <button type="button" className="tel-prompt-toggle" onClick={onTogglePrompt}>
          {collapsed ? '▸ Show prompt' : '▾ Hide prompt (more space)'}
        </button>
      )}
      <div className="tel-canvas-wrap tel-canvas-full" ref={wrapRef}
        onPointerDown={start} onPointerMove={move} onPointerUp={end} onPointerCancel={end}
        onPointerLeave={end}>
        <canvas ref={canvasRef} className="tel-canvas"/>
      </div>
      <div className="tel-tools pad">
        <div className="tel-brush-row">
          {BRUSH_SIZES.map((s, i) => (
            <button key={s} type="button" className={'tel-brush' + (brush === i && !eraser ? ' on' : '')}
              onClick={() => { setBrush(i); setEraser(false); }}>
              <i style={{ width: s + 4, height: s + 4 }}/>
            </button>
          ))}
          <button type="button" className={'tel-brush eraser' + (eraser ? ' on' : '')} onClick={() => setEraser(e => !e)} title="Eraser">⌫</button>
        </div>
        <div className="btn-row" style={{ marginTop: 10 }}>
          <Btn variant="ghost" onClick={undo} disabled={strokeCount <= 0}>Undo</Btn>
          <Btn variant="ghost" onClick={clear}>Clear</Btn>
          <Btn color="lime" size="lg" disabled={!hasInk} onClick={submit}>Done →</Btn>
        </div>
      </div>
    </div>
  );
}

/* ---------- guess pad ---------- */
function GuessPad({ image, guessSecs, sayAloud, onDone, onBack }) {
  const [text, setText] = useT('');
  const [running, setRunning] = useT(true);
  const [listening, setListening] = useT(false);
  const [rem] = useCountdown(guessSecs, running, () => setRunning(false));
  const recRef = useRT(null);

  useET(() => () => { try { recRef.current?.stop(); } catch {} }, []);

  const submit = () => {
    const t = text.trim();
    if (!t) return;
    onDone(t);
  };

  const startVoice = () => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) return;
    try { recRef.current?.stop(); } catch {}
    const r = new SR();
    recRef.current = r;
    r.lang = 'en-US';
    r.interimResults = false;
    r.maxAlternatives = 1;
    r.onresult = (ev) => {
      const t = ev.results?.[0]?.[0]?.transcript;
      if (t) setText(t.trim());
      setListening(false);
    };
    r.onerror = () => setListening(false);
    r.onend = () => setListening(false);
    setListening(true);
    r.start();
  };

  return (
    <div className="app screen">
      <TopBar onBack={onBack} title="Guess it" color="var(--lime)"
        right={<span className="mono" style={{ fontSize: 11, color: rem <= 5 ? 'var(--coral)' : '#9082BE' }}>{fmtTime(rem)}</span>}/>
      <TimerBar remaining={rem} total={guessSecs}/>
      <div className="center" style={{ flex: 1, padding: '10px 20px', gap: 12, overflow: 'auto' }}>
        <div className="kicker">what is this?</div>
        <div className="tel-reveal-img card" style={{ padding: 8, width: '100%', maxWidth: 380 }}>
          <img src={image} alt="Drawing to guess" className="tel-drawing"/>
        </div>
        {sayAloud ? (
          <>
            <p className="serif-i" style={{ fontSize: 18, color: '#FFD79A', textAlign: 'center', maxWidth: 300 }}>
              Say your guess out loud — someone types what you said.
            </p>
            <input className="addinput tel-guess-input" value={text} placeholder="Witness types the guess…" maxLength={80}
              onChange={e => setText(e.target.value)} style={{ maxWidth: 380, fontSize: 20, padding: '16px 18px' }}/>
          </>
        ) : (
          <>
            <p className="muted" style={{ fontSize: 14, maxWidth: 300 }}>Type what you think the drawing shows.</p>
            <input className="addinput tel-guess-input" value={text} placeholder="Your guess…" maxLength={80}
              onChange={e => setText(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()}
              autoFocus style={{ maxWidth: 380, fontSize: 20, padding: '16px 18px' }}/>
            {(window.SpeechRecognition || window.webkitSpeechRecognition) && (
              <Btn variant="ghost" onClick={startVoice} style={{ maxWidth: 380 }}>
                {listening ? '🎙 Listening…' : '🎙 Voice input'}
              </Btn>
            )}
          </>
        )}
        {!running && <span className="tier-pill" style={{ background: 'var(--coral)', color: '#fff' }}>hurry!</span>}
      </div>
      <div className="pad" style={{ paddingTop: 4 }}>
        <Btn color="lime" size="lg" disabled={!text.trim()} onClick={submit}>Lock in guess →</Btn>
      </div>
    </div>
  );
}

/* ---------- chain reveal card ---------- */
function ChainCard({ chain, idx, compact }) {
  const last = chain.steps[chain.steps.length - 1];
  return (
    <div className={'tel-chain card' + (compact ? ' compact' : '')} style={{ padding: '16px 16px 18px', width: '100%', maxWidth: 400 }}>
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 10 }}>
        <span className="mono" style={{ fontSize: 11, color: '#897BB6' }}>Chain {idx + 1}</span>
        {chain.starter && <span className="chip" style={{ fontSize: 12 }}><Avatar name={chain.starter} size={18}/>{chain.starter}</span>}
      </div>
      <div className="tel-chain-start">
        <span className="kicker">Started as</span>
        <div className="secret-word" style={{ fontSize: compact ? 20 : 26, color: 'var(--ink)', marginTop: 4 }}>{chain.prompt}</div>
      </div>
      {!compact && (
        <div className="tel-chain-steps">
          {chain.steps.map((step, si) => (
            <div key={si} className="tel-step">
              <div className="tel-step-meta">
                <Avatar name={step.player} size={20}/>
                <b>{step.player}</b>
                <span>{step.type === 'draw' ? 'drew' : 'guessed'}</span>
              </div>
              {step.type === 'draw'
                ? <div className="tel-reveal-img"><img src={step.data} alt="" className="tel-drawing"/></div>
                : <div className="tel-guess-bubble">"{step.text}"</div>}
            </div>
          ))}
        </div>
      )}
      {last && (
        <div className="tel-chain-end">
          <span className="kicker">Ended as</span>
          {last.type === 'guess'
            ? <div className="display" style={{ fontSize: compact ? 22 : 28, color: 'var(--ink)', marginTop: 4 }}>"{last.text}"</div>
            : <div className="tel-reveal-img" style={{ marginTop: 8 }}><img src={last.data} alt="" className="tel-drawing"/></div>}
        </div>
      )}
    </div>
  );
}

/* ---------- presentation mode ---------- */
function Presentation({ chains, order, onDone }) {
  const slides = useM(() => {
    const out = [];
    chains.forEach((chain, ci) => {
      out.push({ kind: 'title', chain: ci, prompt: chain.prompt, starter: chain.starter });
      chain.steps.forEach((step, si) => out.push({ kind: 'step', chain: ci, step, si }));
      out.push({ kind: 'compare', chainIdx: ci, chainData: chain });
    });
    return out;
  }, [chains]);
  const [idx, setIdx] = useT(0);
  const slide = slides[idx];
  const last = idx >= slides.length - 1;

  if (!slide) return null;

  return (
    <div className="app screen tel-present">
      <TopBar title="Presentation" color="var(--lime)"
        right={<span className="mono" style={{ fontSize: 11, color: '#9082BE' }}>{idx + 1}/{slides.length}</span>}/>
      <div className="center tel-present-body" style={{ flex: 1, padding: '16px 22px', gap: 12 }}>
        {slide.kind === 'title' && (
          <>
            <div className="kicker">Chain {slide.chain + 1}{slide.starter ? ` · ${slide.starter} started` : ''}</div>
            <div className="stamp sh-lime" style={{ fontSize: 38 }}>It started as…</div>
            <div className="bigcard" style={{ minHeight: 160 }}>
              <div className="q" style={{ fontSize: 34 }}>{slide.prompt}</div>
            </div>
          </>
        )}
        {slide.kind === 'step' && (
          <>
            <div className="tel-step-meta" style={{ justifyContent: 'center' }}>
              <Avatar name={slide.step.player} size={28}/>
              <b style={{ fontSize: 18 }}>{slide.step.player}</b>
              <span>{slide.step.type === 'draw' ? 'drew' : 'guessed'}</span>
            </div>
            {slide.step.type === 'draw'
              ? <div className="tel-reveal-img card" style={{ padding: 10, width: '100%', maxWidth: 400 }}><img src={slide.step.data} alt="" className="tel-drawing"/></div>
              : <div className="tel-guess-bubble present">"{slide.step.text}"</div>}
          </>
        )}
        {slide.kind === 'compare' && (
          <>
            <div className="stamp sh-coral" style={{ fontSize: 34 }}>The damage</div>
            <div className="tel-compare">
              <div className="tel-compare-side">
                <span className="kicker">Started</span>
                <div className="secret-word" style={{ fontSize: 22, color: 'var(--ink)' }}>{slide.chainData.prompt}</div>
              </div>
              <div className="tel-compare-arrow">→</div>
              <div className="tel-compare-side">
                <span className="kicker">Ended</span>
                {(() => {
                  const l = slide.chainData.steps[slide.chainData.steps.length - 1];
                  if (!l) return <span className="muted">—</span>;
                  return l.type === 'guess'
                    ? <div className="secret-word" style={{ fontSize: 20, color: 'var(--ink)' }}>"{l.text}"</div>
                    : <img src={l.data} alt="" className="tel-drawing" style={{ maxHeight: 120 }}/>;
                })()}
              </div>
            </div>
          </>
        )}
      </div>
      <div className="pad">
        <Btn color="lime" size="lg" onClick={() => last ? onDone() : setIdx(idx + 1)}>
          {last ? 'Awards & gallery →' : 'Next →'}
        </Btn>
      </div>
    </div>
  );
}

/* ---------- awards vote ---------- */
function AwardsVote({ chains, players, onDone }) {
  const [awardIdx, setAwardIdx] = useT(0);
  const [votes, setVotes] = useT({});
  const award = AWARDS[awardIdx];

  if (!award) return null;

  return (
    <div className="app screen">
      <TopBar title="Awards" color="var(--lime)"
        right={<span className="mono" style={{ fontSize: 11, color: '#9082BE' }}>{awardIdx + 1}/{AWARDS.length}</span>}/>
      <SecretBallot
        players={players}
        accent="var(--lime)"
        btnColor="lime"
        completeLabel="Tally →"
        onComplete={(v) => {
          const merged = { ...votes };
          players.forEach(p => { merged[`${award.key}:${p}`] = v[p]; });
          if (awardIdx + 1 >= AWARDS.length) onDone(merged);
          else { setVotes(merged); setAwardIdx(awardIdx + 1); }
        }}
        renderVote={(name, onPick) => (
          <div className="center" style={{ gap: 12, width: '100%', maxWidth: 380 }}>
            <div className="tier-pill" style={{ background: 'var(--lime)', color: 'var(--ink)' }}>{award.emoji} {award.label}</div>
            <p className="muted" style={{ fontSize: 14 }}>Pick the chain that wins this award.</p>
            <div className="col" style={{ gap: 8, width: '100%' }}>
              {chains.map((c, i) => (
                <button key={i} className="choice-btn" style={{ fontSize: 17, padding: 14, background: 'var(--cream)' }}
                  onClick={() => onPick(i)}>
                  Chain {i + 1}: "{c.prompt}" → "{c.steps[c.steps.length - 1]?.text || '…'}"
                </button>
              ))}
            </div>
          </div>
        )}
      />
    </div>
  );
}

function AwardResults({ chains, votes, players }) {
  return (
    <div className="col" style={{ gap: 14, width: '100%', maxWidth: 400 }}>
      {AWARDS.map(a => {
        const counts = {};
        players.forEach(p => {
          const v = votes[`${a.key}:${p}`];
          if (v != null) counts[v] = (counts[v] || 0) + 1;
        });
        const winner = Object.entries(counts).sort((x, y) => y[1] - x[1])[0];
        const chain = winner ? chains[Number(winner[0])] : null;
        return (
          <div key={a.key} className="card" style={{ padding: '14px 16px' }}>
            <div className="tier-pill" style={{ background: 'var(--gold)', color: 'var(--ink)', marginBottom: 8 }}>{a.emoji} {a.label}</div>
            {chain
              ? <p style={{ fontSize: 14, color: 'var(--ink)', fontWeight: 600 }}>Chain "{chain.prompt}" ({winner[1]} votes)</p>
              : <p className="muted" style={{ fontSize: 13 }}>No votes</p>}
          </div>
        );
      })}
    </div>
  );
}

/* ---------- share card export ---------- */
function exportChainCard(chain, idx) {
  const last = chain.steps[chain.steps.length - 1];
  const c = document.createElement('canvas');
  c.width = 800;
  c.height = 1000;
  const ctx = c.getContext('2d');
  ctx.fillStyle = '#FFF6E6';
  ctx.fillRect(0, 0, 800, 1000);
  ctx.fillStyle = '#1A1330';
  ctx.font = 'bold 36px Bricolage Grotesque, sans-serif';
  ctx.fillText('Drawing Telephone', 40, 56);
  ctx.font = '600 22px Hanken Grotesk, sans-serif';
  ctx.fillStyle = '#6B5F8A';
  ctx.fillText(`Chain ${idx + 1}`, 40, 90);
  ctx.fillStyle = '#1A1330';
  ctx.font = 'bold 28px Bricolage Grotesque, sans-serif';
  wrapText(ctx, 'Started: ' + chain.prompt, 40, 140, 720, 34);
  const endY = 280;
  ctx.strokeStyle = '#1A1330';
  ctx.lineWidth = 3;
  ctx.strokeRect(40, endY, 340, 340);
  ctx.strokeRect(420, endY, 340, 340);
  ctx.font = '600 16px Hanken Grotesk, sans-serif';
  ctx.fillStyle = '#6B5F8A';
  ctx.fillText('First draw', 50, endY + 24);
  ctx.fillText('Final', 430, endY + 24);
  const firstDraw = chain.steps.find(s => s.type === 'draw');
  const drawImg = (src, x, y, w, h, done) => {
    if (!src) { done(); return; }
    const img = new Image();
    img.onload = () => { ctx.drawImage(img, x, y, w, h); done(); };
    img.onerror = done;
    img.src = src;
  };
  return new Promise(resolve => {
    let n = 0;
    const check = () => { if (++n >= 2) resolve(c.toDataURL('image/png')); };
    drawImg(firstDraw?.data, 50, endY + 34, 320, 300, check);
    if (last?.type === 'draw') drawImg(last.data, 430, endY + 34, 320, 300, check);
    else {
      ctx.fillStyle = '#1A1330';
      ctx.font = 'bold 24px Bricolage Grotesque, sans-serif';
      wrapText(ctx, '"' + (last?.text || '?') + '"', 430, endY + 120, 320, 30);
      check();
    }
  });
}

function wrapText(ctx, text, x, y, maxW, lineH) {
  const words = text.split(' ');
  let line = '';
  let cy = y;
  words.forEach((w, i) => {
    const test = line + w + ' ';
    if (ctx.measureText(test).width > maxW && i > 0) {
      ctx.fillText(line, x, cy);
      line = w + ' ';
      cy += lineH;
    } else line = test;
  });
  ctx.fillText(line, x, cy);
}

/* ---------- guest / waiting screens ---------- */
function TelWaiting({ label, sub }) {
  return (
    <div className="center screen" style={{ flex: 1, padding: 30, gap: 12 }}>
      <div className="stamp sh-lime" style={{ fontSize: 42 }}>Hang tight</div>
      <p className="serif-i" style={{ fontSize: 20, color: '#FFD79A' }}>{label}</p>
      {sub && <p className="muted" style={{ maxWidth: 300, fontSize: 14 }}>{sub}</p>}
    </div>
  );
}

function TelGuestJoin({ room, players, onJoin, onBack }) {
  const [name, setName] = useT('');
  const [connecting, setConnecting] = useT(false);
  const [err, setErr] = useT('');
  const join = async () => {
    const n = name.trim();
    if (!n) return;
    setConnecting(true);
    setErr('');
    try { await onJoin(n); }
    catch (e) { setErr(e?.message || 'Could not connect'); setConnecting(false); }
  };
  return (
    <div className="app screen">
      <TopBar onBack={onBack} title="Join game" color="var(--lime)"/>
      <div className="setup">
        <h2>Join Drawing Telephone</h2>
        <p className="lead">Room <b className="mono">{room}</b> — pick your name and connect on your phone.</p>
        <div className="field">
          <label>Who are you?</label>
          <div className="wrap" style={{ gap: 8 }}>
            {players.map(p => (
              <button key={p} className={'chip selectable' + (name === p ? ' sel' : '')} onClick={() => setName(p)}>{p}</button>
            ))}
          </div>
          <input className="addinput" style={{ marginTop: 10 }} value={name} placeholder="Or type your name…" maxLength={14}
            onChange={e => setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && join()}/>
        </div>
        {err && <p style={{ color: 'var(--coral)', fontSize: 14, marginBottom: 12 }}>{err}</p>}
        <Btn color="lime" size="lg" disabled={!name.trim() || connecting} onClick={join}>
          {connecting ? 'Connecting…' : 'Connect →'}
        </Btn>
      </div>
    </div>
  );
}

/* ---------- main game ---------- */
function GameTelephone({ game, players, onExit, onHome }) {
  const leave = onHome || onExit;
  const telParams = useM(() => window.TelephoneSync?.parseTelParams(), []);
  const isGuestEntry = !!(telParams?.room && !telParams.player);

  const [cats, setCats] = useT(() => new Set(DATA.TELEPHONE_CATS));
  const [mode, setMode] = useT('quick');
  const [drawSecs, setDrawSecs] = useT(45);
  const [guessSecs, setGuessSecs] = useT(GUESS_SECS_DEFAULT);
  const [sayAloud, setSayAloud] = useT(false);
  const [multiDevice, setMultiDevice] = useT(false);
  const [customPrompt, setCustomPrompt] = useT('');
  const [presentOrder, setPresentOrder] = useT('chain');
  const [phase, setPhase] = useT(isGuestEntry ? 'guest_join' : 'setup');
  const [order, setOrder] = useT([]);
  const [chains, setChains] = useT([]);
  const [turnIndex, setTurnIndex] = useT(0);
  const [passReady, setPassReady] = useT(false);
  const [promptCollapsed, setPromptCollapsed] = useT(false);
  const [awardVotes, setAwardVotes] = useT(null);
  const [roomCode, setRoomCode] = useT(null);
  const [joinUrl, setJoinUrl] = useT('');
  const [connectedGuests, setConnectedGuests] = useT({});
  const [guestRole, setGuestRole] = useT(null);
  const [hostWaiting, setHostWaiting] = useT(null);
  const [lapChainIdx, setLapChainIdx] = useT(0);
  const syncRef = useRT(null);
  const turnRef = useRT(null);

  const toggleCat = (c) => setCats(s => { const n = new Set(s); n.has(c) ? n.delete(c) : n.add(c); return n; });

  useET(() => {
    if (phase !== 'reveal') return;
    window.partySounds?.play('reveal');
    const slam = setTimeout(() => window.partySounds?.play('slam'), 380);
    const cheer = setTimeout(() => window.partySounds?.play('celebrate'), 650);
    return () => { clearTimeout(slam); clearTimeout(cheer); };
  }, [phase]);

  const persist = useCT((extra = {}) => {
    saveSession({
      mode, drawSecs, guessSecs, sayAloud, multiDevice, customPrompt, presentOrder,
      order, chains, turnIndex, phase, roomCode, cats: [...cats], ...extra,
    });
  }, [mode, drawSecs, guessSecs, sayAloud, multiDevice, customPrompt, presentOrder, order, chains, turnIndex, phase, roomCode, cats]);

  useET(() => {
    const saved = loadSession();
    if (saved?.chains?.length && saved.turnIndex < (saved.chains[0]?.steps ? 999 : 0)) {
      /* offer resume via setup only */
    }
  }, []);

  const buildChains = (ord, m, custom) => {
    const n = ord.length;
    const count = chainCountForMode(m);
    const pool = [...cats].flatMap(c => DATA.TELEPHONE[c].map(w => ({ w, cat: c })));
    const shuffled = shuffle(pool.length ? pool : DATA.TELEPHONE.Simple.map(w => ({ w, cat: 'Simple' })));
    const picked = shuffled.slice(0, count);
    if (custom.trim()) picked[0] = { w: custom.trim(), cat: 'Custom' };
    return picked.map((p, i) => ({
      prompt: p.w,
      starter: count > 1 ? ord[i] : ord[0],
      steps: [],
    }));
  };

  const destroySync = useCT(() => {
    syncRef.current?.destroy();
    syncRef.current = null;
  }, []);

  useET(() => () => destroySync(), [destroySync]);

  const setupHostSync = async (code) => {
    destroySync();
    const sync = new window.TelephoneSync.TelephoneSync({
      role: 'host',
      roomCode: code,
      onEvent: (ev) => {
        if (ev.type === 'host_ready') setJoinUrl(ev.joinUrl);
        if (ev.type === 'guest_hello') {
          setConnectedGuests(g => ({ ...g, [ev.peerId]: ev.playerName }));
          sync.hostReply(ev.peerId, { type: 'roster', players });
        }
        if (ev.type === 'guest_disconnected') {
          setConnectedGuests(g => { const n = { ...g }; delete n[ev.peerId]; return n; });
        }
        if (ev.type === 'guest_submit' && ev.payload) {
          handleRemoteSubmit(ev.playerName, ev.payload);
        }
      },
    });
    syncRef.current = sync;
    await sync.startHost();
    sync.hostBroadcast({ type: 'roster', players });
  };

  const handleRemoteSubmit = (playerName, payload) => {
    const info = turnRef.current;
    if (info.player !== playerName) return;
    completeTurn(payload);
    setHostWaiting(null);
  };

  const startGame = async (resumeState) => {
    const ord = resumeState?.order || shuffle(players);
    const m = resumeState?.mode || mode;
    const ch = resumeState?.chains || buildChains(ord, m, resumeState?.customPrompt ?? customPrompt);
    const ti = resumeState?.turnIndex ?? 0;
    setOrder(ord);
    setChains(ch);
    setTurnIndex(ti);
    setPassReady(false);
    setPromptCollapsed(false);

    if (multiDevice && !resumeState) {
      const code = window.TelephoneSync.makeRoomCode();
      setRoomCode(code);
      setPhase('host_lobby');
      try {
        await setupHostSync(code);
      } catch {
        setPhase('setup');
        return;
      }
      return;
    }

    setPhase(ti > 0 ? 'pass' : 'pass');
    persist({ order: ord, chains: ch, turnIndex: ti, phase: 'pass' });
  };

  const beginFromLobby = () => {
    setPhase('pass');
    const first = getTurnInfo(mode, order, 0);
    syncRef.current?.hostPushState({ turnIndex: 0, phase: 'pass', chains, order, mode });
    if (multiDevice && syncRef.current) {
      syncRef.current.hostBroadcast({ type: 'waiting', label: `Waiting for ${first.player} to ${first.action}…` });
    }
    persist({ phase: 'pass' });
  };

  const joinAsGuest = async (playerName) => {
    const sync = new window.TelephoneSync.TelephoneSync({
      role: 'guest',
      roomCode: telParams.room,
      playerName,
      onEvent: (ev) => {
        if (ev.type === 'message') {
          const d = ev.data;
          if (d.type === 'roster' && d.players?.length) { window.__telRoster = d.players; }
          if (d.type === 'state') {
            setChains(d.state.chains || []);
            setOrder(d.state.order || []);
            setTurnIndex(d.state.turnIndex || 0);
            setMode(d.state.mode || 'quick');
            setPhase(d.state.phase || 'pass');
          }
          if (d.type === 'turn') {
            if (d.target === playerName) {
              setGuestRole({ action: d.action, prompt: d.prompt, sub: d.sub, image: d.image, drawSecs: d.drawSecs, guessSecs: d.guessSecs, sayAloud: d.sayAloud });
              setPhase(d.action === 'draw' ? 'guest_draw' : 'guest_guess');
            } else setPhase('guest_wait');
          }
          if (d.type === 'waiting') setHostWaiting(d.label);
        }
        if (ev.type === 'disconnected') setPhase('guest_join');
      },
    });
    syncRef.current = sync;
    await sync.joinGuest();
    setGuestRole({ playerName });
    setPhase('guest_wait');
  };

  const turn = getTurnInfo(mode, order, turnIndex);
  turnRef.current = turn;
  const chain = chains[turn.chainIdx];
  const etaMin = Math.max(1, Math.ceil((turn.total - turnIndex) * avgSecPerTurn(drawSecs, guessSecs) / 60));

  const getDrawContent = () => {
    if (!chain) return { prompt: '', sub: null };
    if (turn.step === 0) return { prompt: chain.prompt, sub: null };
    const prev = chain.steps[turn.step - 1];
    if (prev?.type === 'guess') return { prompt: prev.text, sub: "Draw this guess — you haven't seen the original!" };
    return { prompt: chain.prompt, sub: null };
  };

  const getPrevDrawing = () => {
    const prev = chain?.steps[turn.step - 1];
    return prev?.type === 'draw' ? prev.data : null;
  };

  const broadcastTurn = (info) => {
    if (!multiDevice || !syncRef.current) return;
    const { prompt, sub } = info.action === 'draw' ? getDrawContent() : {};
    syncRef.current.hostBroadcast({
      type: 'turn',
      target: info.player,
      action: info.action,
      prompt,
      sub,
      image: info.action === 'guess' ? getPrevDrawing() : null,
      drawSecs,
      guessSecs,
      sayAloud,
    });
    syncRef.current.hostBroadcast({
      type: 'waiting',
      label: `Waiting for ${info.player} to ${info.action}…`,
    });
  };

  const completeTurn = (payload) => {
    const info = turnRef.current;
    setChains(cs => {
      const next = cs.map(c => ({ ...c, steps: [...c.steps] }));
      next[info.chainIdx].steps.push({
        type: info.action,
        player: info.player,
        ...(info.action === 'draw' ? { data: payload } : { text: payload }),
      });
      return next;
    });

    const nextTurn = turnIndex + 1;
    const nextInfo = getTurnInfo(mode, order, nextTurn);

    if (info.isLapEnd && nextTurn < info.total) {
      setLapChainIdx(info.chainIdx);
      setTurnIndex(nextTurn);
      setPassReady(false);
      setPhase('lap');
      persist({ turnIndex: nextTurn, phase: 'lap' });
      return;
    }

    if (nextTurn >= info.total) {
      clearSession();
      destroySync();
      setChains(cs => {
        let next = [...cs];
        if (presentOrder === 'random') next = shuffle(next);
        else if (presentOrder === 'starter') next = [...next].sort((a, b) => order.indexOf(a.starter) - order.indexOf(b.starter));
        return next;
      });
      setPhase('present');
      return;
    }

    setTurnIndex(nextTurn);
    setPassReady(false);
    setPhase('pass');
    if (multiDevice && syncRef.current) {
      syncRef.current.hostPushState({ chains, order, turnIndex: nextTurn, phase: 'pass', mode });
      broadcastTurn(nextInfo);
    }
    persist({ turnIndex: nextTurn, phase: 'pass' });
  };

  const guestComplete = (payload) => {
    syncRef.current?.guestSubmit(payload);
    setGuestRole(null);
    setPhase('guest_wait');
  };

  const backFromTurn = () => { setPassReady(false); setPhase('pass'); };

  const sortedChains = chains;

  /* ---- GUEST JOIN (URL) ---- */
  if (phase === 'guest_join') {
    return (
      <TelGuestJoin
        room={telParams.room}
        players={(window.__telRoster?.length ? window.__telRoster : null) || (players.length ? players : ['Player 1', 'Player 2', 'Player 3'])}
        onJoin={joinAsGuest}
        onBack={leave}
      />
    );
  }

  if (phase === 'guest_wait') {
    return (
      <div className="app screen">
        <TopBar title="Drawing Telephone" color={game.color}/>
        <TelWaiting label={hostWaiting || 'Waiting for your turn…'} sub="Keep this screen open." />
      </div>
    );
  }

  if (phase === 'guest_draw' && guestRole) {
    return (
      <DrawPad
        prompt={guestRole.prompt}
        subPrompt={guestRole.sub}
        drawSecs={guestRole.drawSecs || drawSecs}
        collapsed={promptCollapsed}
        onTogglePrompt={() => setPromptCollapsed(c => !c)}
        onDone={guestComplete}
        onBack={() => setPhase('guest_wait')}
      />
    );
  }

  if (phase === 'guest_guess' && guestRole?.image) {
    return (
      <GuessPad
        image={guestRole.image}
        guessSecs={guestRole.guessSecs || guessSecs}
        sayAloud={guestRole.sayAloud}
        onDone={guestComplete}
        onBack={() => setPhase('guest_wait')}
      />
    );
  }

  /* ---- SETUP ---- */
  if (phase === 'setup') {
    const saved = loadSession();
    return (
      <div className="app screen">
        <TopBar onBack={onExit} title="Drawing Telephone" color={game.color}/>
        <div className="setup">
          <h2>Drawing Telephone</h2>
          <p className="lead">Draw → guess → draw. Meaning collapses. Presentation mode at the end.</p>
          {saved?.chains?.length > 0 && saved.turnIndex < 200 && (
            <div className="card" style={{ padding: '14px 16px', marginBottom: 18, borderColor: 'var(--lime)' }}>
              <p style={{ fontSize: 14, color: 'var(--ink)', fontWeight: 600 }}>Resume game in progress?</p>
              <p className="muted" style={{ fontSize: 12, marginTop: 4 }}>Turn {saved.turnIndex + 1} · {saved.mode} mode</p>
              <Btn color="lime" style={{ marginTop: 10 }} onClick={() => {
                setMode(saved.mode || 'quick');
                setDrawSecs(saved.drawSecs || 45);
                setGuessSecs(saved.guessSecs || GUESS_SECS_DEFAULT);
                setSayAloud(!!saved.sayAloud);
                setMultiDevice(!!saved.multiDevice);
                setCustomPrompt(saved.customPrompt || '');
                if (saved.cats) setCats(new Set(saved.cats));
                startGame(saved);
              }}>Resume →</Btn>
            </div>
          )}
          <div className="field">
            <label>Mode</label>
            <Segmented color="var(--lime)" value={mode} onChange={setMode}
              options={[
                { value: 'quick', label: 'Quick' },
                { value: 'classic', label: 'Classic' },
                { value: 'double', label: 'Double' },
              ]}/>
            <p className="muted" style={{ fontSize: 13, marginTop: 8 }}>
              {mode === 'quick' && `One chain · ${chainLength(players.length)} turns · ends on a guess.`}
              {mode === 'classic' && 'Same as Quick — always ends on text.'}
              {mode === 'double' && `Two chains · ${chainLength(players.length) * 2} turns total.`}
            </p>
          </div>
          <div className="field">
            <label>Custom starter (optional)</label>
            <input className="addinput" value={customPrompt} placeholder="e.g. nervous penguin at the DMV" maxLength={60}
              onChange={e => setCustomPrompt(e.target.value)}/>
          </div>
          <div className="field">
            <label>Draw time</label>
            <Segmented color="var(--lime)" value={drawSecs} onChange={setDrawSecs}
              options={[{ value: 30, label: '30s' }, { value: 45, label: '45s' }, { value: 60, label: '60s' }, { value: 90, label: '90s' }]}/>
          </div>
          <div className="field">
            <label>Guess time</label>
            <Segmented color="var(--lime)" value={guessSecs} onChange={setGuessSecs}
              options={[{ value: 15, label: '15s' }, { value: 20, label: '20s' }, { value: 30, label: '30s' }]}/>
          </div>
          <div className="toggle-row" style={{ marginBottom: 14 }}>
            <Switch on={sayAloud} onChange={setSayAloud}/>
            <div className="lbl"><b>Say-it-aloud guesses</b><small>Guess spoken aloud, witness types it in</small></div>
          </div>
          <div className="toggle-row" style={{ marginBottom: 14 }}>
            <Switch on={multiDevice} onChange={setMultiDevice}/>
            <div className="lbl"><b>Everyone on their own phone</b><small>Host shares a room link — no passing</small></div>
          </div>
          <div className="field">
            <label>Reveal order</label>
            <Segmented color="var(--lime)" value={presentOrder} onChange={setPresentOrder}
              options={[{ value: 'chain', label: 'In order' }, { value: 'random', label: 'Random' }, { value: 'starter', label: 'By starter' }]}/>
          </div>
          <div className="field">
            <label>Prompt packs</label>
            <div className="wrap" style={{ gap: 8 }}>
              {DATA.TELEPHONE_CATS.map(c => (
                <button key={c} onClick={() => toggleCat(c)} className={'chip selectable' + (cats.has(c) ? ' sel' : '')} style={{ fontSize: 14 }}>
                  {cats.has(c) ? '✓ ' : ''}{c}
                </button>
              ))}
            </div>
          </div>
          <Btn color="lime" size="lg" disabled={cats.size === 0 && !customPrompt.trim()} onClick={() => startGame()}>Start →</Btn>
        </div>
      </div>
    );
  }

  /* ---- HOST LOBBY (multi-device) ---- */
  if (phase === 'host_lobby') {
    const connected = Object.values(connectedGuests);
    return (
      <div className="app screen">
        <TopBar onBack={() => { destroySync(); setPhase('setup'); }} title="Room lobby" color={game.color}/>
        <div className="setup">
          <h2 style={{ fontSize: 26 }}>Room {roomCode}</h2>
          <p className="lead">Friends open this link on their phones and pick their name.</p>
          <div className="card" style={{ padding: 16, wordBreak: 'break-all', fontSize: 13, color: 'var(--ink)', marginBottom: 16 }}>
            {joinUrl || window.TelephoneSync.joinUrl(roomCode)}
          </div>
          <div className="field">
            <label>Connected ({connected.length}/{players.length})</label>
            <div className="wrap" style={{ gap: 8 }}>
              {players.map(p => (
                <span key={p} className="chip" style={{ opacity: connected.includes(p) ? 1 : 0.45 }}>
                  <Avatar name={p} size={18}/>{p}{connected.includes(p) ? ' ✓' : ''}
                </span>
              ))}
            </div>
          </div>
          <Btn color="lime" size="lg" onClick={beginFromLobby}>Start game →</Btn>
          <p className="muted" style={{ fontSize: 12, marginTop: 12 }}>Pass-the-phone still works for players without a device.</p>
        </div>
      </div>
    );
  }

  /* ---- LAP CHECK ---- */
  if (phase === 'lap') {
    const lapChain = chains[lapChainIdx];
    if (!lapChain) { setPhase('pass'); return null; }
    const latest = lapChain.steps[lapChain.steps.length - 1];
    return (
      <div className="app screen">
        <Burst fire colors={['#9AE66E', '#FFC22E']}/>
        <TopBar title="Chain check" color={game.color}/>
        <div className="center" style={{ flex: 1, padding: '20px 24px', gap: 14 }}>
          <div className="kicker">After one full lap</div>
          <div className="stamp sh-lime" style={{ fontSize: 36 }}>How's it going?</div>
          <p className="muted" style={{ fontSize: 14 }}>Started as <b style={{ color: 'var(--cream)' }}>{lapChain.prompt}</b></p>
          {latest?.type === 'guess'
            ? <div className="tel-guess-bubble present">"{latest.text}"</div>
            : latest?.data && <div className="tel-reveal-img card" style={{ padding: 8 }}><img src={latest.data} alt="" className="tel-drawing"/></div>}
        </div>
        <div className="pad">
          <Btn color="lime" size="lg" onClick={() => {
            setPassReady(false);
            setPhase('pass');
            if (multiDevice && syncRef.current) broadcastTurn(getTurnInfo(mode, order, turnIndex));
          }}>Keep going →</Btn>
        </div>
      </div>
    );
  }

  /* ---- PASS / PLAY ---- */
  if (phase === 'pass') {
    const info = turn;
    const pct = ((turnIndex) / info.total) * 100;

    if (!passReady) {
      return (
        <div className="reveal-screen screen">
          <TopBar onBack={onExit} title="Drawing Telephone" color={game.color}
            right={<span className="mono" style={{ fontSize: 11, color: '#9082BE' }}>{turnIndex + 1}/{info.total}</span>}/>
          <div className="tel-progress-wrap pad" style={{ paddingTop: 0, paddingBottom: 8 }}>
            <div className="timerbar"><i style={{ width: pct + '%', background: 'var(--lime)' }}/></div>
            <p className="mono" style={{ fontSize: 11, color: '#897BB6', marginTop: 6 }}>~{etaMin} min left</p>
          </div>
          <div className="kicker">Pass the phone to</div>
          <div className="mt16" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 18 }}>
            <div style={{ transform: 'scale(1.8)' }}><Avatar name={info.player} size={46}/></div>
            <div className="display wiggle" style={{ fontSize: 52 }}>{info.player}</div>
          </div>
          <p className="muted mt16">
            {info.action === 'draw'
              ? (turn.step === 0 ? 'Draw the secret prompt 🎨' : 'Draw the last guess 🎨')
              : 'Guess the drawing 👀'}
          </p>
          {info.chains > 1 && <p className="mono" style={{ fontSize: 11, color: '#7E70AE', marginTop: 8 }}>Chain {info.chainIdx + 1} of {info.chains}</p>}
          <div style={{ width: '100%', maxWidth: 340, marginTop: 24 }}>
            <Btn color="lime" size="lg" sound="pass" onClick={() => {
              setPassReady(true);
              if (multiDevice) broadcastTurn(info);
            }}>Tap to {info.action} →</Btn>
          </div>
        </div>
      );
    }
    if (info.action === 'draw') {
      const { prompt, sub } = getDrawContent();
      return (
        <DrawPad prompt={prompt} subPrompt={sub} drawSecs={drawSecs} collapsed={promptCollapsed}
          onTogglePrompt={() => setPromptCollapsed(c => !c)} onDone={completeTurn} onBack={backFromTurn}/>
      );
    }
    const img = getPrevDrawing();
    if (!img) {
      return (
        <div className="center screen" style={{ flex: 1, padding: 30 }}>
          <p className="muted">Something went wrong — no drawing to guess.</p>
          <Btn color="lime" onClick={onExit}>Exit</Btn>
        </div>
      );
    }
    return <GuessPad image={img} guessSecs={guessSecs} sayAloud={sayAloud} onDone={completeTurn} onBack={backFromTurn}/>;
  }

  /* ---- PRESENTATION ---- */
  if (phase === 'present') {
    return (
      <Presentation chains={sortedChains} order={order} onDone={() => setPhase('awards')}/>
    );
  }

  /* ---- AWARDS ---- */
  if (phase === 'awards') {
    return (
      <AwardsVote chains={sortedChains} players={players} onDone={(v) => { setAwardVotes(v); setPhase('reveal'); }}/>
    );
  }

  /* ---- REVEAL GALLERY ---- */
  if (phase === 'reveal') {
    const share = async (ci) => {
      const data = await exportChainCard(sortedChains[ci], ci);
      const a = document.createElement('a');
      a.href = data;
      a.download = `telephone-chain-${ci + 1}.png`;
      a.click();
    };
    return (
      <div className="app screen reveal-cinematic" style={{ overflow: 'auto' }}>
        <div className="reveal-flash" aria-hidden="true"/>
        <Burst fire={true} colors={['#9AE66E', '#FF5C9D', '#FFC22E', '#3D8BFF']}/>
        <TopBar title="The chains" color={game.color}/>
        <div className="center" style={{ padding: '18px 26px 8px', gap: 6 }}>
          <div className="kicker reveal-kicker-in">the damage is done</div>
          <div className="stamp sh-lime stamp-slam" style={{ fontSize: 42 }}>Chain reveal!</div>
        </div>
        <div className="pad reveal-body-in" style={{ paddingTop: 0, paddingBottom: 30 }}>
          {awardVotes && <AwardResults chains={sortedChains} votes={awardVotes} players={players}/>}
          <div className="kicker" style={{ margin: '18px 0 14px' }}>full gallery</div>
          <div className="col" style={{ gap: 18 }}>
            {sortedChains.map((c, i) => (
              <div key={i}>
                <ChainCard chain={c} idx={i}/>
                <Btn variant="ghost" style={{ marginTop: 8 }} onClick={() => share(i)}>Share chain {i + 1} ↓</Btn>
              </div>
            ))}
          </div>
          <div className="btn-row" style={{ marginTop: 24 }}>
            <Btn variant="ghost" onClick={() => { clearSession(); leave(); }}>Home</Btn>
            <Btn color="lime" onClick={() => { clearSession(); setPhase('setup'); }}>Play again ↻</Btn>
          </div>
        </div>
      </div>
    );
  }

  return null;
}

window.GameTelephone = GameTelephone;
