const ACTION_OPTIONS = [
  { value: 'replacestring', label: 'Replace String' },
  { value: 'insertbefore', label: 'Insert Before' },
  { value: 'insertafter', label: 'Insert After' },
  { value: 'rewritefile', label: 'Replace File' },
  { value: 'newfile', label: 'New File' },
  { value: 'replacefunction', label: 'Replace Function' },
  { value: 'replaceblock', label: 'Replace Block' },
  { value: 'manifest', label: 'Manifest' }
];

const FILE_ICONS = ['assets/extf1.png', 'assets/extf2.png', 'assets/extf3.png', 'assets/extf4.png'];
const INTERVENTION_ICONS = {
  filepath: 'assets/extf1.png',
  complete: 'assets/extf2.png',
  payload: 'assets/extf4.png',
  action: 'assets/extf3.png'
};
const MAX_FILEPATH_CACHE = 5000;
const POLL_MS = 5000;
const REMEMBERABLE_ACTIONS = new Set(['replacestring', 'insertbefore', 'insertafter', 'replacefunction', 'replaceblock']);
const CONTEXT_EXCLUDE_DIRS = new Set(['node_modules', '.git', '.next', 'dist', 'build', 'coverage', 'logs', 'logarchive', 'archive', 'failed', 'scrapes', 'restore', 'localsecure', '.cache', 'tmp', 'temp']);
const CONTEXT_EXCLUDE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg', '.mp4', '.mov', '.mp3', '.wav', '.zip', '.sqlite', '.db', '.exe', '.dll', '.map']);

const MONITOR_EQ_BARS = 18;
const MONITOR_EQ_SEGMENTS = 13;
let monitorMode = 'idle';
let monitorEqFrame = null;
let monitorEqHeights = Array.from({ length: MONITOR_EQ_BARS }, () => Math.random() * 0.22);
let monitorEqTargets = Array.from({ length: MONITOR_EQ_BARS }, () => Math.random() * 0.26);

const state = {
  tabId: null,
  settings: null,
  cards: [],
  selectedIndex: 0,
  isSending: false,
  sendLoop: null,
  readyExpanded: false,
  projectData: null,
  allFilePaths: [],
  lastIncomingFingerprint: '',
  contextMenuIndex: -1,
  backgroundActivity: { lowUntil: 0, mediumUntil: 0, highUntil: 0 },
  localActivity: { lowUntil: 0, mediumUntil: 0, highUntil: 0 },
  connectionOk: false,
  tickerEvents: []
};

init();

async function init() {
  buildMonitorBars();
  wireTransport();
  relabelSections();

  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  state.tabId = tab?.id;

  const stored = await chrome.storage.local.get(['projectData', 'allFilePaths']);
  if (stored.projectData) {
    state.projectData = stored.projectData;
    state.allFilePaths = (stored.allFilePaths || flattenFilePaths(stored.projectData.roots || [])).slice(0, MAX_FILEPATH_CACHE);
    renderProjectBar(stored.projectData);
  }

  await ensureContentScriptReady();
  await refresh();
  await checkConnectionOnBoot();
  updateTicker();
  setInterval(refresh, POLL_MS);
  setInterval(updateTicker, 1000);
}
chrome.runtime.onMessage.addListener((message) => {
  if (message?.type !== 'NGO_CANDIDATES_CHANGED') return false;
  if (typeof message.tabId === 'number' && message.tabId !== state.tabId) return false;
  refresh();
  return false;
});

async function ensureContentScriptReady() {
  if (typeof state.tabId !== 'number') return;
  try { await chrome.runtime.sendMessage({ type: 'NGO_ENSURE_CONTENT_SCRIPT', tabId: state.tabId }); } catch {}
}

function relabelSections() {
  const header = document.querySelector('#unready-section .Label-Fo');
  if (header) header.textContent = 'IDENTIFIED CHANGES';
}

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('play-ready-btn').addEventListener('click', () => sendAllReady());
  document.getElementById('send-selected').addEventListener('click', () => sendAllReady());
  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;
    renderCards();
  });
  document.getElementById('evidence-close').addEventListener('click', closeEvidenceOverlay);
  document.getElementById('evidence-overlay').addEventListener('click', (event) => {
    if (event.target === event.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', (event) => {
    if (event.target === event.currentTarget) document.getElementById('airef-overlay').classList.add('hidden');
  });
  document.getElementById('airef-copy-btn').addEventListener('click', copyAiRef);
  document.addEventListener('click', hideCardContextMenu);
  document.getElementById('card-menu-promote').addEventListener('click', () => applyCardMenuAction('promote'));
  document.getElementById('card-menu-demote').addEventListener('click', () => applyCardMenuAction('demote'));
  document.getElementById('card-menu-remove').addEventListener('click', () => applyCardMenuAction('remove'));
}

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;
  state.backgroundActivity = response.state?.activity || { lowUntil: 0, mediumUntil: 0, highUntil: 0 };
  applyMonitorActivity();
  const incoming = Array.isArray(response.state?.candidates) ? response.state.candidates : [];
  const hasIncomingCandidates = incoming.length > 0;
  const incomingFingerprint = JSON.stringify(incoming.map((card) => [card.id, card.task, card.action, card.actionConfidence, card.fileCandidates?.map((entry) => entry.file), card.payload]));
  if (hasIncomingCandidates) {
    addTickerEvent(incoming.some((card) => card.exactManifest) ? 'Recording Sheet Found' : 'Listening', true);
    const previousById = new Map(state.cards.map((card) => [card.id, card]));
    state.cards = incoming.map((card, index) => hydrateCard(card, previousById.get(card.id), index));
  }
  if (!state.cards.length) state.selectedIndex = 0;
  else if (state.selectedIndex > state.cards.length - 1) state.selectedIndex = state.cards.length - 1;
  const isEditing = isUserEditing();
  if (incomingFingerprint !== state.lastIncomingFingerprint) {
    state.lastIncomingFingerprint = incomingFingerprint;
    if (!isEditing) { renderSettings(); renderCards(); flashUpdate(); }
  } else if (!isEditing) {
    renderSettings(); renderCards();
  }
}

function isUserEditing() {
  const active = document.activeElement;
  return Boolean(active?.id === 'host' || active?.id === 'port' || active?.classList?.contains('file-input') || active?.classList?.contains('action-select'));
}

