/* Entraînement syllabique progressif — drill LIBRE (ROADMAP dictée, PEDAGOGIE §3).
   Le piège central du coréen oral : 사람은 s'entend [사라믄] (liaison du batchim).
   Flux par item : écouter -> compter les syllabes -> reconstruire ce qui est
   ÉCRIT parmi des tuiles qui contiennent aussi la forme ENTENDUE. Se tromper en
   écrivant ce qu'on entend N'EST PAS une faute aléatoire : c'est LA confusion,
   et le feedback la nomme. Aucun effet SRS. */

const ST_STATS_KEY = "syllableTrainerStats";
const ST_RUN_LENGTH = 10;
const ST_RETRY_DELAY = 3;
const ST_LIAISON_PARTICLES = ["은", "이", "을"];

function stShuffle(list, seed) {
  const out = list.slice();
  let s = seed >>> 0;
  for (let i = out.length - 1; i > 0; i--) {
    s = (s * 1103515245 + 12345) & 0x7fffffff;
    const j = s % (i + 1);
    const tmp = out[i]; out[i] = out[j]; out[j] = tmp;
  }
  return out;
}

/* Construit le vivier d'items depuis vocab_freq : mots réels 2-3 syllabes.
   - liaison : mot à batchim + particule vocalique -> la forme entendue change ;
   - plain   : mot où rien ne bouge (contrôle, évite le réflexe « ça change toujours »). */
function stBuildPool(vocabItems) {
  const S = window.LangSyllables;
  const liaison = [];
  const plain = [];
  (vocabItems || []).forEach((item, index) => {
    const kr = String(item.kr || "").trim();
    if (!/^[가-힣]{2,3}$/.test(kr)) return;
    const particle = ST_LIAISON_PARTICLES[index % ST_LIAISON_PARTICLES.length];
    const eojeol = kr + particle;
    const sp = S.spoken(eojeol);
    if (sp.supported && sp.changed) {
      liaison.push({ text: eojeol, base: kr, fr: item.fr || "", spoken: sp, kind: "liaison" });
      return;
    }
    const alone = S.spoken(kr);
    if (alone.supported && !alone.changed) {
      plain.push({ text: kr, base: kr, fr: item.fr || "", spoken: alone, kind: "plain" });
    }
  });
  return { liaison, plain };
}

function stBuildRun(pool, seed) {
  const nLiaison = Math.min(6, pool.liaison.length);
  const nPlain = Math.min(ST_RUN_LENGTH - nLiaison, pool.plain.length);
  const picks = stShuffle(pool.liaison, seed).slice(0, nLiaison)
    .concat(stShuffle(pool.plain, seed + 7).slice(0, nPlain));
  return stShuffle(picks, seed + 13).map((item, index) => {
    const written = item.spoken.written;
    const distractors = item.spoken.heard.filter((syl, k) => syl !== written[k]);
    const tiles = stShuffle(
      written.concat(distractors).map((syl, k) => ({ id: `t${index}_${k}`, syl })),
      seed + index * 31 + 3
    );
    return Object.assign({}, item, { tiles });
  });
}

function stSpeak(text, rate) {
  if (window.LangAudio) window.LangAudio.speak(text, { rate: rate || 0.9 });
}

function stBumpStats(kind, correct) {
  const store = window.LangStore;
  if (!store || !store.get) return;
  const stats = store.get(ST_STATS_KEY, {}) || {};
  const entry = stats[kind] || { attempts: 0, correct: 0 };
  entry.attempts += 1;
  if (correct) entry.correct += 1;
  stats[kind] = entry;
  store.set(ST_STATS_KEY, stats);
}

