import { useState, useEffect, useRef } from 'react';
import { useNavigate, useLocation, Navigate } from 'react-router-dom';
import treeBg from '../assets/tree_bg_svg.svg';

const STAGE_SIZE = 11;
const API = import.meta.env.VITE_API_URL;

const NAV_BTN = {
  padding: '14px 40px', borderRadius: 999,
  fontFamily: 'system-ui,sans-serif', fontSize: 17, fontWeight: 600,
  cursor: 'pointer', border: '1.5px solid #b6d8f5',
  background: '#e8f4fd', color: '#1a73e8',
};

const FORFEIT_BTN = {
  padding: '14px 28px', borderRadius: 999, fontFamily: 'system-ui,sans-serif',
  fontSize: 14, fontWeight: 600, cursor: 'pointer',
  border: '1.5px solid #e57373', background: '#fff', color: '#c62828',
};

function authHeaders() {
  const token = sessionStorage.getItem('ranked_token');
  return token ? { Authorization: `Bearer ${token}` } : {};
}

function SpeechBubbleIcon({ size = 44 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 44 44" fill="none">
      <rect x="4" y="4" width="30" height="24" rx="6" stroke="white" strokeWidth="2.2" />
      <path d="M9 28 L6 38 L20 28" stroke="white" strokeWidth="2.2" strokeLinejoin="round" fill="none" />
    </svg>
  );
}

function GlobalStyles() {
  return (
    <style>{`
      @keyframes emote-fade { 0%,65%{opacity:1;}100%{opacity:0;} }
      @keyframes slide-in-right { from{transform:translateX(100%);}to{transform:translateX(0);} }
      @keyframes done-overlay { 0%,70%{opacity:1;}100%{opacity:0;} }
      @keyframes spin { to{transform:rotate(360deg);} }
      @keyframes questionEnter { from{opacity:0;transform:translateX(20px)} to{opacity:1;transform:translateX(0)} }
      @keyframes answerPulse { 0%{transform:scale(1)} 50%{transform:scale(0.97)} 100%{transform:scale(1)} }
      @keyframes bubblePop { 0%{transform:scale(0.7)} 70%{transform:scale(1.1)} 100%{transform:scale(1)} }
      @keyframes nameFlash { 0%{color:#e67e22} 50%{color:#f5c518} 100%{color:#e67e22} }
      @keyframes timerPulse { 0%{color:#1a73e8} 50%{color:#ef4444} 100%{color:#1a73e8} }
      @keyframes notifBounce { 0%{transform:translateX(120%)} 80%{transform:translateX(-8px)} 100%{transform:translateX(0)} }
      @keyframes stageReveal { from{opacity:0;transform:translateY(24px)} to{opacity:1;transform:translateY(0)} }
      @keyframes submitGlow { from{box-shadow:0 0 8px #ff69b4aa} to{box-shadow:0 0 22px #ff69b4, 0 0 40px #ff69b466} }
      @keyframes slideInLeft { from{opacity:0;transform:translateX(-30px)} to{opacity:1;transform:translateX(0)} }
      @keyframes slideInRight { from{opacity:0;transform:translateX(30px)} to{opacity:1;transform:translateX(0)} }
      @keyframes overlayFade { from{opacity:0} to{opacity:1} }
      @keyframes resultReveal { from{opacity:0;transform:scale(0.85)} to{opacity:1;transform:scale(1)} }
      @keyframes defeatTitleIn { from{opacity:0;transform:translateY(-16px)} to{opacity:1;transform:translateY(0)} }
      @keyframes statsRowIn { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} }
      @keyframes screenFlash { 0%{opacity:1} 100%{opacity:0} }
    `}</style>
  );
}

