/* Produire — cahier d'écriture calme + couverture, statuts, note, journal,
   stats, pont LLM optionnel, boutons continuer/pause (parcours du jour). */

const STATUS_META = {
  todo: { label: "À produire", color: "var(--ink-faint)", tint: "var(--card-2)" },
  corriger: { label: "À corriger", color: "var(--warn)", tint: "var(--warn-tint)" },
  ok: { label: "OK", color: "var(--good)", tint: "var(--good-tint)" },
  fluide: { label: "Fluide", color: "var(--accent)", tint: "var(--accent-tint)" },
};

function productionJournalFallback() {
  return (window.LANGUES_DATA && window.LANGUES_DATA.productionJournal) || [];
}

function readProductionJournal() {
  const helper = window.LangOutputCorrections;
  if (helper && helper.readProductionJournal) return helper.readProductionJournal(window.LangStore, productionJournalFallback());
  if (window.LangStore && window.LangStore.get) {
    const list = window.LangStore.get("productionJournal", null);
    if (Array.isArray(list)) return list;
  }
  return productionJournalFallback();
}

function writeProductionJournal(list) {
  const helper = window.LangOutputCorrections;
  if (helper && helper.writeProductionJournal) return helper.writeProductionJournal(window.LangStore, list);
  if (window.LangStore && window.LangStore.set) window.LangStore.set("productionJournal", list);
  return list;
}

