/* Atelier - Compter : lecon + drills nombres (sino/natif), audio-first.
   Drill libre, sans effet SRS. Extrait d'atelier.jsx (decoupage 2026-06-12). */

const NUMBER_FILTERS = [
  { value: "mix", label: "Mix" },
  { value: "system", label: "Systemes" },
  { value: "qcm", label: "QCM" },
  { value: "production", label: "Clavier" },
  { value: "audio", label: "Audio" },
  { value: "contrast", label: "Contrastes" },
];

const NUMBER_TYPE_LABELS = {
  choose_system: "Choisir le systeme",
  qcm_number: "Lire le nombre",
  production: "Produire en coreen",
  audio_first: "Audio first",
  contrast_pair: "Serie contrastive",
};

const NUMBER_RUN_LIMIT = 24;

function numberTypeFilter(type) {
  if (type === "choose_system") return "system";
  if (type === "qcm_number") return "qcm";
  if (type === "production") return "production";
  if (type === "audio_first") return "audio";
  if (type === "contrast_pair") return "contrast";
  return "mix";
}

function numberAnswerToken(value) {
  return String(value || "")
    .normalize("NFC")
    .trim()
    .replace(/[.,!?;:'"`()[\]{}]/g, "")
    .replace(/\s+/g, "");
}

function numberMatches(exercise, answer) {
  const got = numberAnswerToken(answer);
  const accepted = (Array.isArray(exercise.answers) && exercise.answers.length ? exercise.answers : [exercise.answer])
    .map(numberAnswerToken);
  return accepted.indexOf(got) >= 0;
}

function numberExerciseKey(id, byId) {
  const ex = byId && byId[id] ? byId[id] : id;
  if (!ex || typeof ex === "string") return String(ex || "");
  return ex.repeatKey || `${ex.type}:${ex.prompt || ex.id}`;
}

function numberChoiceOrder(exercise) {
  const choices = Array.isArray(exercise.choices) ? exercise.choices.slice() : [];
  return choices.sort((a, b) => practiceHash(`${exercise.id}:num:${a}`) - practiceHash(`${exercise.id}:num:${b}`));
}

function numberExerciseSet(exercises, filter) {
  const list = Array.isArray(exercises) ? exercises : [];
  if (!filter || filter === "mix") return list;
  return list.filter((exercise) => numberTypeFilter(exercise.type) === filter);
}

function numberSystemChip(system) {
  if (system === "native") return "Natif";
  if (system === "sino") return "Sino";
  return "Nombre";
}

function numberGeneratedExercises(payload, filter, seed) {
  if (window.LangNumbers && typeof window.LangNumbers.generatedExercises === "function") {
    return window.LangNumbers.generatedExercises(payload, { filter: filter || "mix", seed: seed || "preview" });
  }
  const exercises = Array.isArray(payload) ? payload : (payload && payload.exercises);
  return numberExerciseSet(exercises, filter);
}

function numberHasKorean(text) {
  return /[\u3131-\u318e\uac00-\ud7a3]/.test(String(text || ""));
}

function playNumberText(text) {
  const value = String(text || "");
  if (value && numberHasKorean(value) && window.LangAudio) window.LangAudio.speak(value, { ref: value });
}

function NumberSpeakText({ text, className, style, audioByText }) {
  const value = String(text || "");
  if (!value) return null;
  if (!numberHasKorean(value)) return <span className={className} style={style}>{value}</span>;
  const alternatives = value.split(/\s+\/\s+/).filter(Boolean);
  if (alternatives.length > 1 && alternatives.every(numberHasKorean)) {
    return <NumberSpeakGroup values={alternatives} style={style} audioByText={audioByText} />;
  }
  const audioUrl = audioByText && audioByText[value] ? audioByText[value] : "";
  return <Speakable text={value} audioRef={value} audioUrl={audioUrl} frame={false} className={className || "kr"} style={style}>{value}</Speakable>;
}

function NumberSpeakGroup({ values, style, audioByText }) {
  const parts = (Array.isArray(values) ? values : [values]).filter(Boolean);
  return (
    <span style={style}>
      {parts.map((part, index) => (
        <React.Fragment key={`${part}:${index}`}>
          {index > 0 && <span> / </span>}
          <NumberSpeakText text={part} className="kr" style={{ fontSize: "inherit", color: "inherit" }} audioByText={audioByText} />
        </React.Fragment>
      ))}
    </span>
  );
}

function NumberRichText({ text, className, style, audioByText }) {
  const parts = String(text || "").split(/([\u3131-\u318e\uac00-\ud7a3]+(?:\s+[\u3131-\u318e\uac00-\ud7a3]+)*)/g);
  return (
    <span className={className} style={style}>
      {parts.map((part, index) => (
        numberHasKorean(part)
          ? <NumberSpeakText key={index} text={part} className="kr" style={{ color: "inherit", fontSize: "inherit" }} audioByText={audioByText} />
          : <React.Fragment key={index}>{part}</React.Fragment>
      ))}
    </span>
  );
}

function startNumbersRun(payload, filter) {
  const seed = `run:${filter || "mix"}:${Date.now()}:${Math.random()}`;
  const pool = numberGeneratedExercises(payload, filter || "mix", seed);
  const selected = pool.slice(0, Math.min(NUMBER_RUN_LIMIT, pool.length));
  const byId = {};
  selected.forEach((exercise) => { byId[exercise.id] = exercise; });
  let queue = selected.map((exercise) => exercise.id);
  if (window.LangReviewQueue) {
    for (let index = 1; index < queue.length; index++) {
      queue = window.LangReviewQueue.avoidImmediateRepeatAtCursor(queue, index, queue[index - 1], {
        keyFor: (id) => numberExerciseKey(id, byId),
      });
    }
  }
  return {
    id: `numbers_${Date.now()}_${filter || "mix"}`,
    filter: filter || "mix",
    queue,
    items: selected,
    poolSize: pool.length,
    pos: 0,
    attempts: {},
    results: [],
    startedAt: Date.now(),
    finished: false,
  };
}


function NumberRun({ run, setRun, payload }) {
  const [picked, setPicked] = useState(null);
  const [answer, setAnswer] = useState("");
  const [checked, setChecked] = useState(null);

  const byId = {};
  (payload.exercises || []).concat(run.items || []).forEach((exercise) => { byId[exercise.id] = exercise; });
  const currentId = run.queue[run.pos];
  const current = byId[currentId];
  const currentAttempt = (run.attempts[currentId] || 0) + 1;
  const progress = run.queue.length ? run.pos / run.queue.length : 0;

  useEffect(() => {
    setPicked(null);
    setAnswer("");
    setChecked(null);
  }, [run.id, run.pos]);

  function finish(nextRun) {
    setRun(Object.assign({}, nextRun, { finished: true }));
  }

  function submitResult(correct, value) {
    if (!current) return;
    const result = {
      exerciseId: current.id,
      type: current.type,
      system: current.system || "",
      correct,
      answer: value || "",
      expected: current.answer || "",
      attempt: currentAttempt,
      finishedAt: Date.now(),
    };
    const attempts = Object.assign({}, run.attempts, { [current.id]: currentAttempt });
    let queue = run.queue.slice();
    const nextPos = run.pos + 1;
    if (!correct && currentAttempt < 3 && window.LangReviewQueue) {
      queue = window.LangReviewQueue.requeueAfterGap(queue, current.id, nextPos, {
        keyFor: (id) => numberExerciseKey(id, byId),
        gap: 2,
      });
    }
    if (window.LangReviewQueue) {
      queue = window.LangReviewQueue.avoidImmediateRepeatAtCursor(queue, nextPos, current.id, {
        keyFor: (id) => numberExerciseKey(id, byId),
      });
    }
    const nextRun = Object.assign({}, run, {
      attempts,
      queue,
      pos: nextPos,
      results: run.results.concat(result),
    });
    if (nextPos >= queue.length) finish(nextRun);
    else setRun(nextRun);
  }

  function choose(choice) {
    const correct = numberMatches(current, choice);
    setPicked(choice);
    setChecked({ correct, value: choice });
  }

  function checkProduction() {
    const correct = numberMatches(current, answer);
    setChecked({ correct, value: answer });
  }

  if (run.finished) {
    const total = run.results.length;
    const correct = run.results.filter((result) => result.correct).length;
    const bySystem = run.results.reduce((acc, result) => {
      if (result.system) acc[result.system] = (acc[result.system] || 0) + 1;
      return acc;
    }, {});
    return (
      <section className="card fade-up" style={{ padding: "var(--pad-card)", display: "grid", gap: 18 }}>
        <div>
          <Eyebrow>Compter</Eyebrow>
          <h2 style={{ fontSize: 34, marginTop: 4 }}>{correct === total ? "Serie nette" : "A consolider"}</h2>
          <p className="muted" style={{ margin: "8px 0 0", maxWidth: 620 }}>
            {correct} reponses justes sur {total}. Les erreurs peuvent etre relancees dans le meme mode.
          </p>
        </div>
        <StatStrip items={[
          { value: total, label: "Questions" },
          { value: correct, label: "Justes", accent: true },
          { value: bySystem.native || 0, label: "Natif" },
          { value: bySystem.sino || 0, label: "Sino" },
        ]} />
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
          <Btn kind="primary" icon="refresh" onClick={() => setRun(startNumbersRun(payload, run.filter))}>Refaire ce mode</Btn>
          <Btn kind="secondary" icon="grid" onClick={() => setRun(null)}>Retour Compter</Btn>
        </div>
      </section>
    );
  }

  if (!current) return <EmptyState icon="grid" title="Serie vide" body="Choisis un autre mode de nombres." />;

  const isProduction = current.type === "production";
  const isAudio = current.type === "audio_first";
  const choices = numberChoiceOrder(current);
  const title = NUMBER_TYPE_LABELS[current.type] || "Nombres";
  const systemLabel = numberSystemChip(current.system);

  return (
    <section className="card fade-up" style={{ padding: "var(--pad-card)", display: "grid", gap: 18 }}>
      <div style={{ display: "flex", justifyContent: "space-between", gap: 12, flexWrap: "wrap", alignItems: "flex-start" }}>
        <div>
          <Eyebrow>{title}</Eyebrow>
          <h2 style={{ fontSize: 32, marginTop: 4 }}>Compter en coreen</h2>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }}>
          <span className="chip" style={{ fontSize: 12 }}>{systemLabel}</span>
          <span className="chip" style={{ fontSize: 12 }}>{run.pos + 1} / {run.queue.length}</span>
          {currentAttempt > 1 && <span className="chip" style={{ fontSize: 12, background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>essai {currentAttempt}</span>}
        </div>
      </div>
      <Progress value={progress} />

      <div style={{ display: "grid", gap: 14, justifyItems: "center", textAlign: "center", padding: "4px 0 2px" }}>
        {isAudio ? (
          <>
            <SpeakBtn text={current.audioText} audioRef={current.audioText} size={54} title="Ecouter" />
            <div className="muted" style={{ fontSize: 16 }}>{current.prompt}</div>
          </>
        ) : current.type === "qcm_number" ? (
          <Speakable text={current.prompt} audioRef={current.prompt} className="kr" style={{ fontSize: "clamp(42px, 7vw, 68px)", lineHeight: 1.05, padding: "6px 14px", color: "var(--accent-deep)" }}>
            {current.prompt}
          </Speakable>
        ) : (
          <div style={{ fontFamily: "var(--serif)", fontSize: "clamp(34px, 6vw, 58px)", color: "var(--accent-deep)", lineHeight: 1.08 }}>
            {current.prompt}
          </div>
        )}
        {current.type === "choose_system" && <div className="muted" style={{ fontSize: 15 }}>Quel systeme faut-il utiliser ?</div>}
      </div>

      {isProduction ? (
        <div style={{ display: "grid", gap: 10 }}>
          <textarea value={answer} onChange={(ev) => { setAnswer(ev.target.value); setChecked(null); }} rows={2} placeholder="Tapez la reponse en coreen..." className="kr" lang="ko" autoCapitalize="off" autoCorrect="off" spellCheck={false}
            style={{ width: "100%", resize: "vertical", border: "1px solid var(--rule)", borderRadius: "var(--radius-sm)", padding: 13, background: "var(--card-2)", color: "var(--ink)", fontSize: 22, lineHeight: 1.5, boxSizing: "border-box" }} />
          {window.KoreanKeyboard && <KoreanKeyboard value={answer} onChange={(next) => { setAnswer(next); setChecked(null); }} compact />}
          <div style={{ display: "flex", justifyContent: "flex-end" }}>
            <Btn kind="secondary" icon="check" disabled={!answer.trim()} onClick={checkProduction}>Verifier</Btn>
          </div>
        </div>
      ) : (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 8 }}>
          {choices.map((choice) => {
            const on = picked === choice;
            const correct = checked && numberMatches(current, choice);
            return (
              <button key={choice} onClick={() => { playNumberText(choice); choose(choice); }} style={{
                border: `1px solid ${on ? "var(--accent-line)" : "var(--rule)"}`,
                background: checked && correct ? "var(--good-tint)" : on ? "var(--accent-tint)" : "var(--card-2)",
                color: checked && correct ? "var(--good)" : on ? "var(--accent-deep)" : "var(--ink-soft)",
                borderRadius: "var(--radius-sm)",
                padding: "12px 14px",
                textAlign: "center",
                cursor: "pointer",
                fontWeight: 850,
                minHeight: 48,
              }}><span className={numberHasKorean(choice) ? "kr" : undefined}>{choice}</span></button>
            );
          })}
        </div>
      )}

      {checked && (
        <div className="fade-up" style={{
          border: "1px solid var(--rule)",
          background: checked.correct ? "var(--good-tint)" : "var(--warn-tint)",
          borderRadius: "var(--radius-sm)",
          padding: 15,
          display: "grid",
          gap: 8,
        }}>
          <div style={{ fontWeight: 900, color: checked.correct ? "var(--good)" : "var(--warn)" }}>
            {checked.correct ? "Juste" : "A reprendre"} - reponse : <NumberSpeakText text={current.answer} className={numberHasKorean(current.answer) ? "kr" : undefined} />
          </div>
          {(current.kr || current.audioText) && (
            <div className="kr" style={{ fontSize: 22, color: "var(--ink)" }}>
              <Speakable text={current.kr || current.audioText} audioRef={current.kr || current.audioText} frame={false}>
                {current.kr || current.audioText}
              </Speakable>
            </div>
          )}
          {current.explanation && <div className="muted" style={{ fontSize: 14 }}>{current.explanation}</div>}
        </div>
      )}

      <div style={{ display: "flex", justifyContent: "space-between", gap: 10, flexWrap: "wrap" }}>
        <Btn kind="ghost" icon="grid" onClick={() => setRun(null)}>Quitter</Btn>
        <Btn kind="primary" icon="next" disabled={!checked} onClick={() => checked && submitResult(checked.correct, checked.value)}>Continuer</Btn>
      </div>
    </section>
  );
}

