/* Hangeul - drill libre et secours de lecture.
   Aucun effet SRS : les ratés restent locaux tant que hangeul.json n'existe pas. */

const HANGEUL_AUDIO_KIND_BY_BLOCK = {
  voy: "voyelle",
  cons: "consonne",
  bat: "consonne",
};

function hangeulAudioLabel(kind) {
  if (kind === "voyelle") return "Écouter voyelles";
  if (kind === "consonne") return "Écouter consonnes";
  return "Écouter alphabet";
}

function isJamoAudioItem(item) {
  return !!(item && item.kr && /^[\u3130-\u318F]+$/.test(item.kr));
}

function uniqueJamoItems(items) {
  const seen = new Set();
  return (items || []).filter(item => {
    if (!isJamoAudioItem(item) || seen.has(item.kr)) return false;
    seen.add(item.kr);
    return true;
  });
}

function hangeulChoiceOptions(items, item, index) {
  if (!item) return [];
  const pool = uniqueJamoItems(items).filter(candidate => candidate.kr !== item.kr);
  const choices = [item];
  if (pool.length) {
    const start = (index * 7 + 3) % pool.length;
    for (let offset = 0; offset < pool.length && choices.length < 4; offset++) {
      choices.push(pool[(start + offset) % pool.length]);
    }
  }
  const shift = choices.length ? ((index * 3 + 1) % choices.length) : 0;
  return choices.slice(shift).concat(choices.slice(0, shift));
}

const HANGEUL_RETRY_DELAY = 3;

function ageHangeulMissQueue(queue) {
  return (queue || []).map(entry => ({ ...entry, wait: Math.max(0, (entry.wait || 0) - 1) }));
}

function normalizeJamoAnswer(value) {
  return String(value || "").normalize("NFC").replace(/\s+/g, "").trim();
}

