/**
 * 推荐栏 / 主页视频：UP 主标签命中屏蔽规则时，封面灰色遮罩（双击查看）。
 */
(function () {
  "use strict";

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

  const BLOCK_MSG = "已根据规则屏蔽，如需查看请双击";
  const PASSERBY_CODE = "passerby";
  const OVERLAY_CLASS = "biliindex-video-block-overlay";
  const COVER_HOST_CLASS = "biliindex-video-cover-host";

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

  const 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(", ");

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

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

  const COVER_HOST_SEL = [
    ".bili-video-card__image",
    ".bili-video-card__image--wrap",
    ".bili-video-card__cover",
    ".cover",
    ".thumbnail",
    ".pic",
    ".image-area",
    ".bili-cover",
    ".video-card__image",
    ".rec-cover",
    ".cover-wrapper",
    ".bili-video-card__stub",
    ".video-page-card-small .cover",
    ".video-page-card-small .bili-video-card__image",
    ".video-page-card-small .bili-video-card__image--wrap",
    ".video-page-card-small a[href*='/video/']",
  ].join(", ");

  const VIDEO_SCAN_ROOTS = [
    "#reco_list",
    ".rcmd-list",
    ".recommend-list-v1",
    ".rcmd-box",
    "#i_cecream",
    ".bili-feed4-layout",
    ".search-page",
    ".search-page-wrapper",
    "#all-list",
    "#user-list",
    ".search-component-result-list",
    ".video-list.bili-grid",
    ".search-result-container",
    ".user-item",
    ".user-list-item",
    ".bili-user-card",
    ".user-card",
  ];

  const AUTHOR_LINK_SEL = [
    "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']",
    ".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']",
  ];

  const SKIP_ZONE_SEL =
    "#comment, bili-comments, .comment-list, .bb-comment, #comment-app, .player-wrap, #bilibili-player, .bpx-player-container";

  let blockCodes = [];
  let blockReady = false;
  const displayCache = new Map();
  const scanQueued = new Set();

  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 codesMatchBlock(codes) {
    if (!blockReady || !blockCodes.length || !codes?.length) return false;
    const set = new Set(blockCodes);
    return codes.some((c) => c && set.has(c));
  }

  function shouldBlockDisplay(display) {
    if (!display) return false;
    const tag1 = display.slots?.[0];
    const emoji = display.emoji;
    return codesMatchBlock([tag1?.code, emoji?.code].filter(Boolean));
  }

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

  function shouldBlockFromBar(bar) {
    return codesMatchBlock(codesFromBar(bar));
  }

  function shouldBlockUp(display, bar) {
    return shouldBlockDisplay(display) || shouldBlockFromBar(bar);
  }

  async function loadBlockRules() {
    blockCodes = [];
    blockReady = false;
    displayCache.clear();
    scanQueued.clear();
    try {
      const stored = await chrome.storage.local.get(["blockTagCodes", "blockIsMember"]);
      if (stored.blockIsMember && Array.isArray(stored.blockTagCodes) && stored.blockTagCodes.length) {
        blockCodes = stored.blockTagCodes.filter((c) => c && c !== PASSERBY_CODE);
        blockReady = blockCodes.length > 0;
        return;
      }
      if (typeof BiliIndex === "undefined" || !BiliIndex.getToken) return;
      const token = await BiliIndex.getToken();
      if (!token) return;
      const data = await BiliIndex.apiFetch("/api/user/block-tags");
      if (!data?.is_member || !data?.codes?.length) return;
      blockCodes = data.codes.filter((c) => c !== PASSERBY_CODE);
      blockReady = blockCodes.length > 0;
      await chrome.storage.local.set({
        blockTagCodes: blockCodes,
        blockIsMember: true,
      });
    } catch (e) {
      console.warn("[BiliIndex] 视频屏蔽规则加载失败", e);
    }
  }

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

  function findAuthorLink(card) {
    if (!(card instanceof Element)) return null;
    for (const sel of AUTHOR_LINK_SEL) {
      const links = card.querySelectorAll(sel);
      for (const a of links) {
        if (!(a instanceof HTMLAnchorElement)) continue;
        const mid = extractMid(a.getAttribute("href") || a.href);
        if (!mid) continue;
        if (a.querySelector("img") && !(a.textContent || "").trim()) continue;
        return a;
      }
    }
    return null;
  }

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

  function findSearchUpBlock(el) {
    if (!(el instanceof Element)) return null;
    const block = el.closest(SEARCH_UP_BLOCK_SEL);
    return block instanceof HTMLElement ? block : null;
  }

  function resolveUpMidFromBlock(block) {
    if (!(block instanceof HTMLElement)) return null;
    const cached = block.dataset.biliindexUpMid;
    if (cached && /^\d+$/.test(cached)) return cached;
    const headerLinks = block.querySelectorAll(
      "a[href*='space.bilibili.com'], a[href*='/space/']"
    );
    for (const a of headerLinks) {
      if (!(a instanceof HTMLAnchorElement)) continue;
      const mid = extractMid(a.getAttribute("href") || a.href);
      if (mid) {
        block.dataset.biliindexUpMid = mid;
        return mid;
      }
    }
    return null;
  }

  function resolveUpMidFromSearchBlock(card) {
    const block = findSearchUpBlock(card);
    if (!block) return null;
    return resolveUpMidFromBlock(block);
  }

  function resolveUpMid(card) {
    if (!(card instanceof HTMLElement)) return null;
    if (card.dataset.biliindexForceUpMid && /^\d+$/.test(card.dataset.biliindexForceUpMid)) {
      return card.dataset.biliindexForceUpMid;
    }
    const bar = card.querySelector(".biliindex-bar[data-up-mid]");
    const fromBar = bar?.dataset?.upMid;
    if (fromBar && /^\d+$/.test(String(fromBar))) return String(fromBar);
    const fromSearchBlock = resolveUpMidFromSearchBlock(card);
    if (fromSearchBlock) return fromSearchBlock;
    const link = findAuthorLink(card);
    if (link) {
      const mid = extractMid(link.getAttribute("href") || link.href);
      if (mid) return mid;
    }
    return null;
  }

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

  function expandVideoCard(card) {
    card.classList.add("biliindex-video-block-expanded");
    card.dataset.biliindexVideoExpanded = "1";
    card.querySelector(`.${OVERLAY_CLASS}`)?.remove();
  }

  function removeVideoBlock(card) {
    card.classList.remove("biliindex-video-block-expanded");
    card.dataset.biliindexVideoBlocked = "0";
    delete card.dataset.biliindexVideoExpanded;
    card.querySelector(`.${OVERLAY_CLASS}`)?.remove();
    const host = card.querySelector(`.${COVER_HOST_CLASS}`);
    host?.classList.remove(COVER_HOST_CLASS);
  }

  function ensureVideoOverlay(card, coverHost) {
    if (card.dataset.biliindexVideoExpanded === "1") return;
    coverHost.classList.add(COVER_HOST_CLASS);
    let overlay = coverHost.querySelector(`:scope > .${OVERLAY_CLASS}`);
    if (!overlay) {
      overlay = document.createElement("div");
      overlay.className = OVERLAY_CLASS;
      const msg = document.createElement("span");
      msg.className = "biliindex-video-block-msg";
      msg.textContent = BLOCK_MSG;
      overlay.appendChild(msg);
      coverHost.appendChild(overlay);
      const onExpand = (e) => {
        e.preventDefault();
        e.stopPropagation();
        expandVideoCard(card);
      };
      overlay.addEventListener("dblclick", onExpand);
      overlay.addEventListener("click", (e) => e.stopPropagation());
    }
    card.dataset.biliindexVideoBlocked = "1";
  }

  function applyBlockToCard(card, forcedUpMid) {
    if (!(card instanceof HTMLElement)) return;
    if (card.closest?.(SKIP_ZONE_SEL)) return;
    if (card.dataset.biliindexVideoExpanded === "1") return;

    const upMid = forcedUpMid || resolveUpMid(card);
    if (!upMid) return;

    const display = displayCache.has(upMid) ? displayCache.get(upMid) : null;
    const bar = card.querySelector(".biliindex-bar[data-up-mid]");

    if (!shouldBlockUp(display, bar)) {
      if (card.dataset.biliindexVideoBlocked === "1") removeVideoBlock(card);
      return;
    }

    const coverHost = findCoverHost(card);
    if (!coverHost) return;

    ensureVideoOverlay(card, coverHost);
  }

  function collectSearchUpVideoCards() {
    const cards = [];
    const seen = new Set();
    const add = (el, upMid) => {
      if (!(el instanceof HTMLElement) || seen.has(el)) return;
      if (el.closest?.(SKIP_ZONE_SEL)) return;
      seen.add(el);
      if (upMid) el.dataset.biliindexForceUpMid = upMid;
      cards.push(el);
    };

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

    for (const block of blocks) {
      const upMid = resolveUpMidFromBlock(block);
      if (!upMid) continue;
      block.querySelectorAll(VIDEO_CARD_SEL).forEach((el) => add(el, upMid));
      block.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => {
        if (!(a instanceof HTMLAnchorElement)) return;
        const wrap =
          a.closest(".bili-video-card, .video-page-card-small, .video-list-card") || a;
        add(wrap, upMid);
      });
    }
    return cards;
  }

  function collectVideoCards() {
    const cards = [];
    const seen = new Set();
    const add = (el) => {
      if (!(el instanceof HTMLElement) || seen.has(el)) return;
      if (el.closest?.(SKIP_ZONE_SEL)) return;
      seen.add(el);
      cards.push(el);
    };

    for (const rsel of VIDEO_SCAN_ROOTS) {
      document.querySelectorAll(rsel).forEach((root) => {
        if (root.matches?.(VIDEO_CARD_SEL)) add(root);
        root.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
      });
    }
    collectSearchUpVideoCards().forEach((c) => add(c));

    if (isSearchSite()) {
      document.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
    } else if (!cards.length) {
      document.querySelectorAll(VIDEO_CARD_SEL).forEach(add);
    }
    return cards;
  }

  async function processVideoCards(cards) {
    if (!blockReady) return;
    const mids = [];
    for (const card of cards) {
      const upMid = resolveUpMid(card);
      if (!upMid || card.dataset.biliindexVideoExpanded === "1") continue;
      if (!displayCache.has(upMid) && !scanQueued.has(upMid)) {
        mids.push(upMid);
        scanQueued.add(upMid);
      }
    }
    if (mids.length) await fetchDisplaysBatch(mids);
    for (const card of cards) {
      applyBlockToCard(card, card.dataset.biliindexForceUpMid || null);
    }
  }

  async function processSearchUpBlocks() {
    if (!blockReady || !isSearchSite()) return;
    const byBlock = new Map();
    document.querySelectorAll(SEARCH_UP_BLOCK_SEL).forEach((block) => {
      const upMid = resolveUpMidFromBlock(block);
      if (!upMid) return;
      if (!byBlock.has(upMid)) byBlock.set(upMid, []);
      byBlock.get(upMid).push(block);
    });

    const mids = [...byBlock.keys()].filter((m) => !displayCache.has(m));
    if (mids.length) await fetchDisplaysBatch(mids);

    for (const [upMid, blocks] of byBlock) {
      const display = displayCache.get(upMid);
      if (!shouldBlockDisplay(display)) continue;
      for (const block of blocks) {
        block.querySelectorAll(VIDEO_LINK_SEL).forEach((a) => {
          if (!(a instanceof HTMLAnchorElement)) return;
          const card =
            a.closest(".bili-video-card, .video-page-card-small, .video-list-card") || a;
          card.dataset.biliindexForceUpMid = upMid;
          applyBlockToCard(card, upMid);
        });
        block.querySelectorAll(VIDEO_CARD_SEL).forEach((card) => {
          card.dataset.biliindexForceUpMid = upMid;
          applyBlockToCard(card, upMid);
        });
      }
    }
  }

  function scanVideos() {
    if (!blockReady) return;
    void processSearchUpBlocks();
    const cards = collectVideoCards();
    if (cards.length) void processVideoCards(cards);
  }

  let scanTimer = null;
  function scheduleScan() {
    clearTimeout(scanTimer);
    scanTimer = setTimeout(scanVideos, 400);
  }

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

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area !== "local") return;
    if (changes.blockTagCodes || changes.blockIsMember) {
      void loadBlockRules().then(() => scheduleScan());
    }
  });

  async function init() {
    if (typeof BiliIndex === "undefined") return;
    await loadBlockRules();
    if (!blockReady) return;
    scheduleScan();
    for (const ms of [600, 1500, 3000, 5500]) {
      setTimeout(scanVideos, ms);
    }
    const obs = new MutationObserver(() => scheduleScan());
    let attached = false;
    for (const sel of VIDEO_SCAN_ROOTS) {
      document.querySelectorAll(sel).forEach((el) => {
        attached = true;
        obs.observe(el, { childList: true, subtree: true });
      });
    }
    if (isSearchSite() || !attached) {
      obs.observe(document.body, { childList: true, subtree: true });
    }
    if (isSearchSite()) {
      console.info("[BiliIndex] 搜索页视频屏蔽监听已启动");
    }
  }

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