const ACTIONS = ['newfile','rewritefile','replacestring','replaceall','replaceblock','replacefunction','insertbefore','insertafter'];
const FILE_ICONS = ['assets/extf1.png','assets/extf2.png','assets/extf3.png'];

const state = {
  tabId: null,
  settings: null,
  cards: [],
  selectedIndex: 0,
  isSending: false,
  sendLoop: null,
  readyExpanded: false,
  projectData: null,
  allFilePaths: [],
  lastCardCount: -1,      // tracks changes for update indicator
};

init();

async function init() {
  buildMonitorBars();
  wireTransport();
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  state.tabId = tab?.id;
  // Restore cached project data if available
  const stored = await chrome.storage.local.get(['projectData', 'allFilePaths']);
  if (stored.projectData) {
    state.projectData = stored.projectData;
    state.allFilePaths = stored.allFilePaths || flattenFilePaths(stored.projectData.roots || []);
    renderProjectBar(stored.projectData);
  }
  await refresh();
  setInterval(refresh, 1500);
}

/* ── WIRING ── */
function wireTransport() {
  document.getElementById('prev-btn').addEventListener('click', () => moveSelection(-1));
  document.getElementById('next-btn').addEventListener('click', () => moveSelection(1));
  document.getElementById('arm-send-btn').addEventListener('click', () => sendSelected());
  document.getElementById('send-selected').addEventListener('click', () => sendSelected());
  document.getElementById('save-settings').addEventListener('click', saveSettings);
  document.getElementById('clear-btn').addEventListener('click', clearAll);
  document.getElementById('refresh-tree-btn').addEventListener('click', refreshTree);

  document.getElementById('ready-toggle').addEventListener('click', () => {
    state.readyExpanded = !state.readyExpanded;
    const readyCards = document.getElementById('ready-cards');
    const toggle = document.getElementById('ready-toggle').querySelector('.Label-Fo');
    readyCards.classList.toggle('collapsed', !state.readyExpanded);
    toggle.textContent = (state.readyExpanded ? '\u25BC' : '\u25BA') + ' READY TO RECORD';
  });

  document.getElementById('evidence-close').addEventListener('click', closeEvidenceOverlay);
  document.getElementById('evidence-overlay').addEventListener('click', (e) => {
    if (e.target === e.currentTarget) closeEvidenceOverlay();
  });

  document.getElementById('ai-ref-btn').addEventListener('click', () => {
    document.getElementById('airef-copied').style.display = 'none';
    document.getElementById('airef-overlay').classList.remove('hidden');
  });
  document.getElementById('airef-close').addEventListener('click', () => {
    document.getElementById('airef-overlay').classList.add('hidden');
  });
  document.getElementById('airef-overlay').addEventListener('click', (e) => {
    if (e.target === e.currentTarget) document.getElementById('airef-overlay').classList.add('hidden');
  });
  document.getElementById('airef-copy-btn').addEventListener('click', copyAiRef);
}

/* ── REFRESH ── */
async function refresh() {
  if (typeof state.tabId !== 'number') return;
  const response = await chrome.runtime.sendMessage({ type: 'NGO_GET_TAB_STATE', tabId: state.tabId });
  if (!response?.ok) return;
  state.settings = response.settings;
  const incoming = Array.isArray(response.state?.candidates) ? response.state.candidates : [];
  const prior = state.cards;
  state.cards = incoming.map((card, index) => hydrateCard(card, prior[index], index));
  if (!state.cards.length) state.selectedIndex = 0;
  else if (state.selectedIndex > state.cards.length - 1) state.selectedIndex = state.cards.length - 1;
  const active = document.activeElement;
  const isEditingConnection =
    active?.id === 'host' ||
    active?.id === 'port';

  const isEditingCardField =
    active?.classList?.contains('file-input') ||
    active?.classList?.contains('action-select');

  if (!isEditingConnection) {
    renderSettings();
  }
  if (!isEditingCardField) {
    renderCards();
  }
}