function hydrateCard(card, previous, index) {
  const normalizedFiles = mergeWithTreeMatches(card.fileCandidates || []);
  const initialAction = card.exactManifest ? 'manifest' : (card.action || card.actionChoice || actionFromUiType(card.uiType));
  const preservedAction = previous?.actionChoice || initialAction || 'replacestring';
  const actionConfidence = card.exactManifest ? 'certain' : (card.actionConfidence || 'review');
  let selectedFile = previous?.selectedFile || '';
  if (card.exactManifest && card.manifest?.FilePath) selectedFile = String(card.manifest.FilePath);
  if (!card.exactManifest && selectedFile && !card.filepathReviewed) selectedFile = '';
  if (!selectedFile && previous?.manualFilePath) selectedFile = '';
  const filepathReviewed = card.exactManifest ? true : Boolean(previous?.filepathReviewed && (selectedFile || previous?.manualFilePath));
  const actionReviewed = card.exactManifest ? true : Boolean(previous?.actionReviewed || actionConfidence === 'certain');
  const hydrated = { ...card, fileCandidates: normalizedFiles, actionChoice: preservedAction, actionReviewed, filepathReviewed, selectedFile, manualFilePath: previous?.manualFilePath || '', rememberChoice: previous?.rememberChoice || 'once', manualBucket: previous?.manualBucket || '', learnedActionPhrase: previous?.learnedActionPhrase || '', dismissed: Boolean(previous?.dismissed) };
  hydrated.icon = iconForIntervention(hydrated);
  return hydrated;
}

function getInterventionType(card) {
  const manifest = buildManifest(card, true);
  const action = String(manifest.Action || '').trim().toLowerCase();
  const filePath = String(manifest.FilePath || '').trim();
  const payload = manifest.Payload;

  if (!filePath) return 'filepath';
  if (!action || card.actionConfidence === 'review' || !card.actionReviewed) return 'action';
  if (payload === undefined || payload === null || payload === '') return 'payload';
  if (typeof payload === 'object' && !Array.isArray(payload)) {
    const values = Object.values(payload).map((value) => String(value ?? '').trim());
    if (!values.length || values.some((value) => !value)) return 'payload';
  }
  return 'complete';
}

function iconForIntervention(card) {
  return INTERVENTION_ICONS[getInterventionType(card)] || INTERVENTION_ICONS.complete;
}

function mergeWithTreeMatches(fileCandidates) {
  const merged = [];
  const used = new Set();
  for (const candidate of fileCandidates || []) {
    const key = String(candidate.file || '').toLowerCase();
    if (!key || used.has(key)) continue;
    used.add(key);
    merged.push({ file: candidate.file, evidence: Array.isArray(candidate.evidence) ? candidate.evidence : [], fromTree: false });
  }
  for (const candidate of fileCandidates || []) {
    const file = String(candidate.file || '').trim();
    if (!file || !state.allFilePaths.length) continue;
    const matches = state.allFilePaths.filter((path) => path.toLowerCase().includes(file.toLowerCase())).slice(0, 4);
    for (const path of matches) {
      const key = path.toLowerCase();
      if (used.has(key)) continue;
      used.add(key);
      merged.push({ file: path, evidence: [`Tree match for: ${file}`], fromTree: true });
    }
  }
  return merged;
}

function renderSettings() {
  const host = document.getElementById('host');
  const port = document.getElementById('port');
  if (document.activeElement !== host) host.value = state.settings?.host || '127.0.0.1';
  if (document.activeElement !== port) port.value = state.settings?.port || '5054';
}

async function saveSettings() {
  const host = document.getElementById('host').value.trim() || '127.0.0.1';
  const port = document.getElementById('port').value.trim() || '5054';
  const response = await chrome.runtime.sendMessage({ type: 'NGO_SAVE_SETTINGS', settings: { host, port, path: '/record' } });
  if (!response?.ok) {
    state.connectionOk = false;
    setStatus(response?.error || 'Save failed', false);
    applyMonitorActivity();
    return;
  }
  await checkConnectionOnBoot();
  await refresh();
}

async function checkConnectionOnBoot() {
  try {
    const ping = await chrome.runtime.sendMessage({ type: 'NGO_PING_RECORDER' });
    if (!ping?.reachable) {
      state.connectionOk = false;
      setStatus(`Not connected — ${ping?.error || 'enter host and port'}`, false);
      applyMonitorActivity();
      return;
    }
    state.connectionOk = true;
    setStatus(`Connected — ${ping.endpoint}`, true);
    applyMonitorActivity();
    await fetchProjectTree({ quiet: true });
  } catch (error) {
    state.connectionOk = false;
    setStatus(error?.message || 'Not connected — enter host and port', false);
    applyMonitorActivity();
  }
}

async function refreshTree() {
  const button = 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; }
  button.classList.add('spinning');
  setStatus('Fetching project tree…', true);
  await fetchProjectTree({ quiet: false });
  button.classList.remove('spinning');
}

async function fetchProjectTree(options = {}) {
  const quiet = Boolean(options.quiet);
  try {
    const response = await chrome.runtime.sendMessage({ type: 'NGO_FETCH_PROJECT_TREE' });
    if (!response?.ok || !response.body?.ok) {
      clearProjectData();
      if (!quiet) setStatus(state.connectionOk ? 'Connected — no project tree' : 'Not connected — enter host and port', state.connectionOk);
      applyMonitorActivity();
      return;
    }
    state.connectionOk = true;
    state.projectData = response.body;
    state.allFilePaths = flattenFilePaths(response.body.roots || []).slice(0, MAX_FILEPATH_CACHE);
    await chrome.storage.local.set({ projectData: response.body, allFilePaths: state.allFilePaths });
    chrome.runtime.sendMessage({ type: 'NGO_SAVE_PROJECT_JSON', data: response.body });
    renderProjectBar(response.body);
    state.cards = state.cards.map((card) => hydrateCard(card, card, 0));
    renderCards();
    setStatus(`Project loaded — ${state.allFilePaths.length} files`, true);
    applyMonitorActivity();
  } catch (error) {
    clearProjectData();
    if (!quiet) setStatus(error?.message || 'Project fetch failed', false);
    applyMonitorActivity();
  }
}

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

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