function journalStatusKey(entry) {
  if (entry && entry.statusKey && STATUS_META[entry.statusKey]) return entry.statusKey;
  const helper = window.LangOutputCorrections;
  if (helper && helper.canonicalStatus) return helper.canonicalStatus(entry && entry.status, null);
  const s = String((entry && entry.status) || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
  if (s.indexOf("fluide") >= 0) return "fluide";
  if (s.indexOf("corriger") >= 0) return "corriger";
  if (s.indexOf("ok") >= 0 || s.indexOf("correct") >= 0) return "ok";
  return "todo";
}

function PromptPicker({ prompts, idx, onPick }) {
  return (
    <div className="no-sb" style={{ display: "flex", gap: 7, overflowX: "auto", paddingBottom: 4 }}>
      {prompts.map((p, i) => {
        const on = i === idx;
        const st = STATUS_META[p.status] || STATUS_META.todo;
        return (
          <button key={p.id} onClick={() => onPick(i)} aria-label={`Production ${i + 1}: ${p.fr}`} title={p.fr} style={{ flexShrink: 0, width: 34, height: 34, borderRadius: 9, cursor: "pointer", border: `1px solid ${on ? "var(--accent)" : "var(--rule)"}`, background: on ? "var(--accent)" : st.tint, color: on ? "#fff" : st.color, fontFamily: "var(--mono)", fontSize: 13, fontWeight: 600 }}>{i + 1}</button>
        );
      })}
    </div>
  );
}

function Coverage({ must, modeles, text }) {
  // Couverture relative au modèle visé (cf. output-corrections.coverage) : une variante
  // acceptée d'un autre modèle ne doit pas être comptée « manquante ».
  const oc = window.LangOutputCorrections;
  const cov = oc && oc.coverage
    ? oc.coverage({ must, modeles }, text)
    : (() => { const got = (must || []).filter(m => text.includes(m)); return { got, missing: (must || []).filter(m => !text.includes(m)), total: (must || []).length, expected: must || [] }; })();
  const expected = cov.expected && cov.expected.length ? cov.expected : (must || []);
  const got = cov.got;
  const missing = cov.missing;
  const words = text.trim() ? text.trim().split(/\s+/) : [];
  const extra = words.filter(w => !expected.some(m => w.includes(m) || m.includes(w)));
  return (
    <div style={{ display: "grid", gap: 10 }}>
      <div style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
        <div><div className="faint" style={{ fontSize: 11.5 }}>Couverture</div><div style={{ fontFamily: "var(--serif)", fontSize: 22, color: got.length === expected.length ? "var(--good)" : "var(--ink)" }}>{got.length}/{expected.length}</div></div>
        <div style={{ flex: 1 }}>
          <div className="faint" style={{ fontSize: 11.5, marginBottom: 5 }}>Éléments attendus</div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            {expected.map((m, i) => { const ok = got.includes(m); return <span key={i} className="kr" style={{ padding: "4px 10px", borderRadius: 999, fontSize: 14, border: "1px solid transparent", background: ok ? "var(--good-tint)" : "var(--warn-tint)", color: ok ? "var(--good)" : "var(--warn)" }}>{ok ? "✓ " : ""}{m}</span>; })}
          </div>
        </div>
      </div>
      {missing.length > 0 && <div className="faint" style={{ fontSize: 12.5 }}>Manquants : <span className="kr">{missing.join(", ")}</span></div>}
      {extra.length > 0 && <div className="faint" style={{ fontSize: 12.5 }}>En trop possibles : <span className="kr">{extra.slice(0, 4).join(", ")}</span></div>}
    </div>
  );
}

function LLMBridge({ prompt, answer, onApply }) {
  const [step, setStep] = useState(0);
  const [resp, setResp] = useState("");
  const [applied, setApplied] = useState(null);
  const [copied, setCopied] = useState("");
  const bridge = window.LangLLMBridge;
  const generated = bridge && bridge.buildPrompt ? bridge.buildPrompt({ prompt, answer }) : "";
  const apply = () => {
    if (!bridge || !bridge.parseCorrection || !bridge.toOutputResponse) {
      setApplied({ note: "Pont LLM indisponible.", statut: "—", error: true });
      return;
    }
    const parsed = bridge.parseCorrection(resp);
    if (!parsed.ok) {
      setApplied({ note: parsed.message || "JSON invalide — collez la réponse complète.", statut: "—", error: true });
      return;
    }
    const converted = bridge.toOutputResponse(parsed.value);
    if (!converted.ok) {
      setApplied({ note: converted.message || "Token non reconnu.", statut: "—", error: true });
      return;
    }
    const out = onApply ? onApply(converted.value) : { entry: converted.value };
    const entry = (out && out.entry) || converted.value;
    setApplied(Object.assign({}, entry, { llmToken: converted.value.llmToken, delayLabel: converted.value.delayLabel }));
  };
  const copyPrompt = () => {
    setStep(2);
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(generated).then(() => setCopied("Prompt copié."), () => setCopied("Prompt affiché, copie manuelle."));
    } else {
      setCopied("Prompt affiché, copie manuelle.");
    }
  };
  return (
      <div style={{ display: "grid", gap: 12 }}>
      <p className="muted" style={{ fontSize: 14, margin: 0 }}>Optionnel — la production manuelle reste prioritaire. Préparez un prompt, copiez-le vers votre LLM, puis collez la réponse JSON.</p>
      <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
        <Btn kind="secondary" size="sm" icon="spark" onClick={() => setStep(1)}>1 · Préparer le prompt</Btn>
        <Btn kind="secondary" size="sm" icon="copy" disabled={!answer || !answer.trim()} onClick={copyPrompt}>2 · Copier</Btn>
        <Btn kind="secondary" size="sm" icon="upload" onClick={() => setStep(3)}>3 · Coller la réponse</Btn>
      </div>
      {copied && <span className="chip fade-up" style={{ justifySelf: "start" }}>{copied}</span>}
      {step >= 1 && <pre className="no-sb" style={{ margin: 0, padding: 12, borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px solid var(--rule)", fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink-soft)", whiteSpace: "pre-wrap", overflowX: "auto" }}>{generated}</pre>}
      {step >= 3 && (
        <div style={{ display: "grid", gap: 8 }}>
          <textarea value={resp} onChange={e => setResp(e.target.value)} placeholder='```json&#10;{"schema":"LANGUES2_LLM_CORRECTION_V1","token":"WRONG_FORM","corrected":"...","explanation_fr":"...","error_tags":["forme"],"confidence":0.8,"needs_human_review":false}&#10;```' style={{ width: "100%", minHeight: 96, padding: 12, borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card)", fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--ink)" }} />
          <div><Btn kind="primary" size="sm" icon="check" onClick={apply}>Appliquer la note</Btn></div>
          {applied && <div className="fade-up" style={{ padding: 12, borderRadius: "var(--radius-sm)", background: applied.error ? "var(--warn-tint)" : "var(--accent-tint)", border: applied.error ? "1px solid var(--warn)" : "1px solid var(--accent-line)", fontSize: 13.5 }}><strong>Note :</strong> {applied.note} {(applied.status || applied.statut) && <span className="chip" style={{ marginLeft: 8, fontSize: 11 }}>{applied.status || applied.statut}</span>} {applied.llmToken && <span className="chip" style={{ marginLeft: 8, fontSize: 11 }}>{applied.llmToken}{applied.delayLabel ? ` · ${applied.delayLabel}` : ""}</span>}</div>}
        </div>
      )}
    </div>
  );
}