function OpponentProgressStrip({ progress, total, name, dced, disconnected, emoteBubble, stageLabel }) {
  const prevProgressRef = useRef(progress);
  const [nameFlashing, setNameFlashing] = useState(false);

  useEffect(() => {
    if (progress > prevProgressRef.current) {
      setNameFlashing(true);
      const t = setTimeout(() => setNameFlashing(false), 500);
      prevProgressRef.current = progress;
      return () => clearTimeout(t);
    }
    prevProgressRef.current = progress;
  }, [progress]);

  const items = [];
  for (let qi = 0; qi < total; qi++) {
    const filled = qi < progress;
    items.push(
      <div key={`b-${qi}`} style={{ width: 34, height: 34, borderRadius: '50%', border: `2px solid ${filled ? '#e67e22' : '#ccc'}`, background: filled ? '#e67e22' : '#fff', color: filled ? '#fff' : '#aaa', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, fontFamily: 'system-ui,-apple-system,sans-serif', flexShrink: 0, padding: 0, boxSizing: 'border-box', transition: 'background 0.15s, border-color 0.15s' }}>{qi + 1}</div>
    );
    if (qi < total - 1) {
      items.push(<div key={`l-${qi}`} style={{ flex: 1, height: 3, minWidth: 2, background: filled ? '#e67e22' : '#ddd' }} />);
    }
  }
  return (
    <div style={{ position: 'relative' }}>
      {emoteBubble && (
        <div key={emoteBubble.key} style={{
          position: 'absolute', top: -36, left: 58,
          background: '#1a1a2a', color: '#fff', borderRadius: 10,
          padding: '5px 12px', fontSize: 13, fontWeight: 600,
          border: '1px solid rgba(255,255,255,0.15)', pointerEvents: 'none',
          whiteSpace: 'nowrap', animation: 'emote-fade 3s forwards',
          boxShadow: '0 2px 8px rgba(0,0,0,0.4)', zIndex: 10,
        }}>
          {emoteBubble.text}
          <div style={{ position: 'absolute', bottom: -6, left: 14, width: 0, height: 0, borderLeft: '6px solid transparent', borderRight: '6px solid transparent', borderTop: '6px solid #1a1a2a' }} />
        </div>
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{ width: 120, minWidth: 120, maxWidth: 120, display: 'flex', flexDirection: 'column', gap: 2, flexShrink: 0, overflow: 'hidden' }}>
          <span style={{ fontSize: 11, fontWeight: 700, color: (dced || disconnected) ? '#c62828' : '#e67e22', textTransform: 'uppercase', letterSpacing: '0.05em', fontFamily: 'system-ui,sans-serif', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', animation: nameFlashing ? 'nameFlash 0.5s ease-out' : 'none' }}>{name}</span>
          {dced && <span style={{ fontSize: 10, fontWeight: 700, color: '#fff', background: '#c62828', borderRadius: 4, padding: '1px 5px', fontFamily: 'system-ui,sans-serif', letterSpacing: '0.04em' }}>DC'd</span>}
          {!dced && disconnected && <span style={{ fontSize: 10, fontWeight: 700, color: '#fff', background: '#e57373', borderRadius: 4, padding: '1px 5px', fontFamily: 'system-ui,sans-serif', letterSpacing: '0.04em' }}>disconnected?</span>}
          {stageLabel && <span style={{ fontSize: 10, color: '#aaa', fontFamily: 'system-ui,sans-serif' }}>Stage {stageLabel}</span>}
        </div>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', flexWrap: 'nowrap', opacity: (dced || disconnected) ? 0.5 : 1 }}>
          {items}
        </div>
      </div>
    </div>
  );
}

function ProgressStrip({ total, current, answers, onJump }) {
  const [fillCounts, setFillCounts] = useState({});
  const prevAnswersRef = useRef({});

  useEffect(() => {
    const updates = {};
    for (let qi = 0; qi < total; qi++) {
      if (answers[qi] != null && prevAnswersRef.current[qi] == null) updates[qi] = true;
    }
    if (Object.keys(updates).length > 0) {
      setFillCounts(prev => {
        const next = { ...prev };
        for (const qi of Object.keys(updates)) next[Number(qi)] = (prev[Number(qi)] || 0) + 1;
        return next;
      });
    }
    prevAnswersRef.current = answers;
  }, [answers, total]);

  const items = [];
  for (let qi = 0; qi < total; qi++) {
    const active = qi === current, answered = answers[qi] != null, filled = answered;
    const popCount = fillCounts[qi] || 0;
    items.push(
      <button key={`b-${qi}-${popCount}`} onClick={() => onJump && onJump(qi)} style={{ width: 34, height: 34, borderRadius: '50%', border: `2px solid ${filled || active ? '#1a73e8' : '#ccc'}`, background: filled ? '#1a73e8' : active ? '#e8f0fe' : '#fff', color: filled ? '#fff' : active ? '#1a73e8' : '#aaa', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, fontFamily: 'system-ui,-apple-system,sans-serif', flexShrink: 0, cursor: 'pointer', padding: 0, boxSizing: 'border-box', transition: 'background 0.15s, border-color 0.15s', animation: popCount > 0 ? 'bubblePop 0.25s ease-out' : 'none' }}>{qi + 1}</button>
    );
    if (qi < total - 1) items.push(<div key={`l-${qi}`} style={{ flex: 1, height: 3, minWidth: 2, background: answers[qi] != null ? '#1a73e8' : '#ddd' }} />);
  }
  return <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'nowrap', width: '100%' }}>{items}</div>;
}

function QuestionScreen({ question: q, qIdx, answers, selected, onSelect, onNext, onBack, onJump, opponentProgress, opponentName, opponentDced, opponentConnected, onForfeit, emoteBubble, stageNum, timerDisplay, retryBanner, opponentStage, grade }) {
  const [pulsingIdx,  setPulsingIdx]  = useState(null);
  const [hoverIdx,    setHoverIdx]    = useState(null);
  const [timerFlash,  setTimerFlash]  = useState(false);
  const lastPulseRef = useRef(-1);

  useEffect(() => {
    const handler = (e) => {
      if (e.target.tagName === 'INPUT') return;
      const i = +e.key - 1;
      if (i >= 0 && i < q.choices.length) { onSelect(i); return; }
      if ((e.key === 'Enter' || e.key === 'ArrowRight') && qIdx < STAGE_SIZE - 1) onNext();
      if (e.key === 'ArrowLeft' && qIdx > 0) onBack();
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [q.choices.length, qIdx, onSelect, onNext, onBack]);

  useEffect(() => {
    if (!timerDisplay) return;
    const parts = timerDisplay.split(':');
    if (parts.length !== 2) return;
    const totalSeconds = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
    if (totalSeconds > 0 && totalSeconds % 30 === 0 && totalSeconds !== lastPulseRef.current) {
      lastPulseRef.current = totalSeconds;
      setTimerFlash(true);
      const t = setTimeout(() => setTimerFlash(false), 500);
      return () => clearTimeout(t);
    }
  }, [timerDisplay]);

  return (
    <div style={{ background: '#07080f', minHeight: '100vh', fontFamily: 'system-ui,-apple-system,sans-serif' }}>
      <div className="arena-bg"><div className="arena-grain" /></div>
      <div style={{ maxWidth: 760, margin: '0 auto', background: '#fff', minHeight: '100vh', display: 'flex', flexDirection: 'column', position: 'relative', zIndex: 1, borderTop: '3px solid #1a73e8', boxShadow: '0 0 60px rgba(26,115,232,0.08), 0 0 40px rgba(0,60,120,0.10)' }}>
        {retryBanner != null && (
          <div style={{ background: '#fef2f2', borderBottom: '1px solid #fca5a5', padding: '10px 32px', fontSize: 14, fontWeight: 600, color: '#991b1b', fontFamily: 'system-ui,sans-serif' }}>
            {retryBanner === 1 ? '1 answer incorrect' : `${retryBanner} answers incorrect`} — fix them and resubmit
          </div>
        )}
        <div style={{ borderBottom: '1px solid #e8e8e8', padding: '20px 32px 16px' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <p style={{ fontSize: 36, fontWeight: 700, color: '#333', margin: 0, lineHeight: 1, fontFamily: "'Barlow Condensed', system-ui, sans-serif", letterSpacing: '0.01em' }}>
                Stage {stageNum ?? 1} · Question {qIdx + 1} of {STAGE_SIZE}
              </p>
              {grade && (
                <span style={{ fontSize: 11, fontWeight: 700, padding: '3px 8px', borderRadius: 4, background: 'rgba(26,115,232,0.08)', border: '1px solid rgba(26,115,232,0.25)', color: '#1a73e8', letterSpacing: '0.06em', fontFamily: 'system-ui,sans-serif', flexShrink: 0 }}>
                  {grade}
                </span>
              )}
            </div>
            {timerDisplay && <span style={{ fontSize: 19, fontWeight: 700, color: '#1a73e8', fontFamily: 'monospace', letterSpacing: 2, flexShrink: 0, animation: timerFlash ? 'timerPulse 0.5s ease-out' : 'none' }}>{timerDisplay}</span>}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <span style={{ width: 120, minWidth: 120, maxWidth: 120, fontSize: 11, fontWeight: 700, color: '#1a73e8', textTransform: 'uppercase', letterSpacing: '0.05em', fontFamily: 'system-ui,sans-serif', flexShrink: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>You</span>
              <div style={{ flex: 1 }}><ProgressStrip total={STAGE_SIZE} current={qIdx} answers={answers} onJump={onJump} /></div>
            </div>
            <OpponentProgressStrip progress={opponentProgress} total={STAGE_SIZE} name={opponentName} dced={opponentDced} disconnected={!opponentConnected && !opponentDced} emoteBubble={emoteBubble} stageLabel={opponentStage} />
          </div>
        </div>
        <div key={qIdx} style={{ flex: 1, padding: '28px 32px 24px', animation: 'questionEnter 0.2s ease-out' }}>
          <p style={{ fontSize: 24, color: '#111', lineHeight: 1.7, whiteSpace: 'pre-line', fontWeight: 500, margin: '0 0 28px 0' }}>{q.question}</p>
          <div style={q.layout === 'grid' ? { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 } : { display: 'flex', flexDirection: 'column', gap: 12 }}>
            {q.choices.map((choice, i) => {
              const sel = selected === i;
              const hov = hoverIdx === i && !sel;
              return (
                <button
                  key={i}
                  onClick={() => { onSelect(i); setPulsingIdx(i); setTimeout(() => setPulsingIdx(p => p === i ? null : p), 150); }}
                  onMouseEnter={() => setHoverIdx(i)}
                  onMouseLeave={() => setHoverIdx(null)}
                  style={{
                    minHeight: 64, padding: '18px 24px',
                    background: sel ? '#e8f0fe' : hov ? '#eef2ff' : '#f7f8fa',
                    border: `2px solid ${sel ? '#1a73e8' : hov ? '#1a73e8' : '#e0e0e0'}`,
                    borderRadius: 12, fontSize: 20, color: sel ? '#1a73e8' : '#222',
                    textAlign: 'left', cursor: 'pointer', fontWeight: sel ? 600 : 400,
                    fontFamily: 'system-ui,-apple-system,sans-serif', lineHeight: 1.45,
                    transition: 'background 0.12s, border-color 0.12s',
                    animation: pulsingIdx === i ? 'answerPulse 0.15s ease-out' : 'none',
                  }}
                >
                  <span style={{ fontSize: 14, fontWeight: 700, color: sel ? '#1a73e8' : '#aaa', marginRight: 20, opacity: 0.7 }}>{i + 1}</span>{choice}
                </button>
              );
            })}
          </div>
        </div>
        <div style={{ borderTop: '1px solid #e0e0e0', boxShadow: '0 -2px 8px rgba(0,0,0,0.06)', padding: '16px 32px', display: 'flex', justifyContent: 'space-between', gap: 14 }}>
          <button style={FORFEIT_BTN} onClick={onForfeit}>Forfeit</button>
          <div style={{ display: 'flex', gap: 14 }}>
            {qIdx > 0 && <button className="quiz-nav-btn" style={NAV_BTN} onClick={onBack}>‹ Back</button>}
            {qIdx === STAGE_SIZE - 1 ? (
              <button className="quiz-nav-btn" style={{ padding: '14px 40px', borderRadius: 999, fontSize: 17, fontWeight: 700, cursor: 'pointer', border: 'none', fontFamily: 'system-ui,sans-serif', background: '#ff69b4', color: '#000', boxShadow: '0 0 6px #ff69b4aa', animation: 'submitGlow 1.5s infinite alternate' }} onClick={onNext}>Submit ★</button>
            ) : (
              <button className="quiz-nav-btn" style={NAV_BTN} onClick={onNext}>Next ›</button>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

function SubmittingOverlay() {
  return (
    <div style={{
      position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
      background: 'rgba(0,0,0,0.85)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      zIndex: 9000,
      animation: 'done-overlay 1.2s forwards',
    }}>
      <div style={{
        fontSize: 'clamp(48px,8vw,96px)',
        fontWeight: 900,
        color: '#fff',
        fontFamily: '"Russo One", system-ui, sans-serif',
        letterSpacing: '-0.02em',
      }}>Done.</div>
    </div>
  );
}

function EmotePicker({ open, onToggle, onSend, onQuickChat }) {
  return (
    <div style={{
      position: 'fixed', right: 20, top: '50%', transform: 'translateY(-50%)',
      zIndex: 1400, display: 'flex', alignItems: 'center', gap: 8,
    }}>
      {open && (
        <div style={{
          background: '#1a1a2a', border: '1px solid rgba(255,255,255,0.15)',
          borderRadius: 16, padding: 16, width: 300,
          boxShadow: '0 4px 24px rgba(0,0,0,0.5)',
          display: 'flex', flexDirection: 'column', gap: 12,
        }}>
          {/* Section 1: 2×4 emote grid */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            {Array.from({ length: 8 }, (_, i) => (
              <button
                key={i}
                onClick={() => onSend(`emote_${i + 1}`)}
                style={{ height: 100, borderRadius: 12, background: '#2a2a3a', border: '1px solid rgba(255,255,255,0.1)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0, transition: 'border-color 0.15s, background 0.15s' }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = '#c9971c'; e.currentTarget.style.background = '#32324a'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)'; e.currentTarget.style.background = '#2a2a3a'; }}
              >
                <div style={{ width: 64, height: 64, background: '#3a3a4a', borderRadius: 8 }} />
              </button>
            ))}
          </div>

          <div style={{ height: 1, background: 'rgba(255,255,255,0.1)' }} />

          {/* Section 2: 1×4 emoji row */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
            {Array.from({ length: 4 }, (_, i) => (
              <button
                key={i}
                onClick={() => onSend(`emoji_${i + 1}`)}
                style={{ height: 64, borderRadius: 10, background: '#2a2a3a', border: '1px solid rgba(255,255,255,0.1)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0, transition: 'border-color 0.15s, background 0.15s' }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = '#c9971c'; e.currentTarget.style.background = '#32324a'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)'; e.currentTarget.style.background = '#2a2a3a'; }}
              >
                <div style={{ width: 40, height: 40, background: '#3a3a4a', borderRadius: 6 }} />
              </button>
            ))}
          </div>

          <div style={{ height: 1, background: 'rgba(255,255,255,0.1)' }} />

          {/* Section 3: 2×4 quick-chat grid */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6 }}>
            {['1','2','3','4','5','6','7','8'].map((label) => (
              <button
                key={label}
                onClick={() => onQuickChat(label)}
                style={{ height: 44, borderRadius: 8, background: 'rgba(37,99,235,0.2)', border: '1px solid rgba(37,99,235,0.4)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0, transition: 'border-color 0.15s, background 0.15s', color: '#93c5fd', fontSize: 14, fontWeight: 700, fontFamily: 'system-ui,sans-serif' }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(37,99,235,0.7)'; e.currentTarget.style.background = 'rgba(37,99,235,0.35)'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(37,99,235,0.4)'; e.currentTarget.style.background = 'rgba(37,99,235,0.2)'; }}
              >
                {label}
              </button>
            ))}
          </div>
        </div>
      )}

      {/* Toggle button */}
      <button
        onClick={onToggle}
        style={{ width: 56, height: 56, background: '#1a1a2a', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 12, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0, transition: 'border-color 0.15s, background 0.15s', flexShrink: 0 }}
        onMouseEnter={e => { e.currentTarget.style.borderColor = '#f5c518'; e.currentTarget.style.background = '#22223a'; }}
        onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255,255,255,0.15)'; e.currentTarget.style.background = '#1a1a2a'; }}
      >
        <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
          <rect x="2" y="2" width="28" height="20" rx="4" ry="4" stroke="white" strokeWidth="2.5" fill="none"/>
          <line x1="7" y1="8" x2="25" y2="8" stroke="white" strokeWidth="1.8" strokeLinecap="round"/>
          <line x1="7" y1="12" x2="25" y2="12" stroke="white" strokeWidth="1.8" strokeLinecap="round"/>
          <line x1="7" y1="16" x2="20" y2="16" stroke="white" strokeWidth="1.8" strokeLinecap="round"/>
          <path d="M13 22 L16 28 L19 22" fill="white" stroke="white" strokeWidth="1.5" strokeLinejoin="round"/>
        </svg>
      </button>
    </div>
  );
}

function WrongAnswerCard({ notif, onDismiss }) {
  const [fading, setFading] = useState(false);
  const dismissRef = useRef(onDismiss);
  dismissRef.current = onDismiss;

  useEffect(() => {
    const t1 = setTimeout(() => setFading(true), 7500);
    const t2 = setTimeout(() => dismissRef.current(notif.id), 8000);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [notif.id]);

  return (
    <div style={{
      background: '#fff',
      borderRadius: 0,
      border: 'none',
      borderTop: '3px solid #dc2626',
      padding: 24,
      minWidth: 420,
      maxWidth: 480,
      boxShadow: '0 4px 20px rgba(0,0,0,0.18)',
      opacity: fading ? 0 : 1,
      transition: 'opacity 0.5s',
      animation: 'notifBounce 0.35s ease-out',
      position: 'relative',
      fontFamily: 'system-ui,sans-serif',
    }}>
      <button
        onClick={() => onDismiss(notif.id)}
        style={{ position: 'absolute', top: 10, right: 14, background: 'none', border: 'none', color: '#999', fontSize: 20, cursor: 'pointer', padding: 0, lineHeight: 1 }}
      >×</button>
      <div style={{ fontSize: 17, fontWeight: 700, color: '#111', marginBottom: 12, paddingRight: 28, fontFamily: 'system-ui,sans-serif' }}>
        {notif.opponentName} got this question WRONG!
      </div>
      <div style={{ fontSize: 16, color: '#111', marginBottom: 16, whiteSpace: 'pre-line', lineHeight: 1.6 }}>
        {notif.question}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column' }}>
        {notif.choices.map((choice, i) => (
          <div
            key={i}
            style={{
              padding: '14px 18px',
              borderRadius: 8,
              fontSize: 15,
              textAlign: 'left',
              cursor: 'default',
              marginBottom: 8,
              width: '100%',
              boxSizing: 'border-box',
              background: i === notif.correctIndex ? '#f0fdf4' : '#fef2f2',
              border: `2px solid ${i === notif.correctIndex ? '#22c55e' : '#fca5a5'}`,
              color: i === notif.correctIndex ? '#166534' : '#991b1b',
            }}
          >
            {choice}
          </div>
        ))}
      </div>
    </div>
  );
}

function WrongAnswerNotifications({ notifications, onDismiss }) {
  if (!notifications.length) return null;
  return (
    <div style={{ position: 'fixed', bottom: 20, right: 20, zIndex: 1500, display: 'flex', flexDirection: 'column', gap: 10, alignItems: 'flex-end', pointerEvents: 'none' }}>
      {notifications.map(n => (
        <div key={n.id} style={{ pointerEvents: 'all' }}>
          <WrongAnswerCard notif={n} onDismiss={onDismiss} />
        </div>
      ))}
    </div>
  );
}

function MinecraftChat({ messages, myUserId, gameId, emotePickerOpen, onChatOpen }) {
  const [inputActive, setInputActive] = useState(false);
  const [inputValue, setInputValue] = useState('');
  const [flashKey, setFlashKey] = useState(null);
  const inputRef = useRef(null);
  const prevLenRef = useRef(0);
  const inputActiveRef = useRef(false);
  inputActiveRef.current = inputActive;
  const onChatOpenRef = useRef(onChatOpen);
  onChatOpenRef.current = onChatOpen;

  // Close chat when emote picker opens
  useEffect(() => {
    if (emotePickerOpen) {
      setInputActive(false);
      setInputValue('');
    }
  }, [emotePickerOpen]);

  useEffect(() => {
    if (messages.length > prevLenRef.current && !inputActiveRef.current) {
      const newest = messages[messages.length - 1];
      if (newest && newest.userId !== myUserId) {
        const k = `${newest.sentAt}-${newest.userId}`;
        setFlashKey(k);
        setTimeout(() => setFlashKey(null), 1000);
      }
    }
    prevLenRef.current = messages.length;
  }, [messages.length, myUserId]);

  useEffect(() => {
    function handler(e) {
      if (e.key === '/' && e.target.tagName !== 'INPUT' && !inputActiveRef.current) {
        e.preventDefault();
        setInputActive(true);
        onChatOpenRef.current?.();
      }
    }
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, []);

  useEffect(() => {
    if (inputActive) inputRef.current?.focus();
  }, [inputActive]);

  async function handleSend() {
    const trimmed = inputValue.trim();
    setInputActive(false);
    setInputValue('');
    if (!trimmed) return;
    try {
      await fetch(`${API}/game/chat`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders() },
        body: JSON.stringify({ gameId, message: trimmed }),
      });
    } catch {}
  }

  function handleKeyDown(e) {
    if (e.key === 'Enter') { e.preventDefault(); handleSend(); }
    if (e.key === 'Escape') { setInputActive(false); setInputValue(''); }
  }

  const shown = messages.slice(-6);

  return (
    <div style={{ position: 'fixed', bottom: 12, left: 12, width: 340, zIndex: 500, display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'flex-start' }}>
      {shown.map((msg, i) => {
        const isMe = msg.userId === myUserId;
        const k = `${msg.sentAt}-${msg.userId}`;
        const isFlash = k === flashKey;
        return (
          <div key={`${k}-${i}`} style={{ maxWidth: '100%', pointerEvents: 'none' }}>
            <span style={{
              display: 'block',
              background: 'rgba(0,0,0,0.55)',
              padding: '2px 6px',
              borderRadius: 2,
              fontSize: 13,
              fontFamily: 'system-ui,sans-serif',
              lineHeight: 1.5,
              wordBreak: 'break-word',
              boxShadow: isFlash ? '0 0 0 1px rgba(255,255,255,0.85)' : 'none',
            }}>
              <span style={{ color: isMe ? '#f5c518' : '#67e8f9', fontWeight: 700 }}>{msg.username}</span>
              <span style={{ color: '#fff' }}>: {msg.text}</span>
            </span>
          </div>
        );
      })}
      {inputActive ? (
        <input
          ref={inputRef}
          value={inputValue}
          onChange={e => setInputValue(e.target.value)}
          onKeyDown={handleKeyDown}
          onBlur={() => { setInputActive(false); setInputValue(''); }}
          placeholder="Press Enter to send, Esc to cancel"
          maxLength={120}
          style={{
            width: '100%',
            background: 'rgba(0,0,0,0.7)',
            color: '#fff',
            border: 'none',
            outline: 'none',
            padding: '6px 8px',
            fontFamily: 'system-ui,sans-serif',
            fontSize: 13,
            boxSizing: 'border-box',
          }}
        />
      ) : (
        <div
          onClick={() => { setInputActive(true); onChatOpenRef.current?.(); }}
          style={{
            width: '100%',
            background: 'rgba(0,0,0,0.55)',
            color: 'rgba(255,255,255,0.4)',
            padding: '8px 12px',
            borderRadius: 4,
            fontSize: 13,
            fontFamily: 'system-ui,sans-serif',
            cursor: 'text',
            boxSizing: 'border-box',
            userSelect: 'none',
          }}
        >
          Press / to chat...
        </div>
      )}
    </div>
  );
}

function DefeatOverlay({ opponentName, onViewResults }) {
  return (
    <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 2000, animation: 'overlayFade 0.4s ease-out' }}>
      <div style={{ background: '#fff', borderRadius: 16, padding: '40px 48px', textAlign: 'center', maxWidth: 380, width: '90%', fontFamily: 'system-ui,sans-serif', boxShadow: '0 8px 32px rgba(0,0,0,0.2)', animation: 'resultReveal 0.3s ease-out 0.15s both' }}>
        <div style={{ fontSize: 64, marginBottom: 12 }}>💀</div>
        <h1 style={{ fontSize: 32, fontWeight: 800, color: '#c62828', margin: '0 0 8px' }}>Defeated!</h1>
        <p style={{ fontSize: 16, color: '#555', margin: '0 0 24px', lineHeight: 1.5 }}>{opponentName} finished before you.</p>
        <button onClick={onViewResults} style={{ padding: '12px 36px', fontSize: 16, fontWeight: 600, background: '#1a73e8', color: '#fff', border: 'none', borderRadius: 999, cursor: 'pointer', fontFamily: 'system-ui,sans-serif' }}>See Results</button>
      </div>
    </div>
  );
}

function WaitingScreen({ opponentName, opponentProgress, opponentDced, opponentConnected, onForfeit, emoteBubble, opponentStage }) {
  return (
    <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: '#fff', fontFamily: 'system-ui,sans-serif', gap: 24 }}>
      {opponentDced
        ? <div style={{ fontSize: 48 }}>🔌</div>
        : <div style={{ width: 48, height: 48, borderRadius: '50%', border: '3px solid #e0e0e0', borderTopColor: '#ff69b4', animation: 'spin 0.6s linear infinite' }} />
      }
      <h2 style={{ fontSize: 22, fontWeight: 600, color: opponentDced ? '#c62828' : '#333', margin: 0 }}>
        {opponentDced ? `${opponentName} disconnected!` : `Waiting for ${opponentName}...`}
      </h2>
      <p style={{ fontSize: 15, color: '#888', margin: 0 }}>
        {opponentDced ? 'Awarding victory...' : 'You finished! Now waiting for your opponent to complete.'}
      </p>
      <div style={{ width: '100%', maxWidth: 480, marginTop: 8 }}>
        <OpponentProgressStrip progress={opponentProgress} total={STAGE_SIZE} name={opponentName} dced={opponentDced} disconnected={!opponentConnected && !opponentDced} emoteBubble={emoteBubble} stageLabel={opponentStage} />
      </div>
      <button onClick={onForfeit} style={{ marginTop: 16, ...FORFEIT_BTN }}>Forfeit & Leave</button>
    </div>
  );
}

function ForfeitConfirmModal({ onConfirm, onCancel }) {
  return (
    <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 3000 }}>
      <div style={{ background: '#fff', borderRadius: 16, padding: '36px 40px', textAlign: 'center', maxWidth: 360, width: '90%', fontFamily: 'system-ui,sans-serif', boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}>
        <div style={{ fontSize: 48, marginBottom: 12 }}>🏳️</div>
        <h2 style={{ fontSize: 22, fontWeight: 800, color: '#c62828', margin: '0 0 8px' }}>Forfeit?</h2>
        <p style={{ fontSize: 15, color: '#555', margin: '0 0 24px', lineHeight: 1.5 }}>This counts as a loss and will reduce your ELO.</p>
        <div style={{ display: 'flex', gap: 12, justifyContent: 'center' }}>
          <button onClick={onCancel} style={{ padding: '11px 28px', fontSize: 15, fontWeight: 600, background: '#f5f5f5', color: '#555', border: '1.5px solid #ccc', borderRadius: 999, cursor: 'pointer', fontFamily: 'system-ui,sans-serif' }}>Cancel</button>
          <button onClick={onConfirm} style={{ padding: '11px 28px', fontSize: 15, fontWeight: 700, background: '#c62828', color: '#fff', border: 'none', borderRadius: 999, cursor: 'pointer', fontFamily: 'system-ui,sans-serif' }}>Forfeit</button>
        </div>
      </div>
    </div>
  );
}

function CompleteScreen({ won, opponentName, duration, eloChange, newElo, opponentDced, youDced, onDashboard, onPlayAgain }) {
  const [reveal, setReveal] = useState(false);
  useEffect(() => {
    const t = setTimeout(() => setReveal(true), 60);
    return () => clearTimeout(t);
  }, []);

  const fmtDuration = duration > 0
    ? `${String(Math.floor(duration / 1000 / 60)).padStart(2, '0')}:${String(Math.floor(duration / 1000) % 60).padStart(2, '0')}`
    : '—';

  const accent     = won ? '#f5c518' : '#dc2626';
  const accentGlow = won ? 'rgba(245,197,24,0.45)' : 'rgba(220,38,38,0.5)';
  const accentDim  = won ? 'rgba(245,197,24,0.09)' : 'rgba(220,38,38,0.09)';
  const accentHov  = won ? 'rgba(245,197,24,0.18)' : 'rgba(220,38,38,0.18)';

  const eloVal = eloChange !== 0 ? `${eloChange > 0 ? '+' : ''}${eloChange}` : '—';
  const eloColor = eloChange > 0 ? '#22c55e' : eloChange < 0 ? '#ef4444' : '#555';

  const stats = [
    { label: 'Time',   value: fmtDuration, color: '#b8bcd8' },
    { label: 'ELO',    value: eloVal,       color: eloColor  },
    { label: 'Rating', value: newElo != null ? newElo.toLocaleString() : '—', color: '#b8bcd8' },
  ];

  const opponentLine = won
    ? (opponentDced ? 'OPPONENT DISCONNECTED' : 'YOU DEFEATED')
    : 'DEFEATED BY';

  return (
    <div style={{
      minHeight: '100vh', background: '#07080f',
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      fontFamily: 'system-ui,sans-serif', position: 'relative', overflow: 'hidden',
    }}>
      {/* Atmospheric top-glow */}
      <div style={{
        position: 'absolute', top: 0, left: 0, right: 0, height: '55%', pointerEvents: 'none',
        background: won
          ? 'radial-gradient(ellipse 80% 70% at 50% -15%, rgba(245,197,24,0.22) 0%, transparent 65%)'
          : 'radial-gradient(ellipse 80% 70% at 50% -15%, rgba(190,0,0,0.28) 0%, transparent 65%)',
      }} />
      {/* Hard vignette on edges */}
      <div style={{
        position: 'absolute', inset: 0, pointerEvents: 'none',
        background: 'radial-gradient(ellipse 100% 100% at 50% 50%, transparent 55%, rgba(0,0,0,0.65) 100%)',
      }} />
      {/* Subtle scanline texture */}
      <div style={{
        position: 'absolute', inset: 0, pointerEvents: 'none',
        backgroundImage: 'repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(0,0,0,0.06) 3px, rgba(0,0,0,0.06) 4px)',
      }} />

      {/* Flash on mount */}
      <div style={{
        position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 5,
        background: won ? 'rgba(245,197,24,0.10)' : 'rgba(180,0,0,0.14)',
        opacity: reveal ? 0 : 1, transition: 'opacity 0.5s ease-out',
      }} />

      {/* Content */}
      <div style={{
        position: 'relative', zIndex: 2,
        width: '100%', maxWidth: 500, padding: '0 28px', boxSizing: 'border-box', textAlign: 'center',
        opacity: reveal ? 1 : 0, transform: reveal ? 'none' : 'translateY(24px)',
        transition: 'opacity 0.5s ease-out, transform 0.5s ease-out',
      }}>

        {/* Eyebrow */}
        <div style={{
          fontSize: 10, fontWeight: 700, letterSpacing: '0.32em', textTransform: 'uppercase',
          color: `${accent}55`, marginBottom: 20,
        }}>
          Ranked Match
        </div>

        {/* Icon */}
        <div style={{
          fontSize: 72, lineHeight: 1, marginBottom: 10,
          filter: `drop-shadow(0 0 32px ${accentGlow})`,
          animation: 'resultReveal 0.55s cubic-bezier(.22,1,.36,1) 0.1s both',
        }}>
          {won ? '🏆' : '💀'}
        </div>

        {/* Title */}
        <div style={{
          fontSize: 'clamp(72px,12vw,112px)', fontWeight: 900,
          color: accent, letterSpacing: '0.04em', lineHeight: 0.88,
          fontFamily: '"Russo One","Impact",system-ui,sans-serif',
          textShadow: `0 0 48px ${accentGlow}, 0 4px 0 rgba(0,0,0,0.6)`,
          animation: 'defeatTitleIn 0.5s cubic-bezier(.22,1,.36,1) 0.12s both',
        }}>
          {won ? 'VICTORY' : 'DEFEAT'}
        </div>

        {/* Accent rule */}
        <div style={{
          width: 90, height: 2, margin: '22px auto 22px',
          background: `linear-gradient(90deg, transparent, ${accent}, transparent)`,
        }} />

        {/* Opponent */}
        <div style={{ marginBottom: 28 }}>
          <div style={{
            fontSize: 10, fontWeight: 700, letterSpacing: '0.2em',
            textTransform: 'uppercase', color: '#2e3450', marginBottom: 8,
          }}>
            {opponentLine}
          </div>
          <div style={{
            fontSize: 20, fontWeight: 700, letterSpacing: '0.01em',
            color: won ? 'rgba(255,255,255,0.82)' : 'rgba(255,110,110,0.9)',
          }}>
            {opponentName}
          </div>
        </div>

        {/* Stats */}
        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 8, marginBottom: 32,
          animation: 'statsRowIn 0.45s ease-out 0.28s both',
        }}>
          {stats.map(s => (
            <div key={s.label} style={{
              background: 'rgba(255,255,255,0.025)',
              border: '1px solid rgba(255,255,255,0.055)',
              borderRadius: 8, padding: '18px 8px',
            }}>
              <div style={{
                fontSize: 9, fontWeight: 700, letterSpacing: '0.16em',
                color: '#252840', textTransform: 'uppercase', marginBottom: 10,
              }}>
                {s.label}
              </div>
              <div style={{
                fontSize: 22, fontWeight: 700, color: s.color,
                fontFamily: 'monospace', letterSpacing: '0.02em',
              }}>
                {s.value}
              </div>
            </div>
          ))}
        </div>

        {/* Actions */}
        <div style={{
          display: 'flex', gap: 10, justifyContent: 'center',
          animation: 'statsRowIn 0.45s ease-out 0.44s both',
        }}>
          <button
            onClick={onPlayAgain}
            style={{
              padding: '12px 28px', borderRadius: 5, fontSize: 12, fontWeight: 700,
              letterSpacing: '0.12em', textTransform: 'uppercase', cursor: 'pointer',
              border: `1px solid ${accent}55`, background: accentDim, color: accent,
              fontFamily: 'system-ui,sans-serif', transition: 'background 0.15s, border-color 0.15s',
            }}
            onMouseEnter={e => { e.currentTarget.style.background = accentHov; e.currentTarget.style.borderColor = `${accent}88`; }}
            onMouseLeave={e => { e.currentTarget.style.background = accentDim; e.currentTarget.style.borderColor = `${accent}55`; }}
          >
            Play Again
          </button>
          <button
            onClick={onDashboard}
            style={{
              padding: '12px 28px', borderRadius: 5, fontSize: 12, fontWeight: 700,
              letterSpacing: '0.12em', textTransform: 'uppercase', cursor: 'pointer',
              border: '1px solid rgba(255,255,255,0.09)', background: 'transparent',
              color: '#3c4260', fontFamily: 'system-ui,sans-serif',
              transition: 'background 0.15s, border-color 0.15s, color 0.15s',
            }}
            onMouseEnter={e => {
              e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
              e.currentTarget.style.borderColor = 'rgba(255,255,255,0.2)';
              e.currentTarget.style.color = '#7a80a0';
            }}
            onMouseLeave={e => {
              e.currentTarget.style.background = 'transparent';
              e.currentTarget.style.borderColor = 'rgba(255,255,255,0.09)';
              e.currentTarget.style.color = '#3c4260';
            }}
          >
            Dashboard
          </button>
        </div>
      </div>
    </div>
  );
}

function StageTransition({ onNext }) {
  useEffect(() => {
    const id = setTimeout(onNext, 1500);
    return () => clearTimeout(id);
  }, [onNext]);

  return (
    <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: '#fff', fontFamily: 'system-ui,sans-serif', gap: 16 }}>
      <p style={{ fontSize: 11, color: '#999', letterSpacing: 3, textTransform: 'uppercase', margin: 0 }}>Stage 1 Complete</p>
      <h1 style={{ fontSize: 'clamp(30px,5vw,52px)', fontWeight: 800, color: '#1a73e8', margin: 0, textAlign: 'center' }}>Stage 2 — Get Ready</h1>
    </div>
  );
}

export default function RankedGame() {
  const navigate = useNavigate();
  const { state } = useLocation();

  // Router state is lost on hard reload — try to reconstruct from sessionStorage
  let effectiveState = state;
  if (!effectiveState?.gameId || !effectiveState?.questions) {
    try {
      const savedId = sessionStorage.getItem('ranked_active_game');
      if (savedId) {
        const savedInit = sessionStorage.getItem(`ranked_init_${savedId}`);
        if (savedInit) effectiveState = { gameId: savedId, ...JSON.parse(savedInit) };
      }
    } catch {}
  }

  if (!effectiveState?.gameId || !effectiveState?.questions) {
    return <Navigate to="/dashboard" replace />;
  }

  const { gameId, questions: initialQuestionsObj, opponentName: initOppName, opponentAvatar: initOppAvatar, myName: initMyName, myAvatar: initMyAvatar, grade } = effectiveState;

  const [phase, setPhase] = useState(() => {
    try {
      const saved = sessionStorage.getItem('rg_phase_' + gameId);
      if (saved && saved !== 'intro1') return saved;
    } catch {}
    return 'intro1';
  });
  const [stage, setStage] = useState(() => {
    try { const v = sessionStorage.getItem('ranked_stage_' + gameId); return v ? parseInt(v, 10) : 1; } catch { return 1; }
  });
  const [stage1Questions] = useState(() => initialQuestionsObj?.stage1 ?? []);
  const [stage2Questions] = useState(() => initialQuestionsObj?.stage2 ?? []);
  const questions = stage === 1 ? stage1Questions : stage2Questions;
  const [qIdx, setQIdx] = useState(() => {
    try { const v = sessionStorage.getItem('ranked_qidx_' + gameId); return v ? parseInt(v, 10) : 0; } catch { return 0; }
  });
  const [answers, setAnswers] = useState(() => {
    try { const v = sessionStorage.getItem('ranked_answers_' + gameId); return v ? JSON.parse(v) : {}; } catch { return {}; }
  });
  const [selected, setSelected] = useState(() => {
    try {
      const savedAnswers = sessionStorage.getItem('ranked_answers_' + gameId);
      const savedQIdx = sessionStorage.getItem('ranked_qidx_' + gameId);
      const parsedAnswers = savedAnswers ? JSON.parse(savedAnswers) : {};
      const parsedQIdx = savedQIdx ? parseInt(savedQIdx, 10) : 0;
      return parsedAnswers[parsedQIdx] ?? null;
    } catch { return null; }
  });
  const [opponentProgress, setOpponentProgress] = useState(0);
  const [opponentStage, setOpponentStage] = useState(1);
  const opponentStageRef = useRef(1);
  const [opponentName] = useState(initOppName);
  const [opponentAvatar] = useState(initOppAvatar);
  const [playerWon, setPlayerWon] = useState(null);
  const [showDefeatOverlay, setShowDefeatOverlay] = useState(false);
  const [startTime] = useState(() => Date.now());
  const [duration, setDuration] = useState(0);
  const [eloChange, setEloChange] = useState(0);
  const [newElo, setNewElo] = useState(null);
  const [opponentDced, setOpponentDced] = useState(false);
  const [youDced, setYouDced] = useState(false);
  const [opponentConnected, setOpponentConnected] = useState(true);
  const [showForfeitConfirm, setShowForfeitConfirm] = useState(false);
  const [wrongCount, setWrongCount] = useState(0);
  const [resultsViewQ, setResultsViewQ] = useState(0);

  // Emote state
  const [emotePickerOpen, setEmotePickerOpen] = useState(false);
  const [opponentEmoteBubble, setOpponentEmoteBubble] = useState(null);
  const lastDisplayedEmoteRef = useRef(null);

  // Wrong answer notifications
  const [wrongAnswerNotifs, setWrongAnswerNotifs] = useState([]);
  const opponentPrevStateRef = useRef({ currentQuestion: 0, correctAnswers: 0 });

  const [chatMessages, setChatMessages] = useState([]);
  const [myUserId] = useState(() => {
    try {
      const token = sessionStorage.getItem('ranked_token');
      if (!token) return null;
      return JSON.parse(atob(token.split('.')[1])).userId;
    } catch { return null; }
  });

  const pollRef = useRef(null);
  const completedRef = useRef(false);
  const submittedRef = useRef(false);
  const [retryBanner, setRetryBanner] = useState(null);
  const [timerDisplay, setTimerDisplay] = useState('00:00');
  const timerRef = useRef(null);
  const timerStartRef = useRef(null);
  const stageRef = useRef(1);
  stageRef.current = stage;
  const questionsRef = useRef([]);
  questionsRef.current = questions;
  const ssRestoredRef = useRef(sessionStorage.getItem('ranked_qidx_' + gameId) !== null);
  const firstPollDoneRef = useRef(false);

  useEffect(() => {
    sessionStorage.setItem('ranked_answers_' + gameId, JSON.stringify(answers));
  }, [answers]); // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => {
    sessionStorage.setItem('ranked_qidx_' + gameId, String(qIdx));
  }, [qIdx]); // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => {
    sessionStorage.setItem('ranked_stage_' + gameId, String(stage));
  }, [stage]); // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => {
    sessionStorage.setItem('rg_phase_' + gameId, phase);
  }, [phase]); // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => {
    if (phase === 'done') {
      const id = setTimeout(() => setPhase('waiting'), 1200);
      return () => clearTimeout(id);
    }
  }, [phase]);

  useEffect(() => {
    if (!sessionStorage.getItem('ranked_init_' + gameId)) {
      sessionStorage.setItem('ranked_active_game', gameId);
      sessionStorage.setItem('ranked_init_' + gameId, JSON.stringify({
        questions: initialQuestionsObj,
        opponentName: initOppName,
        opponentAvatar: initOppAvatar,
        myName: initMyName,
        myAvatar: initMyAvatar,
        grade,
      }));
    }
    const savedTimerStart = sessionStorage.getItem('ranked_timer_start_' + gameId);
    const savedPhaseForTimer = sessionStorage.getItem('rg_phase_' + gameId);
    if (savedTimerStart) {
      timerStartRef.current = parseInt(savedTimerStart, 10);
    } else if (savedPhaseForTimer && savedPhaseForTimer !== 'intro1') {
      // Restored session that's past the intro — start timer now
      timerStartRef.current = Date.now();
      sessionStorage.setItem('ranked_timer_start_' + gameId, String(timerStartRef.current));
    }
    // else: timer starts when user clicks Next on intro1, not on mount
    timerRef.current = setInterval(() => {
      if (!timerStartRef.current) return;
      const ms = Date.now() - timerStartRef.current;
      const s = Math.floor(ms / 1000);
      setTimerDisplay(`${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`);
    }, 1000);
    pollGameStatus();
    return () => {
      clearInterval(pollRef.current);
      clearInterval(timerRef.current);
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  function clearGameStorage() {
    ['answers', 'qidx', 'stage', 'timer_start', 'init'].forEach(k =>
      sessionStorage.removeItem(`ranked_${k}_${gameId}`)
    );
    sessionStorage.removeItem('rg_phase_' + gameId);
    sessionStorage.removeItem('rg_timerstart_' + gameId);
    if (sessionStorage.getItem('ranked_active_game') === gameId)
      sessionStorage.removeItem('ranked_active_game');
  }

  function submitProgress(currentQuestion, correctAnswers, finished, timeSpentMs) {
    fetch(`${API}/game/progress`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHeaders() },
      body: JSON.stringify({ gameId, currentQuestion, correctAnswers, finished, timeSpentMs, stage: stageRef.current }),
    }).catch(() => {});
  }

  function pollGameStatus() {
    pollRef.current = setInterval(() => {
      if (completedRef.current) { clearInterval(pollRef.current); return; }

      fetch(`${API}/game/status/${gameId}`, { headers: authHeaders() })
        .then(r => r.json())
        .then(data => {
          if (data.opponentDced) setOpponentDced(true);
          if (data.youDced) setYouDced(true);
          if (data.opponentConnected === false) setOpponentConnected(false);
          else if (data.opponentConnected === true && !data.opponentDced) setOpponentConnected(true);

          if (data.status === 'completed') {
            clearInterval(pollRef.current);
            clearInterval(timerRef.current);
            const you = data.players.you;
            const won = data.youWon === true;
            completedRef.current = true;
            setDuration(you.timeSpentMs || (Date.now() - startTime));
            setEloChange(you.eloChange || 0);
            setNewElo(you.newElo ?? null);
            clearGameStorage();
            setPlayerWon(won);
            if (won) {
              setPhase('complete');
            } else {
              setShowDefeatOverlay(true);
            }
          } else {
            const opp = data.players.opponent;
            const prevOpp = opponentPrevStateRef.current;

            // Wrong answer detection
            if (
              opp.currentQuestion === prevOpp.currentQuestion + 1 &&
              opp.correctAnswers === prevOpp.correctAnswers
            ) {
              const wrongQ = questionsRef.current[prevOpp.currentQuestion];
              if (wrongQ) {
                const id = Date.now() + Math.random();
                setWrongAnswerNotifs(prev => [...prev, {
                  id,
                  question: wrongQ.question,
                  correctIndex: wrongQ.correct,
                  choices: wrongQ.choices,
                  opponentName,
                  timestamp: Date.now(),
                }].slice(-3));
                setTimeout(() => {
                  setWrongAnswerNotifs(prev => prev.filter(n => n.id !== id));
                }, 8200);
              }
            }
            opponentPrevStateRef.current = { currentQuestion: opp.currentQuestion, correctAnswers: opp.correctAnswers };

            setOpponentProgress(opp.currentQuestion + 1);
            const newOppStage = opp.currentStage ?? 1;
            if (newOppStage !== opponentStageRef.current) {
              opponentStageRef.current = newOppStage;
              setOpponentStage(newOppStage);
              opponentPrevStateRef.current = { currentQuestion: 0, correctAnswers: 0 };
            }

            // Emote bubble
            if (opp.lastEmote && opp.lastEmote !== lastDisplayedEmoteRef.current) {
              lastDisplayedEmoteRef.current = opp.lastEmote;
              const key = Date.now();
              setOpponentEmoteBubble({ text: opp.lastEmote, key });
              setTimeout(() => setOpponentEmoteBubble(null), 3100);
            }

            if (data.chatMessages) {
              setChatMessages(data.chatMessages);
            }

            if (!firstPollDoneRef.current && !ssRestoredRef.current) {
              const sp = data.players.you.savedProgress;
              if (sp) {
                if (sp.currentQuestion > 0) setQIdx(sp.currentQuestion);
                if (sp.stage > 1) setStage(sp.stage);
              }
            }
            firstPollDoneRef.current = true;
          }
        })
        .catch(() => {});
    }, 1000);
  }

  function handleNext() {
    const newAnswers = { ...answers, [qIdx]: selected };
    setAnswers(newAnswers);
    const correctCount = questions.filter((q, i) => newAnswers[i] === q.correct).length;

    if (qIdx < STAGE_SIZE - 1) {
      submitProgress(qIdx + 1, correctCount, false);
      const next = qIdx + 1;
      setQIdx(next);
      setSelected(newAnswers[next] ?? null);
    } else {
      const wc = STAGE_SIZE - correctCount;
      setWrongCount(wc);
      setResultsViewQ(0);
      setPhase(phase === 'questions1' ? 'results1' : 'results2');
    }
  }

  function handleBack() {
    const newAnswers = selected !== null ? { ...answers, [qIdx]: selected } : answers;
    setAnswers(newAnswers);
    const prev = qIdx - 1;
    const correctCount = questions.filter((q, i) => newAnswers[i] === q.correct).length;
    submitProgress(prev, correctCount, false);
    setQIdx(prev);
    setSelected(newAnswers[prev] ?? null);
  }

  function handleJump(targetIdx) {
    const newAnswers = selected !== null ? { ...answers, [qIdx]: selected } : answers;
    setAnswers(newAnswers);
    const correctCount = questions.filter((q, i) => newAnswers[i] === q.correct).length;
    submitProgress(targetIdx, correctCount, false);
    setQIdx(targetIdx);
    setSelected(newAnswers[targetIdx] ?? null);
  }

  async function handleForfeit() {
    setShowForfeitConfirm(false);
    clearInterval(pollRef.current);
    clearInterval(timerRef.current);
    completedRef.current = true;
    try {
      const r = await fetch(`${API}/game/forfeit`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders() },
        body: JSON.stringify({ gameId }),
      });
      if (r.ok) {
        const data = await r.json();
        setEloChange(data.eloChange ?? 0);
        setNewElo(data.newElo ?? null);
      }
    } catch {}
    clearGameStorage();
    setDuration(0);
    setPlayerWon(false);
    setPhase('complete');
  }

  async function handleSendEmote(emote) {
    setEmotePickerOpen(false);
    try {
      await fetch(`${API}/game/emote`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders() },
        body: JSON.stringify({ gameId, emote }),
      });
    } catch {}
  }

  async function handleSendQuickChat(text) {
    setEmotePickerOpen(false);
    try {
      await fetch(`${API}/game/chat`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders() },
        body: JSON.stringify({ gameId, message: text }),
      });
    } catch {}
  }

  function dismissNotif(id) {
    setWrongAnswerNotifs(prev => prev.filter(n => n.id !== id));
  }

  const showMultiplayerUI = phase === 'questions1' || phase === 'questions2' || phase === 'waiting';
  const resultsQuestion = questions[resultsViewQ] ?? questions[0];
  const resultsUserAns = answers[resultsViewQ];

  return (
    <>
      <GlobalStyles />

      {phase === 'intro1' && (
        <div style={{ display: 'flex', minHeight: '100vh', fontFamily: 'system-ui,-apple-system,sans-serif' }}>
          <div style={{ flex: 1, overflow: 'hidden' }}>
            <img src={treeBg} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
          </div>
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '64px 56px', background: '#fff', position: 'relative', minHeight: '100vh' }}>
            <h1 style={{ fontSize: 'clamp(24px,3vw,38px)', fontWeight: 800, color: '#111', margin: '0 0 20px 0', lineHeight: 1.2 }}>
              You are ready to begin Stage 1 in Mathematics.
            </h1>
            <p style={{ fontSize: 18, color: '#555', margin: 0, lineHeight: 1.6 }}>
              Answer the next 11 questions to complete this stage.
            </p>
            <div style={{ position: 'absolute', bottom: 32, right: 40 }}>
              <button
                style={NAV_BTN}
                onClick={() => {
                  timerStartRef.current = Date.now();
                  sessionStorage.setItem('ranked_timer_start_' + gameId, String(timerStartRef.current));
                  setPhase('questions1');
                }}
              >Next ›</button>
            </div>
          </div>
        </div>
      )}

      {(phase === 'questions1' || phase === 'questions2') && (
        <QuestionScreen
          question={questions[qIdx]} qIdx={qIdx} answers={answers} selected={selected}
          onSelect={setSelected} onNext={handleNext} onBack={handleBack} onJump={handleJump}
          opponentProgress={opponentProgress} opponentName={opponentName}
          opponentDced={opponentDced} opponentConnected={opponentConnected}
          onForfeit={() => setShowForfeitConfirm(true)}
          emoteBubble={opponentEmoteBubble}
          stageNum={stage} timerDisplay={timerDisplay} retryBanner={retryBanner}
          opponentStage={opponentStage} grade={grade}
        />
      )}

      {(phase === 'results1' || phase === 'results2') && (
        <div style={{ minHeight: '100vh', background: '#fff', fontFamily: 'system-ui,-apple-system,sans-serif', display: 'flex', flexDirection: 'column' }}>
          <div style={{ padding: '28px 40px 0' }}>
            <h1 style={{ fontSize: 28, fontWeight: 800, color: '#111', margin: '0 0 16px 0' }}>Mathematics</h1>
            <div style={{ height: 1, background: '#e0e0e0', marginBottom: 16 }} />
            {wrongCount > 0 && (
              <p style={{ fontSize: 18, fontWeight: 600, color: '#c62828', margin: '0 0 16px 0' }}>
                {wrongCount} {wrongCount === 1 ? 'question was' : 'questions were'} incorrect.
              </p>
            )}
          </div>
          <div style={{ flex: 1, display: 'flex', padding: '0 40px', gap: 32, overflow: 'auto' }}>
            <div style={{ flex: '0 0 40%', paddingBottom: 32, paddingTop: 16, animation: 'slideInLeft 0.3s ease-out' }}>
              <p style={{ fontSize: 20, color: '#111', lineHeight: 1.7, whiteSpace: 'pre-line', fontWeight: 500, margin: '0 0 20px 0' }}>
                {resultsQuestion?.question}
              </p>
              <div style={resultsQuestion?.layout === 'grid' ? { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 } : { display: 'flex', flexDirection: 'column', gap: 10 }}>
                {(resultsQuestion?.choices ?? []).map((choice, i) => {
                  const sel = resultsUserAns === i;
                  return (
                    <button
                      key={i}
                      onClick={() => setAnswers(prev => ({ ...prev, [resultsViewQ]: i }))}
                      style={{ minHeight: 56, padding: '14px 20px', background: sel ? '#e8f0fe' : '#f7f8fa', border: `2px solid ${sel ? '#1a73e8' : '#e0e0e0'}`, borderRadius: 12, fontSize: 17, color: sel ? '#1a73e8' : '#222', textAlign: 'left', cursor: 'pointer', fontWeight: sel ? 600 : 400, fontFamily: 'system-ui,-apple-system,sans-serif', lineHeight: 1.45, transition: 'background 0.12s, border-color 0.12s' }}
                    >
                      <span style={{ fontSize: 13, fontWeight: 700, color: sel ? '#1a73e8' : '#aaa', marginRight: 16, opacity: 0.7 }}>{i + 1}</span>{choice}
                    </button>
                  );
                })}
              </div>
              <div style={{ marginTop: 16, padding: '10px 14px', background: '#f5f5f5', borderRadius: 8, fontSize: 13, color: '#666', fontFamily: 'system-ui,sans-serif' }}>
                Your answer: {resultsUserAns != null ? resultsQuestion?.choices[resultsUserAns] : '—'}
              </div>
            </div>
            <div style={{ flex: '0 0 58%', paddingBottom: 32, paddingTop: 16, animation: 'slideInRight 0.3s ease-out 0.05s both' }}>
              <h2 style={{ fontSize: 18, fontWeight: 700, color: '#333', margin: '0 0 16px 0' }}>
                {phase === 'results1' ? 'Stage 1' : 'Stage 2'}
              </h2>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
                {Array.from({ length: STAGE_SIZE }, (_, i) => {
                  const active = i === resultsViewQ;
                  return (
                    <button
                      key={i}
                      onClick={() => setResultsViewQ(i)}
                      style={{ padding: active ? '8px 18px' : '6px 4px', borderRadius: active ? 8 : 0, border: 'none', background: active ? '#1a73e8' : 'transparent', color: active ? '#fff' : '#1a73e8', fontSize: 15, fontWeight: active ? 700 : 500, cursor: 'pointer', fontFamily: 'system-ui,sans-serif', textDecoration: active ? 'none' : 'underline', transition: 'background 0.12s' }}
                    >
                      Question {i + 1}
                    </button>
                  );
                })}
              </div>
            </div>
          </div>
          <div style={{ padding: '16px 40px', borderTop: '1px solid #e0e0e0', display: 'flex', justifyContent: 'flex-end' }}>
            {wrongCount === 0 ? (
              <button
                style={NAV_BTN}
                onClick={() => {
                  if (phase === 'results1') {
                    submitProgress(STAGE_SIZE, STAGE_SIZE, true, timerStartRef.current ? Date.now() - timerStartRef.current : 0);
                    setPhase('intro2');
                    setQIdx(0);
                    setAnswers({});
                    setSelected(null);
                    setResultsViewQ(0);
                  } else {
                    const elapsed = timerStartRef.current ? Date.now() - timerStartRef.current : 0;
                    clearInterval(timerRef.current);
                    submittedRef.current = true;
                    submitProgress(STAGE_SIZE, STAGE_SIZE, true, elapsed);
                    setPhase('done');
                    setResultsViewQ(0);
                  }
                }}
              >Continue ›</button>
            ) : (
              <button
                style={NAV_BTN}
                onClick={() => {
                  setPhase(phase === 'results1' ? 'questions1' : 'questions2');
                  setQIdx(0);
                  setSelected(answers[0] ?? null);
                  setResultsViewQ(0);
                }}
              >Try Again</button>
            )}
          </div>
        </div>
      )}

      {phase === 'intro2' && (
        <div style={{ display: 'flex', minHeight: '100vh', fontFamily: 'system-ui,-apple-system,sans-serif' }}>
          <div style={{ flex: 1, overflow: 'hidden' }}>
            <img src={treeBg} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
          </div>
          <div key="intro2-content" style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '64px 56px', background: '#fff', position: 'relative', minHeight: '100vh' }}>
            <h1 style={{ fontSize: 'clamp(24px,3vw,38px)', fontWeight: 800, color: '#111', margin: '0 0 20px 0', lineHeight: 1.2, animation: 'stageReveal 0.4s ease-out 0.1s both' }}>
              You are ready to begin Stage 2 in Mathematics.
            </h1>
            <p style={{ fontSize: 18, color: '#555', margin: 0, lineHeight: 1.6, animation: 'stageReveal 0.4s ease-out 0.25s both' }}>
              Answer the next 11 questions to complete this stage.
            </p>
            <div style={{ position: 'absolute', bottom: 32, right: 40 }}>
              <button
                style={{ ...NAV_BTN, animation: 'stageReveal 0.4s ease-out 0.4s both' }}
                onClick={() => {
                  stageRef.current = 2;
                  setStage(2);
                  setQIdx(0);
                  setAnswers({});
                  setSelected(null);
                  setRetryBanner(null);
                  setPhase('questions2');
                }}
              >Next ›</button>
            </div>
          </div>
        </div>
      )}

      {phase === 'done' && <SubmittingOverlay />}

      {phase === 'waiting' && (
        <WaitingScreen
          opponentName={opponentName} opponentProgress={opponentProgress}
          opponentDced={opponentDced} opponentConnected={opponentConnected}
          onForfeit={() => setShowForfeitConfirm(true)}
          emoteBubble={opponentEmoteBubble}
          opponentStage={opponentStage}
        />
      )}

      {phase === 'complete' && (
        <CompleteScreen
          won={playerWon} opponentName={opponentName} duration={duration}
          eloChange={eloChange} newElo={newElo} opponentDced={opponentDced} youDced={youDced}
          onDashboard={() => { clearGameStorage(); navigate('/dashboard'); }}
          onPlayAgain={() => { clearGameStorage(); navigate('/queue', { state: { grade } }); }}
        />
      )}

      {showDefeatOverlay && (
        <DefeatOverlay opponentName={opponentName} onViewResults={() => { setShowDefeatOverlay(false); setPhase('complete'); }} />
      )}
      {showForfeitConfirm && (
        <ForfeitConfirmModal onConfirm={handleForfeit} onCancel={() => setShowForfeitConfirm(false)} />
      )}

      {showMultiplayerUI && (
        <>
          <EmotePicker
            open={emotePickerOpen}
            onToggle={() => setEmotePickerOpen(v => !v)}
            onSend={handleSendEmote}
            onQuickChat={handleSendQuickChat}
          />
          <MinecraftChat messages={chatMessages} myUserId={myUserId} gameId={gameId} emotePickerOpen={emotePickerOpen} onChatOpen={() => setEmotePickerOpen(false)} />
          <WrongAnswerNotifications notifications={wrongAnswerNotifs} onDismiss={dismissNotif} />
        </>
      )}
    </>
  );
}