function numberAudioMapFromManifest(manifest) {
  const map = {};
  const entries = manifest && Array.isArray(manifest.entries) ? manifest.entries : [];
  entries.forEach((entry) => {
    const text = entry && (entry.text || entry.kr);
    const rawUrl = entry && (entry.url || entry.path);
    const hash = entry && entry.hash ? String(entry.hash).replace(/^sha256:/, "").slice(0, 12) : "";
    const url = rawUrl && hash && String(rawUrl).indexOf("/audio/numbers/") === 0
      ? rawUrl + (String(rawUrl).indexOf("?") >= 0 ? "&" : "?") + "h=" + hash
      : rawUrl;
    if (text && url && (entry.cat === "numbers" || String(url).indexOf("/audio/numbers/") === 0)) map[text] = url;
  });
  return map;
}

function useNumberAudioByText() {
  const [audioByText, setAudioByText] = useState({});
  useEffect(() => {
    let alive = true;
    const loader = window.LangData && window.LangData.loadAudioManifest
      ? window.LangData.loadAudioManifest()
      : fetch("/data/audio_manifest.json").then((r) => r.ok ? r.json() : Promise.reject(new Error("manifest indisponible")));
    loader
      .then((manifest) => { if (alive) setAudioByText(numberAudioMapFromManifest(manifest)); })
      .catch(() => { if (alive) setAudioByText({}); });
    return () => { alive = false; };
  }, []);
  return audioByText;
}