function clearAll() {
  state.cards = [];
  state.selectedIndex = 0;
  renderCards();
}

/* ── SETTINGS / CONNECTION ── */
function renderSettings() {
  document.getElementById('host').value = state.settings?.host || '127.0.0.1';
  document.getElementById('port').value = state.settings?.port || '5054';
}

async function saveSettings() {
  const host = document.getElementById('host').value.trim();
  const port = document.getElementById('port').value.trim();
  const settings = { host, port, path: '/record' };
  const response = await chrome.runtime.sendMessage({ type: 'NGO_SAVE_SETTINGS', settings });
  if (response?.ok) {
    setStatus('Connection saved — fetching project…', true);
    await fetchProjectTree(host, port);
  } else {
    setStatus(response?.error || 'Save failed', false);
  }
  await refresh();
}

async function refreshTree() {
  const btn = document.getElementById('refresh-tree-btn');
  const host = document.getElementById('host').value.trim();
  const port = document.getElementById('port').value.trim();
  if (!host || !port) { setStatus('Enter host and port first', false); return; }
  btn.classList.add('spinning');
  setStatus('Fetching project tree…', true);
  await fetchProjectTree(host, port);
  btn.classList.remove('spinning');
}

/* ── PROJECT TREE FETCH ── */
async function fetchProjectTree(host, port) {
  try {
    const endpoint = `http://${host}:${port}/read/tree`;
    const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) });
    if (!res.ok) {
      setStatus(`Connected — no project tree (${res.status})`, true);
      clearProjectData();
      return;
    }
    const data = await res.json();
    if (!data?.ok) {
      setStatus('Connected — project tree error', false);
      clearProjectData();
      return;
    }
    state.projectData = data;
    state.allFilePaths = flattenFilePaths(data.roots || []);
    // Persist into chrome storage so background can access if needed
    await chrome.storage.local.set({ projectData: data, allFilePaths: state.allFilePaths });
    chrome.runtime.sendMessage({ type: 'NGO_SAVE_PROJECT_JSON', data });
    renderProjectBar(data);
    setStatus(`Project loaded — ${state.allFilePaths.length} files`, true);
  } catch (err) {
    setStatus('Connected — project fetch failed', false);
    clearProjectData();
  }
}

function clearProjectData() {
  state.projectData = null;
  state.allFilePaths = [];
  chrome.storage.local.remove(['projectData', 'allFilePaths']);
  document.getElementById('project-name').style.display = 'none';
}

function renderProjectBar(data) {
  const nameEl = document.getElementById('project-name');
  nameEl.textContent = data.activeProject || 'Project loaded';
  nameEl.style.display = 'inline';
}

function flattenFilePaths(nodes) {
  const paths = [];
  function walk(nodeList) {
    for (const node of nodeList) {
      if (node.type === 'file') paths.push(node.path);
      if (node.children?.length) walk(node.children);
    }
  }
  walk(nodes);
  return paths;
}

