(() => {
  "use strict";
  if (globalThis.__sdmStableCollector) return;
  globalThis.__sdmStableCollector = true;

  const clean = value => String(value ?? "").replace(/\s+/g, " ").trim();
  const multiline = value => String(value ?? "").replace(/\r\n?/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
  const unique = values => [...new Set(values.map(clean).filter(Boolean))];
  const isColorDimension = name => /colou?r|\u989c\u8272|\u984f\u8272|farbe|couleur|colore|coloris|kleur/i.test(clean(name));
  const escapeRegExp = value => String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

  function stripBrand(value, brand, keepLines = false) {
    const source = String(value || "");
    if (!brand) return keepLines ? multiline(source) : clean(source);
    const result = source
      .replace(new RegExp(`\\b${escapeRegExp(brand)}\\b`, "gi"), "")
      .replace(/\b(?:brand|品牌)\s*[:：]\s*/gi, "")
      .replace(/\s+([,.;:，。；：])/g, "$1");
    return keepLines ? multiline(result) : clean(result);
  }

  function firstText(selectors) {
    for (const selector of selectors) {
      const node = document.querySelector(selector);
      const value = node?.getAttribute("content") || node?.textContent;
      if (clean(value)) return clean(value);
    }
    return "";
  }

  function extractProductHighlight() {
    for (const selector of [
      "#titleSection .a-size-base",
      "#titleSection span.a-color-secondary",
    ]) {
      const text = clean(document.querySelector(selector)?.textContent);
      if (text && text.length >= 15 && text.length <= 300) return text;
    }
    for (const row of document.querySelectorAll("#productOverview_feature_div tr, #productDetails_techSpec_section_1 tr")) {
      const cells = [...row.querySelectorAll("th, td")];
      const label = clean(cells[0]?.textContent).replace(/[:：]\s*$/, "");
      if (!/^product highlights?$|^special features?$|^key features?$|^产品亮点$|^特殊功能$/i.test(label)) continue;
      const text = clean(cells.slice(1).map(cell => cell.textContent).join(" "));
      if (text && text.length <= 300) return text;
    }
    return "";
  }

  function extractSpecificationRows() {
    const rows = [];
    const seen = new Set();
    document.querySelectorAll([
      "#productOverview_feature_div tr",
      "#productDetails_techSpec_section_1 tr",
      "#productDetails_detailBullets_sections1 tr",
    ].join(",")).forEach(row => {
      const cells = [...row.querySelectorAll("th, td")];
      if (cells.length < 2) return;
      const label = clean(cells[0].textContent).replace(/[:：]\s*$/, "");
      const value = clean(cells.slice(1).map(cell => cell.textContent).join(" "));
      const key = `${label.toLowerCase()}|${value.toLowerCase()}`;
      if (!label || !value || seen.has(key)) return;
      seen.add(key);
      rows.push({ label, value });
    });
    return rows.slice(0, 100);
  }

  function specificationValue(rows, patterns) {
    return rows.find(row => patterns.some(pattern => pattern.test(row.label)))?.value || "";
  }

  function parseItemDimensions(value) {
    const text = clean(value);
    const unitMatch = text.match(/\b(mm|millimet(?:er|re)s?|cm|centimet(?:er|re)s?|m|met(?:er|re)s?|in|inch(?:es)?|ft|foot|feet)\.?\b/i);
    const unitText = clean(unitMatch?.[1]).toLowerCase().replace(/\./g, "");
    const unit = /^(?:mm|millimet)/.test(unitText) ? "millimeters"
      : /^(?:cm|centimet)/.test(unitText) ? "centimeters"
      : /^(?:m|met)/.test(unitText) ? "meters"
      : /^(?:in|inch)/.test(unitText) ? "inches"
      : /^(?:ft|foot|feet)/.test(unitText) ? "feet" : "";
    if (!unit) return null;
    const labeled = {};
    for (const match of text.matchAll(/(\d+(?:[.,]\d+)?)\s*([DLWH])(?=\s*(?:x|×|by|\b))/gi)) {
      const number = Number(match[1].replace(",", "."));
      const axis = match[2].toUpperCase();
      if (number > 0) labeled[axis === "D" || axis === "L" ? "depth" : axis === "W" ? "width" : "height"] = number;
    }
    if (labeled.depth && labeled.width && labeled.height) return { ...labeled, unit };
    const plain = text.match(/^\s*(\d+(?:[.,]\d+)?)\s*(?:x|×|by)\s*(\d+(?:[.,]\d+)?)\s*(?:x|×|by)\s*(\d+(?:[.,]\d+)?)/i);
    if (!plain) return null;
    const numbers = plain.slice(1).map(number => Number(number.replace(",", ".")));
    return numbers.every(number => number > 0) ? { depth: numbers[0], width: numbers[1], height: numbers[2], unit } : null;
  }

  function extractPhysicalSpecifications(rows) {
    const productDimensions = specificationValue(rows, [
      /^product dimensions?$/i, /^item dimensions?$/i, /^dimensions?$/i,
      /^produktabmessungen$/i, /^dimensions? (?:du produit|de l['’]article)$/i,
      /^dimensiones? (?:del producto|del art[ií]culo)$/i, /^dimensioni (?:del prodotto|dell['’]articolo)$/i,
      /^商品尺寸$/i, /^产品尺寸$/i,
    ]);
    return {
      productDimensions,
      dimensions: parseItemDimensions(productDimensions),
      material: specificationValue(rows, [/^frame material$/i, /^base material$/i, /^material$/i, /^材质$/i, /^框架材质$/i]),
      color: specificationValue(rows, [/^colou?r$/i, /^farbe$/i, /^couleur$/i, /^colore$/i, /^颜色$/i]),
      style: specificationValue(rows, [/^style$/i, /^stil$/i, /^stile$/i, /^estilo$/i, /^风格$/i]),
      size: specificationValue(rows, [/^size$/i, /^gr[oö][ßs]e$/i, /^taille$/i, /^taglia$/i, /^tama[nñ]o$/i, /^尺寸$/i]),
    };
  }

  function firstAttribute(selectors, attributes) {
    for (const selector of selectors) {
      const node = document.querySelector(selector);
      if (!node) continue;
      for (const attribute of attributes) {
        const value = node.getAttribute(attribute);
        if (clean(value)) return clean(value);
      }
    }
    return "";
  }

  function numberValue(value) {
    const match = clean(value).replace(/,/g, "").match(/\d+(?:\.\d+)?/);
    return match ? Number(match[0]) : "";
  }

  function normalizeAsin(value) {
    const text = clean(value).toUpperCase();
    return /^[A-Z0-9]{10}$/.test(text) ? text : "";
  }

  function asinFromUrl(value) {
    const match = clean(value).match(/(?:\/dp\/|\/gp\/product\/|\/product\/)([A-Z0-9]{10})(?:[/?#]|$)/i);
    return match ? normalizeAsin(match[1]) : "";
  }

  function safeImage(value) {
    const url = clean(value)
      .replace(/\\u0026/g, "&")
      .replace(/\\u003d/g, "=")
      .replace(/\\\//g, "/");
    return /^https?:\/\//i.test(url) ? url : "";
  }

  function fullAmazonImage(value) {
    return safeImage(value).replace(/\._[^/]+_\.(?=jpe?g|png|webp)/i, ".");
  }

  function inferCurrency(priceText) {
    if (priceText.includes("€")) return "EUR";
    if (priceText.includes("£")) return "GBP";
    if (priceText.includes("￥") || priceText.includes("¥")) return "JPY";
    if (priceText.includes("CA$") || priceText.includes("CDN$")) return "CAD";
    if (priceText.includes("AU$")) return "AUD";
    return "USD";
  }

  function extractBrand() {
    for (const row of document.querySelectorAll("#productOverview_feature_div tr, #productDetails_techSpec_section_1 tr, #productDetails_detailBullets_sections1 tr")) {
      const label = clean(row.querySelector("th, td:first-child")?.textContent);
      if (/^(brand|品牌)$/i.test(label)) {
        const cells = row.querySelectorAll("td");
        const value = clean(cells[cells.length - 1]?.textContent);
        if (value && !/^(brand|品牌)$/i.test(value)) return value;
      }
    }
    return firstText(["#bylineInfo"])
      .replace(/^Visit the\s+/i, "")
      .replace(/\s+Store$/i, "")
      .replace(/^Brand\s*:\s*/i, "")
      .replace(/^品牌\s*[:：]\s*/i, "")
      .trim();
  }

  function plainTextDescription() {
    const node = document.querySelector("#productDescription");
    if (!node) return "";
    if (node.querySelector("img, picture, video, svg, canvas")) return "";
    return multiline(node.innerText || node.textContent);
  }

  function balancedAt(source, start) {
    const opener = source[start];
    const closer = opener === "{" ? "}" : opener === "[" ? "]" : "";
    if (!closer) return null;
    let depth = 0;
    let quote = "";
    let escaped = false;
    for (let index = start; index < source.length; index++) {
      const char = source[index];
      if (quote) {
        if (escaped) escaped = false;
        else if (char === "\\") escaped = true;
        else if (char === quote) quote = "";
        continue;
      }
      if (char === '"' || char === "'") quote = char;
      else if (char === opener) depth++;
      else if (char === closer && --depth === 0) return source.slice(start, index + 1);
    }
    return null;
  }

  function balancedValue(source, token) {
    const tokenAt = source.indexOf(token);
    if (tokenAt < 0) return null;
    const colon = source.indexOf(":", tokenAt + token.length);
    if (colon < 0) return null;
    const objectAt = source.indexOf("{", colon);
    const arrayAt = source.indexOf("[", colon);
    let start = -1;
    if (objectAt >= 0 && arrayAt >= 0) start = Math.min(objectAt, arrayAt);
    else start = Math.max(objectAt, arrayAt);
    return start >= 0 ? balancedAt(source, start) : null;
  }

  function parseJsonValue(source, tokens) {
    for (const token of tokens) {
      const text = balancedValue(source, token);
      if (!text) continue;
      try { return JSON.parse(text); } catch {}
    }
    return null;
  }

  function quotedProperty(text, key) {
    const expression = new RegExp(`["']${key}["']\\s*:\\s*(?:null|["']([^"']*)["'])`, "i");
    return text.match(expression)?.[1] || "";
  }

  function imageEntriesFromArray(arrayText) {
    const entries = [];
    for (let index = 0; index < arrayText.length; index++) {
      if (arrayText[index] !== "{") continue;
      const objectText = balancedAt(arrayText, index);
      if (!objectText) continue;
      index += objectText.length - 1;
      const variant = quotedProperty(objectText, "variant").toUpperCase();
      if (variant && !/^(MAIN|PT0[1-8])$/.test(variant)) continue;
      const url = fullAmazonImage(
        quotedProperty(objectText, "hiRes")
        || quotedProperty(objectText, "large")
        || quotedProperty(objectText, "mainUrl")
        || quotedProperty(objectText, "thumb")
      );
      if (url) entries.push(url);
    }
    return unique(entries).slice(0, 20);
  }

  function parseColorImageSets() {
    const sets = {};
    document.querySelectorAll("script").forEach(script => {
      const source = script.textContent || "";
      if (!/colorImages/.test(source)) return;
      const objectText = balancedValue(source, "colorImages");
      if (!objectText) return;
      const propertyPattern = /["']([^"']+)["']\s*:\s*\[/g;
      let match;
      while ((match = propertyPattern.exec(objectText))) {
        const arrayAt = objectText.indexOf("[", match.index + match[0].length - 1);
        const arrayText = balancedAt(objectText, arrayAt);
        if (!arrayText) continue;
        const images = imageEntriesFromArray(arrayText);
        if (images.length && (!sets[match[1]] || images.length > sets[match[1]].length)) {
          sets[match[1]] = images;
        }
        propertyPattern.lastIndex = arrayAt + arrayText.length;
      }
    });
    return sets;
  }

  function getSetCaseInsensitive(sets, value) {
    const wanted = clean(value).toLowerCase();
    if (!wanted) return [];
    const key = Object.keys(sets).find(item => clean(item).toLowerCase() === wanted);
    return key ? sets[key] : [];
  }

  function parseScriptVariationRows() {
    const byAsin = new Map();
    document.querySelectorAll("script").forEach(script => {
      const source = script.textContent || "";
      if (!source.includes("dimensionValuesDisplayData")) return;
      const display = parseJsonValue(source, ['"dimensionValuesDisplayData"', "'dimensionValuesDisplayData'", "dimensionValuesDisplayData"]);
      const dimensions = parseJsonValue(source, ['"dimensions"', "'dimensions'"]);
      if (!display || !Array.isArray(dimensions)) return;
      Object.entries(display).forEach(([rawAsin, rawValues]) => {
        const asin = normalizeAsin(rawAsin);
        const values = Array.isArray(rawValues) ? rawValues.map(clean) : [];
        if (!asin || !values.length) return;
        const previous = byAsin.get(asin) || {};
        const attributes = {};
        const colors = [], secondary = [];
        values.forEach((value, index) => {
          const name = clean(dimensions[index]) || `dimension_${index + 1}`;
          if (!value) return;
          attributes[name] = value;
          (isColorDimension(name) ? colors : secondary).push(value);
        });
        byAsin.set(asin, {
          asin,
          color: unique(colors).join(" / ") || previous.color || "",
          size: unique(secondary).join(" / ") || previous.size || "",
          attributes: { ...(previous.attributes || {}), ...attributes },
          options: values
        });
      });
    });
    return [...byAsin.values()];
  }

  function parseDomVariantGroups() {
    const groups = [];
    const seen = new Set();
    [...document.querySelectorAll("#twister .a-row, #twister_feature_div .a-row, [id^='variation_']")].forEach(root => {
      const container = root.closest("[id^='variation_']") || root;
      if (seen.has(container)) return;
      seen.add(container);
      const name = clean(
        container.querySelector(".a-form-label")?.textContent
        || container.querySelector("label")?.textContent
        || container.id.replace(/^variation_/, "").replace(/_/g, " ")
      ).replace(/[:：]\s*$/, "");
      const options = [];

      container.querySelectorAll("select option").forEach(option => {
        if (option.disabled || /select|choose|选择/i.test(clean(option.textContent))) return;
        options.push({
          asin: normalizeAsin(clean(option.value).match(/[A-Z0-9]{10}/i)?.[0] || option.getAttribute("data-asin")),
          value: clean(option.textContent),
          dimension: name,
          available: !option.disabled,
          selected: option.selected,
          image: "",
          price: numberValue(option.getAttribute("data-price") || "")
        });
      });

      container.querySelectorAll("li, .a-button").forEach(node => {
        const image = node.querySelector("img");
        const value = clean(image?.alt || node.querySelector(".a-button-text")?.textContent || node.getAttribute("title"));
        if (!value) return;
        options.push({
          asin: normalizeAsin(
            node.getAttribute("data-defaultasin")
            || node.getAttribute("data-asin")
            || clean(node.getAttribute("data-dp-url")).match(/[A-Z0-9]{10}/i)?.[0]
          ),
          value,
          dimension: name,
          available: !/unavailable/i.test(node.className),
          selected: /selected/i.test(node.className) || node.getAttribute("aria-checked") === "true",
          image: fullAmazonImage(image?.getAttribute("data-old-hires") || image?.currentSrc || image?.src),
          price: numberValue(node.querySelector(".a-price .a-offscreen, .twisterSwatchPrice, .a-color-price")?.textContent || "")
        });
      });

      const keys = new Set();
      const distinct = options.filter(option => {
        const key = `${option.asin}|${option.value}`;
        if (keys.has(key)) return false;
        keys.add(key);
        return true;
      });
      if (name && distinct.length) groups.push({ name, options: distinct });
    });
    return groups;
  }

  function scrapeProduct() {
    const rawTitle = firstText(["#productTitle", 'meta[property="og:title"]']);
    const captcha = Boolean(
      document.querySelector('form[action*="validateCaptcha"]')
      || document.querySelector("#captchacharacters")
      || /captcha|robot check/i.test(document.title)
    );
    if (!rawTitle) {
      return {
        ok: false,
        waiting: true,
        captcha,
        error: captcha ? "页面需要人工验证码" : "尚未识别到商品标题"
      };
    }
    const brand = extractBrand();
    const title = stripBrand(rawTitle, brand);
    const productHighlight = extractProductHighlight();
    const specifications = extractSpecificationRows();
    const physical = extractPhysicalSpecifications(specifications);

    const selectedAsin = normalizeAsin(
      document.querySelector("#twister-plus-asin")?.value
      || document.querySelector("#ASIN")?.value
      || document.querySelector("input[name='ASIN']")?.value
    ) || asinFromUrl(location.href);

    const colorImageSets = parseColorImageSets();
    const images = [];
    const addImage = value => {
      const url = fullAmazonImage(value);
      if (url && !images.includes(url)) images.push(url);
    };
    (colorImageSets.initial || []).forEach(addImage);

    // `colorImages.initial` is not guaranteed to contain the whole gallery.
    // Some Amazon layouts expose only the selected hero image there while the
    // remaining product-owned images live in #altImages.  Always merge these
    // three sources and de-duplicate by the normalized high-resolution URL;
    // otherwise every rendered fallback still submits only one image.
    const landing = document.querySelector("#landingImage, #imgTagWrapperId img");
    if (landing) {
      addImage(landing.getAttribute("data-old-hires"));
      try {
        Object.keys(JSON.parse(landing.getAttribute("data-a-dynamic-image") || "{}")).forEach(addImage);
      } catch {}
      addImage(landing.currentSrc || landing.src);
    }
    document.querySelectorAll("#altImages li:not(.videoThumbnail) img").forEach(image => {
      addImage(image.getAttribute("data-old-hires"));
      addImage(image.currentSrc || image.src);
    });
    addImage(firstAttribute(['meta[property="og:image"]'], ["content"]));

    const variantGroups = parseDomVariantGroups();
    const swatchImages = new Map();
    const swatchDetails = new Map();
    variantGroups.forEach(group => group.options.forEach(option => {
      if (option.asin && option.image) swatchImages.set(option.asin, [option.image]);
      if (option.asin) swatchDetails.set(option.asin, { ...(swatchDetails.get(option.asin) || {}), ...option });
    }));

    let variantRows = parseScriptVariationRows();
    if (!variantRows.length) {
      const rowMap = new Map();
      variantGroups.forEach(group => group.options.forEach(option => {
          if (!option.asin) return;
          const previous = rowMap.get(option.asin) || { asin: option.asin, color: "", size: "", options: [], attributes: {} };
          previous.attributes[group.name] = option.value;
          previous.options = unique([...previous.options, option.value]);
          if (isColorDimension(group.name)) previous.color = unique([previous.color, option.value]).join(" / ");
          else previous.size = unique([previous.size, option.value]).join(" / ");
          rowMap.set(option.asin, previous);
        }));
      variantRows = [...rowMap.values()];
    }

    variantRows = variantRows.map(row => {
      const colorImages = getSetCaseInsensitive(colorImageSets, row.color);
      const rowImages = row.asin === selectedAsin
        ? images
        : colorImages.length ? colorImages : swatchImages.get(row.asin) || [];
      const swatch = swatchDetails.get(row.asin) || {};
      return {
        ...row,
        selected: row.asin === selectedAsin,
        originalPrice: row.originalPrice ?? row.price ?? swatch.price ?? "",
        images: unique(rowImages).slice(0, 20)
      };
    });

    const variants = variantRows.length
      ? variantRows.map(row => ({
          asin: row.asin,
          value: [row.color, row.size].filter(Boolean).join(" / "),
          dimension: "组合变体",
          available: true,
          selected: row.selected,
          image: row.images[0] || "",
          images: row.images,
          color: row.color,
          size: row.size,
          options: row.options
        }))
      : variantGroups.flatMap(group => group.options);

    const breadcrumbs = unique(
      [...document.querySelectorAll("#wayfinding-breadcrumbs_feature_div a")].map(node => node.textContent)
    );
    const lastCategoryNode = [...document.querySelectorAll("#wayfinding-breadcrumbs_feature_div a")].at(-1);
    const lastCategoryHref = lastCategoryNode?.getAttribute("href") || lastCategoryNode?.href || "";
    let categoryId = "";
    try { categoryId = new URL(lastCategoryHref, document.querySelector('link[rel="canonical"]')?.href || location.href).searchParams.get("node") || ""; } catch {
      categoryId = lastCategoryHref.match(/[?&]node=([^&#]+)/)?.[1] || "";
    }
    const bulletPoints = unique(
      [...document.querySelectorAll("#feature-bullets li span.a-list-item, #productFactsDesktopExpander li span.a-list-item")]
        .map(node => stripBrand(node.textContent, brand))
        .filter(value => !/make sure this fits/i.test(value || ""))
    );
    const priceText = firstText([
      "#twister-plus-price-data-price",
      "#corePrice_feature_div .a-price .a-offscreen",
      "#apex_desktop .a-price .a-offscreen",
      "#priceblock_ourprice",
      "#priceblock_dealprice",
      ".priceToPay .a-offscreen",
      'meta[property="product:price:amount"]'
    ]);
    const capturedCurrency = firstAttribute(['meta[property="product:price:currency"]'], ["content"]).toUpperCase() || inferCurrency(priceText);
    const parsedMoney = globalThis.SDMPriceParser?.parseMoney?.(priceText, capturedCurrency);
    const capturedPrice = parsedMoney === "" || parsedMoney == null ? numberValue(priceText) : parsedMoney;
    variantRows.forEach(row => {
      if (row.selected || row.originalPrice === "" || row.originalPrice == null) row.originalPrice = row.selected ? capturedPrice : row.originalPrice;
      row.priceCurrency = capturedCurrency;
    });
    variants.forEach(variant => {
      const row = variantRows.find(item => item.asin === variant.asin);
      if (row) Object.assign(variant, { originalPrice: row.originalPrice, priceCurrency: row.priceCurrency });
    });
    const canonical = document.querySelector('link[rel="canonical"]')?.href || location.href;
    const sellerLink = document.querySelector("#sellerProfileTriggerId, #merchant-info a[href*='seller='], #merchant-info a[href*='me=']");
    const sellerHref = sellerLink?.href || sellerLink?.getAttribute("href") || "";
    let sellerId = "";
    try {
      const sellerUrl = new URL(sellerHref, canonical);
      sellerId = sellerUrl.searchParams.get("seller") || sellerUrl.searchParams.get("me") || "";
    } catch {
      sellerId = sellerHref.match(/[?&](?:seller|me)=([^&#]+)/i)?.[1] || "";
    }
    const sellerName = clean(sellerLink?.textContent) || firstText(["#merchant-info"]);
    const offerText = firstText(["#olp-upd-new-used-freeshipping", "#aod-filter-list", "#buybox-see-all-buying-choices"]);
    const sellerCount = numberValue(offerText);
    const aPlusDescription = multiline(
      document.querySelector("#aplus, #aplus_feature_div, #dpx-aplus-product-description_feature_div")?.innerText || ""
    );
    const detailText = multiline([
      ...document.querySelectorAll("#detailBullets_feature_div li, #productDetails_detailBullets_sections1 tr, #productDetails_techSpec_section_1 tr")
    ].map(node => node.innerText || node.textContent).join("\n"));
    const firstAvailableDate = detailText.match(/(?:Date First Available|首次上架|上架日期)\s*[:：]?\s*([^\n]+)/i)?.[1]?.trim() || "";
    const rankMatches = [...detailText.matchAll(/#?([\d,]+)\s+in\s+([^\n(#]+)/gi)].map(match => ({
      rank: Number(match[1].replace(/,/g, "")),
      category: clean(match[2])
    }));
    const shipsFrom = firstText(["#fulfillerInfoFeature_feature_div", "#tabular-buybox-truncate-0"]);
    const soldBy = firstText(["#merchantInfoFeature_feature_div", "#tabular-buybox-truncate-1", "#sellerProfileTriggerId"]);
    const fulfillmentText = `${shipsFrom} ${soldBy} ${firstText(["#merchant-info"])}`;
    const fulfillmentType = /ships from\s+amazon|dispatches from\s+amazon|fulfilled by amazon|亚马逊配送/i.test(fulfillmentText)
      ? (/sold by\s+amazon|seller\s*[:：]?\s*amazon|销售方\s*[:：]?\s*亚马逊/i.test(fulfillmentText) ? "Amazon" : "FBA")
      : "FBM";
    const selectedVariantRow = variantRows.find(row => row.selected);
    const currentColor = firstText(["#variation_color_name .selection", "#variation_color_name .a-color-secondary", "#variation_colour_name .selection"]) || selectedVariantRow?.color || "";
    const currentSize = firstText(["#variation_size_name .selection", "#variation_size_name .a-color-secondary"]) || selectedVariantRow?.size || "";

    return {
      ok: true,
      product: {
         asin: selectedAsin,
         parentAsin: clean((document.documentElement.innerHTML.match(/"parentAsin"\s*:\s*"([^"]+)"/) || [])[1]),
         rawTitle,
         cleanedTitle: title,
         title,
        productHighlight,
        specifications,
        productDimensions: physical.productDimensions,
        itemDepth: physical.dimensions?.depth || "",
        itemWidth: physical.dimensions?.width || "",
        itemHeight: physical.dimensions?.height || "",
        itemDimensionUnit: physical.dimensions?.unit || "",
        material: physical.material,
        color: physical.color,
        style: physical.style,
        price: capturedPrice,
        currency: capturedCurrency,
        image: images[0] || "",
        images: images.slice(0, 20),
        rating: numberValue(firstAttribute(["#acrPopover"], ["title"]) || firstText(["#acrPopover"])),
        reviews: numberValue(firstText(["#acrCustomerReviewText"])),
         seller: sellerName,
         sellerId,
         sellerName,
         sellerCount,
        brand,
        category: breadcrumbs[breadcrumbs.length - 1] || "",
        categoryPath: breadcrumbs.join("/"),
        categoryId,
        breadcrumbs,
        availability: firstText(["#availability", "#outOfStock"]),
         bulletPoints,
         description: stripBrand(plainTextDescription(), brand, true),
         aPlusDescription,
         ranks: rankMatches,
         mainRank: rankMatches[0] || null,
         subRank: rankMatches[1] || null,
         firstAvailableDate,
         fulfillmentType,
         currentColor: currentColor || physical.color,
         currentSize: currentSize || physical.size,
        url: canonical,
        variants,
        variantRows,
        variantGroups,
        status: "complete",
        collectedAt: new Date().toISOString()
      }
    };
  }

  function installCollectorStyle() {
    if (document.getElementById("wzc-card-style")) return;
    const style = document.createElement("style");
    style.id = "wzc-card-style";
    style.textContent = `
      .wzc-grab-host{position:relative!important}
      .wzc-grab-button{position:absolute;left:50%;top:50%;z-index:2147483000;min-width:136px;min-height:46px;padding:10px 18px;border:1px solid rgba(255,255,255,.65);border-radius:10px;background:linear-gradient(135deg,#0fb6ac,#087f79);box-shadow:0 12px 32px rgba(0,0,0,.3);color:#fff!important;font:700 15px/1.2 "Microsoft YaHei",Arial,sans-serif;text-align:center;cursor:pointer;opacity:0;pointer-events:none;transform:translate(-50%,-42%) scale(.94);transition:opacity .16s ease,transform .16s ease}
      .wzc-grab-button.wzc-product-page-button{min-width:156px;min-height:50px;font-size:16px}
      .wzc-grab-host:hover>.wzc-grab-button,.wzc-grab-button:focus{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
      .wzc-grab-button[data-state="loading"]{opacity:1;pointer-events:none;background:#334155}
      .wzc-grab-button[data-state="success"]{opacity:1;background:#059669}
      .wzc-grab-button[data-state="error"]{opacity:1;background:#dc2626}
      .sdm-batch-panel{position:fixed;right:18px;bottom:22px;z-index:2147483600;width:210px;padding:12px;border:1px solid rgba(255,255,255,.65);border-radius:14px;background:rgba(18,27,43,.94);box-shadow:0 16px 42px rgba(0,0,0,.28);color:#fff;font:13px/1.45 "Microsoft YaHei",Arial,sans-serif;backdrop-filter:blur(12px)}
      .sdm-batch-panel strong{display:block;margin-bottom:8px;font-size:14px}.sdm-batch-actions{display:grid;gap:6px}.sdm-batch-button,.sdm-batch-stop{width:100%;padding:10px 12px;border:0;border-radius:9px;font-weight:800;cursor:pointer}.sdm-batch-button{background:linear-gradient(135deg,#ffc400,#ff8a00);color:#1c2433}.sdm-batch-stop{background:#ef4444;color:#fff}.sdm-batch-button:disabled,.sdm-batch-stop:disabled{opacity:.65;cursor:wait}.sdm-batch-stop[hidden]{display:none}.sdm-batch-status{min-height:18px;margin-top:7px;color:#cbd5e1;font-size:11px}
    `;
    document.documentElement.appendChild(style);
  }

  function productCardForImage(image) {
    return image.closest('[data-component-type="s-search-result"], [data-asin]:not([data-asin=""]), .a-carousel-card, li.a-carousel-card, .puis-card-container, .s-result-item, [id^="gridItemRoot"], .zg-grid-general-faceout, .zg-item-immersion, .zg-item, .zg-carousel-general-faceout, [data-csa-c-item-id]')
      || image.closest("li, article, .a-section");
  }

  const CARD_IMAGE_SELECTOR = [
    '[data-component-type="s-search-result"] img',
    '[data-asin]:not([data-asin=""]) img',
    '.s-result-item img.s-image',
    'img.s-image',
    '.a-carousel-card img',
    '.puis-card-container img',
    '[id^="gridItemRoot"] img',
    '.zg-grid-general-faceout img',
    '.zg-item-immersion img',
    '#zg-ordered-list img',
    '.zg-item img',
    '.zg-carousel-general-faceout img',
    '[data-csa-c-item-id] img.s-image'
  ].join(",");

  function productUrlForCard(image, card) {
    const direct = image.closest('a[href*="/dp/"], a[href*="/gp/product/"], a[href*="/product/"]');
    const link = direct || card?.querySelector('a[href*="/dp/"], a[href*="/gp/product/"], a[href*="/product/"], h2 a[href]');
    const rawHref = link?.href || "";
    const cardAsin = normalizeAsin(card?.getAttribute("data-asin"));
    const urlAsin = asinFromUrl(rawHref) || asinFromUrl(decodeURIComponent(rawHref || ""));
    const asin = cardAsin || urlAsin;
    if (!asin) return "";
    return `${location.origin}/dp/${asin}?th=1&psc=1`;
  }

  function textWithin(root, selectors) {
    for (const selector of selectors) {
      const node = root?.querySelector(selector);
      const value = clean(node?.getAttribute("aria-label") || node?.textContent);
      if (value) return value;
    }
    return "";
  }

  function scrapeProductCard(image, card, url) {
    const asin = normalizeAsin(card?.getAttribute("data-asin")) || asinFromUrl(url);
    const rawTitle = textWithin(card, [
      "h2 a span", "h2 span", "[data-cy='title-recipe'] span",
      ".a-size-base-plus", ".a-size-medium", "a[title]"
    ]) || clean(image.alt);
    const priceText = textWithin(card, [".a-price .a-offscreen", ".a-price-whole", "[data-a-color='price']"]);
    const breadcrumbs = unique(
      [...document.querySelectorAll("#wayfinding-breadcrumbs_feature_div a, .a-breadcrumb a")]
        .map(node => node.textContent)
    );
    const imageUrl = fullAmazonImage(image.getAttribute("data-old-hires") || image.currentSrc || image.src);
    return {
      asin,
      parentAsin: "",
      title: rawTitle,
      price: numberValue(priceText),
      currency: inferCurrency(priceText),
      image: imageUrl,
      images: imageUrl ? [imageUrl] : [],
      rating: numberValue(textWithin(card, [".a-icon-alt", "[aria-label*='out of 5']"])),
      reviews: numberValue(textWithin(card, ["[aria-label$='ratings']", "[aria-label$='rating']", ".a-size-base.s-underline-text"])),
      seller: "",
      brand: "",
      category: breadcrumbs.at(-1) || "",
      categoryPath: breadcrumbs.join("/"),
      categoryId: "",
      breadcrumbs,
      availability: "",
      bulletPoints: [],
      description: "",
      url,
      variants: [],
      variantRows: [],
      variantGroups: [],
      status: "partial",
      captureSource: "list-card",
      dataCompleteness: "待补全",
      collectedAt: new Date().toISOString()
    };
  }

  function attachCollectorButton(image) {
    if (!(image instanceof HTMLImageElement) || image.dataset.wzcGrabMounted === "true") return;
    const card = productCardForImage(image);
    const url = productUrlForCard(image, card);
    if (!url) return;
    if (card?.querySelector(":scope .wzc-grab-button")) {
      image.dataset.wzcGrabMounted = "true";
      return;
    }
    const rect = image.getBoundingClientRect();
    if ((rect.width && rect.width < 70) || (rect.height && rect.height < 70)) return;
    const host = image.closest("a") || image.parentElement;
    if (!host || host.querySelector(":scope > .wzc-grab-button")) return;
    image.dataset.wzcGrabMounted = "true";
    host.classList.add("wzc-grab-host");
    const button = document.createElement("button");
    button.type = "button";
    button.className = "wzc-grab-button";
    button.textContent = "易联跨境";
    button.title = "后台采集该商品，不离开当前页面";
    button.addEventListener("click", async event => {
      event.preventDefault();
      event.stopPropagation();
      event.stopImmediatePropagation();
      button.dataset.state = "loading";
      button.textContent = "正在采集…";
      try {
        const product = scrapeProductCard(image, card, url);
        if (!product.asin || !product.title) throw new Error("当前商品卡片信息不足，请进入详情页采集");
        const response = await chrome.runtime.sendMessage({ type: "COLLECT_CARD", url, preview: product, includeVariants: true });
        if (!response?.ok) throw new Error(response?.error || "采集失败");
        button.dataset.state = "success";
        button.textContent = response.duplicate ? "任务已存在" : "任务已受理";
      } catch (error) {
        button.dataset.state = "error";
        button.textContent = "采集失败，点此重试";
        button.title = error.message || "采集失败";
      }
    }, true);
    host.appendChild(button);
  }

  function attachProductPageCollector() {
    const pageAsin = normalizeAsin(
      document.querySelector("#ASIN")?.value
      || asinFromUrl(location.href)
      || asinFromUrl(firstAttribute(['link[rel="canonical"]'], ["href"]))
    );
    if (!document.querySelector("#productTitle") || !pageAsin) return;
    const image = document.querySelector("#landingImage, #imgTagWrapperId img, #main-image-container img.a-dynamic-image, #ivLargeImage img");
    if (!(image instanceof HTMLImageElement)) return;
    const host = document.querySelector("#imgTagWrapperId, #main-image-container")
      || image.closest("#imageBlock, .imgTagWrapper")
      || image.parentElement;
    if (!host || host.dataset.wzcProductMounted === "true") return;
    host.dataset.wzcProductMounted = "true";
    host.classList.add("wzc-grab-host");
    const button = document.createElement("button");
    button.type = "button";
    button.className = "wzc-grab-button wzc-product-page-button";
    button.textContent = "易联跨境 · 采集";
    button.title = "直接读取当前商品页面，不打开新的采集页签";
    button.addEventListener("click", async event => {
      event.preventDefault();
      event.stopPropagation();
      event.stopImmediatePropagation();
      button.dataset.state = "loading";
      button.textContent = "正在采集当前页…";
      try {
        const response = await chrome.runtime.sendMessage({ type: "COLLECT_CURRENT", includeVariants: true });
        if (!response?.ok) throw new Error(response?.error || "采集失败");
        button.dataset.state = "success";
        button.textContent = "采集成功";
        button.title = "商品已进入采集库";
      } catch (error) {
        button.dataset.state = "error";
        button.textContent = "采集失败，点此重试";
        button.title = error.message || "采集失败";
      }
    }, true);
    host.appendChild(button);
  }

  let injectTimer = 0;

  function batchProductsOnPage() {
    const products = [];
    const seen = new Set();
    document.querySelectorAll(CARD_IMAGE_SELECTOR).forEach(image => {
      const card = productCardForImage(image);
      const url = productUrlForCard(image, card);
      const product = url ? scrapeProductCard(image, card, url) : null;
      if (!product?.asin || !product.title || seen.has(product.asin)) return;
      seen.add(product.asin);
      products.push(product);
    });
    return products;
  }

  function installBatchPanel() {
    if (document.getElementById("sdm-batch-panel")) return;
    const panel = document.createElement("div");
    panel.id = "sdm-batch-panel";
    panel.className = "sdm-batch-panel";
    panel.innerHTML = `<strong>⚡ 易联跨境采集</strong><div class="sdm-batch-actions"><button type="button" class="sdm-batch-button">批量采集本页商品</button><button type="button" class="sdm-batch-stop" hidden>停止批量采集</button></div><div class="sdm-batch-status">列表页一键入采集库</div>`;
    const button = panel.querySelector(".sdm-batch-button");
    const stopButton = panel.querySelector(".sdm-batch-stop");
    const status = panel.querySelector(".sdm-batch-status");
    let activeBatchId = "";
    let pollTimer = 0;
    const resetControls = () => {
      clearInterval(pollTimer);
      pollTimer = 0;
      activeBatchId = "";
      button.disabled = false;
      button.textContent = "批量采集本页商品";
      stopButton.disabled = false;
      stopButton.hidden = true;
    };
    const refreshBatchStatus = async batchId => {
      if (!batchId || activeBatchId !== batchId) return;
      try {
        const response = await chrome.runtime.sendMessage({ type: "GET_CAPTURE_BATCH_STATUS", batchId });
        if (!response?.ok) return;
        const counts = response.counts || {};
        status.textContent = `等待 ${counts.waiting || 0}，采集中 ${counts.collecting || 0}，完成 ${(counts.success || 0) + (counts.partial_success || 0)}`;
        if (!response.active) {
          const stopped = Number(counts.cancelled || 0);
          resetControls();
          status.textContent = stopped ? `已停止 ${stopped} 个未完成任务，已完成结果保留` : `本批次已完成 ${Number(counts.success || 0) + Number(counts.partial_success || 0)} 个商品`;
        }
      } catch {}
    };
    button.addEventListener("click", async () => {
      const products = batchProductsOnPage();
      if (!products.length) {
        status.textContent = "未找到商品卡片；详情页请点击主图上的采集按钮";
        return;
      }
      const batchId = `batch_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
      activeBatchId = batchId;
      button.disabled = true;
      button.textContent = `正在采集 ${products.length} 个商品…`;
      stopButton.hidden = false;
      stopButton.disabled = false;
      status.textContent = "正在写入采集队列和 product.db";
      try {
        const response = await chrome.runtime.sendMessage({
          type: "ENQUEUE_CAPTURE_BATCH",
          batchId,
          includeVariants: true,
          targets: products.map(product => ({ asin: product.asin, domain: location.hostname, url: product.url, preview: product }))
        });
        if (!response?.ok) throw new Error(response?.error || "批量采集失败");
        if (activeBatchId !== batchId) {
          await chrome.runtime.sendMessage({ type: "STOP_CAPTURE_BATCH", batchId }).catch(() => {});
          return;
        }
        button.textContent = `正在采集 ${response.accepted || 0} 个商品`;
        status.textContent = `已受理 ${response.accepted || 0} 个，重复 ${response.duplicates || 0} 个；后台将依次补全详情`;
        pollTimer = setInterval(() => refreshBatchStatus(batchId), 1000);
        refreshBatchStatus(batchId);
      } catch (error) {
        button.textContent = "批量采集失败，点击重试";
        status.textContent = error.message || "扩展通信失败";
        stopButton.hidden = true;
        activeBatchId = "";
        button.disabled = false;
      }
    });
    stopButton.addEventListener("click", async () => {
      const batchId = activeBatchId;
      if (!batchId) return;
      stopButton.disabled = true;
      status.textContent = "正在停止尚未完成的任务…";
      activeBatchId = "";
      clearInterval(pollTimer);
      try {
        const response = await chrome.runtime.sendMessage({ type: "STOP_CAPTURE_BATCH", batchId });
        if (!response?.ok) throw new Error(response?.error || "停止失败");
        resetControls();
        status.textContent = `已停止 ${response.stopped || 0} 个未完成任务，已完成结果保留`;
      } catch (error) {
        activeBatchId = batchId;
        stopButton.disabled = false;
        status.textContent = error.message || "停止批量采集失败";
        pollTimer = setInterval(() => refreshBatchStatus(batchId), 1000);
      }
    });
    document.documentElement.appendChild(panel);
  }

  function injectCardCollectors() {
    installCollectorStyle();
    installBatchPanel();
    attachProductPageCollector();
    document.querySelectorAll(CARD_IMAGE_SELECTOR).forEach(attachCollectorButton);
  }

  function scheduleCollectorInjection() {
    clearTimeout(injectTimer);
    injectTimer = setTimeout(injectCardCollectors, 350);
  }

  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message?.type !== "SCRAPE_PRODUCT" && message?.type !== "COLLECT_PRODUCT_V2") return;
    try {
      const result = scrapeProduct();
      if (message.type === "COLLECT_PRODUCT_V2" && result?.ok && result.product && globalThis.SDMCollectorV2) {
        const embedded = globalThis.SDMCollectorV2.extractEmbeddedVariationData(
          document.documentElement.outerHTML,
          result.product.asin || message.target?.asin || ""
        );
        result.product = globalThis.SDMCollectorV2.mergeVariantData(result.product, embedded);
      }
      sendResponse(result);
    } catch (error) {
      sendResponse({ ok: false, error: error.message || "页面解析失败" });
    }
  });

  injectCardCollectors();
  new MutationObserver(scheduleCollectorInjection).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-asin", "src"] });
})();
