'use strict';

window.Feedback = (() => {
  const state = {
    modal: null,
    draft: null,
    openContext: {},
    saveTimer: null,
    triageTimer: null,
    saving: false,
    triaging: false,
    submitting: false,
    lastTriageFingerprint: '',
  };

  function ctmState() {
    return window._ctmState || {};
  }

  function tokenParam() {
    const token = ctmState().token || '';
    return token ? `token=${encodeURIComponent(token)}` : '';
  }

  function api(path) {
    const join = path.includes('?') ? '&' : '?';
    const token = tokenParam();
    return token ? `${path}${join}${token}` : path;
  }

  function esc(value) {
    return String(value || '').replace(/[&<>"']/g, (ch) => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;',
    }[ch]));
  }

  function showToast(message, opts) {
    if (typeof window.toast === 'function') window.toast(message, opts || {});
    else console.log('[feedback]', message);
  }

  async function requestJson(path, options = {}) {
    const res = await fetch(api(path), {
      ...options,
      headers: {
        ...(options.body ? { 'Content-Type': 'application/json' } : {}),
        ...(options.headers || {}),
      },
    });
    const data = await res.json().catch(() => ({}));
    if (!res.ok || data.ok === false) throw new Error(data.error || `HTTP ${res.status}`);
    return data;
  }

  function sessionContext(sessionId) {
    const appState = ctmState();
    const id = sessionId || (appState.sessions && appState.sessions.has(appState.activeTab) ? appState.activeTab : '');
    const s = id && appState.sessions ? appState.sessions.get(id) : null;
    const meta = s && s.meta ? s.meta : {};
    return {
      ctm_session_id: id || '',
      agent_session_id: meta.agentSessionId || meta.agentSessionToken || meta.claudeSessionId || '',
      project_path: meta.cwd || meta.project_path || '',
      git_branch: meta.branch || meta.git_branch || '',
      model_provider: meta.model_provider || '',
      model_id: meta.model_id || '',
      session_label: meta.label || id || '',
    };
  }

  function typeLabel(type) {
    return ({
      bug: 'Bug',
      confusing_ux: 'UX',
      wrong_answer: 'Answer',
      feature_request: 'Idea',
      performance: 'Perf',
      crash: 'Crash',
      other: 'Other',
    })[type] || 'Bug';
  }

  function renderShell() {
    if (state.modal) return state.modal;
    const modal = document.createElement('div');
    modal.id = 'feedback-modal';
    modal.setAttribute('role', 'dialog');
    modal.setAttribute('aria-modal', 'true');
    modal.setAttribute('aria-label', 'Send feedback');
    modal.innerHTML = `
      <div class="feedback-shell">
        <section class="feedback-main">
          <div class="feedback-header">
            <div class="feedback-title">Send feedback</div>
            <span class="feedback-status-pill" id="feedback-status">Draft</span>
            <button class="feedback-icon-btn" type="button" data-action="close" title="Close feedback">x</button>
          </div>
          <div class="feedback-form">
            <div class="feedback-row">
              <input class="feedback-title-input" id="feedback-title-input" placeholder="What should we fix?" autocomplete="off" spellcheck="true">
              <label class="feedback-field">
                <span>Impact</span>
                <select class="feedback-select" id="feedback-severity" title="Impact">
                  <option value="medium">Medium</option>
                  <option value="low">Low</option>
                  <option value="high">High</option>
                  <option value="blocking">Blocking</option>
                </select>
              </label>
              <label class="feedback-field feedback-destination-field">
                <span>Destination</span>
                <select class="feedback-select" id="feedback-consent" title="Destination">
                  <option value="maintainer_report">Send to CTM maintainers</option>
                  <option value="diagnostic_bundle">Send with selected evidence</option>
                  <option value="local_only">Save locally only</option>
                  <option value="local_walle">Remember in local Wall-E</option>
                </select>
              </label>
            </div>
            <div class="feedback-destination-note" id="feedback-destination-note"></div>
            <div class="feedback-segmented" id="feedback-types"></div>
            <div class="feedback-editor-wrap">
              <div class="feedback-toolbar">
                <button class="feedback-icon-btn" type="button" data-cmd="bold" title="Bold"><b>B</b></button>
                <button class="feedback-icon-btn" type="button" data-cmd="italic" title="Italic"><i>I</i></button>
                <button class="feedback-icon-btn" type="button" data-cmd="insertUnorderedList" title="Bulleted list">&#8226;</button>
                <button class="feedback-icon-btn" type="button" data-action="attach-file" title="Attach image">+</button>
                <button class="feedback-icon-btn" type="button" data-action="screenshot" title="Capture and annotate screenshot">&#128248;</button>
                <span class="feedback-note" id="feedback-save-state" style="margin-left:auto">Autosaved locally</span>
              </div>
              <div id="feedback-editor" class="feedback-editor" contenteditable="true" role="textbox" aria-multiline="true" aria-label="Feedback details" spellcheck="true" data-placeholder="Describe what happened, what you expected, and any steps to reproduce. Paste images here, or use the screenshot button."></div>
              <input id="feedback-file-input" type="file" accept="image/*" style="display:none">
            </div>
            <div class="feedback-attachments" id="feedback-attachments"></div>
          </div>
          <div class="feedback-submit-row">
            <button class="feedback-btn primary" type="button" data-action="submit" id="feedback-submit-btn">Send feedback</button>
            <span class="feedback-note" id="feedback-submit-note">Reviewed automatically. You can change the selected evidence below.</span>
          </div>
        </section>
        <aside class="feedback-side">
          <div class="feedback-side-section feedback-review-section">
            <div class="feedback-review-heading">
              <h3>Quality check</h3>
              <span id="feedback-review-state" class="feedback-review-state">Automatic</span>
            </div>
            <div class="feedback-triage-log" id="feedback-triage-log"></div>
          </div>
          <div class="feedback-side-section">
            <h3>Where this goes</h3>
            <div class="feedback-context-line" id="feedback-route-summary"></div>
            <div class="feedback-context-line muted" id="feedback-route-detail"></div>
          </div>
          <details class="feedback-side-section feedback-side-details" open>
            <summary>Linked session</summary>
            <div id="feedback-context"></div>
          </details>
          <details class="feedback-side-section feedback-side-details">
            <summary>Selected evidence <span id="feedback-evidence-count"></span></summary>
            <div class="feedback-evidence-list" id="feedback-evidence"></div>
          </details>
        </aside>
      </div>
    `;
    document.body.appendChild(modal);
    state.modal = modal;
    wireModal(modal);
    return modal;
  }

  function wireModal(modal) {
    const types = [
      'bug',
      'confusing_ux',
      'wrong_answer',
      'feature_request',
      'performance',
    ];
    const typeWrap = modal.querySelector('#feedback-types');
    typeWrap.innerHTML = types.map((type) =>
      `<button class="feedback-type-btn" type="button" data-type="${type}">${typeLabel(type)}</button>`
    ).join('');

    modal.querySelector('[data-action="close"]').addEventListener('click', close);
    modal.addEventListener('click', (e) => {
      if (e.target === modal) close();
    });
    modal.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') close();
      if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') submit();
    });
    modal.querySelectorAll('[data-cmd]').forEach((btn) => {
      btn.addEventListener('click', () => {
        document.execCommand(btn.dataset.cmd, false, null);
        modal.querySelector('#feedback-editor').focus();
        scheduleSave();
      });
    });
    typeWrap.addEventListener('click', (e) => {
      const btn = e.target.closest('[data-type]');
      if (!btn) return;
      state.draft.type = btn.dataset.type;
      renderTypeButtons();
      scheduleSave();
    });
    modal.querySelector('#feedback-title-input').addEventListener('input', scheduleSave);
    modal.querySelector('#feedback-severity').addEventListener('change', scheduleSave);
    modal.querySelector('#feedback-consent').addEventListener('change', () => {
      renderDestination();
      scheduleSave();
    });
    const editor = modal.querySelector('#feedback-editor');
    editor.addEventListener('input', scheduleSave);
    editor.addEventListener('paste', onEditorPaste);
    editor.addEventListener('drop', onEditorDrop);
    editor.addEventListener('dragover', (e) => e.preventDefault());
    modal.querySelector('[data-action="attach-file"]').addEventListener('click', () => {
      modal.querySelector('#feedback-file-input').click();
    });
    modal.querySelector('#feedback-file-input').addEventListener('change', async (e) => {
      if (e.target.files && e.target.files[0]) await attachImage(e.target.files[0], e.target.files[0].name);
      e.target.value = '';
    });
    modal.querySelector('[data-action="screenshot"]').addEventListener('click', captureScreenshot);
    modal.querySelector('[data-action="submit"]').addEventListener('click', submit);
  }

  function open(options = {}) {
    renderShell();
    state.openContext = {
      ...sessionContext(options.sessionId),
      source: options.source || (options.sessionId ? 'session_toolbar' : 'global'),
      surface: options.surface || 'ctm',
    };
    state.draft = {
      type: options.type || 'bug',
      severity: options.severity || 'medium',
      consent_level: 'maintainer_report',
      title: options.title || '',
      description_text: '',
      description_html: '',
      attachments: [],
      context_items: [],
      clarification_questions: [],
      triage_result: {},
    };
    fillForm();
    state.modal.classList.add('active');
    ensureDraft().catch((err) => showToast('Feedback draft failed: ' + err.message, { type: 'error' }));
    setTimeout(() => state.modal.querySelector('#feedback-editor').focus(), 60);
  }

  function close() {
    if (!state.modal) return;
    state.modal.classList.remove('active');
  }

  function fillForm() {
    const modal = state.modal;
    modal.querySelector('#feedback-title-input').value = state.draft.title || '';
    modal.querySelector('#feedback-severity').value = state.draft.severity || 'medium';
    modal.querySelector('#feedback-consent').value = state.draft.consent_level || 'maintainer_report';
    modal.querySelector('#feedback-editor').innerHTML = state.draft.description_html || '';
    renderTypeButtons();
    renderContext();
    renderDestination();
    renderAttachments();
    renderEvidence();
    renderTriage();
  }

  function readForm() {
    const modal = state.modal;
    const editor = modal.querySelector('#feedback-editor');
    return {
      ...state.openContext,
      type: state.draft.type || 'bug',
      title: modal.querySelector('#feedback-title-input').value.trim(),
      severity: modal.querySelector('#feedback-severity').value,
      consent_level: modal.querySelector('#feedback-consent').value,
      description_text: editor.innerText.trim(),
      description_html: editor.innerHTML,
    };
  }

  function renderTypeButtons() {
    if (!state.modal) return;
    state.modal.querySelectorAll('.feedback-type-btn').forEach((btn) => {
      btn.classList.toggle('active', btn.dataset.type === (state.draft.type || 'bug'));
    });
  }

  function renderContext() {
    if (!state.modal) return;
    const ctx = state.openContext || {};
    const el = state.modal.querySelector('#feedback-context');
    if (!ctx.ctm_session_id) {
      el.innerHTML = '<div class="feedback-context-line muted">No session linked. The feedback will still be saved locally.</div>';
      return;
    }
    el.innerHTML = `
      <div class="feedback-context-line">${esc(ctx.session_label || ctx.ctm_session_id)}</div>
      <div class="feedback-context-line muted">${esc(ctx.ctm_session_id)}</div>
      ${ctx.project_path ? `<div class="feedback-context-line muted">${esc(ctx.project_path)}</div>` : ''}
      ${ctx.git_branch ? `<div class="feedback-context-line muted">${esc(ctx.git_branch)}</div>` : ''}
      ${ctx.model_id ? `<div class="feedback-context-line muted">${esc([ctx.model_provider, ctx.model_id].filter(Boolean).join(' / '))}</div>` : ''}
    `;
  }

  function destinationCopy(consent) {
    if (consent === 'diagnostic_bundle') {
      return {
        route: 'Sent to CTM maintainers with selected evidence previews.',
        detail: 'Uploads the report, session metadata, selected evidence previews, and attachment metadata to the Fly.io feedback endpoint.',
        submit: 'Send feedback + evidence',
        note: 'Use this when the selected context is necessary to reproduce the problem.',
      };
    }
    if (consent === 'local_walle') {
      return {
        route: 'Saved in CTM and written to local Wall-E only.',
        detail: 'No Fly.io upload. The report becomes a local Wall-E memory for follow-up work on this machine.',
        submit: 'Save + remember locally',
        note: 'Use this for personal notes or feedback that should not leave this device.',
      };
    }
    if (consent === 'local_only') {
      return {
        route: 'Saved in CTM on this machine only.',
        detail: 'No Fly.io upload and no Wall-E memory handoff.',
        submit: 'Save locally',
        note: 'Private local draft/submission. Maintainers cannot retrieve it with telemetry.',
      };
    }
    return {
      route: 'Sent to CTM maintainers through Fly.io telemetry.',
      detail: 'Uploads the report, quality check, session metadata, and evidence manifest. Transcript/scrollback previews stay local unless you choose evidence upload.',
      submit: 'Send feedback',
      note: 'Maintainers can retrieve this report with the telemetry skill. Sensitive context previews are excluded by default.',
    };
  }

  function isRemoteDestination(consent) {
    return consent === 'maintainer_report' || consent === 'diagnostic_bundle' || consent === 'anonymous_aggregate';
  }

  function isWalleDestination(consent) {
    return consent === 'local_walle';
  }

  function submitToast(copy, data, consent) {
    if (isRemoteDestination(consent)) {
      showToast(data.remote?.ok ? 'Feedback sent to CTM maintainers' : 'Feedback saved locally; remote send did not complete', {
        type: data.remote?.ok ? 'success' : 'warning',
      });
      return;
    }
    if (isWalleDestination(consent)) {
      showToast(data.walle?.ok ? 'Feedback saved in CTM and local Wall-E' : 'Feedback saved in CTM; local Wall-E handoff did not complete', {
        type: data.walle?.ok ? 'success' : 'warning',
      });
      return;
    }
    showToast(copy.route, { type: 'success' });
  }

  function legacyDestination(consent) {
    return consent === 'anonymous_aggregate' ? 'maintainer_report' : consent;
  }

  function currentDestination() {
    return legacyDestination(state.modal?.querySelector('#feedback-consent')?.value || state.draft?.consent_level || 'maintainer_report');
  }

  function routeSummary(consent) {
    return destinationCopy(legacyDestination(consent));
  }

  function renderDestination() {
    if (!state.modal) return;
    const consent = currentDestination();
    const copy = routeSummary(consent);
    const route = state.modal.querySelector('#feedback-route-summary');
    const detail = state.modal.querySelector('#feedback-route-detail');
    const note = state.modal.querySelector('#feedback-destination-note');
    const submitBtn = state.modal.querySelector('#feedback-submit-btn');
    if (route) route.textContent = copy.route;
    if (detail) detail.textContent = copy.detail;
    if (note) note.textContent = copy.note;
    if (submitBtn) submitBtn.textContent = copy.submit;
  }

  function renderAttachments() {
    if (!state.modal) return;
    const attachments = state.draft.attachments || [];
    const wrap = state.modal.querySelector('#feedback-attachments');
    wrap.innerHTML = attachments.map((a) => {
      const filename = a.filename || '';
      const imgSrc = filename ? `/api/images/file/${encodeURIComponent(filename)}?${tokenParam()}` : '';
      return `
        <div class="feedback-attachment">
          ${imgSrc ? `<img src="${imgSrc}" alt="">` : ''}
          <span>${esc(a.kind || 'image')}</span>
        </div>
      `;
    }).join('');
  }

  function renderEvidence() {
    if (!state.modal) return;
    const items = state.draft.context_items || [];
    const wrap = state.modal.querySelector('#feedback-evidence');
    const count = state.modal.querySelector('#feedback-evidence-count');
    const includedCount = items.filter((item) => item.include_state !== 'excluded').length;
    if (count) count.textContent = items.length ? `(${includedCount}/${items.length})` : '';
    if (!items.length) {
      wrap.innerHTML = '<div class="feedback-context-line muted">No evidence gathered yet. Link a session or add a screenshot.</div>';
      return;
    }
    wrap.innerHTML = items.map((item) => {
      const checked = item.include_state !== 'excluded';
      return `
        <label class="feedback-evidence-item">
          <input type="checkbox" data-context-id="${esc(item.id)}" ${checked ? 'checked' : ''}>
          <span>
            <span class="feedback-evidence-title">${esc(item.label)}</span>
            <span class="feedback-evidence-meta">${esc(item.kind)} · ~${Number(item.token_estimate || 0)} tokens</span>
          </span>
          <span class="feedback-sensitivity">${esc(item.sensitivity || 'medium')}</span>
        </label>
      `;
    }).join('');
    wrap.querySelectorAll('[data-context-id]').forEach((input) => {
      input.addEventListener('change', () => saveContextSelection());
    });
  }

  function renderTriage() {
    if (!state.modal) return;
    const status = state.draft.status || 'draft';
    const pill = state.modal.querySelector('#feedback-status');
    const triage = state.draft.triage_result || {};
    const statusLabels = {
      draft: 'auto review',
      needs_clarification: 'needs more info',
      actionable: 'ready',
      evidence_ready: 'ready with evidence',
      submitted: 'saved',
    };
    pill.textContent = statusLabels[status] || status.replace(/_/g, ' ');
    pill.classList.toggle('ready', status === 'actionable' || status === 'evidence_ready' || status === 'submitted');
    pill.classList.toggle('needs', status === 'needs_clarification');
    const reviewState = state.modal.querySelector('#feedback-review-state');
    if (reviewState) {
      reviewState.textContent = state.triaging ? 'Reviewing...' : (statusLabels[status] || 'Automatic');
      reviewState.classList.toggle('ready', status === 'actionable' || status === 'evidence_ready' || status === 'submitted');
      reviewState.classList.toggle('needs', status === 'needs_clarification');
    }
    const log = state.modal.querySelector('#feedback-triage-log');
    const questions = state.draft.clarification_questions || [];
    const actionable = Boolean(triage.actionable);
    const summary = state.draft.triage_summary || (actionable ? 'This looks actionable.' : 'Write a short description to start triage.');
    const visibleSummary = status === 'needs_clarification'
      ? 'Add a bit more context in the description so this is easier to act on.'
      : summary;
    const submitNote = state.modal.querySelector('#feedback-submit-note');
    if (submitNote) {
      submitNote.textContent = status === 'needs_clarification'
        ? 'Add the missing detail in the description if you can. You can still save this feedback.'
        : 'Reviewed automatically. You can change the selected evidence below.';
    }
    log.innerHTML = `
      <div class="feedback-agent-msg ${actionable ? '' : 'warn'}">${esc(visibleSummary)}</div>
      ${questions.length ? '<div class="feedback-review-label">Helpful details to include</div>' : ''}
      ${questions.slice(0, 3).map((q) => `<div class="feedback-agent-msg warn question">${esc(q)}</div>`).join('')}
    `;
  }

  function setSaveState(text) {
    const el = state.modal && state.modal.querySelector('#feedback-save-state');
    if (el) el.textContent = text;
  }

  async function ensureDraft() {
    if (state.draft && state.draft.id) return state.draft;
    const payload = readForm();
    const data = await requestJson('/api/feedback/drafts', {
      method: 'POST',
      body: JSON.stringify(payload),
    });
    state.draft = data.feedback;
    renderAll();
    return state.draft;
  }

  function scheduleSave() {
    if (!state.draft) return;
    setSaveState('Saving...');
    clearTimeout(state.saveTimer);
    state.saveTimer = setTimeout(saveDraft, 450);
  }

  async function saveDraft(options = {}) {
    if (!state.draft || state.saving) return;
    state.saving = true;
    try {
      await ensureDraft();
      const payload = readForm();
      const data = await requestJson(`/api/feedback/${encodeURIComponent(state.draft.id)}`, {
        method: 'PUT',
        body: JSON.stringify(payload),
      });
      state.draft = data.feedback;
      setSaveState('Saved');
      renderTypeButtons();
      renderDestination();
      if (!options.skipAutoReview) scheduleAutoReview();
    } catch (err) {
      setSaveState('Save failed');
      console.error('[feedback] save failed', err);
    } finally {
      state.saving = false;
    }
  }

  function renderAll() {
    renderContext();
    renderDestination();
    renderAttachments();
    renderEvidence();
    renderTriage();
    renderTypeButtons();
  }

  function triageFingerprint() {
    const form = state.modal ? readForm() : {};
    const included = (state.draft.context_items || []).filter((item) => item.include_state !== 'excluded').length;
    return JSON.stringify({
      title: form.title || '',
      description_text: form.description_text || '',
      type: form.type || '',
      severity: form.severity || '',
      attachments: (state.draft.attachments || []).length,
      included,
    });
  }

  function scheduleAutoReview(delay = 650) {
    clearTimeout(state.triageTimer);
    state.triageTimer = setTimeout(() => runTriage({ auto: true }).catch((err) => {
      console.error('[feedback] auto review failed', err);
    }), delay);
  }

  async function runTriage(options = {}) {
    if (state.triaging) return;
    const fingerprint = triageFingerprint();
    if (options.auto && fingerprint === state.lastTriageFingerprint) return;
    clearTimeout(state.triageTimer);
    state.triaging = true;
    renderTriage();
    try {
      await saveDraft({ skipAutoReview: true });
      if (!state.draft?.id) return;
      setSaveState('Reviewing...');
      const data = await requestJson(`/api/feedback/${encodeURIComponent(state.draft.id)}/triage`, {
        method: 'POST',
        body: JSON.stringify(readForm()),
      });
      state.draft = data.feedback;
      state.lastTriageFingerprint = triageFingerprint();
      setSaveState('Reviewed');
    } finally {
      state.triaging = false;
      renderAll();
    }
  }

  async function saveContextSelection() {
    if (!state.draft?.id) return;
    const items = Array.from(state.modal.querySelectorAll('[data-context-id]')).map((input) => ({
      id: input.dataset.contextId,
      include_state: input.checked ? 'included' : 'excluded',
    }));
    const data = await requestJson(`/api/feedback/${encodeURIComponent(state.draft.id)}/context-items`, {
      method: 'POST',
      body: JSON.stringify({ items }),
    });
    state.draft = data.feedback;
    renderEvidence();
    scheduleAutoReview(200);
    renderTriage();
  }

  async function attachImage(fileOrBlob, filename, redacted) {
    await ensureDraft();
    clearTimeout(state.saveTimer);
    state.saveTimer = null;
    await saveDraft();
    const filenameSafe = filename || `feedback-${Date.now()}.png`;
    setSaveState('Uploading image...');
    const normalized = window.CTMImage && typeof window.CTMImage.normalizeForProvider === 'function'
      ? await window.CTMImage.normalizeForProvider(fileOrBlob, { filename: filenameSafe })
      : { blob: fileOrBlob, filename: filenameSafe, mimeType: fileOrBlob.type || 'image/png' };
    const uploadFilename = normalized.filename || filenameSafe;
    const uploadBlob = normalized.blob || fileOrBlob;
    const res = await fetch(api(`/api/images/upload?prompt_id=0&filename=${encodeURIComponent(uploadFilename)}`), {
      method: 'POST',
      headers: { 'Content-Type': normalized.mimeType || fileOrBlob.type || 'image/png' },
      body: uploadBlob,
    });
    const image = await res.json();
    if (!res.ok || !image.id) throw new Error(image.error || 'Image upload failed');
    const data = await requestJson(`/api/feedback/${encodeURIComponent(state.draft.id)}/attachments`, {
      method: 'POST',
      body: JSON.stringify({
        image_id: image.id,
        kind: 'screenshot',
        filename: image.filename || uploadFilename,
        mime_type: normalized.mimeType || fileOrBlob.type || 'image/png',
        redacted: Boolean(redacted),
      }),
    });
    state.draft = data.feedback;
    renderAttachments();
    renderEvidence();
    renderTriage();
    setSaveState('Image attached');
  }

  async function onEditorPaste(e) {
    const items = Array.from(e.clipboardData?.items || []);
    const imageItem = items.find((item) => item.type && item.type.startsWith('image/'));
    if (!imageItem) return;
    e.preventDefault();
    try {
      await attachImage(imageItem.getAsFile(), `paste-${Date.now()}.png`, false);
    } catch (err) {
      showToast('Image paste failed: ' + err.message, { type: 'error' });
    }
  }

  async function onEditorDrop(e) {
    const files = Array.from(e.dataTransfer?.files || []).filter((file) => file.type.startsWith('image/'));
    if (!files.length) return;
    e.preventDefault();
    for (const file of files) {
      try {
        await attachImage(file, file.name, false);
      } catch (err) {
        showToast('Image upload failed: ' + err.message, { type: 'error' });
      }
    }
  }

  async function captureScreenshot() {
    if (window.Screenshot && typeof window.Screenshot.start === 'function') {
      window.Screenshot.start({
        attachLabel: 'Attach',
        onAttach: async (blob) => {
          await attachImage(blob, `feedback-screenshot-${Date.now()}.png`, true);
          showToast('Screenshot attached', { type: 'success' });
        },
      });
      return;
    }
    showToast('Screenshot tool is unavailable', { type: 'error' });
  }

  async function submit() {
    if (state.submitting) return;
    state.submitting = true;
    try {
      await saveDraft();
      await saveContextSelection();
      await runTriage();
      const payload = readForm();
      payload.consent_level = legacyDestination(payload.consent_level);
      const copy = routeSummary(payload.consent_level);
      payload.send_to_remote = isRemoteDestination(payload.consent_level);
      payload.send_to_walle = isWalleDestination(payload.consent_level);
      const data = await requestJson(`/api/feedback/${encodeURIComponent(state.draft.id)}/submit`, {
        method: 'POST',
        body: JSON.stringify(payload),
      });
      state.draft = data.feedback;
      renderAll();
      submitToast(copy, data, payload.consent_level);
      close();
    } catch (err) {
      showToast('Feedback submit failed: ' + err.message, { type: 'error' });
    } finally {
      state.submitting = false;
    }
  }

  function init() {
    renderShell();
    document.addEventListener('keydown', (e) => {
      if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'f') {
        e.preventDefault();
        open({ source: 'keyboard', surface: 'global' });
      }
    });
  }

  return {
    init,
    open,
    close,
    attachImage,
    _state: state,
  };
})();

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => window.Feedback.init());
} else {
  window.Feedback.init();
}
