/* Apprendre — entraînement libre : exercices, vocabulaire fréquent, structures,
   Hangeul (secours), audio. Distinction claire SRS / non-SRS. */

function IntentRow({ icon, eyebrow, title, body, badge, actions }) {
  return (
    <div className="card" style={{ padding: "var(--pad-card)", display: "flex", gap: 18, alignItems: "center", flexWrap: "wrap" }}>
      <div style={{ width: 46, height: 46, flexShrink: 0, borderRadius: 12, background: "var(--accent-tint)", color: "var(--accent-deep)", display: "grid", placeItems: "center" }}><Icon name={icon} size={22} /></div>
      <div style={{ flex: "1 1 240px", minWidth: 200 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}><Eyebrow>{eyebrow}</Eyebrow>{badge && <span className="chip" style={{ fontSize: 10.5, padding: "1px 7px", background: "var(--card-2)", color: "var(--ink-faint)" }}>{badge}</span>}</div>
        <div style={{ fontFamily: "var(--serif)", fontSize: 21, color: "var(--accent-deep)", marginTop: 3 }}>{title}</div>
        <p className="muted" style={{ fontSize: 14, margin: "4px 0 0" }}>{body}</p>
      </div>
      <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>{actions}</div>
    </div>
  );
}

function SrsLegend() {
  return (
    <div style={{ display: "flex", gap: 14, flexWrap: "wrap", alignItems: "center", padding: "10px 14px", borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px solid var(--rule)" }}>
      <span className="faint" style={{ fontSize: 12.5 }}>Légende :</span>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5 }}><span className="chip" style={{ fontSize: 10.5, padding: "1px 7px", background: "var(--accent-tint)", color: "var(--accent-deep)", borderColor: "transparent" }}>SRS</span> modifie vos révisions</span>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5 }}><span className="chip" style={{ fontSize: 10.5, padding: "1px 7px" }}>libre</span> entraînement sans effet SRS</span>
    </div>
  );
}

function ExercicesTab({ go }) {
  const toBuild = "À brancher via exercise-router avant activation.";
  const TRAINING_DIALOGUE_ID = "P1-04A";
  const launchRepair = (source) => go("runner", {
    duration: 15,
    charge: "consolidation",
    intention: "reparer",
    drillMode: "dictee",
    source,
  });
  const draft = window.LangSessionRunner && window.LangSessionRunner.readDraft ? window.LangSessionRunner.readDraft(window.LangStore) : null;
  const draftBlocks = draft && draft.session ? (draft.session.blocks || []) : [];
  const draftTotal = draftBlocks.length || (draft && draft.plan && draft.plan.blocks ? draft.plan.blocks.length : 0);
  const draftDone = draftBlocks.filter((b) => b.completion !== "pending").length;
  const draftRemaining = Math.max(0, draftTotal - draftDone);
  const exercises = [
    { label: "Dictée par blocs", icon: "layers", meta: "39 scènes · 136 lignes", onClick: () => go("apprendre", { tab: "audio", focus: "dictee" }) },
    { label: "Micro-contrastes", icon: "spark", meta: "ㅓ/ㅗ · ㅡ/ㅜ", disabled: true },
    { label: "Jours guidés", icon: "today", meta: "Jour 4/12", disabled: true },
    { label: "Reprise des ratés", icon: "refresh", meta: "6 items", onClick: () => launchRepair("rates") },
    { label: "Reconstruction", icon: "grid", meta: "tuiles", onClick: () => go("dialogues", { dialogue: TRAINING_DIALOGUE_ID, mode: "entrainement" }) },
    { label: "Mémoire d'erreurs", icon: "book", meta: "journal", disabled: true },
  ];
  return (
    <div style={{ display: "grid", gap: "var(--gap)" }}>
      <SrsLegend />
      {draft && <IntentRow icon="play" eyebrow="Continuer" title="Reprendre la séance en cours" badge="suivi" body={draftRemaining ? `${draftRemaining} étape${draftRemaining > 1 ? "s" : ""} restante${draftRemaining > 1 ? "s" : ""}.` : "Bilan à finaliser."} actions={<Btn kind="primary" size="sm" icon="play" onClick={() => go("runner", { resume: true, source: "learn" })}>Continuer</Btn>} />}
      <IntentRow icon="wrench" eyebrow="Réparer" title="Reprendre mes ratés" badge="SRS" body="6 items signalés et 2 points faibles récurrents (batchim, -아요/-어요)." actions={<Btn kind="secondary" size="sm" icon="refresh" onClick={() => launchRepair("repair")}>Réparer (6)</Btn>} />
      <div>
        <div className="eyebrow" style={{ marginBottom: 10 }}>S'entraîner par blocs <span className="faint" style={{ fontWeight: 400, textTransform: "none", letterSpacing: 0 }}>· {window.LANGUES_DATA.corpus.exercises} exercices, sans effet SRS</span></div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(190px,1fr))", gap: 10 }}>
          {exercises.map(ex => (
            <Tile key={ex.label} icon={ex.icon} label={ex.label} meta={ex.meta} badge="libre" disabled={ex.disabled} title={ex.disabled ? toBuild : undefined} onClick={ex.onClick} />
          ))}
        </div>
      </div>
    </div>
  );
}