/* ── AI REF COPY ── */
async function copyAiRef() {
  const wantPaths    = document.getElementById('airef-check-paths').checked;
  const wantOverview = document.getElementById('airef-check-overview').checked;
  const wantFormat   = document.getElementById('airef-check-format').checked;
  const data = state.projectData;
  const msg  = document.getElementById('airef-copied');
  const parts = [];

  if (!data) {
    msg.style.display = 'block';
    msg.style.color = 'var(--red)';
    msg.textContent = 'No project loaded — connect first.';
    return;
  }

  if (wantPaths && state.allFilePaths.length) {
    parts.push('=== FILEPATH LIST ===\n' + state.allFilePaths.join('\n'));
  }
  if (wantOverview && data.projectOverview) {
    const overview = Array.isArray(data.projectOverview)
      ? data.projectOverview.map(p => `${p.name}\n  root: ${p.root}\n  file: ${p.projectFile}`).join('\n')
      : JSON.stringify(data.projectOverview, null, 2);
    parts.push('=== PROJECT OVERVIEW ===\n' + overview);
  }
  if (wantFormat && data.aiReference) {
    parts.push('=== REPLY STRUCTURE GUIDE ===\n' + data.aiReference);
  }

  if (!parts.length) {
    msg.style.display = 'block';
    msg.style.color = 'var(--amber)';
    msg.textContent = 'Nothing selected to copy.';
    return;
  }

  const text = parts.join('\n\n');
  let copied = false;

  // Try modern API first
  try {
    await navigator.clipboard.writeText(text);
    copied = true;
  } catch {
    // Fallback for side panel context where clipboard API may be restricted
    try {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0';
      document.body.appendChild(ta);
      ta.focus();
      ta.select();
      copied = document.execCommand('copy');
      document.body.removeChild(ta);
    } catch { copied = false; }
  }

  msg.style.display = 'block';
  if (copied) {
    msg.style.color = 'var(--green)';
    msg.textContent = '\u2714 Copied to clipboard!';
  } else {
    msg.style.color = 'var(--red)';
    msg.textContent = 'Clipboard access denied — try pasting from the text area directly.';
  }
}

/* ── CARD HELPERS ── */
function isCardReady(card) {
  const manifest = buildManifest(card, true);
  return !!(manifest.Action && manifest.FilePath && manifest.Payload !== '' && manifest.Task);
}

function getMissingFields(card) {
  const manifest = buildManifest(card, true);
  const missing = [];
  if (!manifest.Action || (card.kind === 'unclear' && !card.actionChoice)) missing.push('Action');
  if (!manifest.FilePath) missing.push('FilePath');
  if (manifest.Payload === '' || manifest.Payload === null || manifest.Payload === undefined) missing.push('Payload');
  return missing;
}

function isActionRecognized(card) {
  return ACTIONS.includes(card.action || card.actionChoice || '');
}

function hydrateCard(card, previous, index) {
  const fileCandidates = Array.isArray(card.fileCandidates) ? card.fileCandidates : [];
  let selectedFile = previous?.selectedFile || card.selectedFile || '';
  if (!selectedFile && fileCandidates[0]?.file) selectedFile = fileCandidates[0].file;
  const task = card.task || card.phrase || card.sourceSnippet || 'Detected Change';
  return {
    ...card, task, selectedFile,
    rememberChoice: previous?.rememberChoice || 'once',
    actionChoice: previous?.actionChoice || card.action || '',
    manualFilePath: previous?.manualFilePath || '',
    icon: FILE_ICONS[index % FILE_ICONS.length],
  };
}

/* ── RENDER ── */
function renderCards() {
  const emptyState = document.getElementById('empty-state');
  const readySection = document.getElementById('ready-section');
  const unreadySection = document.getElementById('unready-section');
  const readyContainer = document.getElementById('ready-cards');
  const unreadyContainer = document.getElementById('unready-cards');

  readyContainer.innerHTML = '';
  unreadyContainer.innerHTML = '';

  const newCount = state.cards.length;
  const badge = document.getElementById('count-badge');
  badge.textContent = String(newCount);

  // Flash badge and bars when candidate count changes
  if (newCount !== state.lastCardCount && state.lastCardCount !== -1) {
    flashUpdate();
  }
  state.lastCardCount = newCount;

  if (!state.cards.length) {
    emptyState.style.display = 'block';
    readySection.style.display = 'none';
    unreadySection.style.display = 'none';
    document.getElementById('send-selected').style.display = 'none';
    return;
  }
  emptyState.style.display = 'none';

  const readyCards = state.cards.filter(c => isCardReady(c));
  const unreadyCards = state.cards.filter(c => !isCardReady(c));

  document.getElementById('ready-count').textContent = readyCards.length;
  document.getElementById('unready-count').textContent = unreadyCards.length;
  readySection.style.display = readyCards.length ? 'grid' : 'none';
  unreadySection.style.display = unreadyCards.length ? 'grid' : 'none';
  document.getElementById('send-selected').style.display = readyCards.length ? 'block' : 'none';

  readyCards.forEach(card => readyContainer.appendChild(buildReadyCard(card, state.cards.indexOf(card))));
  unreadyCards.forEach(card => unreadyContainer.appendChild(buildUnreadyCard(card, state.cards.indexOf(card))));
}

