(function () {
  const MESSAGE_TYPE = {
    RAW_CAPTURE_OBSERVED: 'raw_capture_observed',
    GET_JOBS: 'get_jobs'
  };

  const BRIDGE_SOURCE = 'job-collector-bridge';
  const ZHILIAN_AUTO_HOVER_SELECTOR = '.jobinfo__name-row';
  const BOSS_AUTO_CLICK_SELECTOR = 'a[href*="/job_detail/"]';
  const BOSS_DETAIL_PAGE_PATH = /\/job_detail\/[^/?#]+\.html$/i;
  const AUTO_HOVER_INTERVAL_MS = 220;
  const AUTO_CLICK_INTERVAL_MS = 900;
  const AUTO_HOVER_PAUSE_MS = 2500;
  const AUTO_HOVER_JOBS_CACHE_MS = 2500;
  const AUTO_HOVER_RETRY_MS = 45000;
  const AUTO_SCROLL_STEP_RATIO = 0.82;
  const AUTO_SCROLL_SETTLE_MS = 180;
  const AUTO_SCROLL_BOTTOM_THRESHOLD_PX = 24;
  const PAGE_CONTEXT_POLL_MS = 1000;
  /** 给详情接口一点时间发出后再收起预览弹层 */
  const HOVER_PREVIEW_DISMISS_DELAY_MS = 140;
  const HOVER_PREVIEW_STYLE_ID = 'job-collector-hide-hover-preview';
  /** BOSS 是虚拟滚动，scrollHeight 稳定后也要继续向下滚一段去找未补全的卡片 */
  const BOSS_SCROLL_FORCE_MAX_STEPS = 6;
  const BOSS_SCROLL_FORCE_STEP_RATIO = 0.6;
  /** 一次 auto-hover 会话最多自动补全多少条；用户重新滚动/翻页后会再开 30 条 */
  const AUTO_HOVER_SESSION_LIMIT = 30;

  const autoHoverState = {
    enabled: false,
    timerId: null,
    pageWatchTimerId: null,
    dismissTimerId: null,
    pausedUntil: 0,
    ignorePauseUntil: 0,
    bottomReached: false,
    /** BOSS 虚拟滚动时，连续"scrollHeight 不变但还有未补全"的滚动步数；超限则停 */
    bossForceScrollCount: 0,
    /** 本次 auto-hover 会话已自动补全的条数；达到 AUTO_HOVER_SESSION_LIMIT 就停 */
    sessionCompletedCount: 0,
    currentPageUrl: window.location.href,
    currentListSignature: '',
    attemptedAtByJobId: {},
    jobsCache: {
      fetchedAt: 0,
      jobs: []
    },
    mutationObserver: null
  };

  function log(level, module, action, message, meta) {
    const payload = meta || {};
    const prefix = `[job-collector][${module}][${action}]`;
    const method = level === 'debug' ? console.debug : console[level];
    method(prefix, message, payload);
  }

  function injectBridgeScript() {
    if (document.documentElement.dataset.jobCollectorBridgeInjected === 'true') {
      return;
    }

    const script = document.createElement('script');
    script.src = chrome.runtime.getURL('page-bridge.js');
    script.dataset.jobCollectorBridge = 'true';
    script.onload = function () {
      script.remove();
    };

    (document.head || document.documentElement).appendChild(script);
    document.documentElement.dataset.jobCollectorBridgeInjected = 'true';
  }

  function isObservedCaptureMessage(data) {
    return Boolean(
      data &&
        typeof data === 'object' &&
        data.source === BRIDGE_SOURCE &&
        data.type === MESSAGE_TYPE.RAW_CAPTURE_OBSERVED &&
        data.payload
    );
  }

  function listenBridgeMessages() {
    window.addEventListener('message', function (event) {
      if (event.source !== window || !isObservedCaptureMessage(event.data)) {
        return;
      }

      chrome.runtime.sendMessage(
        {
          type: MESSAGE_TYPE.RAW_CAPTURE_OBSERVED,
          payload: event.data.payload
        },
        function (response) {
          const runtimeError = chrome.runtime.lastError;

          if (runtimeError) {
            log('error', 'capture', 'forward_capture_failed', 'Failed to forward observed capture to background.', {
              error: runtimeError.message
            });
            return;
          }

          if (!response) {
            log(
              'warn',
              'capture',
              'forward_capture_empty_response',
              'Background returned empty response for observed capture.'
            );
          }
        }
      );
    });
  }

  injectBridgeScript();
  listenBridgeMessages();

  function getCurrentPlatform() {
    const hostname = window.location.hostname;
    if (hostname === 'zhipin.com' || hostname.endsWith('.zhipin.com')) return 'boss';
    if (hostname === 'zhaopin.com' || hostname.endsWith('.zhaopin.com')) return 'zhilian';
    return '';
  }

  function isSearchResultPage() {
    if (getCurrentPlatform() === 'boss') {
      // 详情页（/job_detail/*.html）一定不是搜索结果页，哪怕它有"相关职位"模块
      if (BOSS_DETAIL_PAGE_PATH.test(window.location.pathname)) {
        return false;
      }
      return getAllRows().length > 0;
    }
    return window.location.pathname.indexOf('/sou/') !== -1;
  }

  function scheduleAutoHover(delay) {
    if (autoHoverState.timerId !== null) {
      window.clearTimeout(autoHoverState.timerId);
    }

    autoHoverState.timerId = window.setTimeout(runAutoHoverLoop, delay);
  }

  function schedulePageWatch() {
    if (autoHoverState.pageWatchTimerId !== null) {
      return;
    }

    autoHoverState.pageWatchTimerId = window.setInterval(function () {
      if (!autoHoverState.enabled) {
        return;
      }

      checkPageContextChanges('poll');
    }, PAGE_CONTEXT_POLL_MS);
  }

  function clearPageWatch() {
    if (autoHoverState.pageWatchTimerId === null) {
      return;
    }

    window.clearInterval(autoHoverState.pageWatchTimerId);
    autoHoverState.pageWatchTimerId = null;
  }

  function setPausedUntil(nextTimestamp, reason) {
    const previousPausedUntil = autoHoverState.pausedUntil;
    autoHoverState.pausedUntil = Math.max(autoHoverState.pausedUntil, nextTimestamp);

    if (previousPausedUntil >= Date.now()) {
      return;
    }

    log('info', 'auto_hover', 'paused', 'Auto detail completion paused for current user activity.', {
      reason: reason
    });
  }

  function installPauseListeners() {
    const pauseHandler = function (reason) {
      return function (event) {
        if (!autoHoverState.enabled) {
          return;
        }

        if (event && event.isTrusted === false) {
          return;
        }

        if (reason === 'scroll' && Date.now() < autoHoverState.ignorePauseUntil) {
          return;
        }

        setPausedUntil(Date.now() + AUTO_HOVER_PAUSE_MS, reason);
      };
    };

    // 用户主动滚动列表：把会话计数清零，让 auto-hover 再补一轮。
    // 同时监听 wheel 和实际 scroll，兼容 BOSS 自己接管滚轮事件的情况。
    function resumeOnUserScroll(event) {
      if (!autoHoverState.enabled) {
        return;
      }

      if (event && event.isTrusted === false) {
        return;
      }

      // 自动滚动引发的 scroll 事件会带 ignorePauseUntil 标记，跳过
      if (Date.now() < autoHoverState.ignorePauseUntil) {
        return;
      }

      // 只在已到上限时才续；中途滚动不需要重置计数
      if (autoHoverState.sessionCompletedCount < AUTO_HOVER_SESSION_LIMIT) {
        return;
      }

      if (getCurrentPlatform() === 'boss') {
        const root = getBossScrollRoot();
        const target = event && event.target;
        const isListInteraction = isDocumentScrollRoot(root)
          || !target
          || target === root
          || (typeof root.contains === 'function' && root.contains(target));
        if (!isListInteraction) {
          return;
        }
      }

      autoHoverState.sessionCompletedCount = 0;
      autoHoverState.bottomReached = false;
      autoHoverState.bossForceScrollCount = 0;
      autoHoverState.jobsCache.fetchedAt = 0;

      log('info', 'auto_hover', 'session_resumed_by_user', 'User scrolled the list; auto-hover resumed for another session batch.', {
        limit: AUTO_HOVER_SESSION_LIMIT
      });

      // pauseHandler 会保留用户操作后的短暂停顿；这里在暂停结束后再启动，
      // 保证下一批职位已经进入虚拟列表且不会和用户滚动竞争。
      scheduleAutoHover(AUTO_HOVER_PAUSE_MS + 120);
    }

    window.addEventListener('mousemove', pauseHandler('mousemove'), { passive: true });
    window.addEventListener('wheel', pauseHandler('wheel'), { passive: true });
    window.addEventListener('wheel', resumeOnUserScroll, { passive: true });
    // BOSS 的职位列表通常是独立的 overflow 容器，scroll 事件不会冒泡到 window；
    // 用捕获阶段监听，才能同时识别页面滚动和左侧列表的用户滚动。
    document.addEventListener('scroll', pauseHandler('scroll'), { passive: true, capture: true });
    document.addEventListener('scroll', resumeOnUserScroll, { passive: true, capture: true });
    window.addEventListener('mousedown', pauseHandler('mousedown'), { passive: true });
    window.addEventListener('keydown', pauseHandler('keydown'));
    window.addEventListener('blur', pauseHandler('blur'));
    document.addEventListener('visibilitychange', function () {
      if (document.hidden) {
        setPausedUntil(Date.now() + AUTO_HOVER_PAUSE_MS, 'hidden');
      }
    });
  }

  function parseJobIdFromHref(href) {
    const value = String(href || '');
    const bossMatch = value.match(/\/job_detail\/([^/?#]+)\.html/i);
    if (bossMatch) {
      try {
        return decodeURIComponent(bossMatch[1]).toUpperCase();
      } catch {
        return bossMatch[1].toUpperCase();
      }
    }
    const zhilianMatch = value.match(/\/jobdetail\/([A-Z0-9]+)\.htm/i);
    return zhilianMatch ? zhilianMatch[1].toUpperCase() : '';
  }

  function getRowLink(rowElement) {
    if (rowElement.matches && rowElement.matches('a[href*="/job_detail/"], a[href*="/jobdetail/"]')) {
      return rowElement;
    }
    return rowElement.querySelector('a[href*="/job_detail/"], a[href*="/jobdetail/"]');
  }

  function getJobIdFromRow(rowElement) {
    const link = getRowLink(rowElement);
    return link ? parseJobIdFromHref(link.href) : '';
  }

  function isRowVisible(rowElement) {
    const rect = rowElement.getBoundingClientRect();

    if (rect.width === 0 || rect.height === 0) {
      return false;
    }

    const root = getScrollRoot();
    if (isDocumentScrollRoot(root)) {
      return rect.bottom > 0 && rect.top < window.innerHeight;
    }

    const rootRect = root.getBoundingClientRect();
    return rect.bottom > Math.max(0, rootRect.top) && rect.top < Math.min(window.innerHeight, rootRect.bottom);
  }

  function isRetryCoolingDown(jobId) {
    const attemptedAt = autoHoverState.attemptedAtByJobId[jobId];

    if (!attemptedAt) {
      return false;
    }

    return Date.now() - attemptedAt < AUTO_HOVER_RETRY_MS;
  }

  function getAllRows() {
    if (getCurrentPlatform() !== 'boss') {
      return Array.prototype.slice.call(document.querySelectorAll(ZHILIAN_AUTO_HOVER_SELECTOR));
    }

    // 只取"职位列表容器"里的链接；详情页里"相关职位 / 看了还看"模块的链接不算
    const listContainer = findBossListContainer();
    const scope = listContainer || document;
    const seenJobIds = {};
    return Array.prototype.slice
      .call(scope.querySelectorAll(BOSS_AUTO_CLICK_SELECTOR))
      .filter(function (link) {
        const jobId = parseJobIdFromHref(link.href);
        const rect = link.getBoundingClientRect();
        const belongsToLeftList = rect.right <= window.innerWidth * 0.55;
        if (!jobId || !belongsToLeftList || seenJobIds[jobId]) return false;
        seenJobIds[jobId] = true;
        return true;
      });
  }

  /**
   * 寻找 BOSS 直聘列表页左侧的职位列表容器。
   * 详情页的"相关职位"模块不应该被命中。
   */
  function findBossListContainer() {
    const candidates = [
      '.job-list-container',
      '.job-list-box',
      '.job-list-wrap',
      '[class*="job-list"]',
      '[class*="jobList"]',
      '.search-job-result',
      '.job-primary',
      '.job-list-wrapper'
    ];
    for (const selector of candidates) {
      const elements = document.querySelectorAll(selector);
      for (const element of elements) {
        const rect = element.getBoundingClientRect();
        // 列表容器必须在视口左半部分、且包含足够多的职位链接
        if (rect.width === 0 || rect.height === 0) continue;
        if (rect.right > window.innerWidth * 0.55) continue;
        const linkCount = element.querySelectorAll(BOSS_AUTO_CLICK_SELECTOR).length;
        if (linkCount >= 2) return element;
      }
    }
    return null;
  }

  function getAutoCompleteSelectorLabel() {
    return getCurrentPlatform() === 'boss' ? BOSS_AUTO_CLICK_SELECTOR : ZHILIAN_AUTO_HOVER_SELECTOR;
  }

  function buildListSignature(rows) {
    const ids = rows
      .slice(0, 8)
      .map(function (rowElement) {
        return getJobIdFromRow(rowElement);
      })
      .filter(Boolean);

    return `${window.location.href}::${rows.length}::${ids.join('|')}`;
  }

  function getDocumentScrollRoot() {
    return document.scrollingElement || document.documentElement;
  }

  function isDocumentScrollRoot(root) {
    return root === document.scrollingElement || root === document.documentElement || root === document.body;
  }

  function hasScrollableOverflow(element) {
    if (!element || element === document.body) {
      return false;
    }

    const hasScrollRange = element.scrollHeight - element.clientHeight > 2;
    if (!hasScrollRange) {
      return false;
    }

    const style = window.getComputedStyle(element);
    return /auto|scroll|overlay/i.test(`${style.overflowY} ${style.overflow}`);
  }

  /**
   * BOSS 搜索页的职位列表是独立滚动区，不能只滚 document。
   * 从列表容器向上寻找真正拥有 scrollTop 的祖先，找不到时才回退到页面滚动。
   */
  function getBossScrollRoot() {
    const listContainer = findBossListContainer();
    if (!listContainer) {
      return getDocumentScrollRoot();
    }

    let candidate = listContainer;
    while (candidate && candidate !== document.body) {
      if (hasScrollableOverflow(candidate)) {
        return candidate;
      }
      candidate = candidate.parentElement;
    }

    return getDocumentScrollRoot();
  }

  function getScrollRoot() {
    return getCurrentPlatform() === 'boss' ? getBossScrollRoot() : getDocumentScrollRoot();
  }

  function getScrollViewportHeight(root) {
    return isDocumentScrollRoot(root) ? window.innerHeight : root.clientHeight;
  }

  function canScrollFurther() {
    const root = getScrollRoot();
    const remaining = root.scrollHeight - (root.scrollTop + getScrollViewportHeight(root));
    return remaining > AUTO_SCROLL_BOTTOM_THRESHOLD_PX;
  }

  function scrollForward(stepRatio) {
    const ratio = typeof stepRatio === 'number' && stepRatio > 0 ? stepRatio : AUTO_SCROLL_STEP_RATIO;
    const root = getScrollRoot();
    const viewportHeight = getScrollViewportHeight(root);
    const maxScrollTop = Math.max(root.scrollHeight - viewportHeight, 0);
    const nextScrollTop = Math.min(
      root.scrollTop + Math.max(viewportHeight * ratio, 240),
      maxScrollTop
    );

    if (nextScrollTop <= root.scrollTop + 2) {
      return false;
    }

    autoHoverState.ignorePauseUntil = Date.now() + AUTO_SCROLL_SETTLE_MS;
    if (isDocumentScrollRoot(root)) {
      window.scrollTo({
        top: nextScrollTop,
        behavior: 'auto'
      });
    } else if (typeof root.scrollTo === 'function') {
      root.scrollTo({
        top: nextScrollTop,
        behavior: 'auto'
      });
    } else {
      root.scrollTop = nextScrollTop;
    }

    log('info', 'auto_hover', 'page_scrolled', 'Scrolled down to continue completing job details.', {
      nextScrollTop: nextScrollTop,
      ratio: ratio,
      root: isDocumentScrollRoot(root) ? 'document' : root.className || root.tagName
    });

    return true;
  }

  function pauseAtBottom() {
    if (autoHoverState.bottomReached) {
      return;
    }

    autoHoverState.bottomReached = true;

    if (autoHoverState.timerId !== null) {
      window.clearTimeout(autoHoverState.timerId);
      autoHoverState.timerId = null;
    }

    log('info', 'auto_hover', 'bottom_reached', 'Reached the bottom of the current results page. Waiting for page changes.', {
      selector: getAutoCompleteSelectorLabel()
    });
  }

  function resumeFromPageChange(reason) {
    if (!autoHoverState.enabled || !autoHoverState.bottomReached) {
      return;
    }

    autoHoverState.bottomReached = false;
    autoHoverState.jobsCache.fetchedAt = 0;

    log('info', 'auto_hover', 'resumed', 'Detected page changes and resumed automatic detail completion.', {
      reason: reason
    });

    scheduleAutoHover(120);
  }

  function resetPageState(reason) {
    autoHoverState.bottomReached = false;
    autoHoverState.pausedUntil = Date.now() + 120;
    autoHoverState.ignorePauseUntil = 0;
    autoHoverState.bossForceScrollCount = 0;
    autoHoverState.jobsCache.fetchedAt = 0;
    autoHoverState.attemptedAtByJobId = {};
    autoHoverState.currentPageUrl = window.location.href;
    autoHoverState.currentListSignature = buildListSignature(getAllRows());

    log('info', 'auto_hover', 'page_context_reset', 'Detected a refreshed or changed results page and reset auto detail completion state.', {
      reason: reason,
      url: window.location.href
    });
  }

  function checkPageContextChanges(reason) {
    const rows = getAllRows();
    const nextSignature = buildListSignature(rows);
    const urlChanged = autoHoverState.currentPageUrl !== window.location.href;
    const listChanged = autoHoverState.currentListSignature && autoHoverState.currentListSignature !== nextSignature;

    if (!urlChanged && !listChanged) {
      if (!autoHoverState.currentListSignature && nextSignature) {
        autoHoverState.currentListSignature = nextSignature;
      }
      return;
    }

    resetPageState(urlChanged ? `${reason}_url` : `${reason}_list`);

    if (autoHoverState.enabled) {
      scheduleAutoHover(120);
    }
  }

  function installMutationObserver() {
    if (autoHoverState.mutationObserver) {
      return;
    }

    const targetNode = document.body || document.documentElement;

    if (!targetNode) {
      window.addEventListener(
        'DOMContentLoaded',
        function handleObserverInstall() {
          installMutationObserver();
        },
        { once: true }
      );
      return;
    }

    const observer = new MutationObserver(function (mutations) {
      if (!autoHoverState.enabled) {
        return;
      }

      const hasMeaningfulChange = mutations.some(function (mutation) {
        if (mutation.type !== 'childList') {
          return false;
        }

        return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
      });

      if (!hasMeaningfulChange) {
        return;
      }

      if (autoHoverState.bottomReached) {
        resumeFromPageChange('mutation');
      }

      checkPageContextChanges('mutation');
    });

    observer.observe(targetNode, {
      childList: true,
      subtree: true
    });

    autoHoverState.mutationObserver = observer;
  }

  function requestJobs() {
    return new Promise(function (resolve, reject) {
      chrome.runtime.sendMessage(
        {
          type: MESSAGE_TYPE.GET_JOBS
        },
        function (response) {
          const runtimeError = chrome.runtime.lastError;

          if (runtimeError) {
            reject(new Error(runtimeError.message));
            return;
          }

          if (!response || !response.jobs) {
            resolve([]);
            return;
          }

          resolve(response.jobs);
        }
      );
    });
  }

  async function getJobsCache() {
    if (Date.now() - autoHoverState.jobsCache.fetchedAt < AUTO_HOVER_JOBS_CACHE_MS) {
      return autoHoverState.jobsCache.jobs;
    }

    const jobs = await requestJobs();
    autoHoverState.jobsCache = {
      fetchedAt: Date.now(),
      jobs: Array.isArray(jobs) ? jobs : []
    };

    return autoHoverState.jobsCache.jobs;
  }

  function dispatchHoverEvent(element, type) {
    const rect = element.getBoundingClientRect();
    const event = new MouseEvent(type, {
      bubbles: true,
      cancelable: true,
      view: window,
      clientX: rect.left + Math.min(rect.width / 2, Math.max(rect.width - 6, 1)),
      clientY: rect.top + Math.min(rect.height / 2, Math.max(rect.height - 6, 1))
    });
    element.dispatchEvent(event);
  }

  function ensurePreviewSuppressStyle() {
    if (document.getElementById(HOVER_PREVIEW_STYLE_ID)) {
      return;
    }

    const style = document.createElement('style');
    style.id = HOVER_PREVIEW_STYLE_ID;
    style.textContent = [
      'html.jc-auto-hover [data-jc-preview-hidden="1"] {',
      '  opacity: 0 !important;',
      '  visibility: hidden !important;',
      '  pointer-events: none !important;',
      '}'
    ].join('\n');
    (document.head || document.documentElement).appendChild(style);
  }

  function setPreviewSuppressActive(active) {
    ensurePreviewSuppressStyle();
    document.documentElement.classList.toggle('jc-auto-hover', active);

    if (!active) {
      document.querySelectorAll('[data-jc-preview-hidden="1"]').forEach(function (node) {
        node.removeAttribute('data-jc-preview-hidden');
      });
    }
  }

  function hideVisibleJobPreviews() {
    if (!autoHoverState.enabled) {
      return;
    }

    const candidates = document.querySelectorAll('button, a, span, div');

    for (let i = 0; i < candidates.length; i++) {
      const el = candidates[i];
      const label = (el.textContent || '').replace(/\s+/g, ' ').trim();

      if (label !== '立即投递' && label !== '投递简历') {
        continue;
      }

      let node = el.parentElement;
      for (let depth = 0; depth < 14 && node && node !== document.body; depth++) {
        const text = node.textContent || '';
        if (text.indexOf('职位描述') === -1 && text.indexOf('岗位要求') === -1) {
          node = node.parentElement;
          continue;
        }

        const style = window.getComputedStyle(node);
        const position = style.position;
        const zIndex = parseInt(style.zIndex, 10) || 0;
        const isFloating = position === 'fixed' || position === 'absolute' || zIndex >= 20;

        if (isFloating && text.length < 12000) {
          node.setAttribute('data-jc-preview-hidden', '1');
          break;
        }

        node = node.parentElement;
      }
    }
  }

  function dismissHoverPreview(target) {
    if (!autoHoverState.enabled) {
      return;
    }

    dispatchHoverEvent(target, 'pointerout');
    dispatchHoverEvent(target, 'pointerleave');
    dispatchHoverEvent(target, 'mouseout');
    dispatchHoverEvent(target, 'mouseleave');
    hideVisibleJobPreviews();

    // 弹层可能异步挂载，再扫两次
    window.setTimeout(hideVisibleJobPreviews, 80);
    window.setTimeout(hideVisibleJobPreviews, 220);
  }

  function triggerAutoHover(rowElement, jobId) {
    const link = getRowLink(rowElement);
    const target = link || rowElement;

    if (getCurrentPlatform() === 'boss') {
      autoHoverState.attemptedAtByJobId[jobId] = Date.now();
      target.click();
      log('info', 'auto_hover', 'job_clicked', 'Clicked one visible BOSS job card to reveal its detail panel.', {
        jobId: jobId
      });
      return;
    }

    dispatchHoverEvent(target, 'pointerover');
    dispatchHoverEvent(target, 'mouseover');
    dispatchHoverEvent(target, 'mouseenter');
    dispatchHoverEvent(target, 'mousemove');

    autoHoverState.attemptedAtByJobId[jobId] = Date.now();

    if (autoHoverState.dismissTimerId !== null) {
      window.clearTimeout(autoHoverState.dismissTimerId);
    }

    autoHoverState.dismissTimerId = window.setTimeout(function () {
      autoHoverState.dismissTimerId = null;
      dismissHoverPreview(target);
    }, HOVER_PREVIEW_DISMISS_DELAY_MS);

    log('info', 'auto_hover', 'job_hovered', 'Triggered automatic detail completion for one job.', {
      jobId: jobId
    });
  }

  async function runAutoHoverLoop() {
    if (!autoHoverState.enabled) {
      return;
    }

    if (autoHoverState.bottomReached) {
      return;
    }

    if (!isSearchResultPage()) {
      scheduleAutoHover(AUTO_HOVER_INTERVAL_MS);
      return;
    }

    if (document.hidden || Date.now() < autoHoverState.pausedUntil) {
      scheduleAutoHover(AUTO_HOVER_INTERVAL_MS);
      return;
    }

    try {
      const platform = getCurrentPlatform();
      const rows = getAllRows().filter(isRowVisible);

      if (rows.length === 0) {
        scheduleAutoHover(AUTO_HOVER_INTERVAL_MS);
        return;
      }

      const jobs = await getJobsCache();
      const completedJobIds = {};
      let pendingCount = 0;

      jobs.forEach(function (job) {
        if (!job || job.platform !== platform || !job.platformJobId) {
          return;
        }
        const normalizedId = String(job.platformJobId).toUpperCase();
        if (job.detailCaptured) {
          completedJobIds[normalizedId] = true;
        } else {
          pendingCount += 1;
        }
      });

      const nextRow = rows.find(function (rowElement) {
        const jobId = getJobIdFromRow(rowElement);

        if (!jobId) {
          return false;
        }

        if (completedJobIds[jobId]) {
          return false;
        }

        return !isRetryCoolingDown(jobId);
      });

      if (nextRow) {
        // 找到了未补全的卡片就点一次；点过就重置强制滚动计数
        autoHoverState.bossForceScrollCount = 0;

        if (autoHoverState.sessionCompletedCount >= AUTO_HOVER_SESSION_LIMIT) {
          // 本次会话已达上限；不硬滚、不再触发 list API，
          // 直接停在这里，等用户主动滚动 / 翻页再继续。
          log('info', 'auto_hover', 'session_limit_reached', 'Auto-hover session hit the per-session limit; waiting for user action to resume.', {
            limit: AUTO_HOVER_SESSION_LIMIT
          });
          pauseAtBottom();
          return;
        }

        autoHoverState.sessionCompletedCount += 1;
        triggerAutoHover(nextRow, getJobIdFromRow(nextRow));
        scheduleAutoHover(platform === 'boss' ? AUTO_CLICK_INTERVAL_MS : AUTO_HOVER_INTERVAL_MS);
        return;
      }

      // 当前可见区已没有可点的卡片 → 滚动找下一批
      // BOSS 是虚拟滚动，scrollHeight 可能稳定不变，所以允许有限次的"硬滚动"
      if (platform === 'boss') {
        if (pendingCount === 0) {
          // 所有已知职位都已补全，可以收工
          autoHoverState.bossForceScrollCount = 0;
          pauseAtBottom();
          return;
        }

        if (autoHoverState.sessionCompletedCount >= AUTO_HOVER_SESSION_LIMIT) {
          // 已经做了 30 条但还没找完下一个可点的 row；
          // 不要硬滚触发新的 list API（避免把用户没看到的下一批先加载掉），
          // 等用户主动滚动列表 / 翻页再继续。
          log('info', 'auto_hover', 'session_limit_no_more_scroll', 'Session limit reached and no clickable row visible; halting scroll to preserve user intent.', {
            limit: AUTO_HOVER_SESSION_LIMIT,
            pendingCount: pendingCount
          });
          pauseAtBottom();
          return;
        }

        if (autoHoverState.bossForceScrollCount >= BOSS_SCROLL_FORCE_MAX_STEPS) {
          // 已超出强制滚动次数上限，避免无限循环；等 mutation/手动操作恢复
          log('warn', 'auto_hover', 'boss_scroll_forced_stop', 'No clickable rows but pending jobs remain; pausing to wait for list refresh.', {
            pendingCount: pendingCount,
            forceScrollSteps: autoHoverState.bossForceScrollCount
          });
          pauseAtBottom();
          return;
        }

        if (scrollForward(BOSS_SCROLL_FORCE_STEP_RATIO)) {
          autoHoverState.bossForceScrollCount += 1;
          scheduleAutoHover(AUTO_SCROLL_SETTLE_MS);
        } else {
          // scrollHeight 已经到底；等分页/懒加载触发新内容
          scheduleAutoHover(AUTO_HOVER_INTERVAL_MS);
        }
        return;
      }

      if (canScrollFurther() && scrollForward()) {
        scheduleAutoHover(AUTO_SCROLL_SETTLE_MS);
        return;
      }

      pauseAtBottom();
      return;
    } catch (error) {
      log('error', 'auto_hover', 'run_failed', 'Auto detail completion loop failed.', {
        error: error instanceof Error ? error.message : String(error)
      });
    }

    scheduleAutoHover(AUTO_HOVER_INTERVAL_MS);
  }

  function startAutoHover() {
    if (autoHoverState.enabled) {
      return;
    }

    autoHoverState.enabled = true;
    autoHoverState.pausedUntil = Date.now() + 180;
    autoHoverState.ignorePauseUntil = 0;
    autoHoverState.bottomReached = false;
    autoHoverState.bossForceScrollCount = 0;
    autoHoverState.sessionCompletedCount = 0;
    autoHoverState.currentPageUrl = window.location.href;
    autoHoverState.currentListSignature = buildListSignature(getAllRows());
    autoHoverState.jobsCache.fetchedAt = 0;

    setPreviewSuppressActive(true);

    log('info', 'auto_hover', 'started', 'Auto detail completion started.', {
      selector: getAutoCompleteSelectorLabel()
    });

    schedulePageWatch();
    scheduleAutoHover(80);
  }

  function stopAutoHover() {
    if (!autoHoverState.enabled) {
      return;
    }

    autoHoverState.enabled = false;
    autoHoverState.pausedUntil = 0;
    autoHoverState.ignorePauseUntil = 0;
    autoHoverState.bottomReached = false;
    autoHoverState.bossForceScrollCount = 0;
    autoHoverState.sessionCompletedCount = 0;
    autoHoverState.currentPageUrl = window.location.href;
    autoHoverState.currentListSignature = '';
    autoHoverState.jobsCache.fetchedAt = 0;
    autoHoverState.attemptedAtByJobId = {};

    if (autoHoverState.timerId !== null) {
      window.clearTimeout(autoHoverState.timerId);
      autoHoverState.timerId = null;
    }

    if (autoHoverState.dismissTimerId !== null) {
      window.clearTimeout(autoHoverState.dismissTimerId);
      autoHoverState.dismissTimerId = null;
    }

    setPreviewSuppressActive(false);
    clearPageWatch();

    log('info', 'auto_hover', 'stopped', 'Auto detail completion stopped.');
  }

  function isAutoHoverAllowed(settings) {
    // 采集关闭时强制停止自动补全，避免只 Hover 不入库
    const captureEnabled = !settings || settings.captureEnabled !== false;
    const autoHoverEnabled = Boolean(settings && settings.autoHoverEnabled === true);
    return captureEnabled && autoHoverEnabled;
  }

  function applyAutoHoverSetting(settings) {
    if (isAutoHoverAllowed(settings)) {
      startAutoHover();
      return;
    }

    stopAutoHover();
  }

  function syncAutoHoverSetting() {
    chrome.storage.local.get(['autoHoverEnabled', 'captureEnabled'], function (result) {
      applyAutoHoverSetting(result || {});
    });
  }

  chrome.storage.onChanged.addListener(function (changes, areaName) {
    if (areaName !== 'local') {
      return;
    }

    if (!changes.autoHoverEnabled && !changes.captureEnabled) {
      return;
    }

    chrome.storage.local.get(['autoHoverEnabled', 'captureEnabled'], function (result) {
      applyAutoHoverSetting(result || {});
    });
  });

  installPauseListeners();
  installMutationObserver();
  syncAutoHoverSetting();

  window.addEventListener('pageshow', function () {
    checkPageContextChanges('pageshow');
    syncAutoHoverSetting();
  });

  window.addEventListener('load', function () {
    checkPageContextChanges('load');
    syncAutoHoverSetting();
  });

  window.addEventListener('popstate', function () {
    checkPageContextChanges('popstate');
  });

  window.addEventListener('hashchange', function () {
    checkPageContextChanges('hashchange');
  });

  log('info', 'capture', 'content_loaded', 'Content script loaded and bridge injected.', {
    url: window.location.href
  });

  window.addEventListener('error', function (event) {
    log('error', 'capture', 'content_error', 'Content script runtime error.', {
      error: event.error instanceof Error ? event.error.message : String(event.error)
    });
  });
})();
