/**
 * BiliIndex 标签槽 demo1.0.0（扩展 manifest 1.0.0 基线）
 * 评论区 3+1 槽；推荐/UP 仅 Emoji
 * 无法修复时的回滚包：baseline/demo-1.0.0/
 */
(function () {
  "use strict";

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

  const BILIINDEX_VERSION = "1.0.0";
  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 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, .international-header, #bili-header-container, .bili-header__bar";
  const COLLECTION_ZONE_SELECTOR =
    ".video-section-head, .video-pod, bili-playlist, bili-ugc-season, .video-list-section";

  const COMMENT_NICKNAME_LINK_SELECTORS = [
    "#user-name a[href*='space.bilibili.com']",
    "#user-name a[href*='/space/']",
    ".user-name 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/']",
    "a[href*='space.bilibili.com']",
    "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";

  const mountedBars = new WeakSet();
  const styledShadowRoots = new WeakSet();
  const commentHostRun = new WeakMap();
  const commentItemRun = new WeakMap();
  const recoHostRun = new WeakMap();
  let scanRunId = 0;
  let apiOk = true;
  let lastMountCount = 0;
  let lastRunAt = 0;

  function isBiliShadowHost(el) {
    if (!(el instanceof Element)) return false;
    const tag = (el.tagName || "").toUpperCase();
    if (tag.startsWith("BILI-") || tag === "BILI-EMOJI") return true;
    try {
      return !!el.matches?.(BILI_SHADOW_HOST_SELECTOR);
    } catch {
      return false;
    }
  }

  function getShadowRoot(el) {
    if (!(el instanceof Element) || !isBiliShadowHost(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 walkCommentShadowScopes(entry, visit, depth = 0) {
    if (!entry || depth > 12) return;
    visit(entry);
    if (entry instanceof Element) {
      try {
        entry.querySelectorAll(BILI_SHADOW_HOST_SELECTOR).forEach((host) => {
          const sr = getShadowRoot(host);
          if (sr) walkCommentShadowScopes(sr, visit, depth + 1);
        });
      } catch {
        /* ignore */
      }
      const sr = getShadowRoot(entry);
      if (sr) walkCommentShadowScopes(sr, visit, depth + 1);
    }
  }

  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 && isBiliShadowHost(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)) scheduleRun();
        }).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 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) {
    const mount = getNicknameMountPoint(anchor);
    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 inForbiddenZone(el) {
    return !!(
      el?.closest?.(SITE_HEADER_SELECTOR) || el?.closest?.(COLLECTION_ZONE_SELECTOR)
    );
  }

  function getNicknameMountPoint(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" ? "😀" : "标";
  }

  function applyInlineBarStyles(bar) {
    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;";
    bar.querySelectorAll(`.${SLOT_CLASS}`).forEach((slot) => {
      slot.style.cssText =
        "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) {
    const bar = document.createElement("span");
    bar.className = BAR_CLASS;
    bar.dataset.upMid = upMid;
    bar.dataset.layout = layout;
    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);
    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:2147483646;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 renderSlotContent(slotEl, tag) {
    slotEl.innerHTML = "";
    if (!tag || (!tag.name && !tag.icon && !tag.code)) {
      slotEl.classList.remove("biliindex-slot-filled");
      showEmptySlot(slotEl);
      return;
    }
    const label = document.createElement("span");
    label.className = "biliindex-slot-label";
    label.textContent = tagLabel(tag);
    label.style.cssText =
      "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.textContent = String(votes);
      c.style.cssText = "font-size:0.82em;font-weight:700;color:#00aeec;margin-left:3px;";
      slotEl.appendChild(c);
    }
    slotEl.classList.add("biliindex-slot-filled");
    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) {
    try {
      const data = await BiliIndex.fetchPublic(
        `/api/tag/slot-options?up_mid=${encodeURIComponent(upMid)}&slot=${encodeURIComponent(slot)}`
      );
      apiOk = true;
      return { items: data.items || [], hint: data.hint || null, parent: data.parent || null };
    } catch {
      apiOk = false;
      return { items: [], hint: null, parent: null };
    }
  }

  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;
  }

  async function submitQuickTag(bar, slotName, tag) {
    const payload = {
      slot: slotName,
      nickname: bar.dataset.nickname || "",
      fan_count: null,
    };
    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 showMenu(bar, slotEl, tags, opts = {}) {
    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 {
      tags.forEach((tag) => {
        const btn = document.createElement("button");
        btn.type = "button";
        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;";
        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);
          }
        });
        menu.appendChild(btn);
      });
    }
    document.body.appendChild(menu);
    const rect = slotEl.getBoundingClientRect();
    menu.style.left = `${Math.max(8, rect.left)}px`;
    menu.style.top = `${rect.bottom + 4}px`;
  }

  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 } = await fetchSlotOptions(bar.dataset.upMid, slotEl.dataset.slot);
    showMenu(bar, slotEl, items, {
      showCounts: slotEl.dataset.slot === "emoji",
      hint,
      parent,
    });
  }

  function bindBar(bar) {
    if (mountedBars.has(bar)) return;
    mountedBars.add(bar);
    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"} placement */
  function mountBar(anchor, info, layout, placement) {
    if (!(anchor instanceof HTMLElement) || !info?.upMid) return;
    if (inForbiddenZone(anchor)) return;

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

    const itemRoot =
      placement === "comment"
        ? getCommentItemRoot(anchor)
        : placement === "up"
          ? getUpPanelItemRoot(anchor)
          : getCardItemRoot(anchor);

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

    const bar = createBar(info.upMid, layout);
    bar.dataset.nickname = info.nickname || "";
    const wrap = createBarWrap(bar);
    getNicknameMountPoint(anchor).insertAdjacentElement("afterend", wrap);
    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 processComments(root) {
    if (!root || commentHostRun.get(root) === scanRunId) return;
    commentHostRun.set(root, scanRunId);

    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.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) || hasBarForUp(panel, String(mid))) return;
    mountBar(
      link,
      { upMid: String(mid), nickname: (link.textContent || "").trim().slice(0, 64) },
      "emoji-only",
      "up"
    );
  }

  function processRoot(root) {
    if (!root || !(root instanceof Element)) return;
    if (matchesAnySelector(root, COMMENT_ROOT_SELECTORS)) processComments(root);
    if (matchesAnySelector(root, RECOMMEND_ROOT_SELECTORS)) {
      processRecoCards(root);
    }
    if (matchesAnySelector(root, UP_PANEL_ROOT_SELECTORS)) processUpPanel(root);
  }

  function run() {
    try {
      scanRunId += 1;
      lastMountCount = 0;
      attachShadowObservers();
      const roots = new Set([
        ...collectRoots(COMMENT_ROOT_SELECTORS),
        ...collectRoots(RECOMMEND_ROOT_SELECTORS),
        ...collectRoots(UP_PANEL_ROOT_SELECTORS),
      ]);

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

      for (const root of roots) processRoot(root);

      if (lastMountCount > 0) {
        console.info(`[BiliIndex] 标签栏已挂载（${lastMountCount} 处）`);
      }
    } catch (e) {
      console.warn("[BiliIndex] 标签槽扫描失败", e);
    }
  }

  let scheduled = false;
  let debounceTimer = null;

  function scheduleRun() {
    if (scheduled) return;
    scheduled = true;
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
      scheduled = false;
      const now = Date.now();
      if (now - lastRunAt < 900) return;
      lastRunAt = now;
      const work = () => run();
      if (typeof requestIdleCallback === "function") {
        requestIdleCallback(work, { timeout: 1200 });
      } else {
        requestAnimationFrame(work);
      }
    }, 400);
  }

  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）");
    } catch (e) {
      apiOk = false;
      console.warn("[BiliIndex] 本地 API 未连接", e?.message || e);
    }
  }

  void probeApiOnce();
  scheduleRun();

  const observedRoots = new WeakSet();

  function observeCommentRoot(root) {
    if (!root || observedRoots.has(root)) return;
    observedRoots.add(root);
    new MutationObserver((mutations) => {
      if (mutationHasRelevantAdds(mutations)) scheduleRun();
    }).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));
    }
    const body = document.body;
    if (body && !observedRoots.has(body)) {
      observedRoots.add(body);
      new MutationObserver(() => {
        attachRootObservers();
        if (document.querySelector("bili-comments, #comment-app")) scheduleRun();
      }).observe(body, { childList: true, subtree: false });
    }
  }

  attachRootObservers();

  window.addEventListener("load", scheduleRun);

  let pollTicks = 0;
  const pollTimer = setInterval(() => {
    scheduleRun();
    pollTicks += 1;
    if (pollTicks >= 12) clearInterval(pollTimer);
  }, 2000);

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