/* ── READY CARD ── */
function buildReadyCard(card, globalIndex) {
  const article = document.createElement('article');
  article.className = 'card card-ready' + (globalIndex === state.selectedIndex ? ' selected' : '');
  article.addEventListener('click', () => { state.selectedIndex = globalIndex; renderCards(); });

  const dot = document.createElement('div');
  dot.className = 'dot';
  article.appendChild(dot);

  const title = document.createElement('div');
  title.className = 'title';
  title.textContent = card.task || 'Untitled change';
  article.appendChild(title);

  const pill = document.createElement('div');
  pill.className = 'pill';
  pill.textContent = card.action || card.actionChoice || 'unassigned';
  article.appendChild(pill);

  return article;
}

/* ── UNREADY CARD ── */
function buildUnreadyCard(card, globalIndex) {
  const article = document.createElement('article');
  article.className = 'card card-unready' + (globalIndex === state.selectedIndex ? ' selected' : '');
  article.addEventListener('click', () => { state.selectedIndex = globalIndex; renderCards(); });

  // Head
  const head = document.createElement('div');
  head.className = 'card-head';
  const titleWrap = document.createElement('div');
  const titleEl = document.createElement('div');
  titleEl.className = 'card-title';
  titleEl.textContent = card.task || 'Untitled change';
  titleWrap.appendChild(titleEl);
  const metaEl = document.createElement('div');
  metaEl.className = 'card-meta';
  metaEl.textContent = (card.kind || 'parsed') + (card.confidence ? ' \u2022 ' + card.confidence : '');
  titleWrap.appendChild(metaEl);
  head.appendChild(titleWrap);

  const actionStr = card.action || card.actionChoice || '';
  const pill = document.createElement('div');
  if (card.kind === 'unclear' || !actionStr) {
    pill.className = 'pill unassigned';
    pill.textContent = 'unassigned';
  } else if (!isActionRecognized(card)) {
    pill.className = 'flag';
    pill.textContent = '\u2691 ' + actionStr;
  } else {
    pill.className = 'pill';
    pill.textContent = actionStr;
  }
  head.appendChild(pill);
  article.appendChild(head);

  // Missing button
  const missing = getMissingFields(card);
  if (missing.length) {
    const missingBtn = document.createElement('button');
    missingBtn.className = 'missing';
    missingBtn.addEventListener('click', (e) => { e.stopPropagation(); openEvidenceOverlay(card); });
    const icon = document.createElement('span');
    icon.className = 'icon';
    icon.textContent = '\u26A0';
    missingBtn.appendChild(icon);
    const rightSide = document.createElement('div');
    const missingLabel = document.createElement('div');
    missingLabel.textContent = 'MISSING';
    missingLabel.style.fontWeight = '700';
    rightSide.appendChild(missingLabel);
    const tags = document.createElement('div');
    tags.className = 'missing-tags';
    missing.forEach(field => {
      const tag = document.createElement('span');
      tag.className = 'missing-tag';
      tag.textContent = field;
      tags.appendChild(tag);
    });
    rightSide.appendChild(tags);
    missingBtn.appendChild(rightSide);
    article.appendChild(missingBtn);
  }

  // Unclear phrase context
  if (card.kind === 'unclear' || (!card.action && !card.actionChoice)) {
    const phraseCtx = buildPhraseContext(card);
    if (phraseCtx) article.appendChild(phraseCtx);
  }

  // Unrecognized action note
  if (actionStr && !ACTIONS.includes(actionStr) && card.kind !== 'unclear') {
    const flagNote = document.createElement('div');
    flagNote.className = 'phrase';
    flagNote.style.borderColor = 'rgba(214,84,84,0.25)';
    flagNote.style.background = 'rgba(214,84,84,0.07)';
    flagNote.innerHTML = `<span class="label" style="color:var(--red)">UNRECOGNIZED ACTION</span>
      Action "<strong style="color:var(--text)">${escapeHtml(actionStr)}</strong>" is not in the known list. Assign manually below.`;
    article.appendChild(flagNote);
  }

  // Filepath section — chips from candidates + project file autocomplete
  const fileCandidates = Array.isArray(card.fileCandidates) ? card.fileCandidates : [];
  if (fileCandidates.length) {
    const choiceLabel = document.createElement('div');
    choiceLabel.className = 'section-label';
    choiceLabel.textContent = 'FILEPATH';
    article.appendChild(choiceLabel);

    const chips = document.createElement('div');
    chips.className = 'chips';
    fileCandidates.forEach(candidate => {
      const btn = document.createElement('button');
      btn.className = 'chip' + (candidate.file === card.selectedFile ? ' active' : '');
      btn.textContent = candidate.file;
      btn.title = candidate.file;
      btn.addEventListener('click', (e) => {
        e.stopPropagation();
        card.selectedFile = candidate.file;
        card.manualFilePath = '';
        renderCards();
      });
      chips.appendChild(btn);
    });
    article.appendChild(chips);
  }

  // File input with project-aware autocomplete
  article.appendChild(buildFileInput(card));

  // Assign action
  if (card.kind === 'unclear' || (!card.action && !card.actionChoice)) {
    const assign = document.createElement('div');
    assign.className = 'assign';
    const assignLabel = document.createElement('div');
    assignLabel.className = 'section-label';
    assignLabel.textContent = 'ASSIGN ACTION';
    assign.appendChild(assignLabel);
    const select = document.createElement('select');
    select.className = 'action-select';
    select.innerHTML = `<option value="">Choose action\u2026</option>` +
      ACTIONS.map(a => `<option value="${a}"${card.actionChoice === a ? ' selected' : ''}>${a}</option>`).join('');
    select.addEventListener('click', e => e.stopPropagation());
    select.addEventListener('change', e => { card.actionChoice = e.target.value; renderCards(); });
    assign.appendChild(select);
    const radioRow = document.createElement('div');
    radioRow.className = 'radio-row';
    ['once', 'remember'].forEach(mode => {
      const btn = document.createElement('button');
      btn.className = 'radio' + (card.rememberChoice === mode ? ' active' : '');
      btn.textContent = mode === 'once' ? 'Assign Once' : 'Assign & Remember';
      btn.addEventListener('click', e => { e.stopPropagation(); card.rememberChoice = mode; renderCards(); });
      radioRow.appendChild(btn);
    });
    assign.appendChild(radioRow);
    article.appendChild(assign);
  }

  // Manifest preview
  const payloadLabel = document.createElement('div');
  payloadLabel.className = 'preview-label';
  payloadLabel.textContent = 'MANIFEST PREVIEW';
  article.appendChild(payloadLabel);
  const payloadBox = document.createElement('div');
  payloadBox.className = 'preview';
  payloadBox.textContent = JSON.stringify(buildManifest(card, true), null, 2);
  article.appendChild(payloadBox);

  return article;
}