function isContextEligiblePath(filePath) {
  const clean = String(filePath || '').replace(/\\/g, '/').toLowerCase();
  if (!clean) return false;
  const parts = clean.split('/').filter(Boolean);
  if (parts.some(part => CONTEXT_EXCLUDE_DIRS.has(part))) return false;
  const ext = clean.includes('.') ? clean.slice(clean.lastIndexOf('.')) : '';
  if (CONTEXT_EXCLUDE_EXTS.has(ext)) return false;
  return true;
}

function compactTreeNode(node, depth = 0, maxDepth = 4) {
  if (!node) return null;
  const nodePath = String(node.path || node.name || '').replace(/\\/g, '/');
  const name = String(node.name || nodePath || '').trim();
  const lowerName = name.toLowerCase();
  if (CONTEXT_EXCLUDE_DIRS.has(lowerName)) return null;

  if (node.type === 'file') {
    return isContextEligiblePath(nodePath) ? { name, path: nodePath, type: 'file' } : null;
  }

  if (depth >= maxDepth) return { name, path: nodePath, type: 'dir', children: [] };

  const children = (Array.isArray(node.children) ? node.children : [])
    .map(child => compactTreeNode(child, depth + 1, maxDepth))
    .filter(Boolean);

  if (!children.length && depth > 0) return null;
  return { name, path: nodePath, type: 'dir', children };
}

function treeToText(node, depth = 0) {
  if (!node) return '';
  const pad = '  '.repeat(depth);
  const label = `${pad}${node.type === 'dir' ? '▸ ' : '• '}${node.name || node.path || 'root'}`;
  const children = Array.isArray(node.children) ? node.children : [];
  return [label, ...children.map(child => treeToText(child, depth + 1))].filter(Boolean).join('\n');
}

function buildProjectMapText(data) {
  const roots = Array.isArray(data?.roots) ? data.roots : [];
  const compactRoots = roots.map(root => compactTreeNode(root)).filter(Boolean);
  const lines = compactRoots.map(root => treeToText(root)).filter(Boolean);
  return lines.length ? lines.join('\n') : '(no eligible source files found)';
}

function flattenFilePaths(nodes) {
  const paths = [];
  function walk(nodeList) {
    for (const node of nodeList) {
      if (paths.length >= MAX_FILEPATH_CACHE) return;
      if (node.type === 'file' && isContextEligiblePath(node.path)) paths.push(node.path);
      if (node.children?.length) walk(node.children);
    }
  }
  walk(nodes || []);
  return paths;
}
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 message = document.getElementById('airef-copied');
  const parts = [];

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

  if (wantPaths) {
    parts.push([
      '=== PROJECT MAP ===',
      `Active Project: ${data.activeProject || 'Unknown'}`,
      '',
      buildProjectMapText(data)
    ].join('\n'));
  }

  if (wantOverview && data.projectOverview) {
    const overview = Array.isArray(data.projectOverview)
      ? data.projectOverview.map((item) => {
          const summary = item?.data?.summary || item?.data?.details?.summary || '';
          const notes = item?.data?.notes || item?.data?.details?.notes || '';
          return [
            item.name,
            `  root: ${item.root}`,
            `  file: ${item.projectFile}`,
            summary ? `  summary: ${summary}` : '',
            notes ? `  notes: ${notes}` : ''
          ].filter(Boolean).join('\n');
        }).join('\n\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) {
    message.style.display = 'block';
    message.style.color = 'var(--amber)';
    message.textContent = 'Nothing selected to copy.';
    return;
  }

  const text = parts.join('\n\n');
  let copied = false;
  try { await navigator.clipboard.writeText(text); copied = true; } catch {
    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; }
  }

  message.style.display = 'block';
  message.style.color = copied ? 'var(--green)' : 'var(--red)';
  message.textContent = copied ? '✔ Copied filtered project context!' : 'Clipboard access denied.';
}
function renderCards() {
  const empty = document.getElementById('empty-state');
  const count = document.getElementById('count-badge');
  const readySection = document.getElementById('ready-section');
  const readyCards = document.getElementById('ready-cards');
  const readyCount = document.getElementById('ready-count');
  const unreadySection = document.getElementById('unready-section');
  const unreadyCards = document.getElementById('unready-cards');
  const unreadyCount = document.getElementById('unready-count');
  const sendButton = document.getElementById('send-selected');
  const toggle = document.getElementById('ready-toggle').querySelector('.Label-Fo');
  readyCards.innerHTML = '';
  unreadyCards.innerHTML = '';
  const ready = [];
  const review = [];
  state.cards.forEach((card, index) => {
    const bucket = getCardBucket(card);
    if (bucket === 'hidden') return;
    (bucket === 'ready' ? ready : review).push({ card, index });
  });
  count.textContent = String(state.cards.length);
  empty.style.display = state.cards.length ? 'none' : 'block';
  readySection.style.display = ready.length ? 'grid' : 'none';
  unreadySection.style.display = review.length ? 'grid' : 'none';
  sendButton.style.display = ready.length ? 'block' : 'none';
  readyCount.textContent = String(ready.length);
  unreadyCount.textContent = String(review.length);
  readyCards.classList.toggle('condensed', !state.readyExpanded);
  toggle.innerHTML = `${state.readyExpanded ? '&#9660;' : '&#9658;'} READY TO RECORD`;
  ready.forEach(({ card, index }) => readyCards.appendChild(buildReadyCard(card, index, !state.readyExpanded)));
  review.forEach(({ card, index }) => unreadyCards.appendChild(buildReviewCard(card, index)));
}

function isCardReady(card) {
  const manifest = buildManifest(card, true);
  const validationError = validateManifestContract(manifest);
  return Boolean(!validationError && (card.exactManifest || card.actionReviewed) && (card.exactManifest || card.filepathReviewed));
}

function getCardBucket(card) {
  if (card?.dismissed) return 'hidden';
  if (card?.manualBucket === 'review') return 'review';
  if (card?.manualBucket === 'ready') return isCardReady(card) ? 'ready' : 'review';
  return isCardReady(card) ? 'ready' : 'review';
}

