/**
 * 关键词屏蔽：精准汉字匹配，效果同超级讨厌（视频纯白遮挡、评论完全折叠）。
 * 扫描策略：限定 MutationObserver 范围、视口优先、增量跳过已判定项。
 */
(function () {
  "use strict";

  if (window.self !== window.top) return;
  if (typeof BiliDom === "undefined" || typeof BiliIndex === "undefined") {
    console.warn("[BiliIndex] keyword-block: 缺少 bili-dom / biliindex-core");
    return;
  }

  const MAIN_COMMENT_SEL = BiliDom.MAIN_COMMENT_SEL || "bili-comment-renderer";
  const REPLY_COMMENT_SEL = BiliDom.REPLY_COMMENT_SEL || "bili-comment-reply-renderer";
  const getShadowRoot = BiliDom.getCommentShadowRoot;
  const queryAllDeep = (root, sel) => BiliDom.queryAllDeep(root, sel);

  const VIDEO_CARD_SEL = [
    ".video-page-card-small",
    ".rec-list-item",
    ".rcmd-item",
    ".bili-video-card",
    ".video-list-card",
    ".video-card-reco",
    "bili-feed-card",
  ].join(", ");

  const VIDEO_SCAN_ROOTS = [
    "#reco_list",
    ".rcmd-list",
    ".recommend-list-v1",
    "#i_cecream",
    ".bili-feed4-layout",
    ".search-page",
    ".search-page-wrapper",
    "#all-list",
    "#user-list",
    ".search-component-result-list",
    ".search-result-container",
    ".video-list.bili-grid",
    ".s-space",
    ".video-list",
    "#comment-app",
    "#comment-module",
  ];

  const RECO_OBSERVE_ROOTS = new Set([
    "#reco_list",
    ".rcmd-list",
    ".recommend-list-v1",
  ]);

  const TITLE_SEL = [
    ".bili-video-card__info--tit",
    ".video-name",
    "h3",
    ".title",
    ".name",
  ];

  const COMMENT_TEXT_SEL = ["#contents", ".reply-content", ".content", ".comment-text"];

  const FAST_SCAN_MS = 120;
  const FULL_SCAN_MS = 280;
  const VIDEO_RECO_FAST_MS = 720;
  const OFFSCREEN_BATCH = 10;

  let keywords = [];
  let ready = false;
  let keywordGen = 0;
  let fastTimer = null;
  let fullTimer = null;
  let fullRaf = 0;
  let watchPageGen = -1;
  let watchPageBlocked = false;

  const observedCommentHosts = new WeakSet();
  const observedScanRoots = new WeakSet();
  const offscreenQueue = [];
  let offscreenIdleScheduled = false;

  function isVideoWatchPage() {
    return /\/video\/(BV|bv|av)/i.test(location.pathname || "");
  }

  function normalizeText(text) {
    return String(text || "")
      .replace(/\s+/g, "")
      .trim();
  }

  function applyKeywords(list) {
    keywords = (list || []).map((k) => String(k || "").trim()).filter(Boolean);
    ready = keywords.length > 0;
    keywordGen += 1;
    watchPageGen = -1;
  }

  function textMatches(text) {
    const t = normalizeText(text);
    if (!t || !ready) return false;
    for (let i = 0; i < keywords.length; i++) {
      if (keywords[i] && t.includes(keywords[i])) return true;
    }
    return false;
  }

  function kwGenTag() {
    return String(keywordGen);
  }

  function isCheckedThisGen(el) {
    return el instanceof HTMLElement && el.dataset.biliindexKwGen === kwGenTag();
  }

  function markChecked(el, blocked) {
    if (!(el instanceof HTMLElement)) return;
    el.dataset.biliindexKwGen = kwGenTag();
    if (blocked) el.dataset.biliindexKeywordBlocked = "1";
    else delete el.dataset.biliindexKeywordBlocked;
  }

  function extractTextFromElement(el) {
    if (!(el instanceof Element)) return "";
    const shadow = getShadowRoot(el);
    const roots = shadow ? [shadow, el] : [el];
    for (const root of roots) {
      for (const sel of COMMENT_TEXT_SEL) {
        const node = root.querySelector?.(sel);
        if (node?.textContent) return normalizeText(node.textContent);
      }
    }
    return normalizeText(el.textContent || "");
  }

  function extractVideoCardTitle(card) {
    if (!(card instanceof Element)) return "";
    const direct = card.getAttribute("title");
    if (direct) {
      const t = normalizeText(direct);
      if (t) return t;
    }
    for (const sel of TITLE_SEL) {
      const node = card.querySelector?.(sel);
      if (node?.textContent) {
        const t = normalizeText(node.textContent);
        if (t) return t;
      }
    }
    const link = card.querySelector?.("a[title]");
    if (link?.getAttribute("title")) return normalizeText(link.getAttribute("title"));
    return "";
  }

  function getWatchPageTitle() {
    const nodes = document.querySelectorAll(
      "h1[title], .video-title, .video-title-text, #viewbox_report h1, .media-title"
    );
    for (const node of nodes) {
      const t = normalizeText(node.getAttribute?.("title") || node.textContent);
      if (t) return t;
    }
    const og = document.querySelector("meta[property='og:title']");
    if (og?.content) return normalizeText(og.content);
    return "";
  }

  function blockComment(main) {
    if (!(main instanceof HTMLElement)) return;
    if (main.dataset.biliindexKeywordBlocked === "1") return;
    if (typeof BiliIndex.applySuperHateBlockToComment === "function") {
      BiliIndex.applySuperHateBlockToComment(main);
    }
  }

  function unblockComment(main) {
    if (!(main instanceof HTMLElement)) return;
    if (main.dataset.biliindexKeywordBlocked !== "1") return;
    if (typeof BiliIndex.restoreSuperHateBlockedComment === "function") {
      BiliIndex.restoreSuperHateBlockedComment(main);
    }
    delete main.dataset.biliindexKeywordBlocked;
  }

  function blockVideoCard(card) {
    if (!(card instanceof HTMLElement)) return;
    if (card.dataset.biliindexKeywordVideo === "1") return;
    if (typeof BiliIndex.applySuperHateBlockToVideo === "function") {
      BiliIndex.applySuperHateBlockToVideo(card);
      card.dataset.biliindexKeywordVideo = "1";
    }
  }

  function unblockVideoCard(card) {
    if (!(card instanceof HTMLElement)) return;
    if (card.dataset.biliindexKeywordVideo !== "1") return;
    if (typeof BiliIndex.removeSuperHateBlockFromVideo === "function") {
      BiliIndex.removeSuperHateBlockFromVideo(card);
    }
    delete card.dataset.biliindexKeywordVideo;
  }

  function scanCommentItem(item, force) {
    if (!(item instanceof Element)) return;
    const main = BiliDom.resolveCommentTarget?.(item) || item;
    if (!(main instanceof HTMLElement)) return;
    if (!force && isCheckedThisGen(main)) return;

    const text = extractTextFromElement(item);
    if (textMatches(text)) {
      blockComment(main);
      markChecked(main, true);
      return;
    }
    unblockComment(main);
    markChecked(main, false);
  }

  function scanVideoCard(card, force) {
    if (!(card instanceof HTMLElement)) return;
    if (!force && card.dataset.biliindexKwGen === kwGenTag()) {
      return;
    }
    const title = extractVideoCardTitle(card);
    if (!title && card.dataset.biliindexKeywordVideo !== "1") {
      card.dataset.biliindexKwGen = kwGenTag();
      return;
    }
    if (textMatches(title)) {
      blockVideoCard(card);
      card.dataset.biliindexKwGen = kwGenTag();
      return;
    }
    unblockVideoCard(card);
    card.dataset.biliindexKwGen = kwGenTag();
  }

  function isNearViewport(el) {
    if (!(el instanceof Element)) return true;
    try {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      return r.bottom >= -280 && r.top <= vh + 280;
    } catch {
      return true;
    }
  }

  function collectCommentItems() {
    const out = [];
    const seen = new Set();
    for (const sel of [MAIN_COMMENT_SEL, REPLY_COMMENT_SEL]) {
      const items = BiliIndex.queryInCommentHosts
        ? BiliIndex.queryInCommentHosts(sel)
        : queryAllDeep(document, sel);
      for (const item of items) {
        if (!(item instanceof Element) || seen.has(item)) continue;
        seen.add(item);
        out.push(item);
      }
    }
    return out;
  }

  function collectVideoCards() {
    const out = new Set();
    for (const sel of VIDEO_SCAN_ROOTS) {
      document.querySelectorAll(sel).forEach((root) => {
        root.querySelectorAll?.(VIDEO_CARD_SEL).forEach((el) => out.add(el));
      });
    }
    document.querySelectorAll(VIDEO_CARD_SEL).forEach((el) => out.add(el));
    if (BiliDom.collectSearchGridCards) {
      BiliDom.collectSearchGridCards().forEach((el) => out.add(el));
    }
    return [...out];
  }

  function processCommentBatch(items, force) {
    const near = [];
    const far = [];
    for (const item of items) {
      if (isNearViewport(item)) near.push(item);
      else far.push(item);
    }
    for (const item of near) scanCommentItem(item, force);
    if (far.length) scheduleOffscreenComments(far, force);
  }

  function scheduleOffscreenComments(items, force) {
    if (items?.length) offscreenQueue.push({ items, force });
    if (!offscreenQueue.length || offscreenIdleScheduled) return;
    offscreenIdleScheduled = true;
    const run = () => {
      offscreenIdleScheduled = false;
      const job = offscreenQueue.shift();
      if (job?.items?.length && ready) {
        const batch = job.items.splice(0, OFFSCREEN_BATCH);
        for (const item of batch) scanCommentItem(item, job.force);
        if (batch.length < job.items.length) {
          offscreenQueue.unshift({ items: job.items, force: job.force });
        }
      }
      if (offscreenQueue.length) scheduleOffscreenComments();
    };
    if (typeof BiliIndex.runWhenIdle === "function") {
      BiliIndex.runWhenIdle(run, { timeout: 800 });
    } else if (typeof requestIdleCallback === "function") {
      requestIdleCallback(run, { timeout: 800 });
    } else {
      setTimeout(run, 48);
    }
  }

  function scanWatchPagePlayer(force) {
    if (!/\/video\/(BV|bv|av)/i.test(location.pathname || "")) return;
    if (!force && watchPageGen === keywordGen) {
      if (watchPageBlocked) {
        const playerRoots = document.querySelectorAll(
          "#playerWrap, #bilibili-player, .bpx-player-container, #viewbox_report"
        );
        for (const root of playerRoots) {
          if (root instanceof HTMLElement && root.dataset.biliindexKeywordVideo !== "1") {
            blockVideoCard(root);
          }
        }
      }
      return;
    }
    watchPageGen = keywordGen;
    const title = getWatchPageTitle();
    watchPageBlocked = textMatches(title);
    if (!watchPageBlocked) return;
    const playerRoots = document.querySelectorAll(
      "#playerWrap, #bilibili-player, .bpx-player-container, #viewbox_report, .video-info-container"
    );
    for (const root of playerRoots) {
      if (root instanceof HTMLElement) blockVideoCard(root);
    }
  }

  function runFastScan() {
    if (!ready || BiliIndex.isCommentsSectionLoading?.()) return;
    const comments = collectCommentItems();
    const pendingComments = comments.filter((item) => {
      const main = BiliDom.resolveCommentTarget?.(item) || item;
      return !(main instanceof HTMLElement && isCheckedThisGen(main));
    });
    if (pendingComments.length) processCommentBatch(pendingComments, false);

    for (const card of collectVideoCards()) {
      if (card.dataset.biliindexKwGen !== kwGenTag()) scanVideoCard(card, false);
    }
    scanWatchPagePlayer(false);
  }

  function runFullScan() {
    if (!ready || BiliIndex.isCommentsSectionLoading?.()) return;
    processCommentBatch(collectCommentItems(), true);
    for (const card of collectVideoCards()) scanVideoCard(card, true);
    scanWatchPagePlayer(true);
  }

  function scheduleFastScan() {
    if (!ready) return;
    clearTimeout(fastTimer);
    const delay = isVideoWatchPage() ? VIDEO_RECO_FAST_MS : FAST_SCAN_MS;
    fastTimer = setTimeout(() => {
      fastTimer = null;
      runFastScan();
    }, delay);
  }

  function scheduleFullScan() {
    if (!ready) return;
    scheduleFastScan();
    clearTimeout(fullTimer);
    fullTimer = setTimeout(() => {
      cancelAnimationFrame(fullRaf);
      fullRaf = requestAnimationFrame(() => {
        if (BiliIndex.isCommentsSectionLoading?.()) {
          scheduleFullScan();
          return;
        }
        runFullScan();
      });
    }, FULL_SCAN_MS);
  }

  function observeScanRoot(observer, el, sel) {
    if (observedScanRoots.has(el)) return;
    observedScanRoots.add(el);
    const shallowReco = isVideoWatchPage() && RECO_OBSERVE_ROOTS.has(sel);
    observer.observe(el, { childList: true, subtree: !shallowReco });
  }

  function hookCommentHostObservers(domObserver) {
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app", "#comment-module"]) {
      document.querySelectorAll(sel).forEach((host) => {
        if (observedCommentHosts.has(host)) return;
        observedCommentHosts.add(host);
        domObserver.observe(host, { childList: true, subtree: false });
        const shadow = getShadowRoot(host);
        if (shadow) domObserver.observe(shadow, { childList: true, subtree: true });
      });
    }
  }

  function setupObservers() {
    const domObserver = new MutationObserver(() => scheduleFullScan());
    for (const sel of VIDEO_SCAN_ROOTS) {
      document.querySelectorAll(sel).forEach((el) => observeScanRoot(domObserver, el, sel));
    }
    if (document.body) {
      domObserver.observe(document.body, { childList: true, subtree: false });
    }
    hookCommentHostObservers(domObserver);
    let ticks = 0;
    const finder = setInterval(() => {
      hookCommentHostObservers(domObserver);
      ticks += 1;
      if (document.querySelector("bili-comments, #comment-app") || ticks >= 30) {
        clearInterval(finder);
      }
    }, 1000);
  }

  function bootstrapFromStorage() {
    chrome.storage.local.get(["keywordBlockKeywords"], (stored) => {
      void chrome.runtime?.lastError;
      applyKeywords(stored?.keywordBlockKeywords || []);
      if (ready) {
        console.info("[BiliIndex] 关键词屏蔽已加载", keywords.length, "个词");
        setupObservers();
        runFastScan();
        scheduleFullScan();
        if (isVideoWatchPage()) {
          BiliIndex.whenCommentsReady?.(() => scheduleFullScan());
        }
      }
    });
  }

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area !== "local" || !changes.keywordBlockKeywords) return;
    applyKeywords(changes.keywordBlockKeywords.newValue || []);
    scheduleFullScan();
  });

  chrome.runtime.onMessage.addListener((msg) => {
    if (msg?.type === "KEYWORD_BLOCK_SYNCED") bootstrapFromStorage();
  });

  document.addEventListener("biliindex-keyword-block-changed", () => scheduleFullScan());

  bootstrapFromStorage();
})();
