/* Dictée libre — entraînement audio -> hangeul sans effet durable ni SRS. */

function lineAudioOptions(line, rate) {
  const audio = (line && line.audio) || {};
  return { rate: rate || 1, ref: audio.ref || audio.id, url: audio.url };
}

function DictationRunner() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [scenes, setScenes] = useState([]);
  const [sceneIndex, setSceneIndex] = useState(0);
  const [lineIndex, setLineIndex] = useState(0);
  const [answer, setAnswer] = useState("");
  const [checked, setChecked] = useState(null);
  const [playing, setPlaying] = useState(false);
  const [levelId, setLevelId] = useState("blocks");
  const [unitAnswersByLevel, setUnitAnswersByLevel] = useState({});
  const [unitChecked, setUnitChecked] = useState(null);

  const load = useCallback(() => {
    if (!window.LangData || !window.LangData.loadExercises) {
      setLoading(false);
      setError("Données de dictée indisponibles.");
      return undefined;
    }
    let alive = true;
    setLoading(true);
    setError("");
    window.LangData.loadExercises()
      .then(payload => {
        if (!alive) return;
        const nextScenes = (payload.dictation_scenes || [])
          .map(scene => ({
            ...scene,
            lines: (scene.lines || []).filter(line => line.kr && line.audio && line.audio.url),
          }))
          .filter(scene => scene.lines.length);
        setScenes(nextScenes);
        setSceneIndex(0);
        setLineIndex(0);
        setAnswer("");
        setChecked(null);
        setLevelId("blocks");
        setUnitAnswersByLevel({});
        setUnitChecked(null);
      })
      .catch(() => {
        if (!alive) return;
        setError("Impossible de charger les dictées canoniques.");
      })
      .finally(() => { if (alive) setLoading(false); });
    return () => { alive = false; };
  }, []);

  useEffect(load, [load]);

  const scene = scenes[sceneIndex] || null;
  const lines = scene ? scene.lines : [];
  const line = lines[lineIndex] || null;
  const totalLines = scenes.reduce((n, item) => n + item.lines.length, 0);
  const doneBefore = scenes.slice(0, sceneIndex).reduce((n, item) => n + item.lines.length, 0) + lineIndex;
  const progress = totalLines ? (doneBefore + 1) / totalLines : 0;
  const exact = !!checked && !!checked.correct;
  const feedbackTags = (checked && checked.tags) || [];
  const levels = line ? dictationLevelsForLine(line) : [];
  const activeLevel = levels.find(level => level.id === levelId) || levels.find(level => level.id === "blocks") || levels[0] || null;
  const activeUnits = activeLevel ? activeLevel.units : [];
  const activeUnitAnswers = unitAnswersByLevel[activeLevel && activeLevel.id] || [];

  const resetLine = () => {
    setAnswer("");
    setChecked(null);
    setLevelId("blocks");
    setUnitAnswersByLevel({});
    setUnitChecked(null);
    setPlaying(false);
    if (window.LangAudio) window.LangAudio.stop();
  };

  const playLine = (rate) => {
    if (!line || !window.LangAudio) return;
    setPlaying(true);
    Promise.resolve(window.LangAudio.speak(line.kr, lineAudioOptions(line, rate))).finally(() => setPlaying(false));
  };

  const goLine = (delta) => {
    if (!scenes.length) return;
    let nextScene = sceneIndex;
    let nextLine = lineIndex + delta;
    if (nextLine < 0 && nextScene > 0) {
      nextScene -= 1;
      nextLine = scenes[nextScene].lines.length - 1;
    }
    if (nextLine >= scenes[nextScene].lines.length && nextScene + 1 < scenes.length) {
      nextScene += 1;
      nextLine = 0;
    }
    nextLine = Math.max(0, Math.min((scenes[nextScene].lines.length || 1) - 1, nextLine));
    setSceneIndex(nextScene);
    setLineIndex(nextLine);
    resetLine();
  };

  const chooseScene = (id) => {
    const index = scenes.findIndex(item => item.id === id);
    if (index < 0) return;
    setSceneIndex(index);
    setLineIndex(0);
    resetLine();
  };

  const setUnitAnswer = (index, value) => {
    const id = (activeLevel && activeLevel.id) || "blocks";
    setUnitAnswersByLevel(prev => {
      const next = (prev[id] || []).slice();
      next[index] = value;
      return Object.assign({}, prev, { [id]: next });
    });
    setUnitChecked(null);
  };

  const checkUnits = () => {
    setUnitChecked(compareDictationUnits(activeUnits, activeUnitAnswers));
  };

  if (loading) return <LoadingCard lines={4} />;
  if (error) return <ErrorState body={error} onRetry={load} />;
  if (!line) return <EmptyState icon="speaker" title="Aucune dictée disponible" body="Les scènes existent dans la banque, mais aucune ligne audio n'a été trouvée." />;

  return (
    <section style={{ display: "grid", gap: "var(--gap)" }}>
      <div className="card" style={{ padding: "var(--pad-card)", display: "grid", gap: 14 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 14, flexWrap: "wrap" }}>
          <div>
            <Eyebrow>Dictée canonique</Eyebrow>
            <div style={{ fontFamily: "var(--serif)", fontSize: 23, color: "var(--accent-deep)", marginTop: 3 }}>Blocs TTS legacy déployés</div>
          </div>
          <div style={{ display: "flex", gap: 7, flexWrap: "wrap", justifyContent: "flex-end" }}>
            <span className="chip">{scenes.length} scènes</span>
            <span className="chip">{totalLines} lignes</span>
            <span className="chip">tier {line.tier || 1}</span>
          </div>
        </div>

        <Progress value={progress} height={5} />

        <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          <select value={scene.id} onChange={e => chooseScene(e.target.value)} style={{ flex: "1 1 240px", minWidth: 0, padding: "9px 12px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card)", color: "var(--ink)", fontFamily: "var(--sans)", fontWeight: 600 }}>
            {scenes.map((item, i) => <option key={item.id} value={item.id}>{i + 1}. {item.legacy_id || item.id}</option>)}
          </select>
          <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 12 }}>{doneBefore + 1} / {totalLines}</span>
        </div>
      </div>

      <div className="card" style={{ padding: "calc(var(--pad-card) + 2px)", display: "grid", gap: 16 }}>
        <div style={{ display: "flex", justifyContent: "space-between", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
          <div>
            <div className="faint" style={{ fontSize: 12, fontWeight: 700 }}>Ligne {lineIndex + 1} · locuteur {line.speaker || "A"}</div>
            <div style={{ fontFamily: "var(--serif)", fontSize: 20, color: "var(--accent-deep)", marginTop: 3 }}>{scene.legacy_id || scene.id}</div>
          </div>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <Btn kind="primary" size="sm" icon={playing ? "pause" : "play"} onClick={() => playLine(1)}>Écouter</Btn>
            <Btn kind="secondary" size="sm" icon="repeat" onClick={() => playLine(.78)}>Lent</Btn>
          </div>
        </div>

        {levels.length > 0 && (
          <div style={{ display: "grid", gap: 10, padding: "12px 14px", borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px solid var(--rule)" }}>
            <div style={{ display: "flex", gap: 7, flexWrap: "wrap", alignItems: "center" }}>
              {levels.map(level => (
                <button key={level.id} onClick={() => { setLevelId(level.id); setUnitChecked(null); }} className="chip" style={{ cursor: "pointer", background: activeLevel && activeLevel.id === level.id ? "var(--accent-tint)" : "var(--card)", color: activeLevel && activeLevel.id === level.id ? "var(--accent-deep)" : "var(--ink-soft)", borderColor: activeLevel && activeLevel.id === level.id ? "transparent" : "var(--rule)" }}>
                  {level.order} · {level.label} <span className="faint" style={{ fontSize: 11 }}>{level.units.length}</span>
                </button>
              ))}
              {activeLevel && activeLevel.audioTags && activeLevel.audioTags.length > 0 && <span className="faint" style={{ fontSize: 12 }}>À écouter : {activeLevel.audioTags.join(" · ")}</span>}
            </div>
            {activeLevel && activeLevel.kind !== "line" ? (
              <>
                <div style={{ display: "grid", gap: 8 }}>
                  {activeUnits.map((unit, index) => {
                    const result = unitChecked && unitChecked.results && unitChecked.results[index];
                    const bg = result ? (result.correct ? "var(--good-tint)" : "var(--warn-tint)") : "var(--card)";
                    return (
                      <div key={unit.id || index} style={{ display: "grid", gridTemplateColumns: "auto minmax(0, 1fr) auto", gap: 8, alignItems: "center" }}>
                        <SpeakBtn text={unit.text} audioRef={(unit.audio && (unit.audio.ref || unit.audio.id)) || unit.text} audioUrl={unit.audio && unit.audio.url} size={30} title={`Écouter ${activeLevel.label.toLowerCase()} ${index + 1}`} />
                        <input value={activeUnitAnswers[index] || ""} onChange={ev => setUnitAnswer(index, ev.target.value)} className="kr" lang="ko" autoCapitalize="off" autoCorrect="off" spellCheck={false} placeholder={`${activeLevel.kind} ${index + 1}`} style={{ minWidth: 0, padding: "8px 10px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: bg, color: "var(--ink)", fontSize: 17 }} />
                        {result && <span className="faint" style={{ fontSize: 12 }}>{result.correct ? "OK" : dictationFeedbackLabel(result)}</span>}
                      </div>
                    );
                  })}
                </div>
                <div style={{ display: "flex", justifyContent: "space-between", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
                  {unitChecked && !unitChecked.correct && unitChecked.tags.length > 0 && <span className="faint" style={{ fontSize: 12 }}>Tags niveau : {unitChecked.tags.join(" · ")}</span>}
                  <span style={{ flex: 1 }} />
                  <Btn kind="secondary" size="sm" icon="check" disabled={!activeUnitAnswers.some(v => String(v || "").trim())} onClick={checkUnits}>Vérifier ce niveau</Btn>
                </div>
              </>
            ) : (
              <div className="faint" style={{ fontSize: 12.5 }}>La ligne entière se travaille dans le champ principal.</div>
            )}
          </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: "100%", minHeight: 86, resize: "vertical", padding: 14, borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card)", color: "var(--ink)", fontSize: 22, lineHeight: 1.45 }} />
        <KoreanKeyboard value={answer} onChange={(next) => { setAnswer(next); setChecked(null); }} />

        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <Btn kind="primary" size="sm" icon="check" disabled={!answer.trim()} onClick={() => setChecked(compareDictationAnswer(line.kr, answer))}>Vérifier</Btn>
          <Btn kind="ghost" size="sm" icon="eye" onClick={() => setChecked(revealDictationAnswer(line.kr, answer))}>Révéler</Btn>
          <span style={{ flex: 1 }} />
          <Btn kind="ghost" size="sm" icon="prev" disabled={sceneIndex === 0 && lineIndex === 0} onClick={() => goLine(-1)}>Précédent</Btn>
          <Btn kind="secondary" size="sm" iconR="next" disabled={sceneIndex === scenes.length - 1 && lineIndex === lines.length - 1} onClick={() => goLine(1)}>Suivant</Btn>
        </div>

        {checked && (
          <div className="fade-up" style={{ display: "grid", gap: 12, padding: 14, borderRadius: "var(--radius-sm)", border: `1px solid ${exact ? "transparent" : "var(--rule)"}`, background: exact ? "var(--good-tint)" : "var(--card-2)" }}>
            <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
              <span className="chip" style={{ background: exact ? "var(--good-tint)" : "var(--warn-tint)", color: exact ? "var(--good)" : "var(--warn)", borderColor: "transparent" }}>{dictationFeedbackLabel(checked)}</span>
              <span className="muted" style={{ fontSize: 13.5 }}>{line.fr}</span>
            </div>
            {!exact && feedbackTags.length > 0 && <div className="faint" style={{ fontSize: 12.5 }}>Erreurs : {feedbackTags.join(" · ")}</div>}
            <Speakable as="div" text={line.kr} audioRef={line.audio && (line.audio.ref || line.audio.id)} audioUrl={line.audio && line.audio.url} className="kr" style={{ fontSize: 27, lineHeight: 1.45, color: "var(--accent-deep)" }}>{line.kr}</Speakable>
          </div>
        )}
      </div>
    </section>
  );
}