function validateManifestContract(manifest) {
  if (!manifest || typeof manifest !== 'object') return 'Manifest must be an object.';
  const task = String(manifest.Task || '').trim();
  const action = String(manifest.Action || '').trim().toLowerCase();
  const filePath = String(manifest.FilePath || '').trim();
  const payload = manifest.Payload;
  if (!task) return 'Missing Task.';
  if (!action) return 'Missing Action.';
  if (!filePath) return 'Missing FilePath.';
  if (payload === undefined || payload === null || payload === '') return 'Missing Payload.';
  if (action === 'replacestring') {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return `${action} requires object Payload.`;
    if (!String(payload.target || '').trim()) return `${action} requires Payload.target.`;
    if (!String(payload.replacement || '').trim()) return `${action} requires Payload.replacement.`;
    return null;
  }
  if (action === 'replacefunction') {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return `${action} requires object Payload.`;
    if (!String(payload.start || '').trim()) return `${action} requires Payload.start.`;
    if (!String(payload.replacement || '').trim()) return `${action} requires Payload.replacement.`;
    return null;
  }
  if (action === 'replaceblock') {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return `${action} requires object Payload.`;
    if (!String(payload.start || '').trim()) return `${action} requires Payload.start.`;
    if (!String(payload.end || '').trim()) return `${action} requires Payload.end.`;
    if (!String(payload.replacement || '').trim()) return `${action} requires Payload.replacement.`;
    return null;
  }
  if (['insertbefore', 'insertafter'].includes(action)) {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return `${action} requires object Payload.`;
    if (!String(payload.find || '').trim()) return `${action} requires Payload.find.`;
    if (!String(payload.content || '').trim()) return `${action} requires Payload.content.`;
    return null;
  }
  if (['newfile', 'rewritefile'].includes(action)) {
    if (typeof payload !== 'string' || !payload.trim()) return `${action} requires string Payload.`;
    return null;
  }
  return `Unsupported Action: ${action}`;
}

function buildReadyCard(card, index, condensed = false) {
  const wrap = document.createElement('div');
  wrap.className = 'card card-ready' + (condensed ? ' condensed' : '') + (index === state.selectedIndex ? ' selected' : '');
  wrap.addEventListener('click', () => { state.selectedIndex = index; renderCards(); });
  wrap.addEventListener('contextmenu', (event) => openCardContextMenu(event, index));
  const selected = index === state.selectedIndex;
  wrap.innerHTML = `<div class="card-ready-head"><img class="file-icon" src="${escapeHtml(card.icon || FILE_ICONS[0])}" alt="" /><div class="card-copy"><span class="title">${escapeHtml(card.task || 'Apply parsed change')}</span><span class="card-subline">${escapeHtml(buildReadySubline(card))}</span></div><span class="pill">READY</span></div>`;
  if (!condensed && selected) wrap.appendChild(buildReadyDetails(card));
  return wrap;
}

function buildReadySubline(card) {
  const manifest = buildManifest(card, true);
  const filePath = manifest.FilePath || card.selectedFile || card.manualFilePath || 'filepath set';
  return `${uiLabel(card)} • ${filePath}`;
}

function buildReadyDetails(card) {
  const details = document.createElement('div');
  details.className = 'ready-details';
  const fileSection = document.createElement('div');
  const fileLabel = document.createElement('div');
  fileLabel.className = 'section-label';
  fileLabel.textContent = 'FILEPATH';
  fileSection.appendChild(fileLabel);
  const fileValue = document.createElement('div');
  fileValue.className = 'ready-file';
  const manifest = buildManifest(card, true);
  fileValue.textContent = manifest.FilePath || card.selectedFile || card.manualFilePath || 'No filepath selected';
  fileSection.appendChild(fileValue);
  details.appendChild(fileSection);
  details.appendChild(buildPreview(card));
  return details;
}

function buildReviewCard(card, index) {
  const wrap = document.createElement('div');
  wrap.className = 'card card-unready' + (index === state.selectedIndex ? ' selected' : '');
  wrap.addEventListener('click', () => { state.selectedIndex = index; renderCards(); });
  wrap.addEventListener('contextmenu', (event) => openCardContextMenu(event, index));
  wrap.appendChild(buildHead(card));
  wrap.appendChild(buildContextSection(card));
  wrap.appendChild(buildFileSection(card));
  wrap.appendChild(buildActionSection(card));
  wrap.appendChild(buildPreview(card));
  return wrap;
}

function buildHead(card) {
  const head = document.createElement('div');
  head.className = 'card-head';
  const validationError = validateManifestContract(buildManifest(card, true));
  const confidence = validationError ? validationError : card.exactManifest ? 'EXACT MANIFEST' : (card.actionConfidence || 'review').toUpperCase();
  head.innerHTML = `<img class="file-icon" src="${escapeHtml(card.icon || FILE_ICONS[0])}" alt="" /><div class="card-copy"><div class="card-title">${escapeHtml(card.task || 'Apply parsed change')}</div><div class="card-meta">${escapeHtml(confidence)}</div></div><div class="pill ${validationError || card.actionConfidence === 'review' ? 'unassigned' : ''}">${escapeHtml(uiLabel(card))}</div>`;
  return head;
}

function buildContextSection(card) {
  const wrap = document.createElement('div');
  wrap.className = 'phrase';
  const label = document.createElement('span');
  label.className = 'label';
  label.textContent = 'CONTEXT';
  wrap.appendChild(label);
  const taskLine = document.createElement('div');
  taskLine.style.cssText = 'margin-bottom:6px;color:var(--text)';
  taskLine.textContent = `Task sentence: ${card.taskContextSentence || card.task || '—'}`;
  wrap.appendChild(taskLine);
  const actionLine = document.createElement('div');
  actionLine.style.cssText = 'margin-bottom:6px;color:var(--muted)';
  actionLine.textContent = `Action context: ${card.actionContextSentence || card.sourceSnippet || '—'}`;
  wrap.appendChild(actionLine);
  const extractedLine = document.createElement('div');
  extractedLine.style.cssText = 'margin-bottom:6px;color:var(--muted)';
  extractedLine.textContent = `Parsed text: ${card.sourceSnippet || card.actionContextSentence || card.taskContextSentence || '—'}`;
  wrap.appendChild(extractedLine);
  const pieces = extractPayloadPieces(card);
  if (pieces.length) {
    const pairWrap = document.createElement('div');
    pairWrap.className = 'phrase-pair';
    pieces.forEach((entry) => {
      const item = document.createElement('div');
      item.className = 'phrase-item';
      item.innerHTML = `<strong>${escapeHtml(entry.label)}:</strong> ${escapeHtml(String(entry.value).slice(0, 180))}`;
      pairWrap.appendChild(item);
    });
    wrap.appendChild(pairWrap);
  }
  return wrap;
}