function NumbersLesson({ payload, audioByText: providedAudioByText }) {
  const loadedAudioByText = useNumberAudioByText();
  const audioByText = providedAudioByText || loadedAudioByText;
  const lesson = payload.lesson || {};
  const generatedEntries = window.LangNumbers && typeof window.LangNumbers.generatedNumberEntries === "function"
    ? window.LangNumbers.generatedNumberEntries(99)
    : (payload.numbers || []);
  const firstNumbers = generatedEntries.filter((entry) => entry.value >= 0 && entry.value <= 10);
  const extraByValue = {};
  (payload.numbers || []).forEach((entry) => { extraByValue[entry.value] = entry; });
  const firstNumberRows = firstNumbers.map((entry) => Object.assign({}, entry, extraByValue[entry.value] || {}));
  const structureNumbers = (payload.numbers || []).filter((entry) => [20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 42000].indexOf(entry.value) >= 0);
  return (
    <section className="card fade-up" style={{ padding: "var(--pad-card)", display: "grid", gap: 18 }}>
      <div>
        <Eyebrow>Cours</Eyebrow>
        <h2 style={{ fontSize: 32, marginTop: 4 }}>{lesson.title || "La base des nombres coreens"}</h2>
        {lesson.lead && <p className="muted" style={{ margin: "8px 0 0", maxWidth: 760, fontSize: 16 }}>{lesson.lead}</p>}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(230px, 1fr))", gap: 10 }}>
        {(lesson.sections || []).map((section) => (
          <div key={section.id} style={{ border: "1px solid var(--rule)", background: "var(--card-2)", borderRadius: "var(--radius-sm)", padding: 14, display: "grid", gap: 8 }}>
            <div style={{ fontWeight: 900, color: "var(--accent-deep)" }}>{section.title}</div>
            {(section.body || []).map((line, index) => (
              <p key={index} className="muted" style={{ margin: 0, fontSize: 13.5, lineHeight: 1.45 }}><NumberRichText text={line} audioByText={audioByText} /></p>
            ))}
          </div>
        ))}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))", gap: 14 }}>
        <div style={{ border: "1px solid var(--rule)", borderRadius: "var(--radius-sm)", overflow: "hidden" }}>
          <div style={{ padding: "10px 12px", background: "var(--card-2)", fontWeight: 900 }}>Premiers nombres</div>
          <div style={{ display: "grid" }}>
            {firstNumberRows.map((entry) => (
              <div key={entry.value} style={{ display: "grid", gridTemplateColumns: "42px 1fr 1fr", gap: 8, padding: "8px 12px", borderTop: "1px solid var(--rule)", alignItems: "baseline" }}>
                <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 12 }}>{entry.value}</span>
                <NumberSpeakText text={entry.sino} className="kr" style={{ fontSize: 18 }} audioByText={audioByText} />
                {entry.native ? (
                  <NumberSpeakGroup values={entry.nativeAttributive && entry.nativeAttributive !== entry.native ? [entry.native, entry.nativeAttributive] : [entry.native]} style={{ fontSize: 18 }} audioByText={audioByText} />
                ) : entry.code ? (
                  <NumberSpeakText text={entry.code} className="kr" style={{ fontSize: 18 }} audioByText={audioByText} />
                ) : (
                  <span className="faint" style={{ fontSize: 18 }}>-</span>
                )}
              </div>
            ))}
          </div>
        </div>

        <div style={{ border: "1px solid var(--rule)", borderRadius: "var(--radius-sm)", overflow: "hidden" }}>
          <div style={{ padding: "10px 12px", background: "var(--card-2)", fontWeight: 900 }}>Structures utiles</div>
          <div style={{ display: "grid" }}>
            {structureNumbers.map((entry) => (
              <div key={entry.value} style={{ display: "grid", gridTemplateColumns: "72px 1fr", gap: 8, padding: "8px 12px", borderTop: "1px solid var(--rule)", alignItems: "baseline" }}>
                <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 12 }}>{entry.value}</span>
                <NumberSpeakGroup values={entry.native ? [entry.sino, entry.nativeAttributive || entry.native] : [entry.sino]} style={{ fontSize: 18 }} audioByText={audioByText} />
              </div>
            ))}
          </div>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 8 }}>
        {(lesson.examples || []).map((example) => (
          <div key={example.label} style={{ border: "1px solid var(--rule)", background: "var(--card-2)", borderRadius: "var(--radius-sm)", padding: 12, display: "grid", gap: 5 }}>
            <div className="faint" style={{ fontSize: 12 }}>{example.label}</div>
            <NumberSpeakText text={example.kr} className="kr" style={{ fontSize: 21, color: "var(--ink)" }} audioByText={audioByText} />
            <div className="muted" style={{ fontSize: 12.5 }}><NumberRichText text={example.note} audioByText={audioByText} /></div>
          </div>
        ))}
      </div>
    </section>
  );
}

