/**
 * content_script.js
 * 注入公开网页/业务页面，负责：
 * 1. 保留安全读取兼容能力（主读取由 background 一次性快照完成）
 * 2. 侧边栏 iframe 管理（滑入/滑出/拖拽调宽）
 * 3. 支持各类消息指令
 */

(function () {
  'use strict';

  const AISC_NS = '__aisc_plugin_runtime__';

  // 防止重复注入（但 REFRESH_CONTEXT 时允许重跑）
  if (window[AISC_NS]?.injected) {
    // 已经注入过：只响应消息，不重复注册
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg.type === 'REFRESH_CONTEXT') {
        sendResponse({ ok: false, error: '请使用 background 安全快照读取' });
        return true;
      }
      if (msg.type === 'FILL_OPINION') {
        const result = fillApprovalOpinion(msg.text, { silent: !!msg.silent });
        sendResponse(result);
        return true;
      }
      if (msg.type === 'SELECT_COPY_TEXT') {
        const text = selectCopyPageText();
        sendResponse({ ok: true, text });
        return true;
      }
      return true;
    });
    return;
  }
  window[AISC_NS] = { injected: true };

  // ===== 侧边栏状态 =====
  const PANEL_ID = '__aisc_panel__';
  const RESIZER_ID = '__aisc_resizer__';
  const STORAGE_WIDTH_KEY = 'aisc_panel_width';
  let panelOpen = false;

  // ===== 侧边栏管理 =====

  function getPanelWidth() {
    try {
      const saved = localStorage.getItem(STORAGE_WIDTH_KEY);
      if (saved) {
        const w = parseInt(saved);
        if (w >= 50) return w;
      }
    } catch (e) {}
    // 默认打开宽度为屏幕的 30%
    return Math.round(window.innerWidth * 0.3);
  }

  function savePanelWidth(w) {
    try { localStorage.setItem(STORAGE_WIDTH_KEY, String(w)); } catch (e) {}
  }

  function injectPanelStyles() {
    if (document.getElementById('__aisc_panel_styles__')) return;
    const style = document.createElement('style');
    style.id = '__aisc_panel_styles__';
    style.textContent = `
      #${PANEL_ID} {
        position: fixed !important;
        top: 0 !important;
        right: 0 !important;
        height: 100vh !important;
        z-index: 2147483647 !important;
        background: #fff !important;
        opacity: 1 !important;
        box-shadow: -4px 0 24px rgba(0,0,0,0.18) !important;
        border-left: none !important;
        transform: translateX(100%) !important;
        transition: transform 0.32s cubic-bezier(0.4,0,0.2,1) !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
      }
      #${PANEL_ID}.open {
        transform: translateX(0) !important;
      }
      #${PANEL_ID} iframe {
        flex: 1 !important;
        width: 100% !important;
        height: 100% !important;
        border: none !important;
        background: #fff !important;
        opacity: 1 !important;
        display: block !important;
      }
      /* ===== 拖拽条：可见指示线 + 悬停/拖拽动效 ===== */
      #${RESIZER_ID} {
        position: fixed !important;
        top: 0 !important;
        width: 20px !important;
        height: 100vh !important;
        cursor: col-resize !important;
        z-index: 2147483648 !important;
        background: transparent !important;
      }
      /* 常驻可见的细线指示器 */
      #${RESIZER_ID}::after {
        content: '' !important;
        position: absolute !important;
        right: 0 !important;
        top: 0 !important;
        bottom: 0 !important;
        width: 2px !important;
        background: rgba(198,161,91,0.15) !important;
        border-radius: 1px !important;
        transition: background 0.3s, width 0.3s, box-shadow 0.3s !important;
      }
      /* 悬停：线条变粗 + 发光 */
      #${RESIZER_ID}:hover {
        background: rgba(198,161,91,0.05) !important;
      }
      #${RESIZER_ID}:hover::after {
        width: 4px !important;
        background: rgba(198,161,91,0.6) !important;
        box-shadow: 0 0 12px rgba(198,161,91,0.28), 0 0 4px rgba(198,161,91,0.46) !important;
      }
      /* 拖拽中：更强光晕 */
      #${RESIZER_ID}.dragging {
        background: rgba(198,161,91,0.08) !important;
      }
      #${RESIZER_ID}.dragging::after {
        width: 5px !important;
        background: #c6a15b !important;
        box-shadow: 0 0 20px rgba(198,161,91,0.42), 0 0 8px rgba(198,161,91,0.62) !important;
      }
    `;
    document.head.appendChild(style);
  }

  function openPanel() {
    if (panelOpen) return;
    injectPanelStyles();

    const w = Math.min(getPanelWidth(), window.innerWidth);
    const panelUrl = chrome.runtime.getURL('panel.html');

    // 创建侧边栏容器
    let panel = document.getElementById(PANEL_ID);
    if (!panel) {
      panel = document.createElement('div');
      panel.id = PANEL_ID;
      panel.style.width = w + 'px';

      const iframe = document.createElement('iframe');
      iframe.src = panelUrl;
      iframe.title = '詹老师的AIPROS';
      iframe.allow = 'clipboard-write; microphone';
      panel.appendChild(iframe);
      document.body.appendChild(panel);
    } else {
      panel.style.width = w + 'px';
    }

    // 创建拖拽条
    let resizer = document.getElementById(RESIZER_ID);
    if (!resizer) {
      resizer = document.createElement('div');
      resizer.id = RESIZER_ID;
      document.body.appendChild(resizer);
      setupResizer(resizer, panel);
    }
    // resizer 起始位置 right:0（与面板 translateX(100%) 同步，均在屏幕右侧外）
    resizer.style.right = '0px';
    resizer.style.transition = 'none';

    // 同步触发面板滑入 + resizer 位移。不要改业务页面 body 布局。
    requestAnimationFrame(() => {
      resizer.style.transition = 'right 0.32s cubic-bezier(0.4,0,0.2,1)';
      resizer.style.right = w + 'px';
      panel.classList.add('open');
    });

    panelOpen = true;
    chrome.runtime.sendMessage({ type: 'PANEL_STATE', open: true });
  }

  function closePanel() {
    if (!panelOpen) return;
    const panel = document.getElementById(PANEL_ID);
    const resizer = document.getElementById(RESIZER_ID);

    // 面板滑出
    if (panel) panel.classList.remove('open');

    // resizer 同步滑回
    if (resizer) {
      resizer.style.transition = 'right 0.32s cubic-bezier(0.4,0,0.2,1)';
      resizer.style.right = '0px';
    }

    // 动画结束后彻底清理（防止快速重开时被误清）
    setTimeout(() => {
      if (panelOpen) return; // 面板已重新打开，跳过清理
      if (resizer) resizer.style.transition = 'none';
    }, 350);

    panelOpen = false;
    chrome.runtime.sendMessage({ type: 'PANEL_STATE', open: false });
  }

  function togglePanel() {
    panelOpen ? closePanel() : openPanel();
  }

  function setupResizer(resizer, panel) {
    let startX, startW;
    let previousUserSelect = '';

    resizer.addEventListener('mousedown', (e) => {
      e.preventDefault();
      startX = e.clientX;
      startW = parseInt(panel.style.width) || getPanelWidth();
      resizer.classList.add('dragging');
      // 拖拽期间禁用 right 过渡，使拖拽即时响应
      resizer.style.transition = 'none';
      previousUserSelect = document.body.style.userSelect || '';
      document.body.style.userSelect = 'none';

      const onMove = (e) => {
        const delta = startX - e.clientX;
        const minW = 0;                    // 最窄 0%
        const maxW = window.innerWidth;     // 最宽 100%
        const newW = Math.max(minW, Math.min(maxW, startW + delta));
        panel.style.width = newW + 'px';
        // resizer 右边缘对齐面板左边缘
        resizer.style.right = newW + 'px';
      };

      const onUp = () => {
        resizer.classList.remove('dragging');
        document.body.style.userSelect = previousUserSelect;

        const finalW = parseInt(panel.style.width);
        // 拖到极窄时自动关闭面板
        if (finalW < 50) {
          closePanel();
        } else {
          savePanelWidth(finalW);
        }

        document.removeEventListener('mousemove', onMove);
        document.removeEventListener('mouseup', onUp);
      };

      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onUp);
    });
  }

  // ===== 主入口 =====
  function init() {
    // 监听面板和 background 发来的指令
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg.type === 'TOGGLE_PANEL') {
        togglePanel();
        sendResponse({ ok: true, open: panelOpen });
        return true;
      }
      if (msg.type === 'OPEN_PANEL') {
        openPanel();
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === 'CLOSE_PANEL') {
        closePanel();
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === 'REFRESH_CONTEXT') {
        sendResponse({ ok: false, error: '请使用 background 安全快照读取' });
        return true;
      }
      if (msg.type === 'FILL_OPINION') {
        const result = fillApprovalOpinion(msg.text, { silent: !!msg.silent });
        sendResponse(result);
        return true;
      }
      if (msg.type === 'SELECT_COPY_TEXT') {
        const text = selectCopyPageText();
        sendResponse({ ok: true, text });
        return true;
      }
      // ===== 语音识别控制（在页面 context 里运行，可以调麦克风）=====
      if (msg.type === 'VOICE_START') {
        voiceStart();
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === 'VOICE_STOP') {
        voiceStop();
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === 'VOICE_PAUSE') {
        voicePause();
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === 'VOICE_RESUME') {
        voiceResume();
        sendResponse({ ok: true });
        return true;
      }
      // background 转发过来的语音结果 → 通过扩展消息通道给 panel
      if (msg.type === 'VOICE_TO_PANEL') {
        _postVoiceToPanel(msg.payload || {});
        return true;
      }
      return true;
    });
  }

  // ===== 语音识别模块（在页面 frame 里运行，可以请求麦克风）=====
  let _voiceRec = null;
  let _voiceRunning = false;
  let _noSpeechCount = 0;       // 连续 no-speech 计数
  const _MAX_NO_SPEECH = 5;    // 超过此次数提示用户

  // 通过扩展消息通道回传给 panel，避免页面脚本伪造 postMessage
  function _postVoiceToPanel(payload) {
    console.log('[voice-cs] send voice payload:', JSON.stringify(payload).slice(0, 200));
    try {
      chrome.runtime.sendMessage({ type: 'VOICE_TO_PANEL_RUNTIME', payload });
    } catch (e) {
      console.warn('[voice-cs] 语音消息发送失败:', e.message);
    }
  }

  function _ensureVoiceRec() {
    if (_voiceRec) return _voiceRec;
    console.log('[voice-cs] _ensureVoiceRec: 创建语音识别实例...');
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) {
      console.error('[voice-cs] SpeechRecognition API 不可用');
      _postVoiceToPanel({ error: 'not-supported' });
      return null;
    }
    console.log('[voice-cs] SpeechRecognition API 可用, lang=zh-CN, continuous=true, interimResults=true');
    const rec = new SR();
    rec.lang = 'zh-CN';
    rec.continuous = true;
    rec.interimResults = true;
    rec.maxAlternatives = 1;

    rec.onresult = (event) => {
      _noSpeechCount = 0; // 收到结果，重置计数器
      let interim = '';
      let finalChunk = '';
      for (let i = event.resultIndex; i < event.results.length; i++) {
        const t = event.results[i][0].transcript;
        if (event.results[i].isFinal) finalChunk += t;
        else interim += t;
      }
      console.log('[voice-cs] onresult: final="' + finalChunk + '" interim="' + interim + '"');
      _postVoiceToPanel({ finalChunk, interim });
    };

    rec.onerror = (event) => {
      console.error('[voice-cs] onerror:', event.error, event.message || '');
      if (event.error === 'no-speech') {
        _noSpeechCount++;
        console.log('[voice-cs] no-speech #' + _noSpeechCount);
        if (_noSpeechCount >= _MAX_NO_SPEECH) {
          _postVoiceToPanel({ error: 'no-speech-repeated', detail: '麦克风已运行但连续' + _noSpeechCount + '次未检测到语音，请检查：1) 麦克风是否正常 2) 网络是否能访问 Google 语音服务' });
          _noSpeechCount = 0;
          _voiceRunning = false;
          return;
        }
        if (_voiceRunning) { try { rec.start(); } catch(e) { console.error('[voice-cs] no-speech 后重启失败:', e); } }
        return;
      }
      if (event.error === 'network') {
        _postVoiceToPanel({ error: 'network', detail: '无法连接语音识别服务（可能需要访问 Google 服务器），请检查网络' });
        _voiceRunning = false;
        return;
      }
      if (event.error === 'audio-capture') {
        _postVoiceToPanel({ error: 'audio-capture', detail: '无法捕获音频，请确认麦克风已授权且未被其他应用占用' });
        _voiceRunning = false;
        return;
      }
      if (event.error !== 'aborted') {
        _postVoiceToPanel({ error: event.error });
      }
      _voiceRunning = false;
      _noSpeechCount = 0;
    };

    rec.onend = () => {
      console.log('[voice-cs] onend, _voiceRunning=' + _voiceRunning);
      if (_voiceRunning) { try { rec.start(); } catch(e) { console.error('[voice-cs] onend 后重启失败:', e); } }
    };

    rec.onstart = () => {
      console.log('[voice-cs] onstart: 语音识别已启动');
      _noSpeechCount = 0;
    };

    _voiceRec = rec;
    return rec;
  }

  function voiceStart() {
    console.log('[voice-cs] voiceStart 被调用');
    const rec = _ensureVoiceRec();
    if (!rec) return;
    _voiceRunning = true;
    _noSpeechCount = 0;
    try {
      rec.start();
      console.log('[voice-cs] rec.start() 成功');
    } catch(e) {
      console.error('[voice-cs] rec.start() 失败:', e);
      _postVoiceToPanel({ error: 'start-failed', detail: '语音识别启动失败: ' + (e.message || e) });
      _voiceRunning = false;
    }
  }

  function voiceStop() {
    _voiceRunning = false;
    if (_voiceRec) { try { _voiceRec.stop(); } catch(e) {} }
    _postVoiceToPanel({ voiceStopped: true });
  }

  function voicePause() {
    if (_voiceRec) { try { _voiceRec.stop(); } catch(e) {} }
  }

  function voiceResume() {
    if (_voiceRec) { try { _voiceRec.start(); } catch(e) {} }
  }

  function sendContext(context) {
    if (!context) return;
    try {
      chrome.runtime.sendMessage({ type: 'PAGE_CONTEXT', data: context });
    } catch (e) {
      // 扩展上下文失效（页面刷新期间），忽略
    }
  }

  /**
   * 等待页面内容稳定（最多等 3 秒）
   * SPA 场景下 DOM 可能在 load 后才渲染完成
   */
  function waitForContent(callback, delayMs = 300) {
    // 如果页面已有足够文字内容，直接执行
    if (document.body && document.body.innerText.length > 200) {
      setTimeout(callback, delayMs);
      return;
    }
    // 否则用 MutationObserver 等 DOM 变化稳定
    let timer = null;
    const observer = new MutationObserver(() => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        observer.disconnect();
        callback();
      }, 500);
    });
    observer.observe(document.body || document.documentElement, {
      childList: true, subtree: true
    });
    // 兜底：3 秒后强制执行
    setTimeout(() => {
      observer.disconnect();
      callback();
    }, 3000);
  }

  // ===== 嵌入文件检测与解析（PDF/Word/Excel 嵌入页面）=====

  /** 检测页面中嵌入的 Office/PDF 文件 URL */
  function detectEmbeddedFileUrls() {
    const urls = new Map(); // url → { type: 'pdf'|'docx'|'xlsx', element: 'embed'|'iframe'|'object' }

    // 1. <embed> 标签（Chrome 内置 PDF 查看器）
    document.querySelectorAll('embed[type="application/pdf"], embed[src$=".pdf"], embed[src*=".pdf?"]').forEach(el => {
      const src = el.src || el.getAttribute('src');
      if (src) urls.set(src, { type: 'pdf', element: 'embed' });
    });

    // 2. <object> 标签
    document.querySelectorAll('object[type="application/pdf"], object[data$=".pdf"], object[data*=".pdf?"]').forEach(el => {
      const data = el.data || el.getAttribute('data');
      if (data) urls.set(data, { type: 'pdf', element: 'object' });
    });

    // 3. <iframe> 标签（PDF / Office Online / Google Viewer）
    document.querySelectorAll('iframe').forEach(el => {
      let src = el.src || el.getAttribute('src');
      if (!src) return;
      src = src.trim();
      if (!src) return;

      // 直接 PDF
      if (/\.pdf(\?|#|$)/i.test(src)) {
        urls.set(src, { type: 'pdf', element: 'iframe' });
        return;
      }
      // Google Docs Viewer 中包含 PDF
      const gviewMatch = src.match(/[?&](?:url|src)=([^&]+)/i);
      if (gviewMatch) {
        const decoded = decodeURIComponent(gviewMatch[1]);
        if (/\.pdf$/i.test(decoded)) {
          urls.set(decoded, { type: 'pdf', element: 'iframe-gview' });
          return;
        }
        if (/\.docx?$/i.test(decoded)) {
          urls.set(decoded, { type: 'docx', element: 'iframe-gview' });
          return;
        }
        if (/\.xlsx?$/i.test(decoded)) {
          urls.set(decoded, { type: 'xlsx', element: 'iframe-gview' });
          return;
        }
      }
      // SharePoint / Office Online Word
      if (/\.docx?(\?|#|$)/i.test(src) || /\.aspx\?.*\.docx/i.test(src)) {
        urls.set(src, { type: 'docx', element: 'iframe' });
      }
      // SharePoint / Office Online Excel
      if (/\.xlsx?(\?|#|$)/i.test(src) || /\.aspx\?.*\.xlsx/i.test(src)) {
        urls.set(src, { type: 'xlsx', element: 'iframe' });
      }
    });

    // 4. 当前页面本身就是 PDF（直接打开了 PDF 文件）
    if (/\.pdf(\?|#|$)/i.test(window.location.href)) {
      urls.set(window.location.href, { type: 'pdf', element: 'current-page' });
    }
    if (/\.docx?$/i.test(window.location.href)) {
      urls.set(window.location.href, { type: 'docx', element: 'current-page' });
    }
    if (/\.xlsx?$/i.test(window.location.href)) {
      urls.set(window.location.href, { type: 'xlsx', element: 'current-page' });
    }

    return urls;
  }

  /** 动态加载 PDF.js 库 */
  async function loadPdfJs() {
    if (window.pdfjsLib) return;
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = chrome.runtime.getURL('libs/pdf.min.js');
      script.onload = () => {
        // 设置 worker
        if (window.pdfjsLib) {
          window.pdfjsLib.GlobalWorkerOptions.workerSrc = chrome.runtime.getURL('libs/pdf.worker.min.js');
        }
        resolve();
      };
      script.onerror = () => reject(new Error('Failed to load PDF.js'));
      document.head.appendChild(script);
    });
  }

  /** 动态加载 Mammoth.js 库（DOCX 解析） */
  async function loadMammoth() {
    if (window.mammoth) return;
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = chrome.runtime.getURL('libs/mammoth.browser.min.js');
      script.onload = resolve;
      script.onerror = () => reject(new Error('Failed to load mammoth.js'));
      document.head.appendChild(script);
    });
  }

  /** 使用 PDF.js 提取 PDF 文本 */
  async function extractPdfText(arrayBuffer) {
    try {
      await loadPdfJs();
      const pdfjsLib = window.pdfjsLib;
      if (!pdfjsLib) throw new Error('PDF.js not loaded');

      const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
      const maxPages = Math.min(pdf.numPages, 50); // 最多解析 50 页
      const textParts = [];

      for (let pageNum = 1; pageNum <= maxPages; pageNum++) {
        const page = await pdf.getPage(pageNum);
        const content = await page.getTextContent();
        const pageText = content.items.map(item => item.str).join(' ');
        if (pageText.trim()) {
          textParts.push(`--- 第 ${pageNum} 页 ---\n${pageText.trim()}`);
        }
      }

      return textParts.join('\n\n').slice(0, 15000);
    } catch (e) {
      console.warn('[AISC] PDF 文本提取失败:', e.message);
      return null;
    }
  }

  /** 使用 Mammoth.js 提取 DOCX 文本 */
  async function extractDocxText(arrayBuffer) {
    try {
      await loadMammoth();
      if (!window.mammoth) throw new Error('mammoth.js not loaded');

      const result = await window.mammoth.extractRawText({ arrayBuffer });
      return result.value.slice(0, 15000);
    } catch (e) {
      console.warn('[AISC] DOCX 文本提取失败:', e.message);
      return null;
    }
  }

  /** 使用 SheetJS 提取 XLSX 文本（libs/xlsx.min.js 已存在） */
  async function extractXlsxText(arrayBuffer) {
    try {
      // 尝试用已有的本地库
      if (typeof XLSX !== 'undefined') {
        return xlsxToText(arrayBuffer);
      }
      // 从 libs/ 动态加载
      await new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = chrome.runtime.getURL('libs/xlsx.min.js');
        script.onload = resolve;
        script.onerror = () => reject(new Error('Failed to load XLSX'));
        document.head.appendChild(script);
      });
      if (typeof XLSX !== 'undefined') {
        return xlsxToText(arrayBuffer);
      }
      return null;
    } catch (e) {
      console.warn('[AISC] XLSX 文本提取失败:', e.message);
      return null;
    }
  }

  function xlsxToText(arrayBuffer) {
    const workbook = XLSX.read(new Uint8Array(arrayBuffer), { type: 'array' });
    const parts = [];
    workbook.SheetNames.forEach(name => {
      const sheet = workbook.Sheets[name];
      const json = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '' });
      if (json.length > 0) {
        parts.push(`--- 工作表: ${name} ---`);
        json.slice(0, 500).forEach(row => {
          const line = row.filter(c => c !== '').map(c => String(c)).join('\t');
          if (line.trim()) parts.push(line);
        });
      }
    });
    return parts.join('\n').slice(0, 15000);
  }

  /** 从 Blob URL 获取 ArrayBuffer（处理 blob:http://... 类型的嵌入） */
  async function fetchBlobUrl(blobUrl) {
    try {
      const response = await fetch(blobUrl);
      if (!response.ok) return null;
      return await response.arrayBuffer();
    } catch (e) {
      return null;
    }
  }

  /** 通过 background.js 跨域获取文件（适用于非同源 URL），带超时保护 */
  async function fetchViaBackground(url) {
    return new Promise((resolve) => {
      let settled = false;
      // 30秒超时保护，防止消息永不返回导致 Promise 悬挂
      const timeout = setTimeout(() => {
        if (!settled) { settled = true; resolve(null); }
      }, 30000);
      try {
        chrome.runtime.sendMessage({ type: 'FETCH_FILE', url }, (resp) => {
          if (settled) return;
          settled = true;
          clearTimeout(timeout);
          if (!resp || !resp.ok) {
            resolve(null);
            return;
          }
          try {
            const binaryStr = atob(resp.data);
            const bytes = new Uint8Array(binaryStr.length);
            for (let i = 0; i < binaryStr.length; i++) {
              bytes[i] = binaryStr.charCodeAt(i);
            }
            resolve(bytes.buffer);
          } catch (e) {
            resolve(null);
          }
        });
      } catch (e) {
        // chrome.runtime.sendMessage 本身抛异常（如扩展上下文失效）
        if (!settled) { settled = true; clearTimeout(timeout); resolve(null); }
      }
    });
  }

  /** 主入口：检测并提取嵌入文件的文本内容 */
  async function extractEmbeddedContent() {
    const fileUrls = detectEmbeddedFileUrls();
    if (fileUrls.size === 0) {
      console.log('[AISC] 未检测到嵌入的 PDF/Office 文件');
      return null;
    }

    console.log('[AISC] 检测到嵌入文件:', fileUrls.size, '个',
      [...fileUrls.entries()].map(([url, info]) => `${info.type}(${info.element}): ${url.slice(0, 80)}`));

    const results = [];

    for (const [url, info] of fileUrls) {
      try {
        let arrayBuffer = null;

        // blob: URL 可直接 fetch
        if (url.startsWith('blob:')) {
          arrayBuffer = await fetchBlobUrl(url);
        } else if (url.startsWith(location.origin) || url.startsWith('/') || url.startsWith('./')) {
          // 同源 URL
          try {
            const resp = await fetch(url);
            if (resp.ok) arrayBuffer = await resp.arrayBuffer();
          } catch (e) {}
        }

        // 跨域 URL → 通过 background.js 代理
        if (!arrayBuffer) {
          arrayBuffer = await fetchViaBackground(url);
        }

        if (!arrayBuffer) {
          console.warn('[AISC] 无法获取文件:', url.slice(0, 80));
          continue;
        }

        // 根据类型选择解析器
        let extractedText = null;
        if (info.type === 'pdf') {
          extractedText = await extractPdfText(arrayBuffer);
        } else if (info.type === 'docx') {
          extractedText = await extractDocxText(arrayBuffer);
        } else if (info.type === 'xlsx') {
          extractedText = await extractXlsxText(arrayBuffer);
        }

        if (extractedText) {
          results.push({
            url,
            type: info.type,
            element: info.element,
            text: extractedText,
            length: extractedText.length
          });
          console.log('[AISC] 嵌入文件解析成功:', info.type, '字数:', extractedText.length,
            '来源:', info.element, url.slice(0, 60));
        }
      } catch (e) {
        console.warn('[AISC] 嵌入文件处理异常:', url.slice(0, 60), e.message);
      }
    }

    return results.length > 0 ? results : null;
  }

  // ===== 智能 Skill 路由：根据页面内容关键词匹配最佳 Skill =====

  /** 
   * 根据页面内容自动检测最适合的 Skill
   * 返回 { name, confidence } — name 是 Skill 文件名，confidence 0-100
   */
  function detectBestSkill(context) {
    const text = [
      context.pageText || '',
      context.pageTitle || '',
      context.url || '',
      Object.values(context.fields || {}).join(' ')
    ].join(' ').toLowerCase();

    // 如果文本内容太少（<20字），无法判断
    if (text.length < 20) return null;

    // === 关键词 → Skill 映射（按优先级排序）===
    const rules = [
      { skill: "PRD生成", keywords: ["prd","产品需求文档","需求文档","功能规格","需求规格","产品需求"], weight: 8 },
      { skill: "需求评审", keywords: ["需求评审","审prd","评审需求","需求质量","验收标准","需求漏洞"], weight: 7 },
      { skill: "技术方案评审", keywords: ["技术方案","架构设计","接口设计","技术评审","可行性","系统设计"], weight: 7 },
      { skill: "运营方案制定", keywords: ["运营方案","活动方案","上线运营","增长方案","拉新","促活","留存","转化"], weight: 6 },
      { skill: "前端项目生成", keywords: ["前端项目","react项目","vue项目","vite项目","tailwind","工程级","前端工程","项目源码","可运行项目","组件库"], weight: 7 },
      { skill: "本地文件Agent", keywords: ["读文件","写文件","查目录","查名单","搜索文件","整理文件","移动文件","复制文件","删除文件","本地agent"], weight: 8 },
      { skill: "浏览器操作Agent", keywords: ["打开页面","点击按钮","填写页面","填表单","自动操作网页","截图","导出pdf","浏览器agent"], weight: 8 },
      { skill: "接口侦察Agent", keywords: ["获取接口信息","分析接口","抓接口","看请求","生成curl","接口清单","openapi","xhr","fetch"], weight: 8 },
      { skill: "Agent执行工作台", keywords: ["agent执行闭环","任务计划","步骤状态","工具调用日志","失败重试","回滚点","权限确认","执行轨迹","checkpoint","审计回放","浏览器验收"], weight: 8 },
      { skill: "Skill运行工作台", keywords: ["运行skill","调用skill","列出skill","选择skill","skill工作台","skill路由","本地skill管理"], weight: 8 },
      { skill: "Skill生成", keywords: ["生成skill","创建skill","写一个skill","把流程做成skill","优化skill","新增技能"], weight: 8 },
      { skill: "Skill评测", keywords: ["评测skill","审一下skill","skill质量检查","skill打分","测试skill","trigger测试"], weight: 8 },
      { skill: "流程图构建", keywords: ["流程图","泳道图","用户流程","业务流程","mermaid","流程梳理"], weight: 6 },
      { skill: "流程文件构建", keywords: ["流程文件","流程文档","sop","流程说明","流程规范"], weight: 6 },
      { skill: "用户故事拆解", keywords: ["用户故事","user story","拆需求","拆epic","story map","backlog"], weight: 6 },
      { skill: "需求优先级排序", keywords: ["优先级","rice","ice","moscow","kano","backlog排序","迭代规划"], weight: 6 }
    ];

    // 计算每个 Skill 的得分
    const scores = rules.map(rule => {
      let hits = 0;
      for (const kw of rule.keywords) {
        const lowerKw = kw.toLowerCase();
        // 检查关键词是否出现在文本中（整词匹配更好）
        const regex = new RegExp(lowerKw.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
        const matches = text.match(regex);
        if (matches) hits += matches.length;
      }
      const score = hits * rule.weight;
      return { skill: rule.skill, hits, score, weight: rule.weight };
    });

    // 按得分降序排序
    scores.sort((a, b) => b.score - a.score);

    // 取最高分，最低命中 2 次才返回
    const best = scores[0];
    if (!best || best.hits < 2) return null;

    // 计算 confidence（0-100）：基于命中次数和最高可能得分
    const maxPossibleScore = best.weight * 10; // 假设每个关键词最多出现10次
    const confidence = Math.min(100, Math.round((best.score / Math.max(maxPossibleScore, 1)) * 100));

    console.log('[AISC] Skill 路由:', best.skill, 
      '命中:', best.hits, '次', '得分:', best.score, '置信度:', confidence + '%',
      '候选:', scores.slice(1, 3).map(s => `${s.skill}(${s.score})`).join(', '));

    if (confidence < 15) return null; // 置信度太低不推荐

    return { name: best.skill, confidence };
  }

  // ===== 抓取页面数据（增强版，多策略覆盖） =====
  function extractPageContext() {
    const url = window.location.href;
    const billId = extractBillId(url);
    let fields = {}, tableData = [], attachments = [], pageText = '';
    try { fields = extractFormFields(); } catch(e) { console.warn('[AISC] extractFormFields error:', e.message); }
    try { tableData = extractTableData(); } catch(e) { console.warn('[AISC] extractTableData error:', e.message); }
    try { attachments = extractAttachments(); } catch(e) { console.warn('[AISC] extractAttachments error:', e.message); }
    try { pageText = extractCleanText(); } catch(e) { console.warn('[AISC] extractCleanText error:', e.message); }

    const result = {
      url,
      billId,
      pageTitle: document.title,
      fields,
      tableData,
      attachments,
      pageText,
      extractedAt: new Date().toISOString(),
      // 智能 Skill 路由推荐
      suggestedSkill: detectBestSkill({ pageText, pageTitle: document.title, url, fields, tableData }),
      // 嵌入文件检测（PDF/Word/Excel 内嵌场景）
      embeddedFiles: (() => {
        const m = detectEmbeddedFileUrls();
        return m.size > 0 ? [...m.entries()].map(([u, info]) => ({ url: u, type: info.type, element: info.element })) : [];
      })(),
      // 质量评估：让 panel 判断是否需要截图兜底
      quality: assessQuality(fields, tableData, pageText)
    };
    console.log('[AISC] 抓取结果 quality=' + result.quality, result);
    return result;
  }

  /** 数据质量评分：good / fair / poor */
  function assessQuality(fields, tableData, pageText) {
    const fieldCount = Object.keys(fields).length;
    const hasTable = tableData.length > 0;
    const textLen = pageText.length;
    if (fieldCount >= 5 || (fieldCount >= 2 && hasTable)) return 'good';
    if (fieldCount >= 2 || textLen > 500) return 'fair';
    // 钉钉/飞书文档等文件页：只要有内容就认为 fair，不触发截图降级
    const host = window.location.hostname || '';
    const isDocPage = host.includes('dingtalk.com') || host.includes('alidocs.com') ||
                      host.includes('feishu.cn') || host.includes('larkoffice.com') ||
                      host.includes('yuque.com') || host.includes('notion.so') ||
                      host.includes('confluence') || host.includes('docs.google.com');
    if (isDocPage && textLen > 50) return 'fair';
    return 'poor';  // 需要截图兜底
  }

  function extractBillId(url) {
    // 从URL参数提取
    const match = url.match(/[?&](?:billId|approvalId|id|formId|processId|taskId)=([^&]+)/i);
    if (match) return decodeURIComponent(match[1]);
    // 从路径提取（/approval/123456 模式）
    const pathMatch = url.match(/\/(?:approval|approve|form|task|process)\/([A-Z0-9\-_]+)/i);
    if (pathMatch) return pathMatch[1];
    // 从页面文字提取（常见单号格式）
    const bodyText = document.body.innerText;
    const textMatch = bodyText.match(/(?:单号|编号|流水号|申请号|NO\.|No\.)[：:\s]*([A-Z0-9\-]{4,30})/);
    return textMatch ? textMatch[1] : 'UNKNOWN';
  }

  function extractFormFields() {
    const result = {};

    // 策略1：data-field 属性（理想情况）
    document.querySelectorAll('[data-field]').forEach(el => {
      const key = el.dataset.field;
      const val = el.innerText.trim() || el.value || '';
      if (key && val) result[key] = val;
    });

    // 策略2：label + 相邻 value（最通用，覆盖大多数OA表单）
    const labelSelectors = [
      'label',
      '.form-label', '.field-label', '.ant-form-item-label label',
      'th', 'td:first-child',
      '[class*="label"]', '[class*="Label"]',
      '[class*="title"]', 'dt',
    ];
    document.querySelectorAll(labelSelectors.join(',')).forEach(label => {
      const labelText = (label.innerText || label.textContent || '').trim().replace(/[：:*\s]+$/, '');
      if (!labelText || labelText.length > 25 || labelText.length < 2) return;

      const candidates = [
        label.nextElementSibling,
        label.parentElement?.nextElementSibling,
        label.closest('tr')?.cells?.[1],
        label.closest('td')?.nextElementSibling,
        label.closest('.ant-form-item')?.querySelector('.ant-form-item-control'),
        label.closest('.form-item')?.querySelector('.form-value, .field-value, input, textarea, select, span:last-child'),
      ].filter(Boolean);

      for (const el of candidates) {
        // 优先读 input/textarea/select 的值
        let value = '';
        const input = el.querySelector('input, textarea, select') || (el.tagName === 'INPUT' ? el : null);
        if (input) {
          value = input.value?.trim() || input.innerText?.trim() || '';
        } else {
          value = el.innerText?.trim() || '';
        }
        if (value && value !== labelText && value.length < 500) {
          result[labelText] = value;
          break;
        }
      }
    });

    // 策略3：扫描所有 input/textarea/select 的 name/placeholder
    document.querySelectorAll('input[name], textarea[name], select[name]').forEach(el => {
      if (el.type === 'hidden' || el.type === 'password') return;
      const key = el.getAttribute('name') || el.getAttribute('placeholder') || el.id;
      const val = el.value?.trim() || '';
      if (key && val && !result[key]) result[key] = val;
    });

    // 策略4：dl/dt/dd 结构（常见描述列表）
    document.querySelectorAll('dl').forEach(dl => {
      const dts = dl.querySelectorAll('dt');
      const dds = dl.querySelectorAll('dd');
      dts.forEach((dt, i) => {
        const key = dt.innerText.trim().replace(/[：:]+$/, '');
        const val = dds[i]?.innerText.trim() || '';
        if (key && val && key.length < 25) result[key] = val;
      });
    });

    return result;
  }

  function extractTableData() {
    const tables = [];
    document.querySelectorAll('table').forEach((table, idx) => {
      // 跳过布局用的表格（行数少且无 th）
      if (table.querySelectorAll('th').length === 0 &&
          table.querySelectorAll('tr').length < 2) return;

      const headers = [];
      // 同时识别 thead th 和第一行 th/td 作为表头
      const headerRow = table.querySelector('thead tr') || table.querySelector('tr');
      if (headerRow) {
        headerRow.querySelectorAll('th, td').forEach(cell => {
          headers.push(cell.innerText.trim());
        });
      }

      const rows = [];
      const dataRows = table.querySelectorAll('tbody tr');
      const allRows = dataRows.length > 0 ? dataRows : table.querySelectorAll('tr:not(:first-child)');

      allRows.forEach(tr => {
        const row = {};
        let hasData = false;
        tr.querySelectorAll('td').forEach((td, i) => {
          const key = headers[i] || `列${i + 1}`;
          const val = td.innerText.trim();
          if (val) { row[key] = val; hasData = true; }
        });
        if (hasData) rows.push(row);
      });

      if (rows.length > 0) {
        tables.push({ tableIndex: idx, headers, rows: rows.slice(0, 50) });
      }
    });
    return tables;
  }

  function extractAttachments() {
    const attachments = [];
    const seen = new Set();
    const sel = [
      'a[href*=".pdf"]', 'a[href*=".xlsx"]', 'a[href*=".docx"]', 'a[href*=".doc"]',
      'a[href*=".xls"]', 'a[href*=".ppt"]', 'a[href*=".pptx"]', 'a[href*=".zip"]',
      '[class*="attach"]', '[class*="file"]', '[class*="upload"]',
    ].join(',');
    document.querySelectorAll(sel).forEach(el => {
      const name = (el.innerText || el.textContent || '').trim() || el.getAttribute('title') || el.getAttribute('href') || '附件';
      const href = el.getAttribute('href') || '';
      const key = name + href;
      if (!seen.has(key)) {
        seen.add(key);
        attachments.push({ name, href });
      }
    });
    return attachments;
  }

  /** 提取干净正文（去除导航、脚本等噪音） */
  function extractCleanText() {
    const host = window.location.hostname;
    const isDingdocPage = host.includes('dingtalk.com') || host.includes('alidocs.com');
    const isLarkPage = host.includes('feishu.cn') || host.includes('larkoffice.com') || host.includes('bytedance.net');

    if (isDingdocPage || isLarkPage) {
      // === 钉钉/飞书文档：多策略暴力提取 ===
      console.log('[AISC] 检测到钉钉/飞书文档页面，启动多策略提取');

      // 策略1：contenteditable（钉钉/飞书文档编辑区几乎都有此属性）
      try {
        const editableEls = document.querySelectorAll('[contenteditable="true"]');
        for (const el of editableEls) {
          const text = (el.innerText || '').trim();
          if (text.length > 50) {
            console.log('[AISC] contenteditable 提取成功，字数:', text.length);
            return text.slice(0, 8000);
          }
        }
      } catch (e) {}

      // 策略2：已知 CSS 选择器（覆盖各版本钉钉文档 DOM）
      try {
        const docSelectors = [
          '.docs-editor-content',        // 钉钉文档编辑区
          '.dingtalk-editor',
          '[data-doc-content]',
          '.lark-content',               // 部分钉钉文档复用 lark 渲染器
          '.sheet-content',              // 钉钉表格
          '.et-table-container',
          '[class*="spreadsheet"]',
          '[class*="docContent"]',
          '[class*="editorContent"]',
          '[class*="table-container"]',
          '.ProseMirror',                // ProseMirror 编辑器（新版钉钉/飞书）
          '.doc-editor',                 // 通用文档编辑器
          '.docx-editor',
          '[data-content-editable-root]', // 飞书文档
          '.block-edit',                 // 飞书 block 编辑器
          '.render-unit-wrapper',        // 钉钉文档渲染单元
          '.text-wrapper',               // 钉钉文档文本块
          '.title-wrapper',              // 钉钉文档标题块
        ];
        for (const sel of docSelectors) {
          const el = document.querySelector(sel);
          if (el && el.innerText && el.innerText.trim().length > 50) {
            console.log('[AISC] 选择器命中:', sel, '字数:', el.innerText.trim().length);
            return el.innerText.trim().slice(0, 8000);
          }
        }
      } catch (e) {}

      // 策略3：深度遍历 Shadow DOM。安全模式下不主动进入 iframe。
      try {
        const shadowText = extractFromShadowDOM(document.body);
        if (shadowText.length > 50) {
          console.log('[AISC] Shadow DOM 提取成功，字数:', shadowText.length);
          return shadowText.slice(0, 8000);
        }
      } catch (e) {}

      // 策略4：直接读 document.body.innerText（只读，无选区副作用）
      try {
        const rawText = (document.body?.innerText || '').trim();
        if (rawText.length > 50) {
          console.log('[AISC] body innerText 提取成功，字数:', rawText.length);
          return rawText.slice(0, 8000);
        }
      } catch (e) {}

      console.warn('[AISC] 钉钉/飞书文档：所有策略均未提取到内容');
    }

    // === 通用路径：克隆body，移除干扰元素 ===
    if (!document.body) {
      console.warn('[AISC] document.body 不存在，无法提取文本');
      return '';
    }
    const clone = document.body.cloneNode(true);
    clone.querySelectorAll('script, style, nav, header, footer, [class*="menu"], [class*="nav"]')
         .forEach(el => el.remove());
    const text = clone.innerText || '';
    return text.slice(0, 8000).trim();
  }

  /** 深度遍历 Shadow DOM，收集所有可见文本 */
  function extractFromShadowDOM(root) {
    let result = '';
    const walk = (node) => {
      if (!node) return;
      // 如果节点有 shadowRoot，遍历 shadowRoot
      if (node.shadowRoot) {
        const shadowText = (node.shadowRoot.innerText || '').trim();
        if (shadowText) result += shadowText + '\n';
        // 递归遍历 shadowRoot 内部
        node.shadowRoot.querySelectorAll('*').forEach(walk);
      }
      // 递归遍历子节点
      if (node.querySelectorAll) {
        node.querySelectorAll('*').forEach(walk);
      }
    };
    walk(root);
    return result.trim();
  }

  // ===== 填写分析结果到页面文本框 =====
  function fillApprovalOpinion(text, options = {}) {
    const result = fillTextIntoBestEditable(text, {
      clear: options.clear !== false,
      selector: options.selector || '',
      intent: options.intent || 'opinion'
    });

    if (!options.silent) {
      chrome.runtime.sendMessage({
        type: 'FILL_RESULT',
        ok: !!result.ok,
        msg: result.ok ? undefined : (result.error || '未找到可填写的文本框，请手动复制')
      });
    }
    return result;
  }

  function fillTextIntoBestEditable(text, options = {}) {
    const value = String(text || '');
    const target = findFillTarget(options);
    if (!target) return { ok: false, error: '未找到可填写的文本框' };

    try {
      target.scrollIntoView?.({ behavior: 'smooth', block: 'center', inline: 'nearest' });
      target.focus?.({ preventScroll: true });

      const tag = getTagName(target);
      if (tag === 'SELECT') {
        setSelectValue(target, value);
      } else if (tag === 'INPUT' || tag === 'TEXTAREA') {
        setInputLikeValue(target, value, options);
      } else if (isRichTextEditable(target)) {
        setContentEditableValue(target, value, options);
      } else if ('value' in target) {
        setInputLikeValue(target, value, options);
      } else {
        return { ok: false, error: '目标元素不是可填写控件' };
      }

      return {
        ok: true,
        tagName: tag.toLowerCase(),
        label: getElementMetaText(target).slice(0, 120),
        length: value.length
      };
    } catch (e) {
      return { ok: false, error: e.message || String(e) };
    }
  }

  function findFillTarget(options = {}) {
    if (options.selector) {
      const selected = queryDeep(document, options.selector);
      return isEditableControl(selected) ? selected : null;
    }

    const active = document.activeElement;
    if (isEditableControl(active)) return active;

    const selectors = [
      'textarea[name*="opinion" i]', 'textarea[name*="remark" i]', 'textarea[name*="comment" i]',
      'textarea[name*="advice" i]', 'textarea[name*="suggest" i]',
      'textarea[placeholder*="意见"]', 'textarea[placeholder*="建议"]', 'textarea[placeholder*="备注"]',
      'textarea[placeholder*="说明"]', 'textarea[aria-label*="意见"]', 'textarea[aria-label*="备注"]',
      'input[name*="opinion" i]', 'input[name*="remark" i]', 'input[name*="comment" i]',
      'input[placeholder*="意见"]', 'input[placeholder*="建议"]', 'input[placeholder*="备注"]',
      '[contenteditable="true"][aria-label*="意见"]', '[contenteditable="true"][aria-label*="备注"]',
      '[role="textbox"][aria-label*="意见"]', '[role="textbox"][aria-label*="备注"]',
      '.remark-input textarea', '.opinion-input textarea', '.comment-input textarea',
      '.ProseMirror', '[contenteditable="true"]', '[role="textbox"]',
      'textarea', 'input[type="text"]', 'input:not([type])'
    ];

    const preferred = [];
    for (const selector of selectors) {
      const found = queryDeep(document, selector);
      if (isEditableControl(found)) preferred.push(found);
    }

    const candidates = uniqueElements([
      ...preferred,
      ...collectDeepElements(document).filter(isEditableControl)
    ]);
    candidates.sort((a, b) => scoreFillTarget(b) - scoreFillTarget(a));
    return candidates[0] || null;
  }

  function getTagName(el) {
    return (el?.tagName || '').toUpperCase();
  }

  function getElementWindow(el) {
    return el?.ownerDocument?.defaultView || window;
  }

  function uniqueElements(items) {
    return Array.from(new Set(items.filter(Boolean)));
  }

  function isVisibleEnough(el) {
    if (!el || el.nodeType !== 1) return false;
    const style = getElementWindow(el).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;
  }

  function isRichTextEditable(el) {
    return !!el && (el.isContentEditable || el.getAttribute?.('contenteditable') === 'true' || el.getAttribute?.('role') === 'textbox');
  }

  function isEditableControl(el) {
    if (!isVisibleEnough(el)) return false;
    if (el.disabled || el.readOnly || el.getAttribute?.('aria-disabled') === 'true') return false;
    const tag = getTagName(el);
    if (tag === 'TEXTAREA' || tag === 'SELECT') return true;
    if (tag === 'INPUT') {
      const type = String(el.type || 'text').toLowerCase();
      return !['button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'range', 'reset', 'submit'].includes(type);
    }
    return isRichTextEditable(el);
  }

  function getElementMetaText(el) {
    if (!el) return '';
    return [
      el.getAttribute?.('aria-label'),
      el.getAttribute?.('placeholder'),
      el.getAttribute?.('name'),
      el.getAttribute?.('id'),
      el.getAttribute?.('title'),
      el.labels?.[0]?.innerText,
      el.closest?.('label')?.innerText,
      el.closest?.('[aria-label]')?.getAttribute?.('aria-label')
    ].filter(Boolean).join(' ');
  }

  function scoreFillTarget(el) {
    let score = 0;
    const tag = getTagName(el);
    const meta = getElementMetaText(el).toLowerCase();
    const keywords = ['意见', '建议', '备注', '说明', '评论', '审批', 'opinion', 'comment', 'remark', 'advice', 'suggest', 'note'];
    if (tag === 'TEXTAREA') score += 60;
    if (tag === 'INPUT') score += 40;
    if (isRichTextEditable(el)) score += 45;
    if (document.activeElement === el) score += 80;
    if (keywords.some(keyword => meta.includes(keyword.toLowerCase()))) score += 120;
    const rect = el.getBoundingClientRect();
    score += Math.min(30, Math.round((rect.width * rect.height) / 5000));
    return score;
  }

  function queryDeep(root, selector) {
    if (!root) return null;
    try {
      const direct = root.querySelector?.(selector);
      if (direct) return direct;
    } catch (_) {
      return null;
    }

    let all = [];
    try { all = Array.from(root.querySelectorAll?.('*') || []); } catch (_) {}
    for (const el of all) {
      if (el.shadowRoot) {
        const found = queryDeep(el.shadowRoot, selector);
        if (found) return found;
      }
      if ((getTagName(el) === 'IFRAME' || getTagName(el) === 'FRAME') && el.contentDocument) {
        try {
          const found = queryDeep(el.contentDocument, selector);
          if (found) return found;
        } catch (_) {}
      }
    }
    return null;
  }

  function collectDeepElements(root, out = []) {
    if (!root) return out;
    try {
      for (const el of Array.from(root.querySelectorAll?.('*') || [])) {
        out.push(el);
        if (el.shadowRoot) collectDeepElements(el.shadowRoot, out);
        if ((getTagName(el) === 'IFRAME' || getTagName(el) === 'FRAME') && el.contentDocument) {
          try { collectDeepElements(el.contentDocument, out); } catch (_) {}
        }
      }
    } catch (_) {}
    return out;
  }

  function dispatchFillEvents(el, data) {
    const win = getElementWindow(el);
    try {
      el.dispatchEvent(new win.InputEvent('beforeinput', {
        bubbles: true,
        cancelable: true,
        inputType: 'insertText',
        data
      }));
    } catch (_) {}
    try {
      el.dispatchEvent(new win.InputEvent('input', {
        bubbles: true,
        inputType: 'insertText',
        data
      }));
    } catch (_) {
      el.dispatchEvent(new win.Event('input', { bubbles: true }));
    }
    el.dispatchEvent(new win.Event('change', { bubbles: true }));
    try {
      el.dispatchEvent(new win.KeyboardEvent('keyup', { bubbles: true, key: 'Unidentified' }));
    } catch (_) {}
  }

  function setInputLikeValue(el, value, options = {}) {
    const win = getElementWindow(el);
    const tag = getTagName(el);
    const finalValue = options.clear === false ? String(el.value || '') + value : value;
    const proto = tag === 'TEXTAREA' ? win.HTMLTextAreaElement?.prototype : win.HTMLInputElement?.prototype;
    const setter = Object.getOwnPropertyDescriptor(proto || {}, 'value')?.set;
    if (setter) setter.call(el, finalValue);
    else el.value = finalValue;
    dispatchFillEvents(el, value);
  }

  function setSelectValue(el, value) {
    const wanted = String(value).trim();
    const options = Array.from(el.options || []);
    const option = options.find(item => item.value === wanted) ||
      options.find(item => (item.textContent || '').trim() === wanted) ||
      options.find(item => (item.textContent || '').includes(wanted));
    if (!option) throw new Error('未找到匹配的下拉选项: ' + wanted);
    el.value = option.value;
    dispatchFillEvents(el, option.value);
  }

  function setContentEditableValue(el, value, options = {}) {
    const doc = el.ownerDocument || document;
    const win = doc.defaultView || window;
    let inserted = false;
    try {
      const selection = win.getSelection();
      const range = doc.createRange();
      range.selectNodeContents(el);
      if (options.clear === false) range.collapse(false);
      selection.removeAllRanges();
      selection.addRange(range);
      inserted = !!doc.execCommand?.('insertText', false, value);
    } catch (_) {}

    if (!inserted) {
      el.textContent = options.clear === false ? (el.textContent || '') + value : value;
    }
    dispatchFillEvents(el, value);
  }

  // ===== 全选复制获取页面文本（层2.5：比截图轻量，比DOM解析更完整） =====
  /**
   * 读取页面可见文本。旧版会模拟全选，容易影响飞书/钉钉编辑器焦点；
   * 现在只做无副作用的 innerText 读取。
   */
  function selectCopyPageText() {
    try {
      return (document.body?.innerText || '').slice(0, 10000).trim();
    } catch (e) {
      return '';
    }
  }

  // ===== 启动 =====
  // 如果在 iframe 内，直接退出。安全底座默认不再全 frame 注入和后台抓取。
  const isInIframe = window !== window.top;
  if (isInIframe) {
    return;
  }

  if (document.readyState === 'complete') {
    init();
  } else {
    window.addEventListener('load', init);
  }

})();