function buildFileSection(card) {
  const section = document.createElement('div');
  const label = document.createElement('div');
  label.className = 'section-label';
  label.textContent = 'FILEPATH';
  section.appendChild(label);
  if (card.fileCandidates?.length) {
    const chips = document.createElement('div');
    chips.className = 'chips';
    card.fileCandidates.forEach((candidate) => {
      const row = document.createElement('div');
      row.style.cssText = 'display:grid;gap:3px;width:100%';
      const chip = document.createElement('button');
      chip.type = 'button';
      chip.className = 'chip' + (candidate.file === card.selectedFile ? ' active' : '');
      chip.textContent = candidate.file + (candidate.fromTree ? ' · tree' : '');
      chip.title = (candidate.evidence || []).join('\n');
      chip.addEventListener('click', (event) => { event.stopPropagation(); card.selectedFile = candidate.file; card.manualFilePath = ''; card.filepathReviewed = true; renderCards(); });
      row.appendChild(chip);
      (candidate.evidence || []).slice(0, 2).forEach((sentence) => {
        const hint = document.createElement('div');
        hint.style.cssText = 'font-size:10px;color:var(--muted);margin-left:2px';
        hint.textContent = sentence;
        row.appendChild(hint);
      });
      chips.appendChild(row);
    });
    section.appendChild(chips);
  }
  section.appendChild(buildAutocomplete(card));
  return section;
}

function buildAutocomplete(card) {
  const wrap = document.createElement('div');
  wrap.className = 'autocomplete';
  const input = document.createElement('input');
  input.className = 'file-input';
  input.placeholder = 'Manual filepath or tree search…';
  input.value = card.manualFilePath || '';
  const list = document.createElement('div');
  list.className = 'ac-list';
  let focusedIndex = -1;
  let currentMatches = [];
  function renderList(query) {
    const value = String(query || '').toLowerCase().trim();
    list.innerHTML = '';
    focusedIndex = -1;
    currentMatches = [];
    if (!value || !state.allFilePaths.length) { list.classList.remove('open'); return; }
    currentMatches = state.allFilePaths.filter((path) => path.toLowerCase().includes(value)).slice(0, 40);
    if (!currentMatches.length) { list.classList.remove('open'); return; }
    currentMatches.forEach((path) => {
      const item = document.createElement('div');
      item.className = 'ac-item';
      item.innerHTML = highlightMatch(path, value);
      item.addEventListener('mousedown', (event) => { event.preventDefault(); card.manualFilePath = path; card.selectedFile = ''; card.filepathReviewed = true; input.value = path; list.classList.remove('open'); renderCards(); });
      list.appendChild(item);
    });
    list.classList.add('open');
  }
  input.addEventListener('click', (event) => event.stopPropagation());
  input.addEventListener('input', (event) => { card.manualFilePath = event.target.value; card.selectedFile = ''; card.filepathReviewed = Boolean(event.target.value.trim()); renderList(event.target.value); });
  input.addEventListener('keydown', (event) => {
    const items = [...list.querySelectorAll('.ac-item')];
    if (!items.length) return;
    if (event.key === 'ArrowDown') { event.preventDefault(); focusedIndex = Math.min(focusedIndex + 1, items.length - 1); items.forEach((el, idx) => el.classList.toggle('focus', idx === focusedIndex)); }
    else if (event.key === 'ArrowUp') { event.preventDefault(); focusedIndex = Math.max(focusedIndex - 1, 0); items.forEach((el, idx) => el.classList.toggle('focus', idx === focusedIndex)); }
    else if (event.key === 'Enter' && focusedIndex >= 0) { event.preventDefault(); const chosen = currentMatches[focusedIndex]; if (chosen) { card.manualFilePath = chosen; card.selectedFile = ''; card.filepathReviewed = true; input.value = chosen; list.classList.remove('open'); renderCards(); } }
    else if (event.key === 'Escape') list.classList.remove('open');
  });
  input.addEventListener('blur', () => setTimeout(() => list.classList.remove('open'), 150));
  wrap.appendChild(input);
  wrap.appendChild(list);
  return wrap;
}

function buildActionSection(card) {
  const wrap = document.createElement('div');
  wrap.className = 'assign';
  const label = document.createElement('div');
  label.className = 'section-label';
  label.textContent = 'ACTION';
  wrap.appendChild(label);
  const summary = document.createElement('div');
  summary.style.cssText = 'font-size:11px;color:var(--muted)';
  summary.textContent = `Suggested: ${uiLabel(card)}${card.actionContextSentence ? ` — ${card.actionContextSentence}` : ''}`;
  wrap.appendChild(summary);
  if (card.exactManifest) return wrap;
  const select = document.createElement('select');
  select.className = 'action-select';
  select.innerHTML = ACTION_OPTIONS.filter((item) => item.value !== 'manifest').map((item) => `<option value="${item.value}" ${card.actionChoice === item.value ? 'selected' : ''}>${item.label}</option>`).join('');
  select.addEventListener('click', (event) => event.stopPropagation());
  select.addEventListener('change', (event) => { card.actionChoice = event.target.value; card.actionReviewed = true; renderCards(); });
  wrap.appendChild(select);
  const teachSelect = document.createElement('select');
  teachSelect.className = 'action-select';
  const phraseOptions = buildTeachPhraseOptions(card);
  teachSelect.innerHTML = phraseOptions.map((phrase) => `<option value="${escapeHtml(phrase)}" ${card.learnedActionPhrase === phrase ? 'selected' : ''}>${escapeHtml(phrase)}</option>`).join('');
  teachSelect.addEventListener('click', (event) => event.stopPropagation());
  teachSelect.addEventListener('change', (event) => { card.learnedActionPhrase = event.target.value; card.rememberChoice = 'remember'; renderCards(); });
  wrap.appendChild(teachSelect);
  const radioRow = document.createElement('div');
  radioRow.className = 'radio-row';
  [{ key: 'once', label: 'Use Once' }, { key: 'remember', label: 'Remember Phrase' }].forEach((option) => {
    const button = document.createElement('button');
    button.type = 'button';
    button.className = 'radio' + (card.rememberChoice === option.key ? ' active' : '');
    button.textContent = option.label;
    button.addEventListener('click', (event) => { event.stopPropagation(); card.rememberChoice = option.key; if (option.key === 'remember' && !card.learnedActionPhrase) card.learnedActionPhrase = phraseOptions[0] || card.actionContextSentence || ''; renderCards(); });
    radioRow.appendChild(button);
  });
  wrap.appendChild(radioRow);
  return wrap;
}

