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

  if (window.self !== window.top) return;

  const BILIINDEX_VERSION = "1.0.10";
  document.documentElement.setAttribute("data-biliindex-version", BILIINDEX_VERSION);
  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 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";

  const COMMENT_ROOT_SELECTORS = [
    "bili-comments",
    "bili-comment-area",
    "#comment-app",
    "#comment-module",
    "#comment",
    ".bb-comment",
  ];
  const RECOMMEND_ROOT_SELECTORS = [
    "#reco_list",
    ".recommend-list-v1",
    ".rcmd-tab",
    ".rcmd-list",
    ".rcmd-box",
    ".rec-list",
    "#i_cecream .bili-feed4-layout",
  ];
  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_NICKNAME_LINK_SELECTORS = [
    "#user-name a[href*='space.bilibili.com']",
    "#user-name a[href*='/space/']",
    ".user-name a[href*='space.bilibili.com']",
    ".user-name-text a[href*='space.bilibili.com']",
    "a.name[href*='space.bilibili.com']",
    "bili-comment-user-renderer a[href*='space.bilibili.com']",
    "bili-comment-user-renderer a[href*='/space/']",
  ];
  const COMMENT_NICKNAME_HOST_SELECTORS = [
    "#user-name",
    ".user-name",
    "bili-comment-user-renderer",
  ];
  const AUTHOR_NICKNAME_LINK_SELECTORS = [
    ".upname a[href*='space.bilibili.com']",
    ".video-page-card-small .upname a[href*='space.bilibili.com']",
    "a.bili-video-card__info--author[href*='space.bilibili.com']",
    ".bili-video-card__info--author a[href*='space.bilibili.com']",
    "a.bili-video-card__info--owner[href*='space.bilibili.com']",
    ".rec-list-item a[href*='space.bilibili.com']",
    ".rcmd-item a[href*='space.bilibili.com']",
  ];
  const UP_PANEL_LINK_SELECTORS = [
    "a.up-name[href*='space.bilibili.com']",
    ".up-detail-top a[href*='space.bilibili.com']",
    "a[href*='space.bilibili.com']",
  ];

  const DEACTIVATED_RE =
    /账号已注销|用户已注销|已注销账号|该账号已注销|账户已注销|注销账号|销号|用户不存在|用户已封禁/;

  const BILI_SHADOW_HOST_SELECTOR =
    "bili-comments, bili-comment-area, bili-comment-thread-renderer, bili-comment-renderer, bili-comment-reply-renderer, bili-comment-user-renderer, bili-emoji";

  /** 仅评论区相关组件可穿透 Shadow，禁止 bili-header 等以免顶栏渲染异常 */
  const COMMENT_SHADOW_HOST_RE = /^BILI-COMMENT|^BILI-EMOJI$/;

  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;

  function isCommentShadowHost(el) {
    if (!(el instanceof Element)) return false;
    const tag = (el.tagName || "").toUpperCase();
    if (COMMENT_SHADOW_HOST_RE.test(tag)) return true;
    try {
      return !!el.matches?.(BILI_SHADOW_HOST_SELECTOR);
    } catch {
      return false;
    }
  }

  function getShadowRoot(el) {
    if (!(el instanceof Element) || !isCommentShadowHost(el)) return null;
    try {
      if (chrome?.dom?.openOrClosedShadowRoot) {
        const sr = chrome.dom.openOrClosedShadowRoot(el);
        if (sr) return sr;
      }
    } catch {
      /* ignore */
    }
    try {
      if (el.openOrClosedShadowRoot) return el.openOrClosedShadowRoot;
    } catch {
      /* ignore */
    }
    return el.shadowRoot || null;
  }

  function queryAllDeep(root, selector) {
    const out = [];
    const seen = new Set();
    function walk(node) {
      if (!node || seen.has(node)) return;
      seen.add(node);
      let scope = node;
      if (node instanceof Element && isCommentShadowHost(node)) {
        const shadow = getShadowRoot(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);
    }
    walk(root);
    return out;
  }

  function extractMid(href) {
    if (!href) return null;
    const m = String(href).match(/space\.bilibili\.com\/(\d+)|\/space\/(\d+)/i);
    return m ? m[1] || m[2] : null;
  }

  function midFromCommentElement(el) {
    const d = el?.__data;
    if (!d) return null;
    const m = d.mid ?? d.member?.mid ?? d.user?.mid ?? d.reply?.member?.mid;
    return m != null && m !== 0 && m !== "0" ? String(m) : null;
  }

  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 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();

  function attachShadowObservers() {
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      document.querySelectorAll(sel).forEach((host) => {
        const shadow = getShadowRoot(host);
        if (!shadow || observedShadowHosts.has(host)) return;
        observedShadowHosts.add(host);
        injectStylesIntoShadow(shadow);
        new MutationObserver((mutations) => {
          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("link[data-biliindex-css]")) return;
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.setAttribute("data-biliindex-css", "1");
      link.href = chrome.runtime.getURL(CSS_URL);
      shadowRoot.prepend(link);
    } catch {
      /* ignore */
    }
  }

  function findCommentNicknameLink(renderer) {
    const links = [];
    for (const sel of COMMENT_NICKNAME_LINK_SELECTORS) {
      queryAllDeep(renderer, sel).forEach((el) => {
        if (el instanceof HTMLAnchorElement && !links.includes(el)) links.push(el);
      });
    }
    return (
      links.find((l) => !l.querySelector("img") && (l.textContent || "").trim()) ||
      links[0] ||
      null
    );
  }

  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 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 = getNicknameMountPoint(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 /space\.bilibili\.com/i.test(location.hostname);
  }

  function midFromSpacePageUrl() {
    const m = location.pathname.match(/^\/(\d{5,})/);
    return m ? m[1] : null;
  }

  function getAnyShadowRoot(el) {
    if (!(el instanceof Element)) return null;
    try {
      if (chrome?.dom?.openOrClosedShadowRoot) {
        const sr = chrome.dom.openOrClosedShadowRoot(el);
        if (sr) return sr;
      }
    } catch {
      /* ignore */
    }
    try {
      if (el.openOrClosedShadowRoot) return el.openOrClosedShadowRoot;
    } catch {
      /* ignore */
    }
    return el.shadowRoot || null;
  }

  /** 空间页：穿透常见 B 站组件 Shadow 查找昵称节点 */
  function queryAllDeepSpace(selector) {
    const out = [];
    const seen = new Set();
    function walk(node) {
      if (!node || seen.has(node)) return;
      seen.add(node);
      let scope = node;
      if (node instanceof Element && isSpaceHostPage()) {
        const shadow = getAnyShadowRoot(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);
    }
    walk(document.body);
    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)
    );
  }

  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}`)
        .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 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;
  }

  function showEmptySlot(slotEl) {
    if (slotEl.classList.contains("biliindex-slot-filled")) return;
    slotEl.textContent = slotEl.dataset.slot === "emoji" ? "😀" : "标";
  }

  const SPACE_SLOT_THEME = {
    slot:
      "display:inline-flex!important;align-items:center;justify-content:center;min-width:28px;padding:2px 8px;border-radius:999px;border:1px solid rgba(255,255,255,0.9);background:#fb7299;color:#fff;cursor:pointer;font-size:inherit;line-height:1.2;font-weight:600;",
    label: "font-size:13px;font-weight:600;color:#fff;line-height:1.2;white-space:nowrap;",
    count: "font-size:0.82em;font-weight:700;color:#fff;margin-left:3px;",
    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;";
    if (isSpace) bar.classList.add("biliindex-bar-space");
    else bar.classList.remove("biliindex-bar-space");
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((slot) => {
      slot.style.cssText = isSpace
        ? SPACE_SLOT_THEME.slot
        : "display:inline-flex!important;align-items:center;justify-content:center;min-width:28px;padding:2px 8px;border-radius:999px;border:1px solid rgba(0,174,236,0.35);background:rgba(0,174,236,0.12);cursor:pointer;font-size:inherit;line-height:1.2;";
      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("span");
      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 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 renderSlotContent(slotEl, tag) {
    const bar = slotEl.closest(`.${BAR_CLASS}`);
    const isSpace = isSpacePlacement(bar);
    slotEl.innerHTML = "";
    if (!tag || (!tag.name && !tag.icon && !tag.code)) {
      slotEl.classList.remove("biliindex-slot-filled");
      if (isSpace) slotEl.style.cssText = SPACE_SLOT_THEME.slot;
      showEmptySlot(slotEl);
      return;
    }
    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;
      label.style.cssText = isSpace
        ? SPACE_SLOT_THEME.label
        : "font-size:18px;font-weight:600;color:#00aeec;line-height:1.1;white-space:nowrap;";
    } else {
      label.textContent = tagLabel(tag);
      label.style.cssText = isSpace
        ? SPACE_SLOT_THEME.label
        : "font-size:13px;font-weight:600;color:#00aeec;line-height:1.2;white-space:nowrap;";
    }
    slotEl.appendChild(label);
    const votes = Number(tag.vote_count) || 0;
    if (votes > 0) {
      const c = document.createElement("span");
      c.className = "biliindex-slot-count";
      c.textContent = String(votes);
      c.style.cssText = isSpace
        ? SPACE_SLOT_THEME.count
        : "font-size:0.82em;font-weight:700;color:#00aeec;margin-left:3px;";
      slotEl.appendChild(c);
    }
    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 = "rgba(0,174,236,0.22)";
      slotEl.style.borderColor = "rgba(0,174,236,0.55)";
    }
  }

  function applyDisplay(bar, display) {
    const slots = display?.slots || [null, null, null];
    const emoji = display?.emoji || null;
    const passerby = !!display?.passerby;
    bar.dataset.passerby = passerby ? "1" : "0";
    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);
    });
  }

  async function fetchDisplay(upMid) {
    try {
      const data = await BiliIndex.fetchPublic(
        `/api/tag/active-list?up_mid=${encodeURIComponent(upMid)}`
      );
      apiOk = true;
      return data;
    } catch {
      apiOk = false;
      return { slots: [null, null, null], emoji: null };
    }
  }

  async function refreshBar(bar) {
    if (!bar?.isConnected) return;
    const d = await fetchDisplay(bar.dataset.upMid);
    applyDisplay(bar, d);
  }

  async function fetchSlotOptions(upMid, slot) {
    const path = `/api/tag/slot-options?up_mid=${encodeURIComponent(upMid)}&slot=${encodeURIComponent(slot)}`;
    try {
      const token = await getAuthToken();
      const data =
        token && typeof BiliIndex !== "undefined" && BiliIndex.apiFetch
          ? await BiliIndex.apiFetch(path)
          : await BiliIndex.fetchPublic(path);
      apiOk = true;
      return {
        items: data.items || [],
        hint: data.hint || null,
        parent: data.parent || null,
        passerby: data.passerby || null,
        frequent: data.frequent || [],
      };
    } catch {
      apiOk = false;
      return { items: [], hint: null, parent: null, passerby: null, frequent: [] };
    }
  }

  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);
    }
    const payload = {
      slot: slotName,
      nickname: bar.dataset.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());
  }

  function bindTagPickButton(bar, slotEl, btn, tag, opts = {}) {
    btn.addEventListener("mousedown", (e) => e.stopPropagation());
    btn.addEventListener("click", async (e) => {
      e.preventDefault();
      e.stopPropagation();
      const token = await getAuthToken();
      if (!token) {
        removeAllMenus();
        showBarToast(bar, "请先点击扩展图标登录后再打标", true);
        return;
      }
      const slotName = slotEl.dataset.slot;
      applyTagToSlot(bar, slotName, tag);
      removeAllMenus();
      const res = await submitQuickTag(bar, slotName, tag);
      if (res?.ok) {
        applyDisplay(bar, res.display || (await fetchDisplay(bar.dataset.upMid)));
        showBarToast(bar, `${res.message || "打标成功"}：${tagLabel(res.tag || tag)}`);
        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 || tags.find((t) => t.code === PASSERBY_CODE) || null;
    const categories = tags.filter((t) => t.code !== PASSERBY_CODE);
    const frequent = Array.isArray(opts.frequent) ? opts.frequent : [];
    const scrollVisible = passerby
      ? CATEGORY_TAB_VISIBLE - 1
      : CATEGORY_TAB_VISIBLE;

    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;
      bindTagPickButton(bar, slotEl, pbBtn, passerby, { title: pbLabel });
      rowMain.appendChild(pbBtn);
    }

    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 showMenu(bar, slotEl, tags, opts = {}) {
    if (opts.slot === "tag1" && tags.length) {
      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;";
      empty.textContent =
        opts.hint ||
        (apiOk ? "暂无标签" : "无法连接 API，请启动 uvicorn app.main:app --port 8000");
      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);
          btn.title =
            opts.showCounts && tag.vote_count
              ? `${name}（${tag.vote_count} 人打标）`
              : 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 {
          btn.textContent = opts.showCounts
            ? `${tagLabel(tag)} (${tag.vote_count || 0})`
            : 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) {
    if (bar.dataset.passerby === "1" && (slotEl.dataset.slot === "tag2" || slotEl.dataset.slot === "tag3")) {
      return;
    }
    const { items, hint, parent, passerby, frequent } = await fetchSlotOptions(
      bar.dataset.upMid,
      slotEl.dataset.slot
    );
    showMenu(bar, slotEl, items, {
      slot: slotEl.dataset.slot,
      showCounts: slotEl.dataset.slot === "emoji",
      hint,
      parent,
      passerby,
      frequent,
    });
  }

  function bindBar(bar) {
    if (mountedBars.has(bar)) return;
    mountedBars.add(bar);
    const wrap = bar.closest(`.${WRAP_CLASS}`);
    if (wrap instanceof HTMLElement) {
      for (const evt of ["mousedown", "click", "mouseenter", "mouseover"]) {
        wrap.addEventListener(evt, (e) => e.stopPropagation());
      }
    }
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((slotEl) => {
      slotEl.addEventListener("click", (e) => {
        e.stopPropagation();
        e.stopImmediatePropagation();
        void toggleSlotMenu(bar, slotEl);
      });
    });
    void refreshBar(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);
    getNicknameMountPoint(anchor, placement).insertAdjacentElement("afterend", 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 (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;
    }
    const mid =
      (link && extractMid(link.getAttribute("href") || link.href)) ||
      midFromCommentElement(item);
    if (!mid) return;
    const upMid = String(mid);
    if (hasBarForUp(item, upMid) || (link && hasWrapAdjacent(link))) {
      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 = 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 = 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 = new MutationObserver(() => runSpaceProfileScan());
    if (document.body) obs.observe(document.body, { childList: true, subtree: true });
  }

  function processComments(root) {
    if (!root) return;

    const items = queryAllDeep(root, "bili-comment-renderer, bili-comment-reply-renderer");
    const list = items.length ? items : queryAllDeep(root, ".reply-item, .list-item");

    for (const item of list) {
      processCommentItem(item);
    }
  }

  function findRecoAuthorLink(card) {
    if (!(card instanceof Element)) return null;
    const selectors = [
      ".upname a[href*='space.bilibili.com']",
      ".r-upinfo .upname a[href*='space.bilibili.com']",
      ".upinfo .upname a[href*='space.bilibili.com']",
      "a.up-name[href*='space.bilibili.com']",
    ];
    for (const sel of selectors) {
      const a = card.querySelector(sel);
      if (a instanceof HTMLAnchorElement && !(a.querySelector("img") && !(a.textContent || "").trim())) {
        return a;
      }
    }
    return null;
  }

  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")) add(root);
      root.querySelectorAll(".video-page-card-small, .rec-list-item, .rcmd-item").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)) {
      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 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);
    const link =
      panel.querySelector?.("a.up-name[href*='space.bilibili.com']") ||
      panel.querySelector?.(".up-detail-top a[href*='space.bilibili.com']");
    if (!(link instanceof HTMLAnchorElement) || inForbiddenZone(link)) return;
    const mid = extractMid(link.getAttribute("href") || link.href);
    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"
    );
  }

  function runCommentPhase() {
    attachShadowObservers();
    for (const sel of ["bili-comments", "bili-comment-area", "#comment-app"]) {
      document.querySelectorAll(sel).forEach((host) => {
        const shadow = getShadowRoot(host);
        if (shadow) injectStylesIntoShadow(shadow);
        processComments(host);
      });
    }
    runSpaceProfileScan();
    cleanupProfilePopoverBars();
  }

  function runRecoPhase() {
    for (const root of collectRoots(RECOMMEND_ROOT_SELECTORS)) {
      processRecoCards(root);
    }
    for (const root of collectRoots(UP_PANEL_ROOT_SELECTORS)) {
      processUpPanel(root);
    }
  }

  function runCommentScan() {
    try {
      scanRunId += 1;
      lastMountCount = 0;
      runCommentPhase();
      cleanupForbiddenBars();
      if (lastMountCount > 0) {
        console.info(`[BiliIndex] 评论标签栏已挂载（${lastMountCount} 处）`);
      }
    } catch (e) {
      console.warn("[BiliIndex] 评论扫描失败", e);
    }
  }

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

  let commentDebounce = null;
  let recoDebounce = null;

  function scheduleCommentScan() {
    commentScanPending = true;
    clearTimeout(commentDebounce);
    commentDebounce = setTimeout(() => {
      const now = Date.now();
      const wait = 450 - (now - lastCommentRunAt);
      if (wait > 0) {
        clearTimeout(commentDebounce);
        commentDebounce = setTimeout(() => scheduleCommentScan(), wait);
        return;
      }
      lastCommentRunAt = now;
      commentScanPending = false;
      const work = () => runCommentScan();
      if (typeof requestIdleCallback === "function") {
        requestIdleCallback(work, { timeout: 1500 });
      } else {
        requestAnimationFrame(work);
      }
    }, 300);
  }

  function scheduleRecoScan() {
    recoScanPending = true;
    clearTimeout(recoDebounce);
    recoDebounce = setTimeout(() => {
      const now = Date.now();
      const wait = 2200 - (now - lastRecoRunAt);
      if (wait > 0) {
        clearTimeout(recoDebounce);
        recoDebounce = setTimeout(() => scheduleRecoScan(), wait);
        return;
      }
      lastRecoRunAt = now;
      recoScanPending = false;
      const work = () => runRecoScan();
      if (typeof requestIdleCallback === "function") {
        requestIdleCallback(work, { timeout: 2000 });
      } else {
        requestAnimationFrame(work);
      }
    }, 500);
  }

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

  async function probeApiOnce() {
    if (typeof BiliIndex === "undefined") return;
    try {
      await BiliIndex.fetchPublic("/api/health");
      apiOk = true;
      console.info("[BiliIndex] 本地 API 已连接（127.0.0.1:8000）");
      refreshAllBars();
    } catch (e) {
      apiOk = false;
      console.warn("[BiliIndex] 本地 API 未连接", e?.message || e);
    }
  }

  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 */
    }
  }

  void probeApiOnce();
  scheduleFullScan();
  setupSpaceProfileObserver();

  const observedRoots = new WeakSet();

  function observeCommentRoot(root) {
    if (!root || observedRoots.has(root)) return;
    observedRoots.add(root);
    new MutationObserver((mutations) => {
      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);
      new MutationObserver(() => {
        attachRootObservers();
        setupCommentVisibilityObserver();
        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();

  window.addEventListener("load", () => {
    scheduleFullScan();
    setupCommentVisibilityObserver();
    setupSpaceProfileObserver();
    if (isSpaceHostPage()) runSpaceProfileScan();
  });

  let pollTicks = 0;
  const pollTimer = setInterval(() => {
    scheduleCommentScan();
    if (pollTicks % 2 === 0) scheduleRecoScan();
    if (isSpaceHostPage()) runSpaceProfileScan();
    pollTicks += 1;
    const commentCount = countCommentRenderers();
    if ((commentCount > 0 && pollTicks >= 4) || pollTicks >= 45) {
      clearInterval(pollTimer);
    }
  }, 2000);

  document.addEventListener(
    "click",
    () => {
      removeAllMenus();
    },
    true
  );
})();