const LISTENING_MODE_LABELS = { ecoute: "Écoute", repetition: "Répétition", lecture: "Lecture" };
const LISTENING_MODE_HINTS = {
  ecoute: "Écoutez, devinez, puis révélez le texte et le sens.",
  repetition: "Écoutez et répétez à voix haute (shadowing). Sans effet SRS.",
  lecture: "Lisez d'abord le coréen, puis écoutez pour vérifier votre lecture.",
};

function ListeningLibrary({ kindFilter, itemKindFilter, emptyTitle, emptyBody }) {
  const [state, setState] = useState(() => ({ status: window.LangData && window.LangData.loadListening ? "loading" : "error", collections: [] }));
  const [reloadKey, setReloadKey] = useState(0);
  const [ci, setCi] = useState(0);
  const [mode, setMode] = useState("ecoute");
  const [i, setI] = useState(0);
  const [revealed, setRevealed] = useState(false);
  const [playError, setPlayError] = useState(false);
  const [toast, setToast] = useState(null);
  const [flagged, setFlagged] = useState(() => new Set());

  useEffect(() => {
    let alive = true;
    if (!window.LangData || !window.LangData.loadListening) {
      setState({ status: "error", collections: [] });
      return () => { alive = false; };
    }
    setState(prev => ({ ...prev, status: "loading" }));
    window.LangData.loadListening()
      .then(payload => { if (alive) setState({ status: "ready", collections: payload.collections || [] }); })
      .catch(() => { if (alive) setState({ status: "error", collections: [] }); });
    return () => { alive = false; if (window.LangAudio) window.LangAudio.stop(); };
  }, [reloadKey]);

  const collections = kindFilter ? state.collections.filter(c => c.kind === kindFilter) : state.collections;
  const collection = collections[ci] || null;
  const modes = (collection && collection.modes) || ["ecoute"];
  const rawItems = (collection && collection.items) || [];
  const items = itemKindFilter ? rawItems.filter(item => item.kind === itemKindFilter) : rawItems;
  const item = items.length ? items[i % items.length] : null;

  useEffect(() => { setCi(0); }, [kindFilter]);
  useEffect(() => { setI(0); setRevealed(false); setPlayError(false); if (window.LangAudio) window.LangAudio.stop(); }, [ci, mode, itemKindFilter]);
  useEffect(() => { if (collection && modes.indexOf(mode) < 0) setMode(modes[0]); }, [ci]); // eslint-disable-line

  const play = (rate) => {
    if (!item || !window.LangAudio) return;
    setPlayError(false);
    Promise.resolve(window.LangAudio.speak(item.kr || "", {
      rate: rate || 1,
      ref: item.audio_ref || item.audioRef || item.id,
      url: item.audioUrl,
    }))
      .then(ok => { if (ok === false) setPlayError(true); });
  };
  const step = (delta) => {
    if (!items.length) return;
    setI(x => (x + items.length + delta) % items.length);
    setRevealed(false); setPlayError(false);
    if (window.LangAudio) window.LangAudio.stop();
  };
  const flag = () => {
    if (!item) return;
    setFlagged(prev => { const next = new Set(prev); next.add(item.id); return next; });
    setToast("Son signalé pour révision"); setTimeout(() => setToast(null), 1900);
  };

  if (state.status === "loading") return <LoadingCard lines={4} />;
  if (state.status === "error") return <ErrorState title="Écoute indisponible" body="La bibliothèque d'écoute (listening.json) ne peut pas être chargée." onRetry={() => setReloadKey(x => x + 1)} />;
  if (!collection) return <EmptyState icon="speaker" title={emptyTitle || "Aucune source d'écoute"} body={emptyBody || "Aucune collection audio n'est disponible."} />;

  const showKrUpfront = mode === "repetition" || mode === "lecture";
  const hasKr = !!(item && item.kr);

  return (
    <div style={{ display: "grid", gap: "var(--gap)" }}>
      <FilterChips options={collections.map(c => c.label)} value={collection.label} onChange={(label) => { const idx = collections.findIndex(c => c.label === label); if (idx >= 0) setCi(idx); }} />

      <div className="card" style={{ padding: "var(--pad-card)", display: "grid", gap: 12, background: "var(--card-2)" }}>
        <div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "flex-start", flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 260px" }}>
            <Eyebrow>{collection.provider}</Eyebrow>
            <div style={{ fontFamily: "var(--serif)", fontSize: 21, color: "var(--accent-deep)", marginTop: 2 }}>{collection.label}</div>
            <p className="muted" style={{ fontSize: 13.5, margin: "4px 0 0" }}>{collection.note}</p>
          </div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "flex-end" }}>
            <span className="chip">{items.length} / {rawItems.length} sons</span>
            <span className="chip" style={{ background: "var(--accent-tint)", color: "var(--accent-deep)", borderColor: "transparent" }}>audio réel</span>
            <span className="chip">libre</span>
          </div>
        </div>
        <Segmented value={modes.indexOf(mode) >= 0 ? mode : modes[0]} options={modes.map(md => ({ value: md, label: LISTENING_MODE_LABELS[md] || md }))} onChange={setMode} />
        <p className="faint" style={{ fontSize: 12.5, margin: 0 }}>{LISTENING_MODE_HINTS[mode] || ""}</p>
      </div>

      {!item ? <EmptyState icon="list" title="Collection vide" body={itemKindFilter ? "Aucun son ne correspond à ce filtre dans cette collection." : "Aucun son dans cette collection."} /> : (
        <div className="card" style={{ padding: "calc(var(--pad-card) + 2px)", display: "grid", gap: 16 }}>
          <div style={{ display: "flex", justifyContent: "space-between", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
            <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 12 }}>{(i % items.length) + 1} / {items.length}</span>
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
              {item.theme && <span className="chip" style={{ fontSize: 11 }}>{item.theme}</span>}
              {item.kind && <span className="chip" style={{ fontSize: 11 }}>{item.kind}</span>}
            </div>
          </div>

          <div style={{ minHeight: 168, display: "grid", alignContent: "center", justifyItems: "center", textAlign: "center", gap: 12 }}>
            <button onClick={() => play(1)} style={{ width: 72, height: 72, borderRadius: 999, border: "none", background: "var(--accent)", color: "#fff", cursor: "pointer", display: "grid", placeItems: "center", boxShadow: "0 16px 28px -20px var(--accent)" }}><Icon name="play" size={30} /></button>
            {showKrUpfront && hasKr && <div className="kr" style={{ fontSize: 40, color: "var(--accent-deep)", lineHeight: 1.3 }}>{item.kr}</div>}
            {item.reading && (showKrUpfront || revealed) && <div className="faint" style={{ fontFamily: "var(--mono)", fontSize: 14 }}>{item.reading}</div>}
            {playError && <span className="chip fade-up" style={{ background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>Son indisponible — aucune voix de secours pour cette source.</span>}
            {revealed && (
              <div className="fade-up" style={{ display: "grid", gap: 6, justifyItems: "center" }}>
                {!showKrUpfront && hasKr && <div className="kr" style={{ fontSize: 38, color: "var(--accent-deep)" }}>{item.kr}</div>}
                {item.fr && <div style={{ fontFamily: "var(--serif)", fontSize: 24, color: "var(--ink)" }}>{item.fr}</div>}
                {!item.fr && item.en && <div className="muted" style={{ fontSize: 15 }}>{item.en}</div>}
                {!hasKr && !item.fr && item.kind === "reference" && <p className="faint" style={{ fontSize: 12.5, margin: 0 }}>Piste de référence — écoute seule.</p>}
              </div>
            )}
          </div>

          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <Btn kind="secondary" size="sm" icon="repeat" onClick={() => play(.75)}>Lent</Btn>
            {!revealed
              ? <Btn kind="primary" size="sm" icon="eye" onClick={() => setRevealed(true)}>{mode === "lecture" ? "Vérifier" : "Révéler"}</Btn>
              : <Btn kind="ghost" size="sm" icon="speaker" onClick={() => play(1)}>Réécouter</Btn>}
            <Btn kind="ghost" size="sm" icon="flag" onClick={flag}>{flagged.has(item.id) ? "Signalé" : "Signaler"}</Btn>
            <span style={{ flex: 1 }} />
            <Btn kind="ghost" size="sm" icon="prev" onClick={() => step(-1)}>Précédent</Btn>
            <Btn kind="primary" size="sm" iconR="next" onClick={() => step(1)}>{mode === "repetition" ? "À moi, suivant" : "Suivant"}</Btn>
          </div>
        </div>
      )}

      {toast && <div className="fade-up" style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", background: "var(--ink)", color: "var(--paper)", padding: "11px 18px", borderRadius: 999, fontSize: 14, fontWeight: 600, zIndex: 60, boxShadow: "var(--shadow-pop)" }}>{toast}</div>}
    </div>
  );
}

function normalizeVocabFreqPayload(payload) {
  return {
    items: (payload && Array.isArray(payload.items)) ? payload.items : [],
    meta: (payload && payload.meta) || {},
  };
}

function VocabFreqTab() {
  const modes = window.LANGUES_DATA.vocabFreqModes;
  const enabledModes = ["sens", "production", "ecoute", "dictee"];
  const [m, setM] = useState("sens");
  const [level, setLevel] = useState("Tous");
  const [loadState, setLoadState] = useState(() => ({
    status: window.LangData && window.LangData.loadVocabFreq ? "loading" : "error",
    items: [],
    meta: null,
    error: null,
  }));
  const [reloadKey, setReloadKey] = useState(0);
  const [i, setI] = useState(0);
  const [revealed, setRevealed] = useState(false);
  const [answer, setAnswer] = useState("");
  const [checked, setChecked] = useState(null);
  const [stats, setStats] = useState({ known: 0, again: 0, done: 0 });
  const cur = modes.find(x => x.id === m) || modes[0];

  useEffect(() => {
    let alive = true;
    if (!window.LangData || !window.LangData.loadVocabFreq) {
      setLoadState({ status: "error", items: [], meta: null, error: new Error("loadVocabFreq missing") });
      return () => { alive = false; };
    }
    setLoadState(prev => ({ ...prev, status: "loading", error: null }));
    window.LangData.loadVocabFreq()
      .then(payload => {
        if (!alive) return;
        const next = normalizeVocabFreqPayload(payload);
        if (window.LANGUES_DATA && window.LANGUES_DATA.corpus) window.LANGUES_DATA.corpus.vocabFreq = next.meta.total || next.items.length;
        setLoadState({ status: "ready", items: next.items, meta: next.meta, error: null });
        setI(0);
      })
      .catch(error => {
        if (!alive) return;
        setLoadState({ status: "error", items: [], meta: null, error });
      });
    return () => { alive = false; };
  }, [reloadKey]);

  const resetItem = () => {
    setRevealed(false);
    setAnswer("");
    setChecked(null);
    if (window.LangAudio) window.LangAudio.stop();
  };
  const allItems = loadState.items || [];
  const levels = ["Tous", ...Array.from(new Set(allItems.map(item => item.level || item.niveau_cible).filter(Boolean)))];
  const shown = level === "Tous" ? allItems : allItems.filter(item => (item.level || item.niveau_cible) === level);
  const item = shown.length ? shown[i % shown.length] : null;
  const progress = shown.length ? (stats.done % shown.length) / shown.length : 0;
  const exact = !!checked && !!checked.correct;
  const feedbackTags = (checked && checked.tags) || [];

  useEffect(() => { setI(0); resetItem(); }, [level, m]);

  const playItem = (rate) => {
    if (!item || !window.LangAudio) return;
    window.LangAudio.speak(item.kr, { rate: rate || 1, ref: item.audio_ref || item.audioRef, url: item.audioUrl });
  };
  const mark = (kind) => {
    setStats(s => ({ ...s, [kind]: s[kind] + 1, done: s.done + 1 }));
    setI(x => x + 1);
    resetItem();
  };
  const jump = (delta) => {
    if (!shown.length) return;
    setI(x => (x + shown.length + delta) % shown.length);
    resetItem();
  };

  const modeOptions = modes.map(mode => ({
    value: mode.id,
    label: mode.label,
    disabled: !enabledModes.includes(mode.id),
    title: enabledModes.includes(mode.id) ? undefined : "À brancher via exercise-router",
  }));
  const count = (loadState.meta && loadState.meta.total) || allItems.length || window.LANGUES_DATA.corpus.vocabFreq;
  const audioReady = (loadState.meta && loadState.meta.audio_ready) || allItems.filter(item => item.audioUrl).length;
  const upcoming = item ? Array.from({ length: Math.min(12, shown.length) }, (_, offset) => shown[(i + offset) % shown.length]) : [];

  return (
    <div style={{ display: "grid", gap: "var(--gap)" }}>
      <div className="card" style={{ padding: "var(--pad-card)", display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap", background: "var(--card-2)" }}>
        <div style={{ fontFamily: "var(--serif)", fontSize: 30, color: "var(--accent-deep)" }}>{count.toLocaleString("fr-FR")}</div>
        <p className="muted" style={{ fontSize: 14.5, margin: 0, flex: "1 1 260px" }}>entrées fréquentielles coréennes, avec {audioReady.toLocaleString("fr-FR")} sons MP3 déployables.</p>
        <span className="chip">libre</span>
      </div>
      <div>
        <div className="eyebrow" style={{ marginBottom: 8 }}>Angle d'entraînement</div>
        <Segmented value={m} options={modeOptions} onChange={setM} />
        <p className="muted" style={{ fontSize: 14, margin: "10px 0 0" }}><strong style={{ color: "var(--ink)" }}>{cur.label}.</strong> {cur.desc}.</p>
      </div>

      {loadState.status === "loading" && <LoadingCard lines={4} />}
      {loadState.status === "error" && <ErrorState title="Vocabulaire indisponible" body="Le fichier canonique du vocabulaire fréquent ne peut pas etre chargé." onRetry={() => setReloadKey(x => x + 1)} />}
      {loadState.status !== "loading" && loadState.status !== "error" && !item && <EmptyState icon="list" title="Aucun mot disponible" body="La banque vocabulaire est vide pour ce filtre." />}

      {item && (
        <>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
            <FilterChips options={levels} value={level} onChange={setLevel} />
            <span className="faint" style={{ fontSize: 13 }}>{shown.length.toLocaleString("fr-FR")} items · {stats.done} vus</span>
          </div>

          <div className="card" style={{ padding: "calc(var(--pad-card) + 2px)", display: "grid", gap: 16 }}>
            <div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "flex-start", flexWrap: "wrap" }}>
              <div>
                <Eyebrow>Série fréquentielle · {cur.label}</Eyebrow>
                <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginTop: 4, flexWrap: "wrap" }}>
                  <span className="chip">#{item.order}</span>
                  <span className="chip">{item.level || item.niveau_cible}</span>
                  <span className="chip">{item.classe || item.pos}</span>
                  <span className="chip">rang {item.rang}</span>
                </div>
              </div>
              <div style={{ minWidth: 160, flex: "0 1 240px" }}>
                <div className="faint" style={{ fontSize: 11.5, marginBottom: 6 }}>Progression locale</div>
                <Progress value={progress} height={5} />
              </div>
            </div>

            <div style={{ minHeight: 190, display: "grid", alignContent: "center", justifyItems: "center", textAlign: "center", gap: 12 }}>
              {m === "production" ? (
                <>
                  <div style={{ fontFamily: "var(--serif)", fontSize: 34, color: "var(--accent-deep)", lineHeight: 1.15 }}>{item.fr}</div>
                  {revealed && <Speakable text={item.kr} audioRef={item.audio_ref || item.audioRef} audioUrl={item.audioUrl} className="kr fade-up" style={{ fontSize: 46, color: "var(--ink)", padding: "5px 16px" }}>{item.kr}</Speakable>}
                </>
              ) : m === "ecoute" ? (
                <>
                  <button onClick={() => playItem(1)} style={{ width: 70, height: 70, borderRadius: 999, border: "none", background: "var(--accent)", color: "#fff", cursor: "pointer", display: "grid", placeItems: "center", boxShadow: "0 16px 28px -20px var(--accent)" }}><Icon name="play" size={30} /></button>
                  {revealed && <div className="fade-up" style={{ display: "grid", gap: 8, justifyItems: "center" }}><Speakable text={item.kr} audioRef={item.audio_ref || item.audioRef} audioUrl={item.audioUrl} className="kr" style={{ fontSize: 44, color: "var(--accent-deep)", padding: "5px 16px" }}>{item.kr}</Speakable><div style={{ fontFamily: "var(--serif)", fontSize: 28, color: "var(--ink)" }}>{item.fr}</div></div>}
                </>
              ) : m === "dictee" ? (
                <>
                  <div style={{ display: "flex", gap: 8, justifyContent: "center", flexWrap: "wrap" }}>
                    <Btn kind="primary" size="sm" icon="play" onClick={() => playItem(1)}>Écouter</Btn>
                    <Btn kind="secondary" size="sm" icon="repeat" onClick={() => playItem(.78)}>Lent</Btn>
                  </div>
                  <textarea value={answer} onChange={e => { setAnswer(e.target.value); setChecked(null); }} className="kr" lang="ko" autoCapitalize="off" autoCorrect="off" spellCheck={false} placeholder="한국어..." style={{ width: "min(100%, 460px)", minHeight: 76, resize: "vertical", padding: 13, borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card)", color: "var(--ink)", fontSize: 24, lineHeight: 1.4 }} />
                  <div style={{ width: "min(100%, 460px)" }}><KoreanKeyboard value={answer} onChange={(next) => { setAnswer(next); setChecked(null); }} compact /></div>
                  {checked && <span className="chip fade-up" style={{ background: exact ? "var(--good-tint)" : "var(--warn-tint)", color: exact ? "var(--good)" : "var(--warn)", borderColor: "transparent" }}>{dictationFeedbackLabel(checked)}</span>}
                </>
              ) : (
                <>
                  <Speakable text={item.kr} audioRef={item.audio_ref || item.audioRef} audioUrl={item.audioUrl} className="kr" style={{ fontSize: 58, color: "var(--accent-deep)", padding: "5px 18px" }}>{item.kr}</Speakable>
                  <div className="faint" style={{ fontFamily: "var(--mono)", fontSize: 14 }}>{item.reading}</div>
                  {revealed && <div className="fade-up" style={{ fontFamily: "var(--serif)", fontSize: 32, color: "var(--ink)" }}>{item.fr}</div>}
                </>
              )}
            </div>

            {revealed && m !== "dictee" && (
              <div className="fade-up" style={{ padding: 14, borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px solid var(--rule)", display: "grid", gap: 7 }}>
                <div style={{ display: "flex", gap: 10, alignItems: "baseline", flexWrap: "wrap" }}>
                  <Speakable text={item.kr} audioRef={item.audio_ref || item.audioRef} audioUrl={item.audioUrl} className="kr" style={{ fontSize: 24, color: "var(--accent-deep)" }}>{item.kr}</Speakable>
                  <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 13 }}>{item.reading}</span>
                  <span style={{ fontFamily: "var(--serif)", fontSize: 22 }}>{item.fr}</span>
                </div>
                {item.example_target && <p className="faint" style={{ fontSize: 12.5, margin: 0 }}>Source : <span className="kr">{item.example_target}</span></p>}
              </div>
            )}

            {checked && m === "dictee" && (
              <div className="fade-up" style={{ padding: 14, borderRadius: "var(--radius-sm)", background: exact ? "var(--good-tint)" : "var(--card-2)", border: "1px solid var(--rule)", display: "grid", gap: 7 }}>
                <Speakable text={item.kr} audioRef={item.audio_ref || item.audioRef} audioUrl={item.audioUrl} className="kr" style={{ fontSize: 30, color: "var(--accent-deep)" }}>{item.kr}</Speakable>
                <span style={{ fontFamily: "var(--serif)", fontSize: 21 }}>{item.fr}</span>
                {!exact && feedbackTags.length > 0 && <span className="faint" style={{ fontSize: 12.5 }}>Erreurs : {feedbackTags.join(" · ")}</span>}
              </div>
            )}

            <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
              {m !== "dictee" && <Btn kind="secondary" size="sm" icon="speaker" onClick={() => playItem(1)}>Écouter</Btn>}
              {m === "dictee"
                ? <Btn kind="primary" size="sm" icon="check" disabled={!answer.trim()} onClick={() => setChecked(compareDictationAnswer(item.kr, answer))}>Vérifier</Btn>
                : <Btn kind="primary" size="sm" icon="eye" onClick={() => setRevealed(true)}>Révéler</Btn>}
              {m === "dictee" && <Btn kind="ghost" size="sm" icon="eye" onClick={() => { setChecked(revealDictationAnswer(item.kr, answer)); setRevealed(true); }}>Révéler</Btn>}
              <span style={{ flex: 1 }} />
              <Btn kind="ghost" size="sm" icon="prev" onClick={() => jump(-1)}>Précédent</Btn>
              <Btn kind="secondary" size="sm" iconR="next" onClick={() => mark("again")}>À revoir</Btn>
              <Btn kind="primary" size="sm" icon="check" onClick={() => mark("known")}>Connu</Btn>
            </div>
          </div>

          <Collapsible eyebrow="File" title="Prochains items" hint={`${shown.length.toLocaleString("fr-FR")} disponibles`}>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(190px,1fr))", gap: 8 }}>
              {upcoming.map((v, offset) => (
                <button key={`${v.id}-${offset}`} onClick={() => { setI((i + offset) % shown.length); resetItem(); }} style={{ textAlign: "left", padding: "9px 11px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: offset === 0 ? "var(--accent-tint)" : "var(--card)", cursor: "pointer" }}>
                  <div style={{ display: "flex", gap: 7, alignItems: "baseline" }}><span className="kr" style={{ fontSize: 18, color: "var(--accent-deep)" }}>{v.kr}</span><span className="faint" style={{ fontSize: 11 }}>#{v.order}</span></div>
                  <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>{v.fr}</div>
                </button>
              ))}
            </div>
          </Collapsible>
        </>
      )}
    </div>
  );
}

function StructuresTab({ go }) {
  const structures = window.LANGUES_DATA.structures;
  return (
    <div style={{ display: "grid", gap: "var(--gap)" }}>
      <p className="muted" style={{ fontSize: 14.5, margin: 0 }}>Les structures de base, regroupées par fonction. Les échéances dues apparaissent en premier.</p>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(240px,1fr))", gap: "var(--gap)" }}>
        {structures.map(s => (
          <div key={s.id} className="card" style={{ padding: 18, display: "grid", gap: 11 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
              <div><div style={{ fontFamily: "var(--serif)", fontSize: 18, color: "var(--accent-deep)" }}>{s.label} <span className="kr faint" style={{ fontSize: 14 }}>{s.kr}</span></div><div className="faint" style={{ fontSize: 12, marginTop: 2 }}>{s.n} points</div></div>
              {s.due > 0 && <span className="chip" style={{ background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>{s.due} dus</span>}
            </div>
            <div className="kr" style={{ fontSize: 17, color: "var(--ink)" }}>{s.ex}</div>
            <Btn kind={s.due > 0 ? "primary" : "secondary"} size="sm" onClick={() => go("cartes", { mode: s.due > 0 ? "dues" : "drill", focus: `structure:${s.id}`, source: "structures" })}>{s.due > 0 ? "Réviser" : "S'entraîner"}</Btn>
          </div>
        ))}
      </div>
    </div>
  );
}

function AudioTab({ focus }) {
  const [mode, setMode] = useState(focus === "dictee" ? "dictee" : "tracks");
  useEffect(() => { if (focus === "dictee") setMode("dictee"); }, [focus]);
  return (
    <div style={{ display: "grid", gap: "var(--gap)" }}>
      <p className="muted" style={{ fontSize: 14.5, margin: 0 }}>L'audio est intégré au fil de l'apprentissage — écoutez, répétez, lisez. Sons réels déployés, signalez un son qui accroche.</p>
      <Segmented value={mode} options={[{ value: "dictee", label: "Dictée" }, { value: "tracks", label: "Écoute" }]} onChange={setMode} />
      {mode === "dictee" ? <DictationRunner /> : <ListeningLibrary />}
    </div>
  );
}

function ApprendrePage({ go, routeParams = {} }) {
  const [tab, setTab] = useState(routeParams.tab || "exercices");
  const tabs = [["exercices", "Exercices"], ["vocab", "Vocabulaire fréquent"], ["structures", "Structures"], ["hangeul", "Hangeul"], ["audio", "Audio"]];
  useEffect(() => { if (routeParams.tab) setTab(routeParams.tab); }, [routeParams.tab]);
  return (
    <div style={{ maxWidth: 1000, margin: "0 auto", display: "grid", gap: "var(--gap-lg)" }}>
      <header className="fade-up">
        <Eyebrow>Entraînement</Eyebrow>
        <h1 style={{ fontSize: 44, margin: "6px 0 0" }}>Apprendre</h1>
        <p className="muted" style={{ fontSize: 16, margin: "10px 0 0", maxWidth: 600 }}>Tout l'entraînement libre, rangé par intention plutôt qu'en longue grille. Les vraies révisions SRS vivent dans Cartes.</p>
      </header>
      <div>
        <div className="no-sb" style={{ display: "flex", gap: 4, borderBottom: "1px solid var(--rule)", marginBottom: "var(--gap)", overflowX: "auto" }}>
          {tabs.map(([k, l]) => (
            <button key={k} onClick={() => setTab(k)} style={{ border: "none", background: "transparent", cursor: "pointer", padding: "10px 15px", whiteSpace: "nowrap", fontFamily: "var(--serif)", fontSize: 16.5, color: tab === k ? "var(--accent-deep)" : "var(--ink-faint)", borderBottom: `2px solid ${tab === k ? "var(--accent)" : "transparent"}`, marginBottom: -1, transition: "color .15s" }}>{l}</button>
          ))}
        </div>
        {tab === "exercices" && <ExercicesTab go={go} />}
        {tab === "vocab" && <VocabFreqTab go={go} />}
        {tab === "structures" && <StructuresTab go={go} />}
        {tab === "hangeul" && <HangeulTab go={go} />}
        {tab === "audio" && <AudioTab focus={routeParams.focus} />}
      </div>
    </div>
  );
}

Object.assign(window, { ApprendrePage });