function buildPreview(card) {
  const wrap = document.createElement('div');
  const label = document.createElement('div');
  label.className = 'preview-label';
  label.textContent = 'MANIFEST PREVIEW';
  wrap.appendChild(label);
  const preview = document.createElement('pre');
  preview.className = 'preview';
  preview.textContent = JSON.stringify(buildManifest(card, true), null, 2);
  wrap.appendChild(preview);
  return wrap;
}

function actionFromUiType(uiType) {
  if (uiType === 'replace') return 'replacestring';
  if (uiType === 'replaceblock') return 'replaceblock';
  if (uiType === 'replacefunction') return 'replacefunction';
  if (uiType === 'rewrite') return 'rewritefile';
  if (uiType === 'before') return 'insertbefore';
  if (uiType === 'after') return 'insertafter';
  if (uiType === 'newfile') return 'newfile';
  return 'replacestring';
}

function uiLabel(card) {
  const action = card.exactManifest ? 'manifest' : (card.actionChoice || card.action || actionFromUiType(card.uiType));
  const match = ACTION_OPTIONS.find((item) => item.value === action);
  return match ? match.label.toUpperCase() : 'UNASSIGNED';
}

function extractPayloadPieces(card) {
  const manifest = buildManifest(card, true);
  const payload = manifest.Payload;
  if (!payload) return [];
  if (typeof payload === 'string') return [{ label: 'Content', value: payload }];
  if (payload.target !== undefined && payload.replacement !== undefined) return [{ label: 'Target', value: payload.target }, { label: 'Replacement', value: payload.replacement }];
  if (payload.start !== undefined && payload.end !== undefined && payload.replacement !== undefined) return [{ label: 'Start', value: payload.start }, { label: 'End', value: payload.end }, { label: 'Replacement', value: payload.replacement }];
  if (payload.start !== undefined && payload.replacement !== undefined) return [{ label: 'Start', value: payload.start }, { label: 'Replacement', value: payload.replacement }];
  if (payload.find !== undefined && payload.content !== undefined) return [{ label: 'Find', value: payload.find }, { label: 'Content', value: payload.content }];
  return Object.entries(payload).map(([key, value]) => ({ label: key, value }));
}

function buildManifest(card, previewOnly = false) {
  if (card.exactManifest && card.manifest) return normalizeManifestForSend(card.manifest);
  const action = card.actionChoice || card.action || actionFromUiType(card.uiType);
  const filePath = (card.manualFilePath || '').trim() || card.selectedFile || '';
  const payload = normalizePayloadForAction(action, card.payload);
  return { Task: card.task || card.taskContextSentence || 'Apply parsed change', Action: action || '', FilePath: filePath, Payload: payload, Comment: card.comment || null };
}

function normalizeManifestForSend(manifest) {
  const normalized = {};
  for (const [key, value] of Object.entries(manifest || {})) {
    const lower = String(key).toLowerCase();
    if (lower === 'task') normalized.Task = value;
    else if (lower === 'action') normalized.Action = String(value || '').toLowerCase();
    else if (lower === 'filepath') normalized.FilePath = value;
    else if (lower === 'payload') normalized.Payload = value;
    else if (lower === 'comment') normalized.Comment = value;
    else normalized[key] = value;
  }
  if (!normalized.Task) normalized.Task = 'Apply manifest';
  if (!('Comment' in normalized)) normalized.Comment = null;
  normalized.Payload = normalizePayloadForAction(normalized.Action, normalized.Payload);
  return normalized;
}

function normalizePayloadForAction(action, payload) {
  if (action === 'newfile' || action === 'rewritefile') return typeof payload === 'string' ? payload : (payload?.replacement || payload?.content || '');
  if (!payload || typeof payload !== 'object') return payload || '';
  if (action === 'replacestring') return { target: payload.target ?? payload.find ?? payload.start ?? '', replacement: payload.replacement ?? payload.content ?? '' };
  if (action === 'replacefunction') return { start: payload.start ?? payload.target ?? payload.find ?? '', replacement: payload.replacement ?? payload.content ?? '' };
  if (action === 'replaceblock') return { start: payload.start ?? payload.target ?? payload.find ?? '', end: payload.end ?? payload.stop ?? '', replacement: payload.replacement ?? payload.content ?? '' };
  if (action === 'insertbefore' || action === 'insertafter') return { find: payload.find ?? payload.target ?? payload.start ?? '', content: payload.content ?? payload.replacement ?? '' };
  return payload;
}

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; }

async function sendSelected() {
  const card = getSelectedCard();
  if (!card) { setStatus('No candidate selected', false); return; }
  if (!isCardReady(card)) { setStatus(validateManifestContract(buildManifest(card, true)) || 'Selected card still needs review', false); return; }
  if (card.rememberChoice === 'remember') await maybeRememberAction(card);
  const manifest = buildManifest(card);
  state.isSending = true;
  markLocalActivity('high');
  animateMonitor('sending');
  setStatus(`Sending ${manifest.Action}…`, true);
  try { const response = await chrome.runtime.sendMessage({ type: 'NGO_SEND_MANIFESTS', payload: manifest }); await handleSendResult(response); }
  catch (error) { animateMonitor('error'); setStatus(error?.message || 'Send failed', false); }
  finally { state.isSending = false; }
}

async function sendAllReady() {
  const readyCards = state.cards.filter((card) => getCardBucket(card) === 'ready' && isCardReady(card));
  if (!readyCards.length) { setStatus('No ready cards to send', false); return; }
  for (const card of readyCards) if (card.rememberChoice === 'remember') await maybeRememberAction(card);
  const payload = readyCards.map((card) => buildManifest(card));
  markLocalActivity('high');
  animateMonitor('sending');
  setStatus(`Sending ${payload.length} manifests…`, true);
  try { const response = await chrome.runtime.sendMessage({ type: 'NGO_SEND_MANIFESTS', payload }); await handleSendResult(response); }
  catch (error) { animateMonitor('error'); setStatus(error?.message || 'Send failed', false); }
}

