/**
 * background.js — Service Worker
 * 负责：图标点击 → 切换侧边栏、消息中转、截图、offscreen语音识别中转
 */

// ===== offscreen 语音识别管理 =====
let offscreenCreating = false;

function getContextStorageArea() {
  return chrome.storage?.session || chrome.storage?.local;
}

function isRestrictedPageUrl(url = '') {
  return url.startsWith('chrome://') ||
    url.startsWith('chrome-extension://') ||
    url.startsWith('edge://') ||
    url.startsWith('about:');
}

function isSensitiveCollabUrl(url = '') {
  return /(?:dingtalk\.com|alidocs\.com|feishu\.cn|larkoffice\.com|larksuite\.com|bytedance\.net)/i.test(url);
}

function queryActiveTab() {
  return new Promise((resolve) => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => resolve(tabs?.[0] || null));
  });
}

function executeScriptSafe(options) {
  return new Promise((resolve, reject) => {
    chrome.scripting.executeScript(options, (results) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
        return;
      }
      resolve(results || []);
    });
  });
}

function setContextStorage(value) {
  return new Promise((resolve, reject) => {
    const contextStorage = getContextStorageArea();
    if (!contextStorage?.set) {
      reject(new Error('storage unavailable'));
      return;
    }
    contextStorage.set(value, () => {
      if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
      else resolve();
    });
  });
}

