(function () {
  const RAW_CAPTURE_OBSERVED = 'raw_capture_observed';
  const DETAIL_PAGE_PATH = /\/job_detail\/([^/?#]+)\.html$/i;
  const state = {
    timer: 0,
    observer: null,
    installed: false,
    lastClickedJobUrl: '',
    lastSignature: ''
  };

  function isBossPage() {
    const hostname = window.location.hostname;
    return hostname === 'zhipin.com' || hostname.endsWith('.zhipin.com');
  }

  function normalizeText(value) {
    return String(value || '')
      .replace(/\u00a0/g, ' ')
      .replace(/[ \t]+/g, ' ')
      .replace(/\n\s*\n\s*\n+/g, '\n\n')
      .trim();
  }

  function isVisible(element) {
    if (!(element instanceof HTMLElement)) return false;
    const style = window.getComputedStyle(element);
    const rect = element.getBoundingClientRect();
    return style.display !== 'none'
      && style.visibility !== 'hidden'
      && rect.width > 0
      && rect.height > 0;
  }

  function readFirst(root, selectors, maxLength, excluded) {
    const excludedValues = excluded || [];
    for (const selector of selectors) {
      for (const element of root.querySelectorAll(selector)) {
        if (!isVisible(element)) continue;
        const text = normalizeText(stripInlineWatermarks(element));
        if (text && !excludedValues.includes(text)) return text.slice(0, maxLength || 5000);
      }
    }
    return '';
  }

  /**
   * 从 panel 里找一个看起来像公司名的文字。BOSS 详情页公司名常挂在：
   * - `.company-name` / `[class*="company-name"]` 类节点
   * - `a[href*="/gongsi/"]` 这种站内公司详情链接
   * - 详情页头部"职位名称下方"那段（如果是 `<a>` 包着的）
   * 上面的 selector 全不命中时，最后兜底：搜 panel 里所有 `<a>`，找最短的、非职位名、非"立即沟通"的文字。
   */
  function fallbackCompanyName(root, knownTitle) {
    const candidates = root.querySelectorAll('a[href*="/gongsi/"], a[href*="/company/"]');
    for (const link of candidates) {
      if (!isVisible(link)) continue;
      const text = normalizeText(stripInlineWatermarks(link));
      if (!text || text === knownTitle) continue;
      if (text.length < 1 || text.length > 30) continue;
      // 公司名通常是 2-20 个汉字（含有限公司 / 集团 / 公司等）
      if (/^[一-龥A-Za-z0-9·\-\s（）()]{2,30}$/.test(text)) return text;
    }
    return '';
  }

  /**
   * 招聘网站对部分文字（薪资、敏感词）用字符级水印遮罩：每个字符一个 span，
   * 内联样式把真字符 font-size:0 / visibility:hidden / 1em×1em 占位。
   * 我们克隆节点后把这些水印 span 移除，再读 textContent 拿真字符。
   */
  function stripInlineWatermarks(node) {
    if (!node || !node.cloneNode) return node && node.textContent ? node.textContent : '';

    const clone = node.cloneNode(true);
    const watermarkSelectors = [
      // 字符水印通用：内联样式让字符不可见 / 字号为 0 / 极小尺寸
      'span[style*="font-size: 0"]',
      'span[style*="font-size:0"]',
      'span[style*="visibility: hidden"]',
      'span[style*="visibility:hidden"]',
      'span[style*="display: none"]',
      'span[style*="display:none"]',
      'span[style*="width: 0"]',
      'span[style*="width:0"]',
      'span[style*="height: 0"]',
      'span[style*="height:0"]',
      'span[style*="opacity: 0"]',
      'span[style*="opacity:0"]',
      'span[style*="text-indent: -"]',
      'span[style*="text-indent:-"]',
      'span[style*="overflow: hidden"]',
      'span[style*="overflow:hidden"]'
    ];
    watermarkSelectors.forEach(function (selector) {
      clone.querySelectorAll(selector).forEach(function (span) {
        span.parentNode && span.parentNode.removeChild(span);
      });
    });

    // 移除内联随机 class 名 + 1em×1em 的 inline-block 字符（CSS 噪音 span）
    // 例如 .mDfEmdZiF { display: inline-block; font-size: 0 !important; width: 1em; height: 1em; visibility: hidden; }
    // 这种 span 通常是单字符且没有可见文字
    Array.prototype.slice.call(clone.querySelectorAll('span')).forEach(function (span) {
      const text = (span.textContent || '').trim();
      if (text) return;
      const style = span.getAttribute('style') || '';
      if (
        /display:\s*inline-block/i.test(style)
        && (/font-size:\s*0/.test(style) || /visibility:\s*hidden/i.test(style) || /opacity:\s*0/.test(style))
      ) {
        span.parentNode && span.parentNode.removeChild(span);
      }
    });

    return clone.textContent || '';
  }

  function sanitizeDescription(text) {
    if (!text) return '';

    let cleaned = String(text);

    // 1. 去掉"举报" + "扫码分享" 等 BOSS 详情页顶部的反爬话术
    cleaned = cleaned
      .replace(/举报[\s\S]{0,12}微信[\s\S]{0,8}扫码[\s\S]{0,6}分享[^\n]*\n?/g, '')
      .replace(/扫码分享[^\n]*\n?/g, '')
      .replace(/举报[^\n]*\n?/g, '')
      .replace(/分享至[^\n]*\n?/g, '');

    // 2. 去掉登录 / 联系方式 / 查看完整等占位提示
    cleaned = cleaned
      .replace(/登录后查看完整内容[^\n]*\n?/g, '')
      .replace(/查看完整职位描述[^\n]*\n?/g, '')
      .replace(/联系方式[^\n]{0,40}\n?/g, '')
      .replace(/工作地址[^\n]{0,80}\n?/g, '');

    // 3. 去掉招聘经理 / HR / "今日活跃" / "去App与BOSS随时沟通" 等模块
    cleaned = cleaned
      .replace(/今日活跃[^\n]*\n?/g, '')
      .replace(/去App与BOSS随时沟通[^\n]*\n?/g, '')
      .replace(/前往App与BOSS随时沟通[^\n]*\n?/g, '')
      .replace(/与BOSS随时沟通[^\n]*\n?/g, '')
      .replace(/招聘经理[^\n]*\n?/g, '')
      .replace(/人事经理[^\n]*\n?/g, '')
      .replace(/HR\s*[·|｜][^\n]*\n?/g, '')
      .replace(/[A-Za-z一-龥]{2,4}\s*·\s*人事[一-龥]{0,6}[^\n]*\n?/g, '')
      .replace(/[A-Za-z一-龥]{2,4}\s*·\s*招聘[一-龥]{0,6}[^\n]*\n?/g, '');

    // 4. 去掉"BOSS直聘" / "kanzhun" / "看准" 等被 inline 嵌入的品牌词
    cleaned = cleaned
      .replace(/BOSS\s*直聘\s*/g, '')
      .replace(/boss\s*zhipin/gi, '')
      .replace(/kanzhun/gi, '')
      .replace(/看准网/gi, '')
      .replace(/智联招聘\s*/g, '');

    // 5. 删掉剩余的内联 CSS 文本（前面没被 stripInlineWatermarks 剥掉的尾巴）
    cleaned = cleaned.replace(/\.[a-zA-Z]{6,12}\s*\{[^}]*display\s*:\s*inline-block[^}]*\}/g, '');
    cleaned = cleaned.replace(/\{display\s*:\s*inline-block[\s\S]*?\}\.[a-zA-Z]{6,12}/g, '');
    cleaned = cleaned.replace(/mDfEmdZiF|KaiCsDPa|wwTyFnbQc|xhJccEDSJCHc|RCjbDEKDK|sATBrbrwkja|wRMZQrht|BWnHaryCFKCZ|KirsYtka|NZiCMiaPQaK/gi, '');

    // 6. 去掉单独的"姓名 今日活跃" 这种短串（任何 2-4 个汉字 + 今日活跃）
    cleaned = cleaned.replace(/[一-龥]{2,4}\s*今日活跃[^\n]*\n?/g, '');

    // 7. 合并多空行
    cleaned = cleaned.replace(/\n\s*\n\s*\n+/g, '\n\n');

    return cleaned.trim();
  }

  function readSectionAfterHeading(root, labels) {
    const headings = root.querySelectorAll('h2, h3, h4, [class*="title"]');
    for (const heading of headings) {
      if (!isVisible(heading)) continue;
      const headingText = normalizeText(stripInlineWatermarks(heading));
      if (!labels.some((label) => headingText === label || headingText.indexOf(label) === 0)) continue;

      const parent = heading.parentElement;
      const siblingText = parent && parent.nextElementSibling && normalizeText(stripInlineWatermarks(parent.nextElementSibling));
      if (siblingText && siblingText.length > 10) return siblingText.slice(0, 10000);

      const parentText = parent && normalizeText(stripInlineWatermarks(parent)).replace(headingText, '').trim();
      if (parentText && parentText.length > 10) return parentText.slice(0, 10000);
    }
    return '';
  }

  function readLabels(root, selectors, excluded) {
    const values = [];
    for (const selector of selectors) {
      for (const element of root.querySelectorAll(selector)) {
        if (!isVisible(element)) continue;
        const text = normalizeText(element.textContent);
        if (!text || text.length > 40 || excluded.includes(text) || values.includes(text)) continue;
        values.push(text);
        if (values.length >= 20) return values;
      }
    }
    return values;
  }

  function findJobId(url) {
    let pathname = '';
    try {
      pathname = new URL(url, window.location.href).pathname;
    } catch {
      return '';
    }
    const match = pathname.match(DETAIL_PAGE_PATH);
    if (!match || !match[1]) return '';
    try {
      return decodeURIComponent(match[1]);
    } catch {
      return match[1];
    }
  }

  function findDetailPanel() {
    const headings = document.querySelectorAll('h2, h3, h4, [class*="title"]');
    for (const heading of headings) {
      if (!isVisible(heading) || normalizeText(heading.textContent) !== '职位描述') continue;
      let node = heading;
      let fallback = heading.parentElement;

      for (let depth = 0; depth < 10 && node && node !== document.body; depth += 1) {
        const text = normalizeText(node.textContent);
        const rect = node.getBoundingClientRect();
        const containsAction = text.includes('立即沟通');
        const looksLikeRightPanel = rect.width >= window.innerWidth * 0.35
          && rect.left >= window.innerWidth * 0.2
          && text.length < 30000;

        if (looksLikeRightPanel) fallback = node;
        if (containsAction && looksLikeRightPanel) return node;
        node = node.parentElement;
      }
      return fallback;
    }
    return null;
  }

  function scoreJobLink(link, panel, title, company) {
    if (!isVisible(link)) return -1;
    const text = normalizeText(link.textContent);
    let score = 0;
    if (panel.contains(link)) score += 8;
    if (title && text.includes(title)) score += 6;
    if (company && text.includes(company)) score += 3;
    if (link.href === state.lastClickedJobUrl) score += 5;
    if (link.closest('[class*="active"], [class*="selected"], [aria-current="true"]')) score += 4;
    return score;
  }

  function findJobUrl(panel, title, company) {
    if (DETAIL_PAGE_PATH.test(window.location.pathname)) return window.location.href;

    const links = Array.from(document.querySelectorAll('a[href*="/job_detail/"]'))
      .filter((link) => findJobId(link.href));
    const ranked = links
      .map((link) => ({ link, score: scoreJobLink(link, panel, title, company) }))
      .filter((item) => item.score >= 0)
      .sort((left, right) => right.score - left.score);

    if (ranked.length && ranked[0].score > 0) return ranked[0].link.href;
    if (findJobId(state.lastClickedJobUrl)) return state.lastClickedJobUrl;
    return '';
  }

  function splitMetadata(text) {
    return normalizeText(text)
      .split(/[·|｜]/)
      .map((item) => item.trim())
      .filter(Boolean);
  }

  function pickMetadata(items, pattern) {
    return items.find((item) => pattern.test(item)) || '';
  }

  function collectVisibleJob() {
    if (!isBossPage()) return null;
    const panel = findDetailPanel();
    if (!panel) return null;

    const title = readFirst(panel, [
      '.job-name',
      '[class*="job-name"]',
      '[class*="jobName"]',
      '.job-detail-header h1',
      '.job-primary .name',
      'h1',
      'h2'
    ], 300, ['职位描述', '工作地址']);
    // 薪资字段完全不读：详情面板里 BOSS 的薪资节点被字符水印遮罩，
    // 由 merge 层用列表里第一次抓到的那条干净值。
    const salary = '';
    const company = readFirst(panel, [
      '.company-name',
      '[class*="company-name"]',
      '[class*="companyName"]',
      '.job-company-info .name',
      '.job-company .name',
      '.company-info .name',
      'a[href*="/gongsi/"]',
      '.sider-company .name'
    ], 300) || fallbackCompanyName(panel, title);
    const jobUrl = findJobUrl(panel, title, company);
    const jobId = findJobId(jobUrl);
    const location = readFirst(panel, [
      '.job-area',
      '[class*="job-area"]',
      '.job-detail-header .job-area'
    ], 200);
    const metadataText = readFirst(panel, [
      '.job-detail-header .info',
      '.job-info',
      '[class*="job-info"]',
      '[class*="job-meta"]',
      '.job-primary .info-primary',
      '.job-require'
    ], 500);
    const metadata = splitMetadata(metadataText);
    const description = sanitizeDescription(normalizeText(readFirst(panel, [
      '.job-sec-text',
      '.job-description',
      '.job-detail-section .job-sec-text',
      '.job-detail-section .job-description',
      '[class*="jobDesc"]'
    ], 10000) || readSectionAfterHeading(panel, ['职位描述', '岗位描述', '工作内容'])));
    const welfare = readLabels(panel, [
      '.job-sec-welfare span',
      '[class*="welfare"] span',
      '[class*="benefit"] span'
    ], [title, company, salary]);
    const labels = readLabels(panel, [
      '.job-tag-list span',
      '.job-labels span',
      '[class*="job-tag"] span',
      '[class*="job-label"] span'
    ], [title, company, salary, ...welfare]);

    if (!jobId || !title || (!description && !salary && !company)) return null;

    return {
      encryptJobId: jobId,
      jobName: title,
      brandName: company,
      salaryDesc: salary,
      cityName: location,
      jobDegree: pickMetadata(metadata, /博士|硕士|本科|大专|学历不限|不限/),
      jobExperience: pickMetadata(metadata, /经验不限|在校生|应届生|经验/),
      postDescription: description,
      jobLabels: labels,
      welfareList: welfare,
      url: jobUrl
    };
  }

  function captureVisibleJob() {
    const job = collectVisibleJob();
    if (!job) return;
    const signature = [job.url, job.jobName, job.brandName, job.salaryDesc, job.postDescription.slice(0, 160)].join('|');
    if (signature === state.lastSignature) return;
    state.lastSignature = signature;

    chrome.runtime.sendMessage({
      type: RAW_CAPTURE_OBSERVED,
      payload: {
        url: job.url,
        method: 'DOM',
        responseType: 'dom',
        responseBody: JSON.stringify({ kind: 'job_detail', job }),
        source: 'dom'
      }
    }, function () {
      void chrome.runtime.lastError;
    });
  }

  function scheduleCapture() {
    if (state.timer) return;
    state.timer = window.setTimeout(function () {
      state.timer = 0;
      captureVisibleJob();
    }, 350);
  }

  function rememberClickedJob(event) {
    if (!(event.target instanceof Element)) return;
    let link = event.target.closest('a[href*="/job_detail/"]');
    if (!link) {
      const card = event.target.closest('li, [class*="job-card"], [class*="job-list"]');
      link = card && card.querySelector('a[href*="/job_detail/"]');
    }
    if (link && findJobId(link.href)) state.lastClickedJobUrl = link.href;
    scheduleCapture();
  }

  function install() {
    if (!isBossPage() || state.installed) return;
    if (!document.body) {
      window.addEventListener('DOMContentLoaded', install, { once: true });
      return;
    }

    state.installed = true;
    scheduleCapture();
    state.observer = new MutationObserver(scheduleCapture);
    state.observer.observe(document.body, { childList: true, subtree: true, characterData: true });
    document.addEventListener('click', rememberClickedJob, true);
    window.addEventListener('load', scheduleCapture, { once: true });
    window.addEventListener('popstate', scheduleCapture);
    window.addEventListener('hashchange', scheduleCapture);
  }

  window.addEventListener('pageshow', install);
  install();
}());
