/* Mots confondables — drill LIBRE de discrimination lexicale.
   Le tag d'erreur dominant est « sens » : on confond des VOISINS DE FORME
   (있다/읽다/입다, 어제/언제), pas des sons. Ici : voir+entendre un mot ->
   choisir son sens parmi ceux de ses sosies, puis étudier le contraste côte à
   côte. Ensembles calculés depuis la banque réelle (engine/confusables.js).
   Aucun effet SRS. */

const CW_RUN_LENGTH = 10;
const CW_RETRY_DELAY = 3;
const CW_STATS_KEY = "confusableWordsStats";

let cwSetsCache = null; // les 365 ensembles ne changent pas pendant la session

function cwLoadSets() {
  if (cwSetsCache) return Promise.resolve(cwSetsCache);
  if (!window.LangData || !window.LangConfusables) return Promise.resolve([]);
  return window.LangData.loadVocabFreq().then(voc => {
    cwSetsCache = window.LangConfusables.buildSets(voc.items || []);
    return cwSetsCache;
  });
}

function cwShuffle(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;
}

/* Priorité aux ensembles dont AU MOINS un mot est déjà en SRS : la confusion
   y est un risque réel, pas théorique. Repli : ordre de fréquence. */
function cwBuildRun(sets, seed) {
  const cards = window.LangStore && window.LangStore.cards ? window.LangStore.cards.all() : {};
  const known = sets.filter(s => s.items.some(i => cards[i.id]));
  const pool = known.length >= CW_RUN_LENGTH ? known : known.concat(sets.filter(s => !known.includes(s)));
  return cwShuffle(pool.slice(0, 80), seed).slice(0, CW_RUN_LENGTH).map((set, index) => {
    const target = cwShuffle(set.items, seed + index * 7 + 1)[0];
    return { set, target, choices: cwShuffle(set.items.map(i => i.fr), seed + index * 13 + 5) };
  });
}

function cwBump(correct) {
  const store = window.LangStore;
  if (!store || !store.get) return;
  const s = store.get(CW_STATS_KEY, { attempts: 0, correct: 0 }) || { attempts: 0, correct: 0 };
  s.attempts += 1;
  if (correct) s.correct += 1;
  store.set(CW_STATS_KEY, s);
}