function HangeulAudioChoiceDrill({ itemKindFilter, drillMode = "choice" }) {
  const [state, setState] = useState(() => ({ status: window.LangData && window.LangData.loadListening ? "loading" : "error", collections: [] }));
  const [reloadKey, setReloadKey] = useState(0);
  const [ci, setCi] = useState(0);
  const [i, setI] = useState(0);
  const [selected, setSelected] = useState("");
  const [answer, setAnswer] = useState("");
  const [revealed, setRevealed] = useState(false);
  const [playError, setPlayError] = useState(false);
  const [stats, setStats] = useState({ correct: 0, total: 0 });
  const [missQueue, setMissQueue] = useState([]);

  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) return;
        setState({ status: "ready", collections: (payload.collections || []).filter(c => c.kind === "alphabet") });
      })
      .catch(() => { if (alive) setState({ status: "error", collections: [] }); });
    return () => { alive = false; if (window.LangAudio) window.LangAudio.stop(); };
  }, [reloadKey]);

  const collections = state.collections;
  const collection = collections[ci] || null;
  const rawItems = (collection && collection.items) || [];
  const items = uniqueJamoItems(rawItems).filter(item => !itemKindFilter || item.kind === itemKindFilter);
  const baseItem = items.length ? items[i % items.length] : null;
  const dueMiss = missQueue.find(entry => entry.wait <= 0 && items.some(candidate => candidate.id === entry.id));
  const retryItem = dueMiss ? items.find(candidate => candidate.id === dueMiss.id) : null;
  const item = retryItem || baseItem;
  const retrying = !!retryItem;
  const choices = hangeulChoiceOptions(items, item, i);
  const writing = drillMode === "write";
  const reading = drillMode === "read";
  const answered = !!selected || (revealed && !reading);
  const exact = answered && item && (selected === item.kr || selected === "__read_ok");
  const progress = stats.total ? stats.correct / stats.total : 0;

  useEffect(() => {
    setI(0);
    setSelected("");
    setAnswer("");
    setRevealed(false);
    setPlayError(false);
    setMissQueue([]);
    if (window.LangAudio) window.LangAudio.stop();
  }, [ci, itemKindFilter, drillMode]);

  const scheduleRetry = (target) => {
    if (!target || !target.id) return;
    setMissQueue(prev => prev.filter(entry => entry.id !== target.id).concat({ id: target.id, wait: HANGEUL_RETRY_DELAY }));
  };

  const clearRetry = (target) => {
    if (!target || !target.id) return;
    setMissQueue(prev => prev.filter(entry => entry.id !== target.id));
  };

  const playItem = (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 choose = (choice) => {
    if (!item || answered) return;
    const ok = choice.kr === item.kr;
    setSelected(choice.kr);
    setRevealed(false);
    if (ok) clearRetry(item);
    else scheduleRetry(item);
    setStats(prev => ({ correct: prev.correct + (ok ? 1 : 0), total: prev.total + 1 }));
  };

  const checkWritten = () => {
    if (!item || answered) return;
    const next = normalizeJamoAnswer(answer);
    if (!next) return;
    const ok = next === item.kr;
    setSelected(next);
    setRevealed(false);
    if (ok) clearRetry(item);
    else scheduleRetry(item);
    setStats(prev => ({ correct: prev.correct + (ok ? 1 : 0), total: prev.total + 1 }));
  };

  const revealWritten = () => {
    if (!item || answered) return;
    setSelected("");
    setRevealed(true);
    scheduleRetry(item);
  };

  const gradeReading = (ok) => {
    if (!item || answered) return;
    setSelected(ok ? "__read_ok" : "__read_miss");
    setRevealed(true);
    if (ok) clearRetry(item);
    else scheduleRetry(item);
    setStats(prev => ({ correct: prev.correct + (ok ? 1 : 0), total: prev.total + 1 }));
  };

  const next = () => {
    if (!items.length) return;
    if (!answered && item) scheduleRetry(item);
    setMissQueue(prev => ageHangeulMissQueue(prev));
    const advanceBase = !retrying || (baseItem && item && baseItem.id === item.id);
    if (advanceBase) setI(value => (value + 1) % items.length);
    setSelected("");
    setAnswer("");
    setRevealed(false);
    setPlayError(false);
    if (window.LangAudio) window.LangAudio.stop();
  };

  if (state.status === "loading") return <LoadingCard lines={4} />;
  if (state.status === "error") return <ErrorState title="Drill Hangeul indisponible" body="Les collections alphabet ne peuvent pas être chargées." onRetry={() => setReloadKey(x => x + 1)} />;
  if (!collection) return <EmptyState icon="speaker" title="Aucun alphabet audio" body="Les collections alphabet ne sont pas disponibles dans listening.json." />;
  if (!item) return <EmptyState icon="list" title="Aucun jamo disponible" body="Aucun item ne correspond au filtre choisi." />;

  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: "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>Drill libre</Eyebrow>
            <div style={{ fontFamily: "var(--serif)", fontSize: 22, color: "var(--accent-deep)", marginTop: 2 }}>{reading ? "Lire → audio" : writing ? "Audio → écrire" : "Audio → jamo"}</div>
          </div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "flex-end" }}>
            <span className="chip">{(i % items.length) + 1} / {items.length}</span>
            <span className="chip">sans SRS</span>
            {retrying && <span className="chip" style={{ background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>reprise</span>}
            {missQueue.length > 0 && <span className="chip">{missQueue.length} raté{missQueue.length > 1 ? "s" : ""}</span>}
            <span className="chip">{stats.correct} / {stats.total}</span>
          </div>
        </div>

        <Progress value={progress} height={5} color={stats.total ? "var(--good)" : "var(--accent)"} />

        <div style={{ minHeight: reading ? 154 : 112, display: "grid", placeItems: "center", gap: 10, textAlign: "center" }}>
          {reading && (
            <Speakable text={item.kr} audioRef={item.audioRef || item.kr} audioUrl={item.audioUrl} className="kr" style={{ fontSize: 56, color: "var(--accent-deep)", padding: "5px 18px" }}>{item.kr}</Speakable>
          )}
          <button onClick={() => { playItem(1); if (reading) setRevealed(true); }} aria-label={reading ? `Écouter ${item.kr}` : "Écouter le jamo"} title="Écouter" 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>
          {reading && revealed && item.reading && <div className="faint fade-up" 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</span>}
        </div>

        {reading ? (
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "center" }}>
            <Btn kind="ghost" size="sm" icon="refresh" disabled={answered} onClick={() => gradeReading(false)}>À reprendre</Btn>
            <Btn kind="primary" size="sm" icon="check" disabled={answered} onClick={() => gradeReading(true)}>Lu juste</Btn>
          </div>
        ) : writing ? (
          <div style={{ display: "grid", gap: 10, justifyItems: "center" }}>
            <input value={answer} onChange={e => { setAnswer(e.target.value); setSelected(""); setRevealed(false); }} className="kr" lang="ko" placeholder="ㄱ" autoCapitalize="off" autoCorrect="off" spellCheck={false} style={{ width: "min(100%, 280px)", textAlign: "center", padding: "12px 14px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card)", color: "var(--ink)", fontSize: 34, lineHeight: 1.2 }} />
            <div style={{ width: "min(100%, 420px)" }}><KoreanKeyboard value={answer} onChange={(next) => { setAnswer(next); setSelected(""); setRevealed(false); }} compact /></div>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "center" }}>
              <Btn kind="primary" size="sm" icon="check" disabled={!answer.trim() || answered} onClick={checkWritten}>Vérifier</Btn>
              <Btn kind="ghost" size="sm" icon="eye" disabled={answered} onClick={revealWritten}>Révéler</Btn>
            </div>
          </div>
        ) : (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(78px,1fr))", gap: 10 }}>
            {choices.map(choice => {
              const picked = selected === choice.kr;
              const isAnswer = answered && choice.kr === item.kr;
              const bg = isAnswer ? "var(--good-tint)" : picked ? "var(--warn-tint)" : "var(--card)";
              const bd = isAnswer ? "var(--good)" : picked ? "var(--warn)" : "var(--rule)";
              const col = isAnswer ? "var(--good)" : picked ? "var(--warn)" : "var(--accent-deep)";
              return (
                <button key={choice.id || choice.kr} onClick={() => choose(choice)} style={{ minHeight: 78, borderRadius: "var(--radius-sm)", border: `1px solid ${bd}`, background: bg, color: col, cursor: answered ? "default" : "pointer", display: "grid", placeItems: "center", gap: 3, padding: "9px 8px", transition: "all .15s" }}>
                  <span className="kr" style={{ fontSize: 32, lineHeight: 1 }}>{choice.kr}</span>
                  {choice.reading && <span className="faint" style={{ fontSize: 11.5, fontFamily: "var(--mono)", color: picked || isAnswer ? col : undefined }}>{choice.reading}</span>}
                </button>
              );
            })}
          </div>
        )}

        {answered && (
          <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: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
            <span className="chip" style={{ background: exact ? "var(--good-tint)" : "var(--warn-tint)", color: exact ? "var(--good)" : "var(--warn)", borderColor: "transparent" }}>{revealed && !selected ? "Modèle" : exact ? "Correct" : "À reprendre"}</span>
            <Speakable text={item.kr} audioRef={item.audioRef || item.kr} audioUrl={item.audioUrl} className="kr" style={{ fontSize: 28, color: "var(--accent-deep)" }}>{item.kr}</Speakable>
            {item.reading && <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 13 }}>{item.reading}</span>}
            {writing && selected && !exact && <span className="faint" style={{ fontSize: 13 }}>Réponse : <span className="kr">{selected}</span></span>}
            {!exact && selected !== "__read_ok" && <span className="faint" style={{ fontSize: 13 }}>Revu bientôt.</span>}
          </div>
        )}

        <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
          <Btn kind="secondary" size="sm" icon="repeat" onClick={() => playItem(.75)}>Lent</Btn>
          {answered && <Btn kind="ghost" size="sm" icon="speaker" onClick={() => playItem(1)}>Réécouter</Btn>}
          <span style={{ flex: 1 }} />
          <Btn kind={answered ? "primary" : "secondary"} size="sm" iconR="next" onClick={next}>{answered ? "Suivant" : "Passer"}</Btn>
        </div>
      </div>
    </div>
  );
}

