/* Paires minimales — drill LIBRE de discrimination auditive (PEDAGOGIE P8/P14).
   Aucun effet SRS : on entraîne l'oreille (ㅓ/ㅗ, 가/까/카, nasales finales…),
   les stats restent locales. Entendre -> choisir -> contraster en réécoutant. */

const MP_STATS_KEY = "minimalPairsStats";
const MP_RETRY_DELAY = 3; // un set raté revient après 3 questions (reprise proche)
const MP_RUN_LENGTH = 12;

function mpLoadStats() {
  const store = window.LangStore;
  const raw = store && store.get ? store.get(MP_STATS_KEY, {}) : {};
  return raw && typeof raw === "object" ? raw : {};
}

function mpSaveStats(stats) {
  const store = window.LangStore;
  if (store && store.set) store.set(MP_STATS_KEY, stats);
}

function mpBumpStats(contrastId, correct) {
  const stats = mpLoadStats();
  const entry = stats[contrastId] || { attempts: 0, correct: 0 };
  entry.attempts += 1;
  if (correct) entry.correct += 1;
  stats[contrastId] = entry;
  mpSaveStats(stats);
  return stats;
}

/* Mélange déterministe par graine (pas de surprise au re-render). */
function mpShuffle(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;
}

/* File de questions : chaque set apparaît avec une cible aléatoire ; mix entrelacé. */
function mpBuildQueue(contrasts, contrastId, seed) {
  const pool = [];
  contrasts.forEach(contrast => {
    if (contrastId !== "mix" && contrast.id !== contrastId) return;
    contrast.sets.forEach(set => pool.push({ contrast, set }));
  });
  const shuffled = mpShuffle(pool, seed);
  return shuffled.slice(0, MP_RUN_LENGTH).map((entry, index) => {
    const target = mpShuffle(entry.set.items, seed + index * 7 + 1)[0];
    return {
      contrast: entry.contrast,
      set: entry.set,
      target,
      choices: mpShuffle(entry.set.items, seed + index * 13 + 5),
    };
  });
}

function mpSpeak(text) {
  if (window.LangAudio) window.LangAudio.speak(text, { rate: 0.92 });
}

function MinimalPairsChoice({ item, state, onPick }) {
  // state: "idle" | "correct" | "wrong" | "actual"
  const bg = state === "correct" ? "var(--good-tint)" : state === "wrong" ? "var(--warn-tint)" : state === "actual" ? "var(--accent-tint)" : "var(--card)";
  const border = state === "idle" ? "var(--rule)" : "transparent";
  return (
    <button onClick={onPick} className="kr" style={{
      cursor: "pointer", padding: "18px 10px", borderRadius: "var(--radius-sm)",
      border: `1px solid ${border}`, background: bg, color: "var(--ink)",
      fontSize: 30, fontWeight: 600, minWidth: 96,
    }}>
      {item.kr}
    </button>
  );
}

