(function () {
  'use strict';

  const BUTTON_ID = 'xiaoqingwa-customized-content-export-btn';
  const MODAL_ID = 'xiaoqingwa-customized-content-export-modal';
  const IMAGE_BATCH_SIZE = 12;

  const runtime = window.__xiaoqingwaCustomizedContentExportRuntime || (window.__xiaoqingwaCustomizedContentExportRuntime = {
    observer: null,
    heartbeat: null,
    exporting: false,
    officialItemMap: new Map(),
    lastPageQueryBody: null,
    lastPageQueryTotal: 0
  });
  if (!(runtime.officialItemMap instanceof Map)) runtime.officialItemMap = new Map();
  if (!Number.isFinite(runtime.lastPageQueryTotal)) runtime.lastPageQueryTotal = 0;

  const FIELDS = [
    { key: 'productName', label: '商品名称', aliases: ['商品名称'], default: true },
    { key: 'spuId', label: '商品SPU ID', aliases: ['商品SPUID', 'SPUID'], default: true },
    { key: 'skcId', label: '商品SKC ID', aliases: ['商品SKCID', 'SKCID'], default: true },
    { key: 'skcImage', label: 'SKC图片', aliases: ['商品信息', 'SKC图片'], image: true, default: true },
    { key: 'skuId', label: '商品SKU ID', aliases: ['商品SKUID', 'SKUID'], default: true },
    { key: 'skuImage', label: 'SKU图片', aliases: ['SKU属性集', 'SKU图片', 'SKU图'], image: true, default: true },
    { key: 'skuCode', label: 'SKU货号', aliases: ['SKU货号'], default: true },
    { key: 'skuAttributes', label: '商品属性集', aliases: ['商品属性集'], default: true },
    { key: 'customType', label: '定制工艺类型', aliases: ['定制工艺类型'], default: true },
    { key: 'stockOrder', label: '备货单', aliases: ['备货单'], default: true },
    { key: 'customSku', label: '定制SKU', aliases: ['定制SKU'], default: true },
    { key: 'customArea', label: '定制区域', aliases: ['定制区域'], default: true },
    { key: 'textContent', label: '文字内容', aliases: ['文字内容'], default: true },
    { key: 'textColor', label: '文字颜色', aliases: ['文字颜色'], default: false },
    { key: 'font', label: '字体', aliases: ['字体'], default: false },
    { key: 'consumerImage', label: '消费者上传的图片', aliases: ['消费者上传的图片'], image: true, default: true },
    { key: 'customAreaImage', label: '定制区域图', aliases: ['定制区域图'], image: true, default: true },
    { key: 'previewImage', label: '合成预览图', aliases: ['合成预览图'], image: true, default: true }
  ];
  function isTemuSellerBackendHost() {
    try {
      return /(agentseller(?:-[a-z]+)?\.temu\.com|seller(?:-[a-z]+)?\.temu\.com|seller\.kuajingmaihuo\.com)$/i.test(location.hostname || '');
    } catch (_) {
      return false;
    }
  }

  function normalizeText(value = '') {
    return String(value || '').replace(/\s+/g, '').trim();
  }

  function isCustomizedContentPage() {
    if (!isTemuSellerBackendHost()) return false;
    const text = normalizeText(`${document.title || ''} ${document.body?.innerText || ''}`);
    return text.includes('定制商品内容') && text.includes('批量导出定制内容');
  }

  function isVisible(el) {
    if (!el || el.nodeType !== Node.ELEMENT_NODE) return false;
    const rect = el.getBoundingClientRect?.();
    if (!rect || rect.width <= 0 || rect.height <= 0) return false;
    const style = window.getComputedStyle?.(el);
    return !style || (style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0');
  }

  function isDisabled(el) {
    return Boolean(el?.disabled || el?.getAttribute?.('aria-disabled') === 'true' || /\bdisabled\b/i.test(String(el?.className || '')));
  }

  function findOfficialExportButton() {
    const buttons = Array.from(document.querySelectorAll('button,[role="button"],a'));
    return buttons.find(el => {
      if (el.id === BUTTON_ID) return false;
      const text = normalizeText(el.innerText || el.textContent || el.getAttribute?.('title') || '');
      return isVisible(el) && text.includes('批量导出定制内容');
    }) || null;
  }

  function showToast(message, type = 'info') {
    const toast = document.createElement('div');
    const bg = type === 'error' ? '#dc2626' : type === 'success' ? '#16a34a' : type === 'warning' ? '#d97706' : '#2563eb';
    toast.style.cssText = [
      'position:fixed',
      'right:24px',
      'top:24px',
      'z-index:2147483647',
      `background:${bg}`,
      'color:#fff',
      'padding:10px 14px',
      'border-radius:6px',
      'box-shadow:0 12px 30px rgba(15,23,42,.22)',
      'font-size:13px',
      'line-height:1.45',
      'max-width:420px',
      'word-break:break-word'
    ].join(';');
    toast.textContent = message;
    document.body.appendChild(toast);
    window.setTimeout(() => toast.remove(), type === 'error' ? 6000 : 3500);
  }

  function sendRuntimeMessage(message, timeoutMs = 120000) {
    return new Promise(resolve => {
      let settled = false;
      const timer = window.setTimeout(() => {
        if (settled) return;
        settled = true;
        resolve({ success: false, error: '插件后台响应超时' });
      }, timeoutMs);
      try {
        chrome.runtime.sendMessage(message, response => {
          if (settled) return;
          settled = true;
          window.clearTimeout(timer);
          if (chrome.runtime?.lastError) {
            resolve({ success: false, error: chrome.runtime.lastError.message });
            return;
          }
          resolve(response || { success: false, error: '插件后台无响应' });
        });
      } catch (error) {
        if (settled) return;
        settled = true;
        window.clearTimeout(timer);
        resolve({ success: false, error: error?.message || String(error) });
      }
    });
  }

  function cleanCellValue(value, fieldKey = '') {
    const text = String(value ?? '').replace(/\r?\n/g, ' ').trim();
    if (text === '\\') return '';
    if (fieldKey === 'textContent' && text === '-') return '';
    return text;
  }

  function normalizeImageUrl(value = '') {
    const text = String(value || '').trim();
    if (!text) return '';
    if (text.startsWith('//')) return `https:${text}`;
    if (text.startsWith('http://')) return `https://${text.slice(7)}`;
    return /^https?:\/\//i.test(text) || /^data:image\//i.test(text) ? text : '';
  }

  function parseJsonObject(value) {
    if (!value) return null;
    if (typeof value === 'object') return value;
    try {
      const parsed = JSON.parse(String(value));
      return parsed && typeof parsed === 'object' ? parsed : null;
    } catch (_) {
      return null;
    }
  }

  function getByPath(source, keys = []) {
    for (const key of keys) {
      if (!source || typeof source !== 'object') continue;
      if (source[key] !== undefined && source[key] !== null && source[key] !== '') return source[key];
    }
    return '';
  }

  function toCellText(value) {
    if (value === undefined || value === null) return '';
    if (Array.isArray(value)) return uniqueList(value.map(toCellText)).join(' / ');
    if (typeof value === 'object') {
      return cleanCellValue(getByPath(value, ['text', 'content', 'value', 'name', 'label', 'fontName', 'fontFamily', 'color']) || '');
    }
    return cleanCellValue(value);
  }

  function storeOfficialCustomizedItems(data, requestBody = null) {
    if (!data || typeof data !== 'object') return;
    const pageItems = Array.isArray(data?.result?.pageItems) ? data.result.pageItems : [];
    pageItems.forEach(item => {
      const key = String(item?.personalProductSkuId || '').trim();
      if (key) runtime.officialItemMap.set(key, item);
    });
    const total = Number(data?.result?.total || 0);
    if (Number.isFinite(total) && total > 0) runtime.lastPageQueryTotal = total;
    if (requestBody && typeof requestBody === 'object') runtime.lastPageQueryBody = requestBody;
  }

  function handlePageQueryMessage(event) {
    if (event.source !== window) return;
    const data = event.data || {};
    if (data.type !== '__TEMU_PAGE_QUERY_DATA__') return;
    if (!/\/product\/customizeSku\/pageQuery/i.test(String(data.url || ''))) return;
    storeOfficialCustomizedItems(data.data, data.requestBody);
  }

  function getCustomization(item = {}) {
    return item.productSkuCustomization || {};
  }

  function getCustomizationData(item = {}) {
    return parseJsonObject(getCustomization(item).customizedData) || {};
  }

  function collectCustomizationElements(item = {}) {
    const data = getCustomizationData(item);
    const elements = [];
    (Array.isArray(data.surfaces) ? data.surfaces : []).forEach((surface, surfaceIndex) => {
      (Array.isArray(surface?.regions) ? surface.regions : []).forEach((region, regionIndex) => {
        (Array.isArray(region?.elements) ? region.elements : []).forEach((element, elementIndex) => {
          elements.push({ surfaceIndex, regionIndex, elementIndex, element: element || {} });
        });
      });
    });
    return elements;
  }

  function getPreviewImage(item = {}, previewType) {
    const previews = Array.isArray(getCustomization(item).customizedPreviewItems)
      ? getCustomization(item).customizedPreviewItems
      : [];
    const matched = previews.find(preview => Number(preview?.previewType) === Number(previewType)) || null;
    return normalizeImageUrl(matched?.imageUrlDisplay || matched?.imageUrl || '');
  }

  function getConsumerImage(item = {}) {
    const imageElement = collectCustomizationElements(item)
      .map(entry => entry.element)
      .find(element => Number(element?.type) === 1 || element?.imageUrl || element?.image_url || element?.origin_url);
    return normalizeImageUrl(getByPath(imageElement, ['imageUrl', 'image_url', 'origin_url', 'originUrl']) || '');
  }

  function getCustomizationTextParts(item = {}, keys = []) {
    const values = [];
    collectCustomizationElements(item).forEach(({ element }) => {
      keys.forEach(key => {
        const value = element?.[key];
        if (value !== undefined && value !== null && value !== '') values.push(toCellText(value));
      });
      const nestedText = element?.customizedText || element?.textStyle || element?.font;
      if (nestedText && typeof nestedText === 'object') {
        keys.forEach(key => {
          const value = nestedText[key];
          if (value !== undefined && value !== null && value !== '') values.push(toCellText(value));
        });
      }
    });
    return uniqueList(values).join(' / ');
  }

  function numberToChineseIndex(index) {
    return ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'][index] || String(index + 1);
  }

  function getOfficialCustomArea(item = {}) {
    const regionIndexes = collectCustomizationElements(item).map(entry => entry.regionIndex);
    return uniqueList(regionIndexes.map(index => `定制区域${numberToChineseIndex(index)}`)).join(' / ');
  }

  function getStockOrderText(item = {}) {
    const details = Array.isArray(item.subPurchaseOrderInfoVOS) ? item.subPurchaseOrderInfoVOS : [];
    if (details.length) {
      return uniqueList(details.map(info => {
        const sn = cleanCellValue(info?.subPurchaseOrderSn || '');
        const qty = Number(info?.purchaseQuantity || 0);
        return sn && qty ? `${sn}，${qty}件` : sn;
      })).join(' / ');
    }
    return uniqueList(Array.isArray(item.subPurchaseOrderSnList) ? item.subPurchaseOrderSnList : []).join(' / ');
  }

  function getSkuAttributesText(item = {}) {
    return uniqueList((Array.isArray(item.productSkuSpecList) ? item.productSkuSpecList : [])
      .map(spec => spec?.specName || spec?.name || spec?.value || '')).join(' / ');
  }

  function getProcessCodeText(item = {}) {
    const vo = item.productTechnologyVO || {};
    const parts = [vo.firstProcessType, ...(Array.isArray(vo.secondProcessType) ? vo.secondProcessType : [])]
      .filter(value => value !== undefined && value !== null && value !== '')
      .map(value => String(value));
    return parts.join(' / ');
  }

  function buildOfficialExportRow(item = {}, supplement = {}) {
    const skcImage = normalizeImageUrl(item.displayImage || '');
    const skuImage = normalizeImageUrl(item.skuThumbUrl || '');
    const consumerImage = getConsumerImage(item);
    const customAreaImage = getPreviewImage(item, 3);
    const previewImage = getPreviewImage(item, 1);
    return {
      productName: cleanCellValue(item.productName || supplement.productName || ''),
      spuId: cleanCellValue(item.productId || supplement.spuId || ''),
      skcId: cleanCellValue(item.productSkcId || supplement.skcId || ''),
      skcImage: skcImage || supplement.skcImage || '',
      skuId: cleanCellValue(item.productSkuId || supplement.skuId || ''),
      skuImage: skuImage || supplement.skuImage || '',
      skuCode: cleanCellValue(item.extCode || supplement.skuCode || ''),
      skuAttributes: getSkuAttributesText(item) || supplement.skuAttributes || '',
      customType: supplement.customType || getProcessCodeText(item),
      stockOrder: getStockOrderText(item) || supplement.stockOrder || '',
      customSku: cleanCellValue(item.personalProductSkuId || supplement.customSku || ''),
      customArea: supplement.customArea || getOfficialCustomArea(item),
      textContent: getCustomizationTextParts(item, ['text', 'textContent', 'content', 'value', 'inputText', 'customizedText']) || supplement.textContent || '',
      textColor: getCustomizationTextParts(item, ['textColor', 'fontColor', 'color']) || supplement.textColor || '',
      font: getCustomizationTextParts(item, ['font', 'fontName', 'fontFamily']) || supplement.font || '',
      consumerImage: consumerImage || supplement.consumerImage || '',
      customAreaImage: customAreaImage || supplement.customAreaImage || '',
      previewImage: previewImage || supplement.previewImage || ''
    };
  }

  async function queryOfficialCustomizedPage(page, pageSize, baseBody = {}) {
    const body = { ...(baseBody || {}) };
    delete body.page;
    delete body.pageSize;
    const result = await sendRuntimeMessage({
      action: 'queryTemuCustomizedContentPage',
      params: { page, pageSize, body, timeoutMs: 60000 }
    }, 90000);
    if (!result?.success) throw new Error(result?.error || '官方定制商品内容列表查询失败');
    storeOfficialCustomizedItems(result.data, { ...body, page, pageSize });
    return result;
  }

  async function resolveOfficialRowsForSelected(supplementRows = [], setStatus) {
    const selectedIds = supplementRows.map(row => String(row.customSku || '').trim()).filter(Boolean);
    const missing = () => selectedIds.filter(id => !runtime.officialItemMap.has(id));
    let missingIds = missing();
    if (missingIds.length) {
      const pageSize = 500;
      const baseBody = runtime.lastPageQueryBody && typeof runtime.lastPageQueryBody === 'object'
        ? runtime.lastPageQueryBody
        : {};
      let page = 1;
      let total = Number(runtime.lastPageQueryTotal || 0) || 0;
      let fetched = 0;
      while (missingIds.length) {
        setStatus?.(`正在读取官方列表数据：第 ${page} 页，待匹配 ${missingIds.length} 个定制SKU`);
        const result = await queryOfficialCustomizedPage(page, pageSize, baseBody);
        const items = Array.isArray(result.pageItems) ? result.pageItems : [];
        fetched += items.length;
        total = Number(result.total || total || 0) || total;
        missingIds = missing();
        if (!items.length) break;
        if (total && fetched >= total) break;
        page += 1;
      }
    }
    missingIds = missing();
    if (missingIds.length) {
      throw new Error(`官方列表数据中未找到 ${missingIds.length} 个已勾选定制SKU：${missingIds.slice(0, 5).join('、')}`);
    }
    return supplementRows.map(row => buildOfficialExportRow(runtime.officialItemMap.get(String(row.customSku).trim()), row));
  }

  function getElementText(el) {
    return cleanCellValue(el?.innerText || el?.textContent || '');
  }

  function normalizeHeader(value = '') {
    return String(value || '')
      .replace(/\s+/g, '')
      .replace(/[：:]/g, '')
      .replace(/（.*?）|\(.*?\)/g, '')
      .trim();
  }

  function uniqueList(values = []) {
    return Array.from(new Set(values.map(value => cleanCellValue(value)).filter(Boolean)));
  }

  function findDataRowForCheckbox(checkbox) {
    let current = checkbox?.parentElement;
    for (let depth = 0; current && depth < 12; depth += 1) {
      if (current.matches?.('[data-testid="beast-core-table-body-tr"],tr,[role="row"],[class*="TB_tr"],[class*="table-body-tr"],[class*="TableRow"]')) return current;
      current = current.parentElement;
    }
    return checkbox?.closest?.('[data-testid="beast-core-table-body-tr"],tr,[role="row"],[class*="TB_tr"],[class*="table-body-tr"],[class*="TableRow"]') || null;
  }

  function getRowIndex(row) {
    if (!row) return -1;
    const explicit = row.getAttribute?.('data-row-index') || row.getAttribute?.('aria-rowindex') || row.dataset?.rowIndex;
    const explicitIndex = Number(explicit);
    if (Number.isFinite(explicitIndex)) return explicitIndex;
    const parent = row.parentElement;
    if (!parent) return -1;
    const siblings = Array.from(parent.children || []).filter(el =>
      el.matches?.('[data-testid="beast-core-table-body-tr"],tr,[role="row"],[class*="TB_tr"],[class*="table-body-tr"],[class*="TableRow"]')
    );
    return siblings.indexOf(row);
  }

  function collectPeerRows(row) {
    const peers = [];
    const add = candidate => {
      if (candidate && !peers.includes(candidate)) peers.push(candidate);
    };
    add(row);
    const rowIndex = getRowIndex(row);
    if (rowIndex < 0) return peers;
    const bodies = Array.from(document.querySelectorAll([
      'tbody',
      '[role="rowgroup"]',
      '[data-testid*="table-body"]',
      '[class*="TB_body"]',
      '[class*="table-body"]',
      '[class*="TableBody"]'
    ].join(',')));
    for (const body of bodies.slice(0, 100)) {
      const rows = Array.from(body.children || []).filter(el =>
        el.matches?.('[data-testid="beast-core-table-body-tr"],tr,[role="row"],[class*="TB_tr"],[class*="table-body-tr"],[class*="TableRow"]')
      );
      add(rows[rowIndex]);
    }
    return peers;
  }

  function getOfficialSelectedRows() {
    const checkboxes = Array.from(document.querySelectorAll('input[type="checkbox"]:checked'))
      .filter(input => !input.closest(`#${MODAL_ID}`));
    const rows = [];
    for (const checkbox of checkboxes) {
      const row = findDataRowForCheckbox(checkbox);
      if (!row || !isVisible(row)) continue;
      const text = getElementText(row);
      if (!text || /商品信息|SKU属性集|批量导出定制内容/.test(text)) continue;
      if (!rows.includes(row)) rows.push(row);
    }
    return rows;
  }

  function hasOfficialSelection() {
    return getOfficialSelectedRows().length > 0;
  }

  function getVisibleHeaderCells() {
    const candidates = Array.from(document.querySelectorAll([
      'th',
      '[role="columnheader"]',
      '[data-testid*="table-header"]',
      '[class*="TB_th"]',
      '[class*="table-header"]',
      '[class*="TableHeader"]'
    ].join(',')));
    const leafHeaders = candidates.filter(el =>
      !candidates.some(other => other !== el && el.contains?.(other))
    );
    return leafHeaders.map((el, index) => {
      const rect = el.getBoundingClientRect?.();
      return {
        el,
        index,
        text: getElementText(el),
        normalized: normalizeHeader(getElementText(el)),
        rect
      };
    }).filter(item =>
      item.normalized
      && item.rect
      && item.rect.width > 0
      && item.rect.height > 0
      && isVisible(item.el)
    );
  }

  function getCellElements(peerRows = []) {
    const cells = [];
    for (const row of peerRows) {
      const rowCells = Array.from(row.querySelectorAll?.([
        '[data-testid*="table-body-td"]',
        'td',
        '[role="cell"]',
        '[class*="TB_td"]',
        '[class*="table-body-td"]',
        '[class*="TableCell"]'
      ].join(',')) || []);
      const source = rowCells.length ? rowCells : Array.from(row.children || []);
      source.forEach(cell => {
        if (!cells.includes(cell) && isVisible(cell)) cells.push(cell);
      });
    }
    return cells;
  }

  function getImageUrlsFromCell(cell) {
    const urls = [];
    Array.from(cell?.querySelectorAll?.('img') || []).forEach(img => {
      const candidates = [
        img.currentSrc,
        img.src,
        img.getAttribute('src'),
        img.getAttribute('data-src'),
        img.getAttribute('data-original'),
        img.getAttribute('data-lazy-src')
      ];
      for (const candidate of candidates) {
        const url = normalizeImageUrl(candidate || '');
        if (url && !urls.includes(url)) urls.push(url);
      }
    });
    return urls;
  }

  function findHeadersForCell(cell, headers = []) {
    const rect = cell.getBoundingClientRect?.();
    if (!rect || rect.width <= 0 || rect.height <= 0) return [];
    const centerX = rect.left + rect.width / 2;
    const matched = headers.filter(header => {
      const headerRect = header.rect;
      if (!headerRect || headerRect.width <= 0) return false;
      return centerX >= headerRect.left - 2 && centerX <= headerRect.right + 2;
    });
    if (!matched.length) return [];
    return matched
      .sort((a, b) => (a.rect.top - b.rect.top) || (a.rect.left - b.rect.left))
      .map(header => header.normalized);
  }

  function getMappedCellsForRow(row) {
    const headers = getVisibleHeaderCells();
    const peerRows = collectPeerRows(row);
    return getCellElements(peerRows).map((cell, index) => ({
      cell,
      index,
      text: getElementText(cell),
      images: getImageUrlsFromCell(cell),
      headers: findHeadersForCell(cell, headers)
    })).filter(item => item.text || item.images.length);
  }

  function cellMatchesAliases(cell, aliases = []) {
    const normalizedAliases = aliases.map(normalizeHeader).filter(Boolean);
    if (!normalizedAliases.length) return false;
    return cell.headers.some(header => normalizedAliases.some(alias => header === alias || header.includes(alias) || alias.includes(header)));
  }

  function readCellTextByAliases(cells, aliases = []) {
    return uniqueList(cells.filter(cell => cellMatchesAliases(cell, aliases)).map(cell => cell.text)).join(' / ');
  }

  function readCellImageByAliases(cells, aliases = []) {
    for (const cell of cells) {
      if (!cellMatchesAliases(cell, aliases)) continue;
      const url = cell.images.find(Boolean);
      if (url) return url;
    }
    return '';
  }

  function matchText(text, patterns) {
    for (const pattern of patterns) {
      const match = String(text || '').match(pattern);
      if (match?.[1]) return cleanCellValue(match[1]);
    }
    return '';
  }

  function readProductName(productCellText = '') {
    const text = cleanCellValue(productCellText);
    if (!text) return '';
    const beforeSpu = text.split(/SPU[:：]/)[0] || text;
    return cleanCellValue(beforeSpu.replace(/\s+/g, ' '));
  }

  function readSkuAttributes(skuCellText = '', skuId = '') {
    let text = cleanCellValue(skuCellText);
    if (!text) return '';
    text = text.replace(new RegExp(`SKU\\s*ID[:：]?\\s*${skuId}`, 'i'), '');
    text = text.replace(new RegExp(`SKU[:：]?\\s*${skuId}`, 'i'), '');
    return cleanCellValue(text.replace(/\s+/g, ' '));
  }

  function readSelectedDomRow(row) {
    const cells = getMappedCellsForRow(row);
    const productCell = readCellTextByAliases(cells, ['商品信息', '商品名称']);
    const skuCell = readCellTextByAliases(cells, ['SKU属性集', '商品属性集', 'SKU属性', '商品规格']);
    const customType = uniqueList([
      readCellTextByAliases(cells, ['定制工艺类型']),
      readCellTextByAliases(cells, ['一级类目']),
      readCellTextByAliases(cells, ['二级类目'])
    ]).join(' / ');
    const skuId = matchText(skuCell, [/SKU\s*ID[:：]?\s*(\d{5,})/i, /SKU[:：]\s*(\d{5,})/i]) || readCellTextByAliases(cells, ['商品SKU ID', 'SKU ID']);
    return {
      productName: readProductName(productCell),
      spuId: matchText(productCell, [/SPU[:：]\s*(\d{5,})/i]) || readCellTextByAliases(cells, ['商品SPU ID', 'SPU ID']),
      skcId: matchText(productCell, [/SKC[:：]\s*(\d{5,})/i]) || readCellTextByAliases(cells, ['商品SKC ID', 'SKC ID']),
      skcImage: readCellImageByAliases(cells, ['商品信息', 'SKC图片']),
      skuId: cleanCellValue(skuId),
      skuImage: readCellImageByAliases(cells, ['SKU属性集', 'SKU图片', 'SKU图']),
      skuCode: readCellTextByAliases(cells, ['SKU货号']),
      skuAttributes: readSkuAttributes(skuCell, skuId),
      customType,
      stockOrder: readCellTextByAliases(cells, ['备货单']),
      customSku: readCellTextByAliases(cells, ['定制SKU']),
      customArea: readCellTextByAliases(cells, ['定制区域']),
      textContent: cleanCellValue(readCellTextByAliases(cells, ['文字内容', '消费者定制内容', '消费者定制内容文字']), 'textContent'),
      textColor: readCellTextByAliases(cells, ['文字颜色']),
      font: readCellTextByAliases(cells, ['字体']),
      consumerImage: readCellImageByAliases(cells, ['消费者上传的图片', '消费者上传图片', '消费者定制内容图片']),
      customAreaImage: readCellImageByAliases(cells, ['定制区域图', '背景图']),
      previewImage: readCellImageByAliases(cells, ['合成预览图', '预览图'])
    };
  }

  function readSelectedDomRows(selectedRows = getOfficialSelectedRows()) {
    if (!selectedRows.length) return [];
    const rows = selectedRows.map(readSelectedDomRow);
    const missingCustomSkuCount = rows.filter(row => !row.customSku).length;
    if (missingCustomSkuCount) {
      throw new Error(`已勾选 ${selectedRows.length} 行，其中 ${missingCustomSkuCount} 行没有读取到“定制SKU”，已停止导出以避免漏数`);
    }
    const seen = new Set();
    const duplicated = [];
    const uniqueRows = [];
    for (const row of rows) {
      const key = String(row.customSku || '').trim();
      if (seen.has(key)) {
        duplicated.push(key);
        continue;
      }
      seen.add(key);
      uniqueRows.push(row);
    }
    if (duplicated.length) {
      throw new Error(`读取到重复的定制SKU：${duplicated.slice(0, 5).join('、')}，已停止导出以避免重复`);
    }
    return uniqueRows;
  }

  async function hydrateImages(rows, imageKeys, setStatus) {
    if (!imageKeys.length) return { rows, embeddedCount: 0, failedCount: 0 };
    const nextRows = rows.map(row => ({ ...row }));
    let embeddedCount = 0;
    let failedCount = 0;
    const total = nextRows.length;
    for (let start = 0; start < total; start += IMAGE_BATCH_SIZE) {
      const chunk = nextRows.slice(start, start + IMAGE_BATCH_SIZE).map(row => {
        const item = {};
        imageKeys.forEach(key => { item[key] = row[key] || ''; });
        return item;
      });
      setStatus?.(`正在抓取图片：${Math.min(start + IMAGE_BATCH_SIZE, total)} / ${total}`);
      const result = await sendRuntimeMessage({
        action: 'embedTemuImageUrls',
        rows: chunk,
        imageKeys
      }, 180000);
      if (!result?.success || !Array.isArray(result.rows)) {
        throw new Error(result?.error || '图片抓取失败');
      }
      result.rows.forEach((imageRow, offset) => {
        const row = nextRows[start + offset];
        imageKeys.forEach(key => {
          if (/^data:image\//i.test(imageRow?.[key] || '')) {
            row[key] = imageRow[key];
            embeddedCount += 1;
          } else if (row[key]) {
            row[key] = '';
            failedCount += 1;
          }
        });
      });
    }
    return { rows: nextRows, embeddedCount, failedCount };
  }

  function triggerDownload(filename, blob) {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    link.rel = 'noopener';
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    window.setTimeout(() => {
      link.remove();
      URL.revokeObjectURL(url);
    }, 1500);
  }

  function createXlsxBlob({ sheetName = '定制内容', fields = [], rows = [], imageFields = new Set() }) {
    const imageLayout = { width: 112, height: 112, columnWidth: 18, rowHeightPt: 92, name: '图片' };
    const xmlEscape = value => String(value ?? '')
      .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&apos;');
    const columnName = index => {
      let name = '';
      let n = index + 1;
      while (n > 0) {
        const rem = (n - 1) % 26;
        name = String.fromCharCode(65 + rem) + name;
        n = Math.floor((n - 1) / 26);
      }
      return name;
    };
    const textEncoder = new TextEncoder();
    const toBytes = value => value instanceof Uint8Array ? value : textEncoder.encode(String(value));
    const base64ToBytes = base64 => {
      const binary = atob(String(base64 || '').replace(/\s+/g, ''));
      const bytes = new Uint8Array(binary.length);
      for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
      return bytes;
    };
    const crcTable = (() => {
      const table = new Uint32Array(256);
      for (let i = 0; i < 256; i += 1) {
        let c = i;
        for (let k = 0; k < 8; k += 1) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
        table[i] = c >>> 0;
      }
      return table;
    })();
    const crc32 = bytes => {
      let crc = 0xffffffff;
      for (let i = 0; i < bytes.length; i += 1) crc = crcTable[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
      return (crc ^ 0xffffffff) >>> 0;
    };
    const write16 = (bytes, offset, value) => {
      bytes[offset] = value & 0xff;
      bytes[offset + 1] = (value >>> 8) & 0xff;
    };
    const write32 = (bytes, offset, value) => {
      bytes[offset] = value & 0xff;
      bytes[offset + 1] = (value >>> 8) & 0xff;
      bytes[offset + 2] = (value >>> 16) & 0xff;
      bytes[offset + 3] = (value >>> 24) & 0xff;
    };
    const dosTimestamp = date => {
      const year = Math.max(1980, date.getFullYear());
      return {
        time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
        date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate()
      };
    };
    const createZipBlob = files => {
      const localParts = [];
      const centralParts = [];
      const now = dosTimestamp(new Date());
      let offset = 0;
      for (const file of files) {
        const nameBytes = toBytes(file.name);
        const dataBytes = toBytes(file.data);
        const crc = crc32(dataBytes);
        const local = new Uint8Array(30 + nameBytes.length);
        write32(local, 0, 0x04034b50);
        write16(local, 4, 20);
        write16(local, 6, 0x0800);
        write16(local, 8, 0);
        write16(local, 10, now.time);
        write16(local, 12, now.date);
        write32(local, 14, crc);
        write32(local, 18, dataBytes.length);
        write32(local, 22, dataBytes.length);
        write16(local, 26, nameBytes.length);
        write16(local, 28, 0);
        local.set(nameBytes, 30);
        localParts.push(local, dataBytes);

        const central = new Uint8Array(46 + nameBytes.length);
        write32(central, 0, 0x02014b50);
        write16(central, 4, 20);
        write16(central, 6, 20);
        write16(central, 8, 0x0800);
        write16(central, 10, 0);
        write16(central, 12, now.time);
        write16(central, 14, now.date);
        write32(central, 16, crc);
        write32(central, 20, dataBytes.length);
        write32(central, 24, dataBytes.length);
        write16(central, 28, nameBytes.length);
        write16(central, 30, 0);
        write16(central, 32, 0);
        write16(central, 34, 0);
        write16(central, 36, 0);
        write32(central, 38, 0);
        write32(central, 42, offset);
        central.set(nameBytes, 46);
        centralParts.push(central);
        offset += local.length + dataBytes.length;
      }
      const centralSize = centralParts.reduce((sum, part) => sum + part.length, 0);
      const end = new Uint8Array(22);
      write32(end, 0, 0x06054b50);
      write16(end, 8, files.length);
      write16(end, 10, files.length);
      write32(end, 12, centralSize);
      write32(end, 16, offset);
      write16(end, 20, 0);
      return new Blob([...localParts, ...centralParts, end], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      });
    };
    const splitDataImage = value => {
      const match = String(value || '').trim().match(/^data:(image\/[a-z0-9.+-]+);base64,([\s\S]+)$/i);
      if (!match) return null;
      return { mimeType: match[1].toLowerCase(), base64: match[2].replace(/\s+/g, '') };
    };
    const imageExtForMime = mimeType => {
      const mime = String(mimeType || '').toLowerCase();
      if (mime.includes('png')) return 'png';
      if (mime.includes('gif')) return 'gif';
      if (mime.includes('bmp')) return 'bmp';
      if (mime.includes('webp')) return 'webp';
      return 'jpeg';
    };
    const imageContentType = ext => ({
      png: 'image/png',
      gif: 'image/gif',
      bmp: 'image/bmp',
      webp: 'image/webp'
    }[ext] || 'image/jpeg');
    const images = [];
    const cellXml = (value, rowNumber, columnIndex) => {
      const ref = `${columnName(columnIndex)}${rowNumber}`;
      return `<c r="${ref}" t="inlineStr"><is><t>${xmlEscape(value)}</t></is></c>`;
    };
    const sheetRows = [
      `<row r="1" ht="24" customHeight="1">${fields.map((field, index) => cellXml(field.label, 1, index)).join('')}</row>`
    ];
    rows.forEach((row, rowIndex) => {
      const rowNumber = rowIndex + 2;
      const cells = fields.map((field, columnIndex) => {
        const value = row[field.key] || '';
        const dataImage = imageFields.has(field.key) ? splitDataImage(value) : null;
        if (dataImage) {
          const ext = imageExtForMime(dataImage.mimeType);
          const imageIndex = images.length + 1;
          images.push({
            rowIndex: rowNumber - 1,
            columnIndex,
            fieldKey: field.key,
            ext,
            bytes: base64ToBytes(dataImage.base64),
            name: `image${imageIndex}.${ext}`
          });
          return cellXml('', rowNumber, columnIndex);
        }
        return cellXml(value, rowNumber, columnIndex);
      }).join('');
      const rowHeight = fields.some(field => imageFields.has(field.key) && splitDataImage(row[field.key] || ''))
        ? imageLayout.rowHeightPt
        : 28;
      sheetRows.push(`<row r="${rowNumber}" ht="${rowHeight}" customHeight="1">${cells}</row>`);
    });
    const colsXml = `<cols>${fields.map((field, index) => {
      const width = imageFields.has(field.key)
        ? imageLayout.columnWidth
        : Math.min(46, Math.max(12, String(field.label || '').length * 2 + 6));
      return `<col min="${index + 1}" max="${index + 1}" width="${width}" customWidth="1"/>`;
    }).join('')}</cols>`;
    const worksheetXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <sheetViews><sheetView workbookViewId="0"/></sheetViews>
  <sheetFormatPr defaultRowHeight="18"/>
  ${colsXml}
  <sheetData>${sheetRows.join('')}</sheetData>
  ${images.length ? '<drawing r:id="rId1"/>' : ''}
</worksheet>`;
    const mediaDefaults = Array.from(new Set(images.map(image => image.ext)))
      .map(ext => `<Default Extension="${ext}" ContentType="${imageContentType(ext)}"/>`)
      .join('');
    const contentTypesXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  ${mediaDefaults}
  <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
  <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
  <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
  <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
  <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
  ${images.length ? '<Override PartName="/xl/drawings/drawing1.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>' : ''}
</Types>`;
    const rootRelsXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>`;
    const workbookXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <sheets><sheet name="${xmlEscape(sheetName).slice(0, 31) || '定制内容'}" sheetId="1" r:id="rId1"/></sheets>
</workbook>`;
    const workbookRelsXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>`;
    const stylesXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <fonts count="1"><font><sz val="11"/><name val="Microsoft YaHei"/></font></fonts>
  <fills count="1"><fill><patternFill patternType="none"/></fill></fills>
  <borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
  <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
  <cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs>
  <cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
</styleSheet>`;
    const nowIso = new Date().toISOString();
    const coreXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <dc:creator>小青蛙电商助手</dc:creator>
  <cp:lastModifiedBy>小青蛙电商助手</cp:lastModifiedBy>
  <dcterms:created xsi:type="dcterms:W3CDTF">${nowIso}</dcterms:created>
  <dcterms:modified xsi:type="dcterms:W3CDTF">${nowIso}</dcterms:modified>
</cp:coreProperties>`;
    const appXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
  <Application>小青蛙电商助手</Application>
</Properties>`;
    const files = [
      { name: '[Content_Types].xml', data: contentTypesXml },
      { name: '_rels/.rels', data: rootRelsXml },
      { name: 'docProps/core.xml', data: coreXml },
      { name: 'docProps/app.xml', data: appXml },
      { name: 'xl/workbook.xml', data: workbookXml },
      { name: 'xl/_rels/workbook.xml.rels', data: workbookRelsXml },
      { name: 'xl/styles.xml', data: stylesXml },
      { name: 'xl/worksheets/sheet1.xml', data: worksheetXml }
    ];
    if (images.length) {
      const sheetRelsXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" Target="../drawings/drawing1.xml"/>
</Relationships>`;
      const drawingRelsXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
${images.map((image, index) => `  <Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/${xmlEscape(image.name)}"/>`).join('\n')}
</Relationships>`;
      const drawingXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
${images.map((image, index) => {
  const pictureId = index + 1;
  return `  <xdr:oneCellAnchor>
    <xdr:from><xdr:col>${image.columnIndex}</xdr:col><xdr:colOff>45720</xdr:colOff><xdr:row>${image.rowIndex}</xdr:row><xdr:rowOff>45720</xdr:rowOff></xdr:from>
    <xdr:ext cx="${imageLayout.width * 9525}" cy="${imageLayout.height * 9525}"/>
    <xdr:pic>
      <xdr:nvPicPr><xdr:cNvPr id="${pictureId}" name="${xmlEscape(imageLayout.name)}${pictureId}"/><xdr:cNvPicPr><a:picLocks noChangeAspect="1"/></xdr:cNvPicPr></xdr:nvPicPr>
      <xdr:blipFill><a:blip r:embed="rId${pictureId}"/><a:stretch><a:fillRect/></a:stretch></xdr:blipFill>
      <xdr:spPr><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></xdr:spPr>
    </xdr:pic>
    <xdr:clientData/>
  </xdr:oneCellAnchor>`;
}).join('\n')}
</xdr:wsDr>`;
      files.push(
        { name: 'xl/worksheets/_rels/sheet1.xml.rels', data: sheetRelsXml },
        { name: 'xl/drawings/drawing1.xml', data: drawingXml },
        { name: 'xl/drawings/_rels/drawing1.xml.rels', data: drawingRelsXml },
        ...images.map(image => ({ name: `xl/media/${image.name}`, data: image.bytes }))
      );
    }
    return { blob: createZipBlob(files), embeddedImageCount: images.length };
  }

  function buildOutputRows(rows, fields) {
    return rows.map(row => {
      const item = {};
      fields.forEach(field => {
        item[field.key] = field.image && !/^data:image\//i.test(row[field.key] || '')
          ? ''
          : (row[field.key] || '');
      });
      return item;
    });
  }

  function formatTimestampForFilename() {
    const date = new Date();
    const pad = n => String(n).padStart(2, '0');
    return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}_${pad(date.getHours())}${pad(date.getMinutes())}`;
  }

  async function runCustomizedContentExport(selectedFieldKeys, setStatus) {
    if (runtime.exporting) return;
    const selectedDomRows = getOfficialSelectedRows();
    if (!selectedDomRows.length) {
      throw new Error('请先勾选要导出的定制内容');
    }
    const officialButton = findOfficialExportButton();
    if (!officialButton || isDisabled(officialButton)) {
      throw new Error('请先按官方要求勾选可导出的定制内容');
    }

    runtime.exporting = true;
    try {
      setStatus('正在读取已勾选的定制内容...');
      const fields = FIELDS.filter(field => selectedFieldKeys.includes(field.key));
      const selectedRows = readSelectedDomRows(selectedDomRows);
      if (!selectedRows.length) {
        throw new Error('没有从已勾选行中识别到可导出的定制内容');
      }
      if (selectedRows.length !== selectedDomRows.length) {
        throw new Error(`已勾选 ${selectedDomRows.length} 行，只读取到 ${selectedRows.length} 条定制SKU，已停止导出以避免漏数`);
      }
      const rows = await resolveOfficialRowsForSelected(selectedRows, setStatus);
      const imageKeys = fields.filter(field => field.image).map(field => field.key);
      const hydrated = await hydrateImages(rows, imageKeys, setStatus);
      setStatus('正在生成带图片的 Excel...');
      const outputRows = buildOutputRows(hydrated.rows, fields);
      const { blob, embeddedImageCount } = createXlsxBlob({
        sheetName: '定制内容',
        fields,
        rows: outputRows,
        imageFields: new Set(imageKeys)
      });
      const filename = `定制内容图片版_${formatTimestampForFilename()}.xlsx`;
      triggerDownload(filename, blob);
      const warning = hydrated.failedCount ? `，${hydrated.failedCount} 张图片抓取失败已留空` : '';
      showToast(`导出完成：${rows.length} 行，嵌入图片 ${embeddedImageCount || hydrated.embeddedCount || 0} 张${warning}`, hydrated.failedCount ? 'warning' : 'success');
    } finally {
      runtime.exporting = false;
    }
  }

  function closeModal(modal) {
    modal?.remove?.();
  }

  function showExportDialog(event) {
    event?.preventDefault?.();
    event?.stopPropagation?.();
    const officialButton = findOfficialExportButton();
    if (!officialButton || isDisabled(officialButton) || !hasOfficialSelection()) {
      showToast('请先勾选要导出的定制内容', 'warning');
      return;
    }
    if (document.getElementById(MODAL_ID)) return;
    const modal = document.createElement('div');
    modal.id = MODAL_ID;
    modal.tabIndex = -1;
    modal.style.cssText = [
      'position:fixed',
      'inset:0',
      'z-index:2147483646',
      'display:flex',
      'align-items:center',
      'justify-content:center',
      'background:rgba(15,23,42,.42)'
    ].join(';');
    modal.innerHTML = `
      <div style="width:min(760px,calc(100vw - 28px));max-height:86vh;overflow:auto;background:#fff;border-radius:8px;box-shadow:0 24px 70px rgba(15,23,42,.30);">
        <div style="padding:18px 20px;border-bottom:1px solid #e5e7eb;display:flex;align-items:center;justify-content:space-between;gap:16px;">
          <div style="font-size:16px;font-weight:700;color:#111827;">定制内容导出项选择</div>
          <button type="button" data-close="1" style="border:0;background:transparent;font-size:22px;line-height:1;cursor:pointer;color:#6b7280;">×</button>
        </div>
        <div style="padding:18px 20px;">
          <label style="display:inline-flex;align-items:center;gap:8px;margin-bottom:12px;font-size:13px;color:#111827;cursor:pointer;">
            <input type="checkbox" id="xqCustomContentSelectAll">
            <span>全部选择</span>
          </label>
          <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px 14px;margin-bottom:16px;">
            ${FIELDS.map(field => `
              <label style="display:flex;align-items:center;gap:8px;border:1px solid #e5e7eb;border-radius:6px;padding:9px 10px;font-size:13px;color:#111827;cursor:pointer;background:#fff;">
                <input type="checkbox" class="xq-custom-content-field" value="${field.key}" ${field.default ? 'checked' : ''}>
                <span>${field.label}${field.image ? '（图片）' : ''}</span>
              </label>
            `).join('')}
          </div>
          <div id="xqCustomContentExportStatus" style="min-height:20px;font-size:13px;color:#2563eb;margin-bottom:14px;"></div>
          <div style="display:flex;justify-content:flex-end;gap:10px;">
            <button type="button" id="xqCustomContentCancelBtn" style="height:34px;padding:0 16px;border:1px solid #d1d5db;border-radius:6px;background:#fff;color:#374151;cursor:pointer;">取消</button>
            <button type="button" id="xqCustomContentConfirmBtn" style="height:34px;padding:0 18px;border:1px solid #2563eb;border-radius:6px;background:#2563eb;color:#fff;cursor:pointer;font-weight:600;">开始导出</button>
          </div>
        </div>
      </div>
    `;
    document.body.appendChild(modal);
    modal.focus?.();
    const fields = Array.from(modal.querySelectorAll('.xq-custom-content-field'));
    const selectAll = modal.querySelector('#xqCustomContentSelectAll');
    const status = modal.querySelector('#xqCustomContentExportStatus');
    const confirmBtn = modal.querySelector('#xqCustomContentConfirmBtn');
    const setStatus = text => { if (status) status.textContent = text || ''; };
    const syncSelectAll = () => {
      const checked = fields.filter(input => input.checked).length;
      selectAll.checked = checked === fields.length;
      selectAll.indeterminate = checked > 0 && checked < fields.length;
    };
    fields.forEach(input => input.addEventListener('change', syncSelectAll));
    selectAll.addEventListener('change', () => {
      fields.forEach(input => { input.checked = selectAll.checked; });
      syncSelectAll();
    });
    modal.addEventListener('click', e => {
      if (e.target === modal || e.target?.dataset?.close === '1') closeModal(modal);
    });
    modal.querySelector('#xqCustomContentCancelBtn')?.addEventListener('click', () => closeModal(modal));
    modal.addEventListener('keydown', event => {
      if (event.key === 'Escape') closeModal(modal);
    });
    confirmBtn?.addEventListener('click', async () => {
      const selected = fields.filter(input => input.checked).map(input => input.value);
      if (!selected.length) {
        setStatus('请至少选择一个导出项');
        return;
      }
      confirmBtn.disabled = true;
      confirmBtn.style.opacity = '0.7';
      try {
        await runCustomizedContentExport(selected, setStatus);
        closeModal(modal);
      } catch (error) {
        const message = error?.message || String(error);
        setStatus(message);
        showToast(`导出失败：${message}`, 'error');
      } finally {
        confirmBtn.disabled = false;
        confirmBtn.style.opacity = '1';
      }
    });
    syncSelectAll();
  }

  function createButton() {
    const button = document.createElement('button');
    button.id = BUTTON_ID;
    button.type = 'button';
    button.textContent = '小青蛙导出定制内容';
    button.title = '勾选定制内容后，导出带表格内图片的 Excel';
    button.style.cssText = [
      'display:inline-flex',
      'align-items:center',
      'justify-content:center',
      'height:34px',
      'padding:0 14px',
      'margin-left:8px',
      'border:1px solid #2563eb',
      'border-radius:4px',
      'background:#2563eb',
      'color:#fff',
      'font-size:14px',
      'font-weight:600',
      'cursor:pointer',
      'white-space:nowrap'
    ].join(';');
    button.addEventListener('click', showExportDialog, true);
    return button;
  }

  function syncButtonState(button, officialButton) {
    const disabled = !officialButton || isDisabled(officialButton) || !hasOfficialSelection();
    button.disabled = disabled;
    button.setAttribute('aria-disabled', disabled ? 'true' : 'false');
    button.title = disabled
      ? '请先勾选要导出的定制内容'
      : '勾选定制内容后，导出带表格内图片的 Excel';
    button.style.background = disabled ? '#f3f4f6' : '#2563eb';
    button.style.borderColor = disabled ? '#d1d5db' : '#2563eb';
    button.style.color = disabled ? '#9ca3af' : '#fff';
    button.style.cursor = disabled ? 'not-allowed' : 'pointer';
  }

  function ensureButton() {
    if (!document.body || !isTemuSellerBackendHost()) return;
    const existing = document.getElementById(BUTTON_ID);
    if (!isCustomizedContentPage()) {
      existing?.remove?.();
      return;
    }
    const officialButton = findOfficialExportButton();
    if (!officialButton) return;
    runtime.lastOfficialButton = officialButton;
    const button = existing || createButton();
    if (button.parentElement !== officialButton.parentElement) {
      button.remove();
      officialButton.parentElement?.insertBefore(button, officialButton.nextSibling);
    } else if (button.previousElementSibling !== officialButton) {
      officialButton.parentElement?.insertBefore(button, officialButton.nextSibling);
    }
    syncButtonState(button, officialButton);
  }

  function init() {
    if (!runtime.pageQueryListenerBound) {
      runtime.pageQueryListenerBound = true;
      window.addEventListener('message', handlePageQueryMessage);
    }
    ensureButton();
    if (runtime.observer) runtime.observer.disconnect();
    runtime.observer = new MutationObserver(() => ensureButton());
    runtime.observer.observe(document.documentElement, { childList: true, subtree: true });
    if (runtime.heartbeat) clearInterval(runtime.heartbeat);
    runtime.heartbeat = setInterval(ensureButton, 2000);
    document.addEventListener('change', event => {
      if (event.target?.matches?.('input[type="checkbox"]')) window.setTimeout(ensureButton, 0);
    }, true);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init, { once: true });
  } else {
    init();
  }
})();
