// screen-memorize.jsx — list + memorization practice modes (bilingual)
function MemorizeScreen({ content, progress, streak, go }) {
  const c = useTheme();
  const memorizeQuotes = content.quotes.filter(q => q.wellspring);
  const themeKeys = Array.from(new Set(memorizeQuotes.map(q => q.theme)));
  const [theme, setTheme] = React.useState('All');
  const list = memorizeQuotes.filter(q => theme === 'All' || q.theme === theme);
  const learned = memorizeQuotes.filter(q => (progress.mastery[q.id] || 0) >= 5).length;

  return (
    <div style={{ padding: '58px 18px 120px' }}>
      <h1 style={{ margin: '0 2px 4px', fontFamily: c.headingStack, fontWeight: 600, fontSize: 32, color: c.text }}>{c.t('memorize')}</h1>
      <p style={{ margin: '0 2px 18px', color: c.textMuted, fontSize: 14.5 }}>{c.t('memorize_sub')}</p>

      <div style={{ display: 'flex', gap: 12, marginBottom: 20 }}>
        <Stat icon="flame" big={streak.count} label={c.t('day_streak')} hot={streak.count > 0} />
        <Stat icon="star" big={`${learned}/${memorizeQuotes.length}`} label={c.t('learned_l')} />
      </div>

      <div style={{ display: 'flex', gap: 8, overflowX: 'auto', margin: '0 -18px 18px', padding: '0 18px' }}>
        <Chip active={theme === 'All'} onClick={() => setTheme('All')}>{c.t('all')}</Chip>
        {themeKeys.map(tk => <Chip key={tk} active={theme === tk} onClick={() => setTheme(tk)}>{c.t('th_' + tk)}</Chip>)}
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
        {list.map(q => {
          const m = progress.mastery[q.id] || 0;
          return (
            <button key={q.id} onClick={() => go({ name: 'practice', quoteId: q.id })} style={asfCardBtn(c)}>
              <div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
                <Ring value={m / 5} size={46} stroke={4}>
                  <span style={{ fontSize: 12.5, fontWeight: 800, color: m >= 5 ? c.accent : c.textMuted }}>{m >= 5 ? '★' : `${m}/5`}</span>
                </Ring>
                <div style={{ flex: 1, textAlign: 'left', minWidth: 0 }}>
                  <div style={{ fontSize: 15, fontWeight: 600, color: c.text, lineHeight: 1.35, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{c.L(q.text)}</div>
                  <div style={{ fontSize: 12.5, color: c.textMuted, marginTop: 4 }}>{q.source} · {c.t('th_' + q.theme)}</div>
                </div>
                <div style={{ color: c.accent }}><Icon name="play" size={16} fill /></div>
              </div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function Stat({ icon, big, label, hot }) {
  const c = useTheme();
  return (
    <div style={{ ...asfCard(c), flex: 1, display: 'flex', alignItems: 'center', gap: 11, padding: '14px 16px' }}>
      <div style={{ color: hot ? c.accent : c.textMuted }}><Icon name={icon} size={22} fill={hot} /></div>
      <div>
        <div style={{ fontFamily: c.headingStack, fontSize: 22, fontWeight: 600, color: c.text, lineHeight: 1 }}>{big}</div>
        <div style={{ fontSize: 12, color: c.textMuted, marginTop: 2 }}>{label}</div>
      </div>
    </div>
  );
}

function PracticeScreen({ content, progress, defaultMode, onResult, route, back }) {
  const c = useTheme();
  const q = content.quotes.find(x => x.id === route.quoteId);
  const text = c.L(q.text);
  const [mode, setMode] = React.useState(defaultMode || 'flash');
  const modes = [
    { id: 'flash', label: 'Reveal' },
    { id: 'slider', label: 'Slider' },
    { id: 'listen', label: 'Listen' },
    { id: 'letters', label: 'Letters' },
    { id: 'blanks', label: c.t('fill_blanks') },
    { id: 'reorder', label: 'Reorder' },
    { id: 'type', label: c.t('type_it') },
    { id: 'choice', label: 'Choices' },
    { id: 'speak', label: 'Speak' },
  ];
  const m = progress.mastery[q.id] || 0;

  return (
    <div style={{ padding: '0 0 40px', minHeight: '100%', display: 'flex', flexDirection: 'column' }}>
      <TopBar back={back} label={c.t('th_' + q.theme)} />
      <div style={{ padding: '4px 20px 0' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div style={{ fontSize: 13, color: c.textMuted }}>{q.source}</div>
          <div style={{ display: 'flex', gap: 4 }}>
            {[0, 1, 2, 3, 4].map(i => <div key={i} style={{ width: 7, height: 7, borderRadius: 99, background: i < m ? c.accent : c.border }} />)}
          </div>
        </div>
        <div className="asf-scroll" style={{ display: 'flex', gap: 6, margin: '14px -20px 0', padding: '4px 20px', overflowX: 'auto' }}>
          {modes.map(mm => (
            <button key={mm.id} onClick={() => setMode(mm.id)} style={{
              flex: '0 0 auto', border: 'none', cursor: 'pointer', borderRadius: 999, padding: '9px 13px', fontFamily: 'inherit',
              fontSize: 13.5, fontWeight: 800, background: mode === mm.id ? (c.btn || c.accent) : c.surface,
              color: mode === mm.id ? (c.btnInk || c.accentInk) : c.textMuted,
              boxShadow: mode === mm.id ? (c.btnShadow || 'none') : (c.dark ? `inset 0 0 0 1px ${c.border}` : '0 4px 10px rgba(40,70,30,0.08), inset 0 1px 0 rgba(255,255,255,0.85)'),
            }}>{mm.label}</button>
          ))}
        </div>
      </div>

      <div style={{ flex: 1, padding: '18px 20px 0' }}>
        {mode === 'flash' && <FlashMode key={'f' + q.id} q={q} text={text} onResult={onResult} />}
        {mode === 'slider' && <SliderMode key={'s' + q.id} text={text} onResult={onResult} />}
        {mode === 'listen' && <ListenMode key={'l' + q.id} text={text} onResult={onResult} />}
        {mode === 'letters' && <FirstLetterMode key={'fl' + q.id} text={text} onResult={onResult} />}
        {mode === 'blanks' && <BlanksMode key={'b' + q.id} text={text} onResult={onResult} />}
        {mode === 'reorder' && <ReorderMode key={'r' + q.id} text={text} onResult={onResult} />}
        {mode === 'type' && <TypeMode key={'t' + q.id} text={text} onResult={onResult} />}
        {mode === 'choice' && <ChoiceMemoryMode key={'c' + q.id} text={text} onResult={onResult} />}
        {mode === 'speak' && <SpeakMode key={'sp' + q.id} text={text} onResult={onResult} />}
      </div>
    </div>
  );
}

function normalizeWords(s) {
  return (s || '').toLowerCase().replace(/[^a-zñáéíóú' ]/g, '').split(/\s+/).filter(Boolean);
}
function scoreText(targetText, attemptText) {
  const target = normalizeWords(targetText);
  const attempt = normalizeWords(attemptText);
  const correct = target.filter((w, i) => attempt[i] === w).length;
  return { correct, total: target.length, pct: target.length ? Math.round((correct / target.length) * 100) : 0 };
}
function cleanWord(w) {
  return (w || '').replace(/^[^A-Za-zÁÉÍÓÚÑáéíóúñ']+|[^A-Za-zÁÉÍÓÚÑáéíóúñ'.!?;:,]+$/g, '');
}
function shuffleDeterministic(items, seedText) {
  let seed = 0;
  for (let i = 0; i < seedText.length; i++) seed = (seed * 31 + seedText.charCodeAt(i)) % 2147483647;
  const arr = [...items];
  for (let i = arr.length - 1; i > 0; i--) {
    seed = (seed * 48271) % 2147483647;
    const j = seed % (i + 1);
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}
function PracticeCard({ children, style }) {
  const c = useTheme();
  return <div style={{ ...asfCard(c), padding: '24px 22px', ...style }}>{children}</div>;
}
function PracticeHint({ children }) {
  const c = useTheme();
  return <div style={{ textAlign: 'center', fontSize: 13, color: c.textMuted, lineHeight: 1.4, marginTop: 12 }}>{children}</div>;
}

function RateBar({ onResult }) {
  const c = useTheme();
  return (
    <div style={{ display: 'flex', gap: 10, marginTop: 22 }}>
      <button onClick={() => onResult(false)} style={{ flex: 1, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 700, fontSize: 15, padding: '14px', borderRadius: 13 }}>{c.t('again')}</button>
      <button onClick={() => onResult(true)} style={{ flex: 1.4, border: 'none', background: c.accent, color: c.accentInk, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 700, fontSize: 15, padding: '14px', borderRadius: 13 }}>{c.t('i_knew_it')}</button>
    </div>
  );
}

function FlashMode({ q, text, onResult }) {
  const c = useTheme();
  const [flipped, setFlipped] = React.useState(false);
  return (
    <div>
      <button onClick={() => setFlipped(f => !f)} style={{
        width: '100%', minHeight: 280, border: 'none', cursor: 'pointer', borderRadius: 22, padding: '34px 26px',
        background: flipped ? c.surface : c.accent, color: flipped ? c.text : c.accentInk,
        boxShadow: c.dark ? `inset 0 0 0 1px ${c.border}` : '0 8px 30px rgba(44,32,23,0.12)',
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 14, fontFamily: 'inherit',
      }}>
        {!flipped ? (<>
          <Icon name="quote" size={40} fill style={{ opacity: 0.5 }} />
          <div style={{ fontFamily: c.headingStack, fontSize: 22, fontWeight: 600 }}>{q.source}</div>
          <div style={{ fontSize: 13.5, opacity: 0.8 }}>{c.t('tap_reveal')} · {c.t('th_' + q.theme)}</div>
        </>) : (
          <p style={{ margin: 0, fontFamily: c.headingStack, fontSize: 23, lineHeight: 1.42, fontWeight: 500 }}>{text}</p>
        )}
      </button>
      <div style={{ textAlign: 'center', fontSize: 13, color: c.textMuted, marginTop: 12 }}>{flipped ? c.t('how_did') : c.t('recall_tap')}</div>
      {flipped && <RateBar onResult={onResult} />}
    </div>
  );
}

function BlanksMode({ text, onResult }) {
  const c = useTheme();
  const tokens = text.split(' ');
  const hideable = tokens.map((w, i) => ({ w, i })).filter(t => t.w.replace(/[^A-Za-zÁÉÍÓÚÑáéíóúñ']/g, '').length > 3);
  const hideSet = React.useMemo(() => {
    const n = Math.max(1, Math.ceil(hideable.length * 0.5));
    const step = hideable.length / n; const s = new Set();
    for (let k = 0; k < n; k++) s.add(hideable[Math.floor(k * step)].i);
    return s;
  }, [text]);
  const [shown, setShown] = React.useState(new Set());
  const allShown = [...hideSet].every(i => shown.has(i));

  return (
    <div>
      <div style={{ ...asfCard(c), padding: '26px 22px', minHeight: 200 }}>
        <p style={{ margin: 0, fontFamily: c.headingStack, fontSize: 22, lineHeight: 1.7, color: c.text }}>
          {tokens.map((w, i) => {
            if (!hideSet.has(i) || shown.has(i)) return <span key={i}>{w} </span>;
            return (
              <button key={i} onClick={() => setShown(s => new Set(s).add(i))} style={{
                border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 'inherit', color: c.accent, fontWeight: 700,
                background: c.accentSoft, borderRadius: 6, padding: '0 4px', margin: '0 2px', lineHeight: 1.2,
                display: 'inline-block', minWidth: Math.max(28, w.length * 9),
              }}>{'·'.repeat(Math.min(8, w.replace(/[^A-Za-zÁÉÍÓÚÑáéíóúñ']/g, '').length))}</button>
            );
          })}
        </p>
      </div>
      <div style={{ display: 'flex', gap: 10, marginTop: 14 }}>
        <button onClick={() => setShown(new Set(hideSet))} style={{ flex: 1, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 700, fontSize: 14, padding: '12px', borderRadius: 12, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7 }}><Icon name="eye" size={16} />{c.t('reveal_all')}</button>
        <button onClick={() => setShown(new Set())} style={{ flex: 1, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 700, fontSize: 14, padding: '12px', borderRadius: 12, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7 }}><Icon name="refresh" size={16} />{c.t('hide')}</button>
      </div>
      <div style={{ textAlign: 'center', fontSize: 13, color: c.textMuted, marginTop: 14 }}>{c.t('tap_blanks')}</div>
      {allShown && <RateBar onResult={onResult} />}
    </div>
  );
}

function SliderMode({ text, onResult }) {
  const c = useTheme();
  const [visible, setVisible] = React.useState(100);
  const words = text.split(' ');
  const shownCount = Math.round(words.length * visible / 100);
  const showOrder = React.useMemo(() => shuffleDeterministic(words.map((_, i) => i), text), [text]);
  const showSet = React.useMemo(() => new Set(showOrder.slice(0, shownCount)), [showOrder, shownCount]);
  const allHidden = visible <= 15;
  return (
    <div>
      <PracticeCard>
        <p style={{ margin: 0, fontFamily: c.headingStack, fontSize: 21, lineHeight: 1.7, color: c.text }}>
          {words.map((w, i) => {
            const show = showSet.has(i);
            return <span key={i} style={{ color: show ? c.text : c.faint }}>{show ? w : '•'.repeat(Math.min(8, cleanWord(w).length || 3))} </span>;
          })}
        </p>
      </PracticeCard>
      <div style={{ ...asfCard(c), marginTop: 14, padding: 16 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
          <span style={{ fontWeight: 800, color: c.text }}>Visible words</span>
          <span style={{ fontWeight: 800, color: c.accent }}>{visible}%</span>
        </div>
        <input type="range" min="0" max="100" step="5" value={visible} onChange={e => setVisible(Number(e.target.value))} style={{ width: '100%', accentColor: c.accent }} />
        <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
          <button onClick={() => setVisible(v => Math.max(0, v - 20))} style={{ flex: 1, border: `1px solid ${c.border}`, background: c.surface, color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 800, fontSize: 13.5, padding: '10px', borderRadius: 11 }}>Hide more</button>
          <button onClick={() => setVisible(v => Math.min(100, v + 20))} style={{ flex: 1, border: `1px solid ${c.border}`, background: c.surface, color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 800, fontSize: 13.5, padding: '10px', borderRadius: 11 }}>Show more</button>
        </div>
      </div>
      <PracticeHint>Slide toward 0% to remove random words across the whole quote.</PracticeHint>
      {allHidden && <RateBar onResult={onResult} />}
    </div>
  );
}

function pickPreferredVoice(voices) {
  if (!voices || !voices.length) return null;
  const english = voices.filter(v => /^en(-|_|$)/i.test(v.lang || ''));
  const list = english.length ? english : voices;
  const naturalNames = ['samantha', 'ava', 'siri', 'karen', 'daniel', 'victoria', 'allison', 'serena', 'aria', 'jenny', 'guy', 'google us english'];
  return list.find(v => naturalNames.some(name => (v.name || '').toLowerCase().includes(name))) || list.find(v => v.default) || list[0];
}

function ListenMode({ text, onResult }) {
  const c = useTheme();
  const [status, setStatus] = React.useState('Ready');
  const [voices, setVoices] = React.useState([]);
  const [voiceURI, setVoiceURI] = React.useState('');
  const canSpeak = typeof window !== 'undefined' && 'speechSynthesis' in window && typeof SpeechSynthesisUtterance !== 'undefined';
  const voiceOptions = voices.filter(v => /^en(-|_|$)/i.test(v.lang || ''));
  const selectableVoices = voiceOptions.length ? voiceOptions : voices;
  React.useEffect(() => {
    if (!canSpeak) return;
    const loadVoices = () => {
      const next = window.speechSynthesis.getVoices();
      setVoices(next);
      setVoiceURI(current => current || pickPreferredVoice(next)?.voiceURI || '');
    };
    loadVoices();
    window.speechSynthesis.onvoiceschanged = loadVoices;
    return () => { if (window.speechSynthesis.onvoiceschanged === loadVoices) window.speechSynthesis.onvoiceschanged = null; };
  }, [canSpeak]);
  const speak = () => {
    if (!canSpeak) { setStatus('Speech is not available in this browser.'); return; }
    window.speechSynthesis.cancel();
    const u = new SpeechSynthesisUtterance(text);
    const selectedVoice = voices.find(v => v.voiceURI === voiceURI) || pickPreferredVoice(voices);
    if (selectedVoice) u.voice = selectedVoice;
    u.rate = 0.9;
    u.pitch = 1;
    u.onstart = () => setStatus('Listening...');
    u.onend = () => setStatus('Finished');
    window.speechSynthesis.speak(u);
  };
  React.useEffect(() => () => { try { window.speechSynthesis?.cancel(); } catch {} }, []);
  return (
    <div>
      <PracticeCard style={{ textAlign: 'center', minHeight: 260, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16 }}>
        <div style={{ width: 74, height: 74, borderRadius: 999, display: 'grid', placeItems: 'center', background: c.accentSoft, color: c.accent, fontSize: 18, fontWeight: 900 }}>Audio</div>
        <p style={{ margin: 0, fontSize: 15, color: c.textMuted, lineHeight: 1.5 }}>Listen once, then try to say it without looking.</p>
        {selectableVoices.length > 1 && (
          <label style={{ width: '100%', textAlign: 'left', fontSize: 12.5, fontWeight: 800, color: c.textMuted }}>
            Voice
            <select value={voiceURI} onChange={e => setVoiceURI(e.target.value)} style={{
              width: '100%', marginTop: 6, borderRadius: 12, border: `1px solid ${c.border}`, background: c.surfaceAlt,
              color: c.text, fontFamily: 'inherit', fontWeight: 700, fontSize: 14, padding: '11px 12px',
            }}>
              {selectableVoices.map(v => <option key={v.voiceURI} value={v.voiceURI}>{v.name} · {v.lang}</option>)}
            </select>
          </label>
        )}
        <button onClick={speak} style={asfPrimaryBtn(c)}>Play quote</button>
        <div style={{ fontSize: 13, fontWeight: 800, color: c.accent }}>{status}</div>
      </PracticeCard>
      <RateBar onResult={onResult} />
    </div>
  );
}

function FirstLetterMode({ text, onResult }) {
  const c = useTheme();
  const [reveal, setReveal] = React.useState(false);
  const tokens = text.split(' ');
  return (
    <div>
      <PracticeCard>
        <p style={{ margin: 0, fontFamily: c.headingStack, fontSize: 21, lineHeight: 1.75, color: c.text }}>
          {tokens.map((w, i) => {
            const cleaned = cleanWord(w);
            const first = cleaned.charAt(0);
            const tail = Math.max(1, cleaned.length - 1);
            return <span key={i}>{reveal ? w : `${first}${'·'.repeat(Math.min(8, tail))}`} </span>;
          })}
        </p>
      </PracticeCard>
      <div style={{ display: 'flex', gap: 10, marginTop: 14 }}>
        <button onClick={() => setReveal(r => !r)} style={{ flex: 1, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 800, fontSize: 14, padding: '12px', borderRadius: 12 }}>{reveal ? 'Hide words' : 'Reveal words'}</button>
      </div>
      <PracticeHint>Use the first letters as a scaffold, then recite the full quote.</PracticeHint>
      {reveal && <RateBar onResult={onResult} />}
    </div>
  );
}

function ReorderMode({ text, onResult }) {
  const c = useTheme();
  const words = React.useMemo(() => text.split(/\s+/).filter(Boolean), [text]);
  const bank = React.useMemo(() => shuffleDeterministic(words.map((w, i) => ({ id: `${i}-${w}`, w, i })), text), [text]);
  const [picked, setPicked] = React.useState([]);
  const [wrongId, setWrongId] = React.useState('');
  const [message, setMessage] = React.useState('Tap words below in order.');
  const pickedIds = new Set(picked.map(x => x.id));
  const attempt = picked.map(x => x.w).join(' ');
  const score = scoreText(text, attempt);
  const complete = picked.length === words.length;
  const pick = (item) => {
    if (pickedIds.has(item.id)) return;
    if (item.i !== picked.length) {
      setWrongId(item.id);
      setMessage(`Not yet. Find word ${picked.length + 1}.`);
      return;
    }
    setWrongId('');
    setMessage(picked.length + 1 === words.length ? 'Complete.' : `Good. Now word ${picked.length + 2}.`);
    setPicked(p => [...p, item]);
  };
  const undo = () => {
    setWrongId('');
    setMessage('Back one word.');
    setPicked(p => p.slice(0, -1));
  };
  const reset = () => {
    setWrongId('');
    setMessage('Tap words below in order.');
    setPicked([]);
  };
  return (
    <div>
      <PracticeCard>
        <div style={{ minHeight: 120, borderRadius: 16, background: c.surfaceAlt, padding: 12, marginBottom: 14 }}>
          {picked.length ? picked.map(item => <span key={item.id} style={{ display: 'inline-block', margin: '4px', padding: '7px 10px', borderRadius: 999, background: c.surface, color: c.text, fontWeight: 700, fontSize: 14 }}>{item.w}</span>) : <span style={{ color: c.textMuted, fontSize: 14 }}>Tap words below in order.</span>}
        </div>
        <div style={{ minHeight: 22, margin: '0 0 12px', fontSize: 13, fontWeight: 800, color: wrongId ? '#B64A3B' : c.accent }}>{message}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
          {bank.map(item => {
            const used = pickedIds.has(item.id);
            const wrong = wrongId === item.id;
            return <button key={item.id} disabled={used} onClick={() => pick(item)} style={{ border: wrong ? '1.5px solid #B64A3B' : 'none', cursor: used ? 'default' : 'pointer', borderRadius: 999, padding: wrong ? '6.5px 9.5px' : '8px 11px', background: used ? 'transparent' : (wrong ? 'rgba(182,74,59,0.12)' : c.accentSoft), color: used ? c.faint : (wrong ? '#B64A3B' : c.text), fontFamily: 'inherit', fontWeight: 800, fontSize: 13.5, textDecoration: used ? 'line-through' : 'none' }}>{item.w}</button>;
          })}
        </div>
      </PracticeCard>
      <div style={{ display: 'flex', gap: 10, marginTop: 14 }}>
        <button onClick={undo} disabled={!picked.length} style={{ flex: 1, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: picked.length ? 'pointer' : 'default', fontFamily: 'inherit', fontWeight: 800, fontSize: 14, padding: '12px', borderRadius: 12 }}>Undo</button>
        <button onClick={reset} style={{ flex: 1, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 800, fontSize: 14, padding: '12px', borderRadius: 12 }}>Reset</button>
      </div>
      {complete && (
        <>
          <PracticeHint>{score.pct}% in exact word order</PracticeHint>
          <RateBar onResult={(ok) => onResult(ok || score.pct >= 80)} />
        </>
      )}
    </div>
  );
}

function ChoiceMemoryMode({ text, onResult }) {
  const c = useTheme();
  const words = React.useMemo(() => text.split(/\s+/).filter(Boolean), [text]);
  const [index, setIndex] = React.useState(0);
  const [wrongId, setWrongId] = React.useState('');
  const [mistakes, setMistakes] = React.useState(0);
  const done = index >= words.length;
  const current = words[index];
  const options = React.useMemo(() => {
    if (!current) return [];
    const seen = new Set([cleanWord(current).toLowerCase()]);
    const pool = words
      .map((w, i) => ({ id: `${i}-${w}`, w, i, clean: cleanWord(w).toLowerCase() }))
      .filter(item => item.i !== index && item.clean && !seen.has(item.clean));
    const distractors = [];
    shuffleDeterministic(pool, `${text}-${index}`).forEach(item => {
      if (distractors.length >= 3) return;
      if (!seen.has(item.clean)) {
        seen.add(item.clean);
        distractors.push(item);
      }
    });
    return shuffleDeterministic([{ id: `${index}-${current}`, w: current, i: index, clean: cleanWord(current).toLowerCase() }, ...distractors], `${current}-${index}`);
  }, [text, words, index, current]);
  const choose = (item) => {
    if (item.i === index) {
      setWrongId('');
      setIndex(i => i + 1);
    } else {
      setWrongId(item.id);
      setMistakes(n => n + 1);
    }
  };
  const reset = () => {
    setIndex(0);
    setWrongId('');
    setMistakes(0);
  };
  const score = words.length ? Math.max(0, Math.round(((words.length - mistakes) / words.length) * 100)) : 0;
  return (
    <div>
      <PracticeCard>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 10, marginBottom: 12 }}>
          <div style={{ fontWeight: 900, color: c.text }}>Choose the next word</div>
          <div style={{ fontSize: 13, fontWeight: 800, color: c.textMuted }}>{Math.min(index + 1, words.length)}/{words.length}</div>
        </div>
        <p style={{ minHeight: 120, margin: 0, padding: 14, borderRadius: 16, background: c.surfaceAlt, color: c.text, fontFamily: c.headingStack, fontSize: 20, lineHeight: 1.55 }}>
          {words.slice(0, index).join(' ')}{index > 0 ? ' ' : ''}{!done && <span style={{ color: c.accent }}>_____</span>}
        </p>
        {!done && (
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 9, marginTop: 14 }}>
            {options.map(item => {
              const wrong = wrongId === item.id;
              return <button key={item.id} onClick={() => choose(item)} style={{ border: `1.5px solid ${wrong ? '#B64A3B' : c.border}`, borderRadius: 13, background: wrong ? 'rgba(182,74,59,0.12)' : c.surface, color: wrong ? '#B64A3B' : c.text, padding: '12px 8px', fontFamily: 'inherit', fontWeight: 900, fontSize: 14 }}>{item.w}</button>;
            })}
          </div>
        )}
        <div style={{ minHeight: 22, marginTop: 12, fontSize: 13, fontWeight: 800, color: wrongId ? '#B64A3B' : c.textMuted }}>
          {done ? `Finished with ${mistakes} ${mistakes === 1 ? 'miss' : 'misses'}.` : (wrongId ? 'Try again. That word comes later.' : 'Build the quote one word at a time.')}
        </div>
      </PracticeCard>
      <button onClick={reset} style={{ width: '100%', marginTop: 14, border: `1px solid ${c.border}`, background: 'transparent', color: c.textMuted, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 800, fontSize: 14, padding: '12px', borderRadius: 12 }}>Reset choices</button>
      {done && (
        <>
          <PracticeHint>{score}% accuracy</PracticeHint>
          <RateBar onResult={(ok) => onResult(ok || score >= 80)} />
        </>
      )}
    </div>
  );
}

function SpeakMode({ text, onResult }) {
  const c = useTheme();
  const Rec = typeof window !== 'undefined' && (window.SpeechRecognition || window.webkitSpeechRecognition);
  const [listening, setListening] = React.useState(false);
  const [spoken, setSpoken] = React.useState('');
  const [error, setError] = React.useState('');
  const score = scoreText(text, spoken);
  const start = () => {
    if (!Rec) { setError('Speech recognition is not available in this browser. You can still use Type mode.'); return; }
    setError('');
    const rec = new Rec();
    rec.lang = 'en-US';
    rec.interimResults = false;
    rec.continuous = false;
    rec.onstart = () => setListening(true);
    rec.onerror = e => { setError(e.error ? `Speech error: ${e.error}` : 'Speech recognition stopped.'); setListening(false); };
    rec.onend = () => setListening(false);
    rec.onresult = e => setSpoken(Array.from(e.results).map(r => r[0]?.transcript || '').join(' '));
    rec.start();
  };
  return (
    <div>
      <PracticeCard style={{ textAlign: 'center' }}>
        <div style={{ width: 74, height: 74, borderRadius: 999, display: 'grid', placeItems: 'center', background: c.accentSoft, color: c.accent, fontSize: 18, fontWeight: 900 }}>Mic</div>
        <h2 style={{ margin: '10px 0 6px', fontFamily: c.headingStack, color: c.text }}>Speak from memory</h2>
        <p style={{ margin: '0 0 16px', fontSize: 14, lineHeight: 1.45, color: c.textMuted }}>Say the quote aloud. Browser support varies on iPhone.</p>
        <button onClick={start} disabled={listening} style={asfPrimaryBtn(c)}>{listening ? 'Listening...' : 'Start speaking'}</button>
        {error && <p style={{ margin: '14px 0 0', color: c.textMuted, fontSize: 13 }}>{error}</p>}
      </PracticeCard>
      {spoken && (
        <PracticeCard style={{ marginTop: 14 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 10 }}>
            <span style={{ fontFamily: c.headingStack, fontSize: 34, fontWeight: 800, color: score.pct >= 80 ? c.accent : c.text }}>{score.pct}%</span>
            <span style={{ fontSize: 13, color: c.textMuted }}>{score.correct}/{score.total} words in order</span>
          </div>
          <p style={{ margin: 0, fontSize: 15, color: c.text, lineHeight: 1.5 }}>{spoken}</p>
        </PracticeCard>
      )}
      {spoken && <RateBar onResult={(ok) => onResult(ok || score.pct >= 80)} />}
    </div>
  );
}

function TypeMode({ text, onResult }) {
  const c = useTheme();
  const [val, setVal] = React.useState('');
  const [checked, setChecked] = React.useState(false);
  const norm = s => s.toLowerCase().replace(/[^a-zñáéíóú' ]/g, '').split(/\s+/).filter(Boolean);
  const target = norm(text);
  const typed = norm(val);
  const correct = typed.filter((w, i) => target[i] === w).length;
  const acc = target.length ? Math.round((correct / target.length) * 100) : 0;
  const words = text.split(' ');

  return (
    <div>
      {!checked ? (
        <textarea value={val} onChange={e => setVal(e.target.value)} autoFocus placeholder={c.t('type_placeholder')} style={{
          width: '100%', minHeight: 150, resize: 'none', borderRadius: 16, padding: '18px', boxSizing: 'border-box',
          border: `1px solid ${c.border}`, background: c.surface, color: c.text, fontFamily: c.headingStack, fontSize: 19, lineHeight: 1.5, outline: 'none',
        }} />
      ) : (
        <div style={{ ...asfCard(c), padding: '24px 22px' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 14 }}>
            <span style={{ fontFamily: c.headingStack, fontSize: 38, fontWeight: 700, color: acc >= 80 ? c.accent : c.text }}>{acc}%</span>
            <span style={{ fontSize: 14, color: c.textMuted }}>{correct} {c.t('of')} {target.length} {c.t('words_of')}</span>
          </div>
          <p style={{ margin: 0, fontFamily: c.headingStack, fontSize: 20, lineHeight: 1.6 }}>
            {target.map((w, i) => (
              <span key={i} style={{ color: typed[i] === w ? c.accent : c.textMuted, fontWeight: typed[i] === w ? 700 : 500, textDecoration: typed[i] === w ? 'none' : 'underline', textDecorationStyle: 'dotted' }}>{words[i] || w} </span>
            ))}
          </p>
        </div>
      )}
      {!checked ? (
        <button onClick={() => setChecked(true)} disabled={!val.trim()} style={{
          width: '100%', marginTop: 14, border: 'none', cursor: val.trim() ? 'pointer' : 'default', borderRadius: 13,
          background: val.trim() ? c.accent : c.border, color: val.trim() ? c.accentInk : c.textMuted,
          fontFamily: 'inherit', fontWeight: 700, fontSize: 15.5, padding: '15px',
        }}>{c.t('check')}</button>
      ) : <RateBar onResult={(ok) => onResult(ok || acc >= 80)} />}
    </div>
  );
}

Object.assign(window, { MemorizeScreen, PracticeScreen });
