/**
 * 节能扫描：汇总弹幕 + 评论区 UID（本页去重），每 200 个不同 UID 提交得 1 积分。
 */
(function () {
  "use strict";

  if (window.self !== window.top) return;
  if (typeof BiliDom === "undefined") {
    console.warn("[BiliIndex] passerby-eco-scan: 缺少 bili-dom.js，已跳过");
    return;
  }

  const COLLECT_MS = 10000;
  const SUBMIT_UID_BATCH = 200;
  const SYNC_MIN_ITEMS = 30;
  const SYNC_INTERVAL_MS = 12000;

  const RENDERER_SEL =
    "bili-comment-renderer, bili-comment-reply-renderer";
  const USER_LINK_SEL =
    "a[href*='space.bilibili.com'], a[href*='/space/']";
  const queryCommentDeep = BiliDom.queryCommentDeep;
  const extractMid = BiliDom.extractMid;

  const DANMAKU_ROOT_SEL = [
    "#danmukuBox",
    "#danmaku",
    ".danmaku-box",
    ".bpx-player-dm-root",
    ".bpx-player-dm-wrap",
    ".player-dm-wrap",
    ".bilibili-player",
    "#bilibili-player",
  ];

  let enabled = false;
  let stopped = true;
  let collecting = false;
  let uploading = false;
  let localScanCount = 0;
  let serverUploadedToday = 0;
  const reportBuffer = [];
  let collectTimer = null;
  let syncTimer = null;
  let statusEl = null;
  let lastPath = "";
  /** 本视频页已发现 UID（跨多次采集去重） */
  const seenMidsOnPage = new Set();
  let lastStatus = {
    progress: 0,
    need: SUBMIT_UID_BATCH,
    earned: 0,
    limit: 100,
    atCap: false,
    unlimited: false,
  };

  function localDayKey() {
    try {
      return new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai" }).format(
        new Date()
      );
    } catch {
      const d = new Date();
      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
    }
  }

  function ecoQuotaCacheEqual(a, b) {
    if (!a || !b) return false;
    return (
      a.day === b.day &&
      a.scan_points_earned === b.scan_points_earned &&
      a.scan_points_limit === b.scan_points_limit &&
      a.scan_points_remaining === b.scan_points_remaining &&
      !!a.eco_at_cap === !!b.eco_at_cap &&
      a.user_points === b.user_points &&
      a.uid_submitted_today === b.uid_submitted_today &&
      a.uid_submit_progress === b.uid_submit_progress &&
      a.uid_submit_need === b.uid_submit_need
    );
  }

  async function persistEcoScanQuotaCache(res) {
    if (!res) return null;
    const prev = (await BiliIndex.storageGet(["passerbyEcoScanStatusCache"]))
      .passerbyEcoScanStatusCache;
    const day = localDayKey();
    const limit = Number(res.scan_points_limit ?? res.eco_points_limit ?? 100);
    const earned = Math.max(
      Number(res.scan_points_earned ?? res.eco_points_earned ?? 0),
      prev && prev.day === day ? Number(prev.scan_points_earned ?? 0) : 0
    );
    const unlimited = !!res.scan_points_unlimited;
    const atCap = unlimited
      ? false
      : !!(
          res.eco_at_cap ||
          res.at_daily_cap ||
          res.scan_points_at_daily_cap ||
          earned >= limit
        );
    const cache = {
      day,
      ts: Date.now(),
      user_points: res.user_points,
      scan_points_unlimited: unlimited,
      scan_points_earned: earned,
      scan_points_limit: limit,
      scan_points_remaining: unlimited ? limit : atCap ? 0 : Math.max(0, limit - earned),
      eco_at_cap: atCap,
      at_daily_cap: atCap,
      uid_submitted_today: res.uid_submitted_today,
      uid_submit_progress: res.uid_submit_progress,
      uid_submit_need: atCap ? 0 : Number(res.uid_submit_need ?? res.need_for_next_point ?? 0),
      uid_batch_size: res.uid_batch_size ?? res.eco_uid_batch_size ?? SUBMIT_UID_BATCH,
    };
    if (prev && ecoQuotaCacheEqual(prev, cache)) {
      return cache;
    }
    await BiliIndex.storageSet({
      lastPasserbyEcoScanResult: { ts: Date.now(), ...res, scan_points_earned: earned },
      passerbyEcoScanStatusCache: cache,
      passerbySharedScanStatusCache: {
        ...cache,
        daily_used: cache.scan_points_earned,
        daily_limit: cache.scan_points_limit,
        daily_remaining: cache.scan_points_remaining,
      },
      passerbyScanQuotaCache: {
        ...cache,
        daily_used: cache.scan_points_earned,
        daily_limit: cache.scan_points_limit,
        daily_remaining: cache.scan_points_remaining,
      },
    });
    return cache;
  }

  function isVideoPage() {
    return /\/video\/(BV[\w]+|av\d+)/i.test(location.pathname || "");
  }

  function isValidEcoMid(mid) {
    if (!mid || !/^\d+$/.test(mid)) return false;
    const n = mid.length;
    return n >= 4 && n <= 16;
  }

  function addMid(mid, nickname) {
    const norm = mid != null ? String(mid).trim() : "";
    if (!isValidEcoMid(norm) || seenMidsOnPage.has(norm)) return;
    seenMidsOnPage.add(norm);
    reportBuffer.push({ up_mid: norm, nickname: nickname || null });
    localScanCount = seenMidsOnPage.size;
  }

  async function hasSession() {
    if (typeof BiliIndex === "undefined" || !BiliIndex.getToken) return false;
    return !!(await BiliIndex.getToken());
  }

  function applyServerStatus(res) {
    if (!res) return;
    if (res.uid_submitted_today != null) {
      serverUploadedToday = Number(res.uid_submitted_today) || 0;
    }
    const limit = Number(res.scan_points_limit ?? res.eco_points_limit ?? 100);
    const earned = Number(res.scan_points_earned ?? res.eco_points_earned ?? 0);
    const unlimited = !!res.scan_points_unlimited;
    const atCap = unlimited
      ? false
      : !!(
          res.eco_at_cap ||
          res.at_daily_cap ||
          res.scan_points_at_daily_cap ||
          earned >= limit
        );
    lastStatus = {
      progress: Number(res.uid_submit_progress ?? lastStatus.progress) || 0,
      need: atCap ? 0 : Number(res.uid_submit_need ?? res.need_for_next_point ?? SUBMIT_UID_BATCH),
      earned,
      limit,
      atCap,
      unlimited,
    };
  }

  function formatStatusLine(extra) {
    const batchSize = SUBMIT_UID_BATCH;
    const { progress, need, earned, limit, atCap, unlimited } = lastStatus;
    let msg =
      `节能扫描 · 本页 ${localScanCount} 人 · 已上传 ${serverUploadedToday} 人` +
      ` · 待上传 ${reportBuffer.length}` +
      (lastStatus.unlimited
        ? ` · 扫描积分 ${earned}（不限次）`
        : ` · 扫描积分 ${earned}/${limit}`);
    if (atCap) {
      msg += " · 今日已满";
    } else if (need > 0) {
      msg += ` · 距 +1 分还差 ${need} 条（进度 ${progress}/${batchSize}）`;
    }
    if (extra) msg += ` · ${extra}`;
    return msg;
  }

  async function uploadBufferToServer(opts = {}) {
    const { force = false } = opts;
    if (uploading || !enabled || stopped) return null;
    if (!reportBuffer.length) return null;
    if (!force && reportBuffer.length < SYNC_MIN_ITEMS) return null;
    if (typeof BiliIndex === "undefined" || !BiliIndex.passerbyEcoReport) return null;

    const token = await BiliIndex.getToken();
    if (!token) {
      stopEco("未登录，已停止节能扫描");
      return null;
    }

    uploading = true;
    const batch = reportBuffer.splice(0, reportBuffer.length);
    if (!force) {
      setStatus(formatStatusLine("同步中…"));
    } else {
      setStatus(`提交中…（${batch.length} 条）`);
    }

    try {
      const res = await BiliIndex.passerbyEcoReport(batch);
      applyServerStatus(res);
      let tail = res.message || `已上传 ${batch.length} 条`;
      if (res.points_earned) tail += `（+${res.points_earned} 积分）`;
      setStatus(formatStatusLine(tail));

      await persistEcoScanQuotaCache(res);

      if (
        !res.scan_points_unlimited &&
        (res.eco_at_cap || res.at_daily_cap || res.scan_points_remaining === 0)
      ) {
        await chrome.storage.local.set({
          passerbyScanMode: "",
          passerbyEcoScanEnabled: false,
        });
        stopEco("今日扫描积分已达上限");
      }
      return res;
    } catch (e) {
      reportBuffer.unshift(...batch);
      const msg = e?.message || String(e);
      setStatus(formatStatusLine(`上传失败：${msg}`));
      if (/会员|403|上限|已达|429/.test(msg)) {
        try {
          const st = await BiliIndex.fetchPasserbyEcoScanStatus?.();
          if (st) await persistEcoScanQuotaCache(st);
        } catch {
          /* ignore */
        }
        await chrome.storage.local.set({
          passerbyScanMode: "",
          passerbyEcoScanEnabled: false,
        });
        stopEco(msg);
      }
      return null;
    } finally {
      uploading = false;
    }
  }

  function nicknameFromCommentEl(el) {
    const d = el?.__data;
    if (!d) return null;
    const name =
      d.member?.uname ??
      d.uname ??
      d.name ??
      d.reply?.member?.uname ??
      d.user?.name;
    const s = name != null ? String(name).trim() : "";
    return s || null;
  }

  function collectCommentMids() {
    const users = new Map();
    for (const el of queryCommentDeep(RENDERER_SEL)) {
      let mid =
        BiliDom.midFromCommentElement?.(el) ??
        el?.__data?.mid ??
        el?.__data?.member?.mid ??
        el?.__data?.user?.mid ??
        el?.__data?.reply?.member?.mid;
      mid = mid != null && mid !== 0 && mid !== "0" ? String(mid) : null;
      let nickname = nicknameFromCommentEl(el);
      if (!mid) {
        const link = el.querySelector?.(USER_LINK_SEL);
        mid = extractMid(link?.getAttribute?.("href") || link?.href);
        if (!nickname && link) nickname = (link.textContent || "").trim() || null;
      }
      if (!isValidEcoMid(mid)) continue;
      if (!users.has(mid)) {
        users.set(mid, nickname || null);
      }
    }
    if (!users.size) {
      for (const link of queryCommentDeep(USER_LINK_SEL)) {
        const mid = extractMid(link.getAttribute("href") || link.href);
        if (!isValidEcoMid(mid)) continue;
        if (!users.has(mid)) {
          users.set(mid, (link.textContent || "").trim() || null);
        }
      }
    }
    for (const [mid, nickname] of users) {
      addMid(mid, nickname);
    }
  }

  function queryDanmakuDeep(selector) {
    const out = [];
    const seen = new Set();
    const roots = [];
    for (const sel of DANMAKU_ROOT_SEL) {
      try {
        document.querySelectorAll(sel).forEach((el) => {
          if (!seen.has(el)) {
            seen.add(el);
            roots.push(el);
          }
        });
      } catch {
        /* ignore */
      }
    }
    for (const root of roots) {
      try {
        root.querySelectorAll?.(selector).forEach((el) => {
          if (!seen.has(el)) {
            seen.add(el);
            out.push(el);
          }
        });
      } catch {
        /* ignore */
      }
    }
    return out;
  }

  function collectDanmakuMids() {
    for (const link of queryDanmakuDeep(USER_LINK_SEL)) {
      const mid = extractMid(link.getAttribute("href") || link.href);
      addMid(mid, (link.textContent || "").trim() || null);
    }
    for (const el of queryDanmakuDeep("[data-mid], [data-uid], [data-user-id]")) {
      const raw =
        el.getAttribute("data-mid") ||
        el.getAttribute("data-uid") ||
        el.getAttribute("data-user-id") ||
        el.dataset?.mid;
      const mid = raw != null ? String(raw).trim() : "";
      if (isValidEcoMid(mid)) addMid(mid, null);
    }
  }

  function collectAllMids() {
    collectCommentMids();
    collectDanmakuMids();
  }

  function setStatus(_text) {
    if (!enabled || stopped) {
      statusEl?.remove();
      statusEl = null;
    }
    /* 节能扫描在后台静默运行，不在页面上展示状态条（避免遮挡评论区） */
  }

  async function getScanMode() {
    const st = await chrome.storage.local.get([
      "passerbyScanMode",
      "passerbyEcoScanEnabled",
    ]);
    if (st.passerbyScanMode === "eco") return "eco";
    if (st.passerbyEcoScanEnabled) return "eco";
    return st.passerbyScanMode || "";
  }

  async function runCollect() {
    if (collecting || !enabled || stopped || !isVideoPage()) return;
    if (!(await hasSession())) {
      stopEco("未登录，已停止节能扫描");
      return;
    }
    if (typeof BiliIndex !== "undefined" && BiliIndex.isCommentsSectionLoading?.()) {
      return;
    }
    collecting = true;
    try {
      collectAllMids();
      setStatus(formatStatusLine());
      if (reportBuffer.length >= SYNC_MIN_ITEMS) {
        void uploadBufferToServer();
      }
      if (reportBuffer.length >= SUBMIT_UID_BATCH) {
        void uploadBufferToServer({ force: true });
      }
    } finally {
      collecting = false;
    }
  }

  function clearTimers() {
    if (collectTimer) {
      clearInterval(collectTimer);
      collectTimer = null;
    }
    if (syncTimer) {
      clearInterval(syncTimer);
      syncTimer = null;
    }
  }

  async function stopEco(reason) {
    if (reportBuffer.length && !uploading) {
      await uploadBufferToServer({ force: true });
    }
    stopped = true;
    enabled = false;
    clearTimers();
    localScanCount = 0;
    serverUploadedToday = 0;
    seenMidsOnPage.clear();
    reportBuffer.length = 0;
    if (reason) {
      chrome.storage.local.set({
        lastPasserbyEcoScanResult: { ts: Date.now(), message: reason, stopped: true },
      });
    }
    statusEl?.remove();
    statusEl = null;
  }

  async function startEco() {
    if (!isVideoPage()) {
      await stopEco();
      return;
    }
    if (!(await hasSession())) {
      await stopEco("未登录，已停止节能扫描");
      return;
    }
    stopped = false;
    statusEl?.remove();
    statusEl = null;
    try {
      const st = await BiliIndex.fetchPasserbyEcoScanStatus?.();
      applyServerStatus(st);
    } catch {
      /* ignore */
    }
    setStatus(formatStatusLine("已开启"));
    void runCollect();
    if (!collectTimer) {
      collectTimer = setInterval(() => void runCollect(), COLLECT_MS);
    }
    if (!syncTimer) {
      syncTimer = setInterval(() => {
        void uploadBufferToServer({ force: reportBuffer.length >= SYNC_MIN_ITEMS });
      }, SYNC_INTERVAL_MS);
    }
  }

  async function applyEnabledFlag() {
    const mode = await getScanMode();
    if (mode !== "eco") {
      await stopEco();
      return;
    }
    const st = await chrome.storage.local.get(["passerbyEcoScanEnabled"]);
    enabled = !!st.passerbyEcoScanEnabled;
    if (!enabled) {
      await stopEco();
      return;
    }
    try {
      const st =
        typeof BiliIndex.fetchPasserbyEcoScanStatus === "function"
          ? await BiliIndex.fetchPasserbyEcoScanStatus()
          : await BiliIndex.fetchMembership();
      if (!st?.is_full_member) {
        await chrome.storage.local.set({
          passerbyScanMode: "",
          passerbyEcoScanEnabled: false,
        });
        await stopEco(
          st?.is_member
            ? "节能扫描需月卡会员（4000 积分兑换）"
            : "节能扫描为月卡会员功能"
        );
        return;
      }
    } catch {
      await stopEco();
      return;
    }
    const path = `${location.pathname}${location.search}`;
    if (path !== lastPath) {
      if (reportBuffer.length) {
        await uploadBufferToServer({ force: true });
      }
      lastPath = path;
      localScanCount = 0;
      seenMidsOnPage.clear();
      reportBuffer.length = 0;
    }
    void startEco();
  }

  window.addEventListener("pagehide", () => {
    if (reportBuffer.length) {
      void uploadBufferToServer({ force: true });
    }
  });

  document.addEventListener("biliindex:comments-scanned", () => {
    if (enabled && !stopped) void runCollect();
  });

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area !== "local") return;
    if (
      changes.passerbyScanMode ||
      changes.passerbyEcoScanEnabled ||
      changes.passerbyScanEnabled ||
      changes.accessToken
    ) {
      void applyEnabledFlag();
    }
  });

  void applyEnabledFlag();
})();
