(() => {
  const BUILT_IN_ACTION_PATTERNS = [
    { action: 'rewritefile',     patterns: ['drop in replacement', 'full file replace', 'replace file', 'replace the whole file', 'replace the entire file', 'use this as the full file', 'rewrite the full file'] },
    { action: 'replacefunction', patterns: ['replace the full function', 'replace the full ', 'replace clearsession()', 'replace the establishsession function'] },
    { action: 'replaceblock',    patterns: ['replace this block', 'replace the constants block', 'replace the userrecord type', 'replace the guest_user object', 'rewrite the section blocks', 'rewrite the whole suspect section', 'safe block-level rewrite'] },
    { action: 'replaceall',      patterns: ['replace every occurrence', 'all occurrences', 'every occurrence', 'rename repeated references', 'replace patterns like'] },
    { action: 'insertbefore',    patterns: ['insert before'] },
    { action: 'insertafter',     patterns: ['insert after', 'insert these helpers after'] },
    { action: 'replacestring',   patterns: ['look for', 'change it to', 'replace this line', 'replace these parts', 'replace the import line', 'replace the token line in', 'use this exact replacement', 'should become'] },
  ];

  const state = {
    learnedPhrases: {},
    lastFingerprint: '',
    observer: null,
  };

  init();

  async function init() {
    const store = await chrome.storage.local.get(['learnedPhrases']);
    state.learnedPhrases = store.learnedPhrases || {};
    observeMessages();
    parseAndSend();
    chrome.storage.onChanged.addListener((changes, area) => {
      if (area === 'local' && changes.learnedPhrases) {
        state.learnedPhrases = changes.learnedPhrases.newValue || {};
        parseAndSend();
      }
    });
  }

  function observeMessages() {
    if (state.observer) state.observer.disconnect();
    state.observer = new MutationObserver(() => parseAndSend());
    state.observer.observe(document.body, { childList: true, subtree: true, characterData: true });
  }

  function getAssistantMessages() {
    const byAttr = [...document.querySelectorAll('[data-message-author-role="assistant"]')];
    if (byAttr.length) return byAttr;

    const byArticle = [...document.querySelectorAll('main article')];
    if (byArticle.length) return byArticle;

    return [...document.querySelectorAll('.markdown, .prose, .font-claude-message')];
  }

  // 4. Broad fallback containers
  const fallback = [...document.querySelectorAll('.font-claude-message, .markdown, .prose')];
  return fallback;
}

  function parseAndSend() {
    const messages = getAssistantMessages();
    const candidates = [];

    for (const message of messages) {
      const text = extractText(message);
      if (!text.trim()) continue;
      const codeBlocks = extractCodeBlocks(message, text);
      const lines = text.split(/\r?\n/);
      const messageCandidates = [];

      messageCandidates.push(...parseExplicitManifestBlocks(text));
      messageCandidates.push(...parseLookForChangePairs(text, codeBlocks, lines));
      messageCandidates.push(...parseLineAndBlockReplacements(text, codeBlocks, lines));
      messageCandidates.push(...parseArrowSwaps(lines));
      messageCandidates.push(...parseUnclearPhrases(text, lines));

      dedupeInto(candidates, messageCandidates);
    }

    const fingerprint = JSON.stringify(candidates.map(c => [c.kind, c.task, c.action, c.fileCandidates?.map(f => f.file)]));
    if (fingerprint === state.lastFingerprint) return;
    state.lastFingerprint = fingerprint;
    chrome.runtime.sendMessage({ type: 'NGO_UPDATE_CANDIDATES', candidates });
  }

  function extractText(node) {
    return node.innerText || node.textContent || '';
  }

  function extractCodeBlocks(node, text) {
    const domBlocks = [...node.querySelectorAll('pre code')].map(el => el.innerText || el.textContent || '').filter(Boolean);
    if (domBlocks.length) return domBlocks;
    const fenced = [];
    const re = /```(?:[a-zA-Z0-9_-]+)?\n([\s\S]*?)```/g;
    let match;
    while ((match = re.exec(text))) fenced.push(match[1].replace(/\n$/, ''));
    return fenced;
  }

  function parseExplicitManifestBlocks(text) {
    const out = [];
    const matches = text.match(/\{[\s\S]*?"Task"[\s\S]*?"Action"[\s\S]*?"FilePath"[\s\S]*?"Payload"[\s\S]*?\}/g) || [];
    for (const raw of matches) {
      try {
        const manifest = JSON.parse(raw);
        out.push({
          id: makeId('manifest'), kind: 'manifest',
          task: String(manifest.Task || 'Apply manifest'),
          action: String(manifest.Action || '').toLowerCase(),
          manifest,
          evidenceSentences: extractEvidenceSentences(text),
          fileCandidates: buildFileCandidates(text),
          sourceSnippet: raw.slice(0, 800),
        });
      } catch { /* malformed JSON, skip */ }
    }
    return out;
  }

  function parseLookForChangePairs(text, codeBlocks, lines) {
    const out = [];
    const lower = text.toLowerCase();
    if (!lower.includes('look for') || !lower.includes('change it to')) return out;
    if (codeBlocks.length < 2) return out;
    const task = findTaskSentence(lines, /(look for|change it to|replace this line|replace these parts)/i) || 'Replace suggested string';
    out.push({
      id: makeId('pair'), kind: 'parsed', task,
      action: 'replacestring',
      payload: { target: codeBlocks[0], replacement: codeBlocks[1] },
      evidenceSentences: extractEvidenceSentences(text),
      fileCandidates: buildFileCandidates(text),
      sourceSnippet: task, confidence: 'high',
    });
    return out;
  }

  function parseLineAndBlockReplacements(text, codeBlocks, lines) {
    const out = [];
    const lower = text.toLowerCase();
    const sentence = findTaskSentence(lines, /(replace this line|replace these parts|replace this block|replace the full|insert .* after|insert .* before|rewrite the section blocks|rewrite the whole suspect section|safe block-level rewrite|drop in replacement|replace file|replace the whole file|replace the entire file)/i);
    if (!sentence) return out;
    const action = determineAction(sentence, lower);
    if (!action) return out;
    let payload = null;
    if (action === 'replacestring' || action === 'replaceall') {
      if (codeBlocks.length >= 2) payload = { target: codeBlocks[0], replacement: codeBlocks[1] };
    } else if (action === 'replaceblock') {
      if (codeBlocks.length >= 2) payload = { start: '', end: '', replacement: codeBlocks[1] || codeBlocks[0] };
    } else if (action === 'replacefunction') {
      if (codeBlocks.length >= 1) payload = { start: extractFunctionAnchor(sentence), replacement: codeBlocks[codeBlocks.length - 1] };
    } else if (action === 'insertbefore' || action === 'insertafter') {
      if (codeBlocks.length >= 1) payload = { find: extractAnchorAfterInsert(sentence), content: codeBlocks[codeBlocks.length - 1] };
    } else if (action === 'rewritefile' || action === 'newfile') {
      if (codeBlocks.length >= 1) payload = codeBlocks[codeBlocks.length - 1];
    }
    out.push({
      id: makeId('pattern'), kind: 'parsed', task: sentence, action, payload,
      evidenceSentences: extractEvidenceSentences(text),
      fileCandidates: buildFileCandidates(text),
      sourceSnippet: sentence, confidence: payload ? 'medium' : 'low',
    });
    return out;
  }

  function parseArrowSwaps(lines) {
    const out = [];
    for (const line of lines) {
      if (!line.includes('\u2192')) continue;
      const parts = line.split('\u2192').map(p => p.trim()).filter(Boolean);
      if (parts.length !== 2) continue;
      out.push({
        id: makeId('arrow'), kind: 'parsed',
        task: 'Replace suggested string', action: 'replacestring',
        payload: { target: stripOuterQuotes(parts[0]), replacement: stripOuterQuotes(parts[1]) },
        evidenceSentences: extractEvidenceSentences(lines.join('\n')),
        fileCandidates: buildFileCandidates(lines.join('\n')),
        sourceSnippet: line.trim(), confidence: 'medium',
      });
    }
    return out;
  }

  function parseUnclearPhrases(text, lines) {
    const out = [];
    for (const sentence of splitSentences(text)) {
      if (!hasActionFlavor(sentence)) continue;
      const action = determineAction(sentence, text.toLowerCase());
      if (action) continue;
      out.push({
        id: makeId('unclear'), kind: 'unclear',
        task: sentence.trim().slice(0, 140),
        phrase: sentence.trim(),
        evidenceSentences: extractEvidenceSentences(text),
        fileCandidates: buildFileCandidates(text),
        sourceSnippet: sentence.trim(), confidence: 'low',
      });
    }
    return out.slice(0, 5);
  }

  function determineAction(sentence, fullLower) {
    const lower = sentence.toLowerCase();
    for (const [action, phrases] of Object.entries(state.learnedPhrases || {})) {
      if ((phrases || []).some(p => lower.includes(p))) return action;
    }
    for (const bucket of BUILT_IN_ACTION_PATTERNS) {
      if (bucket.patterns.some(p => lower.includes(p) || fullLower.includes(p))) return bucket.action;
    }
    return '';
  }

  function buildFileCandidates(text) {
    const sentences = splitSentences(text);
    const map = new Map();
    for (const sentence of sentences) {
      const refs = [...sentence.matchAll(/(?:[A-Za-z]:\\[^\s,;:()\]]+|(?:[\w.-]+\\)*[\w.-]+\.(?:ts|tsx|js|jsx|json|css|html|md|txt))/g)];
      for (const ref of refs) {
        const file = ref[0];
        if (!map.has(file)) map.set(file, { file, sentences: [] });
        map.get(file).sentences.push(sentence.trim());
      }
    }
    return [...map.values()].map(entry => ({
      file: entry.file,
      evidence: [...new Set(entry.sentences)].slice(0, 3),
    }));
  }

  function extractEvidenceSentences(text) {
    return splitSentences(text)
      .filter(s => /[A-Za-z]:\\|\.(ts|tsx|js|jsx|json|css|html|md|txt)\b/.test(s))
      .slice(0, 6);
  }

  function splitSentences(text) {
    return text.split(/\n+/).flatMap(line => line.split(/(?<=[.!?])\s+/)).map(s => s.trim()).filter(Boolean);
  }

  function hasActionFlavor(sentence) {
    return /\b(replace|rewrite|insert|change|swap|drop in|align|update|rename|use this|replace file)\b/i.test(sentence);
  }

  function findTaskSentence(lines, pattern) {
    for (let i = lines.length - 1; i >= 0; i--) {
      const line = (lines[i] || '').trim();
      if (pattern.test(line)) return line;
    }
    return '';
  }

  function extractFunctionAnchor(sentence) {
    const match = sentence.match(/([A-Za-z_$][\w$]*\([^)]*\))/);
    return match ? match[1] : '';
  }

  function extractAnchorAfterInsert(sentence) {
    const match = sentence.match(/after\s+([A-Za-z_$][\w$]*\([^)]*\)|[^.]+)$/i)
                || sentence.match(/before\s+([A-Za-z_$][\w$]*\([^)]*\)|[^.]+)$/i);
    return match ? match[1].trim() : '';
  }

  function stripOuterQuotes(value) {
    return value.replace(/^['"`]/, '').replace(/['"`]$/, '');
  }

  function dedupeInto(target, items) {
    const seen = new Set(target.map(item => item.kind + '|' + item.task + '|' + item.action + '|' + JSON.stringify(item.payload || item.manifest || item.phrase || '')));
    for (const item of items) {
      const key = item.kind + '|' + item.task + '|' + item.action + '|' + JSON.stringify(item.payload || item.manifest || item.phrase || '');
      if (seen.has(key)) continue;
      seen.add(key);
      target.push(item);
    }
  }

  function makeId(prefix) {
    return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
  }
})();