function HangeulTab({ go }) {
  const blocs = window.LANGUES_DATA.hangeulBlocs;
  const [duesOnly, setDuesOnly] = useState(false);
  const [audioKind, setAudioKind] = useState("");
  const [audioMode, setAudioMode] = useState("choice");
  const shown = duesOnly ? blocs.filter(b => b.due > 0) : blocs;
  const audioOptions = [
    { value: "all", label: "Tout" },
    { value: "consonne", label: "Consonnes" },
    { value: "voyelle", label: "Voyelles" },
  ];
  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 className="kr" style={{ fontSize: 38, color: "var(--accent-deep)" }}>한글</div>
        <p className="muted" style={{ fontSize: 14.5, margin: 0, flex: "1 1 280px" }}>Un espace de <strong style={{ color: "var(--ink)" }}>secours et de consolidation</strong> — pas le passage obligé. Revenez-y pour réparer une base de lecture qui accroche.</p>
        <button onClick={() => setDuesOnly(d => !d)} className="chip" style={{ cursor: "pointer", padding: "8px 14px", background: duesOnly ? "var(--accent-tint)" : "var(--card)", color: duesOnly ? "var(--accent-deep)" : "var(--ink-soft)" }}>Dus seulement</button>
      </div>
      {shown.length === 0 ? <EmptyState icon="check" title="Rien à réviser" body="Aucun bloc hangeul dû pour l'instant — vos bases tiennent." /> : (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(240px,1fr))", gap: "var(--gap)" }}>
          {shown.map(b => (
            <div key={b.id} className="card" style={{ padding: 18, display: "grid", gap: 12 }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
                <div><div style={{ fontFamily: "var(--serif)", fontSize: 18, color: "var(--accent-deep)" }}>{b.title}</div><div className="faint" style={{ fontSize: 12, marginTop: 2 }}>{b.count} blocs</div></div>
                {b.due > 0 && <span className="chip" style={{ background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>{b.due} dus</span>}
              </div>
              <div className="kr" style={{ fontSize: 24, color: "var(--ink)", letterSpacing: ".06em" }}>{b.kr}</div>
              <div><div className="faint" style={{ fontSize: 11, marginBottom: 5 }}>Maîtrise {Math.round(b.mastery * 100)}%</div><Progress value={b.mastery} height={5} color={b.mastery < .5 ? "var(--warn)" : "var(--good)"} /></div>
              <Btn kind={b.due > 0 ? "primary" : "secondary"} size="sm" icon="speaker" onClick={() => { setAudioKind(HANGEUL_AUDIO_KIND_BY_BLOCK[b.id] || ""); setAudioMode("choice"); }}>{hangeulAudioLabel(HANGEUL_AUDIO_KIND_BY_BLOCK[b.id])}</Btn>
            </div>
          ))}
        </div>
      )}
      <section className="card" style={{ padding: "var(--pad-card)", display: "grid", gap: 14 }}>
        <div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "flex-start", flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 260px" }}>
            <Eyebrow>Audio alphabet</Eyebrow>
            <div style={{ fontFamily: "var(--serif)", fontSize: 21, color: "var(--accent-deep)", marginTop: 2 }}>Collections jamo déjà migrées</div>
            <p className="muted" style={{ fontSize: 13.5, margin: "4px 0 0" }}>Korean Jun et Hangul.fun alimentent ce bloc : clic sur une lettre, écoute réelle, répétition libre.</p>
          </div>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }}>
            <Segmented size="sm" value={audioMode} options={[{ value: "choice", label: "Choix" }, { value: "write", label: "Écrire" }, { value: "read", label: "Lire" }, { value: "library", label: "Écoute" }]} onChange={setAudioMode} />
            <Segmented size="sm" value={audioKind || "all"} options={audioOptions} onChange={(value) => setAudioKind(value === "all" ? "" : value)} />
          </div>
        </div>
      </section>
      {audioMode === "library" ? (
        <ListeningLibrary
          kindFilter="alphabet"
          itemKindFilter={audioKind}
          emptyTitle="Aucun alphabet audio"
          emptyBody="Les collections alphabet ne sont pas disponibles dans listening.json."
        />
      ) : (
        <HangeulAudioChoiceDrill itemKindFilter={audioKind} drillMode={audioMode} />
      )}
    </div>
  );
}

Object.assign(window, { HangeulTab, HangeulAudioChoiceDrill });