/* ── FILE INPUT WITH AUTOCOMPLETE ── */
function buildFileInput(card) {
  const wrap = document.createElement('div');
  wrap.className = 'autocomplete';

  const input = document.createElement('input');
  input.className = 'file-input';
  input.placeholder = state.allFilePaths.length
    ? 'Search project files\u2026'
    : 'Manual filepath override\u2026';
  input.value = card.manualFilePath || '';
  input.addEventListener('click', e => e.stopPropagation());

  const list = document.createElement('div');
  list.className = 'ac-list';

  let focusedIndex = -1;

  function renderList(query) {
    list.innerHTML = '';
    focusedIndex = -1;
    if (!query || !state.allFilePaths.length) { list.classList.remove('open'); return; }
    const q = query.toLowerCase();
    const matches = state.allFilePaths.filter(p => p.toLowerCase().includes(q)).slice(0, 20);
    if (!matches.length) { list.classList.remove('open'); return; }
    matches.forEach((path, i) => {
      const item = document.createElement('div');
      item.className = 'ac-item';
      // highlight match
      const idx = path.toLowerCase().indexOf(q);
      item.innerHTML = escapeHtml(path.slice(0, idx)) +
        `<span class="hi">${escapeHtml(path.slice(idx, idx + q.length))}</span>` +
        escapeHtml(path.slice(idx + q.length));
      item.addEventListener('mousedown', e => {
        e.preventDefault();
        card.manualFilePath = path;
        card.selectedFile = '';
        input.value = path;
        list.classList.remove('open');
      });
      list.appendChild(item);
    });
    list.classList.add('open');
  }

  input.addEventListener('input', e => {
    card.manualFilePath = e.target.value;
    renderList(e.target.value);
  });

  input.addEventListener('keydown', e => {
    const items = [...list.querySelectorAll('.ac-item')];
    if (!items.length) return;
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
      items.forEach((el, i) => el.classList.toggle('focus', i === focusedIndex));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      focusedIndex = Math.max(focusedIndex - 1, 0);
      items.forEach((el, i) => el.classList.toggle('focus', i === focusedIndex));
    } else if (e.key === 'Enter' && focusedIndex >= 0) {
      e.preventDefault();
      const chosen = state.allFilePaths.filter(p => p.toLowerCase().includes(input.value.toLowerCase()))[focusedIndex];
      if (chosen) { card.manualFilePath = chosen; card.selectedFile = ''; input.value = chosen; list.classList.remove('open'); }
    } else if (e.key === 'Escape') {
      list.classList.remove('open');
    }
  });

  input.addEventListener('blur', () => setTimeout(() => list.classList.remove('open'), 150));

  wrap.appendChild(input);
  wrap.appendChild(list);
  return wrap;
}

