/**
 * B 站页面 DOM 工具：Shadow 穿透、评论区宿主、UP mid 解析（content scripts 共用）
 */
(function () {
  "use strict";

  const BiliDom = (globalThis.BiliDom = globalThis.BiliDom || {});

  BiliDom.COMMENT_ROOT_SELECTORS = [
    "bili-comments",
    "bili-comment-area",
    "#comment-app",
    "#comment-module",
    "#comment",
    ".bb-comment",
  ];

  BiliDom.COMMENT_SHADOW_HOST_RE = /^BILI-COMMENT|^BILI-EMOJI$/;

  BiliDom.RECO_SHADOW_HOST_RE = /^BILI-(COMMENT|EMOJI|VIDEO|FEED|SEARCH)/;

  BiliDom.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";

  BiliDom.RECO_SHADOW_HOST_SELECTOR =
    BiliDom.BILI_SHADOW_HOST_SELECTOR +
    ", bili-video-card, bili-feed-card, bili-search-video-card";

  BiliDom.getCommentHosts = function getCommentHosts() {
    const hosts = [];
    const seen = new Set();
    for (const sel of BiliDom.COMMENT_ROOT_SELECTORS) {
      try {
        document.querySelectorAll(sel).forEach((el) => {
          if (!seen.has(el)) {
            seen.add(el);
            hosts.push(el);
          }
        });
      } catch {
        /* ignore */
      }
    }
    return hosts;
  };

  /** 仅在评论区宿主子树内深度查询（穿透 Shadow，不扫全页） */
  BiliDom.queryCommentDeep = function queryCommentDeep(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) {
        let shadow = null;
        if (BiliDom.isCommentShadowHost(node)) {
          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 (let i = 0; i < kids.length; i += 1) walk(kids[i]);
    }
    for (const host of BiliDom.getCommentHosts()) walk(host);
    return out;
  };

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

  /** 评论区 + 推荐/搜索卡片 Shadow（超级讨厌等） */
  BiliDom.isRecoOrCommentShadowHost = function isRecoOrCommentShadowHost(el) {
    if (!(el instanceof Element)) return false;
    const tag = (el.tagName || "").toUpperCase();
    if (BiliDom.RECO_SHADOW_HOST_RE.test(tag)) return true;
    try {
      return !!el.matches?.(BiliDom.RECO_SHADOW_HOST_SELECTOR);
    } catch {
      return false;
    }
  };

  BiliDom.openShadowRoot = function openShadowRoot(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 {
      return el.openOrClosedShadowRoot || el.shadowRoot || null;
    } catch {
      return null;
    }
  };

  /** 仅评论区相关宿主才穿透 Shadow（避免误扫顶栏等） */
  BiliDom.getCommentShadowRoot = function getCommentShadowRoot(el) {
    if (!BiliDom.isCommentShadowHost(el)) return null;
    return BiliDom.openShadowRoot(el);
  };

  /**
   * @param {ParentNode|null} root
   * @param {string} selector
   * @param {{ commentShadowOnly?: boolean }} [opts]
   */
  BiliDom.queryAllDeep = function queryAllDeep(root, selector, opts = {}) {
    const shadowHostTest =
      opts.shadowHostTest ||
      (opts.commentShadowOnly === false
        ? null
        : BiliDom.isCommentShadowHost);
    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) {
        let shadow = null;
        if (shadowHostTest?.(node)) {
          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);
    }
    walk(root || document.body);
    return out;
  };

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

  BiliDom.midFromSpacePageUrl = function midFromSpacePageUrl() {
    if (!/space\.bilibili\.com/i.test(location.hostname)) return null;
    const m = (location.pathname || "").match(/^\/(\d{5,})/);
    return m ? m[1] : null;
  };

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

  BiliDom.midFromCommentElement = function midFromCommentElement(el) {
    if (!el) return null;
    let node = el;
    for (let depth = 0; depth < 12 && node; depth += 1) {
      const fromData = midFromCommentDataPayload(node.__data || node.data);
      if (fromData) return fromData;
      if (node instanceof Element) {
        for (const attr of ["data-mid", "data-user-id", "data-user-profile-id"]) {
          const raw = node.getAttribute?.(attr);
          if (BiliDom.isValidMid(raw)) return String(raw).trim();
        }
      }
      if (node instanceof HTMLElement && node.parentElement) {
        node = node.parentElement;
        continue;
      }
      const root = node.getRootNode?.();
      if (root instanceof ShadowRoot && root.host instanceof Element) {
        node = root.host;
        continue;
      }
      break;
    }
    return null;
  };

  BiliDom.isValidMid = function isValidMid(mid) {
    return mid != null && /^\d+$/.test(String(mid).trim());
  };

  BiliDom.MAIN_COMMENT_SEL = "bili-comment-renderer";
  BiliDom.REPLY_COMMENT_SEL = "bili-comment-reply-renderer";

  BiliDom.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/']",
    "a[href*='space.bilibili.com']",
    "a[href*='/space/']",
  ];

  BiliDom.SEARCH_UP_BLOCK_SEL = [
    ".user-item",
    ".user-list-item",
    ".bili-user-card",
    ".user-card",
    ".search-user-card",
    ".user-content",
    ".user-video-list",
    ".video-list-row",
    ".search-user-interactive",
    ".user-interactive",
    "[class*='UserSearch']",
    "[class*='user-search']",
    "[class*='SearchUser']",
    "bili-user-card",
    "bili-search-user-card",
  ].join(", ");

  BiliDom.AUTHOR_LINK_SELECTORS = [
    "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']",
    ".bili-video-card__info--owner[href*='space.bilibili.com']",
    ".bili-video-card a[href*='space.bilibili.com']",
    "a[href*='space.bilibili.com']",
    ".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']",
    ".video-page-card-small .upname a[href*='space.bilibili.com']",
  ];

  BiliDom.VIDEO_UP_DOM_SELECTORS = [
    "a.up-name[href*='space.bilibili.com']",
    ".up-detail-top a[href*='space.bilibili.com']",
    "#v_upinfo a[href*='space.bilibili.com']",
    ".up-info-container a[href*='space.bilibili.com']",
  ];

  BiliDom.midFromVideoInitialState = function midFromVideoInitialState() {
    try {
      const st = window.__INITIAL_STATE__;
      const candidates = [
        st?.videoData?.owner,
        st?.upData,
        st?.elecFullInfo?.up_info,
      ].filter(Boolean);
      for (const up of candidates) {
        const mid = up?.mid ?? up?.uid;
        if (mid != null && mid !== "" && mid !== 0 && mid !== "0") {
          return String(mid);
        }
      }
    } catch {
      /* ignore */
    }
    return null;
  };

  BiliDom.midFromVideoDom = function midFromVideoDom() {
    for (const sel of BiliDom.VIDEO_UP_DOM_SELECTORS) {
      const link = document.querySelector(sel);
      if (!(link instanceof HTMLAnchorElement)) continue;
      const mid = BiliDom.extractMid(link.getAttribute("href") || link.href);
      if (mid) return mid;
    }
    return null;
  };

  BiliDom.midFromMountedBar = function midFromMountedBar(root) {
    const scope = root && root.querySelector ? root : document;
    const bar = scope.querySelector(".biliindex-bar[data-up-mid]");
    const mid = bar?.dataset?.upMid;
    return BiliDom.isValidMid(mid) ? String(mid) : null;
  };

  /**
   * @param {{ lastUpMid?: string|null, preferCache?: boolean }} [opts]
   */
  BiliDom.detectPageUpMid = function detectPageUpMid(opts = {}) {
    const preferCache = opts.preferCache !== false;
    const cached = opts.lastUpMid;
    if (preferCache && BiliDom.isValidMid(cached)) {
      return String(cached);
    }
    const fromBar = BiliDom.midFromMountedBar();
    if (fromBar) return fromBar;
    const fromSpace = BiliDom.midFromSpacePageUrl();
    if (fromSpace) return fromSpace;
    const fromVideoState = BiliDom.midFromVideoInitialState();
    if (fromVideoState) return fromVideoState;
    const fromVideoDom = BiliDom.midFromVideoDom();
    if (fromVideoDom) return fromVideoDom;
    return null;
  };

  /**
   * @param {ParentNode} renderer
   * @param {(root: ParentNode, selector: string) => Element[]} queryDeep
   */
  BiliDom.findCommentNicknameLink = function findCommentNicknameLink(renderer, queryDeep) {
    const links = [];
    for (const sel of BiliDom.COMMENT_NICKNAME_LINK_SELECTORS) {
      queryDeep(renderer, sel).forEach((el) => {
        if (el instanceof HTMLAnchorElement && !links.includes(el)) links.push(el);
      });
    }
    const picked =
      links.find((l) => !l.querySelector("img") && (l.textContent || "").trim()) ||
      links[0] ||
      null;
    if (picked) return picked;
    for (const a of queryDeep(renderer, "a[href*='space.bilibili.com'], a[href*='/space/']")) {
      if (!(a instanceof HTMLAnchorElement)) continue;
      if (!BiliDom.extractMid(a.getAttribute("href") || a.href)) continue;
      if (a.querySelector("img") && !(a.textContent || "").trim()) continue;
      return a;
    }
    return null;
  };

  BiliDom.isReplyComment = function isReplyComment(item) {
    if (!(item instanceof Element)) return false;
    const replySel = BiliDom.REPLY_COMMENT_SEL;
    return item.matches?.(replySel) || !!item.closest?.(replySel);
  };

  BiliDom.resolveCommentTarget = function resolveCommentTarget(item) {
    if (!(item instanceof HTMLElement)) return null;
    if (BiliDom.isReplyComment(item)) return null;
    const mainSel = BiliDom.MAIN_COMMENT_SEL;
    const root =
      item.matches?.(mainSel) ? item : item.closest?.(mainSel);
    return root instanceof HTMLElement ? root : null;
  };

  /**
   * 评论 UP mid：__data → 已挂标签栏 → 昵称链接
   * 标签槽需对主评/楼中楼均可解析；屏蔽逻辑仍用 resolveCommentTarget 限定主评。
   * @param {HTMLElement} item
   * @param {(root: ParentNode, selector: string) => Element[]} queryDeep
   */
  BiliDom.resolveCommentUpMid = function resolveCommentUpMid(item, queryDeep) {
    if (!(item instanceof HTMLElement)) return null;
    const scopes = [];
    const main = BiliDom.resolveCommentTarget(item);
    if (main) scopes.push(main);
    if (!scopes.includes(item)) scopes.push(item);

    for (const scope of scopes) {
      const fromData = BiliDom.midFromCommentElement(scope);
      if (fromData) return fromData;
      for (const bar of queryDeep(scope, ".biliindex-bar[data-up-mid]")) {
        const mid = bar?.dataset?.upMid;
        if (BiliDom.isValidMid(mid)) return String(mid);
      }
      const link = BiliDom.findCommentNicknameLink(scope, queryDeep);
      if (link) {
        const mid = BiliDom.extractMid(link.getAttribute("href") || link.href);
        if (mid) return mid;
      }
    }
    return null;
  };

  /**
   * @param {ParentNode} card
   * @param {(root: ParentNode, selector: string) => Element[]} [queryDeep]
   */
  BiliDom.findAuthorLink = function findAuthorLink(card, queryDeep) {
    if (!(card instanceof Element)) return null;
    const walk = (root, sel) => {
      if (queryDeep) return queryDeep(root, sel);
      return [...root.querySelectorAll(sel)];
    };
    for (const sel of BiliDom.AUTHOR_LINK_SELECTORS) {
      const hits = walk(card, sel);
      for (const a of hits) {
        if (!(a instanceof HTMLAnchorElement)) continue;
        const mid = BiliDom.extractMid(a.getAttribute("href") || a.href);
        if (!mid) continue;
        if (a.querySelector("img") && !(a.textContent || "").trim()) continue;
        return a;
      }
    }
    for (const a of walk(
      card,
      "a[href*='space.bilibili.com'], a[href*='/space/']"
    )) {
      if (!(a instanceof HTMLAnchorElement)) continue;
      const mid = BiliDom.extractMid(a.getAttribute("href") || a.href);
      if (mid) return a;
    }
    return null;
  };

  BiliDom.resolveUpMidFromBlock = function resolveUpMidFromBlock(block) {
    if (!(block instanceof HTMLElement)) return null;
    if (BiliDom.isValidMid(block.dataset.biliindexUpMid)) {
      return block.dataset.biliindexUpMid;
    }
    for (const a of block.querySelectorAll(
      "a[href*='space.bilibili.com'], a[href*='/space/']"
    )) {
      if (!(a instanceof HTMLAnchorElement)) continue;
      const mid = BiliDom.extractMid(a.getAttribute("href") || a.href);
      if (mid) {
        block.dataset.biliindexUpMid = mid;
        return mid;
      }
    }
    return null;
  };

  BiliDom.isSpacePage = function isSpacePage() {
    return /space\.bilibili\.com/i.test(location.hostname);
  };

  BiliDom.isSearchSite = function isSearchSite() {
    return (
      /search\.bilibili\.com/i.test(location.hostname) ||
      /\/search/i.test(location.pathname || "")
    );
  };

  /**
   * 卡片/推荐位 UP mid
   * @param {HTMLElement} card
   * @param {{ queryDeep?: Function, spaceVideoZone?: (el: Element) => boolean }} [opts]
   */
  BiliDom.resolveCardUpMid = function resolveCardUpMid(card, opts = {}) {
    if (!(card instanceof HTMLElement)) return null;
    const queryDeep = opts.queryDeep;
    if (
      card.dataset.biliindexForceUpMid &&
      BiliDom.isValidMid(card.dataset.biliindexForceUpMid)
    ) {
      return card.dataset.biliindexForceUpMid;
    }
    const bars = queryDeep
      ? queryDeep(card, ".biliindex-bar[data-up-mid]")
      : [...card.querySelectorAll(".biliindex-bar[data-up-mid]")];
    const bar = bars[0];
    if (bar?.dataset?.upMid && BiliDom.isValidMid(bar.dataset.upMid)) {
      return String(bar.dataset.upMid);
    }
    const block = card.closest(BiliDom.SEARCH_UP_BLOCK_SEL);
    if (block) {
      const fromBlock = BiliDom.resolveUpMidFromBlock(block);
      if (fromBlock) return fromBlock;
    }
    const link = BiliDom.findAuthorLink(card, queryDeep);
    if (link) {
      const mid = BiliDom.extractMid(link.getAttribute("href") || link.href);
      if (mid) return mid;
    }
    if (
      opts.spaceVideoZone?.(card) &&
      BiliDom.isSpacePage()
    ) {
      const owner = BiliDom.midFromSpacePageUrl();
      if (owner) return owner;
    }
    return null;
  };

  if (globalThis.BiliIndex) {
    globalThis.BiliIndex.openShadowRoot = BiliDom.openShadowRoot;
  }
})();