function safePageSnapshotInPage(options = {}) {
  const maxText = options.maxText || 12000;
  const sensitive = !!options.sensitive;

  const cleanText = (value) => String(value || '')
    .replace(/\u00a0/g, ' ')
    .replace(/[ \t]+/g, ' ')
    .replace(/\n{3,}/g, '\n\n')
    .trim();

  const visibleEnough = (el) => {
    if (!el) return false;
    const style = window.getComputedStyle(el);
    if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
    const rect = el.getBoundingClientRect();
    return rect.width > 1 && rect.height > 1;
  };

  const textFromSelectors = () => {
    const selectors = sensitive
      ? [
          '[contenteditable="true"]',
          '[contenteditable]',
          '[data-content-editable-root]',
          '.ProseMirror',
          '.lake-content',
          '.lake-document',
          '.doc-editor',
          '.doc-content',
          '[class*="docContent"]',
          '[class*="editorContent"]',
          '[class*="articleContainer"]',
          'main',
          'article'
        ]
      : [
          'main',
          'article',
          '[role="main"]',
          'form',
          '[contenteditable="true"]',
          '[contenteditable]',
          '.content',
          '.container'
        ];

    const chunks = [];
    const seen = new Set();
    for (const selector of selectors) {
      const elements = Array.from(document.querySelectorAll(selector)).slice(0, 20);
      for (const el of elements) {
        if (!visibleEnough(el)) continue;
        const text = cleanText(el.innerText || el.textContent || '');
        if (text.length < 40 || seen.has(text)) continue;
        seen.add(text);
        chunks.push(text);
        if (chunks.join('\n\n').length > maxText) return chunks.join('\n\n').slice(0, maxText);
      }
    }
    return chunks.join('\n\n').slice(0, maxText);
  };

  const textFromLargestBlocks = () => {
    const candidates = Array.from(document.querySelectorAll('main, article, section, form, div'))
      .filter(visibleEnough)
      .map((el) => {
        const text = cleanText(el.innerText || el.textContent || '');
        return { text, len: text.length };
      })
      .filter(item => item.len > 80)
      .sort((a, b) => b.len - a.len)
      .slice(0, sensitive ? 3 : 5);
    return cleanText(candidates.map(item => item.text).join('\n\n')).slice(0, maxText);
  };

  const textFromBody = () => cleanText(document.body?.innerText || '').slice(0, maxText);

  let pageText = textFromSelectors();
  if (pageText.length < 80) pageText = textFromLargestBlocks();
  if (pageText.length < 80) pageText = textFromBody();

  const fields = {};
  try {
    const controls = Array.from(document.querySelectorAll('input, textarea, select'))
      .filter(el => visibleEnough(el) && el.type !== 'password' && el.type !== 'hidden')
      .slice(0, 80);
    for (const el of controls) {
      const key = el.getAttribute('aria-label') ||
        el.getAttribute('placeholder') ||
        el.getAttribute('name') ||
        el.getAttribute('id') ||
        el.labels?.[0]?.innerText ||
        el.closest('label')?.innerText ||
        el.tagName.toLowerCase();
      const value = cleanText(el.value || el.textContent || '');
      if (key && value) fields[cleanText(key).slice(0, 80)] = value.slice(0, 500);
    }
  } catch (_) {}

  const tableData = [];
  try {
    const tables = Array.from(document.querySelectorAll('table')).filter(visibleEnough).slice(0, 5);
    for (const table of tables) {
      const rows = Array.from(table.querySelectorAll('tr')).slice(0, 20).map(row =>
        Array.from(row.querySelectorAll('th,td')).slice(0, 8).map(cell => cleanText(cell.innerText || cell.textContent || ''))
      ).filter(row => row.some(Boolean));
      if (rows.length) tableData.push(rows);
    }
  } catch (_) {}

  const attachments = [];
  try {
    const filePattern = /\.(pdf|docx?|xlsx?|pptx?)(?:[?#].*)?$/i;
    for (const a of Array.from(document.querySelectorAll('a[href]')).slice(0, 200)) {
      const href = a.href || '';
      if (!filePattern.test(href)) continue;
      attachments.push({
        name: cleanText(a.innerText || a.getAttribute('download') || href.split('/').pop() || '附件').slice(0, 120),
        href
      });
      if (attachments.length >= 20) break;
    }
  } catch (_) {}

  const fieldCount = Object.keys(fields).length;
  const quality = fieldCount >= 5 || pageText.length > 800 || tableData.length > 0
    ? 'good'
    : pageText.length > 80 || fieldCount > 0
      ? 'fair'
      : 'poor';

  return {
    url: window.location.href,
    pageUrl: window.location.href,
    pageTitle: document.title,
    billId: 'UNKNOWN',
    fields,
    tableData,
    attachments,
    embeddedFiles: [],
    pageText,
    quality,
    extractionMode: sensitive ? 'safe-collab-snapshot' : 'safe-snapshot',
    extractedAt: new Date().toISOString()
  };
}

async function safeExtractActivePage() {
  const tab = await queryActiveTab();
  if (!tab?.id) throw new Error('未找到当前Tab');
  if (!tab.url || isRestrictedPageUrl(tab.url)) {
    throw new Error('当前页面不可注入（浏览器内部页面）');
  }

  const sensitive = isSensitiveCollabUrl(tab.url);
  const results = await executeScriptSafe({
    target: { tabId: tab.id },
    func: safePageSnapshotInPage,
    args: [{ sensitive, maxText: sensitive ? 12000 : 16000 }]
  });
  const context = results?.[0]?.result || null;
  if (!context) throw new Error('页面读取无返回');
  await setContextStorage({ pageContext: context, sourceTabId: tab.id });
  return context;
}

function decodeHtmlEntities(value = '') {
  const named = {
    amp: '&',
    lt: '<',
    gt: '>',
    quot: '"',
    apos: "'",
    nbsp: ' '
  };
  return String(value)
    .replace(/&(#x?[0-9a-f]+|\w+);/gi, (m, entity) => {
      const key = entity.toLowerCase();
      if (named[key]) return named[key];
      if (key.startsWith('#x')) {
        const code = parseInt(key.slice(2), 16);
        return Number.isFinite(code) ? String.fromCodePoint(code) : m;
      }
      if (key.startsWith('#')) {
        const code = parseInt(key.slice(1), 10);
        return Number.isFinite(code) ? String.fromCodePoint(code) : m;
      }
      return m;
    });
}

function cleanExtractedText(value = '') {
  return decodeHtmlEntities(value)
    .replace(/\u00a0/g, ' ')
    .replace(/[ \t\r\f\v]+/g, ' ')
    .replace(/\n[ \t]+/g, '\n')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

function htmlToPlainText(html = '') {
  return cleanExtractedText(String(html)
    .replace(/<script[\s\S]*?<\/script>/gi, '\n')
    .replace(/<style[\s\S]*?<\/style>/gi, '\n')
    .replace(/<noscript[\s\S]*?<\/noscript>/gi, '\n')
    .replace(/<svg[\s\S]*?<\/svg>/gi, '\n')
    .replace(/<\/(p|div|section|article|main|header|footer|li|tr|h[1-6]|br)>/gi, '\n')
    .replace(/<[^>]+>/g, ' '));
}

function stripHtmlTags(html = '') {
  return cleanExtractedText(String(html).replace(/<[^>]+>/g, ' '));
}

function attrValue(tag = '', attr = '') {
  const re = new RegExp(`${attr}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i');
  const match = tag.match(re);
  return decodeHtmlEntities(match?.[2] || match?.[3] || match?.[4] || '');
}

function normalizeFetchUrl(rawUrl = '') {
  const value = String(rawUrl || '').trim();
  if (!/^https?:\/\//i.test(value)) throw new Error('URL 必须以 http:// 或 https:// 开头');
  const url = new URL(value);
  if (!['http:', 'https:'].includes(url.protocol)) throw new Error('仅支持 http/https URL');
  url.hash = '';
  return url.toString();
}

function parseTablesFromHtml(html = '') {
  const tables = [];
  const tableRegex = /<table[\s\S]*?<\/table>/gi;
  let tableMatch;
  while ((tableMatch = tableRegex.exec(html)) && tables.length < 8) {
    const rows = [];
    const rowRegex = /<tr[\s\S]*?<\/tr>/gi;
    let rowMatch;
    while ((rowMatch = rowRegex.exec(tableMatch[0])) && rows.length < 80) {
      const cells = [];
      const cellRegex = /<(?:th|td)\b[\s\S]*?<\/(?:th|td)>/gi;
      let cellMatch;
      while ((cellMatch = cellRegex.exec(rowMatch[0])) && cells.length < 20) {
        const cell = stripHtmlTags(cellMatch[0]);
        cells.push(cell);
      }
      if (cells.some(Boolean)) rows.push(cells);
    }
    if (rows.length) tables.push(rows);
  }
  return tables;
}

function parseHtmlPageContext(html, sourceUrl, finalUrl, contentType = '') {
  const raw = String(html || '').slice(0, 2_000_000);
  const titleMatch = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
  const pageTitle = stripHtmlTags(titleMatch?.[1] || '');
  let metaDescription = '';
  const metaRegex = /<meta\b[^>]*>/gi;
  let metaMatch;
  while ((metaMatch = metaRegex.exec(raw))) {
    const tag = metaMatch[0];
    const name = (attrValue(tag, 'name') || attrValue(tag, 'property')).toLowerCase();
    if (['description', 'og:description', 'twitter:description'].includes(name)) {
      metaDescription = attrValue(tag, 'content');
      if (metaDescription) break;
    }
  }

  const headings = [];
  const headingRegex = /<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi;
  let headingMatch;
  while ((headingMatch = headingRegex.exec(raw)) && headings.length < 80) {
    const text = stripHtmlTags(headingMatch[2]);
    if (text) headings.push({ level: Number(headingMatch[1]), text });
  }

  const links = [];
  const linkRegex = /<a\b[^>]*href\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi;
  let linkMatch;
  while ((linkMatch = linkRegex.exec(raw)) && links.length < 120) {
    const href = decodeHtmlEntities(linkMatch[2] || linkMatch[3] || linkMatch[4] || '');
    const text = stripHtmlTags(linkMatch[5]).slice(0, 160);
    if (!href || /^(javascript:|mailto:|tel:)/i.test(href)) continue;
    try {
      links.push({ text: text || href, url: new URL(href, finalUrl || sourceUrl).toString() });
    } catch (_) {}
  }

  const tableData = parseTablesFromHtml(raw);
  const pageText = htmlToPlainText(raw).slice(0, 30000);
  const fieldCount = headings.length + links.length + (metaDescription ? 1 : 0);
  const quality = pageText.length > 1000 || tableData.length > 0
    ? 'good'
    : pageText.length > 120 || fieldCount > 0
      ? 'fair'
      : 'poor';

  return {
    url: finalUrl || sourceUrl,
    pageUrl: finalUrl || sourceUrl,
    pageTitle: pageTitle || finalUrl || sourceUrl,
    billId: 'URL',
    fields: {
      source: 'url-fetch',
      originalUrl: sourceUrl,
      finalUrl: finalUrl || sourceUrl,
      contentType,
      metaDescription,
      headingCount: headings.length,
      linkCount: links.length
    },
    headings,
    links,
    tableData,
    attachments: [],
    embeddedFiles: [],
    pageText,
    quality,
    extractionMode: 'url-fetch',
    extractedAt: new Date().toISOString()
  };
}

async function fetchWebPageContext(rawUrl) {
  const url = normalizeFetchUrl(rawUrl);
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 20000);
  try {
    const response = await fetch(url, {
      method: 'GET',
      redirect: 'follow',
      headers: {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0 Safari/537.36',
        'Accept': 'text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
      },
      signal: controller.signal
    });
    if (!response.ok) throw new Error(`网页请求失败：HTTP ${response.status}`);
    const contentLength = Number(response.headers.get('content-length') || 0);
    if (contentLength > 5 * 1024 * 1024) throw new Error('网页过大（>5MB），请上传文件或换用更具体页面');
    const contentType = response.headers.get('content-type') || '';
    const html = await response.text();
    if (html.length > 5 * 1024 * 1024) throw new Error('网页内容过大（>5MB）');
    return parseHtmlPageContext(html, url, response.url || url, contentType);
  } finally {
    clearTimeout(timeoutId);
  }
}

async function ensureOffscreen() {
  const existingContexts = await chrome.runtime.getContexts({
    contextTypes: ['OFFSCREEN_DOCUMENT']
  });
  if (existingContexts.length > 0) return;
  if (offscreenCreating) return;
  offscreenCreating = true;
  try {
    await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['USER_MEDIA'],
      justification: '语音识别需要麦克风访问权限'
    });
  } finally {
    offscreenCreating = false;
  }
}

// 把语音结果转发给 panel
// 来源可能是 content_script（sender.tab 有值）或 offscreen document（sender.tab 为空）
chrome.runtime.onMessage.addListener((msg, sender) => {
  if (['VOICE_RESULT', 'VOICE_ERROR', 'VOICE_STOPPED'].includes(msg.type)) {
    chrome.runtime.sendMessage({ type: 'VOICE_TO_PANEL_RUNTIME', payload: msg }, () => {
      void chrome.runtime.lastError;
    });
  }
});

// ===== 点击图标切换侧边栏 =====
chrome.action.onClicked.addListener((tab) => {
  if (!tab.id || !tab.url) return;
  // 跳过不可注入的页面
  if (isRestrictedPageUrl(tab.url)) {
    return;
  }
  // 先确保 content_script 已注入，再发切换指令
  chrome.scripting.executeScript(
    { target: { tabId: tab.id }, files: ['content_script.js'] },
    () => {
      if (chrome.runtime.lastError) return; // 注入失败忽略（如 PDF 页面）
      setTimeout(() => {
        chrome.tabs.sendMessage(tab.id, { type: 'TOGGLE_PANEL' }, () => {
          void chrome.runtime.lastError; // 忽略可能的无监听器错误
        });
      }, 150);
    }
  );
});

// ===== 消息中转 =====
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {

  // content_script → 存储页面上下文
  // 支持合并 iframe 数据（钉钉文档等场景，内容在 iframe 里）
  if (message.type === 'PAGE_CONTEXT') {
    const newData = message.data || {};
    const isFromIframe = !!newData._fromIframe;
    const isEmbeddedAppend = !!newData._embeddedAppend;
    const contextStorage = getContextStorageArea();
    if (!contextStorage?.get || !contextStorage?.set) {
      sendResponse({ ok: false, error: 'storage unavailable' });
      return true;
    }

    contextStorage.get(['pageContext'], (stored) => {
      const existing = stored.pageContext || {};

      if (isFromIframe || isEmbeddedAppend) {
        // iframe 数据 / 嵌入文件追加：与已有数据合并，内容拼接到 pageText
        const appendText = newData.pageText || newData.text || '';
        const existingText = existing.pageText || existing.text || '';
        const mergeLabel = isEmbeddedAppend ? '--- 嵌入文件内容 ---' : '--- iframe 内容 ---';
        const mergedText = existingText
          ? existingText + '\n\n' + mergeLabel + '\n' + appendText
          : appendText;

        // 合并字段
        const mergedFields = { ...(existing.fields || {}), ...(newData.fields || {}) };
        // 合并表格
        const mergedTables = [...(existing.tableData || []), ...(newData.tableData || [])];
        // 合并嵌入文件信息
        const mergedEmbeddedFiles = [...(existing.embeddedFiles || []), ...(newData.embeddedFiles || [])];

        const merged = {
          ...existing,
          ...newData,
          pageText: mergedText,
          fields: mergedFields,
          tableData: mergedTables,
          embeddedFiles: mergedEmbeddedFiles,
          _fromIframe: undefined,  // 清理标记
          _embeddedAppend: undefined,
          // 保留顶层 URL 和标题
          url: existing.url || newData.url,
          pageTitle: existing.pageTitle || newData.pageTitle,
          // 升级质量
          quality: ['good', 'fair', 'poor'].sort().indexOf(existing.quality) <
                   ['good', 'fair', 'poor'].sort().indexOf(newData.quality)
                   ? newData.quality : existing.quality
        };

        contextStorage.set({
          pageContext: merged,
          sourceTabId: sender.tab?.id
        });
      } else {
        // 主 frame 数据：直接存储
        contextStorage.set({
          pageContext: newData,
          sourceTabId: sender.tab?.id
        });
      }
    });

    sendResponse({ ok: true });
    return true;
  }

  // panel 内的关闭按钮 → 通知 content_script 关闭侧边栏
  if (message.type === 'CLOSE_PANEL_FROM_PANEL') {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      const tab = tabs[0];
      if (!tab?.id) { sendResponse({ ok: false }); return; }
      chrome.tabs.sendMessage(tab.id, { type: 'CLOSE_PANEL' }, () => {
        void chrome.runtime.lastError;
        sendResponse({ ok: true });
      });
    });
    return true;
  }

  // panel → 截图当前 tab
  if (message.type === 'CAPTURE_SCREENSHOT') {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      const tab = tabs[0];
      if (!tab) { sendResponse({ ok: false, error: '未找到当前Tab' }); return; }
      chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png', quality: 85 }, (dataUrl) => {
        if (chrome.runtime.lastError) {
          sendResponse({ ok: false, error: chrome.runtime.lastError.message });
        } else {
          sendResponse({ ok: true, dataUrl });
        }
      });
    });
    return true;
  }

  // panel → 主动注入 content_script 并触发数据提取
  if (message.type === 'INJECT_AND_EXTRACT') {
    (async () => {
      try {
        const context = await safeExtractActivePage();
        sendResponse({
          ok: true,
          mode: context.extractionMode || 'safe-snapshot',
          quality: context.quality,
          textLength: context.pageText?.length || 0
        });
      } catch (e) {
        sendResponse({ ok: false, error: e.message || String(e) });
      }
    })();
    return true;
  }

  // panel/Skill → 解析用户粘贴的产品页面、竞品页面、需求文档网页 URL
  if (message.type === 'FETCH_WEB_PAGE') {
    (async () => {
      try {
        const context = await fetchWebPageContext(message.url);
        if (message.save !== false) {
          await setContextStorage({ pageContext: context, sourceTabId: sender.tab?.id || null });
        }
        sendResponse({
          ok: true,
          context,
          textLength: context.pageText?.length || 0,
          quality: context.quality
        });
      } catch (e) {
        sendResponse({ ok: false, error: e.message || String(e) });
      }
    })();
    return true;
  }

  // 常规页面注入提取（封装为内部函数）
  function _normalInjectAndExtract(tabId, sendResponse) {
    chrome.scripting.executeScript(
      { target: { tabId }, files: ['content_script.js'] },
      () => {
        if (chrome.runtime.lastError) {
          sendResponse({ ok: false, error: '注入失败: ' + chrome.runtime.lastError.message });
          return;
        }
        setTimeout(() => {
          // 仅保留兼容旧入口；新读取路径使用 safeExtractActivePage。
          chrome.tabs.sendMessage(tabId, { type: 'REFRESH_CONTEXT' }, () => {
            void chrome.runtime.lastError;
          });
          sendResponse({ ok: true });
        }, 800);
      }
    );
  }

  // panel → 语音控制
  // 优先使用 offscreen document（更稳定，不受页面限制），失败时降级到 content_script
  if (['VOICE_START', 'VOICE_STOP', 'VOICE_PAUSE', 'VOICE_RESUME'].includes(message.type)) {
    chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
      const tab = tabs[0];
      if (!tab?.id) { sendResponse({ ok: false }); return; }

      // 方式1：尝试 offscreen document（稳定，不受页面限制）
      if (message.type === 'VOICE_START') {
        try {
          await ensureOffscreen();
          // 给 offscreen 发消息
          chrome.runtime.sendMessage({ target: 'offscreen', type: 'VOICE_START' }, (resp) => {
            if (chrome.runtime.lastError) {
              // offscreen 不可用，降级到 content_script
              console.warn('[background] offscreen 语音不可用，降级到 content_script:', chrome.runtime.lastError.message);
              _fallbackVoiceToContentScript(tab.id, message, sendResponse);
            } else {
              sendResponse({ ok: true });
            }
          });
        } catch (e) {
          console.warn('[background] ensureOffscreen 失败，降级到 content_script:', e.message);
          _fallbackVoiceToContentScript(tab.id, message, sendResponse);
        }
      } else {
        // STOP/PAUSE/RESUME：同时发两个路径（不知道当前用哪个）
        // 发给 offscreen
        try {
          chrome.runtime.sendMessage({ target: 'offscreen', type: message.type }, () => {
            void chrome.runtime.lastError;
          });
        } catch (e) {}
        // 也发给 content_script（兼容旧路径）
        chrome.tabs.sendMessage(tab.id, message, () => {
          void chrome.runtime.lastError;
          sendResponse({ ok: true });
        });
      }
    });
    return true;
  }

  // 语音降级：通过 content_script 执行语音识别
  function _fallbackVoiceToContentScript(tabId, message, sendResponse) {
    chrome.tabs.sendMessage(tabId, message, () => {
      if (chrome.runtime.lastError) {
        sendResponse({ ok: false, error: '语音识别不可用: ' + chrome.runtime.lastError.message });
      } else {
        sendResponse({ ok: true });
      }
    });
  }

  // panel → 向当前 tab content_script 转发消息
  if (message.type === 'SEND_TO_CONTENT') {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      const tab = tabs[0];
      if (!tab?.id) { sendResponse({ ok: false, error: '未找到当前Tab' }); return; }
      chrome.tabs.sendMessage(tab.id, message.payload, (resp) => {
        if (chrome.runtime.lastError) {
          sendResponse({ ok: false, error: chrome.runtime.lastError.message });
        } else {
          sendResponse({ ok: true, data: resp });
        }
      });
    });
    return true;
  }

  // content_script → 跨域获取嵌入文件内容（PDF/Office 文件）
  if (message.type === 'FETCH_FILE') {
    (async () => {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 30000); // 30秒超时
      try {
        const response = await fetch(message.url, { 
          method: 'GET',
          // Service Worker 可以绕过 CORS 限制
          mode: 'cors',
          signal: controller.signal
        });
        if (!response.ok) {
          clearTimeout(timeoutId);
          sendResponse({ ok: false, error: `HTTP ${response.status}` });
          return;
        }
        // 限制大小：最大 20MB，避免内存溢出
        const contentLength = parseInt(response.headers.get('content-length') || '0', 10);
        if (contentLength > 20 * 1024 * 1024) {
          clearTimeout(timeoutId);
          sendResponse({ ok: false, error: '文件过大（>20MB）' });
          return;
        }
        const arrayBuffer = await response.arrayBuffer();
        if (arrayBuffer.byteLength > 20 * 1024 * 1024) {
          clearTimeout(timeoutId);
          sendResponse({ ok: false, error: '文件过大（>20MB）' });
          return;
        }
        // 转为 base64 传输（MessageChannel 不支持 ArrayBuffer 直接传递）
        const bytes = new Uint8Array(arrayBuffer);
        let binary = '';
        // 分批拼接减少字符串重新分配（>1MB 时性能显著提升）
        const CHUNK_SIZE = 8192;
        for (let i = 0; i < bytes.byteLength; i += CHUNK_SIZE) {
          const end = Math.min(i + CHUNK_SIZE, bytes.byteLength);
          for (let j = i; j < end; j++) {
            binary += String.fromCharCode(bytes[j]);
          }
        }
        clearTimeout(timeoutId);
        sendResponse({ ok: true, data: btoa(binary), contentType: response.headers.get('content-type') });
      } catch (e) {
        clearTimeout(timeoutId);
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

// ===== 联网搜索（多引擎降级，Service Worker 绕过 CORS） =====

/**
 * 策略1（主引擎）：Bing 搜索
 * HTML结构：<li class="b_algo"> → <h2><a href="URL">Title</a></h2> → <p>Snippet</p>
 */
async function _searchBing(query, maxResults) {
  const bingUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
  const resp = await fetch(bingUrl, {
    headers: {
      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
      'Accept': 'text/html,application/xhtml+xml'
    },
    signal: AbortSignal.timeout(12000)
  });
  if (!resp.ok) throw new Error(`Bing HTTP ${resp.status}`);
  const html = await resp.text();
  const results = [];

  // 按 b_algo 块拆分
  const blocks = html.split(/<li class="b_algo"/).slice(1);
  for (const block of blocks) {
    if (results.length >= maxResults) break;
    // 从 <h2> 中提取标题和链接（跳过 tilk 等干扰链接）
    const h2Match = block.match(/<h2[^>]*>[\s\S]*?<a[^>]+href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
    if (!h2Match) continue;
    const url = h2Match[1].trim();
    const title = h2Match[2].replace(/<[^>]+>/g, '').trim();
    if (!title) continue;

    // 提取摘要：优先从 <p> 标签，其次从 b_caption div
    let snippet = '';
    const pMatch = block.match(/<p[^>]*>([\s\S]*?)<\/p>/i);
    if (pMatch) {
      snippet = pMatch[1].replace(/<[^>]+>/g, '').trim();
    }
    if (!snippet) {
      const capMatch = block.match(/class="b_caption">[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i);
      if (capMatch) snippet = capMatch[1].replace(/<[^>]+>/g, '').trim();
    }
    // 截断过长摘要
    if (snippet.length > 200) snippet = snippet.slice(0, 200) + '...';

    results.push({ title, url, snippet });
  }

  return results;
}

/**
 * 策略2：Google 搜索（部分网络可用）
 */
async function _searchGoogle(query, maxResults) {
  const googleUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}&hl=zh-CN`;
  const resp = await fetch(googleUrl, {
    headers: {
      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
    },
    signal: AbortSignal.timeout(12000)
  });
  if (!resp.ok) throw new Error(`Google HTTP ${resp.status}`);
  const html = await resp.text();
  const results = [];

  // Google 结果在 <div class="g"> 中，链接在 <a href> 中，标题在 <h3> 中
  const blocks = html.split(/<div class="g"/).slice(1);
  for (const block of blocks) {
    if (results.length >= maxResults) break;
    const h3Match = block.match(/<h3[^>]*>([\s\S]*?)<\/h3>/i);
    if (!h3Match) continue;
    const title = h3Match[1].replace(/<[^>]+>/g, '').trim();
    if (!title) continue;
    // 找 h3 前面最近的链接
    const h3Idx = block.indexOf(h3Match[0]);
    const beforeH3 = block.substring(Math.max(0, h3Idx - 500), h3Idx);
    const linkMatch = beforeH3.match(/href="(https?:\/\/(?!google\.com|youtube\.com)[^"]+)"/i);
    if (!linkMatch) continue;
    const url = linkMatch[1].trim();
    // 摘要
    let snippet = '';
    const spanMatch = block.match(/<span[^>]*>([\s\S]*?)<\/span>/g);
    if (spanMatch) {
      for (const sp of spanMatch) {
        const t = sp.replace(/<[^>]+>/g, '').trim();
        if (t.length > 30) { snippet = t; break; }
      }
    }
    if (snippet.length > 200) snippet = snippet.slice(0, 200) + '...';
    results.push({ title, url, snippet });
  }

  return results;
}

/**
 * 策略3：DuckDuckGo HTML 搜索（部分网络可用）
 */
async function _searchDuckDuckGo(query, maxResults) {
  const ddgUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
  const resp = await fetch(ddgUrl, {
    headers: {
      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
      'Accept': 'text/html,application/xhtml+xml'
    },
    signal: AbortSignal.timeout(8000)
  });
  if (!resp.ok) throw new Error(`DDG HTTP ${resp.status}`);
  const html = await resp.text();
  const results = [];

  // 解析方式1：result__a + result__snippet
  const blockRegex = /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>)?/gi;
  let m;
  while ((m = blockRegex.exec(html)) !== null) {
    if (results.length >= maxResults) break;
    const href = m[1].trim();
    const title = m[2].replace(/<[^>]+>/g, '').trim();
    const snippet = (m[3] || '').replace(/<[^>]+>/g, '').trim();
    if (href.startsWith('/') || href.includes('duckduckgo.com')) continue;
    if (title) results.push({ title, url: href, snippet });
  }

  return results;
}

/**
 * 联网搜索入口：Bing 优先 → Google → DuckDuckGo
 * Bing 在国内环境最稳定
 */
async function webSearch(query, maxResults = 5) {
  const strategies = [
    { name: 'Bing', fn: () => _searchBing(query, maxResults) },
    { name: 'Google', fn: () => _searchGoogle(query, maxResults) },
    { name: 'DuckDuckGo', fn: () => _searchDuckDuckGo(query, maxResults) }
  ];

  for (const strategy of strategies) {
    try {
      const results = await strategy.fn();
      if (results.length > 0) {
        console.log(`[web-search] ${strategy.name} returned ${results.length} results for "${query}"`);
        return results;
      }
      console.log(`[web-search] ${strategy.name} returned 0 results`);
    } catch (e) {
      console.log(`[web-search] ${strategy.name} failed:`, e.message);
    }
  }

  console.warn(`[web-search] All strategies failed for "${query}"`);
  return [];
}

  // panel/bridge_client → 联网搜索代理（通过 Service Worker 绕过 CORS）
  if (message.type === 'WEB_SEARCH') {
    (async () => {
      try {
        const results = await webSearch(message.query, message.maxResults || 5);
        sendResponse({ ok: true, results });
      } catch (e) {
        sendResponse({ ok: false, results: [], error: e.message });
      }
    })();
    return true;
  }

  if (typeof message.type === 'string' && message.type.startsWith('BROWSER_')) {
    sendResponse({ ok: false, error: '产品经理版已关闭浏览器自动化工具' });
    return true;
  }

  // ===== 浏览器自动化工具（Chrome Extension API 实现） =====
  // 全局变量：追踪当前自动化 tab
  if (typeof automationTabId === 'undefined') {
    // 声明在 onMessage 外部（见文件末尾）
  }

  // BROWSER_OPEN — 打开/导航到URL
  if (message.type === 'BROWSER_OPEN') {
    (async () => {
      try {
        const url = message.url;
        const newTab = message.newTab !== false;

        // 校验 URL
        if (!url || (!url.startsWith('http://') && !url.startsWith('https://'))) {
          sendResponse({ ok: false, error: 'URL 必须以 http:// 或 https:// 开头' });
          return;
        }

        let tab;
        if (newTab || !globalThis._automationTabId) {
          // 创建新 tab
          tab = await chrome.tabs.create({ url, active: true });
          globalThis._automationTabId = tab.id;
        } else {
          // 在现有自动化 tab 中导航
          tab = await chrome.tabs.update(globalThis._automationTabId, { url, active: true });
        }

        // 等待页面加载完成
        const loadedTab = await _waitForTabLoad(tab.id, 15000);

        // 提取页面文本摘要
        let pageText = '';
        let pageTitle = loadedTab.title || '';
        try {
          const results = await chrome.scripting.executeScript({
            target: { tabId: loadedTab.id },
            func: () => {
              return {
                title: document.title,
                text: (document.body?.innerText || '').slice(0, 5000)
              };
            }
          });
          if (results?.[0]?.result) {
            pageText = results[0].result.text || '';
            pageTitle = results[0].result.title || pageTitle;
          }
        } catch (e) {
          // 提取失败不阻断
          pageText = '[页面内容提取失败: ' + e.message + ']';
        }

        sendResponse({
          ok: true,
          data: {
            tabId: loadedTab.id,
            url: loadedTab.url || url,
            title: pageTitle,
            text: pageText.slice(0, 2000)
          }
        });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_CLICK — 点击元素
  if (message.type === 'BROWSER_CLICK') {
    (async () => {
      try {
        const tabId = message.tabId || globalThis._automationTabId;
        if (!tabId) { sendResponse({ ok: false, error: '没有活动的自动化 Tab，请先使用 browser.open' }); return; }

        const results = await chrome.scripting.executeScript({
          target: { tabId },
          func: (selector) => {
            const el = document.querySelector(selector);
            if (!el) return { success: false, error: '元素未找到: ' + selector };
            el.scrollIntoView({ behavior: 'instant', block: 'center' });
            el.click();
            return { success: true, tagName: el.tagName, text: (el.textContent || '').slice(0, 100) };
          },
          args: [message.selector]
        });

        // 等待页面响应
        await _sleep(800);

        const result = results?.[0]?.result || { success: false, error: '脚本执行无返回' };
        sendResponse({ ok: true, data: result });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_FILL — 填写表单
  if (message.type === 'BROWSER_FILL') {
    (async () => {
      try {
        const tabId = message.tabId || globalThis._automationTabId;
        if (!tabId) { sendResponse({ ok: false, error: '没有活动的自动化 Tab，请先使用 browser.open' }); return; }

        const results = await chrome.scripting.executeScript({
          target: { tabId },
          func: (selector, value) => {
            const el = document.querySelector(selector);
            if (!el) return { success: false, error: '元素未找到: ' + selector };

            // 聚焦并清空
            el.focus();
            if (el.tagName === 'SELECT') {
              el.value = value;
              el.dispatchEvent(new Event('change', { bubbles: true }));
            } else if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
              // 使用 nativeInputValueSetter 触发 React/Vue 等框架的 onChange
              const nativeSetter = Object.getOwnPropertyDescriptor(
                window.HTMLInputElement.prototype, 'value'
              )?.set || Object.getOwnPropertyDescriptor(
                window.HTMLTextAreaElement.prototype, 'value'
              )?.set;
              if (nativeSetter) {
                nativeSetter.call(el, value);
              } else {
                el.value = value;
              }
              el.dispatchEvent(new Event('input', { bubbles: true }));
              el.dispatchEvent(new Event('change', { bubbles: true }));
            } else {
              // contenteditable 等场景
              el.textContent = value;
              el.dispatchEvent(new Event('input', { bubbles: true }));
            }
            return { success: true, selector, value: value.slice(0, 50) };
          },
          args: [message.selector, message.value]
        });

        const result = results?.[0]?.result || { success: false, error: '脚本执行无返回' };
        sendResponse({ ok: true, data: result });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_GET_TEXT — 获取页面/元素文本
  if (message.type === 'BROWSER_GET_TEXT') {
    (async () => {
      try {
        const tabId = message.tabId || globalThis._automationTabId;
        if (!tabId) { sendResponse({ ok: false, error: '没有活动的自动化 Tab，请先使用 browser.open' }); return; }

        const results = await chrome.scripting.executeScript({
          target: { tabId },
          func: (selector) => {
            if (selector) {
              const el = document.querySelector(selector);
              if (!el) return { text: '', error: '元素未找到: ' + selector };
              return { text: (el.innerText || el.textContent || '').slice(0, 8000) };
            }
            return { text: (document.body?.innerText || '').slice(0, 8000), title: document.title };
          },
          args: [message.selector || null]
        });

        const result = results?.[0]?.result || { text: '' };
        sendResponse({ ok: true, data: result });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_SCREENSHOT — 截图
  if (message.type === 'BROWSER_SCREENSHOT') {
    (async () => {
      try {
        const tabId = message.tabId || globalThis._automationTabId;
        if (!tabId) { sendResponse({ ok: false, error: '没有活动的自动化 Tab，请先使用 browser.open' }); return; }

        const tab = await chrome.tabs.get(tabId);
        const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png', quality: 85 });

        // 同时提取页面文本摘要（LLM 无法看图，文字更有用）
        let pageText = '';
        try {
          const textResults = await chrome.scripting.executeScript({
            target: { tabId },
            func: () => (document.body?.innerText || '').slice(0, 3000)
          });
          pageText = textResults?.[0]?.result || '';
        } catch (_) {}

        sendResponse({
          ok: true,
          data: {
            screenshotTaken: true,
            text: pageText,
            msg: '截图完成（图片已保存，以下为页面文字内容）'
          }
        });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_WAIT — 等待元素出现
  if (message.type === 'BROWSER_WAIT') {
    (async () => {
      try {
        const tabId = message.tabId || globalThis._automationTabId;
        if (!tabId) { sendResponse({ ok: false, error: '没有活动的自动化 Tab，请先使用 browser.open' }); return; }

        const timeout = message.timeout || 5000;
        const results = await chrome.scripting.executeScript({
          target: { tabId },
          func: (selector, timeoutMs) => {
            return new Promise((resolve) => {
              const el = document.querySelector(selector);
              if (el) { resolve({ found: true, immediate: true }); return; }

              const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                  observer.disconnect();
                  clearTimeout(timer);
                  resolve({ found: true });
                }
              });
              observer.observe(document.body, { childList: true, subtree: true });

              const timer = setTimeout(() => {
                observer.disconnect();
                resolve({ found: false, error: '等待超时 (' + timeoutMs + 'ms)' });
              }, timeoutMs);
            });
          },
          args: [message.selector, timeout]
        });

        const result = results?.[0]?.result || { found: false, error: '脚本执行无返回' };
        sendResponse({ ok: true, data: result });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_CLOSE — 关闭自动化 tab
  if (message.type === 'BROWSER_CLOSE') {
    (async () => {
      try {
        const tabId = message.tabId || globalThis._automationTabId;
        if (tabId) {
          await chrome.tabs.remove(tabId);
          if (globalThis._automationTabId === tabId) {
            globalThis._automationTabId = null;
          }
        }
        sendResponse({ ok: true, data: { success: true } });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  // BROWSER_LIST_TABS — 列出当前窗口所有 tab（方便 LLM 选择操作目标）
  if (message.type === 'BROWSER_LIST_TABS') {
    (async () => {
      try {
        const tabs = await chrome.tabs.query({ currentWindow: true });
        const tabList = tabs.map(t => ({
          id: t.id,
          title: (t.title || '').slice(0, 100),
          url: (t.url || '').slice(0, 200),
          active: t.active
        }));
        sendResponse({ ok: true, data: { tabs: tabList, automationTabId: globalThis._automationTabId } });
      } catch (e) {
        sendResponse({ ok: false, error: e.message });
      }
    })();
    return true;
  }

  return true;
});

// ===== 浏览器自动化：Tab 关闭清理 =====
chrome.tabs.onRemoved.addListener((tabId) => {
  if (globalThis._automationTabId === tabId) {
    globalThis._automationTabId = null;
  }
});

// ===== 浏览器自动化辅助函数 =====

/**
 * 等待 tab 加载完成
 * @param {number} tabId
 * @param {number} timeout - 超时毫秒
 * @returns {Promise<chrome.tabs.Tab>}
 */
async function _waitForTabLoad(tabId, timeout = 15000) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      chrome.tabs.onUpdated.removeListener(listener);
      // 超时不报错，返回当前 tab 状态
      chrome.tabs.get(tabId).then(t => resolve(t)).catch(() => reject(new Error('Tab 加载超时')));
    }, timeout);

    const listener = (updatedTabId, changeInfo, tab) => {
      if (updatedTabId !== tabId) return;
      if (changeInfo.status === 'complete') {
        clearTimeout(timer);
        chrome.tabs.onUpdated.removeListener(listener);
        resolve(tab);
      }
    };
    chrome.tabs.onUpdated.addListener(listener);
  });
}

/**
 * 延时工具函数
 */
function _sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// ===== 浏览器自动化全局状态 =====
globalThis._automationTabId = null;