function buildTeachPhraseOptions(card) {
  const options = [card.actionContextSentence, card.sourceSnippet, card.taskContextSentence, card.task, ...(Array.isArray(card.evidenceSentences) ? card.evidenceSentences : [])].map((value) => String(value || '').trim()).filter(Boolean).filter((value, index, list) => list.indexOf(value) === index).slice(0, 8);
  return options.length ? options : [''];
}
async function maybeRememberAction(card) {
  if (card.exactManifest) return;
  const phrase = card.learnedActionPhrase || card.actionContextSentence || card.sourceSnippet || card.taskContextSentence;
  if (!phrase || !REMEMBERABLE_ACTIONS.has(card.actionChoice)) return;
  await chrome.runtime.sendMessage({ type: 'NGO_CLASSIFY_PHRASE', phrase, action: card.actionChoice });
}
function responseSucceeded(response) {
  if (!response?.ok) return false;
  if (response.body && typeof response.body === 'object' && response.body.ok === false) return false;
  return true;
}
async function handleSendResult(response) {
  const success = responseSucceeded(response);
  setTimeout(() => { animateMonitor(success ? 'idle' : 'error'); }, 900);
  const failMessage = response?.body && typeof response.body === 'object' ? response.body.message || response.body.error : response?.body || response?.error || `HTTP ${response?.status || 0}`;
  setStatus(success ? `Sent to ${response.endpoint}` : String(failMessage || 'Send failed'), success);
  if (!response?.ok) return;
  await clearCandidatesAfterRun();
}
async function clearCandidatesAfterRun() {
  if (typeof state.tabId === 'number') await chrome.runtime.sendMessage({ type: 'NGO_CLEAR_TAB_CANDIDATES', tabId: state.tabId });
  state.cards = [];
  state.selectedIndex = 0;
  state.lastIncomingFingerprint = '';
  renderCards();
}
async function clearAll() {
  if (typeof state.tabId === 'number') await chrome.runtime.sendMessage({ type: 'NGO_CLEAR_TAB_CANDIDATES', tabId: state.tabId });
  state.cards = [];
  state.selectedIndex = 0;
  state.lastIncomingFingerprint = '';
  renderCards();
  setStatus('Cleared local candidates', true);
}
function openCardContextMenu(event, index) {
  event.preventDefault();
  event.stopPropagation();
  state.contextMenuIndex = index;
  const menu = document.getElementById('card-context-menu');
  menu.classList.remove('hidden');
  menu.style.left = `${event.clientX}px`;
  menu.style.top = `${event.clientY}px`;
}
function hideCardContextMenu() {
  state.contextMenuIndex = -1;
  document.getElementById('card-context-menu').classList.add('hidden');
}
function applyCardMenuAction(action) {
  const card = state.cards[state.contextMenuIndex];
  if (!card) { hideCardContextMenu(); return; }
  if (action === 'promote') card.manualBucket = 'ready';
  if (action === 'demote') card.manualBucket = 'review';
  if (action === 'remove') card.dismissed = true;
  hideCardContextMenu();
  renderCards();
}
function openEvidenceOverlay(card) {
  const overlay = document.getElementById('evidence-overlay');
  const body = document.getElementById('evidence-modal-body');
  body.innerHTML = '';
  overlay.classList.remove('hidden');
  const files = Array.isArray(card.fileCandidates) ? card.fileCandidates : [];
  const sentences = Array.isArray(card.evidenceSentences) ? card.evidenceSentences : [];
  if (files.length) {
    const label = document.createElement('div');
    label.className = 'section-label';
    label.textContent = 'FILEPATH CONTEXT';
    body.appendChild(label);
    files.forEach((candidate) => {
      const node = document.createElement('div');
      node.className = 'evidence';
      node.innerHTML = `<div class="hl">${escapeHtml(candidate.file)}</div>${(candidate.evidence || []).map((sentence) => `<div style="margin-top:4px">${highlightFilesInSentence(sentence, files)}</div>`).join('')}`;
      body.appendChild(node);
    });
  }
  if (sentences.length) {
    const label = document.createElement('div');
    label.className = 'section-label';
    label.style.marginTop = '6px';
    label.textContent = 'EVIDENCE SENTENCES';
    body.appendChild(label);
    sentences.forEach((sentence) => { const node = document.createElement('div'); node.className = 'evidence'; node.innerHTML = highlightFilesInSentence(sentence, files); body.appendChild(node); });
  }
}
function closeEvidenceOverlay() { document.getElementById('evidence-overlay').classList.add('hidden'); }
function flashUpdate() {
  const badge = document.getElementById('count-badge');
  const barsWrap = document.getElementById('monitor-bars');
  badge.classList.remove('badge-updated');
  void badge.offsetWidth;
  badge.classList.add('badge-updated');
  setTimeout(() => badge.classList.remove('badge-updated'), 450);
  barsWrap.classList.add('bars-updated');
  setTimeout(() => barsWrap.classList.remove('bars-updated'), 550);
}
function buildMonitorBars() {
  const wrap = document.getElementById('monitor-bars');
  wrap.innerHTML = '';
  for (let i = 0; i < MONITOR_EQ_BARS; i += 1) {
    const bar = document.createElement('div');
    bar.className = 'monitor-bar-el';
    for (let level = 0; level < MONITOR_EQ_SEGMENTS; level += 1) {
      const segment = document.createElement('span');
      segment.className = 'monitor-segment';
      bar.appendChild(segment);
    }
    wrap.appendChild(bar);
  }
}
function applyMonitorActivity() {
  const now = Date.now();
  if (!state.connectionOk) { animateMonitor('offline'); return; }
  const timers = {
    lowUntil: Math.max(Number(state.backgroundActivity.lowUntil || 0), Number(state.localActivity.lowUntil || 0)),
    mediumUntil: Math.max(Number(state.backgroundActivity.mediumUntil || 0), Number(state.localActivity.mediumUntil || 0)),
    highUntil: Math.max(Number(state.backgroundActivity.highUntil || 0), Number(state.localActivity.highUntil || 0))
  };
  if (now < timers.highUntil) { animateMonitor('high'); return; }
  if (now < timers.mediumUntil) { animateMonitor('medium'); return; }
  if (now < timers.lowUntil) { animateMonitor('low'); return; }
  animateMonitor('idle');
}
function markLocalActivity(level) {
  const now = Date.now();
  if (level === 'high') state.localActivity.highUntil = Math.max(state.localActivity.highUntil || 0, now + 2000);
  if (level === 'medium') state.localActivity.mediumUntil = Math.max(state.localActivity.mediumUntil || 0, now + 2000);
  if (level === 'low') state.localActivity.lowUntil = Math.max(state.localActivity.lowUntil || 0, now + 5000);
  applyMonitorActivity();
}
function runMonitorEqFrame() {
  const bars = Array.from(document.querySelectorAll('.monitor-bar-el'));
  if (!bars.length) {
    monitorEqFrame = requestAnimationFrame(runMonitorEqFrame);
    return;
  }

  const energy = (monitorMode === 'high' || monitorMode === 'sending')
    ? 'H'
    : monitorMode === 'medium'
    ? 'M'
    : monitorMode === 'low'
    ? 'L'
    : monitorMode === 'idle'
    ? 'I'
    : 'O';

  const range = energy === 'H'
    ? { min: 0.42, max: 1.0 }
    : energy === 'M'
    ? { min: 0.2, max: 0.68 }
    : energy === 'L'
    ? { min: 0.06, max: 0.28 }
    : energy === 'I'
    ? { min: 0.06, max: 0.28 }
    : { min: 0.02, max: 0.08 };

  const jitter = energy === 'H' ? 0.18 : energy === 'M' ? 0.11 : energy === 'O' ? 0.02 : 0.05;
  const smoothing = energy === 'H' ? 0.24 : energy === 'M' ? 0.18 : 0.1;
  const opacity = energy === 'H' ? 0.96 : energy === 'M' ? 0.9 : energy === 'O' ? 0.34 : 0.78;

  bars.forEach((bar, idx) => {
    if (Math.random() < 0.08 + jitter) {
      monitorEqTargets[idx] = range.min + Math.random() * (range.max - range.min);
    }

    monitorEqHeights[idx] += (monitorEqTargets[idx] - monitorEqHeights[idx]) * smoothing;
    const ratio = Math.max(0.03, Math.min(1, monitorEqHeights[idx]));
    const level = energy === 'O' ? 1 : Math.max(1, Math.min(MONITOR_EQ_SEGMENTS, Math.round(ratio * MONITOR_EQ_SEGMENTS)));
    const segments = Array.from(bar.querySelectorAll('.monitor-segment'));

    bar.style.opacity = String(opacity);
    segments.forEach((segment, segmentIndex) => segment.classList.toggle('on', segmentIndex < level));
  });

  monitorEqFrame = requestAnimationFrame(runMonitorEqFrame);
}