function ProduirePage({ go }) {
  const fallbackPrompts = () => (window.LANGUES_DATA && window.LANGUES_DATA.prompts) || [];
  const [prompts, setPrompts] = useState(fallbackPrompts);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const [pi, setPi] = useState(0);
  const [text, setText] = useState("");
  const [compared, setCompared] = useState(false);
  const [compareResult, setCompareResult] = useState(null);
  const [saveMsg, setSaveMsg] = useState("");
  const [showModel, setShowModel] = useState(false);
  const [journal, setJournal] = useState(readProductionJournal);
  const p = prompts[Math.min(pi, Math.max(0, prompts.length - 1))];

  const loadPrompts = useCallback(() => {
    if (!window.LangData || !window.LangData.loadPrompts) return undefined;
    let alive = true;
    setLoading(true);
    setError("");
    window.LangData.loadPrompts()
      .then(payload => {
        if (!alive) return;
        setPrompts(payload.prompts || []);
      })
      .catch(() => {
        if (!alive) return;
        setError("Impossible de charger les prompts canoniques.");
        setPrompts(fallbackPrompts());
      })
      .finally(() => { if (alive) setLoading(false); });
    return () => { alive = false; };
  }, []);

  useEffect(loadPrompts, [loadPrompts]);
  useEffect(() => {
    if (prompts.length && pi >= prompts.length) setPi(0);
  }, [prompts.length, pi]);

  if (loading && !prompts.length) return (
    <div style={{ maxWidth: 780, margin: "0 auto", display: "grid", gap: "var(--gap-lg)" }}>
      <header className="fade-up">
        <Eyebrow>Cahier d'Ã©criture</Eyebrow>
        <h1 style={{ fontSize: 42, margin: "6px 0 0" }}>Produire</h1>
      </header>
      <LoadingCard lines={3} />
    </div>
  );
  if (error && !prompts.length) return (
    <div style={{ maxWidth: 780, margin: "0 auto" }}>
      <ErrorState body={error} onRetry={loadPrompts} />
    </div>
  );
  if (!p) return (
    <div style={{ maxWidth: 780, margin: "0 auto" }}>
      <EmptyState icon="pen" title="Aucun prompt disponible" body="La banque de production est vide pour l'instant." />
    </div>
  );

  const resetWork = () => { setText(""); setCompared(false); setCompareResult(null); setSaveMsg(""); setShowModel(false); };
  const nav = (d) => { setPi(x => Math.max(0, Math.min(prompts.length - 1, x + d))); resetWork(); };
  const pick = (i) => { setPi(i); resetWork(); };
  const buildCorrection = (response) => {
    if (!text.trim() || !window.LangOutputCorrections) return null;
    return window.LangOutputCorrections.buildCorrection({
      prompt: p,
      answer: text,
      response,
      kind: "guided_production",
      validate: window.LangValidate,
    }, { validate: window.LangValidate });
  };
  const preview = () => {
    const out = buildCorrection(null);
    setCompareResult(out);
    setCompared(true);
    setSaveMsg("");
  };
  const applyCorrection = (response) => {
    if (!text.trim() || !window.LangOutputCorrections) return null;
    const out = window.LangOutputCorrections.applyCorrection({
      prompt: p,
      answer: text,
      response,
      kind: "guided_production",
      store: window.LangStore,
      log: window.LangSessionLog,
      scheduler: window.FSRS,
      validate: window.LangValidate,
      fallbackJournal: productionJournalFallback(),
    });
    setJournal(out.journal || readProductionJournal());
    setCompareResult(out);
    setCompared(true);
    setSaveMsg(out.srsUpdate ? "Correction journalisée · carte liée marquée." : "Correction journalisée.");
    return out;
  };
  const save = () => {
    const out = applyCorrection(null);
    if (out || !text.trim()) return;
    const entry = { kr: text.trim(), fr: p.fr, status: "OK", statusKey: "ok", note: "Enregistré." };
    setJournal(j => writeProductionJournal([entry, ...j]));
  };
  const del = (i) => setJournal(j => writeProductionJournal(j.filter((_, k) => k !== i)));

  const counts = {
    ok: journal.filter(j => journalStatusKey(j) === "ok").length,
    fluide: journal.filter(j => journalStatusKey(j) === "fluide").length,
    corriger: journal.filter(j => journalStatusKey(j) === "corriger").length,
  };

  return (
    <div style={{ maxWidth: 780, margin: "0 auto", display: "grid", gap: "var(--gap-lg)" }}>
      <header className="fade-up" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: 12 }}>
        <div>
          <Eyebrow>Cahier d'écriture · {prompts.length || window.LANGUES_DATA.corpus.productionGuided} productions</Eyebrow>
          <h1 style={{ fontSize: 42, margin: "6px 0 0" }}>Produire</h1>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <Btn kind="ghost" size="sm" icon="prev" onClick={() => nav(-1)}>Préc.</Btn>
          <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 13 }}>{pi + 1}/{prompts.length}</span>
          <Btn kind="ghost" size="sm" iconR="next" onClick={() => nav(1)}>Suiv.</Btn>
        </div>
      </header>

      <Collapsible eyebrow="Navigation" title="Choisir un autre prompt" hint={`${pi + 1}/${prompts.length}`}>
        <PromptPicker prompts={prompts} idx={pi} onPick={pick} />
      </Collapsible>

      <div className="card fade-up" style={{ padding: "calc(var(--pad-card) + 6px)", display: "grid", gap: 18 }}>
        <div>
          <Eyebrow>Phrase à produire</Eyebrow>
          <div style={{ fontFamily: "var(--serif)", fontSize: 26, color: "var(--accent-deep)", marginTop: 8 }}>« {p.fr} »</div>
        </div>
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
          <span className="chip"><strong className="kr" style={{ color: "var(--accent-deep)" }}>{p.patron}</strong></span>
          <button onClick={() => setShowModel(s => !s)} className="chip" style={{ cursor: "pointer" }}><Icon name={showModel ? "eyeoff" : "eye"} size={14} /> Modèle</button>
          <button onClick={() => window.LangAudio && window.LangAudio.speak(p.modele)} className="chip" style={{ cursor: "pointer" }}><Icon name="speaker" size={14} /> Écouter le modèle</button>
        </div>
        {showModel && <div className="fade-up kr" style={{ fontSize: 20, color: "var(--ink)", background: "var(--card-2)", padding: "12px 16px", borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)" }}>{p.modele} {p.modeleRo && <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 13 }}>· {p.modeleRo}</span>}</div>}

        <div>
          <div className="eyebrow" style={{ marginBottom: 8 }}>Votre phrase</div>
          <textarea value={text} onChange={e => { setText(e.target.value); setCompared(false); setCompareResult(null); setSaveMsg(""); }} placeholder="여기에 쓰세요…" className="kr" lang="ko" autoCapitalize="off" autoCorrect="off" spellCheck={false} style={{ width: "100%", minHeight: 92, resize: "vertical", padding: 16, borderRadius: "var(--radius-sm)", border: "1px solid var(--rule)", background: "var(--card-2)", fontSize: 22, color: "var(--ink)", lineHeight: 1.5 }} />
          <KoreanKeyboard value={text} onChange={(next) => { setText(next); setCompared(false); setCompareResult(null); setSaveMsg(""); }} />
        </div>

        <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          <Btn kind="primary" icon="check" disabled={!text.trim()} onClick={preview}>Comparer au modèle</Btn>
          <Btn kind="secondary" icon="book" onClick={save}>Enregistrer au journal</Btn>
          {saveMsg && <span className="chip fade-up" style={{ fontSize: 11.5, background: "var(--good-tint)", color: "var(--good)", borderColor: "transparent" }}>{saveMsg}</span>}
        </div>

        {compared && (
          <div className="fade-up" style={{ paddingTop: 16, borderTop: "1px solid var(--rule)", display: "grid", gap: 14 }}>
            <Coverage must={p.must} modeles={p.modeles} text={text} />
            {compareResult && (
              <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                <span className="faint" style={{ fontSize: 13 }}>Statut :</span>
                {(() => {
                  const m = STATUS_META[compareResult.statusKey] || STATUS_META.todo;
                  return <span className="chip" style={{ fontSize: 11.5, background: m.tint, color: m.color, borderColor: "transparent" }}>{m.label}</span>;
                })()}
                {compareResult.errorTags && compareResult.errorTags.map(t => <span key={t} className="chip" style={{ fontSize: 11.5, background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>{t}</span>)}
                {compareResult.srsUpdate && <span className="chip" style={{ fontSize: 11.5 }}>SRS · {compareResult.srsUpdate.cardId}</span>}
              </div>
            )}
            <div style={{ padding: 12, borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px solid var(--rule)" }}>
              <div className="faint" style={{ fontSize: 11.5, marginBottom: 3 }}>Note de correction</div>
              <p style={{ fontSize: 14, margin: 0 }}>{(compareResult && compareResult.entry && compareResult.entry.note) || "Correction prête à journaliser."}</p>
              {compareResult && compareResult.entry && compareResult.entry.correction && (
                <p style={{ fontSize: 13, margin: "8px 0 0" }}>Modèle : <span className="kr">{compareResult.entry.correction}</span></p>
              )}
            </div>
          </div>
        )}
      </div>

      {/* Parcours du jour */}
      <div className="card" style={{ padding: "14px var(--pad-card)", display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap", background: "var(--card-2)" }}>
        <Icon name="today" size={18} style={{ color: "var(--ochre)" }} />
        <span style={{ fontSize: 14, fontWeight: 600, flex: 1 }}>Inclus dans le parcours du jour — étape 5/5</span>
        <Btn kind="primary" size="sm" icon="play" onClick={() => go("today")}>Continuer le parcours</Btn>
      </div>

      <Collapsible eyebrow="Avancé" title="Pont LLM (optionnel)" hint="préparer · copier · appliquer"><LLMBridge prompt={p} answer={text} onApply={applyCorrection} /></Collapsible>

      <Collapsible eyebrow="Historique" title="Journal des productions" hint={`${journal.length} entrées`} defaultOpen>
        <div style={{ display: "grid", gap: 8 }}>
          {journal.length === 0 ? <EmptyState icon="pen" title="Journal vide" body="Vos phrases enregistrées apparaîtront ici." /> : journal.map((e, i) => {
            const key = journalStatusKey(e);
            const sm = STATUS_META[key] || STATUS_META.todo;
            return (
              <div key={i} style={{ display: "flex", alignItems: "center", gap: 12, padding: "11px 14px", borderRadius: "var(--radius-sm)", background: "var(--card)", border: "1px solid var(--rule)" }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="kr" style={{ fontSize: 17 }}>{e.kr}</div>
                  <div className="faint" style={{ fontSize: 12 }}>{e.note}</div>
                  {e.errorTags && e.errorTags.length > 0 && <div style={{ display: "flex", gap: 5, flexWrap: "wrap", marginTop: 5 }}>{e.errorTags.map(t => <span key={t} className="chip" style={{ fontSize: 10.5, padding: "2px 7px", background: "var(--warn-tint)", color: "var(--warn)", borderColor: "transparent" }}>{t}</span>)}</div>}
                </div>
                <span className="chip" style={{ fontSize: 11, background: sm.tint, color: sm.color, borderColor: "transparent" }}>{e.status || sm.label}</span>
                <button onClick={() => del(i)} title="Supprimer" style={{ border: "none", background: "transparent", cursor: "pointer", color: "var(--ink-faint)", padding: 4 }}><Icon name="trash" size={16} /></button>
              </div>
            );
          })}
        </div>
      </Collapsible>

      <Collapsible eyebrow="Suivi" title="Stats de production">
        <div style={{ display: "flex", gap: 24, flexWrap: "wrap" }}>
          {[["OK", counts.ok], ["Fluide", counts.fluide], ["À corriger", counts.corriger], ["Total prompts", prompts.length]].map(([l, v]) => (
            <div key={l}><div style={{ fontFamily: "var(--serif)", fontSize: 26, color: "var(--accent-deep)" }}>{v}</div><div className="faint" style={{ fontSize: 12 }}>{l}</div></div>
          ))}
        </div>
      </Collapsible>
    </div>
  );
}

Object.assign(window, { ProduirePage });