/* ── PHRASE CONTEXT ── */
function buildPhraseContext(card) {
  const codeBlocks = card.payload ? extractPayloadCodeBlocks(card) : [];
  const wrap = document.createElement('div');
  wrap.className = 'phrase';
  const label = document.createElement('span');
  label.className = 'label';
  label.textContent = 'PHRASE CONTEXT \u2014 unclear phrasing detected';
  wrap.appendChild(label);
  if (card.sourceSnippet || card.phrase) {
    const snippetEl = document.createElement('div');
    snippetEl.style.cssText = 'margin-bottom:6px;font-style:italic;color:var(--muted)';
    snippetEl.textContent = '"' + (card.sourceSnippet || card.phrase || '') + '"';
    wrap.appendChild(snippetEl);
  }
  if (codeBlocks.length >= 1) {
    const pairWrap = document.createElement('div');
    pairWrap.className = 'phrase-pair';
    if (codeBlocks[0] !== undefined) {
      const item = document.createElement('div');
      item.className = 'phrase-item';
      item.innerHTML = `<strong>Find:</strong> ${escapeHtml(String(codeBlocks[0]).slice(0, 120))}`;
      pairWrap.appendChild(item);
    }
    if (codeBlocks[1] !== undefined) {
      const item = document.createElement('div');
      item.className = 'phrase-item';
      item.innerHTML = `<strong>Replace with:</strong> ${escapeHtml(String(codeBlocks[1]).slice(0, 120))}`;
      pairWrap.appendChild(item);
    }
    wrap.appendChild(pairWrap);
  } else {
    const note = document.createElement('div');
    note.style.cssText = 'font-size:10px;color:var(--muted)';
    note.textContent = 'No code blocks detected. Assign an action and filepath manually.';
    wrap.appendChild(note);
  }
  return wrap;
}