function ensureMonitorEqLoop() {
  if (!monitorEqFrame) monitorEqFrame = requestAnimationFrame(runMonitorEqFrame);
}

function animateMonitor(mode) {
  const status = document.getElementById('monitor-status');
  const dot = document.getElementById('monitor-dot');
  monitorMode = mode || 'idle';
  if (state.sendLoop) { clearInterval(state.sendLoop); state.sendLoop = null; }
  ensureMonitorEqLoop();

  status.classList.remove('sending');
  dot.classList.remove('sending');

  if (mode === 'high' || mode === 'sending') {
    status.textContent = 'SENDING';
    status.classList.add('sending');
    dot.classList.add('sending');
    return;
  }
  if (mode === 'medium') { status.textContent = 'PARSING'; return; }
  if (mode === 'low') { status.textContent = 'SCANNING'; return; }
  if (mode === 'offline') { status.textContent = 'OFFLINE'; return; }
  if (mode === 'error') { status.textContent = 'FAILED'; return; }
  status.textContent = 'READY';
}
function addTickerEvent(text, ok = true) {
  const clean = String(text || '').trim();
  if (!clean) return;
  const now = Date.now();
  state.tickerEvents = [
    { text: clean, ok: Boolean(ok), expiresAt: now + 30000 },
    ...state.tickerEvents.filter((event) => event.text !== clean && event.expiresAt > now)
  ].slice(0, 6);
  updateTicker();
}

function buildPersistentTickerMessages() {
  const messages = [];
  const activeProject = state.projectData?.activeProject || 'None';
  const overview = state.projectData?.projectOverview ? 'Yes' : 'No';
  const filePaths = state.allFilePaths?.length ? 'Yes' : 'No';
  const port = state.settings?.port || document.getElementById('port')?.value || 'Unknown';

  messages.push(`Project: ${activeProject}`);
  messages.push(`Overview: ${overview}`);
  messages.push(`FilePaths: ${filePaths}`);
  messages.push(state.connectionOk ? `Connected on Port: ${port}` : 'No Connection to Verbatim');

  return messages;
}

function updateTicker() {
  const element = document.getElementById('status-text');
  const connection = document.querySelector('.Connection-Cx');
  if (!element) return;
  const now = Date.now();
  state.tickerEvents = state.tickerEvents.filter((event) => event.expiresAt > now);
  const eventMessages = state.tickerEvents.map((event) => event.text);
  const messages = [...eventMessages, ...buildPersistentTickerMessages()];
  element.textContent = messages.length ? messages.join('                         •                         ') : 'Idle';
  element.classList.toggle('bad', !state.connectionOk);
  if (connection) connection.classList.toggle('connected', Boolean(state.connectionOk));
}

function setStatus(text, ok) {
  const raw = String(text || '').trim();
  let message = raw;
  if (/^Connected/i.test(raw)) message = 'Connected';
  else if (/Not connected|no connection/i.test(raw)) message = 'No Connection to Verbatim';
  else if (/^Sent to/i.test(raw)) message = 'Sent to Verbatim';
  else if (/^Cleared/i.test(raw)) message = 'Deck Cleared';
  else if (/^Sending/i.test(raw)) message = 'Recording';
  else if (/Project loaded/i.test(raw)) message = 'Project Loaded';
  else if (/Fetching project tree/i.test(raw)) message = 'Loading Project Map';
  addTickerEvent(message, ok);
}
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 highlightMatch(path, query) {
  const lower = path.toLowerCase();
  const index = lower.indexOf(query.toLowerCase());
  if (index === -1) return escapeHtml(path);
  return `${escapeHtml(path.slice(0, index))}<span class="hi">${escapeHtml(path.slice(index, index + query.length))}</span>${escapeHtml(path.slice(index + query.length))}`;
}
function escapeHtml(value) {
  return String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}