(function () {
  const NETWORK_EVENT = "__PDD_30D_REFUND_NETWORK__";
  const QUERY_EVENT = "__PDD_30D_REFUND_QUERY__";
  const QUERY_RESULT_EVENT = "__PDD_30D_REFUND_QUERY_RESULT__";
  const STORAGE_KEY = "pdd30dRefundAmountRateStateV2";
  const PRODUCT_API_PATH = "/sydney/api/goodsDataShow/queryGoodsDetailVOListForMMS";
  const AFTER_SALES_API_PATH = "/sydney/api/mallCoreData/queryGoodsAfterSales";
  const RATE_BADGE_CLASS = "pdd-30d-refund-rate-badge";
  const AMOUNT_BADGE_CLASS = "pdd-30d-refund-amount-badge";
  const STATUS_ID = "pdd-30d-refund-status";
  const exporterDecodeCache = new Map();
  const REFUND_CACHE_TTL_MS = 60 * 60 * 1000;

  const state = {
    productByGoodsId: {},
    refundByCacheKey: {},
    pendingByRequestId: {},
    queuedGoodsIds: [],
    activeGoodsId: "",
    renderTimer: null,
    queueTimer: null,
    observer: null,
    status: ""
  };

  init().catch((error) => {
    console.error("[PDD30日退款率] 初始化失败", error);
  });

  async function init() {
    await hydrateState();
    window.addEventListener(NETWORK_EVENT, handleNetworkEvent);
    window.addEventListener(QUERY_RESULT_EVENT, handleQueryResult);
    observeDomChanges();
    scheduleRender();
  }

  async function hydrateState() {
    const stored = await storageGet(STORAGE_KEY);
    if (!stored) {
      return;
    }
    state.productByGoodsId = stored.productByGoodsId || {};
    state.refundByCacheKey = stored.refundByCacheKey || {};
  }

  async function persistState() {
    await storageSet(STORAGE_KEY, {
      productByGoodsId: state.productByGoodsId,
      refundByCacheKey: state.refundByCacheKey
    });
  }

  async function handleNetworkEvent(event) {
    const detail = event && event.detail;
    if (!detail || typeof detail.responseText !== "string" || !detail.url) {
      return;
    }

    const payload = safeJsonParse(detail.responseText);
    if (!payload || payload.success === false) {
      return;
    }

    if (detail.url.includes(PRODUCT_API_PATH)) {
      const products = extractProductRecords(payload);
      if (products.length) {
        products.forEach((record) => {
          state.productByGoodsId[record.goodsId] = record;
        });
        await persistState();
        setStatus(`已识别商品数据 ${products.length} 个`);
        scheduleRender();
      }
      return;
    }

    if (detail.url.includes(AFTER_SALES_API_PATH)) {
      const refund = extractRefundRecord(payload);
      if (refund) {
        const cacheKey = buildRefundCacheKey(refund.goodsId);
        if (cacheKey) {
          state.refundByCacheKey[cacheKey] = refund;
        }
        await persistState();
        scheduleRender();
      }
    }
  }

  async function handleQueryResult(event) {
    const detail = event && event.detail;
    if (!detail || !detail.requestId) {
      return;
    }

    const pending = state.pendingByRequestId[detail.requestId];
    if (pending) {
      delete state.pendingByRequestId[detail.requestId];
    }

    state.activeGoodsId = "";

    if (!detail.ok) {
      setStatus(`售后接口失败 ${detail.goodsId || ""} ${detail.status || ""}`.trim());
      scheduleQueue(5000);
      return;
    }

    const payload = safeJsonParse(detail.responseText);
    const refund = extractRefundRecord(payload);
    if (refund) {
      const cacheKey = pending?.cacheKey || buildRefundCacheKey(refund.goodsId);
      if (cacheKey) {
        state.refundByCacheKey[cacheKey] = refund;
      }
      await persistState();
      setStatus(`已更新 ${refund.goodsId} 退款金额`);
      scheduleRender();
      scheduleQueue(700);
      return;
    }

    setStatus(`售后接口未识别 ${detail.goodsId || ""}`.trim());
    scheduleQueue(2000);
  }

  function extractProductRecords(payload) {
    const list = getByPath(payload, "result.goodsDetailList") ||
      getByPath(payload, "data.goodsDetailList") ||
      payload.goodsDetailList ||
      [];

    if (!Array.isArray(list)) {
      return [];
    }

    return list
      .map((item) => {
        const goodsId = String(item && item.goodsId ? item.goodsId : "");
        const dealAmount = pickDealAmount(item);
        if (!goodsId) {
          return null;
        }

        return {
          goodsId,
          goodsName: item.goodsName || "",
          dealAmount,
          updatedAt: Date.now()
        };
      })
      .filter(Boolean);
  }

  function extractRefundRecord(payload) {
    const result = payload && payload.result;
    if (!result || !result.goodsId) {
      return null;
    }

    const refundAmountCents = toNumber(result.rfSucOrdrAmt1mV2) ??
      toNumber(result.rfSucOrdrAmt1m);
    if (refundAmountCents == null) {
      return null;
    }

    return {
      goodsId: String(result.goodsId),
      goodsName: result.goodsName || "",
      refundAmount: refundAmountCents / 100,
      refundAmountCents,
      refundOrderCount: toNumber(result.rfSucOrdrCnt1m),
      statDate: result.statDate || "",
      updatedAt: Date.now()
    };
  }

  function pickDealAmount(item) {
    if (!item || typeof item !== "object") {
      return null;
    }

    const keys = [
      "payOrdrAmt",
      "payOrderAmt",
      "payOrdAmt"
    ];

    for (const key of keys) {
      if (key in item) {
        const value = toNumber(item[key]);
        if (value != null) {
          return value;
        }
      }
    }
    return null;
  }

  function observeDomChanges() {
    if (state.observer) {
      state.observer.disconnect();
    }

    state.observer = new MutationObserver(() => {
      scheduleRender();
    });

    state.observer.observe(document.documentElement || document.body, {
      childList: true,
      subtree: true
    });
  }

  function scheduleRender() {
    if (state.renderTimer) {
      window.clearTimeout(state.renderTimer);
    }
    state.renderTimer = window.setTimeout(render, 180);
  }

  function render() {
    if (!isProductPage()) {
      removeBadges();
      removeStatus();
      return;
    }

    if (!isThirtyDayMode()) {
      removeBadges();
      setStatus("仅在 30日 口径下显示金额退款率");
      return;
    }

    removeBadges();

    const idNodes = collectGoodsIdNodes();
    let displayed = 0;
    let waiting = 0;

    idNodes.forEach(({ goodsId, anchor, row }) => {
      const product = state.productByGoodsId[goodsId];
      const cacheKey = buildRefundCacheKey(goodsId);
      const refund = cacheKey ? getFreshRefundRecord(cacheKey) : null;

      if (product && !refund) {
        enqueueRefundQuery(goodsId, cacheKey);
        waiting += 1;
        return;
      }

      const visibleDealAmount = pickVisibleDealAmount(row);
      const dealAmount = visibleDealAmount ?? product?.dealAmount;

      if (!product || !refund || dealAmount == null || dealAmount <= 0) {
        return;
      }

      const rate = refund.refundAmount / dealAmount;
      addRateBadge(anchor, goodsId, rate);
      addAmountBadge(row, goodsId, refund.refundAmount);
      displayed += 1;
    });

    if (waiting) {
      scheduleQueue(500);
    }

    setStatus(displayed ? `已显示 ${displayed}/${idNodes.length} 个商品退款率` : "等待商品数据接口加载");
  }

  function removeBadges() {
    document.querySelectorAll(`.${RATE_BADGE_CLASS}, .${AMOUNT_BADGE_CLASS}`).forEach((node) => {
      node.remove();
    });
  }

  function addRateBadge(anchor, goodsId, rate) {
    if (!anchor || anchor.parentElement?.querySelector(`.${RATE_BADGE_CLASS}[data-goods-id="${goodsId}"]`)) {
      return;
    }

    const badge = document.createElement("div");
    badge.className = RATE_BADGE_CLASS;
    badge.dataset.goodsId = goodsId;
    badge.style.cssText = [
      "margin-top:4px",
      "font-size:12px",
      "line-height:1.5",
      "color:#cf1322",
      "background:rgba(255,77,79,0.08)",
      "border:1px solid rgba(255,77,79,0.22)",
      "border-radius:4px",
      "padding:3px 6px",
      "display:inline-block",
      "white-space:nowrap"
    ].join(";");
    badge.textContent = `退款率 ${formatPercent(rate)}`;
    anchor.insertAdjacentElement("afterend", badge);
  }

  function addAmountBadge(row, goodsId, refundAmount) {
    const amountCell = findDealAmountCell(row);
    if (!amountCell || amountCell.querySelector(`.${AMOUNT_BADGE_CLASS}[data-goods-id="${goodsId}"]`)) {
      return;
    }

    const badge = document.createElement("div");
    badge.className = AMOUNT_BADGE_CLASS;
    badge.dataset.goodsId = goodsId;
    badge.style.cssText = [
      "margin-top:8px",
      "font-size:12px",
      "line-height:1.5",
      "color:#cf1322",
      "background:rgba(255,77,79,0.08)",
      "border:1px solid rgba(255,77,79,0.22)",
      "border-radius:4px",
      "padding:3px 6px",
      "display:inline-block",
      "white-space:nowrap"
    ].join(";");
    badge.textContent = `退款金额 ¥${formatAmount(refundAmount)}`;
    amountCell.appendChild(badge);
  }

  function enqueueRefundQuery(goodsId, cacheKey) {
    if (!cacheKey || getFreshRefundRecord(cacheKey) || state.queuedGoodsIds.includes(goodsId)) {
      return;
    }
    if (Object.values(state.pendingByRequestId).some((pending) => pending.cacheKey === cacheKey)) {
      return;
    }
    state.queuedGoodsIds.push(goodsId);
  }

  function scheduleQueue(delay) {
    if (state.queueTimer) {
      return;
    }
    state.queueTimer = window.setTimeout(processQueue, delay);
  }

  function processQueue() {
    state.queueTimer = null;
    if (!isProductPage() || !isThirtyDayMode() || state.activeGoodsId) {
      return;
    }

    const goodsId = state.queuedGoodsIds.shift();
    const cacheKey = buildRefundCacheKey(goodsId);
    if (!goodsId || !cacheKey || getFreshRefundRecord(cacheKey)) {
      if (state.queuedGoodsIds.length) {
        scheduleQueue(300);
      }
      return;
    }

    const requestId = `pdd30d-${Date.now()}-${Math.random().toString(16).slice(2)}`;
    state.activeGoodsId = goodsId;
    state.pendingByRequestId[requestId] = { goodsId, cacheKey, startedAt: Date.now() };
    setStatus(`正在获取 ${goodsId} 退款金额`);
    window.dispatchEvent(new CustomEvent(QUERY_EVENT, {
      detail: {
        requestId,
        goodsId
      }
    }));
  }

  function collectGoodsIdNodes() {
    const tableRows = Array.from(
      document.querySelectorAll('tr[data-testid="beast-core-table-body-tr"]')
    ).filter((row) => isVisibleElement(row));

    if (tableRows.length) {
      return tableRows
        .map((row) => {
          const goodsId = extractGoodsIdFromRow(row);
          if (!goodsId) {
            return null;
          }
          return {
            goodsId,
            anchor: findGoodsIdAnchorInRow(row, goodsId),
            row
          };
        })
        .filter((item) => item && item.anchor);
    }

    const nodes = [];
    const seen = new Set();
    Array.from(document.querySelectorAll("td, div, span, p")).forEach((element) => {
      if (!isVisibleElement(element)) {
        return;
      }
      const match = normalizeText(element.textContent || "").match(/^ID[:：]?\s*(\d{6,})$/);
      if (!match || seen.has(match[1])) {
        return;
      }
      seen.add(match[1]);
      nodes.push({
        goodsId: match[1],
        anchor: element,
        row: element.closest("tr")
      });
    });
    return nodes;
  }

  function extractGoodsIdFromRow(row) {
    const text = normalizeText(row.innerText || row.textContent || "");
    const match = text.match(/ID[:：]\s*(\d{6,})/);
    return match ? match[1] : "";
  }

  function findGoodsIdAnchorInRow(row, goodsId) {
    const exactText = `ID:${goodsId}`;
    const candidates = Array.from(row.querySelectorAll("div, span, p, td"))
      .filter((element) => isVisibleElement(element))
      .filter((element) => normalizeText(element.textContent || "") === exactText);

    if (candidates.length) {
      return candidates.sort((a, b) => {
        const aRect = a.getBoundingClientRect();
        const bRect = b.getBoundingClientRect();
        return (aRect.width * aRect.height) - (bRect.width * bRect.height);
      })[0];
    }

    return row.querySelector('[class*="list_id"]') || row.firstElementChild || row;
  }

  function findDealAmountCell(row) {
    if (!row) {
      return null;
    }

    const metricCell = row.querySelector(
      '[data-tracking-viewid="cell_metric"][data-tracking-params*="metric=3"]'
    );
    if (metricCell) {
      return metricCell.closest("td") || metricCell;
    }

    const cells = Array.from(row.querySelectorAll(":scope > td"));
    return cells.length >= 3 ? cells[2] : null;
  }

  function pickVisibleDealAmount(row) {
    const amountCell = findDealAmountCell(row);
    if (!amountCell) {
      return null;
    }

    const valueNode =
      amountCell.querySelector('[class*="list_value"] .__spider_font') ||
      amountCell.querySelector('[class*="list_value"]') ||
      amountCell;
    const raw = normalizeText(valueNode.innerText || valueNode.textContent || "");
    if (!raw) {
      return null;
    }

    if (!containsSpiderChars(raw)) {
      return toNumber(raw);
    }

    const decoded = decodeSpiderText(valueNode, raw);
    if (containsSpiderChars(decoded)) {
      return null;
    }
    return toNumber(decoded);
  }

  function containsSpiderChars(text) {
    return /[\uE000-\uF8FF]/.test(String(text || ""));
  }

  function decodeSpiderText(node, rawText) {
    const style = window.getComputedStyle(node);
    const cacheKey = [
      style.fontStyle,
      style.fontWeight,
      style.fontSize,
      style.fontFamily,
      style.letterSpacing
    ].join("|");

    if (!exporterDecodeCache.has(cacheKey)) {
      exporterDecodeCache.set(cacheKey, buildSpiderCharMap(node));
    }

    let charMap = exporterDecodeCache.get(cacheKey);
    const missingChars = getMissingSpiderChars(rawText, charMap);
    if (missingChars.length) {
      charMap = {
        ...charMap,
        ...buildSpiderCharMap(node, missingChars)
      };
      exporterDecodeCache.set(cacheKey, charMap);
    }

    return Array.from(String(rawText || ""))
      .map((char) => charMap[char] || char)
      .join("");
  }

  function buildSpiderCharMap(node, requiredChars = []) {
    const style = window.getComputedStyle(node);
    const font = `${style.fontStyle} ${style.fontWeight} 56px ${style.fontFamily}`;
    const candidates = Array.from("0123456789.,%-+");
    const valueNodes = Array.from(
      document.querySelectorAll('[class*="list_value"] .__spider_font, [class*="list_value"]')
    );
    const uniqueChars = new Set();

    valueNodes.forEach((valueNode) => {
      const text = normalizeText(valueNode.innerText || valueNode.textContent || "");
      Array.from(text).forEach((char) => {
        if (containsSpiderChars(char)) {
          uniqueChars.add(char);
        }
      });
    });
    requiredChars.forEach((char) => {
      if (containsSpiderChars(char)) {
        uniqueChars.add(char);
      }
    });

    const candidateSignatures = new Map();
    candidates.forEach((char) => {
      candidateSignatures.set(char, renderCharSignature(char, font));
    });

    const mapped = {};
    uniqueChars.forEach((char) => {
      const signature = renderCharSignature(char, font);
      let bestChar = "";
      let bestScore = Number.POSITIVE_INFINITY;
      let secondBestScore = Number.POSITIVE_INFINITY;

      candidates.forEach((candidate) => {
        const score = compareSignatures(signature, candidateSignatures.get(candidate));
        if (score < bestScore) {
          secondBestScore = bestScore;
          bestScore = score;
          bestChar = candidate;
        } else if (score < secondBestScore) {
          secondBestScore = score;
        }
      });

      if (bestChar && isSpiderMatchReliable(bestScore, secondBestScore, signature)) {
        mapped[char] = bestChar;
      }
    });

    return mapped;
  }

  function getMissingSpiderChars(rawText, charMap) {
    return Array.from(
      new Set(Array.from(String(rawText || "")).filter((char) => containsSpiderChars(char) && !charMap[char]))
    );
  }

  function renderCharSignature(char, font) {
    const sourceCanvas = document.createElement("canvas");
    const sourceSize = 96;
    sourceCanvas.width = sourceSize;
    sourceCanvas.height = sourceSize;

    const ctx = sourceCanvas.getContext("2d", { willReadFrequently: true });
    if (!ctx) {
      return {
        pixels: new Uint8Array(36 * 36),
        aspectRatio: 0,
        density: 0
      };
    }
    ctx.clearRect(0, 0, sourceSize, sourceSize);
    ctx.fillStyle = "#000";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = font;
    ctx.fillText(char, sourceSize / 2, sourceSize / 2 + 3);

    const sourceImage = ctx.getImageData(0, 0, sourceSize, sourceSize);
    const bounds = findGlyphBounds(sourceImage.data, sourceSize, sourceSize);
    const normalizedSize = 36;

    if (!bounds) {
      return {
        pixels: new Uint8Array(normalizedSize * normalizedSize),
        aspectRatio: 0,
        density: 0
      };
    }

    const normalizedCanvas = document.createElement("canvas");
    normalizedCanvas.width = normalizedSize;
    normalizedCanvas.height = normalizedSize;
    const normalizedCtx = normalizedCanvas.getContext("2d", { willReadFrequently: true });
    if (!normalizedCtx) {
      return {
        pixels: new Uint8Array(normalizedSize * normalizedSize),
        aspectRatio: 0,
        density: 0
      };
    }
    normalizedCtx.clearRect(0, 0, normalizedSize, normalizedSize);

    const innerSize = normalizedSize - 8;
    const scale = Math.min(innerSize / bounds.width, innerSize / bounds.height);
    const drawWidth = Math.max(1, Math.round(bounds.width * scale));
    const drawHeight = Math.max(1, Math.round(bounds.height * scale));
    const offsetX = Math.floor((normalizedSize - drawWidth) / 2);
    const offsetY = Math.floor((normalizedSize - drawHeight) / 2);

    normalizedCtx.drawImage(
      sourceCanvas,
      bounds.left,
      bounds.top,
      bounds.width,
      bounds.height,
      offsetX,
      offsetY,
      drawWidth,
      drawHeight
    );

    const { data } = normalizedCtx.getImageData(0, 0, normalizedSize, normalizedSize);
    const pixels = new Uint8Array(normalizedSize * normalizedSize);
    let activePixels = 0;

    for (let pixelIndex = 0; pixelIndex < pixels.length; pixelIndex += 1) {
      const value = data[pixelIndex * 4 + 3] > 24 ? 1 : 0;
      pixels[pixelIndex] = value;
      activePixels += value;
    }

    return {
      pixels,
      aspectRatio: bounds.width / Math.max(bounds.height, 1),
      density: activePixels / pixels.length
    };
  }

  function compareSignatures(left, right) {
    let diff = 0;
    for (let index = 0; index < left.pixels.length; index += 1) {
      if (left.pixels[index] !== right.pixels[index]) {
        diff += 1;
      }
    }

    diff += Math.round(Math.abs(left.aspectRatio - right.aspectRatio) * 180);
    diff += Math.round(Math.abs(left.density - right.density) * 1200);
    return diff;
  }

  function isSpiderMatchReliable(bestScore, secondBestScore, signature) {
    if (!signature || signature.density <= 0) {
      return false;
    }

    if (bestScore <= 260) {
      return true;
    }

    return bestScore * 1.12 < secondBestScore;
  }

  function findGlyphBounds(data, width, height) {
    let left = width;
    let right = -1;
    let top = height;
    let bottom = -1;

    for (let y = 0; y < height; y += 1) {
      for (let x = 0; x < width; x += 1) {
        const alpha = data[(y * width + x) * 4 + 3];
        if (alpha <= 24) {
          continue;
        }

        left = Math.min(left, x);
        right = Math.max(right, x);
        top = Math.min(top, y);
        bottom = Math.max(bottom, y);
      }
    }

    if (right < left || bottom < top) {
      return null;
    }

    return {
      left,
      top,
      width: right - left + 1,
      height: bottom - top + 1
    };
  }

  function isProductPage() {
    return location.pathname.includes("/sycm/goods_effect");
  }

  function isThirtyDayMode() {
    const range = getStatsDateRange();
    if (range) {
      const start = new Date(`${range.startDate}T00:00:00`);
      const end = new Date(`${range.endDate}T00:00:00`);
      if (!Number.isNaN(start.getTime()) && !Number.isNaN(end.getTime())) {
        const diffDays = Math.round((end.getTime() - start.getTime()) / 86400000) + 1;
        if (diffDays >= 28 && diffDays <= 31) {
          return true;
        }
      }
    }

    return hasActiveThirtyDayTab();
  }

  function buildRefundCacheKey(goodsId) {
    const range = getStatsDateRange();
    if (!range || !goodsId) {
      return "";
    }
    return `${goodsId}|${range.startDate}|${range.endDate}`;
  }

  function getFreshRefundRecord(cacheKey) {
    const record = state.refundByCacheKey[cacheKey];
    if (!record) {
      return null;
    }
    if (record.updatedAt && Date.now() - record.updatedAt > REFUND_CACHE_TTL_MS) {
      delete state.refundByCacheKey[cacheKey];
      persistState().catch(() => {});
      return null;
    }
    return record;
  }

  function getStatsDateRange() {
    const pageText = normalizeText(document.body.textContent || "");
    const match = pageText.match(
      /统计时间[^0-9]{0,10}(\d{4}-\d{2}-\d{2})\s*[~～\-—]+\s*(\d{4}-\d{2}-\d{2})/
    );
    if (!match) {
      return null;
    }
    return {
      startDate: match[1],
      endDate: match[2]
    };
  }

  function hasActiveThirtyDayTab() {
    return Array.from(document.querySelectorAll("button, div, span, a")).some((element) => {
      if (!isVisibleElement(element)) {
        return false;
      }

      const text = normalizeText(element.textContent || "");
      if (text !== "30日") {
        return false;
      }

      const current = element.closest("button, a, div, span") || element;
      const style = window.getComputedStyle(current);
      const className = typeof current.className === "string" ? current.className : "";
      const borderColor = style.borderColor || "";
      const color = style.color || "";

      return (
        /active|selected|checked|current/i.test(className) ||
        borderColor.includes("24, 144, 255") ||
        borderColor.includes("64, 158, 255") ||
        color.includes("24, 144, 255") ||
        color.includes("64, 158, 255")
      );
    });
  }

  function setStatus(text) {
    state.status = text;
    renderStatus();
  }

  function renderStatus() {
    if (!isProductPage()) {
      removeStatus();
      return;
    }

    let node = document.getElementById(STATUS_ID);
    if (!node) {
      node = document.createElement("div");
      node.id = STATUS_ID;
      node.style.cssText = [
        "position:fixed",
        "right:16px",
        "bottom:16px",
        "z-index:2147483647",
        "font-size:12px",
        "line-height:1.4",
        "color:#595959",
        "background:rgba(255,255,255,0.96)",
        "border:1px solid rgba(0,0,0,0.12)",
        "box-shadow:0 4px 14px rgba(0,0,0,0.12)",
        "border-radius:4px",
        "padding:7px 9px",
        "max-width:280px"
      ].join(";");
      document.documentElement.appendChild(node);
    }
    node.textContent = state.status || "30日金额退款率已启用";
  }

  function removeStatus() {
    const node = document.getElementById(STATUS_ID);
    if (node) {
      node.remove();
    }
  }

  function getByPath(root, path) {
    if (!root || !path) {
      return null;
    }
    return String(path).split(".").reduce((current, segment) => {
      if (!current || typeof current !== "object") {
        return null;
      }
      return segment in current ? current[segment] : null;
    }, root);
  }

  function toNumber(value) {
    if (typeof value === "number" && Number.isFinite(value)) {
      return value;
    }
    if (typeof value === "string") {
      const normalized = value.replace(/,/g, "").trim();
      if (!normalized) {
        return null;
      }
      const parsed = Number(normalized.replace(/[^\d.-]/g, ""));
      return Number.isFinite(parsed) ? parsed : null;
    }
    return null;
  }

  function formatPercent(value) {
    if (!Number.isFinite(value)) {
      return "--";
    }
    return `${(value * 100).toFixed(2)}%`;
  }

  function formatAmount(value) {
    if (!Number.isFinite(value)) {
      return "--";
    }
    return value.toLocaleString("zh-CN", {
      minimumFractionDigits: 2,
      maximumFractionDigits: 2
    });
  }

  function normalizeText(text) {
    return String(text || "").replace(/\s+/g, " ").trim();
  }

  function isVisibleElement(element) {
    if (!element || !element.getBoundingClientRect) {
      return false;
    }
    const rect = element.getBoundingClientRect();
    const style = window.getComputedStyle(element);
    return rect.width > 0 &&
      rect.height > 0 &&
      style.display !== "none" &&
      style.visibility !== "hidden" &&
      Number(style.opacity || 1) !== 0;
  }

  function safeJsonParse(text) {
    if (!text || typeof text !== "string") {
      return null;
    }
    try {
      return JSON.parse(text);
    } catch (error) {
      return null;
    }
  }

  function storageGet(key) {
    return new Promise((resolve) => {
      chrome.storage.local.get([key], (result) => {
        resolve(result[key]);
      });
    });
  }

  function storageSet(key, value) {
    return new Promise((resolve) => {
      chrome.storage.local.set({ [key]: value }, resolve);
    });
  }
})();