function MinimalPairsDrill() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [contrasts, setContrasts] = useState([]);
  const [contrastId, setContrastId] = useState("mix");
  const [run, setRun] = useState(null); // {queue, idx, correct, total, missQueue:[{question, wait}]}
  const [picked, setPicked] = useState(null);
  const [played, setPlayed] = useState(false);
  const [lifetime, setLifetime] = useState(() => mpLoadStats());

  useEffect(() => {
    let alive = true;
    if (!window.LangData || !window.LangData.loadMinimalPairs) {
      setLoading(false);
      setError("Banque de contrastes indisponible.");
      return () => { alive = false; };
    }
    window.LangData.loadMinimalPairs()
      .then(payload => { if (alive) setContrasts(payload.contrasts || []); })
      .catch(() => { if (alive) setError("Impossible de charger les paires minimales."); })
      .finally(() => { if (alive) setLoading(false); });
    return () => { alive = false; if (window.LangAudio) window.LangAudio.stop(); };
  }, []);

  const start = (id) => {
    const queue = mpBuildQueue(contrasts, id, Date.now() & 0x7fffffff);
    if (!queue.length) return;
    setContrastId(id);
    setRun({ queue, idx: 0, correct: 0, total: 0, missQueue: [] });
    setPicked(null);
    setPlayed(false);
  };

  if (loading) return <p className="faint" style={{ fontSize: 13 }}>Chargement des contrastes…</p>;
  if (error) return <EmptyState icon="speaker" title="Contrastes indisponibles" body={error} />;
  if (!contrasts.length) return <EmptyState icon="speaker" title="Aucun contraste" body="La banque data/minimal_pairs.json est vide." />;

  /* --- écran d'accueil : choisir un contraste --- */
  if (!run) {
    return (
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <Eyebrow>Micro-contrastes — discrimination auditive</Eyebrow>
          <span className="chip" style={{ fontSize: 11 }}>libre · sans effet SRS</span>
        </div>
        <p className="faint" style={{ fontSize: 13.5, margin: 0, maxWidth: 560 }}>
          Écoutez, puis choisissez ce qui a été dit. Ces sons se confondent pour une oreille
          française — c'est l'oreille qu'on entraîne ici, avant la dictée et le shadowing.
        </p>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(250px,1fr))", gap: 10 }}>
          <Tile icon="layers" label="Mix de tout" meta={`${contrasts.reduce((a, c) => a + c.sets.length, 0)} paires/triplets`} onClick={() => start("mix")} />
          {contrasts.map(c => {
            const lt = lifetime[c.id];
            const pct = lt && lt.attempts >= 5 ? Math.round(100 * lt.correct / lt.attempts) : null;
            return (
              <Tile key={c.id} icon="speaker" label={c.label}
                meta={`${c.kind}${pct != null ? ` · ${pct}% sur ${lt.attempts}` : ""}`}
                onClick={() => start(c.id)} />
            );
          })}
        </div>
      </div>
    );
  }

  /* --- bilan de fin de série --- */
  const question = run.queue[run.idx] || null;
  if (!question) {
    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} réponses justes</span>
        </div>
        <p className="faint" style={{ fontSize: 13, margin: 0 }}>
          {pct >= 80
            ? "Bonne oreille sur cette série — passez à un contraste plus dur ou à la dictée."
            : "Normal au début : réécoutez les deux sons côte à côte avant de répondre, l'écart finit par s'entendre."}
        </p>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <Btn kind="primary" icon="refresh" onClick={() => start(contrastId)}>Rejouer</Btn>
          <Btn kind="secondary" icon="grid" onClick={() => setRun(null)}>Choisir un contraste</Btn>
        </div>
      </div>
    );
  }

  const answered = picked != null;
  const isCorrect = answered && picked === question.target.kr;

  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) }));
      // raté -> le même set revient plus loin avec une nouvelle cible
      if (!isCorrect) {
        missQueue = missQueue.concat([{ question: { ...question, target: mpShuffle(question.set.items, Date.now() & 0x7fffffff)[0] }, wait: MP_RETRY_DELAY }]);
      }
      const ready = missQueue.findIndex(m => m.wait === 0);
      let idx = prev.idx + 1;
      if (ready >= 0 && idx >= queue.length - 0) {
        // réinsère les reprises mûres en fin de file
        queue = queue.concat([missQueue[ready].question]);
        missQueue = missQueue.filter((_, i) => i !== ready);
      }
      return { ...prev, queue, idx, missQueue };
    });
    setPicked(null);
    setPlayed(false);
  };

  const pick = (item) => {
    if (answered || !played) return;
    const correct = item.kr === question.target.kr;
    setPicked(item.kr);
    setRun(prev => prev ? { ...prev, correct: prev.correct + (correct ? 1 : 0), total: prev.total + 1 } : prev);
    setLifetime(mpBumpStats(question.contrast.id, correct));
    if (window.LangSounds) {
      if (correct && window.LangSounds.correct) window.LangSounds.correct();
      if (!correct && window.LangSounds.wrong) window.LangSounds.wrong();
    }
  };

  return (
    <div style={{ display: "grid", gap: 14 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
        <Eyebrow>{question.contrast.label}</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} répondu{run.total > 1 ? "s" : ""} · {run.correct} juste{run.correct > 1 ? "s" : ""}</span>
      </div>
      <div className="card" style={{ padding: 24, display: "grid", gap: 16 }}>
        <p className="faint" style={{ fontSize: 13, margin: 0 }}>{question.contrast.note}</p>
        <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
          <Btn kind={played ? "secondary" : "primary"} icon="speaker" onClick={() => { mpSpeak(question.target.kr); setPlayed(true); }}>
            {played ? "Réécouter" : "Écouter"}
          </Btn>
          {!played && <span className="faint" style={{ fontSize: 12.5 }}>Écoutez d'abord — puis choisissez ce que vous avez entendu.</span>}
        </div>
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap", opacity: played ? 1 : 0.45 }}>
          {question.choices.map(item => {
            const state = !answered ? "idle"
              : item.kr === question.target.kr ? (isCorrect ? "correct" : "actual")
                : item.kr === picked ? "wrong" : "idle";
            return <MinimalPairsChoice key={item.kr} item={item} state={state} onPick={() => pick(item)} />;
          })}
        </div>
        {answered && (
          <div className="fade-up" style={{ display: "grid", gap: 10, 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 entendu." : `C'était « ${question.target.kr} »${question.target.fr ? ` (${question.target.fr})` : ""}.`}
            </div>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
              <span className="faint" style={{ fontSize: 12.5 }}>Comparez côte à côte :</span>
              {question.set.items.map(item => (
                <button key={item.kr} onClick={() => mpSpeak(item.kr)} className="chip kr" style={{ cursor: "pointer", fontSize: 14 }}>
                  ▶ {item.kr}{item.fr && !item.fr.startsWith("syllabe") ? ` · ${item.fr}` : ""}
                </button>
              ))}
            </div>
            {!isCorrect && <span className="faint" style={{ fontSize: 12.5 }}>Ce set reviendra un peu plus loin dans la série.</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>
  );
}