function NumbersWorkshop() {
  const [load, setLoad] = useState(() => ({ status: window.LangData && window.LangData.loadNumbers ? "loading" : "error", payload: null, error: null }));
  const [filter, setFilter] = useState("mix");
  const [run, setRun] = useState(null);
  const [showLesson, setShowLesson] = useState(false);
  const audioByText = useNumberAudioByText();

  useEffect(() => {
    let alive = true;
    if (!window.LangData || !window.LangData.loadNumbers) {
      setLoad((s) => ({ ...s, status: "error", error: new Error("loadNumbers missing") }));
      return () => { alive = false; };
    }
    window.LangData.loadNumbers().then((payload) => {
      if (alive) setLoad({ status: "ready", payload, error: null });
    }).catch((error) => {
      if (alive) setLoad((s) => ({ ...s, status: "error", error }));
    });
    return () => { alive = false; };
  }, []);

  if (load.status === "loading") return <div style={{ display: "grid", gap: 16 }}><LoadingCard /><LoadingCard lines={4} /></div>;
  if (load.status === "error") return <ErrorState title="Compter indisponible" body={load.error && load.error.message} onRetry={() => location.reload()} />;

  const payload = load.payload || { systems: [], exercises: [], contrasts: [] };
  const filtered = numberGeneratedExercises(payload, filter, `preview:${filter}`);
  const allGenerated = numberGeneratedExercises(payload, "mix", "preview:mix");
  const systemCount = numberGeneratedExercises(payload, "system", "preview:system").length;
  const productionCount = numberGeneratedExercises(payload, "production", "preview:production").length;
  const audioCount = numberGeneratedExercises(payload, "audio", "preview:audio").length;
  const runSize = Math.min(NUMBER_RUN_LIMIT, filtered.length);

  if (run) return <NumberRun run={run} setRun={setRun} payload={payload} />;

  return (
    <div style={{ display: "grid", gap: 22 }}>
      <section style={{ display: "flex", justifyContent: "space-between", gap: 20, alignItems: "flex-end", flexWrap: "wrap" }}>
        <div>
          <Eyebrow>Compter</Eyebrow>
          <h1 style={{ fontSize: "clamp(36px, 5vw, 58px)", marginTop: 4 }}>Nombres coreens</h1>
          <p className="muted" style={{ maxWidth: 720, margin: "10px 0 0", fontSize: 16 }}>
            Alterner sino-coreen et natif jusqu'a choisir le bon systeme sans hesitation.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }}>
          <Btn kind={showLesson ? "primary" : "secondary"} icon="book" onClick={() => setShowLesson((value) => !value)}>{showLesson ? "Masquer le cours" : "Cours"}</Btn>
          <Btn kind="primary" icon="play" disabled={!filtered.length} onClick={() => setRun(startNumbersRun(payload, filter))}>Lancer</Btn>
        </div>
      </section>

      {showLesson && <NumbersLesson payload={payload} audioByText={audioByText} />}

      <StatStrip items={[
        { value: allGenerated.length, label: "Vivier" },
        { value: systemCount, label: "Choix systeme", accent: true },
        { value: productionCount, label: "Productions" },
        { value: audioCount, label: "Audio-first" },
      ]} />

      <div className="atelier-grid" style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) 330px", gap: 22, alignItems: "start" }}>
        <div style={{ display: "grid", gap: 18 }}>
          <section className="card" style={{ padding: "var(--pad-card)", display: "grid", gap: 18 }}>
            <div style={{ display: "flex", justifyContent: "space-between", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
              <div>
                <Eyebrow>Mode</Eyebrow>
                <h2 style={{ fontSize: 30, marginTop: 4 }}>Serie de nombres</h2>
              </div>
              <Segmented value={filter} options={NUMBER_FILTERS} onChange={setFilter} size="sm" />
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 8 }}>
              {filtered.slice(0, 8).map((exercise) => (
                <div key={exercise.id} style={{ border: "1px solid var(--rule)", background: "var(--card-2)", borderRadius: "var(--radius-sm)", padding: 12, display: "grid", gap: 6 }}>
                  <span className="chip" style={{ fontSize: 11, justifySelf: "start" }}>{numberSystemChip(exercise.system)}</span>
                  <div style={{ fontWeight: 850 }}>{NUMBER_TYPE_LABELS[exercise.type] || "Nombre"}</div>
                  <div className={numberHasKorean(exercise.prompt) ? "kr faint" : "faint"} style={{ fontSize: 13 }}>
                    {numberHasKorean(exercise.prompt)
                      ? <NumberSpeakText text={exercise.prompt} className="kr" style={{ fontSize: "inherit", color: "inherit" }} audioByText={audioByText} />
                      : exercise.prompt}
                  </div>
                </div>
              ))}
            </div>
            <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, flexWrap: "wrap" }}>
              <span className="chip" style={{ fontSize: 12 }}>{runSize} tirees / {filtered.length} dans ce mode</span>
              <Btn kind="primary" icon="play" disabled={!filtered.length} onClick={() => setRun(startNumbersRun(payload, filter))}>Commencer</Btn>
            </div>
          </section>
        </div>

        <aside className="atelier-side" style={{ position: "sticky", top: 22, display: "grid", gap: 16 }}>
          <section className="card" style={{ padding: 18, display: "grid", gap: 12 }}>
            <div>
              <Eyebrow>Systemes</Eyebrow>
              <h2 style={{ fontSize: 24, marginTop: 4 }}>Repere rapide</h2>
            </div>
            {(payload.systems || []).map((system) => (
              <div key={system.id} style={{ borderTop: "1px solid var(--rule)", paddingTop: 10, display: "grid", gap: 4 }}>
                <div style={{ display: "flex", justifyContent: "space-between", gap: 8, alignItems: "center" }}>
                  <strong>{system.label}</strong>
                  <span className="kr chip" style={{ fontSize: 12 }}>{system.krLabel}</span>
                </div>
                <div className="muted" style={{ fontSize: 13.5 }}>{system.cue}</div>
              </div>
            ))}
          </section>

          <section className="card" style={{ padding: 18, display: "grid", gap: 10 }}>
            <Eyebrow>Contrastes</Eyebrow>
            {(payload.contrasts || []).slice(0, 4).map((item) => (
              <div key={item.value} style={{ display: "grid", gridTemplateColumns: "42px 1fr", gap: 8, alignItems: "baseline" }}>
                <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 12 }}>{item.value}</span>
                <NumberSpeakGroup values={[item.sino, item.native, item.attributive]} style={{ fontSize: 18 }} audioByText={audioByText} />
              </div>
            ))}
          </section>
        </aside>
      </div>
    </div>
  );
}


Object.assign(window, { NumbersWorkshop });