function SyllableTrainer() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [pool, setPool] = useState(null);
  const [run, setRun] = useState(null); // {queue, idx, correct, total, missQueue}
  const [step, setStep] = useState("listen"); // listen | count | build
  const [countPick, setCountPick] = useState(null);
  const [assembled, setAssembled] = useState([]); // tile ids
  const [checked, setChecked] = useState(null);   // {correct, heardTrap}

  useEffect(() => {
    let alive = true;
    if (!window.LangData || !window.LangData.loadVocabFreq || !window.LangSyllables) {
      setLoading(false);
      setError("Données ou moteur syllabique indisponibles.");
      return () => { alive = false; };
    }
    window.LangData.loadVocabFreq()
      .then(payload => {
        if (!alive) return;
        const items = Array.isArray(payload.items) ? payload.items : [];
        setPool(stBuildPool(items.slice(0, 600))); // noyau le plus fréquent
      })
      .catch(() => { if (alive) setError("Impossible de charger le vocabulaire fréquent."); })
      .finally(() => { if (alive) setLoading(false); });
    return () => { alive = false; if (window.LangAudio) window.LangAudio.stop(); };
  }, []);

  const resetItem = () => { setStep("listen"); setCountPick(null); setAssembled([]); setChecked(null); };
  const start = () => {
    if (!pool) return;
    setRun({ queue: stBuildRun(pool, Date.now() & 0x7fffffff), idx: 0, correct: 0, total: 0, missQueue: [] });
    resetItem();
  };

  if (loading) return <p className="faint" style={{ fontSize: 13 }}>Chargement du vivier syllabique…</p>;
  if (error) return <EmptyState icon="speaker" title="Syllabes indisponibles" body={error} />;

  if (!run) {
    const nL = pool ? pool.liaison.length : 0;
    return (
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <Eyebrow>Oreille syllabique — entendu vs écrit</Eyebrow>
          <span className="chip" style={{ fontSize: 11 }}>libre · sans effet SRS</span>
        </div>
        <p className="faint" style={{ fontSize: 13.5, margin: 0, maxWidth: 580 }}>
          À l'oral, la consonne finale se lie à la voyelle suivante : 사람은 s'entend
          « 사라믄 ». Ici on écoute, on découpe, puis on reconstruit ce qui est
          <strong> écrit</strong> — les tuiles pièges viennent de ce qu'on <strong>entend</strong>.
          C'est l'étape entre les micro-contrastes et la dictée.
        </p>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <Btn kind="primary" icon="play" onClick={start} disabled={!nL}>Commencer · {ST_RUN_LENGTH} items</Btn>
          <span className="faint" style={{ fontSize: 12.5, alignSelf: "center" }}>{nL} mots à liaison dans le noyau fréquent</span>
        </div>
      </div>
    );
  }

  const item = run.queue[run.idx] || null;
  if (!item) {
    const pct = run.total ? Math.round(100 * run.correct / run.total) : 0;
    return (
      <div className="card" style={{ padding: 24, display: "grid", gap: 12 }}>
        <Eyebrow>Série terminée</Eyebrow>
        <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
          <span style={{ fontFamily: "var(--serif)", fontSize: 38, color: pct >= 80 ? "var(--good)" : "var(--ochre)" }}>{pct}%</span>
          <span className="faint" style={{ fontSize: 13 }}>{run.correct}/{run.total} reconstructions justes</span>
        </div>
        <p className="faint" style={{ fontSize: 13, margin: 0 }}>
          {pct >= 80 ? "L'oreille suit les syllabes — la dictée par blocs est la suite logique." : "Écouter en lent avant de découper aide beaucoup au début."}
        </p>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <Btn kind="primary" icon="refresh" onClick={start}>Rejouer</Btn>
          <Btn kind="secondary" icon="grid" onClick={() => setRun(null)}>Retour</Btn>
        </div>
      </div>
    );
  }

  const written = item.spoken.written;
  const heardText = item.spoken.heardText;
  const writtenText = written.join("");
  const assembledText = assembled.map(id => item.tiles.find(t => t.id === id).syl).join("");

  const advance = (wasCorrect) => {
    setRun(prev => {
      if (!prev) return prev;
      let queue = prev.queue;
      let missQueue = prev.missQueue.map(m => ({ ...m, wait: Math.max(0, m.wait - 1) }));
      if (!wasCorrect) missQueue = missQueue.concat([{ item, wait: ST_RETRY_DELAY }]);
      const ready = missQueue.findIndex(m => m.wait === 0);
      const idx = prev.idx + 1;
      if (ready >= 0 && idx >= queue.length) {
        queue = queue.concat([missQueue[ready].item]);
        missQueue = missQueue.filter((_, i) => i !== ready);
      }
      return { ...prev, queue, idx, missQueue };
    });
    resetItem();
  };

  const pickCount = (n) => {
    setCountPick(n);
    if (n === written.length) setTimeout(() => setStep("build"), 350);
  };

  const toggleTile = (tile) => {
    if (checked) return;
    setAssembled(prev => prev.includes(tile.id) ? prev.filter(id => id !== tile.id) : (prev.length < written.length ? prev.concat(tile.id) : prev));
  };

  const checkBuild = () => {
    const correct = assembledText === writtenText;
    const heardTrap = !correct && assembledText === heardText;
    setChecked({ correct, heardTrap });
    setRun(prev => prev ? { ...prev, correct: prev.correct + (correct ? 1 : 0), total: prev.total + 1 } : prev);
    stBumpStats(item.kind, correct);
    if (window.LangSounds) (correct ? window.LangSounds.correct : window.LangSounds.wrong)();
  };

  return (
    <div style={{ display: "grid", gap: 14 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
        <Eyebrow>Oreille syllabique</Eyebrow>
        <span className="chip" style={{ fontSize: 11 }}>libre · sans effet SRS</span>
        <span style={{ flex: 1 }} />
        <span className="faint" style={{ fontSize: 12.5, fontFamily: "var(--mono)" }}>{run.total} fait{run.total > 1 ? "s" : ""} · {run.correct} juste{run.correct > 1 ? "s" : ""}</span>
      </div>
      <div className="card" style={{ padding: 24, display: "grid", gap: 16 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
          <span className="chip">1 · écouter</span>
          <Btn kind={step === "listen" ? "primary" : "secondary"} size="sm" icon="speaker" onClick={() => { stSpeak(item.text); if (step === "listen") setStep("count"); }}>Écouter</Btn>
          <Btn kind="ghost" size="sm" icon="repeat" onClick={() => stSpeak(item.text, 0.7)}>Lent</Btn>
          {item.fr && <span className="faint" style={{ fontSize: 12.5 }}>({item.fr.split(/[;,]/)[0].trim()}{item.kind === "liaison" ? " + particule" : ""})</span>}
        </div>

        {step !== "listen" && (
          <div className="fade-up" style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
            <span className="chip">2 · découper</span>
            <span className="faint" style={{ fontSize: 13 }}>Combien de syllabes ?</span>
            {[2, 3, 4].map(n => (
              <button key={n} onClick={() => pickCount(n)} className="chip" style={{
                cursor: "pointer", fontSize: 15, fontWeight: 700, minWidth: 40,
                background: countPick === n ? (n === written.length ? "var(--good-tint)" : "var(--warn-tint)") : "var(--card)",
                color: countPick === n ? (n === written.length ? "var(--good)" : "var(--warn)") : "var(--ink-soft)",
                borderColor: countPick === n ? "transparent" : "var(--rule)",
              }}>{n}</button>
            ))}
            {countPick != null && countPick !== written.length && <span className="faint fade-up" style={{ fontSize: 12.5 }}>Réécoute en lent : chaque temps est une syllabe.</span>}
          </div>
        )}

        {step === "build" && (
          <div className="fade-up" style={{ display: "grid", gap: 10 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
              <span className="chip">3 · reconstruire</span>
              <span className="faint" style={{ fontSize: 13 }}>Assemble ce qui est <strong>écrit</strong> — pas forcément ce que tu entends.</span>
            </div>
            <div style={{ minHeight: 54, display: "flex", gap: 8, alignItems: "center", padding: "8px 12px", borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px dashed var(--rule)" }}>
              {assembled.length === 0 && <span className="faint" style={{ fontSize: 12.5 }}>Touche les tuiles dans l'ordre…</span>}
              {assembled.map(id => {
                const t = item.tiles.find(x => x.id === id);
                return <button key={id} onClick={() => toggleTile(t)} className="kr" style={{ cursor: "pointer", fontSize: 24, fontWeight: 700, padding: "8px 12px", borderRadius: "var(--radius-sm)", border: "none", background: "var(--accent-tint)", color: "var(--accent-deep)" }}>{t.syl}</button>;
              })}
            </div>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              {item.tiles.filter(t => !assembled.includes(t.id)).map(t => (
                <button key={t.id} onClick={() => toggleTile(t)} className="kr" style={{ cursor: "pointer", fontSize: 24, fontWeight: 600, padding: "10px 14px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card)", color: "var(--ink)" }}>{t.syl}</button>
              ))}
            </div>
            {checked && (
              <div className="fade-up" style={{ display: "grid", gap: 8, padding: "12px 14px", borderRadius: "var(--radius-sm)", background: checked.correct ? "var(--good-tint)" : "var(--warn-tint)" }}>
                <div style={{ fontWeight: 700, color: checked.correct ? "var(--good)" : "var(--warn)" }}>
                  {checked.correct ? "Exact." : checked.heardTrap ? "Tu as écrit ce que tu ENTENDS — c'est le piège visé." : "Pas tout à fait."}
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: "3px 12px", alignItems: "baseline" }}>
                  <span className="faint" style={{ fontSize: 12 }}>On entend</span>
                  <span className="kr" style={{ fontSize: 18 }}>[{heardText}]</span>
                  <span className="faint" style={{ fontSize: 12 }}>On écrit</span>
                  <span className="kr" style={{ fontSize: 18, fontWeight: 700, color: "var(--good)" }}>{writtenText}</span>
                </div>
                {item.spoken.links.length > 0 && (
                  <span className="faint" style={{ fontSize: 12.5 }}>
                    {item.spoken.links.map(l => l.batchim === "ㅎ"
                      ? `Le ㅎ de « ${written[l.from]} » ne s'entend pas devant une voyelle.`
                      : `Le ${l.batchim} final de « ${written[l.from]} » se lie à la syllabe suivante.`).join(" ")}
                  </span>
                )}
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                  <button onClick={() => stSpeak(item.text)} className="chip kr" style={{ cursor: "pointer", fontSize: 13 }}>▶ réécouter</button>
                  {!checked.correct && <span className="chip" style={{ fontSize: 11, background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>son · batchim{item.kind === "liaison" ? " · particule" : ""}</span>}
                </div>
              </div>
            )}
          </div>
        )}
      </div>
      <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
        {step === "build" && !checked && <Btn kind="primary" icon="check" disabled={assembled.length !== written.length} onClick={checkBuild}>Vérifier</Btn>}
        {checked && <Btn kind="primary" icon="chevronR" onClick={() => advance(checked.correct)}>Continuer</Btn>}
        {!checked && <Btn kind="ghost" size="sm" onClick={() => setRun(null)}>Quitter</Btn>}
      </div>
    </div>
  );
}

Object.assign(window, { SyllableTrainer });