function ConfusableWordsDrill() {
  const [loading, setLoading] = useState(true);
  const [sets, setSets] = useState([]);
  const [run, setRun] = useState(null); // {queue, idx, correct, total, missQueue}
  const [picked, setPicked] = useState(null);

  useEffect(() => {
    let alive = true;
    cwLoadSets()
      .then(s => { if (alive) setSets(s); })
      .finally(() => { if (alive) setLoading(false); });
    return () => { alive = false; if (window.LangAudio) window.LangAudio.stop(); };
  }, []);

  const start = () => {
    const queue = cwBuildRun(sets, Date.now() & 0x7fffffff);
    if (!queue.length) return;
    setRun({ queue, idx: 0, correct: 0, total: 0, missQueue: [] });
    setPicked(null);
  };

  if (loading) return <p className="faint" style={{ fontSize: 13 }}>Construction des ensembles de sosies…</p>;
  if (!sets.length) return <EmptyState icon="book" title="Pas d'ensembles" body="La banque de vocabulaire n'a pas pu être analysée." />;

  if (!run) {
    return (
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <Eyebrow>Mots proches — discrimination du sens</Eyebrow>
          <span className="chip" style={{ fontSize: 11 }}>libre · sans effet SRS</span>
        </div>
        <p className="faint" style={{ fontSize: 13.5, margin: 0, maxWidth: 580 }}>
          있다, 읽다, 입다, 잃다 : quatre mots, une syllabe d'écart. C'est là que naissent
          les erreurs de sens. Un mot s'affiche, choisis SON sens parmi ceux de ses
          sosies — puis compare-les côte à côte. Les ensembles où tu connais déjà un
          mot passent en premier.
        </p>
        <Btn kind="primary" icon="play" onClick={start} style={{ justifySelf: "start" }}>Commencer · {CW_RUN_LENGTH} items ({sets.length} ensembles)</Btn>
      </div>
    );
  }

  const q = run.queue[run.idx] || null;
  if (!q) {
    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} bien discriminés</span>
        </div>
        <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 answered = picked != null;
  const isCorrect = answered && q.choices[picked] === q.target.fr;

  const pick = (idx) => {
    if (answered) return;
    const correct = q.choices[idx] === q.target.fr;
    setPicked(idx);
    setRun(prev => prev ? { ...prev, correct: prev.correct + (correct ? 1 : 0), total: prev.total + 1 } : prev);
    cwBump(correct);
    if (window.LangSounds) (correct ? window.LangSounds.correct : window.LangSounds.wrong)();
  };

  const advance = () => {
    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 (!isCorrect) missQueue = missQueue.concat([{ q: { ...q, target: cwShuffle(q.set.items, Date.now() & 0x7fffffff)[0] }, wait: CW_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].q]);
        missQueue = missQueue.filter((_, i) => i !== ready);
      }
      return { ...prev, queue, idx, missQueue };
    });
    setPicked(null);
  };

  return (
    <div style={{ display: "grid", gap: 14 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
        <Eyebrow>Mots proches</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: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
          <Speakable text={q.target.kr} as="span" className="kr" style={{ fontSize: 40, fontWeight: 700, color: "var(--accent-deep)" }}>{q.target.kr}</Speakable>
          <span className="faint" style={{ fontSize: 12.5 }}>Quel est SON sens — pas celui d'un sosie ?</span>
        </div>
        <div style={{ display: "grid", gap: 8 }}>
          {q.choices.map((c, idx) => {
            let bg = "var(--card)", col = "var(--ink)";
            if (answered && c === q.target.fr) { bg = "var(--good-tint)"; col = "var(--good)"; }
            else if (answered && picked === idx) { bg = "var(--warn-tint)"; col = "var(--warn)"; }
            return <button key={idx} onClick={() => pick(idx)} disabled={answered} style={{ padding: "12px 15px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: bg, color: col, fontFamily: "var(--serif)", fontSize: 17, textAlign: "left", cursor: answered ? "default" : "pointer" }}>{c}</button>;
          })}
        </div>
        {answered && (
          <div className="fade-up" style={{ display: "grid", gap: 8, padding: "12px 14px", borderRadius: "var(--radius-sm)", background: isCorrect ? "var(--good-tint)" : "var(--warn-tint)" }}>
            <div style={{ fontWeight: 700, color: isCorrect ? "var(--good)" : "var(--warn)" }}>
              {isCorrect ? "Bien discriminé." : "C'est le piège classique de cet ensemble."}
            </div>
            <span className="faint" style={{ fontSize: 12.5 }}>Les sosies côte à côte — écoute-les, la différence est dans une syllabe :</span>
            <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
              {q.set.items.map(item => (
                <span key={item.id} style={{ display: "inline-flex", alignItems: "baseline", gap: 6, padding: "6px 10px", borderRadius: "var(--radius-sm)", background: item.id === q.target.id ? "var(--good-tint)" : "var(--card)", border: "1px solid var(--rule)" }}>
                  <Speakable text={item.kr} as="span" gloss={item.fr} className="kr" style={{ fontSize: 18, fontWeight: item.id === q.target.id ? 700 : 500 }}>{item.kr}</Speakable>
                  <span className="faint" style={{ fontSize: 12 }}>{item.fr}</span>
                </span>
              ))}
            </div>
            {!isCorrect && <span className="faint" style={{ fontSize: 12 }}>Cet ensemble reviendra un peu plus loin.</span>}
          </div>
        )}
      </div>
      <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
        {answered && <Btn kind="primary" icon="chevronR" onClick={advance}>Continuer</Btn>}
        {!answered && <Btn kind="ghost" size="sm" onClick={() => setRun(null)}>Quitter la série</Btn>}
      </div>
    </div>
  );
}

Object.assign(window, { ConfusableWordsDrill });
