/**
 * 超级讨厌：按标签 + 单独标记的 UP — 纯白遮挡、评论完全折叠（不可双击解除）。
 */
(function () {
  "use strict";

  if (window.self !== window.top) return;
  if (typeof BiliDom === "undefined" || typeof BiliIndex === "undefined") {
    console.warn("[BiliIndex] super-hate-block: 缺少 bili-dom / biliindex-core");
    document.getElementById("biliindex-super-hate-early-veil")?.remove();
    return;
  }

  const PASSERBY_CODE = "passerby";
  const OVERLAY_CLASS = "biliindex-super-hate-overlay";
  const PAGE_VEIL_CLASS = "biliindex-super-hate-page-veil";
  const COMMENT_HOST_CLASS = "biliindex-super-hate-comment-host";
  const COVER_HOST_CLASS = "biliindex-super-hate-cover-host";
  const MAIN_COMMENT_SEL = "bili-comment-renderer";
  const REPLY_COMMENT_SEL = "bili-comment-reply-renderer";
  const THREAD_SEL = "bili-comment-thread-renderer";
  const THREAD_REPLY_EXTRA_SEL = [
    "bili-comment-replybox-renderer",
    ".reply-list",
    ".sub-reply-list",
    ".reply-container",
    ".view-more-reply",
    ".reply-wrap",
  ].join(", ");
  const isCommentShadowHost = BiliDom.isRecoOrCommentShadowHost;
  const getShadowRoot = (el) =>
    BiliDom.isRecoOrCommentShadowHost(el) ? BiliDom.openShadowRoot(el) : null;
  const queryAllDeep = (root, selector) =>
    BiliDom.queryAllDeep(root, selector, {
      shadowHostTest: BiliDom.isRecoOrCommentShadowHost,
    });
  const observedShadowHosts = new WeakSet();
  const styledShadowRoots = new WeakSet();
  const SUPER_HATE_CSS_URL = "src/content/super-hate-block.css";
  const SUPER_HATE_HOST_STYLE = `
:host(.biliindex-super-hate-comment-host),
:host([data-biliindex-super-hate-collapsed="1"]) {
  display: none !important;
  height: 0 !important;
  min-height: 0 !important;
  max-height: 0 !important;
  overflow: hidden !important;
  visibility: hidden !important;
  margin: 0 !important;
  padding: 0 !important;
  opacity: 0 !important;
  pointer-events: none !important;
}
`;

  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",
    "bili-video-card",
    "bili-search-video-card",
    ".bili-video-card__wrap",
    ".slide-related-overflow .video-page-card-small",
    "#reco_list .video-page-card-small",
    ".rcmd-list .video-page-card-small",
    ".user-video-list .bili-video-card",
    ".video-list-row .bili-video-card",
    ".video-list.bili-grid .bili-video-card",
    ".search-page .bili-video-card",
    ".search-result-container .bili-video-card",
    ".s-space .small-item",
    ".video-list .small-item",
    ".video-list-section .small-item",
    ".small-item",
    ".s-space .feed-card",
    ".feed-card",
  ].join(", ");

  const SEARCH_UP_BLOCK_SEL = BiliDom.SEARCH_UP_BLOCK_SEL;

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

  const SPACE_PROFILE_ZONE_SEL =
    ".s-space .h-header, .s-space .h-gradient, .s-space .h-user, .h-header, .h-gradient, .h-user, .space-info, .header-info, [class*='space-header'], [class*='SpaceHeader']";
  const SPACE_VIDEO_ZONE_SEL =
    ".video-list, .video-list-section, .small-item-list, .feed-card, .video-pod, bili-playlist, bili-ugc-season, .video-section, .video-section-card, .video-section-items, .video-section-list, #video-list";

  const VIDEO_LINK_SEL =
    "a[href*='/video/BV'], a[href*='/video/bv'], a[href*='/video/av'], a[href*='/video/']";

  const COVER_HOST_SEL = [
    ".bili-video-card__image",
    ".bili-video-card__image--wrap",
    ".bili-video-card__cover",
    ".cover",
    ".thumbnail",
    ".pic",
    ".image-area",
    ".bili-cover",
    ".video-card__image",
    ".rec-cover",
    ".cover-wrapper",
    ".video-page-card-small .cover",
    ".video-page-card-small .bili-video-card__image",
    ".video-page-card-small a[href*='/video/']",
  ].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",
    ".user-item",
    ".user-list-item",
    ".bili-user-card",
    ".user-card",
    ".user-video-list",
    ".s-space",
    ".video-list",
    ".video-list-section",
    "#video-list",
    ".small-item-list",
  ];

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

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

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

  const SITE_HEADER_SELECTOR =
    ".bili-header, bili-header, .international-header, #bili-header-container, .bili-header__bar, #biliMainHeader, .bili-top-bar, .bili-header__channel, .header-channel, .nav-search, #nav_searchform, .nav-search-input, .search-input, .search-input-container, .search-input-wrapper, [class*='SearchBar'], [class*='search-bar'], [class*='SearchHeader'], [class*='search-header'], .search-page .search-header, .search-page-header";

  const SKIP_ZONE_SEL = [
    "#comment",
    "bili-comments",
    ".comment-list",
    ".bb-comment",
    "#comment-app",
    ".player-wrap",
    "#bilibili-player",
    ".bpx-player-container",
    ".bpx-player-control-wrap",
    SITE_HEADER_SELECTOR,
  ].join(", ");

  const SUPER_HATE_CODE = "super_hate";

  let superHateCodes = [];
  /** @type {{ code?: string, icon?: string, name?: string }[]} */
  let superHateTags = [];
  let superHateMids = new Set();
  let ready = false;
  const displayCache = new Map();
  const scanQueued = new Set();
  let rulesSyncTimer = null;

  function normIconText(value) {
    return String(value || "").trim();
  }

  function hateIconSet() {
    const icons = new Set();
    for (const t of superHateTags) {
      const ic =
        normIconText(
          typeof BiliIndex !== "undefined" && BiliIndex.resolveTagIcon
            ? BiliIndex.resolveTagIcon(t)
            : t.icon
        ) || normIconText(t.icon);
      if (ic) icons.add(ic);
    }
    return icons;
  }

  function codesMatch(codes) {
    if (!ready || !superHateCodes.length || !codes?.length) return false;
    const set = new Set(superHateCodes.map((c) => String(c)));
    return codes.some((c) => c && set.has(String(c)));
  }

  function iconsMatch(icons) {
    if (!ready || !superHateTags.length || !icons?.length) return false;
    const set = hateIconSet();
    return icons.some((ic) => ic && set.has(normIconText(ic)));
  }

  function barHateIcons(bar) {
    if (!(bar instanceof HTMLElement)) return [];
    const icons = [];
    const emojiSlot = bar.querySelector('.biliindex-slot[data-slot="emoji"]');
    if (emojiSlot) {
      const label = emojiSlot.querySelector(".biliindex-slot-label");
      const ic = normIconText(label?.textContent);
      if (ic && ic !== "标") icons.push(ic);
    }
    return icons;
  }

  function displaySlotCodes(display) {
    const codes = [];
    for (const slot of display?.slots || []) {
      if (slot?.code) codes.push(slot.code);
    }
    if (display?.emoji?.code) codes.push(display.emoji.code);
    return codes;
  }

  function displaySlotIcons(display) {
    const icons = [];
    const pushIcon = (tag) => {
      if (!tag) return;
      const ic =
        normIconText(tag.icon) ||
        (typeof BiliIndex !== "undefined" && BiliIndex.resolveTagIcon
          ? normIconText(BiliIndex.resolveTagIcon(tag))
          : "");
      if (ic) icons.push(ic);
    };
    for (const slot of display?.slots || []) pushIcon(slot);
    pushIcon(display?.emoji);
    return icons;
  }

  function shouldBlockDisplay(display) {
    if (!display) return false;
    const tag1 = display.slots?.[0];
    const emoji = display.emoji;
    if (tag1?.code === SUPER_HATE_CODE) return true;
    if (codesMatch(displaySlotCodes(display))) return true;
    return iconsMatch(displaySlotIcons(display));
  }

  function midBlocked(mid) {
    return ready && mid && superHateMids.has(String(mid));
  }

  function codesFromBar(bar) {
    if (!(bar instanceof HTMLElement)) return [];
    const codes = [];
    if (bar.dataset.biliindexTag1Code) codes.push(bar.dataset.biliindexTag1Code);
    if (bar.dataset.biliindexEmojiCode) codes.push(bar.dataset.biliindexEmojiCode);
    if (midBlocked(bar.dataset.upMid)) return [SUPER_HATE_CODE, ...codes];
    bar.querySelectorAll(".biliindex-slot-filled").forEach((el) => {
      const slot = el.dataset.slot;
      if (slot !== "tag1" && slot !== "emoji") return;
      const code = el.dataset.tagCode;
      if (code) codes.push(code);
    });
    return codes;
  }

  function shouldBlockUp(mid, display, bar) {
    if (midBlocked(mid)) return true;
    if (bar instanceof HTMLElement) {
      if (codesMatch(codesFromBar(bar))) return true;
      if (iconsMatch(barHateIcons(bar))) return true;
    }
    return shouldBlockDisplay(display);
  }

  function resolveCommentUpMid(item) {
    return BiliDom.resolveCommentUpMid(item, queryAllDeep);
  }

  function findCommentThread(mainRenderer) {
    if (!(mainRenderer instanceof Element)) return null;
    const thread = mainRenderer.closest?.(THREAD_SEL);
    return thread instanceof HTMLElement ? thread : null;
  }

  function hideSuperHateNode(el) {
    if (!(el instanceof HTMLElement)) return;
    if (el.hasAttribute("data-biliindex-super-hate-hide")) return;
    el.setAttribute("data-biliindex-super-hate-hide", "1");
    el.style.setProperty("display", "none", "important");
  }

  function hideThreadReplies(mainRenderer) {
    if (!(mainRenderer instanceof HTMLElement)) return;
    const thread = findCommentThread(mainRenderer);

    const hideInScope = (scope) => {
      if (!(scope instanceof Element)) return;
      for (const child of scope.children) {
        if (child === mainRenderer) continue;
        if (child.contains?.(mainRenderer)) continue;
        hideSuperHateNode(child);
      }
      queryAllDeep(scope, REPLY_COMMENT_SEL).forEach((el) => {
        if (el === mainRenderer || mainRenderer.contains(el)) return;
        hideSuperHateNode(el);
      });
      queryAllDeep(scope, THREAD_REPLY_EXTRA_SEL).forEach((el) => {
        if (mainRenderer.contains(el)) return;
        hideSuperHateNode(el);
      });
    };

    if (thread) {
      hideInScope(thread);
      const tShadow = getShadowRoot(thread);
      if (tShadow) hideInScope(tShadow);
    }

    let sib = mainRenderer.nextElementSibling;
    while (sib) {
      hideSuperHateNode(sib);
      sib = sib.nextElementSibling;
    }

    const shadow = getShadowRoot(mainRenderer);
    if (shadow) {
      for (const child of shadow.children) {
        hideSuperHateNode(child);
      }
    }
  }

  function showThreadReplies(mainRenderer) {
    if (!(mainRenderer instanceof HTMLElement)) return;
    const thread = findCommentThread(mainRenderer);
    const scopes = [thread, mainRenderer.parentElement, document.body].filter(Boolean);
    for (const scope of scopes) {
      scope.querySelectorAll?.("[data-biliindex-super-hate-hide]").forEach((el) => {
        el.style.removeProperty("display");
        el.removeAttribute("data-biliindex-super-hate-hide");
      });
      const sr = scope instanceof Element ? getShadowRoot(scope) : null;
      sr?.querySelectorAll("[data-biliindex-super-hate-hide]").forEach((el) => {
        el.style.removeProperty("display");
        el.removeAttribute("data-biliindex-super-hate-hide");
      });
    }
    delete mainRenderer.dataset.biliindexSuperHateCollapsed;
    if (thread) {
      thread.style.removeProperty("display");
      delete thread.dataset.biliindexSuperHateCollapsed;
    }
  }

  function collapseSuperHateComment(main) {
    if (!(main instanceof HTMLElement)) return;
    if (main.dataset.biliindexSuperHateCollapsed === "1") return;

    hideThreadReplies(main);

    const thread = findCommentThread(main);
    if (thread) {
      thread.dataset.biliindexSuperHateCollapsed = "1";
      thread.style.setProperty("display", "none", "important");
      thread.classList.add("biliindex-super-hate-thread-hidden");
    }

    main.dataset.biliindexSuperHateCollapsed = "1";
    main.dataset.biliindexSuperHate = "1";
    main.classList.add(COMMENT_HOST_CLASS);
    main.style.setProperty("display", "none", "important");

    const shadow = getShadowRoot(main);
    if (shadow) {
      injectSuperHateStylesIntoShadow(shadow);
      injectSuperHateHostStyles(shadow);
      for (const child of shadow.children) {
        hideSuperHateNode(child);
      }
    }
    const threadShadow = thread ? getShadowRoot(thread) : null;
    if (threadShadow) {
      injectSuperHateStylesIntoShadow(threadShadow);
      for (const child of threadShadow.children) {
        if (child === main) continue;
        hideSuperHateNode(child);
      }
    }
  }

  function restoreSuperHateComment(main) {
    if (!(main instanceof HTMLElement)) return;
    if (main.dataset.biliindexSuperHateCollapsed !== "1") return;
    main.classList.remove(COMMENT_HOST_CLASS);
    delete main.dataset.biliindexSuperHate;
    main.style.removeProperty("display");
    delete main.dataset.biliindexSuperHateCollapsed;
    const thread = findCommentThread(main);
    if (thread) {
      thread.classList.remove("biliindex-super-hate-thread-hidden");
      thread.style.removeProperty("display");
      delete thread.dataset.biliindexSuperHateCollapsed;
    }
    showThreadReplies(main);
  }

  function injectSuperHateStylesIntoShadow(shadowRoot) {
    if (!shadowRoot || styledShadowRoots.has(shadowRoot)) return;
    styledShadowRoots.add(shadowRoot);
    try {
      if (shadowRoot.querySelector("link[data-biliindex-super-hate-css]")) return;
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.setAttribute("data-biliindex-super-hate-css", "1");
      link.href = chrome.runtime.getURL(SUPER_HATE_CSS_URL);
      shadowRoot.prepend(link);
    } catch {
      /* ignore */
    }
  }

  function injectSuperHateHostStyles(shadowRoot) {
    if (!shadowRoot || shadowRoot.querySelector("style[data-biliindex-super-hate-host]")) return;
    const style = document.createElement("style");
    style.setAttribute("data-biliindex-super-hate-host", "1");
    style.textContent = SUPER_HATE_HOST_STYLE;
    shadowRoot.prepend(style);
  }

  function attachShadowObservers() {
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      document.querySelectorAll(sel).forEach((host) => {
        const shadow = getShadowRoot(host);
        if (!shadow || observedShadowHosts.has(host)) return;
        observedShadowHosts.add(host);
        injectSuperHateStylesIntoShadow(shadow);
        injectSuperHateHostStyles(shadow);
        new MutationObserver(() => {
          if (BiliIndex.isCommentsSectionLoading?.()) return;
          scheduleScan();
        }).observe(shadow, {
          childList: true,
          subtree: true,
        });
        new MutationObserver(() => {
          if (BiliIndex.isCommentsSectionLoading?.()) return;
          scheduleScan();
        }).observe(host, {
          childList: true,
          subtree: false,
        });
      });
    }
  }

  function syncCollapsedThreads() {
    const mains = BiliIndex.queryInCommentHosts
      ? BiliIndex.queryInCommentHosts(MAIN_COMMENT_SEL)
      : [];
    mains.forEach((main) => {
      if (!(main instanceof HTMLElement)) return;
      if (main.dataset.biliindexSuperHateCollapsed === "1") {
        hideThreadReplies(main);
        const shadow = getShadowRoot(main);
        if (shadow) {
          for (const child of shadow.children) {
            hideSuperHateNode(child);
          }
        }
      }
    });
  }

  function videoOwnerMid() {
    return BiliDom.midFromVideoInitialState();
  }

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

  function isSearchSite() {
    return BiliDom.isSearchSite();
  }

  function isSpacePage() {
    return BiliDom.isSpacePage();
  }

  function spaceOwnerMid() {
    return BiliDom.midFromSpacePageUrl();
  }

  function cardHasVideoLink(el) {
    if (!(el instanceof Element)) return false;
    try {
      if (el.matches?.(VIDEO_LINK_SEL)) return true;
      if (el.querySelector?.(VIDEO_LINK_SEL)) return true;
    } catch {
      /* ignore */
    }
    return queryAllDeep(el, VIDEO_LINK_SEL).length > 0;
  }

  function isSpaceVideoZone(el) {
    if (!isSpacePage() || !(el instanceof Element)) return false;
    if (el.closest?.(SPACE_PROFILE_ZONE_SEL)) return false;
    if (!cardHasVideoLink(el)) return false;
    try {
      if (el.closest?.(SPACE_VIDEO_ZONE_SEL)) return true;
    } catch {
      /* ignore */
    }
    const tag = (el.tagName || "").toUpperCase();
    return (
      tag.startsWith("BILI-VIDEO") ||
      el.matches?.(".small-item, .feed-card, .bili-video-card, .video-section-card")
    );
  }

  function findSpaceProfileBar(ownerMid) {
    if (!ownerMid) return null;
    try {
      return document.querySelector(
        `.biliindex-bar[data-placement="space"][data-up-mid="${CSS.escape(ownerMid)}"]`
      );
    } catch {
      return document.querySelector(
        `.biliindex-bar[data-placement="space"][data-up-mid="${ownerMid}"]`
      );
    }
  }

  function isVideoLinkAnchor(el) {
    if (!(el instanceof HTMLAnchorElement)) return false;
    const h = el.getAttribute("href") || el.href || "";
    return /\/video\/(BV|bv|av)/i.test(h);
  }

  function findVideoCardRoot(el) {
    if (!(el instanceof Element)) return null;
    if (el.matches?.(VIDEO_CARD_SEL)) return el instanceof HTMLElement ? el : null;
    const hit = el.closest(VIDEO_ITEM_ROOT_SEL);
    if (hit instanceof HTMLElement) return hit;
    if (isVideoLinkAnchor(el)) {
      let p = el.parentElement;
      for (let i = 0; i < 10 && p; i += 1) {
        const hasLink = p.querySelector(VIDEO_LINK_SEL);
        const hasText = p.querySelector(
          ".bili-video-card__info, .info, .title, [class*='title'], .bili-video-card__info--tit, .bili-video-card__info--bottom"
        );
        if (hasLink && hasText && p.getBoundingClientRect().height > 40) {
          return p;
        }
        p = p.parentElement;
      }
    }
    return null;
  }

  function bindOverlayShield(ov) {
    if (ov.dataset.biliindexShieldBound === "1") return;
    ov.dataset.biliindexShieldBound = "1";
    const stop = (e) => {
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
    };
    for (const type of [
      "click",
      "mousedown",
      "mouseup",
      "dblclick",
      "pointerdown",
      "pointerup",
      "contextmenu",
      "auxclick",
    ]) {
      ov.addEventListener(type, stop, true);
    }
  }

  const SESSION_MIDS_KEY = "biliindexSuperHateMids";

  function persistMidsForEarlyScript() {
    try {
      if (superHateMids.size) {
        sessionStorage.setItem(SESSION_MIDS_KEY, JSON.stringify([...superHateMids]));
      } else {
        sessionStorage.removeItem(SESSION_MIDS_KEY);
      }
    } catch {
      /* ignore */
    }
  }

  function isInSiteHeader(el) {
    if (!(el instanceof Element)) return false;
    try {
      return !!el.closest?.(SITE_HEADER_SELECTOR);
    } catch {
      return false;
    }
  }

  function applyStorageRules(stored) {
    superHateCodes = [];
    superHateTags = [];
    superHateMids = new Set();
    if (Array.isArray(stored?.superHateTagCodes) && stored.superHateTagCodes.length) {
      superHateCodes = stored.superHateTagCodes.filter((c) => c && c !== PASSERBY_CODE);
    }
    if (Array.isArray(stored?.superHateTags) && stored.superHateTags.length) {
      superHateTags = stored.superHateTags.filter(
        (t) => t && t.code !== PASSERBY_CODE && t.code !== SUPER_HATE_CODE
      );
    }
    if (Array.isArray(stored?.superHateMids) && stored.superHateMids.length) {
      superHateMids = new Set(stored.superHateMids.map(String));
    }
    if (!superHateCodes.length && superHateTags.length) {
      superHateCodes = superHateTags.map((t) => t.code).filter(Boolean);
    }
    ready =
      superHateCodes.length > 0 || superHateTags.length > 0 || superHateMids.size > 0;
    if (ready && superHateCodes.length && !superHateTags.length) {
      void refreshRulesFromApi();
    }
    persistMidsForEarlyScript();
    try {
      if (chrome.storage?.session) {
        void chrome.storage.session.set({ superHateMids: [...superHateMids] });
      }
    } catch {
      /* ignore */
    }
  }

  function shouldUsePageVeil() {
    return false;
  }

  function clearPageVeil() {
    document.getElementById("biliindex-super-hate-page-veil")?.remove();
  }

  function clearOrphanVeils() {
    document.getElementById("biliindex-super-hate-early-veil")?.remove();
    if (!shouldUsePageVeil()) {
      clearPageVeil();
    }
  }

  function clearEarlyPrehide() {
    const prehide = document.getElementById("biliindex-super-hate-prehide-style");
    const searchPrehide = document.getElementById("biliindex-super-hate-search-prehide");
    const earlyVeil = document.getElementById("biliindex-super-hate-early-veil");

    if (searchPrehide && isSearchSite()) {
      const cards = collectSearchGridCards();
      const needBlock = cards.some((c) => {
        const mid = c.dataset.biliindexForceUpMid || resolveUpMid(c);
        return mid && (midBlocked(mid) || c.dataset.biliindexSuperHateVideo === "1");
      });
      if (cards.length > 0 && !needBlock && !cards.some((c) => c.dataset.biliindexSuperHateVideo === "1")) {
        return;
      }
    }

    if (prehide && isSpacePage()) {
      const owner = spaceOwnerMid();
      if (owner && midBlocked(owner)) {
        const cards = collectSpaceVideoCards();
        if (
          cards.length > 0 &&
          !cards.some((c) => c.dataset.biliindexSuperHateVideo === "1")
        ) {
          return;
        }
      }
    }

    if (earlyVeil) earlyVeil.remove();

    if (!shouldUsePageVeil()) {
      clearPageVeil();
    }

    prehide?.remove();
    searchPrehide?.remove();
  }

  function findBarForMid(root, upMid) {
    if (!(root instanceof Element) || !upMid) return null;
    const mid = String(upMid);
    const bars = queryAllDeep(root, ".biliindex-bar");
    for (const bar of bars) {
      if (bar instanceof HTMLElement && bar.dataset.upMid === mid) return bar;
    }
    return null;
  }

  function buildSuperHateDiag(upMid) {
    const mid = upMid ? String(upMid) : spaceOwnerMid() || "";
    const display = mid ? displayCache.get(mid) : null;
    const spaceBar = mid ? findSpaceProfileBar(mid) : null;
    const bar =
      spaceBar || (mid ? findBarForMid(document, mid) : null);
    const block = mid ? shouldBlockUp(mid, display, bar) : false;
    return {
      ready,
      page: location.href,
      isSpacePage: isSpacePage(),
      upMid: mid || null,
      hateCodes: [...superHateCodes],
      hateIcons: [...hateIconSet()],
      hateTagNames: superHateTags.map((t) => t.name || t.code),
      superHateUps: [...superHateMids],
      hasProfileBar: !!spaceBar,
      barTag1: bar?.dataset?.biliindexTag1Code || "",
      barEmoji: bar?.dataset?.biliindexEmojiCode || "",
      barCodes: bar ? codesFromBar(bar) : [],
      barIcons: bar ? barHateIcons(bar) : [],
      displayEmojiCode: display?.emoji?.code || "",
      displayEmojiIcon: normIconText(display?.emoji?.icon),
      shouldBlock: block,
      videoCardsOnPage: isSpacePage() ? collectSpaceVideoCards().length : null,
    };
  }

  function logSuperHateDiag(reason) {
    if (!ready) {
      console.info("[BiliIndex] 超级讨厌未启用（无标配标签且未指定 UP）");
      return;
    }
    const diag = buildSuperHateDiag();
    console.info(`[BiliIndex] 超级讨厌诊断 · ${reason}`, diag);
    if (!diag.shouldBlock && superHateCodes.length) {
      console.info(
        "[BiliIndex] 未遮挡原因：该 UP 的标签与标配不一致。Emoji 标配只遮挡「已被打上该 Emoji」的 UP；要挡整个 UP 请在超级讨厌里点「添加当前页UP主」"
      );
    }
    if (diag.isSpacePage && diag.shouldBlock) {
      const n = diag.videoCardsOnPage || 0;
      if (n > 0) void processSpacePageVideos();
      else scheduleSpaceBlockRetries();
    }
  }

  function scheduleSpaceBlockRetries() {
    if (!ready || !isSpacePage()) return;
    const gen = ++spaceBlockRetryGen;
    for (const ms of SPACE_BLOCK_RETRY_MS) {
      setTimeout(() => {
        if (gen !== spaceBlockRetryGen) return;
        void processSpacePageVideos();
      }, ms);
    }
  }

  async function applyRulesAndScan(options = {}) {
    if (!ready) {
      clearOrphanVeils();
      console.info("[BiliIndex] 超级讨厌：规则为空，已跳过扫描");
      return;
    }
    runFastScanAll();
    if (options.light) {
      if (isSpacePage()) {
        await processSpacePageVideos();
        logSuperHateDiag("轻量扫描·空间页");
      }
      return;
    }
    await scanAll();
    if (isSpacePage()) logSuperHateDiag("全量扫描·空间页");
  }

  function scheduleRulesRefresh() {
    clearTimeout(rulesSyncTimer);
    rulesSyncTimer = setTimeout(() => {
      rulesSyncTimer = null;
      chrome.storage.local.get(
        ["superHateTagCodes", "superHateMids", "superHateTags"],
        (stored) => {
          void chrome.runtime?.lastError;
          applyStorageRules(stored || {});
          void applyRulesAndScan({ light: true });
        }
      );
    }, 400);
  }

  function applyBlockToCardFast(card, forcedUpMid) {
    if (!(card instanceof HTMLElement) || !ready) return;
    if (isInSiteHeader(card)) return;
    const upMid = forcedUpMid || resolveUpMid(card);
    if (!upMid) return;
    if (card.closest?.(SKIP_ZONE_SEL)) return;
    card.dataset.biliindexForceUpMid = upMid;
    const display = displayCache.get(upMid);
    const bar = findBarForMid(card, upMid);
    if (!shouldBlockUp(upMid, display, bar)) {
      if (card.dataset.biliindexSuperHateVideo === "1") removeWhiteOverlay(card);
      return;
    }
    ensureWhiteOverlay(card);
  }

  function runFastScanAll() {
    if (!ready) return;
    if (
      isVideoPage() &&
      typeof BiliIndex !== "undefined" &&
      BiliIndex.isRecoSidebarReady &&
      !BiliIndex.isRecoSidebarReady()
    ) {
      return;
    }

    if (isSpacePage()) {
      const owner = spaceOwnerMid();
      if (owner) {
        const cards = collectSpaceVideoCards();
        const display = displayCache.get(owner);
        const spaceBar = findSpaceProfileBar(owner);
        const block = midBlocked(owner) || shouldBlockUp(owner, display, spaceBar);
        if (block && cards.length) {
          for (const card of cards) {
            card.dataset.biliindexForceUpMid = owner;
            ensureWhiteOverlay(card);
          }
        }
      }
    }

    if (isVideoPage()) {
      clearPageVeil();
    }

    if (isSearchSite()) {
      for (const card of collectSearchGridCards()) {
        applyBlockToCardFast(card, card.dataset.biliindexForceUpMid || null);
      }
      for (const block of collectSearchUpBlocks()) {
        const upMid = BiliDom.resolveUpMidFromBlock(block);
        if (!upMid || !midBlocked(upMid)) continue;
        block.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => {
          const root = findVideoCardRoot(a);
          if (root) applyBlockToCardFast(root, upMid);
        });
        block.querySelectorAll(VIDEO_CARD_SEL).forEach((el) => {
          const root = findVideoCardRoot(el);
          if (root) applyBlockToCardFast(root, upMid);
        });
      }
    }

    for (const card of collectVideoCards()) {
      applyBlockToCardFast(card, card.dataset.biliindexForceUpMid || null);
    }

    if (!BiliIndex.isCommentsSectionLoading?.()) {
      const commentItems = BiliIndex.queryInCommentHosts
        ? BiliIndex.queryInCommentHosts(MAIN_COMMENT_SEL)
        : [];
      for (const item of commentItems) {
        const mid = resolveCommentUpMid(item);
        if (!mid) continue;
        const main = BiliDom.resolveCommentTarget(item);
        const bar = main ? findBarForMid(main, mid) : null;
        const display = displayCache.get(mid);
        if (shouldBlockUp(mid, display, bar)) applySuperHateToComment(item);
      }
    }

    clearEarlyPrehide();
  }

  function bootstrapRulesFromStorage() {
    chrome.storage.local.get(
      ["superHateTagCodes", "superHateMids", "superHateTags"],
      (stored) => {
      void chrome.runtime?.lastError;
      applyStorageRules(stored || {});
      clearOrphanVeils();
      const run = () => runFastScanAll();
      if (typeof requestIdleCallback === "function") {
        requestIdleCallback(run, { timeout: 1200 });
      } else {
        requestAnimationFrame(run);
      }
    });
  }

  async function refreshRulesFromApi() {
    if (typeof BiliIndex === "undefined" || !BiliIndex.getToken) return;
    if (!(await BiliIndex.getToken())) return;
    try {
      const [tagData, upData] = await Promise.all([
        BiliIndex.apiFetch("/api/user/super-hate-tags").catch(() => ({ codes: [] })),
        BiliIndex.apiFetch("/api/user/super-hate").catch(() => ({ up_mids: [] })),
      ]);
      superHateCodes = (tagData?.codes || []).filter((c) => c && c !== PASSERBY_CODE);
      superHateTags = (tagData?.tags || []).filter(
        (t) => t?.code && t.code !== PASSERBY_CODE && t.code !== SUPER_HATE_CODE
      );
      superHateMids = new Set((upData?.up_mids || []).map(String));
      await chrome.storage.local.set({
        superHateTagCodes: superHateCodes,
        superHateTags,
        superHateMids: [...superHateMids],
      });
      ready =
        superHateCodes.length > 0 ||
        superHateTags.length > 0 ||
        superHateMids.size > 0;
      applyRulesAndScan({ light: true });
    } catch (e) {
      console.warn("[BiliIndex] 超级讨厌规则同步失败", e);
    }
  }

  async function loadRules(options = {}) {
    const fullReset = options.fullReset === true;
    if (fullReset) {
      displayCache.clear();
      scanQueued.clear();
    }
    try {
      const stored = await chrome.storage.local.get([
        "superHateTagCodes",
        "superHateMids",
        "superHateTags",
      ]);
      applyStorageRules(stored);
      runFastScanAll();
      if (ready && typeof BiliIndex !== "undefined" && (await BiliIndex.getToken?.())) {
        void refreshRulesFromApi();
        return;
      }
      if (ready) return;
      await refreshRulesFromApi();
    } catch (e) {
      console.warn("[BiliIndex] 超级讨厌规则加载失败", e);
    }
  }

  async function fetchDisplaysBatch(mids) {
    const need = mids.filter((m) => !displayCache.has(m));
    if (!need.length || typeof BiliIndex === "undefined") return;
    try {
      const data = await BiliIndex.fetchPublicJson("/api/tag/display-batch", {
        method: "POST",
        body: JSON.stringify({ up_mids: need }),
      });
      const items = data?.items || {};
      for (const mid of need) {
        displayCache.set(mid, items[mid] || { slots: [null, null, null], emoji: null });
      }
    } catch {
      await Promise.all(
        need.map(async (mid) => {
          try {
            const data = await BiliIndex.fetchPublic(
              `/api/tag/active-list?up_mid=${encodeURIComponent(mid)}`
            );
            displayCache.set(mid, data || { slots: [null, null, null], emoji: null });
          } catch {
            displayCache.set(mid, { slots: [null, null, null], emoji: null });
          }
        })
      );
    }
  }

  function ensurePageVeil() {
    clearPageVeil();
  }

  function applySuperHateToComment(item) {
    const main = BiliDom.resolveCommentTarget(item);
    if (!main) return;
    const mid = resolveCommentUpMid(item);
    if (!mid) return;
    const display = displayCache.get(mid);
    const bars = queryAllDeep(main, ".biliindex-bar[data-up-mid]");
    const bar = bars[0];
    if (!shouldBlockUp(mid, display, bar)) {
      restoreSuperHateComment(main);
      return;
    }
    collapseSuperHateComment(main);
  }

  function resolveUpMid(card) {
    return BiliDom.resolveCardUpMid(card, {
      queryDeep,
      spaceVideoZone: isSpaceVideoZone,
    });
  }

  function findCoverHost(card) {
    if (!(card instanceof Element)) return null;
    const minW = isSearchSite() ? 48 : 40;
    const minH = isSearchSite() ? 28 : 24;
    const okSize = (el) => {
      const r = el.getBoundingClientRect();
      return r.width >= minW && r.height >= minH;
    };
    for (const sel of COVER_HOST_SEL.split(", ")) {
      const el = card.querySelector(sel);
      if (el instanceof HTMLElement && okSize(el)) return el;
    }
    const img = card.querySelector("img[src*='hdslb'], img[src*='bilibili']");
    if (img instanceof HTMLImageElement) {
      const host = img.closest(COVER_HOST_SEL) || img.parentElement;
      if (host instanceof HTMLElement && okSize(host)) return host;
    }
    return null;
  }

  function ensureWhiteOverlay(card) {
    if (!(card instanceof HTMLElement)) return;
    if (isInSiteHeader(card)) return;
    const pos = getComputedStyle(card).position;
    if (pos === "static") card.style.position = "relative";

    card.querySelectorAll(".biliindex-video-block-overlay").forEach((el) => el.remove());
    card.classList.remove("biliindex-video-block-expanded");
    delete card.dataset.biliindexVideoExpanded;

    card.querySelectorAll(`.${COVER_HOST_CLASS} .${OVERLAY_CLASS}`).forEach((el) => el.remove());
    card.querySelectorAll(`.${COVER_HOST_CLASS}`).forEach((host) => {
      host.classList.remove(COVER_HOST_CLASS);
    });

    let ov = card.querySelector(`:scope > .${OVERLAY_CLASS}`);
    if (!ov) {
      ov = document.createElement("div");
      ov.className = OVERLAY_CLASS;
      ov.setAttribute("aria-hidden", "true");
      card.appendChild(ov);
    }
    bindOverlayShield(ov);
    card.dataset.biliindexSuperHateVideo = "1";
    card.querySelectorAll("a, button, [role='button']").forEach((el) => {
      if (el instanceof HTMLElement) el.style.pointerEvents = "none";
    });
  }

  function removeWhiteOverlay(card) {
    if (!(card instanceof HTMLElement)) return;
    card.dataset.biliindexSuperHateVideo = "0";
    card.querySelectorAll(`:scope > .${OVERLAY_CLASS}`).forEach((el) => el.remove());
    card.querySelectorAll(`.${COVER_HOST_CLASS}`).forEach((host) => {
      host.classList.remove(COVER_HOST_CLASS);
      host.querySelector(`.${OVERLAY_CLASS}`)?.remove();
    });
    card.querySelectorAll("a, button").forEach((el) => {
      if (el instanceof HTMLElement) el.style.pointerEvents = "";
    });
  }

  function applyBlockToCard(card, forcedUpMid) {
    if (!(card instanceof HTMLElement)) return;
    if (isInSiteHeader(card) || card.closest?.(SKIP_ZONE_SEL)) return;
    const upMid = forcedUpMid || resolveUpMid(card);
    if (!upMid) return;
    const display = displayCache.get(upMid);
    const bars = queryAllDeep(card, ".biliindex-bar[data-up-mid]");
    const bar = bars[0];
    if (!shouldBlockUp(upMid, display, bar)) {
      if (card.dataset.biliindexSuperHateVideo === "1") removeWhiteOverlay(card);
      return;
    }
    ensureWhiteOverlay(card);
  }

  function collectSearchUpBlocks() {
    const blocks = new Set();
    document.querySelectorAll(SEARCH_UP_BLOCK_SEL).forEach((b) => {
      if (b instanceof HTMLElement) blocks.add(b);
    });
    if (isSearchSite()) {
      document
        .querySelectorAll(
          "[class*='user'][class*='card'], [class*='User'][class*='Card'], .search-page .user"
        )
        .forEach((b) => {
          if (
            b instanceof HTMLElement &&
            b.querySelector(VIDEO_LINK_SEL) &&
            b.querySelector("a[href*='space.bilibili.com']")
          ) {
            blocks.add(b);
          }
        });
    }
    return blocks;
  }

  function collectSearchUpVideoCards() {
    const cards = [];
    const seen = new Set();
    const add = (el, upMid) => {
      const root = findVideoCardRoot(el);
      if (!(root instanceof HTMLElement) || seen.has(root)) return;
      if (root.closest?.(SKIP_ZONE_SEL)) return;
      seen.add(root);
      if (upMid) root.dataset.biliindexForceUpMid = upMid;
      cards.push(root);
    };
    for (const block of collectSearchUpBlocks()) {
      const upMid = BiliDom.resolveUpMidFromBlock(block);
      if (!upMid) continue;
      block.querySelectorAll(VIDEO_CARD_SEL).forEach((el) => add(el, upMid));
      block.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => add(a, upMid));
    }
    return cards;
  }

  async function processSearchUpBlocks() {
    if (!ready || !isSearchSite()) return;
    const blocks = collectSearchUpBlocks();
    for (const block of blocks) {
      const upMid = BiliDom.resolveUpMidFromBlock(block);
      if (!upMid || !midBlocked(upMid)) continue;
      block.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => {
        const r = findVideoCardRoot(a);
        if (r) applyBlockToCardFast(r, upMid);
      });
      block.querySelectorAll(VIDEO_CARD_SEL).forEach((el) => {
        const r = findVideoCardRoot(el);
        if (r) applyBlockToCardFast(r, upMid);
      });
    }
    const mids = [];
    for (const block of blocks) {
      const upMid = BiliDom.resolveUpMidFromBlock(block);
      if (!upMid || displayCache.has(upMid) || scanQueued.has(upMid)) continue;
      mids.push(upMid);
      scanQueued.add(upMid);
    }
    if (mids.length) await fetchDisplaysBatch(mids);

    for (const block of blocks) {
      const upMid = BiliDom.resolveUpMidFromBlock(block);
      if (!upMid) continue;
      const display = displayCache.get(upMid);
      const roots = new Set();
      block.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => {
        const r = findVideoCardRoot(a);
        if (r) roots.add(r);
      });
      block.querySelectorAll(VIDEO_CARD_SEL).forEach((el) => {
        const r = findVideoCardRoot(el);
        if (r) roots.add(r);
      });

      if (!shouldBlockUp(upMid, display, null)) {
        for (const root of roots) {
          if (root.dataset.biliindexSuperHateVideo === "1") removeWhiteOverlay(root);
        }
        continue;
      }

      for (const root of roots) {
        root.dataset.biliindexForceUpMid = upMid;
        ensureWhiteOverlay(root);
      }
    }
  }

  function collectSpaceVideoCards() {
    if (!isSpacePage()) return [];
    const cards = [];
    const seen = new Set();
    const add = (el) => {
      const root = findVideoCardRoot(el) || (el instanceof HTMLElement ? el : null);
      if (!(root instanceof HTMLElement) || seen.has(root)) return;
      if (!isSpaceVideoZone(root)) return;
      if (root.closest?.(SKIP_ZONE_SEL)) return;
      seen.add(root);
      cards.push(root);
    };
    for (const rsel of [SPACE_VIDEO_ZONE_SEL, ".s-space"]) {
      document.querySelectorAll(rsel).forEach((root) => {
        root.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
        root.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => add(a));
      });
    }
    queryAllDeep(document.body, VIDEO_CARD_SEL).forEach(add);
    queryAllDeep(document.body, VIDEO_LINK_SEL).forEach((a) => add(a));
    document.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => add(a));
    document
      .querySelectorAll(".small-item, .feed-card, .video-section-card, bili-video-card")
      .forEach((el) => add(el));
    return cards;
  }

  function collectSearchGridCards() {
    if (!isSearchSite()) return [];
    const cards = [];
    const seen = new Set();
    const add = (el) => {
      const root = findVideoCardRoot(el) || (el instanceof HTMLElement ? el : null);
      if (!(root instanceof HTMLElement) || seen.has(root)) return;
      if (root.closest?.(SKIP_ZONE_SEL)) return;
      seen.add(root);
      cards.push(root);
    };
    document.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
    document.querySelectorAll("bili-video-card, bili-search-video-card").forEach(add);
    queryAllDeep(document.body, VIDEO_CARD_SEL).forEach(add);
    queryAllDeep(document.body, VIDEO_LINK_SEL).forEach((a) => add(a));
    document.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => add(a));
    return cards;
  }

  async function processSearchVideoGrid() {
    if (!ready || !isSearchSite()) return;
    const cards = collectSearchGridCards();
    for (const card of cards) {
      applyBlockToCardFast(card, card.dataset.biliindexForceUpMid || null);
    }
    const mids = [];
    for (const card of cards) {
      const upMid = resolveUpMid(card);
      if (!upMid || midBlocked(upMid)) continue;
      if (!displayCache.has(upMid) && !scanQueued.has(upMid)) {
        mids.push(upMid);
        scanQueued.add(upMid);
      }
    }
    if (mids.length) await fetchDisplaysBatch(mids);
    for (const card of cards) {
      applyBlockToCard(card, card.dataset.biliindexForceUpMid || null);
    }
  }

  async function processSpacePageVideos() {
    if (!ready || !isSpacePage()) return;
    const owner = spaceOwnerMid();
    if (!owner) return;
    const cards = collectSpaceVideoCards();
    if (midBlocked(owner)) {
      for (const card of cards) {
        card.dataset.biliindexForceUpMid = owner;
        ensureWhiteOverlay(card);
      }
      return;
    }
    if (!displayCache.has(owner) && !scanQueued.has(owner)) {
      scanQueued.add(owner);
      await fetchDisplaysBatch([owner]);
    }
    const display = displayCache.get(owner);
    const spaceBar = findSpaceProfileBar(owner);
    const block = shouldBlockUp(owner, display, spaceBar);
    if (!block) {
      cards.forEach((c) => removeWhiteOverlay(c));
      return;
    }
    if (!cards.length) {
      scheduleSpaceBlockRetries();
      return;
    }
    spaceBlockRetryGen += 1;
    for (const card of cards) {
      card.dataset.biliindexForceUpMid = owner;
      ensureWhiteOverlay(card);
    }
  }

  function collectVideoCards() {
    const cards = [];
    const seen = new Set();
    const add = (el) => {
      if (!(el instanceof HTMLElement) || seen.has(el)) return;
      if (el.closest?.(SKIP_ZONE_SEL)) return;
      seen.add(el);
      cards.push(el);
    };
    for (const rsel of VIDEO_SCAN_ROOTS) {
      document.querySelectorAll(rsel).forEach((root) => {
        if (root.matches?.(VIDEO_CARD_SEL)) add(root);
        root.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
      });
    }
    collectSearchUpVideoCards().forEach((c) => add(c));
    if (isSpacePage()) {
      collectSpaceVideoCards().forEach((c) => add(c));
    }
    if (isSearchSite()) {
      document.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
    } else if (!cards.length) {
      document.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
    }
    return cards;
  }

  async function processVideoCards(cards) {
    if (!ready) return;
    const normalized = [];
    const seen = new Set();
    for (const card of cards) {
      const root = findVideoCardRoot(card) || card;
      if (!(root instanceof HTMLElement) || seen.has(root)) continue;
      seen.add(root);
      normalized.push(root);
    }
    for (const card of normalized) {
      applyBlockToCardFast(card, card.dataset.biliindexForceUpMid || null);
    }
    const mids = [];
    for (const card of normalized) {
      const upMid = resolveUpMid(card);
      if (!upMid || midBlocked(upMid)) continue;
      if (!displayCache.has(upMid) && !scanQueued.has(upMid)) {
        mids.push(upMid);
        scanQueued.add(upMid);
      }
    }
    if (mids.length) await fetchDisplaysBatch(mids);
    for (const card of normalized) {
      applyBlockToCard(card, card.dataset.biliindexForceUpMid || null);
    }
  }

  async function processComments() {
    if (!ready) return;
    if (BiliIndex.isCommentsSectionLoading?.()) return;
    attachShadowObservers();
    const items = BiliIndex.queryInCommentHosts
      ? BiliIndex.queryInCommentHosts(MAIN_COMMENT_SEL)
      : [];
    for (const item of items) {
      const mid = resolveCommentUpMid(item);
      if (mid && midBlocked(mid)) applySuperHateToComment(item);
    }
    const mids = [];
    for (const item of items) {
      const mid = resolveCommentUpMid(item);
      if (!mid || midBlocked(mid)) continue;
      if (!displayCache.has(mid) && !scanQueued.has(mid)) {
        mids.push(mid);
        scanQueued.add(mid);
      }
    }
    const owner = videoOwnerMid();
    if (
      owner &&
      !midBlocked(owner) &&
      !displayCache.has(owner) &&
      !scanQueued.has(owner)
    ) {
      mids.push(owner);
      scanQueued.add(owner);
    }
    if (mids.length) await fetchDisplaysBatch(mids);
    for (const item of items) {
      applySuperHateToComment(item);
    }
    syncCollapsedThreads();
  }

  async function scanAll() {
    if (!ready) return;
    if (isVideoPage() && typeof BiliIndex !== "undefined" && BiliIndex.isRecoSidebarReady) {
      if (!BiliIndex.isRecoSidebarReady()) {
        scheduleScan();
        return;
      }
    }
    runFastScanAll();
    if (isSearchSite()) {
      await processSearchUpBlocks();
      await processSearchVideoGrid();
    }
    if (isSpacePage()) await processSpacePageVideos();
    const cards = collectVideoCards();
    await processVideoCards(cards);
    await processComments();
    const owner = videoOwnerMid();
    if (owner && !displayCache.has(owner)) {
      await fetchDisplaysBatch([owner]);
    }
    ensurePageVeil();
    clearEarlyPrehide();
  }

  let scanTimer = null;
  let scanRaf = 0;
  let fastScanTimer = null;
  let spaceBlockRetryGen = 0;
  const SCAN_DEBOUNCE_MS = 220;
  const FAST_SCAN_DEBOUNCE_MS = 420;
  const VIDEO_RECO_FAST_SCAN_DEBOUNCE_MS = 900;
  const SPACE_BLOCK_RETRY_MS = [0, 500, 1200, 2500, 5000, 10000, 16000];
  const observedCommentHosts = new WeakSet();

  /** 勿在 MutationObserver 里同步扫推荐区，否则刷新时易打断 B 站封面懒加载（灰块） */
  function scheduleFastScan() {
    if (!ready) return;
    clearTimeout(fastScanTimer);
    const delay = isVideoWatchPage() ? VIDEO_RECO_FAST_SCAN_DEBOUNCE_MS : FAST_SCAN_DEBOUNCE_MS;
    fastScanTimer = setTimeout(() => {
      fastScanTimer = null;
      runFastScanAll();
    }, delay);
  }

  function scheduleScan() {
    scheduleFastScan();
    clearTimeout(scanTimer);
    scanTimer = setTimeout(() => {
      cancelAnimationFrame(scanRaf);
      scanRaf = requestAnimationFrame(() => {
        if (BiliIndex.isCommentsSectionLoading?.()) {
          scheduleScan();
          return;
        }
        void scanAll();
      });
    }, SCAN_DEBOUNCE_MS);
  }

  const observer = new MutationObserver(() => {
    if (!ready) return;
    scheduleFastScan();
    scheduleScan();
  });

  const commentObserver = new MutationObserver(() => {
    if (!ready) return;
    if (BiliIndex.isCommentsSectionLoading?.()) return;
    scheduleScan();
  });

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

  function setupObservers() {
    for (const sel of VIDEO_SCAN_ROOTS) {
      document.querySelectorAll(sel).forEach((el) => {
        observeScanRoot(observer, el, sel);
      });
    }
    if (isSpacePage()) {
      for (const sel of [".s-space", SPACE_VIDEO_ZONE_SEL, "#app"]) {
        document.querySelectorAll(sel).forEach((el) => {
          observer.observe(el, { childList: true, subtree: true });
        });
      }
    }
    if (document.body) {
      observer.observe(document.body, { childList: true, subtree: false });
    }
    hookCommentHostObservers();
    let hostFinderTicks = 0;
    const hostFinder = setInterval(() => {
      hookCommentHostObservers();
      hostFinderTicks += 1;
      if (
        document.querySelector("bili-comments, #comment-app") ||
        hostFinderTicks >= 36
      ) {
        clearInterval(hostFinder);
      }
    }, 1200);
  }

  async function runSuperHateDiag(upMid) {
    const diag = buildSuperHateDiag(upMid);
    if (isSpacePage()) await processSpacePageVideos();
    else runFastScanAll();
    diag.afterScan = buildSuperHateDiag(upMid);
    console.table(diag.afterScan);
    return diag.afterScan;
  }

  /** 诊断仅在 content script 控制台可用，避免向 B 站页面注入 inline script 触发 CSP。 */
  function installPageConsoleBridge() {
    if (document.documentElement?.dataset?.biliindexSuperHateBridge) return;
    document.documentElement.dataset.biliindexSuperHateBridge = "1";
  }

  function start() {
    installPageConsoleBridge();
    clearOrphanVeils();
    clearEarlyPrehide();
    void loadRules().then(() => {
      clearOrphanVeils();
      runFastScanAll();
      scheduleScan();
      if (isSearchSite()) {
        for (const ms of [600, 1500, 3000, 5500, 8000, 12000]) {
          setTimeout(() => void scanAll(), ms);
        }
      }
      setupObservers();
      if (isVideoPage()) {
        BiliIndex.whenCommentsReady(() => void scanAll());
        for (const ms of [2500, 6000]) {
          setTimeout(() => {
            if (!BiliIndex.isCommentsSectionLoading?.()) void scanAll();
          }, ms);
        }
      }
      if (isSpacePage()) {
        scheduleSpaceBlockRetries();
        setTimeout(() => void scanAll(), 3500);
      }
      clearEarlyPrehide();
      setTimeout(clearEarlyPrehide, 2000);
      if (ready) {
        console.info(
          "[BiliIndex] 超级讨厌已加载",
          superHateCodes.length,
          "个标签,",
          superHateMids.size,
          "个 UP"
        );
        if (isSpacePage()) {
          setTimeout(() => logSuperHateDiag("页面启动"), 1200);
        }
      }
    });
  }

  if (typeof BiliIndex !== "undefined") {
    BiliIndex.applySuperHateBlockToComment = (main) => {
      if (main instanceof HTMLElement) collapseSuperHateComment(main);
    };
    BiliIndex.applySuperHateBlockToVideo = (card) => ensureWhiteOverlay(card);
    BiliIndex.removeSuperHateBlockFromVideo = (card) => removeWhiteOverlay(card);
    BiliIndex.restoreSuperHateBlockedComment = (main) => restoreSuperHateComment(main);
  }

  globalThis.__biliindexSuperHateDiag = runSuperHateDiag;
  globalThis.biliindexSuperHateDiag = runSuperHateDiag;

  document.addEventListener("biliindex:up-target", () => scheduleScan());
  document.addEventListener("biliindex:bar-refreshed", () => scheduleScan());

  document.addEventListener("biliindex-super-hate-changed", () => {
    void applyRulesAndScan({ light: true });
  });

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area !== "local") return;
    if (changes.superHateTagCodes || changes.superHateMids || changes.superHateTags) {
      scheduleRulesRefresh();
    }
  });

  chrome.runtime.onMessage.addListener((msg) => {
    if (msg?.type === "SUPER_HATE_SYNCED") {
      chrome.storage.local.get(
        ["superHateTagCodes", "superHateMids", "superHateTags"],
        (stored) => {
          void chrome.runtime?.lastError;
          applyStorageRules(stored || {});
          void applyRulesAndScan({ light: true });
        }
      );
    }
  });

  bootstrapRulesFromStorage();
  clearOrphanVeils();
  window.addEventListener("pageshow", (ev) => {
    if (ev.persisted) clearOrphanVeils();
    clearEarlyPrehide();
  });

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", start);
  } else {
    start();
  }
})();
