/**
 * BiliIndex 标签槽 demo2.0.3（manifest 2.0.3）
 * 评论区 3+1；推荐/UP 仅 Emoji；空间头图 4 槽
 * 回滚基线：baseline/demo-1.0.10/（见 VERSIONING.md）
 */
(function () {
  "use strict";

  if (window.self !== window.top) return;
  if (typeof BiliIndex === "undefined" || typeof BiliDom === "undefined") {
    console.warn("[BiliIndex] tag-slots: 缺少 biliindex-core 或 bili-dom，已跳过");
    return;
  }

  const BILIINDEX_VERSION = BiliIndex.getExtensionVersion();
  document.documentElement.setAttribute("data-biliindex-version", BILIINDEX_VERSION);
  document.documentElement.setAttribute("data-biliindex-tag-slots", "1");
  console.info(`[BiliIndex] ${BILIINDEX_VERSION} 已注入`, location.href);

  const BAR_CLASS = "biliindex-bar";
  const WRAP_CLASS = "biliindex-wrap";
  const SLOT_CLASS = "biliindex-slot";
  const MENU_CLASS = "biliindex-tag-menu";
  const CATEGORY_TAB_VISIBLE = 5;
  const FREQUENT_TAG_SLOTS = 3;
  const PASSERBY_CODE = "passerby";
  const SUPER_HATE_CODE = "super_hate";
  const EXT_MARK = "data-biliindex-ext";
  const BAR_DONE = "data-biliindex-bar-done";
  const ITEM_DONE = "data-biliindex-item-done";
  const CARD_DONE = "data-biliindex-card-done";
  const CSS_URL = "src/content/tag-slots.css";
  let tagSlotsCssText = "";
  const tagSlotsCssTextPromise = (async () => {
    try {
      const res = await fetch(chrome.runtime.getURL(CSS_URL));
      if (res.ok) tagSlotsCssText = await res.text();
    } catch {
      /* ignore */
    }
    return tagSlotsCssText;
  })();

  const COMMENT_ROOT_SELECTORS = BiliDom.COMMENT_ROOT_SELECTORS;
  /** 播放页右侧推荐：挂 Emoji 标签槽 */
  const VIDEO_PAGE_RECO_SELECTORS = [
    "#reco_list",
    ".recommend-list-v1",
    ".rcmd-tab",
    ".rcmd-list",
    ".rcmd-box",
    ".rec-list",
  ];
  /** 首页信息流 / 搜索页：仅由 video-block 做封面屏蔽，不挂标签槽 */
  const FEED_VIDEO_MASK_ONLY_SELECTORS = [
    "#i_cecream",
    ".bili-feed4-layout",
    ".search-page",
    ".search-page-wrapper",
    "#all-list",
    "#user-list",
    ".search-component-result-list",
    ".video-list.bili-grid",
    ".search-result-container",
    ".user-item",
    ".user-list-item",
    ".bili-user-card",
    ".user-card",
    ".user-video-list",
  ];
  const RECOMMEND_ROOT_SELECTORS = [
    ...VIDEO_PAGE_RECO_SELECTORS,
    ...FEED_VIDEO_MASK_ONLY_SELECTORS,
  ];
  const UP_PANEL_ROOT_SELECTORS = [
    ".up-panel-container",
    ".up-info-container",
    ".up-info--wrapper",
    ".video-info-detail",
    "#v_upinfo",
    ".video-info-meta",
  ];
  const ALL_ROOT_SELECTORS = [
    ...COMMENT_ROOT_SELECTORS,
    ...RECOMMEND_ROOT_SELECTORS,
    ...UP_PANEL_ROOT_SELECTORS,
  ];

  const SITE_HEADER_SELECTOR =
    ".bili-header, bili-header, .international-header, #bili-header-container, .bili-header__bar, #biliMainHeader, .bili-top-bar";
  const SPACE_PROFILE_ZONE_SELECTOR =
    ".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_PROFILE_NICKNAME_SELECTORS = [
    ".nickname",
    ".upinfo__user__name",
    ".space-profile__nickname",
    ".header-name",
    ".h-name",
    ".n-name",
    "#h-name",
    "#nav-title",
    ".h-user .h-name",
  ];
  const SPACE_NICKNAME_LINK_SELECTORS = [
    ".s-space .h-name a[href*='space.bilibili.com']",
    ".h-header .h-name a[href*='space.bilibili.com']",
    ".h-user .h-name a[href*='space.bilibili.com']",
    "a.n-name[href*='space.bilibili.com']",
  ];
  const COLLECTION_ZONE_SELECTOR =
    ".video-section-head, .video-pod, bili-playlist, bili-ugc-season, .video-list-section";

  const PROFILE_POPOVER_SELECTOR =
    ".bili-popper, .bili-popover, .bili-popover__content, .v-popper__content, .bili-user-profile-card";

  /** 顶部评论输入框，禁止挂标签（避免挡住占位文案） */
  const COMMENT_COMPOSER_SELECTOR =
    "bili-comment-box, bili-comment-input, .comment-send, .comment-input-wrap, .reply-box.reply-confirm, .comment-confirm";

  const COMMENT_NICKNAME_HOST_SELECTORS = [
    "#user-name",
    ".user-name",
    "bili-comment-user-renderer",
  ];
  const DEACTIVATED_RE =
    /账号已注销|用户已注销|已注销账号|该账号已注销|账户已注销|注销账号|销号|用户不存在|用户已封禁/;
  const COMMENT_PLACEHOLDER_RE =
    /这里是评论区|不是无人区|还没有评论|暂无评论|快来抢沙发|发条评论|期待你的评论|成为第一个|评论为空|沙发还空着/;

  const isCommentShadowHost = BiliDom.isCommentShadowHost;
  const openShadowRoot = BiliDom.openShadowRoot;
  const getShadowRoot = (el) => openShadowRoot(el) || BiliDom.getCommentShadowRoot(el);
  const queryAllDeep = (root, selector) => BiliDom.queryAllDeep(root, selector);
  const extractMid = BiliDom.extractMid;

  function formatVoteCountForTag(tag) {
    if (!tag) return "";
    if (tag.vote_count_display) return String(tag.vote_count_display);
    const votes = Number(tag.vote_count) || 0;
    if (votes <= 0) return "";
    if (typeof BiliIndex !== "undefined" && BiliIndex.formatVoteCount) {
      return BiliIndex.formatVoteCount(votes);
    }
    return String(votes);
  }

  const mountedBars = new WeakSet();
  const styledShadowRoots = new WeakSet();
  const commentItemRun = new WeakMap();
  const recoHostRun = new WeakMap();
  let scanRunId = 0;
  let apiOk = true;
  let lastMountCount = 0;
  let lastCommentRunAt = 0;
  let lastRecoRunAt = 0;
  let commentScanPending = false;
  let recoScanPending = false;
  let recoMediaGateUntil = 0;

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

  /** 推荐区封面懒加载完成后再挂 Emoji 槽，避免刷新偶发灰块 */
  function recoMediaReady() {
    if (typeof BiliIndex !== "undefined" && BiliIndex.isRecoSidebarReady) {
      return BiliIndex.isRecoSidebarReady();
    }
    if (!isVideoWatchPage()) return true;
    if (recoMediaGateUntil && Date.now() > recoMediaGateUntil) return true;
    const list = document.querySelector("#reco_list, .rcmd-list, .recommend-list-v1");
    if (!list) return document.readyState === "complete";
    const imgs = list.querySelectorAll(
      "img[src*='hdslb'], img[src*='bilibili'], img[data-src], picture img"
    );
    if (!imgs.length) return document.readyState === "complete";
    let loaded = 0;
    for (const img of imgs) {
      if (!(img instanceof HTMLImageElement)) continue;
      if (img.naturalWidth > 0) loaded += 1;
      if (loaded >= 2) return true;
    }
    if (!recoMediaGateUntil) recoMediaGateUntil = Date.now() + 5000;
    return false;
  }

  function nudgeRecoLazyImages() {
    const list = document.querySelector("#reco_list, .rcmd-list, .recommend-list-v1");
    if (!list) return;
    list.querySelectorAll("img").forEach((img) => {
      if (!(img instanceof HTMLImageElement)) return;
      const lazySrc =
        img.dataset?.src ||
        img.getAttribute("data-src") ||
        img.getAttribute("data-lazy-src");
      if (lazySrc && (!img.src || img.src.startsWith("data:"))) {
        try {
          img.src = lazySrc;
        } catch {
          /* ignore */
        }
      }
    });
  }

  function matchesAnySelector(el, selectors) {
    for (const sel of selectors) {
      try {
        if (el.matches(sel)) return true;
      } catch {
        /* ignore */
      }
    }
    return false;
  }

  function isExtensionNode(node) {
    if (!(node instanceof Element)) return false;
    return !!node.closest?.(`.${WRAP_CLASS}, .${BAR_CLASS}, .${MENU_CLASS}`);
  }

  function isCommentRelated(node) {
    if (!(node instanceof Element)) return false;
    if ((node.tagName || "").toLowerCase().startsWith("bili-comment")) return true;
    return !!node.closest?.(
      "bili-comments, bili-comment-area, bili-comment-renderer, #comment-app, #comment"
    );
  }

  function nodeTouchesTargets(node) {
    if (!(node instanceof Element) || isExtensionNode(node)) return false;
    if (node.closest?.(`.${WRAP_CLASS}, .${BAR_CLASS}`)) return false;
    if (isCommentRelated(node)) return true;
    if (matchesAnySelector(node, ALL_ROOT_SELECTORS)) return true;
    return !!node.closest?.(ALL_ROOT_SELECTORS.join(", "));
  }

  function mutationHasRelevantAdds(mutations) {
    for (const m of mutations) {
      for (const node of m.addedNodes) {
        if (!(node instanceof Element)) continue;
        if (isExtensionNode(node)) continue;
        if (nodeTouchesTargets(node)) return true;
      }
    }
    return false;
  }

  function collectAddedElements(mutations) {
    const out = [];
    const seen = new Set();
    for (const m of mutations) {
      for (const node of m.addedNodes) {
        if (!(node instanceof Element) || isExtensionNode(node)) continue;
        if (seen.has(node)) continue;
        seen.add(node);
        out.push(node);
      }
    }
    return out;
  }

  function collectRoots(selectors) {
    const roots = new Set();
    for (const sel of selectors) {
      try {
        document.querySelectorAll(sel).forEach((el) => roots.add(el));
      } catch {
        /* ignore */
      }
    }
    return [...roots];
  }

  const observedShadowHosts = new WeakSet();
  const activeObservers = [];

  function trackObserver(obs) {
    activeObservers.push(obs);
    return obs;
  }

  function attachShadowObservers() {
    const shadows = collectCommentShadowRoots();
    for (const shadow of shadows) {
      const host = shadow.host;
      if (!(host instanceof Element) || observedShadowHosts.has(host)) continue;
      observedShadowHosts.add(host);
      injectStylesIntoShadow(shadow);
      trackObserver(
        new MutationObserver((mutations) => {
          const added = collectAddedElements(mutations);
          if (added.length) scheduleCommentScan(added);
          else if (mutationHasRelevantAdds(mutations)) scheduleCommentScan();
        })
      ).observe(shadow, { childList: true, subtree: true });
    }
  }

  function injectStylesIntoShadow(shadowRoot) {
    if (!shadowRoot || styledShadowRoots.has(shadowRoot)) return;
    styledShadowRoots.add(shadowRoot);
    try {
      if (shadowRoot.querySelector("[data-biliindex-css]")) return;
      if (tagSlotsCssText) {
        const style = document.createElement("style");
        style.setAttribute("data-biliindex-css", "1");
        style.textContent = tagSlotsCssText;
        shadowRoot.prepend(style);
        return;
      }
      void tagSlotsCssTextPromise.then((css) => {
        if (!css || shadowRoot.querySelector("[data-biliindex-css]")) return;
        const style = document.createElement("style");
        style.setAttribute("data-biliindex-css", "1");
        style.textContent = css;
        shadowRoot.prepend(style);
      });
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.setAttribute("data-biliindex-css", "link-fallback");
      link.href = chrome.runtime.getURL(CSS_URL);
      shadowRoot.prepend(link);
    } catch {
      /* ignore */
    }
  }

  /** 挂载点可能在多层 Shadow 内，逐层注入 tag-slots.css */
  function injectStylesForElement(el) {
    if (!(el instanceof Element)) return;
    let node = el;
    for (let depth = 0; depth < 16 && node; depth += 1) {
      const root = node.getRootNode?.();
      if (root instanceof ShadowRoot) injectStylesIntoShadow(root);
      if (node instanceof Element && isCommentShadowHost(node)) {
        const sr = openShadowRoot(node);
        if (sr) injectStylesIntoShadow(sr);
      }
      if (node.parentElement) {
        node = node.parentElement;
        continue;
      }
      if (root instanceof ShadowRoot && root.host instanceof Element) {
        node = root.host;
        continue;
      }
      break;
    }
  }

  function collectCommentShadowRoots() {
    const roots = [];
    const elementSeen = new Set();
    const shadowSeen = new Set();
    const walk = (node) => {
      if (!node) return;
      if (node instanceof ShadowRoot) {
        if (!shadowSeen.has(node)) {
          shadowSeen.add(node);
          roots.push(node);
        }
        const kids = node.children;
        if (kids) for (let i = 0; i < kids.length; i += 1) walk(kids[i]);
        return;
      }
      if (node instanceof Element) {
        if (elementSeen.has(node)) return;
        elementSeen.add(node);
        const sr = openShadowRoot(node);
        if (sr) walk(sr);
        const kids = node.children;
        if (kids) for (let i = 0; i < kids.length; i += 1) walk(kids[i]);
      }
    };
    for (const host of BiliDom.getCommentHosts?.() || []) walk(host);
    return roots;
  }

  function findCommentNicknameLink(renderer) {
    return BiliDom.findCommentNicknameLink(renderer, queryAllDeep);
  }

  function findCommentNicknameHost(renderer) {
    for (const sel of COMMENT_NICKNAME_HOST_SELECTORS) {
      const found = queryAllDeep(renderer, sel).find((el) => el instanceof HTMLElement);
      if (found) return found;
    }
    return null;
  }

  function isDeactivatedCommentItem(item, link) {
    if (!(item instanceof HTMLElement)) return false;
    const t = (item.textContent || "") + (link?.textContent || "");
    if (DEACTIVATED_RE.test(t)) return true;
    const d = item.__data;
    const member = d?.member || d?.reply?.member || d?.user;
    if (member) {
      const mid = member.mid ?? member.uid;
      if (mid === 0 || mid === "0") return true;
      const uname = String(member.uname || member.name || "");
      if (uname && DEACTIVATED_RE.test(uname)) return true;
    }
    const uname = d?.member?.uname || d?.reply?.member?.uname || "";
    if (uname && DEACTIVATED_RE.test(String(uname))) return true;
    return false;
  }

  function isPlaceholderCommentItem(item, link) {
    if (!(item instanceof HTMLElement)) return false;
    if (
      item.matches?.(
        "bili-comment-empty, bili-comment-emptystate-renderer, bili-comment-empty-renderer"
      ) ||
      item.closest?.(
        "bili-comment-empty, bili-comment-emptystate-renderer, bili-comment-empty-renderer, .no-comment, .comment-empty"
      )
    ) {
      return true;
    }
    const text = (item.textContent || "").replace(/\s+/g, " ").trim();
    if (!text) return false;
    if (COMMENT_PLACEHOLDER_RE.test(text)) return true;
    if (!link && text.length <= 48 && COMMENT_PLACEHOLDER_RE.test(text.slice(0, 48))) {
      return true;
    }
    return false;
  }

  function hasBarForUp(itemRoot, upMid) {
    if (!itemRoot || !upMid) return false;
    const sel = `.${BAR_CLASS}[data-up-mid="${CSS.escape(upMid)}"]`;
    return queryAllDeep(itemRoot, sel).length > 0;
  }

  /** 同一昵称旁只保留第一个标签栏，删掉重复挂载 */
  function dedupeExtraWraps(scope) {
    if (!(scope instanceof Element)) return;
    const wraps =
      scope.matches?.(".video-page-card-small, .rec-list-item, .rcmd-item") ||
      scope.closest?.(".video-page-card-small, .rec-list-item")
        ? [...scope.querySelectorAll(`.${WRAP_CLASS}`)]
        : queryAllDeep(scope, `.${WRAP_CLASS}`);
    for (let i = 1; i < wraps.length; i++) {
      wraps[i].remove();
    }
  }

  function hasWrapAdjacent(anchor, placement) {
    const mount = getBarInsertRef(anchor, placement);
    let sib = mount.nextElementSibling;
    while (sib) {
      if (sib.classList?.contains(WRAP_CLASS) || sib.classList?.contains(BAR_CLASS)) {
        return true;
      }
      sib = sib.nextElementSibling;
    }
    return false;
  }

  function removeBarsFromRoot(root) {
    queryAllDeep(root, `.${WRAP_CLASS}`).forEach((w) => w.remove());
    if (root instanceof HTMLElement) root.removeAttribute(ITEM_DONE);
  }

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

  /** 空间页：仅在个人主页头图区域穿透 Shadow，避免误扫顶栏等全页组件 */
  function queryAllDeepSpace(selector) {
    const out = [];
    const seen = new Set();
    const roots = [];
    const rootSeen = new Set();
    const addRoot = (el) => {
      if (!(el instanceof Element) || rootSeen.has(el)) return;
      rootSeen.add(el);
      roots.push(el);
    };
    for (const sel of [
      ".s-space",
      SPACE_PROFILE_ZONE_SELECTOR,
      ".h-space",
      "#app",
    ]) {
      try {
        document.querySelectorAll(sel).forEach(addRoot);
      } catch {
        /* ignore */
      }
    }
    if (!roots.length) addRoot(document.body);

    function walk(node) {
      if (!node || seen.has(node)) return;
      seen.add(node);
      let scope = node;
      if (node instanceof Element && isCommentShadowHost(node)) {
        const shadow = BiliDom.openShadowRoot(node);
        if (shadow) {
          walk(shadow);
          scope = shadow;
        }
      }
      try {
        scope.querySelectorAll?.(selector).forEach((el) => {
          if (!seen.has(el)) {
            seen.add(el);
            out.push(el);
          }
        });
      } catch {
        /* ignore */
      }
      const kids = scope.children;
      if (kids) for (const c of kids) walk(c);
    }
    for (const root of roots) walk(root);
    return out;
  }

  function isSpaceProfileZone(el) {
    if (!isSpaceHostPage() || !(el instanceof Element)) return false;
    if (el.closest?.(SITE_HEADER_SELECTOR)) return false;
    if (el.closest?.(".video-list, .small-item, .feed-card, .bili-video-card, #comment")) {
      return false;
    }
    if (el.closest?.(SPACE_PROFILE_ZONE_SELECTOR)) return true;
    for (const sel of SPACE_PROFILE_NICKNAME_SELECTORS) {
      try {
        if (el.matches?.(sel)) {
          const rect = el.getBoundingClientRect();
          return rect.top >= 0 && rect.top < 520 && rect.width > 0;
        }
      } catch {
        /* ignore */
      }
    }
    return false;
  }

  function inForbiddenZone(el) {
    if (!(el instanceof Element)) return false;
    if (isSpaceProfileZone(el)) {
      return !!el.closest?.(PROFILE_POPOVER_SELECTOR);
    }
    return !!(
      el?.closest?.(SITE_HEADER_SELECTOR) ||
      el?.closest?.(COLLECTION_ZONE_SELECTOR) ||
      el?.closest?.(PROFILE_POPOVER_SELECTOR) ||
      el?.closest?.(COMMENT_COMPOSER_SELECTOR)
    );
  }

  function isOurWrap(el) {
    return (
      el instanceof HTMLElement &&
      el.classList.contains(WRAP_CLASS) &&
      el.getAttribute(EXT_MARK) === "1"
    );
  }

  function cleanupForbiddenBars() {
    try {
      document
        .querySelectorAll(
          `${SITE_HEADER_SELECTOR} .${WRAP_CLASS}, ${COLLECTION_ZONE_SELECTOR} .${WRAP_CLASS}, ${COMMENT_COMPOSER_SELECTOR} .${WRAP_CLASS}`
        )
        .forEach((wrap) => {
          if (isOurWrap(wrap) && !wrap.closest?.(".s-space")) wrap.remove();
        });
      document.querySelectorAll(`.${BAR_CLASS}[data-layout="full"]`).forEach((bar) => {
        if (!(bar instanceof HTMLElement)) return;
        const wrap = bar.closest(`.${WRAP_CLASS}`);
        if (!isOurWrap(wrap)) return;
        const inComment = !!bar.closest(
          "bili-comment-renderer, bili-comment-reply-renderer, .reply-item"
        );
        const onSpaceProfile =
          bar.dataset.placement === "space" || isSpaceProfileZone(bar);
        if (!inComment && !onSpaceProfile) wrap.remove();
      });
    } catch {
      /* ignore */
    }
  }

  function cleanupProfilePopoverBars() {
    try {
      document.querySelectorAll(PROFILE_POPOVER_SELECTOR).forEach((root) => {
        root.querySelectorAll?.(`.${WRAP_CLASS}`).forEach((w) => w.remove());
      });
    } catch {
      /* ignore */
    }
  }

  function relocateAllBarsOutsideLinks() {
    try {
      document.querySelectorAll(`.${WRAP_CLASS}`).forEach((wrap) => {
        relocateBarForPlacement(wrap);
      });
    } catch {
      /* ignore */
    }
  }

  function forEachBar(fn) {
    const seen = new WeakSet();
    const visit = (bar) => {
      if (!(bar instanceof HTMLElement) || seen.has(bar)) return;
      seen.add(bar);
      fn(bar);
    };
    document.querySelectorAll(`.${BAR_CLASS}`).forEach(visit);
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      document.querySelectorAll(sel).forEach((host) => {
        const sr = getShadowRoot(host);
        sr?.querySelectorAll(`.${BAR_CLASS}`).forEach(visit);
      });
    }
    document.querySelector("#reco_list")?.querySelectorAll(`.${BAR_CLASS}`).forEach(visit);
  }

  function refreshAllBars() {
    forEachBar((bar) => void refreshBar(bar));
  }

  function countCommentRenderers() {
    let n = 0;
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      const host = document.querySelector(sel);
      if (!host) continue;
      const sr = getShadowRoot(host);
      const scope = sr || host;
      try {
        n += scope.querySelectorAll(
          "bili-comment-renderer, bili-comment-reply-renderer"
        ).length;
      } catch {
        /* ignore */
      }
    }
    return n;
  }

  function getNicknameMountPoint(anchor, placement) {
    if (placement === "space" && anchor instanceof HTMLAnchorElement) {
      return anchor;
    }
    if (anchor instanceof HTMLAnchorElement) {
      return anchor.querySelector("span.name, .name") || anchor;
    }
    return anchor;
  }

  /** 评论区昵称行容器（bili-comment-user-renderer 的父级，含等级/徽章） */
  function findCommentUserRow(anchor) {
    if (!(anchor instanceof Element)) return null;
    const renderer = anchor.closest?.("bili-comment-user-renderer");
    if (renderer?.parentElement instanceof HTMLElement) {
      return renderer.parentElement;
    }
    const userName = anchor.closest?.("#user-name, .user-name");
    if (userName?.parentElement instanceof HTMLElement) return userName.parentElement;
    const userInfo = anchor.closest?.(".user-info, .sub-reply-info");
    if (userInfo instanceof HTMLElement) return userInfo;
    return null;
  }

  const UP_PANEL_INSERT_ROOT_SELECTORS = [
    ".up-detail-top",
    ".up-info--wrapper",
    ".up-info-container",
    ".up-panel-container",
  ];

  /** 评论区：挂在整条 comment-header 之后，避免槽位挤在昵称与等级徽章之间 */
  function getCommentBarInsertRef(anchor) {
    if (!(anchor instanceof Element)) return anchor;
    const header = anchor.closest?.(
      "bili-comment-renderer .comment-header, bili-comment-reply-renderer .comment-header, .comment-header, .comment-item-header"
    );
    if (header instanceof HTMLElement) return header;
    const userInfo = anchor.closest?.(".user-info, .sub-reply-info");
    if (userInfo instanceof HTMLElement) return userInfo;
    const row = findCommentUserRow(anchor);
    if (row instanceof HTMLElement) return row;
    const renderer = anchor.closest?.("bili-comment-user-renderer");
    if (renderer instanceof HTMLElement) return renderer;
    const userName = anchor.closest?.("#user-name, .user-name");
    if (userName instanceof HTMLElement) return userName;
    if (anchor instanceof HTMLAnchorElement) return anchor;
    return anchor;
  }

  function getUpPanelBarInsertRef(anchor) {
    if (anchor instanceof HTMLAnchorElement) return anchor;
    if (!(anchor instanceof Element)) return anchor;
    const link = anchor.closest?.("a.up-name[href*='space.bilibili.com']");
    if (link instanceof HTMLAnchorElement) return link;
    return anchor;
  }

  /** 标签栏插在昵称链接 / 用户悬停区之外，避免悬停出用户卡片、点击被导航吞掉 */
  function getBarInsertRef(anchor, placement) {
    if (placement === "comment") return getCommentBarInsertRef(anchor);
    if (placement === "up" || placement === "card") return getUpPanelBarInsertRef(anchor);
    if (placement === "space" && anchor instanceof HTMLAnchorElement) return anchor;
    if (anchor instanceof HTMLAnchorElement) return anchor;
    return getNicknameMountPoint(anchor, placement);
  }

  function relocateWrapOutsideNicknameLink(wrap) {
    if (!(wrap instanceof HTMLElement)) return;
    const link = wrap.closest(
      "a[href*='space.bilibili.com'], a[href*='/space/']"
    );
    if (!(link instanceof HTMLAnchorElement) || link === wrap) return;
    if (wrap.parentElement === link) {
      link.insertAdjacentElement("afterend", wrap);
      return;
    }
    if (link.contains(wrap)) {
      link.insertAdjacentElement("afterend", wrap);
    }
  }

  function relocateCommentBarOutsideUserHover(wrap) {
    if (!(wrap instanceof HTMLElement)) return;
    const bar = wrap.querySelector(`.${BAR_CLASS}`);
    if (bar?.dataset.placement !== "comment") {
      relocateWrapOutsideNicknameLink(wrap);
      return;
    }
    relocateWrapOutsideNicknameLink(wrap);

    const header = wrap.closest?.(
      "bili-comment-renderer .comment-header, bili-comment-reply-renderer .comment-header, .comment-header, .comment-item-header"
    );
    if (header instanceof HTMLElement && header.contains(wrap)) {
      header.insertAdjacentElement("afterend", wrap);
      return;
    }
    const userInfo = wrap.closest?.(".user-info, .sub-reply-info");
    if (userInfo instanceof HTMLElement && userInfo.contains(wrap)) {
      userInfo.insertAdjacentElement("afterend", wrap);
      return;
    }
    const row = findCommentUserRow(wrap);
    if (row instanceof HTMLElement && row.contains(wrap)) {
      row.insertAdjacentElement("afterend", wrap);
      return;
    }
    const renderer = wrap.closest?.("bili-comment-user-renderer");
    if (renderer instanceof HTMLElement && renderer.contains(wrap)) {
      renderer.insertAdjacentElement("afterend", wrap);
      return;
    }
    const userName = wrap.closest?.("#user-name, .user-name");
    if (userName instanceof HTMLElement && userName.contains(wrap)) {
      userName.insertAdjacentElement("afterend", wrap);
    }
  }

  /** UP 主条 / 推荐卡：emoji 紧挨昵称链接右侧 */
  function relocateEmojiBarBesideNickname(wrap) {
    if (!(wrap instanceof HTMLElement)) return;
    const bar = wrap.querySelector(`.${BAR_CLASS}`);
    const placement = bar?.dataset.placement;
    if (placement !== "up" && placement !== "card") return;
    relocateWrapOutsideNicknameLink(wrap);
    const link =
      wrap.closest?.("a.up-name[href*='space.bilibili.com']") ||
      (() => {
        const card = wrap.closest(
          ".video-page-card-small, .rec-list-item, .rcmd-item, .bili-video-card"
        );
        if (!(card instanceof Element)) return null;
        for (const sel of RECO_UPNAME_LINK_SELECTORS) {
          const a = card.querySelector(sel);
          if (a instanceof HTMLAnchorElement) return a;
        }
        return null;
      })();
    if (link instanceof HTMLAnchorElement && link.nextElementSibling !== wrap) {
      link.insertAdjacentElement("afterend", wrap);
      const row = link.parentElement;
      if (row instanceof HTMLElement) {
        row.style.display = "inline-flex";
        row.style.alignItems = "center";
        row.style.flexWrap = "nowrap";
        row.style.gap = "4px";
      }
    }
  }

  function relocateBarForPlacement(wrap) {
    if (!(wrap instanceof HTMLElement)) return;
    const bar = wrap.querySelector(`.${BAR_CLASS}`);
    const placement = bar?.dataset.placement;
    if (placement === "comment") relocateCommentBarOutsideUserHover(wrap);
    else if (placement === "up" || placement === "card") relocateEmojiBarBesideNickname(wrap);
    else relocateWrapOutsideNicknameLink(wrap);
  }

  const PROFILE_HOVER_SHIELD_EVENTS = [
    "mouseenter",
    "mouseover",
    "pointerenter",
    "pointerover",
  ];

  function hideBiliProfilePopovers() {
    const hideIn = (root) => {
      if (!root?.querySelectorAll) return;
      try {
        root.querySelectorAll(PROFILE_POPOVER_SELECTOR).forEach((el) => {
          if (!(el instanceof HTMLElement)) return;
          el.style.setProperty("display", "none", "important");
          el.style.setProperty("visibility", "hidden", "important");
          el.style.setProperty("pointer-events", "none", "important");
        });
      } catch {
        /* ignore */
      }
    };
    try {
      hideIn(document);
      for (const host of BiliDom.getCommentHosts?.() || []) {
        hideIn(host);
        const sr = openShadowRoot(host);
        if (sr) hideIn(sr);
      }
    } catch {
      /* ignore */
    }
  }

  function shieldSiteProfileHover(el) {
    if (!(el instanceof HTMLElement)) return;
    const stop = (e) => {
      e.stopPropagation();
      hideBiliProfilePopovers();
    };
    for (const type of PROFILE_HOVER_SHIELD_EVENTS) {
      el.addEventListener(type, stop, true);
    }
  }

  function showEmptySlot(slotEl) {
    if (slotEl.classList.contains("biliindex-slot-filled")) return;
    slotEl.innerHTML = "";
    const label = document.createElement("span");
    label.className = "biliindex-slot-label biliindex-slot-empty-mark";
    label.textContent = slotEl.dataset.slot === "emoji" ? "😀" : "标";
    slotEl.appendChild(label);
  }

  const DEFAULT_SLOT_STYLE =
    "display:inline-grid!important;place-items:center;min-width:28px;min-height:24px;padding:4px 8px;border-radius:999px;border:1px solid #c9ccd0;background:#f6f7f8;color:#61666d;cursor:pointer;font-size:12px;line-height:1;text-align:center;opacity:1!important;box-sizing:border-box;";

  const SPACE_SLOT_THEME = {
    slot:
      "display:inline-grid!important;place-items:center;min-width:28px;min-height:24px;padding:4px 8px;border-radius:999px;border:1px solid rgba(255,255,255,0.9);background:#fb7299;color:#fff;cursor:pointer;font-size:12px;line-height:1;text-align:center;font-weight:600;box-sizing:border-box;",
    label: "",
    count: "",
    filledBg: "#fb7299",
    filledBorder: "rgba(255,255,255,0.95)",
  };

  function isSpacePlacement(placementOrBar) {
    if (placementOrBar instanceof HTMLElement) {
      return placementOrBar.dataset?.placement === "space";
    }
    return placementOrBar === "space";
  }

  function applyInlineBarStyles(bar, placement) {
    const isSpace = isSpacePlacement(placement);
    bar.style.cssText =
      "display:inline-flex!important;align-items:center;gap:5px;margin-left:6px;vertical-align:middle;flex-shrink:0;position:relative;z-index:50;visibility:visible!important;opacity:1!important;pointer-events:auto!important;";
    if (isSpace) bar.classList.add("biliindex-bar-space");
    else bar.classList.remove("biliindex-bar-space");
    const isComment = placement === "comment";
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((slot) => {
      if (isSpace) {
        slot.style.cssText = SPACE_SLOT_THEME.slot;
      } else if (isComment) {
        slot.style.cssText =
          DEFAULT_SLOT_STYLE +
          "background:#ffffff!important;border-color:#e3e5e7!important;";
        showEmptySlot(slot);
      } else {
        slot.style.cssText = DEFAULT_SLOT_STYLE;
        showEmptySlot(slot);
      }
    });
  }

  function createBar(upMid, layout, placement) {
    const bar = document.createElement("span");
    bar.className = BAR_CLASS;
    bar.dataset.upMid = upMid;
    bar.dataset.layout = layout;
    if (placement) bar.dataset.placement = placement;
    const names =
      layout === "full" ? ["tag1", "tag2", "tag3", "emoji"] : ["emoji"];
    for (const s of names) {
      const slot = document.createElement("button");
      slot.type = "button";
      slot.className = SLOT_CLASS;
      slot.dataset.slot = s;
      bar.appendChild(slot);
    }
    applyInlineBarStyles(bar, placement);
    return bar;
  }

  function createBarWrap(bar) {
    const wrap = document.createElement("span");
    wrap.className = WRAP_CLASS;
    wrap.setAttribute(EXT_MARK, "1");
    wrap.style.cssText =
      "display:inline-flex;align-items:center;margin-left:6px;vertical-align:middle;position:relative;z-index:12;pointer-events:auto;";
    wrap.appendChild(bar);
    return wrap;
  }

  function dedupeZoneKey(wrap, bar) {
    const placement = bar.dataset.placement || "";
    const mid = bar.dataset.upMid || "";
    if (placement === "card") {
      const card = wrap.closest(
        ".video-page-card-small, .rec-list-item, .rcmd-item, .bili-video-card"
      );
      return `card:${mid}:${card || "orphan"}`;
    }
    if (placement === "up") return `up:${mid}`;
    if (placement === "comment") {
      const item = wrap.closest(
        "bili-comment-renderer, bili-comment-reply-renderer, .reply-item"
      );
      return `comment:${mid}:${item || "orphan"}`;
    }
    if (placement === "space") return `space:${mid}`;
    return `other:${mid}:${placement}`;
  }

  function dedupeAllWrapsGlobally() {
    const seen = new Map();
    const wraps = [];
    const visit = (root) => {
      try {
        root.querySelectorAll(`.${WRAP_CLASS}[${EXT_MARK}="1"]`).forEach((w) => wraps.push(w));
      } catch {
        /* ignore */
      }
    };
    visit(document);
    for (const host of BiliDom.getCommentHosts?.() || []) {
      visit(host);
      const sr = getShadowRoot(host);
      if (sr) visit(sr);
    }
    for (const wrap of wraps) {
      if (!(wrap instanceof HTMLElement)) continue;
      const bar = wrap.querySelector(`.${BAR_CLASS}`);
      if (!(bar instanceof HTMLElement)) {
        wrap.remove();
        continue;
      }
      const key = dedupeZoneKey(wrap, bar);
      if (seen.has(key)) {
        wrap.remove();
        continue;
      }
      seen.set(key, wrap);
    }
  }

  function tagLabel(tag) {
    if (typeof BiliIndex !== "undefined" && BiliIndex.formatTagLabel) {
      return BiliIndex.formatTagLabel(tag);
    }
    return tag?.name || "";
  }

  function tagDisplayName(tag) {
    return (tag?.name || tag?.code || "").trim();
  }

  function tagEmojiIcon(tag) {
    if (typeof BiliIndex !== "undefined" && BiliIndex.resolveTagIcon) {
      return BiliIndex.resolveTagIcon(tag) || "·";
    }
    const ic = (tag?.icon || "").trim();
    return ic || "·";
  }

  function renderEmojiOnlySlot(slotEl, tag) {
    const bar = slotEl.closest(`.${BAR_CLASS}`);
    const isSpace = isSpacePlacement(bar);
    slotEl.innerHTML = "";
    slotEl.classList.remove("biliindex-slot-locked-emoji");
    if (!tag || (!tag.icon && !tag.code)) {
      slotEl.classList.remove("biliindex-slot-filled");
      delete slotEl.dataset.tagCode;
      if (isSpace) slotEl.style.cssText = SPACE_SLOT_THEME.slot;
      showEmptySlot(slotEl);
      return;
    }
    slotEl.dataset.tagCode = tag.code || "";
    slotEl.classList.add("biliindex-slot-filled", "biliindex-slot-locked-emoji");
    const label = document.createElement("span");
    label.className = "biliindex-slot-label";
    label.textContent = tagEmojiIcon(tag);
    slotEl.appendChild(label);
    slotEl.title = tagDisplayName(tag) || "鳄鱼";
    if (isSpace) {
      slotEl.style.background = SPACE_SLOT_THEME.filledBg;
      slotEl.style.borderColor = SPACE_SLOT_THEME.filledBorder;
      slotEl.style.color = "#fff";
    } else {
      slotEl.style.background = "#e3f6fc";
      slotEl.style.borderColor = "#00aeec";
      slotEl.style.opacity = "1";
    }
  }

  function renderSlotContent(slotEl, tag) {
    const bar = slotEl.closest(`.${BAR_CLASS}`);
    const isSpace = isSpacePlacement(bar);
    slotEl.innerHTML = "";
    slotEl.classList.remove("biliindex-slot-locked-emoji");
    if (!tag || (!tag.name && !tag.icon && !tag.code)) {
      slotEl.classList.remove("biliindex-slot-filled");
      delete slotEl.dataset.tagCode;
      if (isSpace) slotEl.style.cssText = SPACE_SLOT_THEME.slot;
      showEmptySlot(slotEl);
      return;
    }
    slotEl.dataset.tagCode = tag.code || "";
    const isEmoji = slotEl.dataset.slot === "emoji";
    const label = document.createElement("span");
    label.className = "biliindex-slot-label";
    if (isEmoji) {
      const name = tagDisplayName(tag);
      label.textContent = tagEmojiIcon(tag);
      slotEl.title = name;
    } else {
      label.textContent = tagLabel(tag);
    }
    slotEl.appendChild(label);
    const votes = Number(tag.vote_count) || 0;
    const countText = formatVoteCountForTag(tag);
    if (countText) {
      const c = document.createElement("span");
      c.className = "biliindex-slot-count";
      c.textContent = countText;
      slotEl.dataset.voteCount = String(votes);
      slotEl.appendChild(c);
    } else {
      delete slotEl.dataset.voteCount;
    }
    slotEl.classList.add("biliindex-slot-filled");
    if (isSpace) {
      slotEl.style.background = SPACE_SLOT_THEME.filledBg;
      slotEl.style.borderColor = SPACE_SLOT_THEME.filledBorder;
      slotEl.style.color = "#fff";
    } else {
      slotEl.style.background = "#e3f6fc";
      slotEl.style.borderColor = "#00aeec";
      slotEl.style.opacity = "1";
    }
  }

  function passerbySlotFallback(voteCount) {
    return {
      id: 0,
      code: PASSERBY_CODE,
      name: "路人",
      icon: "",
      tag_kind: "category",
      level: 1,
      vote_count: voteCount || 1,
    };
  }

  function mergeDisplayPreserveFilled(bar, incoming) {
    if (!incoming) return defaultDisplay();
    const slotsIn = incoming.slots || [null, null, null];
    if (slotsIn[0] && incoming.emoji) return incoming;
    const next = { ...incoming };
    const slots = [...slotsIn];
    const slotKeys = ["tag1", "tag2", "tag3"];
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((el) => {
      const slot = el.dataset.slot;
      if (!slot || slot === "emoji") return;
      const idx = { tag1: 0, tag2: 1, tag3: 2 }[slot];
      if (idx == null || slots[idx]) return;
      if (!el.classList.contains("biliindex-slot-filled")) return;
      const code = el.dataset.tagCode;
      if (!code) return;
      const name = (el.querySelector(".biliindex-slot-label")?.textContent || "").trim();
      const vote_count =
        Number(el.dataset.voteCount) ||
        (typeof BiliIndex !== "undefined" && BiliIndex.parseVoteCountDisplay
          ? BiliIndex.parseVoteCountDisplay(
              el.querySelector(".biliindex-slot-count")?.textContent
            )
          : Number(el.querySelector(".biliindex-slot-count")?.textContent)) ||
        1;
      slots[idx] = {
        code,
        name: name || code,
        icon: "",
        vote_count,
        tag_kind: slot === "tag1" ? "category" : slot === "tag3" ? "style" : "sub",
      };
    });
    next.slots = slots;
    if (!next.emoji) {
      const emojiEl = bar.querySelector(`.${SLOT_CLASS}[data-slot="emoji"]`);
      if (
        emojiEl?.classList.contains("biliindex-slot-filled") &&
        emojiEl.dataset.tagCode
      ) {
        const name = (emojiEl.querySelector(".biliindex-slot-label")?.textContent || "").trim();
        next.emoji = {
          code: emojiEl.dataset.tagCode,
          name: emojiEl.title || name || emojiEl.dataset.tagCode,
          icon: name,
          vote_count:
            Number(emojiEl.dataset.voteCount) ||
            (typeof BiliIndex !== "undefined" && BiliIndex.parseVoteCountDisplay
              ? BiliIndex.parseVoteCountDisplay(
                  emojiEl.querySelector(".biliindex-slot-count")?.textContent
                )
              : Number(emojiEl.querySelector(".biliindex-slot-count")?.textContent)) ||
            1,
          tag_kind: "emoji",
        };
      }
    }
    return next;
  }

  function applyDisplay(bar, display) {
    display = mergeDisplayPreserveFilled(bar, display);
    const locked = !!display?.locked;
    bar.dataset.locked = locked ? "1" : "0";
    if (locked) {
      const croc = display?.slots?.[0] || display?.emoji || null;
      const slots = [croc, croc, croc];
      const emoji = display?.emoji || croc;
      bar.dataset.passerby = "0";
      bar.dataset.biliindexTag1Code = croc?.code || "";
      bar.dataset.biliindexEmojiCode = emoji?.code || "";
      bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((el) => {
        el.style.display = "";
        const slot = el.dataset.slot;
        let tag = null;
        if (slot === "emoji") tag = emoji;
        else tag = slots[{ tag1: 0, tag2: 1, tag3: 2 }[slot]] ?? null;
        if (slot === "emoji" || bar.dataset.layout === "emoji-only") {
          renderEmojiOnlySlot(el, tag);
        } else {
          renderSlotContent(el, tag);
        }
        el.style.cursor = "not-allowed";
        el.title = "该 UP 标签已锁定，无法修改";
      });
      return;
    }
    const slots = [...(display?.slots || [null, null, null])];
    const emoji = display?.emoji || null;
    const passerby = !!display?.passerby;
    if (passerby && !slots[0]) {
      slots[0] = passerbySlotFallback(slots[0]?.vote_count || 1);
    }
    bar.dataset.passerby = passerby ? "1" : "0";
    bar.dataset.biliindexTag1Code = slots[0]?.code || "";
    bar.dataset.biliindexEmojiCode = emoji?.code || "";
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((el) => {
      if (passerby && (el.dataset.slot === "tag2" || el.dataset.slot === "tag3")) {
        el.style.display = "none";
        return;
      }
      el.style.display = "";
      const slot = el.dataset.slot;
      let tag = null;
      if (slot === "emoji") tag = emoji;
      else tag = slots[{ tag1: 0, tag2: 1, tag3: 2 }[slot]] ?? null;
      renderSlotContent(el, tag);
    });
  }

  const DISPLAY_CACHE_MAX = 512;
  const displayCache =
    typeof BiliIndex.createLruMap === "function"
      ? BiliIndex.createLruMap(DISPLAY_CACHE_MAX)
      : new Map();
  const idleCommentMountQueue = [];
  let idleCommentMountScheduled = false;
  const OFFSCREEN_MOUNT_BATCH = 10;
  const FLUSH_OFFSCREEN_MIDS_CAP = 48;
  const pendingDisplayMids = new Set();
  const displayFlushQueue = new Set();
  const visibleCommentMids = new Set();
  const displayInflight = new Map();
  /** 每次 invalidate 递增，丢弃过期的 display 请求结果 */
  const displayFetchGen = new Map();
  const barDisplayRetryTimers = new WeakMap();
  let displayFlushTimer = null;
  let displayFlushRunning = false;
  let commentBarVisibilityObserver = null;
  const DISPLAY_CACHE_PASSERBY_MS = 5 * 60 * 1000;
  const DISPLAY_CACHE_NORMAL_MS = 90 * 1000;
  const DISPLAY_CACHE_EMPTY_MS = 12 * 1000;
  let lastVideoPageKey = "";
  let pendingIncrementalNodes = [];

  function defaultDisplay() {
    return { slots: [null, null, null], emoji: null, passerby: false };
  }

  function normalizeDisplayRow(data) {
    const row = data ? { ...data } : defaultDisplay();
    const markCount = Number(row.passerby_mark_count) || 0;
    if ((row.passerby || markCount >= 1) && !(row.slots && row.slots[0])) {
      row.passerby = true;
      row.slots = [
        passerbySlotFallback(
          row.slots?.[0]?.vote_count || markCount || 1
        ),
        null,
        null,
      ];
    }
    if (markCount >= 1) row.passerby = true;
    if (data?.locked) row.locked = true;
    return row;
  }

  function cacheDisplay(mid, data) {
    const row = normalizeDisplayRow(data);
    displayCache.set(String(mid), { ...row, _at: Date.now() });
  }

  function getCachedDisplay(mid) {
    const row = displayCache.get(String(mid));
    if (!row) return null;
    const age = Date.now() - (row._at || 0);
    const ttl = row.passerby
      ? DISPLAY_CACHE_PASSERBY_MS
      : row._empty
        ? DISPLAY_CACHE_EMPTY_MS
        : DISPLAY_CACHE_NORMAL_MS;
    if (age > ttl) {
      displayCache.delete(String(mid));
      return null;
    }
    return row;
  }

  function invalidateDisplayCache(mid) {
    if (mid) {
      const key = String(mid);
      displayCache.delete(key);
      displayInflight.delete(key);
      displayFetchGen.set(key, (displayFetchGen.get(key) || 0) + 1);
    } else {
      displayCache.clear();
      displayInflight.clear();
      displayFetchGen.clear();
    }
  }

  function resetCommentStateIfVideoChanged() {
    const m = (location.pathname || "").match(/\/video\/(BV[\w]+|av\d+)/i);
    const key = m ? m[1].toUpperCase() : "";
    if (!key || key === lastVideoPageKey) return;
    lastVideoPageKey = key;
    invalidateDisplayCache();
    pendingDisplayMids.clear();
    displayFlushQueue.clear();
    visibleCommentMids.clear();
  }

  function isCommentBarLoading(bar) {
    if (bar?.dataset?.placement !== "comment") return false;
    for (const el of bar.querySelectorAll(`.${SLOT_CLASS}`)) {
      if (el.classList.contains("biliindex-slot-filled")) continue;
      const t = (el.textContent || "").trim();
      if (t === "…" || t === "...") return true;
    }
    return false;
  }

  function isBarAwaitingDisplay(bar) {
    if (!(bar instanceof HTMLElement)) return false;
    /* 未打的槽显示「标」是正常态，不应触发 refresh；仅「…」表示仍在拉取展示 */
    for (const el of bar.querySelectorAll(`.${SLOT_CLASS}`)) {
      const t = (el.textContent || "").trim();
      if (t === "…" || t === "...") return true;
    }
    return false;
  }

  function finalizeCommentBarDisplay(bar) {
    if (!bar?.isConnected || bar.dataset.placement !== "comment") return;
    const mid = bar.dataset.upMid;
    let cached = mid ? getCachedDisplay(mid) : null;
    if (cached?._empty && isCommentBarLoading(bar)) {
      invalidateDisplayCache(mid);
      displayFlushQueue.add(String(mid));
      pendingDisplayMids.add(String(mid));
      scheduleDisplayFlush(true);
      cached = null;
    }
    const d = cached && !cached._empty ? cached : defaultDisplay();
    applyDisplay(bar, d);
  }

  function finalizeAllLoadingCommentBars() {
    forEachBar((bar) => {
      if (!isCommentBarLoading(bar)) return;
      const mid = bar.dataset.upMid;
      const cached = mid ? getCachedDisplay(mid) : null;
      if (cached && !cached._empty) {
        applyDisplay(bar, cached);
        return;
      }
      if (cached?._empty) finalizeCommentBarDisplay(bar);
    });
  }

  function scheduleBarDisplayRetry(bar) {
    if (bar.dataset.placement !== "comment") return;
    const prev = barDisplayRetryTimers.get(bar);
    if (prev) clearTimeout(prev);
    barDisplayRetryTimers.set(
      bar,
      setTimeout(() => {
        barDisplayRetryTimers.delete(bar);
        if (!bar.isConnected || !isCommentBarLoading(bar)) return;
        const mid = String(bar.dataset.upMid || "");
        if (!mid) {
          finalizeCommentBarDisplay(bar);
          return;
        }
        invalidateDisplayCache(mid);
        pendingDisplayMids.add(mid);
        displayFlushQueue.add(mid);
        scheduleDisplayFlush(true);
        setTimeout(() => {
          if (bar.isConnected && isCommentBarLoading(bar)) {
            finalizeCommentBarDisplay(bar);
          }
        }, 6000);
      }, 2600)
    );
  }

  function shouldDeferCommentDisplayFetch() {
    return (
      typeof BiliIndex !== "undefined" && BiliIndex.isCommentsSectionLoading?.()
    );
  }

  let commentDisplayReadyHooked = false;

  function scheduleCommentDisplayWhenReady() {
    if (!shouldDeferCommentDisplayFetch()) {
      scheduleDisplayFlush(true);
      return;
    }
    if (commentDisplayReadyHooked) return;
    commentDisplayReadyHooked = true;
    const run = () => {
      commentDisplayReadyHooked = false;
      scheduleDisplayFlush(true);
    };
    if (typeof BiliIndex !== "undefined" && BiliIndex.whenCommentsReady) {
      BiliIndex.whenCommentsReady(run);
    } else {
      setTimeout(run, 600);
    }
  }

  function isCommentBarNearViewport(bar) {
    if (!(bar instanceof HTMLElement)) return false;
    try {
      const r = bar.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight || 800;
      return r.bottom >= -300 && r.top <= vh + 300;
    } catch {
      return false;
    }
  }

  function isCommentItemNearViewport(item) {
    if (!(item instanceof Element)) return false;
    try {
      const r = item.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight || 800;
      return r.bottom >= -360 && r.top <= vh + 360;
    } catch {
      return false;
    }
  }

  function scheduleIdleCommentMounts() {
    if (idleCommentMountScheduled || !idleCommentMountQueue.length) return;
    idleCommentMountScheduled = true;
    const run = () => {
      idleCommentMountScheduled = false;
      const batch = idleCommentMountQueue.splice(0, OFFSCREEN_MOUNT_BATCH);
      for (const item of batch) processCommentItem(item);
      if (idleCommentMountQueue.length) scheduleIdleCommentMounts();
    };
    if (typeof BiliIndex.runWhenIdle === "function") {
      BiliIndex.runWhenIdle(run, { timeout: 900 });
    } else if (typeof requestIdleCallback === "function") {
      requestIdleCallback(run, { timeout: 900 });
    } else {
      setTimeout(run, 48);
    }
  }

  function queueOffscreenCommentMount(item) {
    if (!(item instanceof HTMLElement)) return;
    if (commentItemRun.get(item) === scanRunId) return;
    idleCommentMountQueue.push(item);
    scheduleIdleCommentMounts();
  }

  function orderMidsByVisibility(mids) {
    const unique = [...new Set(mids.map(String))];
    const visible = [];
    const rest = [];
    for (const mid of unique) {
      if (visibleCommentMids.has(mid)) visible.push(mid);
      else rest.push(mid);
    }
    return [...visible, ...rest];
  }

  function ensureCommentBarVisibilityObserver() {
    if (commentBarVisibilityObserver) return;
    try {
      commentBarVisibilityObserver = new IntersectionObserver(
        (entries) => {
          for (const entry of entries) {
            const bar = entry.target;
            if (!(bar instanceof HTMLElement) || bar.dataset.placement !== "comment") {
              continue;
            }
            const mid = bar.dataset.upMid;
            if (!mid) continue;
            if (entry.isIntersecting) {
              visibleCommentMids.add(String(mid));
              const cached = getCachedDisplay(mid);
              if (cached && !cached._empty) {
                applyDisplay(bar, cached);
              } else {
                pendingDisplayMids.add(String(mid));
                displayFlushQueue.add(String(mid));
                if (shouldDeferCommentDisplayFetch()) {
                  scheduleCommentDisplayWhenReady();
                } else {
                  scheduleDisplayFlush(true);
                }
              }
            } else {
              visibleCommentMids.delete(String(mid));
            }
          }
        },
        { root: null, rootMargin: "280px 0px", threshold: 0 }
      );
    } catch {
      commentBarVisibilityObserver = null;
    }
  }

  function observeCommentBar(bar) {
    if (bar.dataset.placement !== "comment") return;
    ensureCommentBarVisibilityObserver();
    if (!commentBarVisibilityObserver) return;
    try {
      commentBarVisibilityObserver.observe(bar);
    } catch {
      /* ignore */
    }
  }

  async function fetchDisplayOneUncached(upMid) {
    const mid = String(upMid);
    const genAtStart = displayFetchGen.get(mid) || 0;
    const path = `/api/tag/active-list?up_mid=${encodeURIComponent(mid)}`;
    const tryFetch = async (fetcher) => {
      const data = await fetcher(path);
      return normalizeDisplayRow(data);
    };
    try {
      let data = null;
      const token = await getAuthToken();
      if (token && typeof BiliIndex !== "undefined" && BiliIndex.apiFetch) {
        try {
          data = await tryFetch((p) => BiliIndex.apiFetch(p));
        } catch {
          /* 登录态失败时改走公开接口 */
        }
      }
      if (!data && typeof BiliIndex !== "undefined" && BiliIndex.fetchPublic) {
        data = await tryFetch((p) => BiliIndex.fetchPublic(p));
      }
      if (!data) throw new Error("display fetch failed");
      if ((displayFetchGen.get(mid) || 0) !== genAtStart) {
        const kept = getCachedDisplay(mid);
        return kept && !kept._empty ? kept : defaultDisplay();
      }
      apiOk = true;
      cacheDisplay(mid, data);
      return data;
    } catch {
      apiOk = false;
      if ((displayFetchGen.get(mid) || 0) !== genAtStart) {
        const kept = getCachedDisplay(mid);
        return kept && !kept._empty ? kept : defaultDisplay();
      }
      return { ...defaultDisplay(), _empty: true };
    }
  }

  async function fetchDisplayOne(upMid) {
    const mid = String(upMid);
    const cached = getCachedDisplay(mid);
    if (cached && !cached._empty) return cached;
    if (cached?._empty && Date.now() - (cached._at || 0) < 4000) return cached;
    if (displayInflight.has(mid)) return displayInflight.get(mid);
    const p = fetchDisplayOneUncached(mid);
    displayInflight.set(mid, p);
    try {
      return await p;
    } finally {
      displayInflight.delete(mid);
    }
  }

  async function fetchDisplaysBatch(mids) {
    const need = [...new Set(mids.map(String))].filter((m) => {
      if (!/^\d+$/.test(m)) return false;
      const cached = getCachedDisplay(m);
      if (!cached) return true;
      if (cached._empty) {
        return Date.now() - (cached._at || 0) > 2200;
      }
      return false;
    });
    if (!need.length) return;
    const body = JSON.stringify({ up_mids: need.slice(0, 80) });
    const batchOpts = { method: "POST", body };
    try {
      const token = await getAuthToken();
      let data = null;
      if (token && typeof BiliIndex !== "undefined" && BiliIndex.apiFetch) {
        data = await BiliIndex.apiFetch("/api/tag/display-batch", batchOpts);
      } else if (typeof BiliIndex !== "undefined" && BiliIndex.fetchPublicJson) {
        data = await BiliIndex.fetchPublicJson("/api/tag/display-batch", batchOpts);
      }
      if (!data) {
        await Promise.all(need.slice(0, 8).map((mid) => fetchDisplayOne(mid)));
        for (const mid of need.slice(8)) {
          if (!getCachedDisplay(mid)) {
            cacheDisplay(mid, { ...defaultDisplay(), _empty: true });
          }
        }
        return;
      }
      apiOk = true;
      const items = data?.items || {};
      for (const mid of need) {
        const row = items[mid] ?? items[String(mid)];
        if (row) {
          cacheDisplay(mid, row);
          if (row.passerby && BiliIndex.rememberPasserbyMid) {
            void BiliIndex.rememberPasserbyMid(mid);
          }
        } else {
          cacheDisplay(mid, { ...defaultDisplay(), _empty: true });
        }
      }
    } catch (e) {
      console.warn(
        "[BiliIndex] display-batch 失败，跳过本批展示拉取",
        e?.message || e
      );
      for (const mid of need) {
        if (!getCachedDisplay(mid)) {
          cacheDisplay(mid, { ...defaultDisplay(), _empty: true, _fetchFailed: true });
        }
      }
    }
  }

  async function fetchDisplaysBatchAll(mids) {
    const ordered = orderMidsByVisibility(mids);
    for (let i = 0; i < ordered.length; i += 80) {
      await fetchDisplaysBatch(ordered.slice(i, i + 80));
    }
  }

  function scheduleDisplayFlush(priority = false) {
    clearTimeout(displayFlushTimer);
    const burst = pendingDisplayMids.size >= 6;
    const delay = priority || burst ? 0 : 6;
    displayFlushTimer = setTimeout(() => {
      displayFlushTimer = null;
      void flushPendingDisplays();
    }, delay);
  }

  function queueBarDisplay(bar) {
    const mid = bar?.dataset?.upMid;
    if (!mid) return;
    const midStr = String(mid);
    const cached = getCachedDisplay(midStr);
    if (cached && !cached._empty) {
      applyDisplay(bar, cached);
      if (bar.dataset.placement === "comment") observeCommentBar(bar);
      return;
    }
    let priority = false;
    const isComment = bar.dataset.placement === "comment";
    if (isComment) {
      observeCommentBar(bar);
      if (isCommentBarNearViewport(bar)) {
        visibleCommentMids.add(midStr);
        priority = true;
      } else if (visibleCommentMids.has(midStr)) {
        priority = true;
      } else if (shouldDeferCommentDisplayFetch()) {
        pendingDisplayMids.add(midStr);
        scheduleCommentDisplayWhenReady();
        return;
      }
    }
    pendingDisplayMids.add(midStr);
    displayFlushQueue.add(midStr);
    scheduleDisplayFlush(priority);
  }

  async function refetchKnownPasserbyDisplays(mids) {
    if (typeof BiliIndex === "undefined" || !BiliIndex.isPasserbyMidKnown) return;
    const retry = [];
    for (const mid of mids) {
      if (!(await BiliIndex.isPasserbyMidKnown(mid))) continue;
      const cached = getCachedDisplay(mid);
      if (cached?.passerby && cached?.slots?.[0]) continue;
      retry.push(String(mid));
    }
    if (!retry.length) return;
    await fetchDisplaysBatch(retry);
  }

  function applyDisplayForMids(midsFilter, placementOnly) {
    const allow = midsFilter ? new Set(midsFilter.map(String)) : null;
    forEachBar((bar) => {
      if (placementOnly && bar.dataset.placement !== placementOnly) return;
      const mid = bar.dataset.upMid;
      if (!mid || (allow && !allow.has(String(mid)))) return;
      const cached = getCachedDisplay(mid);
      if (!cached) return;
      const d = cached._empty ? defaultDisplay() : cached;
      applyDisplay(bar, d);
      document.dispatchEvent(
        new CustomEvent("biliindex:bar-refreshed", {
          detail: { upMid: mid },
        })
      );
    });
  }

  function applyDisplayToCommentBars(midsFilter) {
    applyDisplayForMids(midsFilter, "comment");
  }

  let commentDisplayRefreshTimer = null;

  async function refreshAllCommentBarDisplays(force = false) {
    const mids = [];
    forEachBar((bar) => {
      if (bar.dataset.upMid) mids.push(String(bar.dataset.upMid));
    });
    if (!mids.length) return;
    const unique = [...new Set(mids)];
    if (force) {
      for (const mid of unique) invalidateDisplayCache(mid);
    }
    await fetchDisplaysBatchAll(unique);
    await refetchKnownPasserbyDisplays(unique);
    applyDisplayForMids(unique);
  }

  function scheduleRefreshAllCommentDisplays(force = false) {
    clearTimeout(commentDisplayRefreshTimer);
    commentDisplayRefreshTimer = setTimeout(() => {
      void refreshAllCommentBarDisplays(force);
    }, force ? 120 : 480);
  }

  async function flushPendingDisplays() {
    for (const mid of pendingDisplayMids) displayFlushQueue.add(mid);
    pendingDisplayMids.clear();
    if (!displayFlushQueue.size) return;
    if (displayFlushRunning) return;
    displayFlushRunning = true;
    const allTouched = new Set();
    try {
      while (displayFlushQueue.size > 0 || pendingDisplayMids.size > 0) {
        for (const mid of pendingDisplayMids) displayFlushQueue.add(mid);
        pendingDisplayMids.clear();
        let mids = orderMidsByVisibility([...displayFlushQueue]);
        displayFlushQueue.clear();
        if (!mids.length) break;
        const visibleFirst = mids.filter((m) => visibleCommentMids.has(m));
        const rest = mids.filter((m) => !visibleCommentMids.has(m));
        mids = [...visibleFirst, ...rest.slice(0, FLUSH_OFFSCREEN_MIDS_CAP)];
        for (const mid of mids) allTouched.add(mid);
        for (let i = 0; i < mids.length; i += 80) {
          const chunk = mids.slice(i, i + 80);
          await fetchDisplaysBatch(chunk);
          applyDisplayForMids(chunk);
        }
        if (rest.length > FLUSH_OFFSCREEN_MIDS_CAP) {
          for (const mid of rest.slice(FLUSH_OFFSCREEN_MIDS_CAP)) {
            displayFlushQueue.add(mid);
          }
        }
      }
      finalizeAllLoadingCommentBars();
      if (allTouched.size) {
        const touched = [...allTouched];
        void refetchKnownPasserbyDisplays(touched).then(() =>
          applyDisplayForMids(touched)
        );
        setTimeout(() => {
          forEachBar((bar) => {
            if (bar.dataset.placement !== "comment") return;
            if (!isBarAwaitingDisplay(bar) && !isCommentBarLoading(bar)) return;
            const mid = bar.dataset.upMid;
            if (!mid) return;
            pendingDisplayMids.add(String(mid));
            displayFlushQueue.add(String(mid));
          });
          if (displayFlushQueue.size || pendingDisplayMids.size) {
            scheduleDisplayFlush(true);
          }
          finalizeAllLoadingCommentBars();
        }, 4000);
      }
    } finally {
      displayFlushRunning = false;
      if (displayFlushQueue.size > 0 || pendingDisplayMids.size > 0) {
        void flushPendingDisplays();
      }
    }
  }

  async function fetchDisplay(upMid) {
    return fetchDisplayOne(upMid);
  }

  async function refreshBar(bar, opts = {}) {
    if (!bar?.isConnected) return;
    const mid = String(bar.dataset.upMid || "");
    if (opts.force) invalidateDisplayCache(mid);
    const d = await fetchDisplay(bar.dataset.upMid);
    applyDisplay(bar, d);
    document.dispatchEvent(
      new CustomEvent("biliindex:bar-refreshed", {
        detail: { upMid: bar.dataset.upMid },
      })
    );
  }

  async function resolveEmojiPickerCategories(categories, emojis) {
    if (Array.isArray(categories) && categories.length) {
      return categories.slice().sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
    }
    let tree = [];
    if (typeof BiliIndex !== "undefined" && BiliIndex.storageGet) {
      const st = await BiliIndex.storageGet(["tagTree"]);
      tree = Array.isArray(st?.tagTree) ? st.tagTree : [];
    }
    const fromTree = tree
      .filter((t) => t.tag_kind === "emoji_cat")
      .sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
    if (fromTree.length) return fromTree;

    const parentIds = [
      ...new Set(
        (emojis || [])
          .map((t) => t.parent_id)
          .filter((id) => id != null && id !== "")
      ),
    ];
    if (!parentIds.length) return [];
    return parentIds
      .map((pid) => {
        const cat = tree.find((t) => Number(t.id) === Number(pid));
        return (
          cat || {
            id: pid,
            name: `分类 ${pid}`,
            code: String(pid),
            sort_order: Number(pid),
          }
        );
      })
      .sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
  }

  async function slotOptionsFromLocalCache(slot) {
    if (typeof BiliIndex === "undefined" || !BiliIndex.storageGet) return null;
    const { tagTree, quickTags } = await BiliIndex.storageGet(["tagTree", "quickTags"]);
    const pool = [
      ...(Array.isArray(tagTree) ? tagTree : []),
      ...(Array.isArray(quickTags) ? quickTags : []),
    ];
    if (!pool.length) return null;
    const seen = new Set();
    const uniq = pool.filter((t) => {
      const k = t?.code || t?.id;
      if (!k || seen.has(k)) return false;
      seen.add(k);
      return true;
    });
    const slotName = slot || "tag1";
    if (slotName === "emoji") {
      const items = uniq.filter(
        (t) =>
          (typeof BiliIndex.isEmojiTag === "function" && BiliIndex.isEmojiTag(t)) ||
          t.tag_kind === "emoji"
      );
      if (!items.length) return null;
      const categories = uniq.filter((t) => t.tag_kind === "emoji_cat");
      return {
        items,
        categories,
        parent: null,
        passerby: null,
        super_hate: null,
        frequent: [],
      };
    }
    if (slotName === "tag1") {
      const items = uniq.filter(
        (t) =>
          t.code !== PASSERBY_CODE &&
          t.code !== SUPER_HATE_CODE &&
          (t.level === 1 || t.parent_id == null || t.parent_id === "")
      );
      if (!items.length) return null;
      return { items, parent: null, passerby: null, super_hate: null, frequent: [] };
    }
    return null;
  }

  async function fetchSlotOptions(upMid, slot) {
    const path = `/api/tag/slot-options?up_mid=${encodeURIComponent(upMid)}&slot=${encodeURIComponent(slot)}`;
    const loadRemote = async () => {
      const token = await getAuthToken();
      return token && typeof BiliIndex !== "undefined" && BiliIndex.apiFetch
        ? await BiliIndex.apiFetch(path, { timeoutMs: 10000 })
        : await BiliIndex.fetchPublic(path);
    };
    try {
      const data = await Promise.race([
        loadRemote(),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error("加载标签列表超时")), 10000)
        ),
      ]);
      apiOk = true;
      return {
        items: data.items || [],
        categories: data.categories || [],
        hint: data.hint || null,
        parent: data.parent || null,
        passerby: data.passerby || null,
        super_hate: data.super_hate || null,
        frequent: data.frequent || [],
        loadError: null,
      };
    } catch (e) {
      apiOk = false;
      const msg =
        typeof BiliIndex !== "undefined" && BiliIndex.humanizeApiError
          ? BiliIndex.humanizeApiError(e)
          : e?.message || String(e);
      console.warn("[BiliIndex] slot-options 失败", upMid, slot, msg);
      const cached = await slotOptionsFromLocalCache(slot);
      if (cached && (cached.items?.length || cached.passerby)) {
        return {
          ...cached,
          hint: `${msg}（以下为扩展内缓存标签，打标仍需联网）`,
          loadError: msg,
        };
      }
      return {
        items: [],
        categories: [],
        hint: msg,
        parent: null,
        passerby: null,
        super_hate: null,
        frequent: [],
        loadError: msg,
      };
    }
  }

  function sendMessage(msg) {
    return new Promise((resolve) => {
      chrome.runtime.sendMessage(msg, (res) => {
        void chrome.runtime.lastError;
        resolve(res ?? { ok: false });
      });
    });
  }

  async function getAuthToken() {
    if (typeof BiliIndex !== "undefined" && BiliIndex.getToken) {
      return BiliIndex.getToken();
    }
    const auth = await sendMessage({ type: "GET_AUTH" });
    return auth?.token || null;
  }

  function resolveFanCountForBar(bar, anchor, itemRoot) {
    const nickname = bar.dataset.nickname || "";
    if (isDeactivatedCommentItem(itemRoot instanceof HTMLElement ? itemRoot : null, anchor)) {
      return null;
    }
    if (DEACTIVATED_RE.test(nickname)) return null;
    if (bar.dataset.fanCount != null && bar.dataset.fanCount !== "") {
      const n = Number(bar.dataset.fanCount);
      if (Number.isFinite(n)) return Math.floor(n);
    }
    if (typeof BiliIndex !== "undefined") {
      const item =
        itemRoot instanceof HTMLElement
          ? itemRoot
          : anchor?.closest?.("bili-comment-renderer, bili-comment-reply-renderer");
      if (item) {
        const fromComment = BiliIndex.fanCountFromCommentItem(item);
        if (fromComment != null) return fromComment;
      }
      const fromVideo = BiliIndex.fanCountFromVideoPage(bar.dataset.upMid);
      if (fromVideo != null) return fromVideo;
      const fromSpace = BiliIndex.fanCountFromSpacePage?.(bar.dataset.upMid);
      if (fromSpace != null) return fromSpace;
    }
    return null;
  }

  async function submitQuickTag(bar, slotName, tag) {
    if (typeof BiliIndex !== "undefined" && BiliIndex.checkTagFrequency) {
      try {
        const check = await BiliIndex.checkTagFrequency(bar.dataset.upMid, slotName);
        if (check && check.allowed === false) {
          return { ok: false, error: check.error || "1 分钟内打标次数已达上限，请稍后再试" };
        }
      } catch {
        /* 预检失败时仍由服务端校验 */
      }
    }
    const itemRoot = bar.closest(
      "bili-comment-renderer, bili-comment-reply-renderer, .reply-item"
    );
    const anchor = itemRoot?.querySelector?.(
      "a[href*='space.bilibili.com'], a[href*='/space/']"
    );
    let fanCount = resolveFanCountForBar(bar, anchor, itemRoot);
    if (
      fanCount == null &&
      typeof BiliIndex !== "undefined" &&
      BiliIndex.fetchFanCountAsync
    ) {
      fanCount = await BiliIndex.fetchFanCountAsync(bar.dataset.upMid);
      if (fanCount != null) bar.dataset.fanCount = String(fanCount);
    }
    let nickname = (bar.dataset.nickname || "").trim();
    if (!nickname) {
      const anchor = itemRoot?.querySelector?.(
        "a[href*='space.bilibili.com'], a[href*='/space/']"
      );
      if (anchor) nickname = (anchor.textContent || "").trim();
    }
    if (!nickname && isSpaceHostPage()) {
      for (const sel of [
        ".h-name",
        ".n-name",
        "#h-name",
        ".nickname",
        ".space-profile__nickname",
      ]) {
        const el = document.querySelector(sel);
        const text = (el?.textContent || "").trim().replace(/\s+/g, " ");
        if (text && !DEACTIVATED_RE.test(text)) {
          nickname = text;
          break;
        }
      }
    }
    if (nickname) bar.dataset.nickname = nickname.slice(0, 64);
    const payload = {
      slot: slotName,
      nickname: nickname || "",
      fan_count: fanCount,
    };
    if (typeof BiliIndex !== "undefined" && BiliIndex.quickTag) {
      try {
        return await BiliIndex.quickTag(bar.dataset.upMid, tag.code, payload);
      } catch (e) {
        return { ok: false, error: e?.message || String(e) };
      }
    }
    return sendMessage({
      type: "QUICK_TAG",
      up_mid: bar.dataset.upMid,
      tag_code: tag.code,
      ...payload,
    });
  }

  function showBarToast(bar, message, isError = false) {
    document.querySelector(".biliindex-toast")?.remove();
    const t = document.createElement("div");
    t.className = "biliindex-toast";
    t.textContent = message;
    const bg = isError ? "#f85a5a" : "#00aeec";
    t.style.cssText = `position:fixed;z-index:2147483647;padding:8px 12px;background:${bg};color:#fff;border-radius:6px;font-size:13px;font-weight:600;pointer-events:none;max-width:280px;`;
    const rect = bar.getBoundingClientRect();
    t.style.left = `${Math.max(8, rect.left)}px`;
    t.style.top = `${Math.max(8, rect.top - 36)}px`;
    document.body.appendChild(t);
    setTimeout(() => t.remove(), isError ? 3200 : 2000);
  }

  function removeAllMenus() {
    document.querySelectorAll(`.${MENU_CLASS}`).forEach((m) => m.remove());
  }

  /** 开菜单后短暂忽略 document 关闭（避免 pointerdown 与 click 同一次手势关掉菜单） */
  let menuCloseSuppressUntil = 0;
  let lastSlotMenuOpenAt = 0;

  function requestSlotMenu(bar, slotEl) {
    const now = Date.now();
    if (now - lastSlotMenuOpenAt < 280) return;
    lastSlotMenuOpenAt = now;
    menuCloseSuppressUntil = now + 700;
    hideBiliProfilePopovers();
    void toggleSlotMenu(bar, slotEl);
  }

  function showMenuLoading(bar, slotEl) {
    removeAllMenus();
    const menu = document.createElement("div");
    menu.className = MENU_CLASS;
    menu.dataset.biliindexLoading = "1";
    menu.style.cssText =
      "position:fixed;z-index:2147483647;min-width:100px;padding:8px 12px;background:#fff;border:1px solid #e3e5e7;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.18);font-size:12px;color:#9499a0;";
    menu.textContent = "加载标签…";
    positionMenu(menu, slotEl);
  }

  function bindTagPickButton(bar, slotEl, btn, tag, opts = {}) {
    btn.addEventListener("mousedown", (e) => {
      e.stopPropagation();
      menuCloseSuppressUntil = Date.now() + 800;
    });
    btn.addEventListener("click", async (e) => {
      e.preventDefault();
      e.stopPropagation();
      if (bar.dataset.locked === "1") {
        removeAllMenus();
        showBarToast(bar, "该 UP 标签已锁定为鳄鱼，无法修改", true);
        return;
      }
      const token = await getAuthToken();
      if (!token) {
        removeAllMenus();
        showBarToast(bar, "请先点击扩展图标登录后再打标", true);
        return;
      }
      const slotName = slotEl.dataset.slot;
      removeAllMenus();
      applyTagToSlot(bar, slotName, tag);
      const res = await submitQuickTag(bar, slotName, tag);
      if (res?.ok) {
        const mid = bar.dataset.upMid;
        if (res.display) {
          const row = normalizeDisplayRow(res.display);
          cacheDisplay(mid, row);
          applyDisplay(bar, row);
        } else {
          invalidateDisplayCache(mid);
          applyDisplay(bar, await fetchDisplay(mid));
        }
        let toastMsg = `${res.message || "打标成功"}：${tagLabel(res.tag || tag)}`;
        if (typeof BiliIndex !== "undefined" && BiliIndex.confirmPointsAfterTag) {
          try {
            const pv = await BiliIndex.confirmPointsAfterTag(res);
            if (pv.message) toastMsg += `（${pv.message}）`;
          } catch {
            /* ignore verify errors */
          }
        }
        showBarToast(bar, toastMsg);
        const needSuperHateSync =
          tag.code === SUPER_HATE_CODE ||
          res?.super_hate ||
          res?.tag_code === SUPER_HATE_CODE ||
          res?.user_super_hate;
        if (needSuperHateSync) {
          if (typeof BiliIndex !== "undefined" && BiliIndex.syncSuperHateRules) {
            await BiliIndex.syncSuperHateRules();
          } else if (typeof BiliIndex !== "undefined" && BiliIndex.syncSuperHateMids) {
            await BiliIndex.syncSuperHateMids();
          } else {
            document.dispatchEvent(new CustomEvent("biliindex-super-hate-changed"));
          }
        } else if (slotName === "emoji" && typeof BiliIndex !== "undefined") {
          const { superHateMids } = await BiliIndex.storageGet(["superHateMids"]);
          const mids = Array.isArray(superHateMids) ? superHateMids.map(String) : [];
          if (mids.includes(String(bar.dataset.upMid || ""))) {
            document.dispatchEvent(new CustomEvent("biliindex-super-hate-changed"));
          }
        }
        console.info("[BiliIndex] 打标成功", slotName, bar.dataset.upMid);
      } else {
        await refreshBar(bar);
        showBarToast(bar, res?.error || "打标失败", true);
      }
    });
    if (opts.title) btn.title = opts.title;
  }

  function positionMenu(menu, slotEl) {
    document.body.appendChild(menu);
    const rect = slotEl.getBoundingClientRect();
    menu.style.left = `${Math.max(8, rect.left)}px`;
    menu.style.top = `${rect.bottom + 4}px`;
    const menuRect = menu.getBoundingClientRect();
    if (menuRect.right > window.innerWidth - 8) {
      menu.style.left = `${Math.max(8, window.innerWidth - menuRect.width - 8)}px`;
    }
  }

  function showCategoryTabMenu(bar, slotEl, tags, opts = {}) {
    removeAllMenus();
    const passerby = opts.passerby || null;
    const superHate = opts.super_hate || null;
    const categories = tags.filter(
      (t) => t.code !== PASSERBY_CODE && t.code !== SUPER_HATE_CODE
    );
    const frequent = Array.isArray(opts.frequent) ? opts.frequent : [];
    let scrollVisible = CATEGORY_TAB_VISIBLE;
    if (passerby) scrollVisible -= 1;
    if (superHate) scrollVisible -= 1;
    scrollVisible = Math.max(1, scrollVisible);

    const menu = document.createElement("div");
    menu.className = `${MENU_CLASS} is-category-tabs`;
    menu.style.cssText =
      "position:fixed;z-index:2147483647;background:#fff;border:1px solid #e3e5e7;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.18);display:flex;flex-direction:column;font-size:13px;";

    const rowMain = document.createElement("div");
    rowMain.className = "biliindex-tag-tab-row-main";
    menu.appendChild(rowMain);

    if (passerby) {
      const pbBtn = document.createElement("button");
      pbBtn.type = "button";
      pbBtn.className = "is-passerby";
      const pbLabel = tagLabel(passerby);
      pbBtn.textContent = pbLabel.length > 6 ? "路人" : pbLabel;
      pbBtn.title = pbLabel;
      bindTagPickButton(bar, slotEl, pbBtn, passerby, { title: pbLabel });
      rowMain.appendChild(pbBtn);
    }

    if (superHate) {
      const shBtn = document.createElement("button");
      shBtn.type = "button";
      shBtn.className = "is-super-hate";
      const shLabel = tagLabel(superHate);
      shBtn.textContent = shLabel.length > 8 ? "超级讨厌" : shLabel;
      shBtn.title = shLabel;
      bindTagPickButton(bar, slotEl, shBtn, superHate, { title: shLabel });
      rowMain.appendChild(shBtn);
    }

    const viewport = document.createElement("div");
    viewport.className = "biliindex-tag-tab-viewport";
    rowMain.appendChild(viewport);

    const rowFreq = document.createElement("div");
    rowFreq.className = "biliindex-tag-tab-row-frequent";
    const freqLabel = document.createElement("div");
    freqLabel.className = "biliindex-tag-tab-row-label";
    freqLabel.textContent = "你常用的标签";
    rowFreq.appendChild(freqLabel);
    const freqRow = document.createElement("div");
    freqRow.className = "biliindex-tag-tab-frequent";
    rowFreq.appendChild(freqRow);
    menu.appendChild(rowFreq);

    for (let i = 0; i < FREQUENT_TAG_SLOTS; i += 1) {
      const tag = frequent[i];
      if (tag) {
        const btn = document.createElement("button");
        btn.type = "button";
        const label = tagLabel(tag);
        btn.textContent = label;
        const count = tag.use_count ?? tag.vote_count;
        if (count != null) btn.title = `${label}（已打标 ${count} 次）`;
        bindTagPickButton(bar, slotEl, btn, tag, { title: btn.title || label });
        freqRow.appendChild(btn);
      } else {
        const empty = document.createElement("span");
        empty.className = "freq-empty";
        empty.textContent = i === 0 && !frequent.length ? "暂无记录" : "—";
        freqRow.appendChild(empty);
      }
    }

    const foot = document.createElement("div");
    foot.className = "biliindex-tag-tab-foot";
    const hint = document.createElement("span");
    const pageEl = document.createElement("span");
    pageEl.className = "tab-page";
    foot.appendChild(hint);
    foot.appendChild(pageEl);
    if (categories.length > scrollVisible) menu.appendChild(foot);

    let start = 0;
    const maxStart = Math.max(0, categories.length - scrollVisible);

    const renderTabs = () => {
      viewport.replaceChildren();
      const slice = categories.slice(start, start + scrollVisible);
      slice.forEach((tag) => {
        const btn = document.createElement("button");
        btn.type = "button";
        const label = tagLabel(tag);
        btn.textContent = label;
        bindTagPickButton(bar, slotEl, btn, tag, { title: label });
        viewport.appendChild(btn);
      });
      for (let i = slice.length; i < scrollVisible; i += 1) {
        const pad = document.createElement("span");
        pad.className = "biliindex-tag-tab-pad";
        pad.setAttribute("aria-hidden", "true");
        pad.style.cssText = "flex:1 1 0;min-width:0;max-width:68px;";
        viewport.appendChild(pad);
      }
      hint.textContent = categories.length > scrollVisible ? "滚轮翻看" : "";
      if (categories.length > scrollVisible) {
        const end = Math.min(start + scrollVisible, categories.length);
        pageEl.textContent = `${start + 1}–${end} / ${categories.length}`;
      } else {
        pageEl.textContent = "";
      }
    };

    menu.tabIndex = -1;

    menu.addEventListener(
      "wheel",
      (e) => {
        if (categories.length <= scrollVisible) return;
        e.preventDefault();
        e.stopPropagation();
        const step =
          Math.abs(e.deltaY) >= Math.abs(e.deltaX)
            ? e.deltaY > 0
              ? 1
              : -1
            : e.deltaX > 0
              ? 1
              : -1;
        start = Math.min(maxStart, Math.max(0, start + step));
        renderTabs();
      },
      { passive: false }
    );

    renderTabs();
    positionMenu(menu, slotEl);
    menu.focus({ preventScroll: true });
  }

  function showEmojiCategoryMenu(bar, slotEl, categories, emojis, opts = {}) {
    removeAllMenus();
    const cats = (categories || [])
      .slice()
      .sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
    let activeCatId = cats[0]?.id ?? null;

    const menu = document.createElement("div");
    menu.className = `${MENU_CLASS} is-emoji-categories`;

    const panel = document.createElement("div");
    panel.className = "biliindex-emoji-panel";

    const grid = document.createElement("div");
    grid.className = "biliindex-emoji-grid";

    const catRow = document.createElement("div");
    catRow.className = "biliindex-emoji-cat-row";

    const renderGrid = () => {
      grid.replaceChildren();
      const list = (emojis || []).filter((t) => {
        if (activeCatId == null || activeCatId === "") return true;
        return Number(t.parent_id) === Number(activeCatId);
      });
      if (!list.length) {
        const empty = document.createElement("div");
        empty.className = "biliindex-emoji-grid-empty";
        empty.textContent = opts.hint || "该分类暂无表情";
        grid.appendChild(empty);
        return;
      }
      list.forEach((tag) => {
        const btn = document.createElement("button");
        btn.type = "button";
        btn.className = "biliindex-emoji-cell";
        const name = tagDisplayName(tag);
        btn.textContent = tagEmojiIcon(tag);
        const voteLabel = opts.showCounts ? formatVoteCountForTag(tag) : "";
        btn.title = voteLabel ? `${name}（${voteLabel} 人打标）` : name;
        bindTagPickButton(bar, slotEl, btn, tag);
        grid.appendChild(btn);
      });
    };

    const renderCats = () => {
      catRow.replaceChildren();
      cats.forEach((cat) => {
        const btn = document.createElement("button");
        btn.type = "button";
        btn.className = Number(cat.id) === Number(activeCatId) ? "is-active" : "";
        btn.textContent = cat.name || cat.code || "分类";
        btn.addEventListener("click", (e) => {
          e.preventDefault();
          e.stopPropagation();
          activeCatId = cat.id;
          renderCats();
          renderGrid();
        });
        catRow.appendChild(btn);
      });
    };

    if (!cats.length) {
      catRow.hidden = true;
      activeCatId = null;
      renderGrid();
    } else {
      renderCats();
      renderGrid();
    }

    panel.appendChild(grid);
    menu.append(panel, catRow);
    menu.tabIndex = -1;
    positionMenu(menu, slotEl);
    menu.focus({ preventScroll: true });
  }

  function showMenu(bar, slotEl, tags, opts = {}) {
    if (opts.slot === "emoji") {
      showEmojiCategoryMenu(bar, slotEl, opts.categories || [], tags, opts);
      return;
    }
    const hasTag1Extras =
      opts.passerby ||
      opts.super_hate ||
      (Array.isArray(opts.frequent) && opts.frequent.length > 0);
    if (opts.slot === "tag1" && (tags.length || hasTag1Extras)) {
      showCategoryTabMenu(bar, slotEl, tags, opts);
      return;
    }
    removeAllMenus();
    const menu = document.createElement("div");
    menu.className = MENU_CLASS;
    menu.style.cssText =
      "position:fixed;z-index:2147483647;min-width:120px;padding:4px;background:#fff;border:1px solid #e3e5e7;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.18);display:flex;flex-direction:column;gap:2px;font-size:13px;";
    if (!tags.length) {
      const empty = document.createElement("div");
      empty.style.cssText = "padding:8px;font-size:12px;color:#9499a0;max-width:220px;line-height:1.4;";
      empty.textContent =
        opts.hint ||
        opts.loginRequired ||
        (apiOk ? "暂无标签" : "无法连接 API，请检查扩展联网设置或启动本地服务");
      menu.appendChild(empty);
    } else {
      const isEmojiMenu = opts.slot === "emoji";
      tags.forEach((tag) => {
        const btn = document.createElement("button");
        btn.type = "button";
        if (isEmojiMenu) {
          const name = tagDisplayName(tag);
          btn.textContent = tagEmojiIcon(tag);
          const voteLabel =
            opts.showCounts ? formatVoteCountForTag(tag) : "";
          btn.title =
            voteLabel ? `${name}（${voteLabel} 人打标）` : name;
          btn.style.cssText =
            "padding:6px 10px;border:none;background:transparent;border-radius:4px;cursor:pointer;text-align:center;font-size:18px;line-height:1.1;";
        } else {
          const voteText = opts.showCounts ? formatVoteCountForTag(tag) : "";
          btn.textContent = voteText
            ? `${tagLabel(tag)} (${voteText})`
            : tagLabel(tag);
          btn.style.cssText =
            "padding:6px 8px;border:none;background:transparent;border-radius:4px;cursor:pointer;text-align:left;font-size:13px;";
        }
        bindTagPickButton(bar, slotEl, btn, tag);
        menu.appendChild(btn);
      });
    }
    positionMenu(menu, slotEl);
  }

  function applyTagToSlot(bar, slotName, tag) {
    const slotEl = bar.querySelector(`.${SLOT_CLASS}[data-slot="${slotName}"]`);
    if (slotEl && tag) renderSlotContent(slotEl, tag);
  }

  async function toggleSlotMenu(bar, slotEl) {
    hideBiliProfilePopovers();
    if (BiliIndex.isExtensionContextInvalidated?.()) {
      BiliIndex.markExtensionContextInvalidated?.();
      showBarToast(bar, "扩展已重载，请按 F5 刷新本页", true);
      return;
    }
    if (bar.dataset.locked === "1") {
      showBarToast(bar, "该 UP 标签已锁定为鳄鱼，无法修改", true);
      return;
    }
    if (bar.dataset.passerby === "1" && (slotEl.dataset.slot === "tag2" || slotEl.dataset.slot === "tag3")) {
      return;
    }
    showMenuLoading(bar, slotEl);
    menuCloseSuppressUntil = Date.now() + 800;
    let options;
    let token = null;
    try {
      token = await getAuthToken();
      options = await fetchSlotOptions(bar.dataset.upMid, slotEl.dataset.slot);
    } catch (e) {
      const msg =
        typeof BiliIndex !== "undefined" && BiliIndex.humanizeApiError
          ? BiliIndex.humanizeApiError(e)
          : e?.message || "加载标签列表失败";
      if (BiliIndex.isContextInvalidatedError?.(e)) {
        BiliIndex.markExtensionContextInvalidated?.();
      }
      showBarToast(bar, msg, true);
      options = {
        items: [],
        categories: [],
        hint: msg,
        loadError: msg,
        parent: null,
        passerby: null,
        super_hate: null,
        frequent: [],
      };
    }
    const { items, hint, parent, passerby, super_hate, frequent, categories } = options;
    let emojiCategories = categories || [];
    if (slotEl.dataset.slot === "emoji") {
      emojiCategories = await resolveEmojiPickerCategories(emojiCategories, items || []);
    }
    const hasExtras =
      passerby ||
      super_hate ||
      (Array.isArray(frequent) && frequent.length > 0);
    const loginRequired =
      !token && !(items || []).length && !hasExtras && !options.loadError
        ? "请先点击扩展图标登录后再打标"
        : null;
    const menuHint =
      options.loadError || hint || loginRequired || null;
    if (loginRequired) {
      showBarToast(bar, loginRequired, true);
    } else if (options.loadError) {
      showBarToast(bar, options.loadError, true);
    }
    menuCloseSuppressUntil = Date.now() + 800;
    showMenu(bar, slotEl, items || [], {
      slot: slotEl.dataset.slot,
      showCounts: slotEl.dataset.slot === "emoji",
      hint: menuHint,
      loginRequired,
      parent,
      passerby,
      super_hate,
      frequent,
      categories: emojiCategories,
    });
  }

  function bindSlotActivation(bar, slotEl) {
    if (slotEl.dataset.biliindexSlotBound === "1") return;
    slotEl.dataset.biliindexSlotBound = "1";
    shieldSiteProfileHover(slotEl);
    const onActivate = (e) => {
      if (e.button !== 0) return;
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
      requestSlotMenu(bar, slotEl);
    };
    for (const type of ["pointerdown", "mousedown"]) {
      slotEl.addEventListener(type, onActivate, true);
    }
    slotEl.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
      requestSlotMenu(bar, slotEl);
    });
  }

  function bindBar(bar) {
    const isNew = !mountedBars.has(bar);
    if (isNew) mountedBars.add(bar);
    document.documentElement?.setAttribute("data-biliindex-slot-pointer", "1");
    const wrap = bar.closest(`.${WRAP_CLASS}`);
    if (wrap instanceof HTMLElement) {
      relocateBarForPlacement(wrap);
      if (wrap.dataset.biliindexWrapEvents !== "1") {
        wrap.dataset.biliindexWrapEvents = "1";
        shieldSiteProfileHover(wrap);
        for (const evt of ["mousedown", "click", "mouseenter", "mouseover"]) {
          wrap.addEventListener(evt, (e) => e.stopPropagation());
        }
      }
    }
    shieldSiteProfileHover(bar);
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((slotEl) => {
      bindSlotActivation(bar, slotEl);
    });
    if (!isNew) return;
    void refreshBar(bar);
    if (bar.dataset.placement === "comment") observeCommentBar(bar);
  }

  function getCommentItemRoot(anchor) {
    return (
      anchor.closest(
        "bili-comment-renderer, bili-comment-reply-renderer, .reply-item, .list-item"
      ) || anchor
    );
  }

  function getCardItemRoot(anchor) {
    return (
      anchor.closest(".video-page-card-small, .bili-video-card, .rec-list-item") ||
      anchor
    );
  }

  function getUpPanelItemRoot(anchor) {
    return anchor.closest(".up-panel-container, .up-info-container, #v_upinfo") || anchor;
  }

  /** @param {"comment"|"card"|"up"|"space"} placement */
  function mountBar(anchor, info, layout, placement) {
    if (!(anchor instanceof HTMLElement) || !info?.upMid) return;
    if (inForbiddenZone(anchor)) return;

    if (layout === "full" && placement !== "comment" && placement !== "space") return;
    if (layout === "emoji-only" && placement !== "card" && placement !== "up") return;

    const itemRoot =
      placement === "comment"
        ? getCommentItemRoot(anchor)
        : placement === "space"
          ? anchor.closest(
              ".h-name, .n-name, #h-name, .space-info, .h-user, .header-info, .profile-info"
            ) || anchor
          : placement === "up"
            ? getUpPanelItemRoot(anchor)
            : getCardItemRoot(anchor);

    dedupeExtraWraps(itemRoot);
    if (hasWrapAdjacent(anchor, placement) || hasBarForUp(itemRoot, info.upMid)) return;

    const bar = createBar(info.upMid, layout, placement);
    bar.dataset.nickname = info.nickname || "";
    const fanCount = resolveFanCountForBar(bar, anchor, itemRoot);
    if (fanCount != null) bar.dataset.fanCount = String(fanCount);
    const wrap = createBarWrap(bar);
    const mount = getBarInsertRef(anchor, placement);
    injectStylesForElement(mount);
    mount.insertAdjacentElement("afterend", wrap);
    relocateBarForPlacement(wrap);
    if (placement === "space") {
      const nameRow =
        anchor.closest?.(
          ".h-name, .n-name, #h-name, .space-info, .h-user, .header-info, .nickname"
        ) || anchor.parentElement;
      if (nameRow instanceof HTMLElement) {
        nameRow.style.display = "inline-flex";
        nameRow.style.alignItems = "center";
        nameRow.style.flexWrap = "wrap";
        nameRow.style.gap = "6px";
        nameRow.style.overflow = "visible";
      }
    }
    bindBar(bar);
    lastMountCount += 1;
    document.dispatchEvent(
      new CustomEvent("biliindex:up-target", {
        detail: { upMid: info.upMid, nickname: info.nickname },
      })
    );
  }

  function processCommentItem(item) {
    if (!(item instanceof HTMLElement)) return;
    if (item.closest?.(COMMENT_COMPOSER_SELECTOR)) return;
    if (commentItemRun.get(item) === scanRunId) return;
    commentItemRun.set(item, scanRunId);

    dedupeExtraWraps(item);

    if (item.getAttribute(ITEM_DONE) === "1") {
      if (queryAllDeep(item, `.${WRAP_CLASS}`).length > 0) return;
      item.removeAttribute(ITEM_DONE);
    }

    const link = findCommentNicknameLink(item);
    if (isDeactivatedCommentItem(item, link)) {
      removeBarsFromRoot(item);
      return;
    }
    if (isPlaceholderCommentItem(item, link)) {
      removeBarsFromRoot(item);
      item.setAttribute(ITEM_DONE, "1");
      return;
    }
    const upMid = String(
      (link && extractMid(link.getAttribute("href") || link.href)) ||
        BiliDom.midFromCommentElement(item) ||
        BiliDom.resolveCommentUpMid(item, queryAllDeep) ||
        ""
    );
    if (!/^\d+$/.test(upMid)) return;
    if (hasBarForUp(item, upMid) || (link && hasWrapAdjacent(link, "comment"))) {
      item.setAttribute(ITEM_DONE, "1");
      return;
    }
    const nickname = (link?.textContent || "").trim().slice(0, 64);
    if (link) {
      mountBar(link, { upMid, nickname }, "full", "comment");
      link.setAttribute(BAR_DONE, "1");
      item.setAttribute(ITEM_DONE, "1");
      return;
    }
    const host = findCommentNicknameHost(item);
    if (host) {
      mountBar(host, { upMid, nickname }, "full", "comment");
      item.setAttribute(ITEM_DONE, "1");
    }
  }

  function findSpaceProfileAnchor() {
    if (!isSpaceHostPage()) return null;
    const upMid = BiliDom.midFromSpacePageUrl();
    if (!upMid) return null;

    if (
      document.querySelector(
        `.${BAR_CLASS}[data-placement="space"][data-up-mid="${CSS.escape(upMid)}"]`
      )
    ) {
      return null;
    }

    const pick = (el) => {
      if (!(el instanceof HTMLElement)) return null;
      const nickname = (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 64);
      if (!nickname || nickname.length < 1) return null;
      if (DEACTIVATED_RE.test(nickname)) return null;
      if (!isSpaceProfileZone(el)) return null;
      return { anchor: el, upMid: String(upMid), nickname };
    };

    for (const sel of SPACE_PROFILE_NICKNAME_SELECTORS) {
      const found = queryAllDeepSpace(sel);
      const plain = found.length ? found : [...document.querySelectorAll(sel)];
      for (const el of plain) {
        const hit = pick(el);
        if (hit) return hit;
      }
    }

    for (const sel of SPACE_NICKNAME_LINK_SELECTORS) {
      const link = document.querySelector(sel);
      if (!(link instanceof HTMLAnchorElement)) continue;
      if (!isSpaceProfileZone(link)) continue;
      const mid = extractMid(link.getAttribute("href") || link.href) || upMid;
      const nickname = (link.textContent || "").trim().slice(0, 64);
      if (nickname && !DEACTIVATED_RE.test(nickname)) {
        return { anchor: link, upMid: String(mid), nickname };
      }
    }

    const fallbackLink = document.querySelector(
      `a[href*="space.bilibili.com/${upMid}"], a[href$="/${upMid}"]`
    );
    if (fallbackLink instanceof HTMLAnchorElement && isSpaceProfileZone(fallbackLink)) {
      const nickname = (fallbackLink.textContent || "").trim().slice(0, 64);
      if (nickname) return { anchor: fallbackLink, upMid: String(upMid), nickname };
    }

    return null;
  }

  function processSpaceProfilePage() {
    const hit = findSpaceProfileAnchor();
    if (!hit) return;
    const { anchor, upMid, nickname } = hit;
    const host =
      anchor.closest(
        ".h-name, .n-name, #h-name, .h-user, .space-info, .header-info, .profile-info"
      ) || anchor.parentElement;
    if (host instanceof HTMLElement) {
      dedupeExtraWraps(host);
      if (host.querySelector(`.${WRAP_CLASS}`)) return;
    }
    mountBar(anchor, { upMid, nickname }, "full", "space");
  }

  function runSpaceProfileScan() {
    if (!isSpaceHostPage()) return;
    try {
      const upMid = BiliDom.midFromSpacePageUrl();
      const hadBar =
        upMid &&
        document.querySelector(
          `.${BAR_CLASS}[data-placement="space"][data-up-mid="${CSS.escape(upMid)}"]`
        );
      processSpaceProfilePage();
      cleanupForbiddenBars();
      if (
        upMid &&
        !hadBar &&
        document.querySelector(
          `.${BAR_CLASS}[data-placement="space"][data-up-mid="${CSS.escape(upMid)}"]`
        )
      ) {
        console.info("[BiliIndex] 空间头图标签栏已挂载", upMid);
      }
    } catch (e) {
      console.warn("[BiliIndex] 空间头图标签栏失败", e);
    }
  }

  function setupSpaceProfileObserver() {
    if (setupSpaceProfileObserver.done || !isSpaceHostPage()) return;
    setupSpaceProfileObserver.done = true;
    const obs = trackObserver(new MutationObserver(() => runSpaceProfileScan()));
    if (document.body) obs.observe(document.body, { childList: true, subtree: true });
  }

  function collectCommentRenderersFromNodes(nodes) {
    const items = [];
    const seen = new Set();
    const add = (el) => {
      if (el instanceof HTMLElement && !seen.has(el)) {
        seen.add(el);
        items.push(el);
      }
    };
    const scanRoot = (root) => {
      if (!(root instanceof Element)) return;
      try {
        if (root.matches?.("bili-comment-renderer, bili-comment-reply-renderer")) add(root);
        root
          .querySelectorAll?.("bili-comment-renderer, bili-comment-reply-renderer")
          .forEach(add);
      } catch {
        /* ignore */
      }
      if (isCommentShadowHost(root)) {
        const sr = getShadowRoot(root);
        if (sr) scanRoot(sr);
      }
    };
    for (const node of nodes || []) scanRoot(node);
    return items;
  }

  function listCommentRenderers() {
    const sel = "bili-comment-renderer, bili-comment-reply-renderer";
    if (BiliDom.queryCommentDeep) {
      const fromDeep = BiliDom.queryCommentDeep(sel);
      if (fromDeep.length) return fromDeep;
    }
    if (typeof BiliIndex !== "undefined" && BiliIndex.queryInCommentHosts) {
      const fromHosts = BiliIndex.queryInCommentHosts(sel);
      if (fromHosts.length) return fromHosts;
    }
    const merged = [];
    const seen = new Set();
    for (const hostSel of COMMENT_ROOT_SELECTORS) {
      document.querySelectorAll(hostSel).forEach((host) => {
        queryAllDeep(host, sel).forEach((el) => {
          if (!seen.has(el)) {
            seen.add(el);
            merged.push(el);
          }
        });
      });
    }
    if (merged.length) return merged;
    if (typeof BiliDom.queryCommentDeep === "function") {
      return BiliDom.queryCommentDeep(".reply-item, .list-item");
    }
    return queryAllDeep(document.body, ".reply-item, .list-item");
  }

  function processComments(root) {
    const sel = "bili-comment-renderer, bili-comment-reply-renderer";
    let list = [];
    if (root instanceof Element || root instanceof DocumentFragment) {
      list = queryAllDeep(root, sel);
    }
    if (!list.length) list = listCommentRenderers();
    const viewportItems = [];
    for (const item of list) {
      if (isCommentItemNearViewport(item)) viewportItems.push(item);
      else queueOffscreenCommentMount(item);
    }
    for (const item of viewportItems) processCommentItem(item);
  }

  function processCommentsIncremental(addedNodes) {
    if (!addedNodes?.length) return;
    const items = collectCommentRenderersFromNodes(addedNodes);
    if (!items.length) return;
    for (const item of items) {
      if (isCommentItemNearViewport(item)) processCommentItem(item);
      else queueOffscreenCommentMount(item);
    }
  }

  const RECO_UPNAME_LINK_SELECTORS = [
    ".upname a[href*='space.bilibili.com']",
    ".r-upinfo .upname a[href*='space.bilibili.com']",
    ".upinfo .upname a[href*='space.bilibili.com']",
    ".bili-video-card__info--author[href*='space.bilibili.com']",
    "a.up-name[href*='space.bilibili.com']",
  ];

  function findRecoAuthorLink(card) {
    if (!(card instanceof Element)) return null;
    for (const sel of RECO_UPNAME_LINK_SELECTORS) {
      try {
        const a = card.querySelector(sel);
        if (!(a instanceof HTMLAnchorElement)) continue;
        const mid = extractMid(a.getAttribute("href") || a.href);
        if (mid && !(a.querySelector("img") && !(a.textContent || "").trim())) {
          return a;
        }
        if (mid) return a;
      } catch {
        /* ignore */
      }
    }
    return BiliDom.findAuthorLink(card);
  }

  function isMaskOnlyVideoZone(el) {
    if (!(el instanceof Element)) return false;
    for (const sel of FEED_VIDEO_MASK_ONLY_SELECTORS) {
      try {
        if (el.closest?.(sel)) return true;
      } catch {
        /* ignore */
      }
    }
    return false;
  }

  function cleanupMaskOnlyZoneBars() {
    try {
      for (const sel of FEED_VIDEO_MASK_ONLY_SELECTORS) {
        document.querySelectorAll(`${sel} .${WRAP_CLASS}`).forEach((wrap) => {
          if (isOurWrap(wrap)) wrap.remove();
        });
      }
    } catch {
      /* ignore */
    }
  }

  function collectRecoCards(root) {
    const cards = [];
    const seen = new Set();
    const add = (el) => {
      if (el instanceof HTMLElement && !seen.has(el)) {
        seen.add(el);
        cards.push(el);
      }
    };
    if (root instanceof HTMLElement) {
      if (root.matches(".video-page-card-small, .rec-list-item, .rcmd-item, .bili-video-card")) {
        add(root);
      }
      root
        .querySelectorAll(
          ".video-page-card-small, .rec-list-item, .rcmd-item, .bili-video-card"
        )
        .forEach(add);
    }
    return cards;
  }

  /** 推荐区：每张卡片只挂一个 Emoji 槽，不用 queryAllDeep（避免拖垮封面加载） */
  function processRecoCards(root) {
    if (!root || recoHostRun.get(root) === scanRunId) return;
    recoHostRun.set(root, scanRunId);

    for (const card of collectRecoCards(root)) {
      if (isMaskOnlyVideoZone(card)) continue;
      dedupeExtraWraps(card);
      if (card.querySelector(`.${WRAP_CLASS}`)) {
        card.setAttribute(CARD_DONE, "1");
        continue;
      }
      if (card.getAttribute(CARD_DONE) === "1" && !card.querySelector(`.${WRAP_CLASS}`)) {
        card.removeAttribute(CARD_DONE);
      }
      const link = findRecoAuthorLink(card);
      if (!link || inForbiddenZone(link)) continue;
      const mid = extractMid(link.getAttribute("href") || link.href);
      if (!mid) continue;
      const upMid = String(mid);
      if (hasWrapAdjacent(link, "card") || card.querySelector(`.${BAR_CLASS}[data-up-mid="${CSS.escape(upMid)}"]`)) {
        card.setAttribute(CARD_DONE, "1");
        continue;
      }
      mountBar(
        link,
        { upMid, nickname: (link.textContent || "").trim().slice(0, 64) },
        "emoji-only",
        "card"
      );
      card.setAttribute(CARD_DONE, "1");
    }
  }

  function getVideoUpPanelRoot() {
    const direct =
      document.querySelector("#v_upinfo") ||
      document.querySelector(".up-panel-container") ||
      document.querySelector(".up-info-container") ||
      document.querySelector(".up-info--wrapper") ||
      document.querySelector(".video-info-detail");
    if (direct) return direct;
    const upName = document.querySelector("a.up-name[href*='space.bilibili.com']");
    if (upName instanceof HTMLAnchorElement) {
      return (
        upName.closest(".up-detail-top, .up-panel-container, .up-info--wrapper") ||
        upName.parentElement
      );
    }
    return null;
  }

  function dedupeUpPanelBarsByMid(panel) {
    if (!(panel instanceof HTMLElement)) return;
    const seen = new Set();
    for (const bar of queryAllDeep(panel, `.${BAR_CLASS}`)) {
      if (!(bar instanceof HTMLElement)) continue;
      const mid = bar.dataset.upMid;
      if (!mid) continue;
      if (seen.has(mid)) {
        bar.closest(`.${WRAP_CLASS}`)?.remove();
        continue;
      }
      seen.add(mid);
    }
  }

  function processUpPanel(root) {
    if (!(root instanceof Element)) return;
    const panel =
      root.closest?.(".up-panel-container, .up-info-container, #v_upinfo") ||
      root.querySelector?.(".up-panel-container, .up-info-container, #v_upinfo") ||
      root;
    dedupeExtraWraps(panel);
    dedupeUpPanelBarsByMid(panel);
    let link = null;
    let mid = null;
    for (const sel of BiliDom.VIDEO_UP_DOM_SELECTORS) {
      const candidate = panel.querySelector?.(sel);
      if (!(candidate instanceof HTMLAnchorElement)) continue;
      const parsed = extractMid(candidate.getAttribute("href") || candidate.href);
      if (parsed) {
        link = candidate;
        mid = parsed;
        break;
      }
    }
    if (!(link instanceof HTMLAnchorElement) || inForbiddenZone(link) || !mid) return;
    if (!mid) return;
    if (hasWrapAdjacent(link, "up") || hasBarForUp(panel, String(mid))) return;
    mountBar(
      link,
      { upMid: String(mid), nickname: (link.textContent || "").trim().slice(0, 64) },
      "emoji-only",
      "up"
    );
  }

  let commentPhaseForceAfter = 0;

  function runCommentPhase() {
    const rendererCount =
      typeof BiliIndex !== "undefined" && BiliIndex.queryInCommentHosts
        ? BiliIndex.queryInCommentHosts("bili-comment-renderer").length
        : countCommentRenderers();
    const loading =
      rendererCount === 0 &&
      typeof BiliIndex !== "undefined" &&
      BiliIndex.isCommentsSectionLoading?.();
    if (loading) {
      if (!commentPhaseForceAfter) {
        commentPhaseForceAfter = Date.now() + 12000;
      }
      if (Date.now() < commentPhaseForceAfter) {
        return;
      }
    } else {
      commentPhaseForceAfter = 0;
    }
    attachShadowObservers();
    for (const shadow of collectCommentShadowRoots()) {
      injectStylesIntoShadow(shadow);
    }
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app", "#comment-module", "#comment"]) {
      document.querySelectorAll(sel).forEach((host) => {
        const shadow = getShadowRoot(host);
        if (shadow) injectStylesIntoShadow(shadow);
        processComments(host);
      });
    }
    runSpaceProfileScan();
    cleanupProfilePopoverBars();
  }

  function runRecoPhase() {
    if (!recoMediaReady()) {
      scheduleRecoScan();
      return;
    }
    cleanupMaskOnlyZoneBars();
    for (const root of collectRoots(VIDEO_PAGE_RECO_SELECTORS)) {
      processRecoCards(root);
    }
    nudgeRecoLazyImages();
    const upRoot = getVideoUpPanelRoot();
    if (upRoot) processUpPanel(upRoot);
    dedupeAllWrapsGlobally();
    relocateAllBarsOutsideLinks();
    forEachBar((bar) => bindBar(bar));
  }

  function runCommentScan() {
    try {
      resetCommentStateIfVideoChanged();
      scanRunId += 1;
      lastMountCount = 0;
      runCommentPhase();
      relocateAllBarsOutsideLinks();
      forEachBar((bar) => bindBar(bar));
      dedupeAllWrapsGlobally();
      cleanupForbiddenBars();
      cleanupMaskOnlyZoneBars();
      const commentNodes = listCommentRenderers().length;
      const hosts = (BiliDom.getCommentHosts?.() || []).length;
      const stillLoading =
        typeof BiliIndex !== "undefined" && BiliIndex.isCommentsSectionLoading?.();
      if (lastMountCount > 0) {
        console.info(`[BiliIndex] 评论标签栏已挂载（${lastMountCount} 处）`);
      } else if (!stillLoading && commentNodes === 0 && hosts > 0) {
        console.warn(
          `[BiliIndex] 评论区暂无评论节点（宿主 ${hosts} 个），若已加载评论请稍候或刷新页面`
        );
      }
      if (lastMountCount > 0 || pendingDisplayMids.size > 0) {
        void flushPendingDisplays();
      }
      document.dispatchEvent(new CustomEvent("biliindex:comments-scanned"));
    } catch (e) {
      console.warn("[BiliIndex] 评论扫描失败", e);
    }
  }

  function runRecoScan() {
    try {
      runRecoPhase();
      cleanupForbiddenBars();
    } catch (e) {
      console.warn("[BiliIndex] 推荐区扫描失败", e);
    }
  }

  let commentDebounce = null;
  let recoDebounce = null;
  let commentScanDeferCount = 0;
  const MAX_COMMENT_SCAN_DEFER = 36;

  function scheduleCommentScan(incrementalNodes) {
    resetCommentStateIfVideoChanged();
    if (Array.isArray(incrementalNodes) && incrementalNodes.length) {
      pendingIncrementalNodes.push(...incrementalNodes);
    }
    commentScanPending = true;
    clearTimeout(commentDebounce);
    const hasIncremental = pendingIncrementalNodes.length > 0;
    commentDebounce = setTimeout(() => {
      if (
        !hasIncremental &&
        typeof BiliIndex !== "undefined" &&
        BiliIndex.isCommentsSectionLoading?.()
      ) {
        commentScanDeferCount += 1;
        if (commentScanDeferCount < MAX_COMMENT_SCAN_DEFER) {
          clearTimeout(commentDebounce);
          commentDebounce = setTimeout(() => scheduleCommentScan(), 400);
          return;
        }
      }
      commentScanDeferCount = 0;
      const now = Date.now();
      const minGap = hasIncremental ? 20 : 180;
      const wait = minGap - (now - lastCommentRunAt);
      if (wait > 0) {
        clearTimeout(commentDebounce);
        commentDebounce = setTimeout(() => scheduleCommentScan(), wait);
        return;
      }
      lastCommentRunAt = now;
      commentScanPending = false;
      const incr = pendingIncrementalNodes.splice(0, pendingIncrementalNodes.length);
      const work = () => {
        if (incr.length) {
          processCommentsIncremental(incr);
          if (pendingDisplayMids.size > 0 || displayFlushQueue.size > 0) {
            scheduleDisplayFlush(true);
          }
          if (shouldDeferCommentDisplayFetch()) {
            scheduleCommentDisplayWhenReady();
          }
          document.dispatchEvent(new CustomEvent("biliindex:comments-scanned"));
        } else {
          runCommentScan();
        }
      };
      if (hasIncremental) {
        work();
      } else if (typeof requestIdleCallback === "function") {
        requestIdleCallback(work, { timeout: 350 });
      } else {
        requestAnimationFrame(work);
      }
    }, hasIncremental ? 16 : 120);
  }

  function scheduleRecoScan() {
    recoScanPending = true;
    clearTimeout(recoDebounce);
    const baseDelay = isVideoWatchPage() ? 320 : 180;
    recoDebounce = setTimeout(() => {
      const now = Date.now();
      const minGap = isVideoWatchPage() ? 900 : 650;
      const wait = minGap - (now - lastRecoRunAt);
      if (wait > 0) {
        clearTimeout(recoDebounce);
        recoDebounce = setTimeout(() => scheduleRecoScan(), wait);
        return;
      }
      if (isVideoWatchPage() && !recoMediaReady()) {
        clearTimeout(recoDebounce);
        recoDebounce = setTimeout(() => scheduleRecoScan(), 280);
        return;
      }
      lastRecoRunAt = now;
      recoScanPending = false;
      const work = () => runRecoScan();
      if (typeof requestIdleCallback === "function") {
        requestIdleCallback(work, { timeout: 2000 });
      } else {
        requestAnimationFrame(work);
      }
    }, baseDelay);
  }

  function scheduleFullScan() {
    scheduleCommentScan();
    scheduleRecoScan();
    if (isSpaceHostPage()) runSpaceProfileScan();
  }

  async function probeApiOnce() {
    if (typeof BiliIndex === "undefined") return;
    try {
      await BiliIndex.ensureFixedApiConfig?.();
      let mode = await BiliIndex.getApiMode();
      let base = await BiliIndex.getApiBase();
      if (mode === "local") {
        try {
          await BiliIndex.probeApiHealth(BiliIndex.LOCAL_API_BASE, { retries: 0 });
        } catch {
          try {
            await BiliIndex.setApiMode("cloud");
            mode = "cloud";
            base = await BiliIndex.getApiBase();
          } catch {
            /* 保持 local */
          }
        }
      }
      await BiliIndex.probeApiHealth(base);
      apiOk = true;
      console.info(`[BiliIndex] API 已连接（${base}，${mode === "local" ? "本地" : "联网"}）`);
      refreshAllBars();
    } catch (e) {
      apiOk = false;
      const base = await BiliIndex.getApiBase?.().catch(() => "");
      const msg =
        typeof BiliIndex.humanizeApiError === "function"
          ? BiliIndex.humanizeApiError(e, base)
          : e?.message || e;
      console.warn("[BiliIndex] API 未连接", msg);
    }
  }

  function setupCommentVisibilityObserver() {
    if (setupCommentVisibilityObserver.done) return;
    const target = document.querySelector("bili-comments, #comment-app, #comment-module");
    if (!target) return;
    setupCommentVisibilityObserver.done = true;
    try {
      new IntersectionObserver(
        (entries) => {
          if (entries.some((e) => e.isIntersecting)) scheduleCommentScan();
        },
        { rootMargin: "240px 0px", threshold: 0.01 }
      ).observe(target);
    } catch {
      /* ignore */
    }
  }

  function setupRecoVisibilityObserver() {
    if (setupRecoVisibilityObserver.done) return;
    const target = document.querySelector("#reco_list, .rcmd-list, .recommend-list-v1");
    if (!target) return;
    setupRecoVisibilityObserver.done = true;
    try {
      new IntersectionObserver(
        (entries) => {
          if (entries.some((e) => e.isIntersecting)) scheduleRecoScan();
        },
        { rootMargin: "120px 0px", threshold: 0.01 }
      ).observe(target);
    } catch {
      /* ignore */
    }
  }

  void probeApiOnce();
  const scheduleInitialRecoScan = () => scheduleRecoScan();
  const bootReco = () => {
    if (typeof requestIdleCallback === "function") {
      requestIdleCallback(scheduleInitialRecoScan, { timeout: 2200 });
    } else {
      setTimeout(scheduleInitialRecoScan, 700);
    }
  };
  if (document.readyState === "complete") bootReco();
  else window.addEventListener("load", bootReco, { once: true });
  setupRecoVisibilityObserver();
  if (typeof BiliIndex !== "undefined" && BiliIndex.whenCommentsReady) {
    BiliIndex.whenCommentsReady(() => scheduleCommentScan());
  } else {
    scheduleCommentScan();
  }
  setupSpaceProfileObserver();

  const observedRoots = new WeakSet();

  function observeCommentRoot(root) {
    if (!root || observedRoots.has(root)) return;
    observedRoots.add(root);
    trackObserver(
      new MutationObserver((mutations) => {
        const added = collectAddedElements(mutations);
        if (added.length) scheduleCommentScan(added);
        else if (mutationHasRelevantAdds(mutations)) scheduleCommentScan();
      })
    ).observe(root, { childList: true, subtree: true });
  }

  function attachRootObservers() {
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      document.querySelectorAll(sel).forEach((el) => {
        observeCommentRoot(el);
        setupCommentVisibilityObserver();
      });
    }
    const body = document.body;
    if (body && !observedRoots.has(body)) {
      observedRoots.add(body);
      trackObserver(
        new MutationObserver(() => {
          attachRootObservers();
          setupCommentVisibilityObserver();
          setupRecoVisibilityObserver();
          if (document.querySelector("bili-comments, #comment-app")) {
            scheduleCommentScan();
          }
          if (document.querySelector("#reco_list, .rcmd-list")) {
            scheduleRecoScan();
          }
          if (isSpaceHostPage()) {
            runSpaceProfileScan();
          }
        })
      ).observe(body, { childList: true, subtree: false });
    }
  }

  attachRootObservers();
  let hostFinderTicks = 0;
  let hostFinder = null;
  let recoFinder = null;
  let pollTimer = null;
  hostFinder = setInterval(() => {
    attachRootObservers();
    setupCommentVisibilityObserver();
    setupRecoVisibilityObserver();
    if (document.querySelector("bili-comments, #comment-app")) {
      scheduleCommentScan();
    }
    if (document.querySelector("#reco_list, .rcmd-list, .recommend-list-v1")) {
      scheduleRecoScan();
    }
    hostFinderTicks += 1;
    if (
      document.querySelector("bili-comments, #comment-app") ||
      hostFinderTicks >= 24
    ) {
      clearInterval(hostFinder);
      scheduleCommentScan();
    }
  }, 800);

  let recoFinderTicks = 0;
  let recoFoundPasses = 0;
  recoFinder = setInterval(() => {
    setupRecoVisibilityObserver();
    recoFinderTicks += 1;
    if (document.querySelector("#reco_list, .rcmd-list, .recommend-list-v1")) {
      scheduleRecoScan();
      recoFoundPasses += 1;
    }
    if (recoFoundPasses >= 4 || recoFinderTicks >= 40) {
      clearInterval(recoFinder);
    }
  }, 800);

  window.addEventListener("load", () => {
    scheduleRecoScan();
    setupRecoVisibilityObserver();
    if (typeof BiliIndex !== "undefined" && BiliIndex.whenCommentsReady) {
      BiliIndex.whenCommentsReady(() => scheduleCommentScan());
    } else {
      scheduleCommentScan();
    }
    setupCommentVisibilityObserver();
    setupSpaceProfileObserver();
    if (isSpaceHostPage()) runSpaceProfileScan();
  });

  let pollTicks = 0;
  pollTimer = setInterval(() => {
    const loading =
      typeof BiliIndex !== "undefined" &&
      BiliIndex.isCommentsSectionLoading?.();
    if (!loading || pollTicks >= 12) {
      scheduleCommentScan();
    }
    if (pollTicks % 2 === 0) scheduleRecoScan();
    if (isSpaceHostPage()) runSpaceProfileScan();
    pollTicks += 1;
    const commentCount = loading ? 0 : countCommentRenderers();
    if ((commentCount > 0 && pollTicks >= 2) || pollTicks >= 20) {
      clearInterval(pollTimer);
      if (!loading) scheduleCommentScan();
      scheduleRecoScan();
      scheduleDisplayFlush(true);
    }
  }, 1200);

  window.addEventListener("biliindex:slot-pointerdown", (e) => {
    const { bar, slot } = e.detail || {};
    if (bar instanceof HTMLElement && slot instanceof HTMLElement) {
      requestSlotMenu(bar, slot);
    }
  });

  document.addEventListener(
    "click",
    (e) => {
      if (Date.now() < menuCloseSuppressUntil) return;
      const t = e.target;
      if (t instanceof Element && t.closest(`.${MENU_CLASS}, .${SLOT_CLASS}, .${WRAP_CLASS}`)) {
        return;
      }
      removeAllMenus();
    },
    true
  );

  forEachBar((bar) => bindBar(bar));

  document.addEventListener("biliindex:comments-scanned", () => {
    if (pendingDisplayMids.size > 0 || displayFlushQueue.size > 0) {
      if (shouldDeferCommentDisplayFetch()) scheduleCommentDisplayWhenReady();
      else scheduleDisplayFlush(true);
    }
  });

  document.addEventListener("biliindex-passerby-marked", (e) => {
    const { up_mid: upMid, display } = e.detail || {};
    if (!upMid) return;
    const mid = String(upMid);
    let payload = display ? normalizeDisplayRow(display) : null;
    if (payload) {
      cacheDisplay(mid, payload);
      if (payload.passerby && BiliIndex.rememberPasserbyMid) {
        void BiliIndex.rememberPasserbyMid(mid);
      }
    } else invalidateDisplayCache(mid);
    applyDisplayForMids([mid]);
    forEachBar((bar) => {
      if (bar.dataset.upMid !== mid) return;
      if (!payload) void refreshBar(bar);
    });
  });

  if (typeof BiliIndex !== "undefined" && BiliIndex.whenCommentsReady) {
    BiliIndex.whenCommentsReady(() => {
      scheduleCommentScan();
      scheduleDisplayFlush(true);
    });
  }

  let lastSpaPath = `${location.pathname}${location.search}`;
  setInterval(() => {
    const path = `${location.pathname}${location.search}`;
    if (path === lastSpaPath) return;
    lastSpaPath = path;
    commentPhaseForceAfter = 0;
    recoMediaGateUntil = 0;
    scanRunId += 1;
    scheduleCommentScan();
    scheduleRecoScan();
    scheduleDisplayFlush(true);
  }, 2000);

  let mountWatchTicks = 0;
  const mountWatch = setInterval(() => {
    mountWatchTicks += 1;
    const bars = document.querySelectorAll(`.${BAR_CLASS}[data-placement="comment"]`).length;
    const hosts = document.querySelector("bili-comments, #comment-app, #comment-module");
    if (hosts && bars === 0 && mountWatchTicks <= 40) {
      scanRunId += 1;
      commentPhaseForceAfter = 0;
      scheduleCommentScan();
    }
    if (bars > 0 || mountWatchTicks > 40 || !hosts) {
      clearInterval(mountWatch);
    }
  }, 3000);

  function collectAllBarsList() {
    const out = [];
    const seen = new WeakSet();
    const push = (bar) => {
      if (!(bar instanceof HTMLElement) || seen.has(bar)) return;
      seen.add(bar);
      out.push(bar);
    };
    document.querySelectorAll(`.${BAR_CLASS}`).forEach(push);
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      document.querySelectorAll(sel).forEach((host) => {
        const sr = getShadowRoot(host);
        sr?.querySelectorAll(`.${BAR_CLASS}`).forEach(push);
      });
    }
    return out;
  }

  async function runBiliIndexTagDiagnostics(opts = {}) {
    const report = {
      ok: true,
      at: new Date().toISOString(),
      href: location.href,
      contextInvalid: !!BiliIndex.isExtensionContextInvalidated?.(),
      version: document.documentElement.getAttribute("data-biliindex-version"),
      slotPointer: document.documentElement.getAttribute("data-biliindex-slot-pointer"),
      tagSlots: document.documentElement.getAttribute("data-biliindex-tag-slots"),
      wrapCount: document.querySelectorAll(`.${WRAP_CLASS}`).length,
      menuCount: document.querySelectorAll(`.${MENU_CLASS}`).length,
      apiOk,
      bars: [],
      tips: [],
    };
    if (report.contextInvalid) {
      report.ok = false;
      report.tips.push("扩展上下文已失效：请 chrome://extensions 重新加载扩展后对本页 F5");
      return report;
    }
    if (!report.tagSlots) {
      report.ok = false;
      report.tips.push("tag-slots 未注入：确认加载的是 biliindex-extension 目录");
      return report;
    }
    try {
      report.apiBase = await BiliIndex.getApiBase();
      report.apiMode = (await BiliIndex.storageGet(["apiMode"])).apiMode;
      report.proxyApi = !!(await BiliIndex.needsApiProxy?.());
      report.apiDirect = !report.proxyApi;
    } catch (e) {
      report.ok = false;
      report.apiError = e?.message || String(e);
      return report;
    }
    try {
      report.token = !!(await getAuthToken());
    } catch (e) {
      report.token = false;
      report.tokenError = e?.message || String(e);
    }
    if (!report.token) {
      report.tips.push("未登录：先点扩展图标登录");
    }
    const bars = collectAllBarsList();
    for (const bar of bars.slice(0, 8)) {
      const slots = [...bar.querySelectorAll(`.${SLOT_CLASS}`)].map((s) => ({
        slot: s.dataset.slot,
        clickBound: s.dataset.biliindexSlotBound === "1",
        filled: s.classList.contains("biliindex-slot-filled"),
      }));
      report.bars.push({
        upMid: bar.dataset.upMid,
        placement: bar.dataset.placement,
        layout: bar.dataset.layout,
        locked: bar.dataset.locked === "1",
        slots,
      });
    }
    if (!bars.length) {
      report.ok = false;
      report.tips.push("页面上没有 .biliindex-bar，请先打开有评论的视频页并等待几秒");
      return report;
    }
    const targetBar =
      bars.find((b) => b.dataset.placement === "comment") || bars[0];
    const slotEl =
      targetBar.querySelector(`.${SLOT_CLASS}[data-slot="tag1"]`) ||
      targetBar.querySelector(`.${SLOT_CLASS}`);
    if (slotEl) {
      let so = null;
      try {
        so = await fetchSlotOptions(
          targetBar.dataset.upMid,
          slotEl.dataset.slot || "tag1"
        );
        report.slotOptions = {
          upMid: targetBar.dataset.upMid,
          slot: slotEl.dataset.slot,
          itemCount: (so.items || []).length,
          hasPasserby: !!so.passerby,
          hasSuperHate: !!so.super_hate,
          frequentCount: (so.frequent || []).length,
          hint: so.hint || null,
        };
        if (!(so.items || []).length && !so.passerby && !so.super_hate) {
          report.tips.push("slot-options 为空：可能未登录或 API/标签库异常");
        }
      } catch (e) {
        report.ok = false;
        report.slotOptionsError = e?.message || String(e);
      }
      const canSubmit =
        so &&
        ((so.items && so.items.length) || so.passerby || so.super_hate);
      if (opts.testSubmit && report.token && canSubmit) {
        const pick =
          (so.items && so.items[0]) ||
          so.passerby ||
          so.super_hate;
        if (pick) {
          try {
            report.quickSubmit = await submitQuickTag(
              targetBar,
              slotEl.dataset.slot || "tag1",
              pick
            );
            if (!report.quickSubmit?.ok) {
              report.ok = false;
              report.tips.push(`打标被拒：${report.quickSubmit?.error || "未知"}`);
            }
          } catch (e) {
            report.ok = false;
            report.quickSubmitError = e?.message || String(e);
          }
        }
      }
      if (opts.openMenu) {
        requestSlotMenu(targetBar, slotEl);
        report.menuOpened = true;
      }
    }
    return report;
  }

  document.addEventListener("biliindex-debug-request", () => {
    const detail = { testSubmit: false, openMenu: false };
    void (async () => {
      try {
        const report = await runBiliIndexTagDiagnostics(detail);
        document.documentElement.setAttribute(
          "data-biliindex-debug",
          JSON.stringify(report)
        );
        document.dispatchEvent(
          new CustomEvent("biliindex-debug-done", { detail: report })
        );
      } catch (e) {
        const fail = { ok: false, error: e?.message || String(e) };
        document.documentElement.setAttribute(
          "data-biliindex-debug",
          JSON.stringify(fail)
        );
        document.dispatchEvent(
          new CustomEvent("biliindex-debug-done", { detail: fail })
        );
      }
    })();
  });

  document.addEventListener("biliindex-debug-request-full", () => {
    void (async () => {
      try {
        const report = await runBiliIndexTagDiagnostics({
          testSubmit: true,
          openMenu: false,
        });
        document.documentElement.setAttribute(
          "data-biliindex-debug",
          JSON.stringify(report)
        );
        document.dispatchEvent(
          new CustomEvent("biliindex-debug-done", { detail: report })
        );
      } catch (e) {
        const fail = { ok: false, error: e?.message || String(e) };
        document.documentElement.setAttribute(
          "data-biliindex-debug",
          JSON.stringify(fail)
        );
        document.dispatchEvent(
          new CustomEvent("biliindex-debug-done", { detail: fail })
        );
      }
    })();
  });

  document.addEventListener("biliindex-debug-open-menu", () => {
    void runBiliIndexTagDiagnostics({ openMenu: true });
  });

})();