function extractPayloadCodeBlocks(card) {
  if (!card.payload) return [];
  if (typeof card.payload === 'string') return [card.payload];
  if (card.payload.target !== undefined) return [card.payload.target, card.payload.replacement];
  if (card.payload.start !== undefined) return [card.payload.start, card.payload.replacement];
  if (card.payload.find !== undefined) return [card.payload.find, card.payload.content];
  return [];
}

/* ── EVIDENCE OVERLAY ── */
function openEvidenceOverlay(card) {
  const overlay = document.getElementById('evidence-overlay');
  const body = document.getElementById('evidence-modal-body');
  body.innerHTML = '';
  overlay.classList.remove('hidden');
  const sentences = Array.isArray(card.evidenceSentences) ? card.evidenceSentences : [];
  const fileCandidates = Array.isArray(card.fileCandidates) ? card.fileCandidates : [];
  if (!sentences.length && !fileCandidates.length) {
    const empty = document.createElement('div');
    empty.style.cssText = 'color:var(--muted);font-size:12px';
    empty.textContent = 'No evidence sentences found for this candidate.';
    body.appendChild(empty);
    return;
  }
  if (fileCandidates.length) {
    const fileSection = document.createElement('div');
    fileSection.style.marginBottom = '8px';
    const fileLabel = document.createElement('div');
    fileLabel.style.cssText = 'font-size:9px;letter-spacing:.15em;color:var(--muted);margin-bottom:4px';
    fileLabel.textContent = 'DETECTED FILEPATHS';
    fileSection.appendChild(fileLabel);
    fileCandidates.forEach(candidate => {
      const fc = document.createElement('div');
      fc.style.cssText = 'font-size:11px;padding:3px 7px;border-radius:4px;background:var(--green-dim);color:var(--green);margin-bottom:3px';
      fc.textContent = candidate.file;
      fileSection.appendChild(fc);
    });
    body.appendChild(fileSection);
  }
  if (sentences.length) {
    const sentLabel = document.createElement('div');
    sentLabel.style.cssText = 'font-size:9px;letter-spacing:.15em;color:var(--muted);margin-bottom:5px';
    sentLabel.textContent = 'EVIDENCE SENTENCES';
    body.appendChild(sentLabel);
    sentences.forEach(sentence => {
      const div = document.createElement('div');
      div.className = 'evidence';
      div.innerHTML = highlightFilesInSentence(sentence, fileCandidates);
      body.appendChild(div);
    });
  }
}

function closeEvidenceOverlay() {
  document.getElementById('evidence-overlay').classList.add('hidden');
}

/* ── UPDATE FLASH ── */
function flashUpdate() {
  const badge = document.getElementById('count-badge');
  const barsWrap = document.getElementById('monitor-bars');

  // Badge pop
  badge.classList.remove('badge-updated');
  void badge.offsetWidth; // force reflow to restart animation
  badge.classList.add('badge-updated');
  setTimeout(() => badge.classList.remove('badge-updated'), 450);

  // Bars flash
  barsWrap.classList.add('bars-updated');
  setTimeout(() => barsWrap.classList.remove('bars-updated'), 550);
}

/* ── MONITOR ── */
function buildMonitorBars() {
  const wrap = document.getElementById('monitor-bars');
  wrap.innerHTML = '';
  for (let i = 0; i < 16; i++) {
    const bar = document.createElement('div');
    bar.className = 'monitor-bar-el';
    bar.style.height = `${16 + Math.round(Math.random() * 18)}px`;
    wrap.appendChild(bar);
  }
}

