/* korrespond.js — page-scoped UI for /korrespond.php
   Three-pass flow: Pass 1 may return clarify questions; Pass 2 returns Norwegian
   + working-language drafts with verified law citations. Pass 3 (opt-in) refines
   the draft with jurisdiction-scoped formal citations + Rettskilder appendix.
*/
(function () {
  'use strict';

  const els = {};
  let lang = window.DBN_TOOLS_LANG || localStorage.getItem('dbn-ui-lang') || 'en';
  let uploadFiles = [];
  let lastClassify = null;
  let lastFinal = null;
  let pendingClarifications = {};

  const LANG_LABELS = { en: 'English', no: 'Norsk', uk: 'Українська', pl: 'Polski' };

  // All user-facing chrome strings, localized. Drafts themselves stay in NO + working lang.
  const I18N = {
    pick_recipient:     { en: 'Pick a recipient body before drafting.',                     no: 'Velg en mottaker før utkast.',                            pl: 'Wybierz odbiorcę przed sporządzeniem projektu.',                  uk: 'Виберіть отримувача перед чернеткою.' },
    initiate_narrative: { en: 'Describe the situation in "What happened" first.',           no: 'Beskriv situasjonen i "Hva skjedde" først.',              pl: 'Najpierw opisz sytuację w polu "Co się stało".',                  uk: 'Спочатку опишіть ситуацію у полі "Що сталося".' },
    reply_no_input:     { en: 'Reply mode needs the received letter (upload or paste it).', no: 'Svarmodus krever det mottatte brevet (last opp eller lim inn).', pl: 'Tryb odpowiedzi wymaga otrzymanego pisma (prześlij lub wklej).', uk: 'Режим відповіді потребує отриманого листа (завантажте або вставте).' },
    analyzing:          { en: 'Analyzing…',                                                 no: 'Analyserer…',                                             pl: 'Analiza…',                                                        uk: 'Аналізую…' },
    refining:           { en: 'Refining…',                                                  no: 'Skriver om…',                                             pl: 'Przepisuję…',                                                     uk: 'Переписую…' },
    started:            { en: 'Started — {body} / {output}',                                no: 'Startet — {body} / {output}',                             pl: 'Rozpoczęto — {body} / {output}',                                  uk: 'Розпочато — {body} / {output}' },
    fetched_sources:    { en: 'Fetched {n} sources for {acts}…',                            no: 'Hentet {n} kilder for {acts}…',                           pl: 'Pobrano {n} źródeł dla {acts}…',                                  uk: 'Завантажено {n} джерел для {acts}…' },
    need_clarify:       { en: 'Need clarification before drafting.',                        no: 'Trenger avklaring før utkast.',                           pl: 'Wymaga wyjaśnienia przed sporządzeniem projektu.',                uk: 'Потрібне уточнення перед чернеткою.' },
    network_error:      { en: 'Network error: {msg}',                                       no: 'Nettverksfeil: {msg}',                                    pl: 'Błąd sieci: {msg}',                                               uk: 'Помилка мережі: {msg}' },
    stream_error:       { en: 'Stream error: {msg}',                                        no: 'Strømfeil: {msg}',                                        pl: 'Błąd strumienia: {msg}',                                          uk: 'Помилка потоку: {msg}' },
    request_failed:     { en: 'Request failed ({status}).',                                 no: 'Forespørselen mislyktes ({status}).',                     pl: 'Żądanie nie powiodło się ({status}).',                            uk: 'Запит не вдалося ({status}).' },
    stream_no_draft:    { en: 'Stream ended without a draft.',                              no: 'Strømmen sluttet uten utkast.',                           pl: 'Strumień zakończył się bez projektu.',                            uk: 'Потік завершився без чернетки.' },
    done_summary:       { en: 'Done in {s} s · {n} cited source(s)',                        no: 'Ferdig på {s} s · {n} sitert(e) kilde(r)',                pl: 'Gotowe w {s} s · {n} cytowanych źródeł',                          uk: 'Готово за {s} с · {n} цитованих джерел' },
    refining_status:    { en: 'Refining draft with {jur} authorities…',                     no: 'Skriver om utkast med {jur}-kilder…',                     pl: 'Przepisuję projekt ze źródłami {jur}…',                           uk: 'Переписую чернетку з джерелами {jur}…' },
    refining_short:     { en: 'Refining ({jur})…',                                          no: 'Skriver om ({jur})…',                                     pl: 'Przepisuję ({jur})…',                                             uk: 'Переписую ({jur})…' },
    refined_summary:    { en: 'Refined in {s} s · {n} cited authority(ies) · {jur}',        no: 'Omskrevet på {s} s · {n} sitert(e) rettskilde(r) · {jur}', pl: 'Przepisano w {s} s · {n} cytowanych autorytetów · {jur}',         uk: 'Переписано за {s} с · {n} цитованих джерел · {jur}' },
    refine_failed:      { en: 'Refine failed ({status}).',                                  no: 'Omskriving mislyktes ({status}).',                        pl: 'Przepisanie nie powiodło się ({status}).',                        uk: 'Переписування не вдалося ({status}).' },
    refine_no_result:   { en: 'Refine stream ended without a result.',                      no: 'Strømmen sluttet uten resultat.',                         pl: 'Strumień zakończył się bez wyniku.',                              uk: 'Потік завершився без результату.' },
    refine_fetched:     { en: 'Fetched {n} authorities for {jur}…',                         no: 'Hentet {n} rettskilder for {jur}…',                       pl: 'Pobrano {n} autorytetów dla {jur}…',                              uk: 'Завантажено {n} джерел для {jur}…' },
    working:            { en: 'Working…',                                                   no: 'Arbeider…',                                               pl: 'Pracuję…',                                                        uk: 'Працюю…' },
    working_long:       { en: 'Pass 1 extracts facts. If anything is missing we will ask. Then Pass 2 runs hard-RAG retrieval, draft, self-check, and translation.', no: 'Pass 1 henter fakta. Hvis noe mangler spør vi. Deretter kjører Pass 2: kilder, utkast, kontroll og oversettelse.', pl: 'Pass 1 wyodrębnia fakty. Jeśli czegoś brak, zapytamy. Następnie Pass 2: źródła, projekt, kontrola, tłumaczenie.', uk: 'Pass 1 витягує факти. Якщо чогось бракує, ми спитаємо. Потім Pass 2: джерела, чернетка, перевірка, переклад.' },
    no_cited:           { en: 'No cited law sources — draft is plain-language (no § references available from corpus).', no: 'Ingen siterte lovkilder — utkastet er på vanlig språk (ingen §-henvisninger fra korpus).', pl: 'Brak cytowanych źródeł prawa — projekt w zwykłym języku (brak odniesień § z korpusu).', uk: 'Немає цитованих джерел права — чернетка звичайною мовою (без § посилань з корпусу).' },
    no_draft_yet:       { en: 'No draft to refine. Run a draft first.',                     no: 'Ingen utkast å skrive om. Lag et utkast først.',          pl: 'Brak projektu do przepisania. Najpierw stwórz projekt.',          uk: 'Немає чернетки для переписування. Спочатку створіть чернетку.' },
    // Section/header chrome
    ready_title:        { en: 'Ready',                                                      no: 'Klar',                                                    pl: 'Gotowe',                                                          uk: 'Готовий' },
    ready_desc:         { en: 'Pick a recipient body, describe the situation, choose an output type and tone, then run. Drafts always come back in Norwegian bokmål + your working language, side-by-side, with verified law citations.', no: 'Velg en mottaker, beskriv situasjonen, velg utdataformat og tone, og kjør. Utkast leveres alltid på norsk bokmål + arbeidsspråket, side ved side, med verifiserte lovhenvisninger.', pl: 'Wybierz odbiorcę, opisz sytuację, wybierz format i ton, uruchom. Projekty zawsze zwracane są po norwesku (bokmål) + w Twoim języku, obok siebie, ze zweryfikowanymi cytatami prawa.', uk: 'Виберіть отримувача, опишіть ситуацію, виберіть формат і тон, запустіть. Чернетки повертаються норвезькою (bokmål) + вашою мовою, поряд, з перевіреними посиланнями на закон.' },
    summary_pass1:      { en: 'Summary (Pass 1)',                                           no: 'Sammendrag (Pass 1)',                                     pl: 'Podsumowanie (Pass 1)',                                           uk: 'Резюме (Pass 1)' },
    assumed_legal:      { en: 'Presumed legal basis:',                                      no: 'Antatt rettslig grunnlag:',                               pl: 'Domniemana podstawa prawna:',                                     uk: 'Передбачувана правова основа:' },
    deadlines_label:    { en: 'Deadlines:',                                                 no: 'Frister:',                                                pl: 'Terminy:',                                                        uk: 'Терміни:' },
    canonical:          { en: 'canonical',                                                  no: 'kanonisk',                                                pl: 'kanoniczny',                                                      uk: 'канонічний' },
    reference:          { en: 'reference',                                                  no: 'referanse',                                               pl: 'referencja',                                                      uk: 'довідка' },
    refined_label:      { en: 'refined',                                                    no: 'omskrevet',                                               pl: 'przepisany',                                                      uk: 'переписаний' },
    copy:               { en: 'Copy',                                                       no: 'Kopier',                                                  pl: 'Kopiuj',                                                          uk: 'Копіювати' },
    copied:             { en: 'Copied ✓',                                                   no: 'Kopiert ✓',                                               pl: 'Skopiowano ✓',                                                    uk: 'Скопійовано ✓' },
    download_txt:       { en: 'Download .txt',                                              no: 'Last ned .txt',                                           pl: 'Pobierz .txt',                                                    uk: 'Завантажити .txt' },
    cited_law_n:        { en: 'Cited law ({n})',                                            no: 'Siterte kilder ({n})',                                    pl: 'Cytowane prawo ({n})',                                            uk: 'Цитоване право ({n})' },
    cited_law_hint:     { en: 'each reference traces to a real corpus passage',             no: 'hver henvisning kan spores til en reell korpuspassasje',  pl: 'każde odniesienie pochodzi z prawdziwego fragmentu korpusu',      uk: 'кожне посилання простежується до реального уривка корпусу' },
    cited_authorities_n:{ en: 'Cited authorities ({n})',                                    no: 'Siterte rettskilder ({n})',                               pl: 'Cytowane autorytety ({n})',                                       uk: 'Цитовані джерела ({n})' },
    view_source:        { en: 'View source',                                                no: 'Se kilde',                                                pl: 'Zobacz źródło',                                                   uk: 'Переглянути джерело' },
    flag_cites:         { en: 'Citations verified',                                         no: 'Henvisninger verifisert',                                 pl: 'Cytaty zweryfikowane',                                            uk: 'Цитати перевірено' },
    flag_deadline:      { en: 'Deadline',                                                   no: 'Frist',                                                   pl: 'Termin',                                                          uk: 'Термін' },
    flag_goal:          { en: 'Goal addressed',                                             no: 'Mål adressert',                                           pl: 'Cel uwzględniony',                                                uk: 'Мета врахована' },
    flag_tone:          { en: 'Tone',                                                       no: 'Tone',                                                    pl: 'Ton',                                                             uk: 'Тон' },
    refine_title:       { en: 'Refine with formal citations',                               no: 'Skriv om med formelle henvisninger',                      pl: 'Przepisz z formalnymi cytatami',                                  uk: 'Переписати з формальними цитатами' },
    one_extra_credit:   { en: '(+1 credit)',                                                no: '(+1 kreditt)',                                            pl: '(+1 kredyt)',                                                     uk: '(+1 кредит)' },
    refine_hint:        { en: 'Optional 2nd pass: pull fresh authorities, rewrite citations in formal style ("jf. forvaltningsloven § 17", "jf. Strand Lobben m.fl. mot Norge, EMD-37283/13, §§ 207–214"), and append a Rettskilder block at the bottom.', no: 'Valgfri 2. omgang: hent ferske rettskilder, skriv om henvisningene formelt ("jf. forvaltningsloven § 17", "jf. Strand Lobben m.fl. mot Norge, EMD-37283/13, §§ 207–214"), og legg ved en Rettskilder-blokk nederst.', pl: 'Opcjonalna 2. tura: pobierz świeże autorytety, przepisz cytaty w stylu formalnym, dołącz blok Rettskilder na końcu.', uk: 'Опційний 2-й прохід: завантажити свіжі джерела, переписати цитати у формальному стилі, додати блок Rettskilder в кінці.' },
    jur_norwegian:      { en: 'Norwegian law only',                                         no: 'Kun norsk rett',                                          pl: 'Tylko prawo norweskie',                                           uk: 'Лише норвезьке право' },
    jur_echr:           { en: 'ECHR (EMK + HUDOC)',                                         no: 'EMK (EMK + HUDOC)',                                       pl: 'EKPC (EMK + HUDOC)',                                              uk: 'ЄКПЛ (EMK + HUDOC)' },
    jur_both:           { en: 'Both',                                                       no: 'Begge',                                                   pl: 'Oba',                                                             uk: 'Обидва' },
    refine_btn:         { en: 'Refine citations',                                           no: 'Skriv om henvisninger',                                   pl: 'Przepisz cytaty',                                                 uk: 'Переписати цитати' },
    refine_btn_busy:    { en: 'Refining…',                                                  no: 'Skriver om…',                                             pl: 'Przepisuję…',                                                     uk: 'Переписую…' },
    // Clarify chrome
    clarify_title:      { en: 'Before we draft, clarify:',                                  no: 'Før vi skriver, avklar:',                                 pl: 'Zanim sporządzimy projekt, wyjaśnij:',                            uk: 'Перш ніж писати, уточніть:' },
    clarify_hint:       { en: 'Answer what you can, then click Continue draft. Or click Draft anyway to proceed with what we have.', no: 'Svar på det du kan, og klikk Fortsett utkast. Eller klikk Skriv likevel for å fortsette med det vi har.', pl: 'Odpowiedz, co możesz, i kliknij Kontynuuj projekt. Albo kliknij Mimo to napisz, aby kontynuować z tym, co mamy.', uk: 'Дайте відповідь, що можете, потім натисніть Продовжити. Або натисніть Все одно написати, щоб продовжити з тим, що є.' },
    clarify_continue:   { en: 'Continue draft',                                             no: 'Fortsett utkast',                                         pl: 'Kontynuuj projekt',                                               uk: 'Продовжити' },
    clarify_force:      { en: 'Draft anyway',                                               no: 'Skriv likevel',                                           pl: 'Mimo to napisz',                                                  uk: 'Все одно написати' },
    optional:           { en: '(optional)',                                                 no: '(valgfritt)',                                             pl: '(opcjonalnie)',                                                   uk: '(необов’язково)' },
    // Bracket label inside drafts column header — value-only (the language NAME stays its own name)
    no_column:          { en: 'Norsk (bokmål)',                                             no: 'Norsk (bokmål)',                                          pl: 'Norsk (bokmål)',                                                  uk: 'Norsk (bokmål)' },
  };

  function t(key, vars) {
    const m = I18N[key];
    if (!m) return key;
    let s = m[lang] || m.en || key;
    if (vars) for (const k in vars) s = s.replaceAll('{' + k + '}', String(vars[k]));
    return s;
  }

  document.addEventListener('DOMContentLoaded', () => {
    if (document.body.dataset.activeTool !== 'korrespond') return;

    Object.assign(els, {
      form:           document.getElementById('korrForm'),
      status:         document.getElementById('korrStatus'),
      runButton:      document.getElementById('korrRunButton'),
      results:        document.getElementById('korrResults'),
      langButtons:    Array.from(document.querySelectorAll('#korrLangSwitcher .lang-btn')),
      modeRadios:     Array.from(document.querySelectorAll('input[name="korrMode"]')),
      bodySelect:     document.getElementById('korrBody'),
      outputRadios:   Array.from(document.querySelectorAll('input[name="korrOutput"]')),
      toneRadios:     Array.from(document.querySelectorAll('input[name="korrTone"]')),
      caseRef:        document.getElementById('korrCaseRef'),
      where:          document.getElementById('korrWhere'),
      deadline:       document.getElementById('korrDeadline'),
      parties:        document.getElementById('korrParties'),
      narrative:      document.getElementById('korrNarrative'),
      goal:           document.getElementById('korrGoal'),
      goalChips:      Array.from(document.querySelectorAll('#korrGoalChips .korr-chip')),
      uploadZone:     document.getElementById('korrUploadZone'),
      uploadInput:    document.getElementById('korrUploadInput'),
      uploadPrompt:   document.getElementById('korrUploadPrompt'),
      uploadFileInfo: document.getElementById('korrUploadFileInfo'),
      uploadFileList: document.getElementById('korrUploadFileList'),
      uploadClear:    document.getElementById('korrUploadClear'),
      clarifyPanel:   document.getElementById('korrClarifyPanel'),
      clarifyList:    document.getElementById('korrClarifyList'),
      clarifyContinue:document.getElementById('korrClarifyContinue'),
      clarifyForce:   document.getElementById('korrClarifyForce'),
    });

    if (!els.form) return;

    bindLang();
    bindGoalChips();
    bindUpload();
    bindClarify();
    applyStaticI18n();
    els.form.addEventListener('submit', (e) => { e.preventDefault(); runRequest(false); });
  });

  // Apply [data-i18n] attributes on static DOM (and re-apply after lang switch).
  function applyStaticI18n() {
    document.querySelectorAll('[data-i18n]').forEach((el) => {
      const key = el.getAttribute('data-i18n');
      if (key) el.textContent = t(key);
    });
  }

  // ── Language switcher ───────────────────────────────────────────────────────
  function bindLang() {
    els.langButtons.forEach((b) => {
      b.classList.toggle('is-active', b.dataset.lang === lang);
      b.addEventListener('click', () => {
        els.langButtons.forEach((x) => x.classList.remove('is-active'));
        b.classList.add('is-active');
        lang = b.dataset.lang || 'en';
        localStorage.setItem('dbn-ui-lang', lang);
        applyStaticI18n();
      });
    });
  }

  function bindGoalChips() {
    els.goalChips.forEach((chip) => {
      chip.addEventListener('click', () => {
        els.goal.value = chip.dataset.goal || '';
        els.goalChips.forEach((c) => c.classList.remove('is-active'));
        chip.classList.add('is-active');
      });
    });
  }

  // ── File upload ─────────────────────────────────────────────────────────────
  function bindUpload() {
    if (!els.uploadZone) return;
    const onFiles = (fileList) => {
      const files = Array.from(fileList || []).slice(0, 4);
      if (uploadFiles.length + files.length > 4) {
        setStatus('At most 4 files can be uploaded per request.', 'error');
        return;
      }
      files.forEach((f) => {
        if (f.size > 8 * 1024 * 1024) {
          setStatus(`${f.name} exceeds the 8 MB limit.`, 'error');
          return;
        }
        const ext = (f.name.split('.').pop() || '').toLowerCase();
        if (!['pdf', 'docx', 'txt'].includes(ext)) {
          setStatus(`${f.name} is not a supported file type (PDF, DOCX, TXT).`, 'error');
          return;
        }
        uploadFiles.push(f);
      });
      renderUploadList();
    };
    els.uploadInput.addEventListener('change', (e) => onFiles(e.target.files));
    els.uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); els.uploadZone.classList.add('is-drop'); });
    els.uploadZone.addEventListener('dragleave', () => els.uploadZone.classList.remove('is-drop'));
    els.uploadZone.addEventListener('drop', (e) => {
      e.preventDefault();
      els.uploadZone.classList.remove('is-drop');
      onFiles(e.dataTransfer?.files);
    });
    els.uploadClear?.addEventListener('click', () => {
      uploadFiles = [];
      els.uploadInput.value = '';
      renderUploadList();
    });
  }

  function renderUploadList() {
    if (!uploadFiles.length) {
      els.uploadFileInfo.classList.add('is-hidden');
      els.uploadPrompt.classList.remove('is-hidden');
      return;
    }
    els.uploadPrompt.classList.add('is-hidden');
    els.uploadFileInfo.classList.remove('is-hidden');
    els.uploadFileList.innerHTML = uploadFiles.map((f) => {
      const kb = (f.size / 1024).toFixed(0);
      return `<li><span class="upload-filename">${esc(f.name)}</span><span class="upload-chars">${kb} KB</span></li>`;
    }).join('');
  }

  // ── Clarify panel ───────────────────────────────────────────────────────────
  function bindClarify() {
    els.clarifyContinue?.addEventListener('click', () => {
      pendingClarifications = collectClarifications();
      hideClarify();
      runRequest(false);
    });
    els.clarifyForce?.addEventListener('click', () => {
      pendingClarifications = collectClarifications();
      hideClarify();
      runRequest(true);
    });
  }

  function showClarify(questions) {
    els.clarifyList.innerHTML = (questions || []).map((q, i) => `
      <div class="korr-clarify-item">
        <label for="clarify_${i}"><strong>${esc(q.question || '')}</strong></label>
        <input type="text" id="clarify_${i}" data-key="${esc(q.key || '')}" placeholder="(optional)">
      </div>
    `).join('');
    els.clarifyPanel.classList.remove('is-hidden');
    els.clarifyPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }

  function hideClarify() {
    els.clarifyPanel.classList.add('is-hidden');
  }

  function collectClarifications() {
    const inputs = els.clarifyList.querySelectorAll('input[data-key]');
    const out = {};
    inputs.forEach((inp) => {
      const v = (inp.value || '').trim();
      if (v) out[inp.dataset.key] = v;
    });
    return out;
  }

  // ── Submit ──────────────────────────────────────────────────────────────────
  function getRadio(list) {
    const checked = list.find((r) => r.checked);
    return checked ? checked.value : '';
  }

  function buildPayload(forceDraft) {
    const deadlines = [];
    if (els.deadline.value.trim()) deadlines.push(els.deadline.value.trim());
    return {
      mode:           getRadio(els.modeRadios) || 'initiate',
      recipient_body: els.bodySelect.value || 'other',
      output_type:    getRadio(els.outputRadios) || 'email',
      tone:           getRadio(els.toneRadios) || 'neutral',
      language:       lang,
      case_ref:       els.caseRef.value.trim(),
      where:          els.where.value.trim(),
      deadlines,
      parties_text:   els.parties.value.trim(),
      narrative:      els.narrative.value.trim(),
      goal:           els.goal.value.trim(),
      clarifications: pendingClarifications,
      force_draft:    !!forceDraft,
    };
  }

  async function runRequest(forceDraft) {
    const payload = buildPayload(forceDraft);
    if (!payload.recipient_body) {
      setStatus(t('pick_recipient'), 'error');
      return;
    }
    if (payload.mode === 'initiate' && !payload.narrative) {
      setStatus(t('initiate_narrative'), 'error');
      return;
    }
    if (payload.mode === 'reply' && !uploadFiles.length && !payload.narrative) {
      setStatus(t('reply_no_input'), 'error');
      return;
    }

    setStatus(t('analyzing'), 'busy');
    els.runButton.disabled = true;
    els.results.innerHTML = `<div class="empty-state"><h3>${esc(t('working'))}</h3><p>${esc(t('working_long'))}</p></div>`;

    const form = new FormData();
    form.append('payload', JSON.stringify(payload));
    uploadFiles.forEach((f) => form.append('files[]', f));

    let response;
    try {
      response = await fetch('api/korrespond.php', { method: 'POST', body: form, credentials: 'same-origin' });
    } catch (err) {
      setStatus(t('network_error', { msg: err.message || err }), 'error');
      els.runButton.disabled = false;
      return;
    }

    if (!response.ok || !response.body) {
      if (response.status === 402 || response.status === 429) {
        const d = await response.json().catch(() => ({}));
        if (typeof window.dbnFreeTierError === 'function') window.dbnFreeTierError(response.status, d);
      } else {
        setStatus(t('request_failed', { status: response.status }), 'error');
      }
      els.runButton.disabled = false;
      return;
    }
    const creditsRemaining = response.headers.get('X-Credits-Remaining');
    if (creditsRemaining !== null && typeof window.dbnUpdateCredits === 'function') {
      window.dbnUpdateCredits(parseInt(creditsRemaining, 10));
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let buffer = '';
    let finalResult = null;
    let errorEvent = null;
    let clarifyEvent = null;

    while (true) {
      let chunk;
      try { chunk = await reader.read(); }
      catch (err) {
        setStatus(t('stream_error', { msg: err.message || err }), 'error');
        els.runButton.disabled = false;
        return;
      }
      const { done, value } = chunk;
      if (value) {
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop();
        for (const line of lines) {
          const trimmed = line.trim();
          if (!trimmed) continue;
          let evt; try { evt = JSON.parse(trimmed); } catch (_) { continue; }
          if (!evt || !evt.event) continue;
          if (evt.event === 'progress') { setStatus(evt.detail || t('working'), 'busy'); continue; }
          if (evt.event === 'start')    { setStatus(t('started', { body: evt.body, output: evt.output_type }), 'busy'); continue; }
          if (evt.event === 'classify') { lastClassify = evt.result; renderClassifySummary(evt.result); continue; }
          if (evt.event === 'retrieval'){ setStatus(t('fetched_sources', { n: evt.sources_count, acts: (evt.applicable_acts || []).join(', ') }), 'busy'); continue; }
          if (evt.event === 'clarify')  { clarifyEvent = evt; continue; }
          if (evt.event === 'final')    { finalResult = evt.result; continue; }
          if (evt.event === 'error')    { errorEvent = evt; continue; }
        }
      }
      if (done) break;
    }

    els.runButton.disabled = false;

    if (errorEvent) {
      setStatus(`${errorEvent.code}: ${errorEvent.message}`, 'error');
      return;
    }
    if (clarifyEvent) {
      setStatus(t('need_clarify'), 'busy');
      showClarify(clarifyEvent.questions);
      return;
    }
    if (!finalResult) {
      setStatus(t('stream_no_draft'), 'error');
      return;
    }

    setStatus(t('done_summary', { s: Math.round((finalResult.latency_ms || 0) / 1000), n: (finalResult.cited_law || []).length }), 'ok');
    lastFinal = finalResult;
    renderFinal(finalResult);
    pendingClarifications = {}; // reset for next run
  }

  // ── Pass 3: refine with jurisdiction-scoped formal citations ────────────────
  async function runRefine(jurisdiction) {
    if (!lastFinal || !lastClassify) {
      setStatus(t('no_draft_yet'), 'error');
      return;
    }
    const jurLabel = jurisdiction === 'echr' ? t('jur_echr')
                   : jurisdiction === 'both' ? t('jur_both')
                   : t('jur_norwegian');
    const refineBtn = document.getElementById('korrRefineBtn');
    if (refineBtn) { refineBtn.disabled = true; refineBtn.textContent = t('refine_btn_busy'); }
    setStatus(t('refining_status', { jur: jurLabel }), 'busy');

    const payload = {
      jurisdiction,
      language: lang,
      original_draft_no: lastFinal.draft_no || '',
      classify: lastClassify,
      intake: {
        recipient_body: lastFinal.recipient_body,
        output_type:    lastFinal.output_type,
        tone:           lastFinal.tone,
        goal:           lastFinal.goal,
      },
    };

    let response;
    try {
      response = await fetch('api/korrespond-refine.php', {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
    } catch (err) {
      setStatus(t('network_error', { msg: err.message || err }), 'error');
      if (refineBtn) { refineBtn.disabled = false; refineBtn.textContent = t('refine_btn'); }
      return;
    }

    if (!response.ok || !response.body) {
      if (response.status === 402 || response.status === 429) {
        const d = await response.json().catch(() => ({}));
        if (typeof window.dbnFreeTierError === 'function') window.dbnFreeTierError(response.status, d);
      } else {
        setStatus(t('refine_failed', { status: response.status }), 'error');
      }
      if (refineBtn) { refineBtn.disabled = false; refineBtn.textContent = t('refine_btn'); }
      return;
    }
    const creditsRemaining = response.headers.get('X-Credits-Remaining');
    if (creditsRemaining !== null && typeof window.dbnUpdateCredits === 'function') {
      window.dbnUpdateCredits(parseInt(creditsRemaining, 10));
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let buffer = '';
    let finalResult = null;
    let errorEvent = null;

    while (true) {
      let chunk;
      try { chunk = await reader.read(); }
      catch (err) {
        setStatus(t('stream_error', { msg: err.message || err }), 'error');
        if (refineBtn) { refineBtn.disabled = false; refineBtn.textContent = t('refine_btn'); }
        return;
      }
      const { done, value } = chunk;
      if (value) {
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop();
        for (const line of lines) {
          const trimmed = line.trim();
          if (!trimmed) continue;
          let evt; try { evt = JSON.parse(trimmed); } catch (_) { continue; }
          if (!evt || !evt.event) continue;
          if (evt.event === 'progress')  { setStatus(evt.detail || t('refining'), 'busy'); continue; }
          if (evt.event === 'start')     { setStatus(t('refining_short', { jur: jurLabel }), 'busy'); continue; }
          if (evt.event === 'retrieval') { setStatus(t('refine_fetched', { n: evt.sources_count, jur: jurLabel }), 'busy'); continue; }
          if (evt.event === 'final')     { finalResult = evt.result; continue; }
          if (evt.event === 'error')     { errorEvent = evt; continue; }
        }
      }
      if (done) break;
    }

    if (refineBtn) { refineBtn.disabled = false; refineBtn.textContent = t('refine_btn'); }

    if (errorEvent) {
      setStatus(`${errorEvent.code}: ${errorEvent.message}`, 'error');
      return;
    }
    if (!finalResult) {
      setStatus(t('refine_no_result'), 'error');
      return;
    }

    setStatus(t('refined_summary', { s: Math.round((finalResult.latency_ms || 0) / 1000), n: (finalResult.cited_law || []).length, jur: jurLabel }), 'ok');
    renderRefined(finalResult);
  }

  // ── Rendering ───────────────────────────────────────────────────────────────
  function renderClassifySummary(c) {
    if (!c || !c.summary) return;
    let block = els.results.querySelector('#korrClassifyBlock');
    if (!block) {
      block = document.createElement('div');
      block.id = 'korrClassifyBlock';
      block.className = 'dr-result-block';
      els.results.innerHTML = '';
      els.results.appendChild(block);
    }
    block.innerHTML = `
      <h3 style="margin:0 0 10px;font-size:0.95rem;color:var(--ink)">${esc(t('summary_pass1'))}</h3>
      <p>${esc(c.summary)}</p>
      ${c.applicable_acts && c.applicable_acts.length ? `<p class="upload-hint"><strong>${esc(t('assumed_legal'))}</strong> ${c.applicable_acts.map(esc).join(', ')}</p>` : ''}
      ${c.deadlines && c.deadlines.length ? `<p class="upload-hint"><strong>${esc(t('deadlines_label'))}</strong> ${c.deadlines.map(esc).join(', ')}</p>` : ''}
    `;
  }

  function renderFinal(data) {
    const userLang = data.draft_user_lang || 'en';
    const userLangLabel = LANG_LABELS[userLang] || userLang.toUpperCase();
    const flags = data.self_check || {};
    const cited = data.cited_law || [];

    const flagBadge = (key, label) => {
      const v = flags[key] || 'ok';
      const cls = v === 'ok' ? 'is-ok' : (v === 'warn' ? 'is-warn' : 'is-error');
      const icon = v === 'ok' ? '✓' : '!';
      return `<span class="korr-flag ${cls}">${icon} ${esc(label)}</span>`;
    };

    const draftNo = data.draft_no || '';
    const draftUser = data.draft_user || '';
    const isSameLang = userLang === 'no';

    els.results.innerHTML = `
      <div class="korr-result-head">
        <span class="tool-badge">${esc(data.recipient_body || '')} · ${esc(data.output_type || '')} · ${esc(data.tone || '')}</span>
        <div class="korr-flags">
          ${flagBadge('citations_verified', t('flag_cites'))}
          ${flagBadge('deadline_mentioned', t('flag_deadline'))}
          ${flagBadge('goal_addressed',     t('flag_goal'))}
          ${flagBadge('tone',                t('flag_tone'))}
        </div>
      </div>

      <div class="korr-drafts ${isSameLang ? 'is-single' : ''}">
        <div class="korr-draft-col">
          <div class="korr-draft-head">
            <h3>${esc(t('no_column'))} — ${esc(t('canonical'))}</h3>
            <div class="korr-draft-actions">
              <button type="button" class="secondary-button" data-copy="no">${esc(t('copy'))}</button>
              <button type="button" class="secondary-button" data-download="no">${esc(t('download_txt'))}</button>
            </div>
          </div>
          <pre class="korr-draft-body" id="korrDraftNo">${esc(draftNo)}</pre>
        </div>
        ${isSameLang ? '' : `
        <div class="korr-draft-col">
          <div class="korr-draft-head">
            <h3>${esc(userLangLabel)} — ${esc(t('reference'))}</h3>
            <div class="korr-draft-actions">
              <button type="button" class="secondary-button" data-copy="user">${esc(t('copy'))}</button>
              <button type="button" class="secondary-button" data-download="user">${esc(t('download_txt'))}</button>
            </div>
          </div>
          <pre class="korr-draft-body" id="korrDraftUser">${esc(draftUser)}</pre>
        </div>`}
      </div>

      ${cited.length ? `
      <details class="korr-cited" open>
        <summary><strong>${esc(t('cited_law_n', { n: cited.length }))}</strong> — ${esc(t('cited_law_hint'))}</summary>
        <div class="korr-cited-list">
          ${cited.map((s) => `
            <div class="korr-cited-item">
              <div class="korr-cited-head"><strong>[${s.n}] ${esc(s.title)}</strong>${s.section ? ' — ' + esc(s.section) : ''}</div>
              <p class="korr-cited-excerpt">${esc(s.excerpt || '')}</p>
              ${s.source_url ? `<a href="${esc(s.source_url)}" target="_blank" rel="noopener">${esc(t('view_source'))}</a>` : ''}
            </div>
          `).join('')}
        </div>
      </details>
      ` : `<p class="upload-hint"><em>${esc(t('no_cited'))}</em></p>`}

      ${data.disclaimer ? `<p class="upload-hint" style="margin-top:16px;font-style:italic">${esc(data.disclaimer)}</p>` : ''}

      ${(data.legal_check && data.legal_check.length) ? `
      <div class="korr-legal-check">
        <h4 class="korr-legal-check__title">⚖ Legal threshold check <small>(dbn-legal-agent-v3)</small></h4>
        ${data.legal_check.map((f) => `
          <div class="bvj-red-flag">
            <div class="bvj-red-flag__head">
              <div class="bvj-red-flag__desc">${esc(f.description || '')}</div>
              <span class="bvj-severity bvj-severity-${esc(f.severity || 'low')}">${esc(f.severity || 'low')}</span>
            </div>
            ${f.legal_basis ? `<span class="bvj-red-flag__legal">${esc(f.legal_basis)}</span>` : ''}
            ${f.what_to_check ? `<details class="bvj-red-flag__details"><summary>What to verify</summary><p class="bvj-red-flag__check">${esc(f.what_to_check)}</p></details>` : ''}
          </div>
        `).join('')}
      </div>
      ` : ''}

      <section class="korr-refine-panel" id="korrRefinePanel" aria-labelledby="korrRefineTitle">
        <h3 id="korrRefineTitle">${esc(t('refine_title'))} <small>${esc(t('one_extra_credit'))}</small></h3>
        <p class="upload-hint">${esc(t('refine_hint'))}</p>
        <div class="korr-refine-controls" role="radiogroup" aria-label="Jurisdiction">
          <label><input type="radio" name="korrJurisdiction" value="norwegian" checked> ${esc(t('jur_norwegian'))}</label>
          <label><input type="radio" name="korrJurisdiction" value="echr"> ${esc(t('jur_echr'))}</label>
          <label><input type="radio" name="korrJurisdiction" value="both"> ${esc(t('jur_both'))}</label>
          <button type="button" id="korrRefineBtn" class="primary-button">${esc(t('refine_btn'))}</button>
        </div>
        <div id="korrRefinedSlot"></div>
      </section>
    `;

    // Wire copy/download
    els.results.querySelectorAll('[data-copy]').forEach((btn) => {
      btn.addEventListener('click', () => {
        const target = btn.dataset.copy === 'no' ? draftNo : draftUser;
        navigator.clipboard?.writeText(target).then(
          () => { btn.textContent = t('copied'); setTimeout(() => btn.textContent = t('copy'), 1500); },
          () => { btn.textContent = 'Failed'; }
        );
      });
    });
    els.results.querySelectorAll('[data-download]').forEach((btn) => {
      btn.addEventListener('click', () => {
        const target = btn.dataset.download === 'no' ? draftNo : draftUser;
        const suffix = btn.dataset.download === 'no' ? 'no' : userLang;
        downloadText(`korrespond-${data.recipient_body}-${suffix}.txt`, target);
      });
    });

    // Wire refine
    const refineBtn = document.getElementById('korrRefineBtn');
    refineBtn?.addEventListener('click', () => {
      const choice = document.querySelector('input[name="korrJurisdiction"]:checked');
      runRefine(choice ? choice.value : 'norwegian');
    });
  }

  function renderRefined(data) {
    const slot = document.getElementById('korrRefinedSlot');
    if (!slot) return;
    const userLang = data.draft_user_lang || 'en';
    const userLangLabel = LANG_LABELS[userLang] || userLang.toUpperCase();
    const flags = data.self_check || {};
    const cited = data.cited_law || [];
    const isSameLang = userLang === 'no';
    const draftNo = data.draft_no || '';
    const draftUser = data.draft_user || '';
    const jurLabel = data.jurisdiction === 'echr' ? t('jur_echr')
                   : data.jurisdiction === 'both' ? t('jur_both')
                   : t('jur_norwegian');

    const flagBadge = (key, label) => {
      const v = flags[key] || 'ok';
      const cls = v === 'ok' ? 'is-ok' : (v === 'warn' ? 'is-warn' : 'is-error');
      const icon = v === 'ok' ? '✓' : '!';
      return `<span class="korr-flag ${cls}">${icon} ${esc(label)}</span>`;
    };

    slot.innerHTML = `
      <div class="korr-refined">
        <div class="korr-result-head">
          <span class="tool-badge">${esc(t('refined_label'))} · ${esc(jurLabel)}</span>
          <div class="korr-flags">
            ${flagBadge('citations_verified', t('flag_cites'))}
            ${flagBadge('deadline_mentioned', t('flag_deadline'))}
            ${flagBadge('goal_addressed',     t('flag_goal'))}
          </div>
        </div>

        <div class="korr-drafts ${isSameLang ? 'is-single' : ''}">
          <div class="korr-draft-col">
            <div class="korr-draft-head">
              <h3>${esc(t('no_column'))} — ${esc(t('refined_label'))}</h3>
              <div class="korr-draft-actions">
                <button type="button" class="secondary-button" data-rcopy="no">${esc(t('copy'))}</button>
                <button type="button" class="secondary-button" data-rdownload="no">${esc(t('download_txt'))}</button>
              </div>
            </div>
            <pre class="korr-draft-body">${esc(draftNo)}</pre>
          </div>
          ${isSameLang ? '' : `
          <div class="korr-draft-col">
            <div class="korr-draft-head">
              <h3>${esc(userLangLabel)} — ${esc(t('refined_label'))}</h3>
              <div class="korr-draft-actions">
                <button type="button" class="secondary-button" data-rcopy="user">${esc(t('copy'))}</button>
                <button type="button" class="secondary-button" data-rdownload="user">${esc(t('download_txt'))}</button>
              </div>
            </div>
            <pre class="korr-draft-body">${esc(draftUser)}</pre>
          </div>`}
        </div>

        ${cited.length ? `
        <details class="korr-cited" open>
          <summary><strong>${esc(t('cited_authorities_n', { n: cited.length }))}</strong> — ${esc(jurLabel)}</summary>
          <div class="korr-cited-list">
            ${cited.map((s) => `
              <div class="korr-cited-item">
                <div class="korr-cited-head"><strong>[${s.n}] ${esc(s.title)}</strong>${s.section ? ' — ' + esc(s.section) : ''}${s.authority_type ? ' <small>(' + esc(s.authority_type) + ')</small>' : ''}</div>
                <p class="korr-cited-excerpt">${esc(s.excerpt || '')}</p>
                ${s.source_url ? `<a href="${esc(s.source_url)}" target="_blank" rel="noopener">${esc(t('view_source'))}</a>` : ''}
              </div>
            `).join('')}
          </div>
        </details>` : ''}

        ${(data.legal_check && data.legal_check.length) ? `
        <div class="korr-legal-check">
          <h4 class="korr-legal-check__title">⚖ Legal threshold check <small>(dbn-legal-agent-v3)</small></h4>
          ${data.legal_check.map((f) => `
            <div class="bvj-red-flag">
              <div class="bvj-red-flag__head">
                <div class="bvj-red-flag__desc">${esc(f.description || '')}</div>
                <span class="bvj-severity bvj-severity-${esc(f.severity || 'low')}">${esc(f.severity || 'low')}</span>
              </div>
              ${f.legal_basis ? `<span class="bvj-red-flag__legal">${esc(f.legal_basis)}</span>` : ''}
              ${f.what_to_check ? `<details class="bvj-red-flag__details"><summary>What to verify</summary><p class="bvj-red-flag__check">${esc(f.what_to_check)}</p></details>` : ''}
            </div>
          `).join('')}
        </div>
        ` : ''}
      </div>
    `;

    slot.querySelectorAll('[data-rcopy]').forEach((btn) => {
      btn.addEventListener('click', () => {
        const target = btn.dataset.rcopy === 'no' ? draftNo : draftUser;
        navigator.clipboard?.writeText(target).then(
          () => { btn.textContent = t('copied'); setTimeout(() => btn.textContent = t('copy'), 1500); },
          () => { btn.textContent = 'Failed'; }
        );
      });
    });
    slot.querySelectorAll('[data-rdownload]').forEach((btn) => {
      btn.addEventListener('click', () => {
        const target = btn.dataset.rdownload === 'no' ? draftNo : draftUser;
        const suffix = btn.dataset.rdownload === 'no' ? 'no' : userLang;
        downloadText(`korrespond-refined-${data.recipient_body}-${data.jurisdiction}-${suffix}.txt`, target);
      });
    });

    slot.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }

  // ── utils ───────────────────────────────────────────────────────────────────
  function setStatus(message, kind) {
    if (!els.status) return;
    els.status.textContent = message || '';
    els.status.dataset.kind = kind || '';
  }

  function esc(s) {
    return String(s == null ? '' : s)
      .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
  }

  function downloadText(filename, text) {
    const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = filename;
    document.body.appendChild(a); a.click(); a.remove();
    URL.revokeObjectURL(url);
  }
})();