function animateMonitor(mode) {
  const status = document.getElementById('monitor-status');
  const dot = document.getElementById('monitor-dot');
  const bars = [...document.querySelectorAll('.monitor-bar-el')];
  if (state.sendLoop) { clearInterval(state.sendLoop); state.sendLoop = null; }
  if (mode === 'sending') {
    status.textContent = 'SENDING';
    status.classList.add('sending');
    dot.classList.add('sending');
    state.sendLoop = setInterval(() => {
      bars.forEach(bar => {
        bar.style.height = `${12 + Math.round(Math.random() * 24)}px`;
        bar.style.opacity = `${0.72 + Math.random() * 0.28}`;
      });
    }, 150);
    return;
  }
  status.textContent = mode === 'error' ? 'FAILED' : 'READY';
  status.classList.remove('sending');
  dot.classList.remove('sending');
  bars.forEach((bar, i) => {
    bar.style.height = `${16 + ((i * 7) % 18)}px`;
    bar.style.opacity = '0.82';
  });
}

/* ── NAVIGATION ── */
function moveSelection(step) {
  if (!state.cards.length) return;
  state.selectedIndex = (state.selectedIndex + step + state.cards.length) % state.cards.length;
  renderCards();
}

function getSelectedCard() { return state.cards[state.selectedIndex] || null; }

/* ── SEND ── */
async function sendSelected() {
  const card = getSelectedCard();
  if (!card) { setStatus('No candidate selected', false); return; }
  if (card.kind === 'unclear' || (!card.action && card.actionChoice)) {
    const action = card.actionChoice;
    if (!action) { setStatus('Choose an action first', false); return; }
    card.action = action;
    card.payload = buildFallbackPayload(action, card.sourceSnippet || card.phrase || card.task || '');
    if (card.rememberChoice === 'remember') {
      const learned = await chrome.runtime.sendMessage({ type: 'NGO_CLASSIFY_PHRASE', phrase: card.phrase || card.sourceSnippet || card.task, action });
      if (!learned?.ok) { setStatus(learned?.error || 'Phrase save failed', false); return; }
    }
  }
  const manifest = buildManifest(card);
  if (!manifest.Action) { setStatus('Missing action', false); return; }
  if (!manifest.FilePath) { setStatus('Choose or enter a filepath first', false); return; }
  state.isSending = true;
  animateMonitor('sending');
  setStatus(`Sending ${manifest.Action}\u2026`, true);
  try {
    const response = await chrome.runtime.sendMessage({ type: 'NGO_SEND_MANIFESTS', payload: manifest });
    setTimeout(() => { animateMonitor(response?.ok ? 'ready' : 'error'); }, 900);
    setStatus(response?.ok ? `Sent to ${response.endpoint}` : response?.error || `HTTP ${response?.status || 0}`, Boolean(response?.ok));
  } catch (error) {
    animateMonitor('error');
    setStatus(error?.message || 'Send failed', false);
  } finally {
    state.isSending = false;
    renderCards();
  }
}

/* ── BUILD HELPERS ── */
function buildFallbackPayload(action, phrase) {
  if (action === 'replacestring' || action === 'replaceall') return { target: '', replacement: '' };
  if (action === 'replaceblock') return { start: '', end: '', replacement: '' };
  if (action === 'replacefunction') return { start: phrase, replacement: '' };
  if (action === 'insertbefore' || action === 'insertafter') return { find: '', content: '' };
  return '';
}

function buildManifest(card, previewOnly = false) {
  if (card.manifest && !previewOnly) return card.manifest;
  return {
    Task: card.task || 'Apply parsed change',
    Action: card.action || card.actionChoice || '',
    FilePath: card.manualFilePath?.trim() || card.selectedFile || '',
    Payload: card.manifest?.Payload ?? card.payload ?? '',
  };
}

function setStatus(text, ok) {
  const el = document.getElementById('status-text');
  el.textContent = text;
  el.style.color = ok ? '#8eb658' : '#d65454';
}

/* ── UTIL ── */
function highlightFilesInSentence(sentence, candidates) {
  let html = escapeHtml(sentence);
  candidates.forEach(candidate => {
    const escaped = candidate.file.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    html = html.replace(new RegExp(escaped, 'g'), `<span class="hl">${escapeHtml(candidate.file)}</span>`);
  });
  return html;
}

function escapeHtml(value) {
  return String(value)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}