(function shangxiaozhuPddProductPublish() {
// 商小助拼多多商品发布助手
console.log("✅ 商小助拼多多商品发布助手已加载");

const PRICE_STORAGE_KEY = 'sxz_pdd_publish_target_price';
const PRICE_LOGIC_STORAGE_KEY = 'sxz_pdd_publish_price_logic_v1';
const CODE_FILL_RULE_STORAGE_KEY = 'sxz_pdd_code_fill_rule_v1';
const SKU_STORAGE_KEY = 'sxz_pdd_publish_saved_skus';
const ITEM_NO_STORAGE_KEY = 'sxz_pdd_publish_item_no';
const CODE_TEMPLATE_STORAGE_KEY = 'sxz_pdd_goods_code_templates';
const TEMPLATE_BATCH_DELAY_STORAGE_KEY = 'sxz_pdd_template_batch_extra_delay_ms';
const LEGACY_STORAGE_KEYS = Object.freeze({
    price: 'pdd_hq_saved_base_price',
    skus: 'pdd_hq_saved_skus',
    itemNo: 'pdd_hq_saved_item_no',
    templates: 'pdd_hq_goods_code_templates',
    delay: 'pdd_hq_template_batch_extra_delay_ms'
});
const IMAGE_CARD_SELECTOR = '[data-testid="beast-core-card"]';
const IMAGE_CHECKBOX_SELECTOR = '[data-testid="beast-core-checkbox"]';
const IMAGE_TITLE_SELECTOR = '[data-testid="beast-core-ellipsis"] span';
const CAROUSEL_IMAGE_ITEM_SELECTOR = '.MaterialModalButton_v2_imageWrapper__ukMzO';
const DETAIL_IMAGE_ITEM_SELECTOR = '.quick_decoration_v2_remarkImage__3tJyD';
const IMAGE_DELETE_SELECTOR = 'i.DeleteIcon_v2_delete__kLVDO, svg[data-testid="beast-core-icon-close"]';
const CAROUSEL_KEEP_FIRST_BTN_ID = 'pdd-carousel-keep-first-btn';
const DETAIL_KEEP_FIRST_BTN_ID = 'pdd-detail-keep-first-btn';
const SPEC_CONTAINER_SELECTOR = '.package-container.color';
const SPEC_ROW_SELECTOR = 'div[data-testid="beast-core-grid-row"]';
const SPEC_CLEAR_BTN_ID = 'pdd-spec-clear-prefilled-btn';
const SKU_ACTIONS_WRAP_ID = 'pdd-sku-actions-wrap';
const SKU_EDITOR_INLINE_BTN_ID = 'pdd-inline-sku-editor-btn';
const SKU_GENERATE_INLINE_BTN_ID = 'pdd-inline-generate-sku-btn';
const PRICE_ACTIONS_WRAP_ID = 'pdd-price-actions-wrap';
const PRICE_INLINE_INPUT_ID = 'pdd-inline-price-input';
const PRICE_INLINE_FILL_BTN_ID = 'pdd-inline-price-fill-btn';
const PRICE_LOGIC_BTN_ID = 'sxz-pdd-price-logic-btn';
const PRICE_LOGIC_DIALOG_ID = 'sxz-pdd-price-logic-dialog';
const GOODS_LIST_CODE_FILL_BTN_ID = 'pdd-goods-list-code-fill-btn';
const GOODS_LIST_TEMPLATE_BATCH_BTN_ID = 'pdd-goods-list-template-batch-btn';
const GOODS_LIST_TEMPLATE_DELAY_BTN_ID = 'pdd-goods-list-template-delay-btn';
const GOODS_LIST_CODE_RULE_BTN_ID = 'sxz-pdd-code-rule-btn';
const CODE_RULE_DIALOG_ID = 'sxz-pdd-code-rule-dialog';
const IMAGE_SELECT_ACTIONS_ID = 'pdd-image-select-actions';
const MAIN_IMAGE_SELECT_BTN_ID = 'pdd-main-image-select-btn';
const DETAIL_IMAGE_SELECT_BTN_ID = 'pdd-detail-image-select-btn';
const IMAGE_HELP_LINK_ID = 'sxz-pdd-image-help-link';
const CODE_TEMPLATE_ACTIONS_ID = 'pdd-code-template-actions';
const CODE_TEMPLATE_SAVE_BTN_ID = 'pdd-code-template-save-btn';
const CODE_TEMPLATE_APPLY_BTN_ID = 'pdd-code-template-apply-btn';
const CODE_TEMPLATE_CLEAR_BTN_ID = 'pdd-code-template-clear-btn';
const CODE_TEMPLATE_STATUS_ID = 'pdd-code-template-status';
const CODE_TEMPLATE_REPORT_ID = 'pdd-code-template-report';
const CODE_TEMPLATE_DEBUG_PANEL_ID = 'pdd-code-template-debug-panel';
const PRIMARY_BTN_COLOR = '#407dff';
const TEMPLATE_BATCH_BASE_CONFIRM_DELAY_MS = 1500;
const TEMPLATE_BATCH_DELAY_OPTIONS = [0, 1000, 2000, 3000];
let hasInitializedHelperPanels = false;
let imageSpaceObserverStarted = false;
let isCarouselKeepFirstRunning = false;
let isDetailKeepFirstRunning = false;
let isSpecClearRunning = false;
let isSkuGenerateRunning = false;
let isPriceBatchRunning = false;
let isGoodsListCodeFillRunning = false;
let isGoodsTemplateBatchRunning = false;
let isImageSelectRunning = false;

function getMigratedStorageValue(primaryKey, legacyKey) {
    const current = localStorage.getItem(primaryKey);
    if (current !== null) return current;
    const legacy = legacyKey ? localStorage.getItem(legacyKey) : null;
    if (legacy !== null) localStorage.setItem(primaryKey, legacy);
    return legacy;
}

function isGoodsAddPage() {
    return location.hostname === 'mms.pinduoduo.com' && location.pathname.includes('goods_add');
}

function isGoodsListPage() {
    return location.hostname === 'mms.pinduoduo.com' && location.pathname === '/goods/goods_list';
}

function isGoodsCodeDialogSupportedPage() {
    return isGoodsListPage();
}

function getDefaultFloatingPositionCss() {
    if (isGoodsAddPage()) {
        return 'top: 50%; left: 20px; right: auto; bottom: auto; transform: translateY(-50%);';
    }
    return 'bottom: 150px; right: 20px; left: auto; top: auto; transform: none;';
}

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

setInterval(() => {
    if (isGoodsListPage() && (
        !document.getElementById(GOODS_LIST_CODE_FILL_BTN_ID) ||
        !document.getElementById(GOODS_LIST_TEMPLATE_BATCH_BTN_ID) ||
        !document.getElementById(GOODS_LIST_TEMPLATE_DELAY_BTN_ID) ||
        !document.getElementById(GOODS_LIST_CODE_RULE_BTN_ID)
    )) {
        createMainPanel();
    }
    if (!isGoodsListPage()) {
        document.getElementById(GOODS_LIST_CODE_FILL_BTN_ID)?.remove();
        document.getElementById(GOODS_LIST_TEMPLATE_BATCH_BTN_ID)?.remove();
        document.getElementById(GOODS_LIST_TEMPLATE_DELAY_BTN_ID)?.remove();
        document.getElementById(GOODS_LIST_CODE_RULE_BTN_ID)?.remove();
        document.getElementById(CODE_TEMPLATE_REPORT_ID)?.remove();
        document.getElementById(CODE_TEMPLATE_DEBUG_PANEL_ID)?.remove();
    }
}, 3000);

function initializeHelperPanels() {
    if (hasInitializedHelperPanels) return;
    hasInitializedHelperPanels = true;
    createMainPanel();
    syncImageSpaceButtons();
    startImageSpaceObserver();
}

function startImageSpaceObserver() {
    if (imageSpaceObserverStarted) return;
    imageSpaceObserverStarted = true;

    let syncTimer = 0;
    let lastSyncAt = 0;
    const scheduleSync = () => {
        if (syncTimer) return;
        const elapsed = Date.now() - lastSyncAt;
        const delay = Math.max(180, 700 - elapsed);
        syncTimer = window.setTimeout(() => {
            syncTimer = 0;
            lastSyncAt = Date.now();
            syncImageSpaceButtons();
        }, delay);
    };

    const observer = new MutationObserver(() => {
        scheduleSync();
    });

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

    window.addEventListener('resize', scheduleSync);
}

function createMainPanel() {
    document.getElementById('pdd-calc-container')?.remove();
    document.getElementById('pdd-calc-minimized')?.remove();

    if (!isGoodsListPage()) {
        document.getElementById(GOODS_LIST_CODE_FILL_BTN_ID)?.remove();
        document.getElementById(GOODS_LIST_TEMPLATE_BATCH_BTN_ID)?.remove();
        document.getElementById(GOODS_LIST_TEMPLATE_DELAY_BTN_ID)?.remove();
        document.getElementById(GOODS_LIST_CODE_RULE_BTN_ID)?.remove();
        document.getElementById(CODE_TEMPLATE_REPORT_ID)?.remove();
        document.getElementById(CODE_TEMPLATE_DEBUG_PANEL_ID)?.remove();
        return;
    }

    let fillBtn = document.getElementById(GOODS_LIST_CODE_FILL_BTN_ID);
    if (!fillBtn) {
        fillBtn = document.createElement('button');
        fillBtn.id = GOODS_LIST_CODE_FILL_BTN_ID;
        fillBtn.type = 'button';
        fillBtn.innerText = '编码填充';
        fillBtn.style.cssText = `position: fixed; right: 20px; bottom: 170px; z-index: 99999; ${getBlueButtonCss({ height: 38, minWidth: 108, fontSize: 14, radius: 10 })}`;
        bindFloatingButtonDrag(fillBtn);
        fillBtn.onclick = async () => {
            if (fillBtn.dataset.isDragged === 'true') {
                fillBtn.dataset.isDragged = 'false';
                return;
            }
            if (isGoodsTemplateBatchRunning) return showToast('⚠️ 母版批量套用执行中，请稍等');
            if (isGoodsListCodeFillRunning) return showToast('⚠️ 编码填充执行中，请稍等');
            isGoodsListCodeFillRunning = true;
            setInlineButtonState(fillBtn, true, '编码填充');
            try {
                runPipelineFill();
            } finally {
                setTimeout(() => {
                    isGoodsListCodeFillRunning = false;
                    setInlineButtonState(fillBtn, false, '编码填充');
                }, 600);
            }
        };

        document.body.appendChild(fillBtn);
    }

    let batchBtn = document.getElementById(GOODS_LIST_TEMPLATE_BATCH_BTN_ID);
    if (!batchBtn) {
        batchBtn = document.createElement('button');
        batchBtn.id = GOODS_LIST_TEMPLATE_BATCH_BTN_ID;
        batchBtn.type = 'button';
        batchBtn.innerText = '批量套母版';
        batchBtn.style.cssText = `position: fixed; right: 20px; bottom: 220px; z-index: 99999; ${getBlueButtonCss({ height: 38, minWidth: 118, fontSize: 14, radius: 10 })}`;
        bindFloatingButtonDrag(batchBtn);
        batchBtn.onclick = async () => {
            if (batchBtn.dataset.isDragged === 'true') {
                batchBtn.dataset.isDragged = 'false';
                return;
            }
            await runTemplateBatchFillCurrentPage();
        };

        document.body.appendChild(batchBtn);
    }

    let delayBtn = document.getElementById(GOODS_LIST_TEMPLATE_DELAY_BTN_ID);
    if (!delayBtn) {
        delayBtn = document.createElement('button');
        delayBtn.id = GOODS_LIST_TEMPLATE_DELAY_BTN_ID;
        delayBtn.type = 'button';
        delayBtn.innerText = getTemplateBatchDelayButtonText();
        delayBtn.style.cssText = `position: fixed; right: 20px; bottom: 270px; z-index: 99999; ${getBlueButtonCss({ height: 34, minWidth: 108, fontSize: 13, radius: 10 })}`;
        bindFloatingButtonDrag(delayBtn);
        delayBtn.onclick = () => {
            if (delayBtn.dataset.isDragged === 'true') {
                delayBtn.dataset.isDragged = 'false';
                return;
            }
            cycleTemplateBatchExtraDelay();
        };

        document.body.appendChild(delayBtn);
    }

    let ruleBtn = document.getElementById(GOODS_LIST_CODE_RULE_BTN_ID);
    if (!ruleBtn) {
        ruleBtn = document.createElement('button');
        ruleBtn.id = GOODS_LIST_CODE_RULE_BTN_ID;
        ruleBtn.type = 'button';
        ruleBtn.innerText = '编码填充规则';
        ruleBtn.style.cssText = `position: fixed; right: 20px; bottom: 316px; z-index: 99999; ${getBlueButtonCss({ height: 36, minWidth: 118, fontSize: 13, radius: 10 })}`;
        bindFloatingButtonDrag(ruleBtn);
        ruleBtn.onclick = () => {
            if (ruleBtn.dataset.isDragged === 'true') {
                ruleBtn.dataset.isDragged = 'false';
                return;
            }
            showCodeRuleDialog();
        };
        document.body.appendChild(ruleBtn);
    }

    updateTemplateBatchDelayButton();
}

function bindFloatingButtonDrag(button) {
    let dragging = false;
    let offsetX = 0;
    let offsetY = 0;
    let startX = 0;
    let startY = 0;

    button.dataset.isDragged = 'false';

    button.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return;
        dragging = true;
        button.dataset.isDragged = 'false';
        startX = e.clientX;
        startY = e.clientY;

        let rect = button.getBoundingClientRect();
        offsetX = e.clientX - rect.left;
        offsetY = e.clientY - rect.top;

        button.style.left = `${rect.left}px`;
        button.style.top = `${rect.top}px`;
        button.style.right = 'auto';
        button.style.bottom = 'auto';
        button.style.cursor = 'grabbing';
    });

    window.addEventListener('mousemove', (e) => {
        if (!dragging) return;
        e.preventDefault();

        if (Math.abs(e.clientX - startX) > 3 || Math.abs(e.clientY - startY) > 3) {
            button.dataset.isDragged = 'true';
        }

        button.style.left = `${e.clientX - offsetX}px`;
        button.style.top = `${e.clientY - offsetY}px`;
    });

    window.addEventListener('mouseup', () => {
        if (!dragging) return;
        dragging = false;
        button.style.cursor = 'pointer';
    });
}

function getTemplateBatchExtraDelayMs() {
    let saved = Number(getMigratedStorageValue(TEMPLATE_BATCH_DELAY_STORAGE_KEY, LEGACY_STORAGE_KEYS.delay) || 0);
    return TEMPLATE_BATCH_DELAY_OPTIONS.includes(saved) ? saved : 0;
}

function setTemplateBatchExtraDelayMs(value) {
    let nextValue = TEMPLATE_BATCH_DELAY_OPTIONS.includes(Number(value)) ? Number(value) : 0;
    localStorage.setItem(TEMPLATE_BATCH_DELAY_STORAGE_KEY, String(nextValue));
    return nextValue;
}

function getTemplateBatchDelayButtonText() {
    return `间隔+${Math.round(getTemplateBatchExtraDelayMs() / 1000)}s`;
}

function updateTemplateBatchDelayButton(disabled = false) {
    let button = document.getElementById(GOODS_LIST_TEMPLATE_DELAY_BTN_ID);
    if (!button) return;

    button.innerText = getTemplateBatchDelayButtonText();
    button.disabled = disabled;
    button.style.opacity = disabled ? '0.72' : '1';
    button.style.cursor = disabled ? 'wait' : 'pointer';
}

function cycleTemplateBatchExtraDelay() {
    let current = getTemplateBatchExtraDelayMs();
    let currentIndex = TEMPLATE_BATCH_DELAY_OPTIONS.indexOf(current);
    let nextIndex = currentIndex >= 0 ? (currentIndex + 1) % TEMPLATE_BATCH_DELAY_OPTIONS.length : 0;
    let nextValue = setTemplateBatchExtraDelayMs(TEMPLATE_BATCH_DELAY_OPTIONS[nextIndex]);
    updateTemplateBatchDelayButton(false);
    showToast(`✅ 母版批量额外间隔已设为 +${Math.round(nextValue / 1000)}s\n默认基础等待 ${TEMPLATE_BATCH_BASE_CONFIRM_DELAY_MS / 1000}s`);
}

function getTemplateBatchPostConfirmDelayMs() {
    return TEMPLATE_BATCH_BASE_CONFIRM_DELAY_MS + getTemplateBatchExtraDelayMs();
}

function ensurePublishDialogStyles() {
    if (document.getElementById('sxz-pdd-publish-dialog-styles')) return;
    let style = document.createElement('style');
    style.id = 'sxz-pdd-publish-dialog-styles';
    style.textContent = `
        .sxz-pdd-overlay{position:fixed;inset:0;z-index:2147483646;background:rgba(20,31,54,.38);display:flex;align-items:center;justify-content:center;padding:24px;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif}
        .sxz-pdd-dialog{width:min(680px,calc(100vw - 32px));max-height:calc(100vh - 48px);overflow:auto;background:#fff;border:1px solid #e5eaf3;border-radius:18px;box-shadow:0 24px 70px rgba(31,45,75,.24);color:#202939}
        .sxz-pdd-dialog *{box-sizing:border-box}.sxz-pdd-dialog-head{display:flex;align-items:flex-start;justify-content:space-between;padding:22px 24px 16px;border-bottom:1px solid #edf0f5}.sxz-pdd-dialog-head h2{margin:0;font-size:20px;line-height:1.35}.sxz-pdd-dialog-head p{margin:6px 0 0;color:#758198;font-size:13px}.sxz-pdd-close{width:32px;height:32px;border:0;border-radius:9px;background:#f2f4f8;color:#566177;font-size:20px;cursor:pointer}
        .sxz-pdd-dialog-body{padding:20px 24px}.sxz-pdd-segment{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:5px;background:#f1f4fa;border-radius:12px;margin-bottom:18px}.sxz-pdd-segment label{position:relative}.sxz-pdd-segment input{position:absolute;opacity:0}.sxz-pdd-segment span{display:block;text-align:center;padding:10px 12px;border-radius:9px;color:#67738a;font-weight:600;cursor:pointer}.sxz-pdd-segment input:checked+span{background:#fff;color:#4054ee;box-shadow:0 2px 8px rgba(45,60,100,.12)}
        .sxz-pdd-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.sxz-pdd-field{display:flex;flex-direction:column;gap:7px}.sxz-pdd-field.full{grid-column:1/-1}.sxz-pdd-field label{font-size:13px;font-weight:650;color:#39465d}.sxz-pdd-field input,.sxz-pdd-field select{width:100%;height:40px;border:1px solid #d7deea;border-radius:9px;padding:0 11px;font-size:14px;outline:none;background:#fff}.sxz-pdd-field input:focus,.sxz-pdd-field select:focus{border-color:#6073ff;box-shadow:0 0 0 3px rgba(79,99,255,.12)}
        .sxz-pdd-note{margin-top:16px;padding:13px 14px;border-radius:11px;background:#f6f8ff;border:1px solid #dfe5ff;color:#53617b;font-size:13px;line-height:1.65}.sxz-pdd-example{margin-top:16px;padding:16px;border:1px solid #e5e9f1;border-radius:12px;background:#fbfcfe}.sxz-pdd-example strong{font-size:14px}.sxz-pdd-result{margin-top:10px;font-size:15px;color:#26324a}.sxz-pdd-result b{color:#4054ee;font-size:20px}
        .sxz-pdd-dialog-actions{display:flex;justify-content:flex-end;gap:10px;padding:16px 24px 22px}.sxz-pdd-dialog-actions button{height:38px;padding:0 18px;border-radius:9px;font-size:14px;font-weight:650;cursor:pointer}.sxz-pdd-secondary{border:1px solid #d8deea;background:#fff;color:#526077}.sxz-pdd-primary{border:1px solid #4f63ff;background:#4f63ff;color:#fff}.sxz-pdd-link{color:#4054ee;text-decoration:none;font-size:13px;font-weight:600;white-space:nowrap}.sxz-pdd-link:hover{text-decoration:underline}
        .sxz-pdd-price-fill-primary{display:inline-flex;align-items:center;justify-content:center;min-width:158px;height:34px;padding:0 14px;border:1px solid #13845d;border-radius:9px;color:#fff;background:linear-gradient(180deg,#25b77f 0%,#159769 100%);box-shadow:0 5px 14px rgba(21,151,105,.25);font-size:13px;font-weight:700;line-height:1;white-space:nowrap;cursor:pointer;transition:transform .16s ease,background .16s ease,border-color .16s ease,box-shadow .16s ease}
        .sxz-pdd-price-fill-primary:hover:not(:disabled){transform:translateY(-1px);border-color:#0f7652;background:linear-gradient(180deg,#2ac188 0%,#128e62 100%);box-shadow:0 7px 17px rgba(21,151,105,.32)}.sxz-pdd-price-fill-primary:active:not(:disabled){transform:translateY(0);box-shadow:0 3px 9px rgba(21,151,105,.24)}.sxz-pdd-price-fill-primary:focus-visible{outline:3px solid rgba(21,151,105,.22);outline-offset:2px}.sxz-pdd-price-fill-primary:disabled{transform:none;box-shadow:0 2px 7px rgba(21,151,105,.18)}
        @media(max-width:620px){.sxz-pdd-grid{grid-template-columns:1fr}.sxz-pdd-field.full{grid-column:auto}.sxz-pdd-overlay{padding:12px}.sxz-pdd-dialog-body,.sxz-pdd-dialog-head{padding-left:18px;padding-right:18px}}
    `;
    document.documentElement.appendChild(style);
}

function openPublishDialog(id, title, subtitle, bodyHtml) {
    document.getElementById(id)?.remove();
    ensurePublishDialogStyles();
    let overlay = document.createElement('div');
    overlay.id = id;
    overlay.className = 'sxz-pdd-overlay';
    overlay.innerHTML = `<section class="sxz-pdd-dialog" role="dialog" aria-modal="true" aria-labelledby="${id}-title"><header class="sxz-pdd-dialog-head"><div><h2 id="${id}-title"></h2><p></p></div><button type="button" class="sxz-pdd-close" aria-label="关闭">×</button></header><div class="sxz-pdd-dialog-body">${bodyHtml}</div><footer class="sxz-pdd-dialog-actions"><button type="button" class="sxz-pdd-secondary" data-role="cancel">取消</button><button type="button" class="sxz-pdd-primary" data-role="save">保存设置</button></footer></section>`;
    overlay.querySelector('h2').textContent = title;
    overlay.querySelector('.sxz-pdd-dialog-head p').textContent = subtitle;
    const close = () => overlay.remove();
    overlay.querySelector('.sxz-pdd-close').onclick = close;
    overlay.querySelector('[data-role="cancel"]').onclick = close;
    overlay.addEventListener('mousedown', (event) => { if (event.target === overlay) close(); });
    document.body.appendChild(overlay);
    overlay.querySelector('input,select,button')?.focus();
    return { overlay, close };
}

function loadCodeFillRule() {
    try {
        return { configured: false, mode: 'auto', template: '{种子前缀}{尺码}{种子后缀}', fixedPrefix: '', fixedSuffix: '', sizeMode: 'first', ...JSON.parse(localStorage.getItem(CODE_FILL_RULE_STORAGE_KEY) || '{}') };
    } catch (_) {
        return { configured: false, mode: 'auto', template: '{种子前缀}{尺码}{种子后缀}', fixedPrefix: '', fixedSuffix: '', sizeMode: 'first' };
    }
}

function buildCodeFromRule(seedRule, sizeText, settings = loadCodeFillRule()) {
    if (!settings.configured || settings.mode === 'auto') return `${seedRule.start}${sizeText}${seedRule.end}`;
    const prefix = settings.fixedPrefix || seedRule.start || '';
    const suffix = settings.fixedSuffix || seedRule.end || '';
    return String(settings.template || '{种子前缀}{尺码}{种子后缀}')
        .replaceAll('{种子前缀}', prefix)
        .replaceAll('{尺码}', sizeText)
        .replaceAll('{种子后缀}', suffix);
}

function showCodeRuleDialog() {
    const saved = loadCodeFillRule();
    const { overlay, close } = openPublishDialog(
        CODE_RULE_DIALOG_ID,
        '编码填充规则',
        '设置“编码填充”如何根据一条已填种子编码生成其余尺码。',
        `<div class="sxz-pdd-segment"><label><input type="radio" name="code-mode" value="auto"><span>自动识别（推荐）</span></label><label><input type="radio" name="code-mode" value="custom"><span>自定义模板</span></label></div><div class="sxz-pdd-grid"><div class="sxz-pdd-field full"><label>编码模板</label><input data-role="template" placeholder="{种子前缀}{尺码}{种子后缀}"></div><div class="sxz-pdd-field"><label>固定前缀（留空则从种子识别）</label><input data-role="prefix" placeholder="例如 XHQ-123-"></div><div class="sxz-pdd-field"><label>固定后缀（留空则从种子识别）</label><input data-role="suffix" placeholder="例如 码"></div><div class="sxz-pdd-field full"><label>区间尺码写法</label><select data-role="size-mode"><option value="first">只取区间第一个尺码（35-36 → 35）</option><option value="full">保留完整区间（35-36 → 35-36）</option></select></div></div><div class="sxz-pdd-note">当前规则示例：先手工填写一条 <b>XHQ-123-35码</b>，系统识别“XHQ-123-”为种子前缀、“码”为种子后缀，再按页面尺码生成 XHQ-123-36码、XHQ-123-37码。模板可使用三个变量：{种子前缀}、{尺码}、{种子后缀}。</div><div class="sxz-pdd-example"><strong>计算示例</strong><div class="sxz-pdd-grid" style="margin-top:12px"><div class="sxz-pdd-field"><label>种子编码</label><input data-role="sample-seed" value="XHQ-123-35码"></div><div class="sxz-pdd-field"><label>目标尺码</label><input data-role="sample-size" value="36"></div></div><div class="sxz-pdd-result">生成结果：<b data-role="sample-result"></b></div></div>`
    );
    overlay.querySelector(`input[name="code-mode"][value="${saved.mode}"]`).checked = true;
    overlay.querySelector('[data-role="template"]').value = saved.template;
    overlay.querySelector('[data-role="prefix"]').value = saved.fixedPrefix;
    overlay.querySelector('[data-role="suffix"]').value = saved.fixedSuffix;
    overlay.querySelector('[data-role="size-mode"]').value = saved.sizeMode;
    const refresh = () => {
        const seed = cleanImageLabel(overlay.querySelector('[data-role="sample-seed"]').value);
        const size = cleanImageLabel(overlay.querySelector('[data-role="sample-size"]').value) || '36';
        const seedRule = extractRuleStrict(seed, '35');
        const previewSettings = { configured: true, mode: overlay.querySelector('input[name="code-mode"]:checked').value, template: overlay.querySelector('[data-role="template"]').value, fixedPrefix: overlay.querySelector('[data-role="prefix"]').value, fixedSuffix: overlay.querySelector('[data-role="suffix"]').value };
        overlay.querySelector('[data-role="sample-result"]').textContent = buildCodeFromRule(seedRule, size, previewSettings) || '—';
    };
    overlay.querySelectorAll('input,select').forEach(node => node.addEventListener('input', refresh));
    refresh();
    overlay.querySelector('[data-role="save"]').onclick = () => {
        const settings = { configured: true, mode: overlay.querySelector('input[name="code-mode"]:checked').value, template: cleanImageLabel(overlay.querySelector('[data-role="template"]').value) || '{种子前缀}{尺码}{种子后缀}', fixedPrefix: overlay.querySelector('[data-role="prefix"]').value.trim(), fixedSuffix: overlay.querySelector('[data-role="suffix"]').value.trim(), sizeMode: overlay.querySelector('[data-role="size-mode"]').value };
        localStorage.setItem(CODE_FILL_RULE_STORAGE_KEY, JSON.stringify(settings));
        close();
        showToast('✅ 编码填充规则已保存');
    };
}

function loadPriceLogic() {
    const defaults = { configured: false, mode: 'direct', discountPercent: 70, couponEnabled: true, couponAmount: 30, adjustment: 0, cost: 50, marginPercent: 30 };
    try {
        return { ...defaults, ...JSON.parse(localStorage.getItem(PRICE_LOGIC_STORAGE_KEY) || '{}') };
    } catch (_) {
        return defaults;
    }
}

function roundPriceUp(value) {
    return Math.ceil((Number(value) + Number.EPSILON) * 100) / 100;
}

function calculatePricePlan(inputTargetPrice, settings = loadPriceLogic()) {
    const rate = Math.max(0.01, Number(settings.discountPercent) / 100);
    const coupon = settings.couponEnabled ? Math.max(0, Number(settings.couponAmount) || 0) : 0;
    const adjustment = Number(settings.adjustment) || 0;
    let targetActualPrice = Number(inputTargetPrice);
    if (settings.mode === 'margin') {
        const cost = Math.max(0, Number(settings.cost) || 0);
        const margin = Math.min(0.95, Math.max(0, Number(settings.marginPercent) / 100));
        targetActualPrice = cost / (1 - margin);
    }
    const listingPrice = roundPriceUp(((targetActualPrice + coupon) / rate) + adjustment);
    return {
        targetActualPrice: roundPriceUp(targetActualPrice),
        listingPrice,
        coupon,
        rate,
        grossProfit: roundPriceUp(targetActualPrice - (Number(settings.cost) || 0))
    };
}

function showPriceLogicDialog() {
    const saved = loadPriceLogic();
    const { overlay, close } = openPublishDialog(
        PRICE_LOGIC_DIALOG_ID,
        '价格设置逻辑',
        '选择一种逻辑。批量填充时会把目标实售价换算成商品上架价。',
        `<div class="sxz-pdd-segment"><label><input type="radio" name="price-mode" value="direct"><span>情景一 · 目标实售价反推</span></label><label><input type="radio" name="price-mode" value="margin"><span>情景二 · 成本毛利反推</span></label></div><div class="sxz-pdd-grid"><div class="sxz-pdd-field" data-direct><label>目标实际售卖价格（元）</label><input data-role="target" type="number" min="0" step="0.01" placeholder="例如 69"></div><div class="sxz-pdd-field" data-margin><label>商品成本（元）</label><input data-role="cost" type="number" min="0" step="0.01"></div><div class="sxz-pdd-field" data-margin><label>期望毛利率（%）</label><input data-role="margin" type="number" min="0" max="95" step="0.1"></div><div class="sxz-pdd-field"><label>活动折扣比例（%）</label><input data-role="discount" type="number" min="1" max="100" step="0.1"></div><div class="sxz-pdd-field"><label>优惠券</label><select data-role="coupon-enabled"><option value="true">添加优惠券</option><option value="false">不添加优惠券</option></select></div><div class="sxz-pdd-field"><label>优惠券金额（元）</label><input data-role="coupon" type="number" min="0" step="0.01"></div><div class="sxz-pdd-field"><label>上架价额外调整（元）</label><input data-role="adjustment" type="number" step="0.01" value="0"></div></div><div class="sxz-pdd-note" data-role="formula"></div><div class="sxz-pdd-example"><strong>计算示例</strong><div class="sxz-pdd-result">目标实售价：<b data-role="actual-result"></b> 元　上架填充价：<b data-role="listing-result"></b> 元</div><div class="sxz-pdd-result" data-role="profit-result"></div></div>`
    );
    const inlineInput = document.getElementById(PRICE_INLINE_INPUT_ID);
    overlay.querySelector(`input[name="price-mode"][value="${saved.mode}"]`).checked = true;
    overlay.querySelector('[data-role="target"]').value = inlineInput?.value || getMigratedStorageValue(PRICE_STORAGE_KEY, LEGACY_STORAGE_KEYS.price) || '69';
    overlay.querySelector('[data-role="cost"]').value = saved.cost;
    overlay.querySelector('[data-role="margin"]').value = saved.marginPercent;
    overlay.querySelector('[data-role="discount"]').value = saved.discountPercent;
    overlay.querySelector('[data-role="coupon-enabled"]').value = String(saved.couponEnabled);
    overlay.querySelector('[data-role="coupon"]').value = saved.couponAmount;
    overlay.querySelector('[data-role="adjustment"]').value = saved.adjustment;
    const read = () => ({
        configured: true,
        mode: overlay.querySelector('input[name="price-mode"]:checked').value,
        cost: Number(overlay.querySelector('[data-role="cost"]').value) || 0,
        marginPercent: Number(overlay.querySelector('[data-role="margin"]').value) || 0,
        discountPercent: Number(overlay.querySelector('[data-role="discount"]').value) || 100,
        couponEnabled: overlay.querySelector('[data-role="coupon-enabled"]').value === 'true',
        couponAmount: Number(overlay.querySelector('[data-role="coupon"]').value) || 0,
        adjustment: Number(overlay.querySelector('[data-role="adjustment"]').value) || 0
    });
    const refresh = () => {
        const settings = read();
        overlay.querySelectorAll('[data-direct]').forEach(node => { node.style.display = settings.mode === 'direct' ? 'flex' : 'none'; });
        overlay.querySelectorAll('[data-margin]').forEach(node => { node.style.display = settings.mode === 'margin' ? 'flex' : 'none'; });
        const target = Number(overlay.querySelector('[data-role="target"]').value) || 0;
        const result = calculatePricePlan(target, settings);
        overlay.querySelector('[data-role="actual-result"]').textContent = result.targetActualPrice.toFixed(2);
        overlay.querySelector('[data-role="listing-result"]').textContent = result.listingPrice.toFixed(2);
        overlay.querySelector('[data-role="profit-result"]').textContent = settings.mode === 'margin' ? `预计单件毛利：${result.grossProfit.toFixed(2)} 元` : '';
        overlay.querySelector('[data-role="formula"]').textContent = settings.mode === 'direct'
            ? '公式：上架价 =（目标实际售卖价格 + 优惠券金额）÷ 活动折扣比例 + 额外调整。示例：目标实售价 69 元、7 折、30 元券，上架价为 141.43 元。'
            : '公式：目标实售价 = 商品成本 ÷（1 - 期望毛利率）；再按活动折扣和优惠券反推上架价。毛利率按实际成交收入计算，不含平台扣点、运费和售后损耗。';
    };
    overlay.querySelectorAll('input,select').forEach(node => node.addEventListener('input', refresh));
    refresh();
    overlay.querySelector('[data-role="save"]').onclick = () => {
        const settings = read();
        if (settings.discountPercent <= 0 || settings.discountPercent > 100) return showToast('❌ 活动折扣比例需在 1% 到 100% 之间');
        if (settings.mode === 'margin' && (settings.marginPercent < 0 || settings.marginPercent >= 100)) return showToast('❌ 毛利率需小于 100%');
        const target = Number(overlay.querySelector('[data-role="target"]').value) || 0;
        const result = calculatePricePlan(target, settings);
        localStorage.setItem(PRICE_LOGIC_STORAGE_KEY, JSON.stringify(settings));
        if (inlineInput && settings.mode === 'margin') {
            inlineInput.value = result.targetActualPrice.toFixed(2);
            localStorage.setItem(PRICE_STORAGE_KEY, inlineInput.value);
        }
        close();
        showToast(`✅ 价格逻辑已保存，示例上架价 ${result.listingPrice.toFixed(2)} 元`);
    };
}

// ==========================================
// 模块 1：价格推算与填充逻辑
// ==========================================
function runBatchFillPrice(targetPrice, options = {}) {
    ensureFakeOneCompensationChecked();

    let inventoryInputs = Array.from(document.querySelectorAll('input[placeholder="库存"]'));
    let validInputs = inventoryInputs.filter(el => el.offsetParent !== null);
    
    if (validInputs.length === 0) return showToast("❌ 未找到【库存】输入框！");

    let entries = validInputs.map(invInput => {
        let row = invInput.closest('tr');
        if (!row) row = invInput.closest('div[data-testid="beast-core-grid-row"]') || invInput.parentElement.parentElement.parentElement;
        return { invInput, row };
    }).filter(item => item.row);

    if (typeof options.rowFilter === 'function') {
        let filteredEntries = entries.filter(item => options.rowFilter(item.row, item.invInput));
        if (filteredEntries.length > 0) {
            entries = filteredEntries;
        }
    }
    
    let count = 0;
    entries.forEach(({ invInput, row }) => {
        let inputsInRow = Array.from(row.querySelectorAll('input[type="text"]'));
        let currentIndex = inputsInRow.indexOf(invInput);
        if (currentIndex === -1) return;

        simulateInput(invInput, "1000"); // 填库存
        
        if (currentIndex + 1 < inputsInRow.length) {
            simulateInput(inputsInRow[currentIndex + 1], targetPrice); // 填拼单价
        }

        let singleBuyInput = row.querySelector('input[placeholder="单买价"]');
        if (singleBuyInput) {
            simulateInput(singleBuyInput, "299"); // 填单买价
        } else if (currentIndex + 2 < inputsInRow.length) {
            simulateInput(inputsInRow[currentIndex + 2], "299"); // 兜底填单买价
        }
        count++;
    });

    let discountInput = document.querySelector('input[data-tracking-viewid="count_discount"]');
    if (discountInput) {
        simulateInput(discountInput, '9.9');
        console.log('✅ 满 2 件折扣已固定填充为 9.9 折');
    }

    if (count > 0) {
        setTimeout(() => {
            let buttons = Array.from(document.querySelectorAll('button'));
            let batchBtn = buttons.find(btn => btn.innerText.includes('批量设置'));
            if (batchBtn) {
                batchBtn.click();
                showToast(`✅ 价格已填充，自动点击【批量设置】`);
            } else {
                showToast(`✅ 价格已填充，但未找到【批量设置】按钮`);
            }
        }, 400); 
    } else {
        showToast("⚠️ 页面结构解析失败");
    }
}

function ensureFakeOneCompensationChecked() {
    let labels = Array.from(document.querySelectorAll('.service-promise-tag-wrapper label[data-testid="beast-core-checkbox"], label[data-testid="beast-core-checkbox"]'));
    let targetLabel = labels.find(label => cleanImageLabel(label.textContent).includes('假一赔十'));
    if (!targetLabel) return false;

    const isChecked = () => {
        let input = targetLabel.querySelector('input[type="checkbox"]');
        let state = (targetLabel.getAttribute('data-checked') || targetLabel.dataset.checked || '').toLowerCase();
        return Boolean(
            input?.checked ||
            state === '1' ||
            state === 'true' ||
            targetLabel.getAttribute('aria-checked') === 'true' ||
            targetLabel.className.includes('CBX_active')
        );
    };

    if (isChecked()) return true;

    let svgIcon = targetLabel.querySelector('svg[data-testid="beast-core-icon-check"]');
    let checkIconWrap = targetLabel.querySelector('[data-testid="beast-core-checkbox-checkIcon"]');
    let square = targetLabel.querySelector('div[class*="CBX_square"]');

    for (let i = 0; i < 3; i++) {
        dispatchMouseClick(svgIcon || checkIconWrap || square || targetLabel);
        dispatchMouseClick(checkIconWrap || square || targetLabel);
        dispatchMouseClick(targetLabel);
        if (isChecked()) return true;
    }

    setTimeout(() => {
        if (!isChecked()) {
            dispatchMouseClick(svgIcon || checkIconWrap || square || targetLabel);
            dispatchMouseClick(checkIconWrap || targetLabel);
        }
    }, 120);

    return isChecked();
}

// ==========================================
// 模块 2：SKU 编辑器与填充逻辑
// ==========================================
function showSkuEditor() {
    if (document.getElementById('pdd-sku-editor')) return;

    let panel = document.createElement('div');
    panel.id = 'pdd-sku-editor';
    panel.style.cssText = `
        position: fixed; top: 80px; left: 50%; transform: translateX(-50%);
        width: 360px; background: #fff; box-shadow: 0 10px 40px rgba(0,0,0,0.5);
        border-radius: 10px; z-index: 100000; padding: 20px; 
        display: flex; flex-direction: column; gap: 12px;
        border: 1px solid #eee; cursor: grab;
    `;

    let header = document.createElement('div');
    header.innerHTML = `<span>🛠️ SKU 排序与填充</span> <span class="close-btn" style="float:right;cursor:pointer;font-size:18px;color:#999">×</span>`;
    header.style.cssText = "font-size:16px; font-weight:bold; border-bottom:1px solid #f0f0f0; padding-bottom:10px;";
    panel.appendChild(header);
    header.querySelector('.close-btn').onclick = () => panel.remove();
    header.querySelector('.close-btn').onmousedown = (e) => e.stopPropagation();

    let inputArea = document.createElement('div');
    
    let itemNoInput = document.createElement('input');
    itemNoInput.placeholder = "在此输入货号 (即商品编码)";
    itemNoInput.style.cssText = "width:100%; padding:8px; border:1px solid #ddd; border-radius:4px; font-size:12px; margin-bottom: 8px; box-sizing: border-box;";
    itemNoInput.onmousedown = (e) => e.stopPropagation();
    itemNoInput.value = getMigratedStorageValue(ITEM_NO_STORAGE_KEY, LEGACY_STORAGE_KEYS.itemNo) || "";
    inputArea.appendChild(itemNoInput);

    let textarea = document.createElement('textarea');
    textarea.placeholder = "在此输入/粘贴 SKU名称，一行一个...";
    textarea.style.cssText = "width:100%; height:80px; border:1px solid #ddd; padding:8px; border-radius:4px; font-size:12px; resize:vertical; box-sizing: border-box;";
    textarea.onmousedown = (e) => e.stopPropagation();

    let genBtn = document.createElement('button');
    genBtn.innerText = "⬇️ 解析并刷新列表";
    genBtn.style.cssText = `
        width:100%; height:32px; margin-top:5px;
        border:none; border-radius:8px; color:#fff; cursor:pointer; font-weight:bold;
        background: ${PRIMARY_BTN_COLOR};
        box-shadow: 0 6px 14px rgba(64, 125, 255, 0.28);
    `;
    genBtn.onmousedown = (e) => e.stopPropagation();
    
    inputArea.appendChild(textarea);
    inputArea.appendChild(genBtn);
    panel.appendChild(inputArea);

    let listLabel = document.createElement('div');
    listLabel.innerText = "按住右侧图标 ☰ 上下拖动排序：";
    listLabel.style.cssText = "font-size:12px; color:#888; margin-top:5px;";
    panel.appendChild(listLabel);

    let sortContainer = document.createElement('div');
    sortContainer.className = "sku-sort-list";
    sortContainer.style.cssText = `
        max-height: 250px; overflow-y: auto; background: #fafafa; 
        border: 1px solid #eee; border-radius: 4px; padding: 5px;
        display: flex; flex-direction: column; gap: 5px;
    `;
    sortContainer.onmousedown = (e) => e.stopPropagation();
    panel.appendChild(sortContainer);

    const refreshList = () => {
        let text = textarea.value.trim();
        if(!text) return showToast("请输入内容");
        let lines = text.split('\n').map(t=>t.trim()).filter(t=>t);
        saveSkusToStorage(lines);
        renderVerticalList(lines, sortContainer);
    };
    genBtn.onclick = refreshList;

    let saveBtn = document.createElement('button');
    saveBtn.innerText = "💾 保存配置并关闭";
    saveBtn.style.cssText = `
        width:100%; padding:12px; color:#fff; border:none; border-radius:8px; font-weight:bold; font-size:14px; cursor:pointer;
        background: ${PRIMARY_BTN_COLOR};
        box-shadow: 0 6px 14px rgba(64, 125, 255, 0.28);
    `;
    saveBtn.onmousedown = (e) => e.stopPropagation();
    
    saveBtn.onclick = () => {
        localStorage.setItem(ITEM_NO_STORAGE_KEY, itemNoInput.value.trim());

        let baseSkus = getSortedSkuValues(sortContainer);
        if(baseSkus.length === 0 && !itemNoInput.value.trim()) return showToast("配置为空！请填写后保存。");
        
        if(baseSkus.length > 0) {
            saveSkusToStorage(baseSkus);
            textarea.value = baseSkus.join('\n'); 
        }
        showToast("✅ SKU 及货号配置已保存！");
        panel.remove();
    };
    panel.appendChild(saveBtn);

    let savedData = loadSkusFromStorage();
    if(savedData && savedData.length > 0) {
        textarea.value = savedData.join('\n');
        renderVerticalList(savedData, sortContainer);
    }

    document.body.appendChild(panel);
    makeDraggable(panel);
}

function renderVerticalList(items, container) {
    container.innerHTML = "";
    items.forEach(text => {
        let row = document.createElement('div');
        row.draggable = true; 
        row.className = "sort-row";
        row.style.cssText = `
            display: flex; justify-content: space-between; align-items: center;
            background: #fff; padding: 8px 12px; border: 1px solid #e8e8e8; border-radius: 4px;
            transition: all 0.2s; user-select: none;
        `;

        let txtSpan = document.createElement('span');
        txtSpan.innerText = text;
        txtSpan.style.cssText = "font-size:13px; color:#333; font-weight:500;";
        
        let handle = document.createElement('span');
        handle.innerHTML = "☰"; 
        handle.className = "drag-handle";
        handle.style.cssText = `cursor: grab; color: #999; font-size: 16px; padding: 0 5px;`;
        
        row.appendChild(txtSpan);
        row.appendChild(handle);

        row.onmousedown = (e) => {
            e.stopPropagation(); 
            if (e.target !== handle) {
                row.draggable = false;
            } else {
                row.draggable = true;
                handle.style.cursor = 'grabbing';
            }
        };
        row.onmouseup = () => {
            row.draggable = true; 
            handle.style.cursor = 'grab';
        };
        row.ondragstart = (e) => {
            e.dataTransfer.effectAllowed = 'move';
            row.classList.add('dragging');
            row.style.opacity = '0.5';
            row.style.borderColor = '#1890ff';
            window.currentDragRow = row;
        };
        row.ondragend = () => {
            row.classList.remove('dragging');
            row.style.opacity = '1';
            row.style.borderColor = '#e8e8e8';
            window.currentDragRow = null;
            let newOrder = getSortedSkuValues(container);
            saveSkusToStorage(newOrder);
            let ta = document.querySelector('#pdd-sku-editor textarea');
            if(ta) ta.value = newOrder.join('\n');
        };

        container.appendChild(row);
    });

    container.ondragover = (e) => {
        e.preventDefault();
        const afterElement = getDragAfterElementVertical(container, e.clientY);
        const draggable = window.currentDragRow;
        if (!draggable) return;
        if (afterElement == null) container.appendChild(draggable);
        else container.insertBefore(draggable, afterElement);
    };
}

function getDragAfterElementVertical(container, y) {
    const draggableElements = [...container.querySelectorAll('.sort-row:not(.dragging)')];
    return draggableElements.reduce((closest, child) => {
        const box = child.getBoundingClientRect();
        const offset = y - box.top - box.height / 2;
        if (offset < 0 && offset > closest.offset) {
            return { offset: offset, element: child };
        } else {
            return closest;
        }
    }, { offset: Number.NEGATIVE_INFINITY }).element;
}

function getSortedSkuValues(container) {
    return Array.from(container.children).map(div => div.querySelector('span').innerText);
}

function saveSkusToStorage(arr) { try{localStorage.setItem(SKU_STORAGE_KEY, JSON.stringify(arr))}catch(e){} }
function loadSkusFromStorage() { try{return JSON.parse(getMigratedStorageValue(SKU_STORAGE_KEY, LEGACY_STORAGE_KEYS.skus))}catch(e){return null} }

async function executeSkuFill(skuList) {
    let savedItemNo = getMigratedStorageValue(ITEM_NO_STORAGE_KEY, LEGACY_STORAGE_KEYS.itemNo);
    
    // ========== 精准狙击货号输入框 ==========
    if (savedItemNo) {
        try {
            let targetItemInput = findGoodsCodeItemInput(document);
            
            if (targetItemInput) {
                simulateInput(targetItemInput, savedItemNo);
                console.log("✅ 成功精准填入商品编码: " + savedItemNo);
            } else {
                console.warn("⚠️ 没找到商品编码输入框");
            }
        } catch (e) {
            console.error("❌ 商品编码填充出错: ", e);
        }
    }

    showToast(`⚡ 正在填充 ${skuList.length} 个SKU...`);
    for (let i = 0; i < skuList.length; i++) {
        let skuName = skuList[i];
        let inputs = Array.from(document.querySelectorAll('input[placeholder="选择或输入主色"]'));
        let targetInput = inputs[inputs.length - 1]; 
        if (!targetInput) { showToast(`❌ 未找到输入框 (第${i+1}个)`); break; }
        targetInput.scrollIntoView({block: "center"});
        simulateInput(targetInput, skuName);
        await new Promise(resolve => setTimeout(resolve, 150)); 
    }
    showToast("✅ SKU 及编码填充全部完成！");
}

// ==========================================
// 模块 3：SKU 编码流水线填充逻辑
// ==========================================
function runPipelineFill() {
    let inputs = Array.from(document.querySelectorAll('textarea[data-testid="beast-core-textArea-htmlInput"]'));
    let validInputs = inputs.filter(el => el.offsetParent !== null);

    if (validInputs.length === 0) {
        showToast("❌ 未找到输入框\n请确保已打开【修改编码】弹窗");
        return;
    }

    const fillSettings = loadCodeFillRule();
    let activeRule = null;
    let activeRuleKey = '';
    let ruleBySpecKey = new Map();
    let lastSizeNum = -1;  
    let fillCount = 0;

    for (let i = 0; i < validInputs.length; i++) {
        let input = validInputs[i];
        let row = input.closest('tr');
        if (!row) continue;

        let specInfo = getSpecInfoFromRow(row);
        let currentCodeSizeText = fillSettings.sizeMode === 'full'
            ? (specInfo.sizeText || specInfo.codeSizeText || getCodeSizeFromPddRow(row))
            : (specInfo.codeSizeText || getCodeSizeFromPddRow(row));
        let currentSizeNum = currentCodeSizeText ? parseFloat(currentCodeSizeText) : -1;
        
        if (lastSizeNum > 0 && currentSizeNum > 0 && currentSizeNum < lastSizeNum) {
            activeRule = null;
            activeRuleKey = '';
        }

        let val = input.value.trim();

        if (val.length > 5 && !input.dataset.autoFilled) {
            activeRule = extractRuleStrict(val, currentCodeSizeText);
            activeRuleKey = specInfo.normalizedName || '';
            if (activeRuleKey) {
                ruleBySpecKey.set(activeRuleKey, activeRule);
            }
            lastSizeNum = currentSizeNum;
            continue;
        }

        let ruleToUse = activeRule;
        if (specInfo.normalizedName && activeRuleKey && activeRuleKey !== specInfo.normalizedName) {
            ruleToUse = null;
        }
        if (!ruleToUse && specInfo.normalizedName) {
            ruleToUse = ruleBySpecKey.get(specInfo.normalizedName) || null;
        }

        if (ruleToUse && currentCodeSizeText && currentSizeNum > 0) {
            let newCode = buildCodeFromRule(ruleToUse, currentCodeSizeText, fillSettings);
            if (input.value !== newCode) {
                reactTextareaChange(input, newCode);
                input.dataset.autoFilled = "true";
                input.style.backgroundColor = "#ffe6e6"; 
                fillCount++;
            }
        }

        if (currentSizeNum > 0) lastSizeNum = currentSizeNum;
    }

    if (fillCount > 0) {
        showToast(`⚡️ 流水线工作完成，填充了 ${fillCount} 行`);
    } else {
        if (!activeRule && fillCount === 0) {
            showToast("⚠️ 未检测到新的种子填写。\n请确保你填写的编码比默认值长。");
        } else {
            showToast("✅ 检查完毕，无需新增填充。");
        }
    }
}

function extractRuleStrict(val, sizeToken = '') {
    let value = cleanImageLabel(val);
    let codeSizeText = getCodeSizeToken(sizeToken);

    if (codeSizeText) {
        let sizeWithSuffix = `${codeSizeText}码`;
        if (value.endsWith(sizeWithSuffix)) {
            return { start: value.slice(0, -sizeWithSuffix.length), end: "码" };
        }
        if (value.endsWith(codeSizeText)) {
            return { start: value.slice(0, -codeSizeText.length), end: "" };
        }
    }

    if (value.endsWith("码")) {
        let prefixPart = value.slice(0, -3); 
        return { start: prefixPart, end: "码" };
    } else {
        let prefixPart = value.slice(0, -2);
        return { start: prefixPart, end: "" };
    }
}

function getCodeSizeFromPddRow(row) {
    if (!row) return '';
    let specDiv = row.querySelector('div[class*="elli_outerWrapper"]');
    if (specDiv) {
        let text = specDiv.innerText.trim();
        let sizeText = extractSizeToken(text);
        if (sizeText) return getCodeSizeToken(sizeText);
    }
    return '';
}

function reactTextareaChange(input, newValue) {
    input.focus();
    let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
    if (nativeInputValueSetter) {
        nativeInputValueSetter.call(input, newValue);
    } else {
        input.value = newValue;
    }
    let inputEvent = new Event('input', { bubbles: true });
    input.dispatchEvent(inputEvent);
    input.blur();
}

function loadCodeTemplatesFromStorage() {
    try {
        return JSON.parse(getMigratedStorageValue(CODE_TEMPLATE_STORAGE_KEY, LEGACY_STORAGE_KEYS.templates)) || {};
    } catch (e) {
        console.warn('⚠️ 读取母版库失败:', e);
        return {};
    }
}

function saveCodeTemplatesToStorage(map) {
    try {
        localStorage.setItem(CODE_TEMPLATE_STORAGE_KEY, JSON.stringify(map));
        return true;
    } catch (e) {
        console.error('❌ 保存母版库失败:', e);
        return false;
    }
}

function getCodeTemplateByItemNo(itemNo) {
    if (!itemNo) return null;
    let templates = loadCodeTemplatesFromStorage();
    return templates[itemNo] || null;
}

function getCodeTemplateEntryKey(entry) {
    return cleanImageLabel(entry?.looseKey || '')
        || buildLooseSpecKey(entry?.normalizedName || entry?.baseName || '');
}

function mergeCodeTemplateEntries(existingEntries = [], incomingEntries = []) {
    let mergedEntries = [];
    let entryIndexMap = new Map();

    for (let entry of Array.isArray(existingEntries) ? existingEntries : []) {
        let key = getCodeTemplateEntryKey(entry);
        if (!key || entryIndexMap.has(key)) continue;
        entryIndexMap.set(key, mergedEntries.length);
        mergedEntries.push(entry);
    }

    let addedCount = 0;
    let updatedCount = 0;
    for (let entry of Array.isArray(incomingEntries) ? incomingEntries : []) {
        let key = getCodeTemplateEntryKey(entry);
        if (!key) continue;

        if (entryIndexMap.has(key)) {
            mergedEntries[entryIndexMap.get(key)] = entry;
            updatedCount += 1;
            continue;
        }

        entryIndexMap.set(key, mergedEntries.length);
        mergedEntries.push(entry);
        addedCount += 1;
    }

    return { entries: mergedEntries, addedCount, updatedCount };
}

function upsertCodeTemplate(template) {
    if (!template?.itemNo) return { saved: false, template: null, addedCount: 0, updatedCount: 0 };
    let templates = loadCodeTemplatesFromStorage();
    let existingTemplate = templates[template.itemNo] || null;
    let mergeResult = mergeCodeTemplateEntries(existingTemplate?.entries, template.entries);
    let sourceProductIds = Array.from(new Set([
        ...(Array.isArray(existingTemplate?.sourceProductIds) ? existingTemplate.sourceProductIds : []),
        existingTemplate?.sourceProductId,
        template.sourceProductId
    ].filter(Boolean)));
    let mergedTemplate = {
        ...existingTemplate,
        ...template,
        version: 2,
        createdAt: existingTemplate?.createdAt || existingTemplate?.updatedAt || template.updatedAt || Date.now(),
        sourceProductIds,
        entries: mergeResult.entries
    };

    templates[template.itemNo] = mergedTemplate;
    let saved = saveCodeTemplatesToStorage(templates);
    return { saved, template: saved ? mergedTemplate : null, ...mergeResult };
}

function normalizeSpecBaseName(name) {
    return cleanImageLabel(name).replace(/[：:]\s*$/, '').trim();
}

function buildLooseSpecKey(name) {
    return normalizeSpecBaseName(name)
        .toLowerCase()
        .replace(/[\s\-_\/|]+/g, '')
        .replace(/[()（）【】\[\]{}<>,，。+]/g, '');
}

function normalizeSizeRangeSeparator(text) {
    return cleanImageLabel(text).replace(/\s*[－—–]\s*/g, '-');
}

function extractSizeToken(text) {
    let cleanText = normalizeSizeRangeSeparator(text);
    let rangeMatch = cleanText.match(/(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*$/);
    if (rangeMatch) return `${rangeMatch[1]}-${rangeMatch[2]}`;

    let match = cleanText.match(/(\d+(?:\.\d+)?)\s*$/);
    return match ? match[1] : '';
}

function getCodeSizeToken(sizeText) {
    let cleanSize = normalizeSizeRangeSeparator(sizeText);
    let rangeMatch = cleanSize.match(/^(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)$/);
    if (rangeMatch) return rangeMatch[1];
    return cleanSize;
}

function stripTrailingSize(text) {
    return normalizeSizeRangeSeparator(text)
        .replace(/\s*\d+(?:\.\d+)?\s*-\s*\d+(?:\.\d+)?\s*$/, '')
        .replace(/\s*\d+(?:\.\d+)?\s*$/, '')
        .trim();
}

function hasTrailingSizeToken(text) {
    return Boolean(extractSizeToken(text));
}

function getSpecLabelTextFromRow(row) {
    if (!row) return '';

    let preferred = Array.from(row.querySelectorAll('div[class*="elli_outerWrapper"]'))
        .find(el => isVisibleElement(el) && hasTrailingSizeToken(el.textContent));
    if (preferred) return cleanImageLabel(preferred.textContent);

    let fallbackNodes = Array.from(row.querySelectorAll('div, span'))
        .filter(el => isVisibleElement(el));

    for (let node of fallbackNodes) {
        let text = cleanImageLabel(node.textContent);
        if (!text || text.length > 60) continue;
        if (hasTrailingSizeToken(text)) {
            return text;
        }
    }
    return '';
}

function getSpecInfoFromRow(row) {
    let labelText = getSpecLabelTextFromRow(row);
    let sizeText = extractSizeToken(labelText);
    let codeSizeText = getCodeSizeToken(sizeText);
    let baseName = stripTrailingSize(labelText);
    return {
        labelText,
        sizeText,
        codeSizeText,
        sizeNum: codeSizeText ? parseFloat(codeSizeText) : -1,
        baseName,
        normalizedName: normalizeSpecBaseName(baseName),
        looseKey: buildLooseSpecKey(baseName)
    };
}

function getRowCodeTextarea(row) {
    return Array.from(row?.querySelectorAll('textarea[data-testid="beast-core-textArea-htmlInput"]') || [])
        .find(isVisibleElement) || null;
}

function getVisibleGoodsCodeTextareas(root = document) {
    return Array.from(root.querySelectorAll('textarea[data-testid="beast-core-textArea-htmlInput"]'))
        .filter(isVisibleElement);
}

function getGoodsCodeItemInputSelectors() {
    return [
        '#out_goods_sn input',
        'div[id="sku.out_goods_sn"] input',
        'input[data-tracking-viewid="insert_goodscode"]',
        'input[data-testid="beast-core-input-htmlInput"][data-tracking-params*="goods_id="]'
    ];
}

function getGoodsCodeItemInputValue(input) {
    return cleanImageLabel(input?.value || input?.getAttribute?.('value') || '');
}

function collectGoodsCodeItemInputs(root = document) {
    let inputs = [];
    for (let selector of getGoodsCodeItemInputSelectors()) {
        inputs.push(...Array.from(root.querySelectorAll(selector)));
    }
    return uniqueElements(inputs);
}

function scoreGoodsCodeItemInput(input, referenceRoot = null) {
    if (!input) return Number.NEGATIVE_INFINITY;

    let score = 0;
    let value = getGoodsCodeItemInputValue(input);
    let trackingParams = cleanImageLabel(input.getAttribute?.('data-tracking-params') || '');

    if (value) score += 220;
    if (trackingParams.includes('goods_id=')) score += 60;
    if (input.matches?.('input[data-tracking-viewid="insert_goodscode"]')) score += 140;
    if (input.matches?.('#out_goods_sn input, div[id="sku.out_goods_sn"] input')) score += 120;
    if (isVisibleElement(input)) score += 80;

    if (referenceRoot) {
        if (referenceRoot.contains(input)) {
            score += 600;
        }

        let refRect = referenceRoot.getBoundingClientRect?.();
        let inputRect = input.getBoundingClientRect?.();
        if (refRect && inputRect && refRect.width > 0 && refRect.height > 0) {
            let refCenterX = refRect.left + refRect.width / 2;
            let refCenterY = refRect.top + refRect.height / 2;
            let inputCenterX = inputRect.left + inputRect.width / 2;
            let inputCenterY = inputRect.top + inputRect.height / 2;
            let distance = Math.abs(inputCenterX - refCenterX) + Math.abs(inputCenterY - refCenterY);
            score += Math.max(0, 320 - Math.min(distance, 320));

            if (inputRect.top <= refRect.top + 120) score += 40;
            if (Math.abs(inputCenterX - refCenterX) <= refRect.width / 2 + 120) score += 30;
        }
    }

    return score;
}

function findGoodsCodeItemInput(root = document) {
    let referenceRoot = root !== document ? root : null;
    let searchRoots = [];

    if (referenceRoot) {
        let current = referenceRoot;
        for (let i = 0; current && current !== document.body && i < 6; i++) {
            searchRoots.push(current);
            current = current.parentElement;
        }
    }

    searchRoots.push(document);

    let candidates = [];
    searchRoots.forEach(targetRoot => {
        candidates.push(...collectGoodsCodeItemInputs(targetRoot));
    });

    candidates = uniqueElements(candidates)
        .filter(input => getGoodsCodeItemInputValue(input) || isVisibleElement(input));

    if (candidates.length === 0) return null;

    candidates.sort((a, b) => scoreGoodsCodeItemInput(b, referenceRoot) - scoreGoodsCodeItemInput(a, referenceRoot));
    return candidates[0] || null;
}

function findCodeDialogActionButtons(root) {
    return Array.from(root?.querySelectorAll('button') || []).filter(btn => {
        if (!isVisibleElement(btn)) return false;
        let text = cleanImageLabel(btn.innerText || btn.textContent);
        return text.includes('确认') || text.includes('取消');
    });
}

function findGoodsCodeDialogRootFromElement(element) {
    let current = element?.parentElement || null;
    let best = null;
    let bestScore = -1;

    while (current && current !== document.body) {
        let visibleTextareas = getVisibleGoodsCodeTextareas(current);
        if (visibleTextareas.length > 0) {
            let score = visibleTextareas.length * 10;
            if (findGoodsCodeItemInput(current)) score += 100;
            score += findCodeDialogActionButtons(current).length * 5;

            if (score > bestScore) {
                best = current;
                bestScore = score;
            }

            if (findGoodsCodeItemInput(current) && findCodeDialogActionButtons(current).length > 0) {
                return current;
            }
        }
        current = current.parentElement;
    }

    return best;
}

function findGoodsCodeDialogRoot() {
    if (!isGoodsCodeDialogSupportedPage()) return null;
    let textareas = getVisibleGoodsCodeTextareas();
    if (textareas.length === 0) return null;
    return findGoodsCodeDialogRootFromElement(textareas[0]);
}

function findGoodsCodeDialogShellRootFromInput(input) {
    let current = input?.parentElement || null;
    let best = null;
    let bestScore = -1;

    while (current && current !== document.body) {
        let score = 0;
        let dialogText = cleanImageLabel(current.textContent);
        let actionCount = findCodeDialogActionButtons(current).length;

        if (collectGoodsCodeItemInputs(current).length > 0) score += 120;
        if (dialogText.includes('编辑商品编码')) score += 90;
        if (dialogText.includes('规格编码')) score += 40;
        if (current.querySelector?.('table')) score += 20;
        score += actionCount * 8;

        if (score > bestScore) {
            best = current;
            bestScore = score;
        }

        if (collectGoodsCodeItemInputs(current).length > 0 && actionCount > 0 && dialogText.includes('编辑商品编码')) {
            return current;
        }

        current = current.parentElement;
    }

    return best;
}

function findGoodsCodeDialogShellRoot() {
    if (!isGoodsCodeDialogSupportedPage()) return null;
    let itemInputs = collectGoodsCodeItemInputs(document).filter(input => {
        return isVisibleElement(input) || getGoodsCodeItemInputValue(input);
    });
    if (itemInputs.length === 0) return null;

    let scored = itemInputs.map(input => {
        let root = findGoodsCodeDialogShellRootFromInput(input);
        let score = scoreGoodsCodeItemInput(input, root || null);
        if (root && cleanImageLabel(root.textContent).includes('编辑商品编码')) score += 160;
        if (root && findCodeDialogActionButtons(root).length > 0) score += 100;
        return { input, root, score };
    }).filter(item => item.root);

    if (scored.length === 0) return null;
    scored.sort((a, b) => b.score - a.score);
    return scored[0].root;
}

function getGoodsCodeDialogShellContext(root = null) {
    if (!isGoodsCodeDialogSupportedPage()) return null;
    let dialogRoot = root || findGoodsCodeDialogShellRoot();
    if (!dialogRoot) return null;

    let itemInput = findGoodsCodeItemInput(dialogRoot) || findGoodsCodeItemInput(document);
    if (!itemInput) return null;

    let itemNo = getGoodsCodeItemInputValue(itemInput);
    let dialogText = cleanImageLabel(dialogRoot.textContent);
    if (!dialogText.includes('编辑商品编码') && !dialogText.includes('商品编码') && !dialogText.includes('规格编码')) {
        return null;
    }
    let trackingParams = cleanImageLabel(itemInput?.getAttribute?.('data-tracking-params') || '');
    let productIdMatch = dialogText.match(/ID[:：]\s*(\d{5,})/) || trackingParams.match(/goods_id=(\d{5,})/);

    return {
        root: dialogRoot,
        itemInput,
        itemNo,
        productId: productIdMatch ? productIdMatch[1] : ''
    };
}

function isExpectedGoodsCodeContext(context, expectedProductId = '') {
    if (!context) return false;
    if (!expectedProductId) return true;
    return !context.productId || context.productId === expectedProductId;
}

function getGoodsCodeDialogContext(root = null) {
    if (!isGoodsCodeDialogSupportedPage()) return null;
    let dialogRoot = root || findGoodsCodeDialogRoot();
    if (!dialogRoot) return null;

    let textareas = getVisibleGoodsCodeTextareas(dialogRoot);
    if (textareas.length === 0) return null;

    let rows = [];
    let seenRows = new Set();
    textareas.forEach(textarea => {
        let row = textarea.closest('tr');
        if (!row || seenRows.has(row)) return;
        seenRows.add(row);
        rows.push(row);
    });

    if (rows.length === 0) return null;

    let itemInput = findGoodsCodeItemInput(dialogRoot) || findGoodsCodeItemInput(document);
    if (!dialogRoot.contains(itemInput) && !cleanImageLabel(dialogRoot.textContent).includes('编辑商品编码') && !cleanImageLabel(dialogRoot.textContent).includes('商品编码')) {
        return null;
    }
    let itemNo = getGoodsCodeItemInputValue(itemInput);
    let dialogText = cleanImageLabel(dialogRoot.textContent);
    if (!dialogText.includes('编辑商品编码') && !dialogText.includes('商品编码') && !dialogText.includes('规格编码')) {
        return null;
    }
    let trackingParams = cleanImageLabel(itemInput?.getAttribute?.('data-tracking-params') || '');
    let productIdMatch = dialogText.match(/ID[:：]\s*(\d{5,})/) || trackingParams.match(/goods_id=(\d{5,})/);

    return {
        root: dialogRoot,
        textareas,
        rows,
        itemInput,
        itemNo,
        productId: productIdMatch ? productIdMatch[1] : ''
    };
}

function buildCodeTemplateFromCurrentDialog(context) {
    let entryMap = new Map();

    for (let row of context.rows) {
        let specInfo = getSpecInfoFromRow(row);
        let input = getRowCodeTextarea(row);
        let codeValue = cleanImageLabel(input?.value || '');

        if (!specInfo.normalizedName || !specInfo.sizeText || !codeValue || codeValue.length <= 5) {
            continue;
        }

        let key = specInfo.normalizedName;
        if (!entryMap.has(key)) {
            entryMap.set(key, {
                baseName: specInfo.baseName,
                normalizedName: specInfo.normalizedName,
                looseKey: specInfo.looseKey,
                rule: extractRuleStrict(codeValue, specInfo.codeSizeText),
                sampleCode: codeValue,
                sampleSize: specInfo.sizeText,
                sampleCodeSize: specInfo.codeSizeText
            });
        }
    }

    return Array.from(entryMap.values());
}

function runSaveCurrentDialogTemplate() {
    let context = getGoodsCodeDialogContext();
    if (!context) {
        showToast('❌ 未找到商品编码弹窗');
        return null;
    }
    if (!context.itemNo) {
        showToast('❌ 未识别到顶部商品编码');
        return null;
    }

    let entries = buildCodeTemplateFromCurrentDialog(context);
    if (entries.length === 0) {
        showToast('❌ 当前弹窗没有可保存的已填规格编码');
        return null;
    }

    let template = {
        version: 1,
        itemNo: context.itemNo,
        sourceProductId: context.productId || '',
        updatedAt: Date.now(),
        entries
    };

    let saveResult = upsertCodeTemplate(template);
    if (!saveResult.saved) {
        showToast('❌ 母版保存失败，请稍后重试');
        return null;
    }

    syncGoodsCodeTemplateActions();
    let changeSummary = [
        saveResult.addedCount ? `新增 ${saveResult.addedCount} 项` : '',
        saveResult.updatedCount ? `更新 ${saveResult.updatedCount} 项` : ''
    ].filter(Boolean).join('，');
    showToast(`✅ 已添加到货号 ${context.itemNo} 的母版：${changeSummary || '无变化'}，共 ${saveResult.template.entries.length} 项`);
    return saveResult.template;
}

function findCodeTemplateEntry(template, specInfo) {
    if (!template?.entries?.length || !specInfo?.normalizedName) return null;

    let exact = template.entries.find(entry => entry.normalizedName === specInfo.normalizedName);
    if (exact) return exact;

    let looseKey = specInfo.looseKey;
    if (!looseKey) return null;

    let fuzzyMatches = template.entries.filter(entry => {
        if (!entry.looseKey) return false;
        return looseKey.includes(entry.looseKey) || entry.looseKey.includes(looseKey);
    });

    return fuzzyMatches.length === 1 ? fuzzyMatches[0] : null;
}

function applyCodeTemplateToContext(context, template) {
    let filledCount = 0;
    let skippedExistingCount = 0;
    let unknownSpecs = new Set();

    for (let row of context.rows) {
        let specInfo = getSpecInfoFromRow(row);
        let input = getRowCodeTextarea(row);
        if (!input || !specInfo.baseName || !specInfo.sizeText || !specInfo.codeSizeText) continue;

        let entry = findCodeTemplateEntry(template, specInfo);
        if (!entry) {
            unknownSpecs.add(specInfo.baseName);
            continue;
        }

        let newCode = `${entry.rule.start}${specInfo.codeSizeText}${entry.rule.end}`;
        let currentValue = cleanImageLabel(input.value || '');
        if (currentValue === newCode) {
            skippedExistingCount += 1;
            continue;
        }

        reactTextareaChange(input, newCode);
        input.dataset.autoFilled = 'true';
        input.dataset.autoFilledByTemplate = 'true';
        input.style.backgroundColor = '#e8fff0';
        filledCount += 1;
    }

    return {
        itemNo: context.itemNo,
        productId: context.productId,
        totalRows: context.rows.length,
        filledCount,
        skippedExistingCount,
        unknownSpecs: Array.from(unknownSpecs)
    };
}

function runApplyTemplateToCurrentDialog() {
    let context = getGoodsCodeDialogContext();
    if (!context) {
        showToast('❌ 未找到商品编码弹窗');
        return null;
    }
    if (!context.itemNo) {
        showToast('❌ 未识别到顶部商品编码');
        return null;
    }

    let template = getCodeTemplateByItemNo(context.itemNo);
    if (!template) {
        showToast(`⚠️ 商品编码 ${context.itemNo} 暂无母版`);
        return null;
    }

    let result = applyCodeTemplateToContext(context, template);
    if (result.filledCount > 0) {
        showToast(`✅ 已应用母版，填充 ${result.filledCount} 行${result.unknownSpecs.length ? `，未识别 ${result.unknownSpecs.length} 项` : ''}`);
    } else if (result.unknownSpecs.length > 0) {
        showToast(`⚠️ 未识别 ${result.unknownSpecs.length} 个规格，已跳过`);
    } else {
        showToast('✅ 母版已匹配完成，无需新增填充');
    }
    return result;
}

function clearCurrentDialogSpecCodes(context) {
    let clearedCount = 0;

    for (let row of context.rows) {
        let input = getRowCodeTextarea(row);
        if (!input) continue;

        let currentValue = cleanImageLabel(input.value || '');
        if (!currentValue) continue;

        reactTextareaChange(input, '');
        delete input.dataset.autoFilled;
        delete input.dataset.autoFilledByTemplate;
        input.style.backgroundColor = '';
        clearedCount += 1;
    }

    return clearedCount;
}

function runClearCurrentDialogCodes() {
    let context = getGoodsCodeDialogContext();
    if (!context) {
        showToast('❌ 未找到商品编码弹窗');
        return 0;
    }

    let clearedCount = clearCurrentDialogSpecCodes(context);
    if (clearedCount > 0) {
        showToast(`✅ 已清空 ${clearedCount} 个规格编码`);
    } else {
        showToast('✅ 当前没有可清空的规格编码');
    }
    return clearedCount;
}

function findCodeTemplateActionsAnchor(context) {
    let firstTable = context.root.querySelector('table');
    if (firstTable) {
        return { element: firstTable, position: 'beforebegin' };
    }

    if (context.itemInput) {
        let rowContainer = context.itemInput.closest('div[data-testid="beast-core-grid-row"]')
            || context.itemInput.parentElement?.parentElement
            || context.itemInput.parentElement;
        if (rowContainer) {
            return { element: rowContainer, position: 'afterend' };
        }
    }

    return { element: context.root.firstElementChild || context.root, position: 'afterbegin' };
}

function ensureCodeTemplateActions(context) {
    let wrap = document.getElementById(CODE_TEMPLATE_ACTIONS_ID);
    if (!wrap) {
        wrap = document.createElement('div');
        wrap.id = CODE_TEMPLATE_ACTIONS_ID;
        wrap.style.cssText = `
            display:flex; align-items:center; gap:8px; flex-wrap:wrap;
            margin: 12px 0; padding: 10px 12px;
            background: #f7faff; border: 1px solid #d8e5ff; border-radius: 10px;
        `;
    }

    let status = document.getElementById(CODE_TEMPLATE_STATUS_ID);
    if (!status) {
        status = document.createElement('div');
        status.id = CODE_TEMPLATE_STATUS_ID;
        status.style.cssText = 'font-size:12px; color:#486181; font-weight:600; margin-right:6px;';
        wrap.appendChild(status);
    }

    let saveBtn = document.getElementById(CODE_TEMPLATE_SAVE_BTN_ID);
    if (!saveBtn) {
        saveBtn = document.createElement('button');
        saveBtn.id = CODE_TEMPLATE_SAVE_BTN_ID;
        saveBtn.type = 'button';
        saveBtn.style.cssText = getBlueButtonCss({ height: 32, minWidth: 98, fontSize: 13 });
        saveBtn.onmousedown = (e) => e.stopPropagation();
        saveBtn.onclick = runSaveCurrentDialogTemplate;
        wrap.appendChild(saveBtn);
    }

    let clearBtn = document.getElementById(CODE_TEMPLATE_CLEAR_BTN_ID);
    if (!clearBtn) {
        clearBtn = document.createElement('button');
        clearBtn.id = CODE_TEMPLATE_CLEAR_BTN_ID;
        clearBtn.type = 'button';
        clearBtn.innerText = '清空预填编码';
        clearBtn.style.cssText = getBlueButtonCss({ height: 32, minWidth: 108, fontSize: 13 });
        clearBtn.onmousedown = (e) => e.stopPropagation();
        clearBtn.onclick = runClearCurrentDialogCodes;
        wrap.appendChild(clearBtn);
    }

    let applyBtn = document.getElementById(CODE_TEMPLATE_APPLY_BTN_ID);
    if (!applyBtn) {
        applyBtn = document.createElement('button');
        applyBtn.id = CODE_TEMPLATE_APPLY_BTN_ID;
        applyBtn.type = 'button';
        applyBtn.style.cssText = getBlueButtonCss({ height: 32, minWidth: 98, fontSize: 13 });
        applyBtn.onmousedown = (e) => e.stopPropagation();
        applyBtn.onclick = (e) => {
            e?.preventDefault?.();
            e?.stopPropagation?.();
            return runApplyTemplateToCurrentDialog();
        };
        wrap.appendChild(applyBtn);
    }

    let template = getCodeTemplateByItemNo(context.itemNo);
    saveBtn.innerText = template ? '添加到母版' : '保存母版';
    clearBtn.innerText = '清空预填编码';
    applyBtn.innerText = '应用母版';
    applyBtn.style.display = template ? 'inline-flex' : 'none';
    status.innerText = template
        ? `已识别母版：${context.itemNo}（${template.entries.length} 项）`
        : `当前商品编码：${context.itemNo || '未识别'}，可先手工填 1 条后保存母版`;

    return wrap;
}

function syncGoodsCodeTemplateActions() {
    let wrap = document.getElementById(CODE_TEMPLATE_ACTIONS_ID);
    if (!isGoodsCodeDialogSupportedPage()) {
        wrap?.remove();
        return;
    }

    let context = getGoodsCodeDialogContext();

    if (!context) {
        wrap?.remove();
        return;
    }

    wrap = ensureCodeTemplateActions(context);
    let anchor = findCodeTemplateActionsAnchor(context);
    if (!anchor?.element) return;

    if (anchor.position === 'beforebegin') {
        if (wrap.parentElement !== anchor.element.parentElement || wrap.nextElementSibling !== anchor.element) {
            anchor.element.insertAdjacentElement('beforebegin', wrap);
        }
    } else if (anchor.position === 'afterend') {
        if (wrap.parentElement !== anchor.element.parentElement || wrap.previousElementSibling !== anchor.element) {
            anchor.element.insertAdjacentElement('afterend', wrap);
        }
    } else if (anchor.position === 'afterbegin') {
        if (wrap.parentElement !== anchor.element || anchor.element.firstElementChild !== wrap) {
            anchor.element.insertAdjacentElement('afterbegin', wrap);
        }
    }
}

function getVisibleGoodsListRows() {
    let strictRows = Array.from(document.querySelectorAll('[data-testid="beast-core-table-body-tr"]'))
        .filter(isVisibleElement)
        .filter(row => /商品编码[:：]\s*[A-Za-z0-9_-]+/.test(cleanImageLabel(row.textContent)));

    if (strictRows.length > 0) return strictRows;

    return Array.from(document.querySelectorAll('tr'))
        .filter(isVisibleElement)
        .filter(row => /商品编码[:：]\s*[A-Za-z0-9_-]+/.test(cleanImageLabel(row.textContent)));
}

function getGoodsListRowInfo(row) {
    let text = cleanImageLabel(row?.textContent || '');
    let idMatch = text.match(/ID[:：]\s*(\d{5,})/);
    let itemNoMatch = text.match(/商品编码[:：]\s*([A-Za-z0-9_-]+)/);

    return {
        productId: idMatch ? idMatch[1] : '',
        itemNo: itemNoMatch ? itemNoMatch[1] : ''
    };
}

function getVisibleGoodsListRowTasks() {
    return getVisibleGoodsListRows()
        .map(row => getGoodsListRowInfo(row))
        .filter(info => info.productId || info.itemNo)
        .filter((info, index, arr) => {
            return arr.findIndex(other => {
                return other.productId === info.productId && other.itemNo === info.itemNo;
            }) === index;
        });
}

function findVisibleGoodsListRowByInfo(targetInfo) {
    if (!targetInfo?.productId && !targetInfo?.itemNo) return null;

    let rows = getVisibleGoodsListRows();
    let exactRow = rows.find(row => {
        let info = getGoodsListRowInfo(row);
        if (targetInfo.productId && info.productId !== targetInfo.productId) return false;
        if (targetInfo.itemNo && info.itemNo && info.itemNo !== targetInfo.itemNo) return false;
        return Boolean(info.productId || info.itemNo);
    });
    if (exactRow) return exactRow;

    if (targetInfo.productId) {
        return rows.find(row => getGoodsListRowInfo(row).productId === targetInfo.productId) || null;
    }
    if (targetInfo.itemNo) {
        return rows.find(row => getGoodsListRowInfo(row).itemNo === targetInfo.itemNo) || null;
    }
    return null;
}

async function waitForVisibleGoodsListRowByInfo(targetInfo, timeoutMs = 3200) {
    return waitForCondition(() => findVisibleGoodsListRowByInfo(targetInfo), timeoutMs, 100);
}

function getVisibleGoodsListSignature() {
    return getVisibleGoodsListRows()
        .map(row => {
            let info = getGoodsListRowInfo(row);
            return `${info.productId || '-'}:${info.itemNo || '-'}`;
        })
        .join('|');
}

async function waitForGoodsListStable(timeoutMs = 2600, minWaitMs = 0) {
    if (minWaitMs > 0) {
        await sleep(minWaitMs);
    }

    let start = Date.now();
    let lastSignature = '';
    let stableHits = 0;

    while (Date.now() - start < timeoutMs) {
        let signature = getVisibleGoodsListSignature();
        if (signature && signature === lastSignature) {
            stableHits += 1;
            if (stableHits >= 2) {
                return getVisibleGoodsListRows();
            }
        } else {
            lastSignature = signature;
            stableHits = 0;
        }
        await sleep(140);
    }

    return getVisibleGoodsListRows();
}

async function waitForGoodsListRefresh(baseSignature = '', timeoutMs = 3200, minWaitMs = 0) {
    if (!baseSignature) {
        return waitForGoodsListStable(timeoutMs, minWaitMs);
    }

    if (minWaitMs > 0) {
        await sleep(minWaitMs);
    }

    let start = Date.now();
    let changed = false;
    let lastSignature = '';
    let stableHits = 0;

    while (Date.now() - start < timeoutMs) {
        let signature = getVisibleGoodsListSignature();
        if (signature && signature !== baseSignature) {
            changed = true;
        }

        if (changed && signature && signature === lastSignature) {
            stableHits += 1;
            if (stableHits >= 2) {
                return getVisibleGoodsListRows();
            }
        } else {
            lastSignature = signature;
            stableHits = 0;
        }
        await sleep(140);
    }

    return getVisibleGoodsListRows();
}

function uniqueElements(elements) {
    let seen = new Set();
    let result = [];
    elements.forEach(el => {
        if (!el || seen.has(el)) return;
        seen.add(el);
        result.push(el);
    });
    return result;
}

function dispatchMouseHover(element) {
    if (!element) return;
    let rect = element.getBoundingClientRect();
    let clientX = rect.left + Math.max(4, rect.width / 2);
    let clientY = rect.top + Math.max(4, rect.height / 2);

    ['pointerover', 'mouseover', 'mouseenter', 'mousemove'].forEach(type => {
        element.dispatchEvent(new MouseEvent(type, {
            bubbles: true,
            cancelable: true,
            view: window,
            clientX,
            clientY
        }));
    });
}

function findGoodsCodeLabelElements(row) {
    return Array.from(row?.querySelectorAll('span, div') || []).filter(el => {
        let text = cleanImageLabel(el.textContent);
        return text.startsWith('商品编码:') || text.startsWith('商品编码：');
    });
}

function getElementCenter(element) {
    let rect = element?.getBoundingClientRect?.();
    if (!rect) return null;
    return {
        x: rect.left + rect.width / 2,
        y: rect.top + rect.height / 2
    };
}

function getDistanceBetweenElements(a, b) {
    let aCenter = getElementCenter(a);
    let bCenter = getElementCenter(b);
    if (!aCenter || !bCenter) return Number.POSITIVE_INFINITY;
    return Math.abs(aCenter.x - bCenter.x) + Math.abs(aCenter.y - bCenter.y);
}

function applyTemporaryStyles(node, styles) {
    if (!(node instanceof HTMLElement || node instanceof SVGElement)) {
        return () => {};
    }

    let previous = new Map();
    Object.entries(styles).forEach(([key, value]) => {
        previous.set(key, node.style[key] || '');
        node.style[key] = value;
    });

    return () => {
        previous.forEach((value, key) => {
            node.style[key] = value;
        });
    };
}

function clickNode(node) {
    if (!node) return false;

    let rect = node.getBoundingClientRect?.();
    if (rect && (rect.width <= 0 || rect.height <= 0)) {
        node.scrollIntoView?.({ block: 'center', inline: 'nearest' });
    }

    node.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
    node.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
    if (typeof node.click === 'function') {
        node.click();
    } else {
        node.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    }
    return true;
}

function requestNativeBrowserClick(x, y) {
    return new Promise((resolve) => {
        if (!chrome?.runtime?.sendMessage) {
            resolve({ success: false, error: 'runtime sendMessage unavailable' });
            return;
        }

        chrome.runtime.sendMessage({ type: 'pdd-native-click', x, y }, (response) => {
            if (chrome.runtime.lastError) {
                let error = chrome.runtime.lastError.message;
                console.warn('⚠️ 原生点击请求失败:', error);
                resolve({ success: false, error });
                return;
            }
            resolve({
                success: Boolean(response?.success),
                error: response?.error || ''
            });
        });
    });
}

async function nativeClickElement(node) {
    if (!node) return { success: false, error: 'empty node' };

    node.scrollIntoView?.({ block: 'center', inline: 'center' });
    await sleep(40);

    let rect = node.getBoundingClientRect?.();
    if (!rect || rect.width <= 0 || rect.height <= 0) {
        return { success: false, error: 'target rect is empty' };
    }

    let point = getPreferredClickPoint(node) || {
        x: Math.round(rect.left + Math.min(rect.width - 4, Math.max(4, rect.width / 2))),
        y: Math.round(rect.top + Math.min(rect.height - 4, Math.max(4, rect.height / 2)))
    };

    let x = point.x;
    let y = point.y;
    let result = await requestNativeBrowserClick(x, y);
    return {
        ...result,
        x,
        y
    };
}

function getReactProps(node) {
    if (!(node instanceof HTMLElement || node instanceof SVGElement)) return null;

    for (let key of Object.keys(node)) {
        if (key.startsWith('__reactProps$') || key.startsWith('__reactEventHandlers$')) {
            return node[key];
        }
    }
    return null;
}

function createReactMouseLikeEvent(type, target) {
    let nativeEvent = new MouseEvent(type.toLowerCase().replace(/^on/, ''), {
        bubbles: true,
        cancelable: true,
        view: window
    });

    return {
        type: type.toLowerCase().replace(/^on/, ''),
        target,
        currentTarget: target,
        nativeEvent,
        bubbles: true,
        cancelable: true,
        defaultPrevented: false,
        isDefaultPrevented: () => false,
        isPropagationStopped: () => false,
        persist: () => {},
        preventDefault() {
            this.defaultPrevented = true;
            nativeEvent.preventDefault();
        },
        stopPropagation() {
            nativeEvent.stopPropagation();
        }
    };
}

function invokeReactHandler(node, handlerName) {
    let props = getReactProps(node);
    let handler = props?.[handlerName];
    if (typeof handler !== 'function') return false;

    try {
        handler(createReactMouseLikeEvent(handlerName, node));
        return true;
    } catch (e) {
        console.warn(`⚠️ React ${handlerName} 调用失败`, e);
        return false;
    }
}

function invokeReactClickChain(node) {
    let handlers = ['onMouseEnter', 'onMouseOver', 'onMouseMove', 'onPointerEnter', 'onPointerOver', 'onPointerMove', 'onMouseDown', 'onMouseUp', 'onClick'];
    let current = node;
    let depth = 0;
    let invoked = false;

    while (current && current !== document.body && depth < 8) {
        handlers.forEach(handlerName => {
            if (invokeReactHandler(current, handlerName)) {
                invoked = true;
            }
        });
        current = current.parentElement;
        depth += 1;
    }

    return invoked;
}

function getPreferredClickPoint(node) {
    let rect = node?.getBoundingClientRect?.();
    if (!rect || rect.width <= 0 || rect.height <= 0) return null;

    let x = rect.left + rect.width / 2;
    let y = rect.top + rect.height / 2;

    if (node.matches?.('div[class*="edit-sn_snContainer"], div.elli_outerWrapper_5-178-0, div[class*="elli_outerWrapper"]')) {
        x = rect.right - Math.min(12, Math.max(6, rect.width * 0.12));
        y = rect.top + Math.min(rect.height - 4, Math.max(4, rect.height / 2));
    } else if (node.matches?.('div[data-testid="beast-core-box"]')) {
        x = rect.left + Math.min(rect.width - 4, Math.max(4, rect.width / 2));
        y = rect.top + Math.min(rect.height - 4, Math.max(4, rect.height / 2));
    } else if (node.matches?.('span[data-tracking-viewid="ele_edit_article_code"]')) {
        x = rect.left + Math.min(rect.width - 3, Math.max(3, rect.width / 2));
        y = rect.top + Math.min(rect.height - 3, Math.max(3, rect.height / 2));
    }

    return {
        x: Math.round(x),
        y: Math.round(y)
    };
}

function scoreGoodsCodeEditTarget(target, labelElements) {
    let labelCenters = labelElements.map(getElementCenter).filter(Boolean);
    let center = getElementCenter(target);
    let distance = Number.POSITIVE_INFINITY;

    if (center && labelCenters.length > 0) {
        distance = Math.min(...labelCenters.map(labelCenter => {
            return Math.abs(center.x - labelCenter.x) + Math.abs(center.y - labelCenter.y);
        }));
    }

    let score = 0;
    if (isVisibleElement(target)) score += 260;
    if (target.matches?.('span[data-tracking-viewid="ele_edit_article_code"]')) score += 900;
    if (target.matches?.('div[data-testid="beast-core-box"]')) score += 720;
    if (target.matches?.('div[class*="edit-sn_snContainer"]')) score += 680;
    if (target.matches?.('svg[data-testid="beast-core-icon-edit"]')) score += 560;
    if (String(target.className || '').includes('edit-sn_infoIcon')) score += 120;
    if (target.querySelector?.('span[data-tracking-viewid="ele_edit_article_code"]')) score += 180;
    if (target.querySelector?.('svg[data-testid="beast-core-icon-edit"]')) score += 120;
    score -= Math.min(distance, 1000);
    return score;
}

function findGoodsCodeEditTargets(row) {
    let labelElements = findGoodsCodeLabelElements(row);
    let targets = [];

    targets.push(...Array.from(row?.querySelectorAll('span[data-tracking-viewid="ele_edit_article_code"]') || []));
    targets.push(...Array.from(row?.querySelectorAll('div[data-testid="beast-core-box"]') || []).filter(el => {
        return el.querySelector('span[data-tracking-viewid="ele_edit_article_code"], svg[data-testid="beast-core-icon-edit"]');
    }));
    targets.push(...Array.from(row?.querySelectorAll('div[class*="edit-sn_snContainer"]') || []));
    targets.push(...Array.from(row?.querySelectorAll('svg[data-testid="beast-core-icon-edit"], svg[class*="edit-sn_infoIcon"], .edit-sn_infoIcon__lA3mG') || []));

    targets = uniqueElements(targets).filter(Boolean);
    if (targets.length === 0) return [];

    let scored = targets.map(target => ({
        target,
        score: scoreGoodsCodeEditTarget(target, labelElements)
    }));

    scored.sort((a, b) => b.score - a.score);
    return scored.map(item => item.target);
}

function findGoodsCodeEditIcon(row) {
    return findGoodsCodeEditTargets(row).find(target => target.matches?.('svg[data-testid="beast-core-icon-edit"], svg[class*="edit-sn_infoIcon"], .edit-sn_infoIcon__lA3mG')) || null;
}

function getGoodsCodeEditRevealTargets(row) {
    let editTargets = findGoodsCodeEditTargets(row);
    let revealTargets = [];

    editTargets.forEach(target => {
        revealTargets.push(target);
        revealTargets.push(target.parentElement);
        revealTargets.push(target.closest?.('div[data-testid="beast-core-box"]'));
        revealTargets.push(target.closest?.('span[data-tracking-viewid="ele_edit_article_code"]'));
        revealTargets.push(target.querySelector?.('span[data-tracking-viewid="ele_edit_article_code"]'));
        revealTargets.push(target.querySelector?.('svg[data-testid="beast-core-icon-edit"]'));
    });

    let labelElements = findGoodsCodeLabelElements(row);
    labelElements.forEach(label => {
        let current = label;
        let depth = 0;
        while (current && current !== row && depth < 6) {
            revealTargets.push(current);
            current = current.parentElement;
            depth += 1;
        }
    });

    return uniqueElements(revealTargets).filter(Boolean);
}

function revealGoodsCodeEditTargets(row) {
    let revealTargets = getGoodsCodeEditRevealTargets(row);
    let restores = [];

    revealTargets.forEach(target => {
        let styles = {
            visibility: 'visible',
            opacity: '1',
            pointerEvents: 'auto'
        };

        if (target.matches?.('div')) {
            styles.display = 'flex';
        } else if (target.matches?.('span')) {
            styles.display = 'inline-flex';
        } else if (target.matches?.('svg')) {
            styles.display = 'inline-block';
        }

        restores.push(applyTemporaryStyles(target, styles));
    });

    return () => {
        for (let i = restores.length - 1; i >= 0; i -= 1) {
            restores[i]();
        }
    };
}

function getNodeDebugName(node) {
    if (!node) return 'null';
    let parts = [node.tagName?.toLowerCase() || 'node'];
    if (node.getAttribute?.('data-tracking-viewid')) {
        parts.push(`[tracking=${node.getAttribute('data-tracking-viewid')}]`);
    }
    if (node.getAttribute?.('data-testid')) {
        parts.push(`[testid=${node.getAttribute('data-testid')}]`);
    }
    if (node.className && typeof node.className === 'string') {
        let cls = node.className.trim().split(/\s+/).slice(0, 2).join('.');
        if (cls) parts.push(`.${cls}`);
    }
    return parts.join('');
}

function findGoodsCodeFloatingEntry(row = null) {
    let candidates = Array.from(document.querySelectorAll('button, a, div, span')).filter(el => {
        if (!isVisibleElement(el)) return false;
        let text = cleanImageLabel(el.innerText || el.textContent);
        return text === '编码填写';
    });
    if (candidates.length === 0) return null;
    if (!row) return candidates[candidates.length - 1] || null;

    let scored = candidates.map(el => {
        let score = 0;
        let distance = getDistanceBetweenElements(row, el);
        if (distance < 400) score += 300;
        if (distance < 220) score += 240;
        if (String(el.className || '').includes('tooltip')) score += 40;
        score -= Math.min(distance, 800);
        return { el, score };
    });

    scored.sort((a, b) => b.score - a.score);
    return scored[0]?.el || null;
}

function findGoodsCodeOpenCandidates(row) {
    let elements = [];
    let editTargets = findGoodsCodeEditTargets(row);
    editTargets.forEach(target => {
        elements.push(target);
        elements.push(target.parentElement);
    });

    let labelElements = findGoodsCodeLabelElements(row);
    labelElements.forEach(el => {
        elements.push(el);
        elements.push(el.parentElement);
    });

    let all = Array.from(row.querySelectorAll('a, button, span, div'));

    for (let el of all) {
        if (!isVisibleElement(el)) continue;
        let text = cleanImageLabel(el.innerText || el.textContent);
        if (!text || text.length > 40) continue;

        if (text.includes('编码填写')) {
            elements.push(el);
            continue;
        }

        if (text.includes('商品编码')) {
            elements.push(el.closest('a, button, [role="button"]') || el);
            elements.push(el.parentElement);
        }
    }

    elements.push(row);
    return uniqueElements(elements).filter(Boolean);
}

async function waitForCondition(checkFn, timeoutMs = 2500, intervalMs = 80) {
    let start = Date.now();
    while (Date.now() - start < timeoutMs) {
        let result = checkFn();
        if (result) return result;
        await sleep(intervalMs);
    }
    return null;
}

async function waitForGoodsCodeDialogOpen(expectedProductId = '', timeoutMs = 2600) {
    let start = Date.now();
    let shellContext = null;

    while (Date.now() - start < timeoutMs) {
        let context = getGoodsCodeDialogContext();
        if (isExpectedGoodsCodeContext(context, expectedProductId)) {
            return context;
        }

        if (!shellContext) {
            let pending = getGoodsCodeDialogShellContext();
            if (isExpectedGoodsCodeContext(pending, expectedProductId)) {
                shellContext = pending;
            }
        }

        await sleep(80);
    }

    if (!shellContext) {
        shellContext = getGoodsCodeDialogShellContext();
        if (!isExpectedGoodsCodeContext(shellContext, expectedProductId)) {
            shellContext = null;
        }
    }

    if (!shellContext) return null;

    return waitForCondition(() => {
        let context = getGoodsCodeDialogContext(shellContext.root) || getGoodsCodeDialogContext();
        if (!isExpectedGoodsCodeContext(context, expectedProductId)) return null;
        return context?.rows?.length ? context : null;
    }, 2200, 80);
}

async function waitForGoodsCodeDialogClosed(timeoutMs = 2600) {
    let closed = await waitForCondition(() => !getGoodsCodeDialogContext(), timeoutMs, 80);
    return Boolean(closed);
}

async function clickElementAtCenter(target) {
    if (!target) return;

    let rect = target.getBoundingClientRect?.();
    if (!rect) {
        dispatchMouseClick(target);
        target.click?.();
        return;
    }

    let clientX = rect.left + Math.min(Math.max(rect.width / 2, 4), Math.max(rect.width - 4, 4));
    let clientY = rect.top + Math.min(Math.max(rect.height / 2, 4), Math.max(rect.height - 4, 4));
    let pointTarget = document.elementFromPoint(clientX, clientY) || target;
    let clickTargets = uniqueElements([
        pointTarget,
        target,
        target.parentElement,
        pointTarget?.parentElement,
        target.closest?.('[role="button"]'),
        target.closest?.('button'),
        target.closest?.('a'),
        target.closest?.('span'),
        target.closest?.('div')
    ]).filter(Boolean);

    for (let el of clickTargets) {
        ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach((type, index) => {
            el.dispatchEvent(new MouseEvent(type, {
                bubbles: true,
                cancelable: true,
                composed: true,
                view: window,
                clientX,
                clientY,
                button: 0,
                buttons: index < 2 ? 1 : 0,
                detail: type === 'click' ? 1 : 0
            }));
        });
        el.click?.();
    }
}

async function tryClickFloatingGoodsCodeEntry(row, productId, debugSteps = []) {
    let floatingEntry = await waitForCondition(() => findGoodsCodeFloatingEntry(row), 500, 60);
    if (!floatingEntry) return null;

    dispatchMouseHover(floatingEntry);
    await sleep(80);
    let nativeClicked = await nativeClickElement(floatingEntry);
    recordOpenDebug(debugSteps, `floating:${getNodeDebugName(floatingEntry)} native=${nativeClicked.success ? 'ok' : `fail(${nativeClicked.error || 'unknown'})@${nativeClicked.x || '-'},${nativeClicked.y || '-'}`}`);
    if (nativeClicked.success) {
        let context = await waitForGoodsCodeDialogOpen(productId, 1500);
        if (context) return context;
    }
    invokeReactClickChain(floatingEntry);
    clickNode(floatingEntry);
    recordOpenDebug(debugSteps, `floating:${getNodeDebugName(floatingEntry)} synthetic=done`);
    return waitForGoodsCodeDialogOpen(productId, 1200);
}

function getPrioritizedGoodsCodeTargets(row, labelElements, extraTargets = []) {
    let dynamicTargets = findGoodsCodeEditTargets(row);
    let merged = uniqueElements([
        ...dynamicTargets,
        ...dynamicTargets.map(target => target.closest?.('span[data-tracking-viewid="ele_edit_article_code"]')),
        ...dynamicTargets.map(target => target.closest?.('div[data-testid="beast-core-box"]')),
        ...dynamicTargets.map(target => target.closest?.('div[class*="edit-sn_snContainer"]')),
        ...dynamicTargets.map(target => target.parentElement),
        ...extraTargets
    ]).filter(Boolean);

    let scored = merged.map(target => ({
        target,
        score: scoreGoodsCodeEditTarget(target, labelElements)
    }));

    scored.sort((a, b) => b.score - a.score);
    return uniqueElements(scored.map(item => item.target)).slice(0, 2);
}

async function attemptOpenDialogWithTarget(target, productId, debugSteps, phase, nativeWaitMs = 2200, allowSyntheticFallback = false) {
    if (!target) return null;

    dispatchMouseHover(target);
    await sleep(40);

    let nativeClicked = await nativeClickElement(target);
    recordOpenDebug(debugSteps, `${phase}:${getNodeDebugName(target)} native=${nativeClicked.success ? 'ok' : `fail(${nativeClicked.error || 'unknown'})@${nativeClicked.x || '-'},${nativeClicked.y || '-'}`}`);
    if (nativeClicked.success) {
        let context = await waitForGoodsCodeDialogOpen(productId, nativeWaitMs);
        if (context) return context;
    }

    if (!allowSyntheticFallback) {
        return null;
    }

    invokeReactClickChain(target);
    clickNode(target);
    recordOpenDebug(debugSteps, `${phase}:${getNodeDebugName(target)} synthetic=done`);
    return waitForGoodsCodeDialogOpen(productId, 900);
}

async function warmGoodsCodeOpenTarget(target, productId, debugSteps, phase, nativeWaitMs = 320) {
    if (!target) return null;

    dispatchMouseHover(target);
    await sleep(40);

    let nativeClicked = await nativeClickElement(target);
    recordOpenDebug(debugSteps, `${phase}:${getNodeDebugName(target)} native=${nativeClicked.success ? 'ok' : `fail(${nativeClicked.error || 'unknown'})@${nativeClicked.x || '-'},${nativeClicked.y || '-'}`}`);
    if (!nativeClicked.success) return null;

    return waitForGoodsCodeDialogOpen(productId, nativeWaitMs);
}

function findBestGoodsCodeTarget(row, selector) {
    let labelElements = findGoodsCodeLabelElements(row);
    let targets = Array.from(row?.querySelectorAll(selector) || []).filter(Boolean);
    if (targets.length === 0) return null;

    let scored = targets.map(target => ({
        target,
        score: scoreGoodsCodeEditTarget(target, labelElements)
    }));

    scored.sort((a, b) => b.score - a.score);
    return scored[0]?.target || null;
}

async function waitForUsableGoodsCodeTarget(row, selector, timeoutMs = 500) {
    let start = Date.now();
    while (Date.now() - start < timeoutMs) {
        let target = findBestGoodsCodeTarget(row, selector);
        if (target) {
            let rect = target.getBoundingClientRect?.();
            if (rect && rect.width > 0 && rect.height > 0) {
                return target;
            }
        }
        await sleep(60);
    }
    return findBestGoodsCodeTarget(row, selector);
}

async function openGoodsCodeDialogFromRow(row) {
    let rowInfo = getGoodsListRowInfo(row);
    let debugSteps = [];

    let labelElements = findGoodsCodeLabelElements(row);
    let restoreReveal = revealGoodsCodeEditTargets(row);

    try {
        dispatchMouseHover(row);
        labelElements.forEach(dispatchMouseHover);
        await sleep(90);

        let trackingSpan = findBestGoodsCodeTarget(row, 'span[data-tracking-viewid="ele_edit_article_code"]');
        let warmBox = findBestGoodsCodeTarget(row, 'div[data-testid="beast-core-box"]');
        let warmContainer = findBestGoodsCodeTarget(row, 'div[class*="edit-sn_snContainer"]');
        let floatingEntry = findGoodsCodeFloatingEntry(row);

        if (!warmBox && !warmContainer && !trackingSpan && !floatingEntry) {
            throw new Error(`商品ID ${rowInfo.productId || '未知'} 未找到编码入口`);
        }

        let liveSpan = await waitForUsableGoodsCodeTarget(row, 'span[data-tracking-viewid="ele_edit_article_code"]', 900);
        let openTarget = liveSpan || trackingSpan || warmBox || warmContainer || floatingEntry;
        if (!openTarget) {
            throw new Error(`商品ID ${rowInfo.productId || '未知'} 未找到可点击的编码入口`);
        }

        let openPhase = openTarget === floatingEntry
            ? 'floating1-1'
            : (openTarget === warmBox ? 'fallback1-1' : (openTarget === warmContainer ? 'fallback1-2' : 'tracking0-1'));

        let context = await attemptOpenDialogWithTarget(
            openTarget,
            rowInfo.productId,
            debugSteps,
            openPhase,
            2200,
            false
        );
        if (context) {
            context.__openDebugSteps = debugSteps.slice();
            recordOpenDebug(debugSteps, `success:${rowInfo.productId || '未知'} open-by-${openPhase}`);
            return context;
        }

        let retryTarget = uniqueElements([
            liveSpan,
            trackingSpan,
            warmBox,
            warmContainer,
            floatingEntry
        ]).find(target => target && target !== openTarget) || openTarget;

        context = await attemptOpenDialogWithTarget(
            retryTarget,
            rowInfo.productId,
            debugSteps,
            `${openPhase}-retry`,
            1800,
            true
        );
        if (context) {
            context.__openDebugSteps = debugSteps.slice();
            recordOpenDebug(debugSteps, `success:${rowInfo.productId || '未知'} open-by-${openPhase}-retry`);
            return context;
        }
    } finally {
        restoreReveal();
    }

    throw new Error(`商品ID ${rowInfo.productId || '未知'} 打开编码弹窗失败 | ${debugSteps.slice(0, 12).join(' ; ')}`);
}

function findVisibleTextButtonWithin(root, keyword) {
    return Array.from(root?.querySelectorAll('button') || []).find(btn => {
        if (!isVisibleElement(btn)) return false;
        return cleanImageLabel(btn.innerText || btn.textContent).includes(keyword);
    }) || null;
}

function findGoodsCodeCloseButton(root) {
    let candidates = Array.from(root?.querySelectorAll('button, span, i, svg') || []).filter(isVisibleElement);
    return candidates.find(el => {
        let text = cleanImageLabel(el.innerText || el.textContent);
        let className = typeof el.className === 'string' ? el.className : '';
        let ariaLabel = cleanImageLabel(el.getAttribute?.('aria-label') || '');
        return text === '×' || ariaLabel.includes('关闭') || /close|关闭/i.test(className);
    }) || null;
}

async function closeGoodsCodeDialog(root, mode = 'cancel') {
    let button = mode === 'confirm'
        ? findVisibleTextButtonWithin(root, '确认')
        : (findVisibleTextButtonWithin(root, '取消') || findGoodsCodeCloseButton(root));

    if (!button) {
        throw new Error(mode === 'confirm' ? '未找到确认按钮' : '未找到取消/关闭按钮');
    }

    dispatchMouseClick(button);

    let closed = await waitForGoodsCodeDialogClosed(1400);
    if (!closed) {
        dispatchMouseClick(button);
        closed = await waitForGoodsCodeDialogClosed(1200);
    }

    if (!closed) {
        throw new Error('编码弹窗关闭超时');
    }
}

function appendUnknownSummary(result, productId, names) {
    let cleanNames = Array.from(new Set((names || []).map(name => normalizeSpecBaseName(name)).filter(Boolean)));
    if (cleanNames.length === 0) return;

    let existing = result.unknownItems.find(item => item.productId === productId);
    if (existing) {
        existing.names = Array.from(new Set(existing.names.concat(cleanNames)));
        return;
    }

    result.unknownItems.push({ productId, names: cleanNames });
}

function resetCodeTemplateDebugPanel(title = '编码点击实时日志') {
    document.getElementById(CODE_TEMPLATE_DEBUG_PANEL_ID)?.remove();
}

function appendCodeTemplateDebugLine(line) {
    return;
}

function recordOpenDebug(debugSteps, message) {
    debugSteps.push(message);
}

function formatCodeTemplateBatchReport(result) {
    let lines = [
        `当前页商品：${result.totalRows} 条`,
        `已打开弹窗：${result.processedRows} 条`,
        `成功写入链接：${result.changedLinks} 条`,
        `填充规格行数：${result.filledRows} 行`,
        `无母版跳过：${result.skippedNoTemplate} 条`
    ];

    if (result.unknownItems.length > 0) {
        lines.push('');
        lines.push('未识别规格：');
        result.unknownItems.forEach(item => {
            lines.push(`商品ID ${item.productId}：${item.names.join(' / ')}`);
        });
    }

    if (result.errors.length > 0) {
        lines.push('');
        lines.push('异常：');
        result.errors.forEach(item => {
            lines.push(`商品ID ${item.productId}：${item.message}`);
        });
    }

    return lines.join('\n');
}

function showCodeTemplateBatchReport(result) {
    document.getElementById(CODE_TEMPLATE_REPORT_ID)?.remove();

    let panel = document.createElement('div');
    panel.id = CODE_TEMPLATE_REPORT_ID;
    panel.style.cssText = `
        position: fixed; right: 20px; bottom: 280px; z-index: 100000;
        width: 360px; max-height: 440px; background: #fff; border: 1px solid #d9e6ff;
        border-radius: 12px; box-shadow: 0 12px 30px rgba(0,0,0,0.18);
        padding: 14px; display: flex; flex-direction: column; gap: 10px;
    `;

    let header = document.createElement('div');
    header.style.cssText = 'display:flex; align-items:center; justify-content:space-between; gap:8px;';

    let title = document.createElement('div');
    title.innerText = '母版批量结果';
    title.style.cssText = 'font-size:15px; font-weight:700; color:#1f2d3d;';

    let closeBtn = document.createElement('button');
    closeBtn.type = 'button';
    closeBtn.innerText = '关闭';
    closeBtn.style.cssText = getBlueButtonCss({ height: 28, minWidth: 72, fontSize: 12, radius: 8 });
    closeBtn.onclick = () => panel.remove();

    header.appendChild(title);
    header.appendChild(closeBtn);

    let body = document.createElement('pre');
    body.style.cssText = `
        margin:0; white-space:pre-wrap; word-break:break-word;
        font-size:12px; line-height:1.55; color:#334155;
        background:#f8fbff; border-radius:10px; padding:12px; overflow:auto;
    `;
    body.textContent = formatCodeTemplateBatchReport(result);

    panel.appendChild(header);
    panel.appendChild(body);
    document.body.appendChild(panel);

    if ((result.errors?.length || 0) === 0) {
        window.setTimeout(() => {
            if (panel.isConnected) {
                panel.remove();
            }
        }, 2000);
    }
}

async function runTemplateBatchFillCurrentPage() {
    if (isGoodsTemplateBatchRunning) {
        return showToast('⚠️ 母版批量套用执行中，请稍等');
    }
    if (isGoodsListCodeFillRunning) {
        return showToast('⚠️ 编码填充执行中，请稍等');
    }

    let tasks = getVisibleGoodsListRowTasks();
    if (tasks.length === 0) {
        return showToast('❌ 当前页没有识别到可处理的商品行');
    }

    isGoodsTemplateBatchRunning = true;
    let batchBtn = document.getElementById(GOODS_LIST_TEMPLATE_BATCH_BTN_ID);
    let fillBtn = document.getElementById(GOODS_LIST_CODE_FILL_BTN_ID);
    updateTemplateBatchDelayButton(true);
    setInlineButtonState(batchBtn, true, '批量套母版');
    if (fillBtn) {
        fillBtn.disabled = true;
        fillBtn.style.opacity = '0.72';
        fillBtn.style.cursor = 'wait';
    }

    let result = {
        totalRows: tasks.length,
        processedRows: 0,
        changedLinks: 0,
        filledRows: 0,
        skippedNoTemplate: 0,
        unknownItems: [],
        errors: []
    };

    try {
        resetCodeTemplateDebugPanel(`编码点击实时日志（共 ${tasks.length} 条）`);
        showToast(`⚡ 当前页识别到 ${tasks.length} 条商品，开始批量套用母版`);

        for (let index = 0; index < tasks.length; index += 1) {
            let task = tasks[index];
            let template = getCodeTemplateByItemNo(task.itemNo);

            if (!template) {
                result.skippedNoTemplate += 1;
                continue;
            }

            try {
                let row = await waitForVisibleGoodsListRowByInfo(task, 3600);
                if (!row) {
                    throw new Error(`商品ID ${task.productId || '未知'} 当前页未重新定位到商品行`);
                }

                let rowInfo = getGoodsListRowInfo(row);
                appendCodeTemplateDebugLine(`开始处理 商品ID ${rowInfo.productId || task.productId || '未知'} / 商品编码 ${rowInfo.itemNo || task.itemNo || '未知'}`);
                row.scrollIntoView({ block: 'center' });
                await sleep(220);

                let context = await openGoodsCodeDialogFromRow(row);
                result.processedRows += 1;

                let liveTemplate = getCodeTemplateByItemNo(context.itemNo || rowInfo.itemNo || task.itemNo);
                if (!liveTemplate) {
                    result.skippedNoTemplate += 1;
                    await closeGoodsCodeDialog(context.root, 'cancel');
                    await waitForGoodsListStable(900, 120);
                    continue;
                }

                let applyResult = applyCodeTemplateToContext(context, liveTemplate);
                result.filledRows += applyResult.filledCount;
                if (applyResult.filledCount > 0) {
                    result.changedLinks += 1;
                }
                appendUnknownSummary(result, context.productId || rowInfo.productId || '未知', applyResult.unknownSpecs);

                if (applyResult.filledCount > 0) {
                    await sleep(200);
                    await closeGoodsCodeDialog(context.root, 'confirm');
                    await sleep(getTemplateBatchPostConfirmDelayMs());
                } else {
                    await closeGoodsCodeDialog(context.root, 'cancel');
                }
                appendCodeTemplateDebugLine(`完成 商品ID ${context.productId || rowInfo.productId || task.productId || '未知'} | 填充 ${applyResult.filledCount} 行 | 未识别 ${applyResult.unknownSpecs.length} 项`);

            } catch (e) {
                result.errors.push({
                    productId: task.productId || '未知',
                    message: e?.message || '执行失败'
                });
                appendCodeTemplateDebugLine(`异常 商品ID ${task.productId || '未知'} | ${e?.message || '执行失败'}`);

                let context = getGoodsCodeDialogContext();
                if (context) {
                    try {
                        await closeGoodsCodeDialog(context.root, 'cancel');
                    } catch (closeError) {
                        console.warn('⚠️ 关闭异常弹窗失败:', closeError);
                    }
                }
            }
        }

        showCodeTemplateBatchReport(result);
        let summaryText = `✅ 批量完成：写入 ${result.changedLinks} 条，未识别 ${result.unknownItems.length} 条${result.errors.length ? `，异常 ${result.errors.length} 条` : ''}`;
        showToast(summaryText);
    } finally {
        isGoodsTemplateBatchRunning = false;
        setInlineButtonState(batchBtn, false, '批量套母版');
        updateTemplateBatchDelayButton(false);
        if (fillBtn) {
            fillBtn.disabled = false;
            fillBtn.style.opacity = '1';
            fillBtn.style.cursor = 'pointer';
        }
    }
}

// ==========================================
// 图片顺序勾选逻辑
// ==========================================
function cleanImageLabel(value) {
    return String(value || '').replace(/\s+/g, ' ').trim();
}

function getNumericImageName(text) {
    let value = cleanImageLabel(text);
    if (!value) return null;

    if (/^\d+$/.test(value)) {
        return Number(value);
    }

    let fileNameMatch = value.match(/^(\d+)(?:\.[a-z0-9]+)?$/i);
    if (fileNameMatch) {
        return Number(fileNameMatch[1]);
    }

    return null;
}

function isVisibleElement(el) {
    if (!el) return false;
    let style = window.getComputedStyle(el);
    let rect = el.getBoundingClientRect();
    return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
}

function getImageCardInfo(card, index) {
    let preferredNodes = Array.from(card.querySelectorAll(IMAGE_TITLE_SELECTOR));
    let allSpanNodes = Array.from(card.querySelectorAll('span'));
    let candidates = preferredNodes.length > 0 ? preferredNodes : allSpanNodes;

    for (let node of candidates) {
        let text = cleanImageLabel(node.textContent);
        let numericValue = getNumericImageName(text);
        if (numericValue !== null) {
            return { card, index, text, numericValue };
        }
    }

    return null;
}

function normalizeImageMatchName(value) {
  return cleanImageLabel(value)
        .replace(/\.(jpeg|jpg|png|webp|gif|bmp)$/i, '')
        .replace(/[.。…]+$/g, '')
        .trim();
}

function buildLooseImageMatchKey(value) {
    return normalizeImageMatchName(value)
        .replace(/[\s\-_/\\|,，.。…:：;；]+/g, '')
        .toLowerCase();
}

function isImageMetaText(text) {
    return !text ||
        /^\d+\s*[x*]\s*\d+$/i.test(text) ||
        /^\d+(?:\.\d+)?\s*(kb|mb|gb)$/i.test(text) ||
        text === '图片' ||
        text === '查询' ||
        text === '刷新' ||
        text === '本地上传' ||
        text === '确认' ||
        text === '取消';
}

function getImageCardDisplayText(card) {
    let preferredNodes = Array.from(card.querySelectorAll(IMAGE_TITLE_SELECTOR));
    let allNodes = Array.from(card.querySelectorAll('span, div'));
    let candidates = preferredNodes.length > 0 ? preferredNodes : allNodes;
    let texts = [];

    for (let node of candidates) {
        let text = cleanImageLabel(node.textContent);
        if (isImageMetaText(text)) continue;
        texts.push(text);
    }

    if (texts.length === 0) return '';
    texts.sort((a, b) => b.length - a.length);
    return texts[0];
}

function getNamedImageCardInfo(card, index) {
    let text = getImageCardDisplayText(card);
    if (!text) return null;

    return {
        card,
        index,
        text,
        matchName: normalizeImageMatchName(text)
    };
}

function isSkuImageNameMatch(cardName, expectedName) {
    return getSkuImageMatchScore(cardName, expectedName) > 0;
}

function getSkuImageMatchScore(cardName, expectedName) {
    let cardValue = normalizeImageMatchName(cardName);
    let expectedValue = normalizeImageMatchName(expectedName);
    let cardLoose = buildLooseImageMatchKey(cardName);
    let expectedLoose = buildLooseImageMatchKey(expectedName);

    if (!cardValue || !expectedValue || !cardLoose || !expectedLoose) return 0;
    if (cardValue === expectedValue || cardLoose === expectedLoose) return 100;
    if (cardValue.startsWith(expectedValue) || expectedValue.startsWith(cardValue)) return 90;
    if (cardLoose.startsWith(expectedLoose) || expectedLoose.startsWith(cardLoose)) return 80;
    if (cardValue.includes(expectedValue) || expectedValue.includes(cardValue)) return 70;
    if (cardLoose.includes(expectedLoose) || expectedLoose.includes(cardLoose)) return 60;
    return 0;
}

function findBestNamedImageCard(expectedName, namedCards, usedIndexes) {
    let bestItem = null;
    let bestScore = 0;

    for (let item of namedCards) {
        if (!item || usedIndexes.has(item.index)) continue;
        let score = getSkuImageMatchScore(item.text, expectedName);
        if (score <= 0) continue;

        if (!bestItem || score > bestScore || (score === bestScore && item.index < bestItem.index)) {
            bestItem = item;
            bestScore = score;
        }
    }

    return bestItem;
}

function dispatchMouseClick(element) {
    if (!element) return;

    ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach(type => {
        element.dispatchEvent(new MouseEvent(type, {
            bubbles: true,
            cancelable: true,
            view: window
        }));
    });
}

function isImageCardChecked(card) {
    let checkbox = card.querySelector(IMAGE_CHECKBOX_SELECTOR);
    let input = checkbox?.querySelector('input[type="checkbox"]') || card.querySelector('input[type="checkbox"]');

    return Boolean(
        input?.checked ||
        checkbox?.dataset?.checked === 'true' ||
        checkbox?.getAttribute('aria-checked') === 'true'
    );
}

async function clickImageCard(card) {
    if (isImageCardChecked(card)) return true;

    let checkbox = card.querySelector(IMAGE_CHECKBOX_SELECTOR);
    let input = checkbox?.querySelector('input[type="checkbox"]') || card.querySelector('input[type="checkbox"]');
    let target = checkbox || input || card;

    dispatchMouseClick(target);
    await new Promise(resolve => setTimeout(resolve, 100));

    if (!isImageCardChecked(card) && target !== input && input) {
        dispatchMouseClick(input);
        await new Promise(resolve => setTimeout(resolve, 100));
    }

    if (!isImageCardChecked(card)) {
        dispatchMouseClick(card);
        await new Promise(resolve => setTimeout(resolve, 100));
    }

    return isImageCardChecked(card);
}

function findNearestConfirmButton(card) {
    let current = card;
    while (current && current !== document.body) {
        let buttons = Array.from(current.querySelectorAll('button')).filter(btn => {
            return isVisibleElement(btn) && cleanImageLabel(btn.innerText || btn.textContent).includes('确认');
        });
        if (buttons.length > 0) {
            return buttons[buttons.length - 1];
        }
        current = current.parentElement;
    }

    return Array.from(document.querySelectorAll('button')).find(btn => {
        return isVisibleElement(btn) && cleanImageLabel(btn.innerText || btn.textContent).includes('确认');
    }) || null;
}

function findImageSelectionScope() {
    let anchor = findImageSelectSubtitleAnchor();
    if (!anchor) return document;

    let current = anchor;
    while (current && current !== document.body) {
        if (current.querySelector(IMAGE_CARD_SELECTOR) && findVisibleTextButtonWithin(current, '确认')) {
            return current;
        }
        current = current.parentElement;
    }

    return document;
}

function findNumericCardAbove(numericCards, currentIndex, expected) {
    for (let i = numericCards.length - 1; i >= 0; i--) {
        let item = numericCards[i];
        if (item.index < currentIndex && item.numericValue === expected) {
            return item;
        }
    }
    return null;
}

async function runSequentialImageSelection({ mode, maxTargetNumber }) {
    let cards = Array.from(document.querySelectorAll(IMAGE_CARD_SELECTOR)).filter(isVisibleElement);
    if (cards.length === 0) {
        return '❌ 没有找到图片卡片，请先打开图片选择弹窗。';
    }

    let numericCards = cards
        .map((card, index) => getImageCardInfo(card, index))
        .filter(Boolean);

    if (numericCards.length === 0) {
        return '❌ 没有找到名称为纯数字的图片项。';
    }

    let firstOne = numericCards.find(item => item.numericValue === 1);
    if (!firstOne) {
        return `❌ 找到了 ${numericCards.length} 个数字项，但没有找到名称为 1 的图片。`;
    }

    let selectedNumbers = [];
    let currentIndex = firstOne.index;
    let expected = 1;

    while (expected <= maxTargetNumber) {
        let targetItem = expected === 1
            ? firstOne
            : findNumericCardAbove(numericCards, currentIndex, expected);

        if (!targetItem) break;

        let selected = await clickImageCard(targetItem.card);
        if (!selected) {
            return `❌ 找到了名称为 ${expected} 的图片，但点击后未成功勾选。`;
        }

        selectedNumbers.push(expected);
        currentIndex = targetItem.index;
        expected += 1;
    }

    if (selectedNumbers.length === 0) {
        return '❌ 没有成功勾选任何图片。';
    }

    let confirmButton = findNearestConfirmButton(firstOne.card);
    if (!confirmButton) {
        return `⚠️ 已勾选 ${selectedNumbers.join(', ')}，但没有找到“确认”按钮。`;
    }

    dispatchMouseClick(confirmButton);
    await sleep(150);

    let lastNumber = selectedNumbers[selectedNumbers.length - 1];
    if (mode === 'main') {
        return `✅ 主图已勾选 ${selectedNumbers.join(', ')}，最高到 ${lastNumber}，并已点击确认。`;
    }
    return `✅ 详情图已连续勾选到 ${lastNumber}，并已点击确认。`;
}

async function runNamedImageSelection(targetNames) {
    let names = Array.isArray(targetNames)
        ? targetNames.map(name => cleanImageLabel(name)).filter(Boolean)
        : [];

    if (names.length === 0) {
        return '⚠️ 没有可匹配的 SKU 图片名称。';
    }

    let scope = findImageSelectionScope();
    let cards = Array.from(scope.querySelectorAll(IMAGE_CARD_SELECTOR)).filter(isVisibleElement);
    if (cards.length === 0) {
        return '❌ 没有找到图片卡片，请先打开图片选择弹窗。';
    }

    let namedCards = cards
        .map((card, index) => getNamedImageCardInfo(card, index))
        .filter(Boolean);

    if (namedCards.length === 0) {
        return '❌ 没有读到可用于匹配的图片名称。';
    }

    let selectedPairs = [];
    let missingNames = [];
    let usedIndexes = new Set();

    for (let expectedName of names) {
        let item = findBestNamedImageCard(expectedName, namedCards, usedIndexes);
        if (!item) {
            missingNames.push(expectedName);
            continue;
        }

        let selected = await clickImageCard(item.card);
        if (!selected) {
            return `❌ 找到了图片“${item.text}”，但未能成功勾选。`;
        }

        usedIndexes.add(item.index);
        selectedPairs.push({
            skuName: expectedName,
            imageName: item.text,
            card: item.card
        });
    }

    if (selectedPairs.length === 0) {
        return '❌ 没有找到与 SKU 列表匹配的图片名称。';
    }

    let confirmButton = findNearestConfirmButton(selectedPairs[0].card);
    if (!confirmButton) {
        return `⚠️ 已勾选 ${selectedPairs.length} 张 SKU 图片，但没有找到“确认”按钮。`;
    }

    dispatchMouseClick(confirmButton);
    await sleep(150);

    let selectedSummary = selectedPairs.map(item => item.skuName).join('、');
    if (missingNames.length > 0) {
        return `⚠️ SKU 图已按顺序勾选 ${selectedPairs.length} 张：${selectedSummary}。未找到：${missingNames.join('、')}。并已点击确认。`;
    }

    return `✅ SKU 图已按顺序勾选 ${selectedPairs.length} 张：${selectedSummary}，并已点击确认。`;
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function findCarouselRoot() {
    let roots = Array.from(document.querySelectorAll('div[data-testid="beast-core-grid-col-wrapper"]'));
    let best = null;
    let bestScore = -1;

    for (let root of roots) {
        let imageCount = root.querySelectorAll(CAROUSEL_IMAGE_ITEM_SELECTOR).length;
        if (imageCount === 0) continue;

        let text = cleanImageLabel(root.textContent);
        let score = imageCount;

        if (text.includes('拖拽可调整顺序')) score += 120;
        if (text.includes('已上传')) score += 40;
        if (text.includes('图片要求')) score += 20;

        if (score > bestScore) {
            best = root;
            bestScore = score;
        }
    }

    return best;
}

function findDetailRoot() {
    let rows = Array.from(document.querySelectorAll('div[data-testid="beast-core-grid-row"]'));
    let best = null;
    let bestScore = -1;

    for (let row of rows) {
        let imageCount = row.querySelectorAll(DETAIL_IMAGE_ITEM_SELECTOR).length;
        if (imageCount === 0) continue;

        let text = cleanImageLabel(row.textContent);
        let score = imageCount;

        if (text.includes('图片空间上传')) score += 80;
        if (text.includes('上传图片')) score += 30;

        if (score > bestScore) {
            best = row;
            bestScore = score;
        }
    }

    return best;
}

function getCarouselItems(root) {
    if (!root) return [];
    return Array.from(root.querySelectorAll(CAROUSEL_IMAGE_ITEM_SELECTOR)).filter(isVisibleElement);
}

function getDetailItems(root) {
    if (!root) return [];
    return Array.from(root.querySelectorAll(DETAIL_IMAGE_ITEM_SELECTOR)).filter(isVisibleElement);
}

function findImageDeleteButton(imageWrapper) {
    if (!imageWrapper) return null;
    let icon = imageWrapper.querySelector(IMAGE_DELETE_SELECTOR);
    if (!icon) return null;
    return icon.closest('i') || icon;
}

async function waitForListLengthDrop(findRoot, getItems, beforeLength, timeoutMs) {
    let start = Date.now();
    while (Date.now() - start < timeoutMs) {
        let root = findRoot();
        if (root) {
            let nowLength = getItems(root).length;
            if (nowLength < beforeLength) return true;
        }
        await sleep(55);
    }
    return false;
}

async function keepOnlyFirstImage({ typeLabel, findRoot, getItems }) {
    let start = Date.now();
    let hardTimeoutMs = 45000;
    let settleAfterChangeMs = 850;
    let lastLength = -1;
    let lastLengthChangeAt = Date.now();
    let deletedCount = 0;

    while (Date.now() - start < hardTimeoutMs) {
        let root = findRoot();
        if (!root) {
            await sleep(180);
            continue;
        }

        let items = getItems(root);
        if (items.length !== lastLength) {
            lastLength = items.length;
            lastLengthChangeAt = Date.now();
        }

        if (items.length <= 1) {
            let needMs = deletedCount > 0 ? settleAfterChangeMs : 180;
            if (Date.now() - lastLengthChangeAt >= needMs) {
                return deletedCount;
            }
            await sleep(90);
            continue;
        }

        let beforeLength = items.length;
        let target = items[1];
        let deleteBtn = findImageDeleteButton(target);
        if (!deleteBtn) {
            throw new Error(`未找到${typeLabel}第2张的删除按钮`);
        }

        dispatchMouseClick(deleteBtn);
        let removed = await waitForListLengthDrop(findRoot, getItems, beforeLength, 900);
        if (!removed) {
            await sleep(100);
            dispatchMouseClick(deleteBtn);
            removed = await waitForListLengthDrop(findRoot, getItems, beforeLength, 1100);
        }

        if (removed) {
            deletedCount += 1;
            lastLengthChangeAt = Date.now();
            continue;
        }

        await sleep(160);
    }

    throw new Error(`${typeLabel}删除超时，请稍后重试`);
}

function setInlineButtonState(button, running, idleText) {
    if (!button) return;
    button.disabled = running;
    button.innerText = running ? '处理中...' : idleText;
    button.style.opacity = running ? '0.72' : '1';
    button.style.cursor = running ? 'wait' : 'pointer';
}

function isAnyImageActionRunning() {
    return Boolean(isImageSelectRunning || isCarouselKeepFirstRunning || isDetailKeepFirstRunning);
}

function setImageActionButtonsLocked(locked, activeId = '') {
    const configs = [
        { id: CAROUSEL_KEEP_FIRST_BTN_ID, idle: '只保留首图' },
        { id: DETAIL_KEEP_FIRST_BTN_ID, idle: '只保留首图' },
        { id: MAIN_IMAGE_SELECT_BTN_ID, idle: '主图选择' },
        { id: DETAIL_IMAGE_SELECT_BTN_ID, idle: '详情图选择' }
    ];

    for (const cfg of configs) {
        const btn = document.getElementById(cfg.id);
        if (!btn) continue;

        btn.disabled = locked;
        btn.style.opacity = locked ? '0.68' : '1';
        btn.style.cursor = locked ? 'wait' : 'pointer';
        btn.innerText = locked && cfg.id === activeId ? '处理中...' : cfg.idle;
    }
}

function getBlueButtonCss({ height = 32, minWidth = 120, paddingX = 12, fontSize = 13, radius = 8 } = {}) {
    return `
        display: inline-flex; align-items: center; justify-content: center;
        min-width: ${minWidth}px; height: ${height}px; padding: 0 ${paddingX}px;
        border: none; border-radius: ${radius}px; font-size: ${fontSize}px; font-weight: bold;
        color: #fff; background: ${PRIMARY_BTN_COLOR}; cursor: pointer; white-space: nowrap;
        box-shadow: 0 6px 16px rgba(64, 125, 255, 0.32);
    `;
}

async function runMainImageSelection() {
    if (isAnyImageActionRunning()) return showToast('⚠️ 图片任务进行中，请等待当前任务完成');
    isImageSelectRunning = true;
    setImageActionButtonsLocked(true, MAIN_IMAGE_SELECT_BTN_ID);

    try {
        showToast('⚡ 主图选择执行中...');
        let result = await runSequentialImageSelection({ mode: 'main', maxTargetNumber: 9 });
        showToast(result);
    } catch (e) {
        showToast(`❌ 主图选择失败：${e.message}`);
    } finally {
        isImageSelectRunning = false;
        if (!isAnyImageActionRunning()) setImageActionButtonsLocked(false);
    }
}

async function runDetailImageSelection() {
    if (isAnyImageActionRunning()) return showToast('⚠️ 图片任务进行中，请等待当前任务完成');
    isImageSelectRunning = true;
    setImageActionButtonsLocked(true, DETAIL_IMAGE_SELECT_BTN_ID);

    try {
        showToast('⚡ 详情图选择执行中...');
        let result = await runSequentialImageSelection({ mode: 'detail', maxTargetNumber: Number.POSITIVE_INFINITY });
        showToast(result);
    } catch (e) {
        showToast(`❌ 详情图选择失败：${e.message}`);
    } finally {
        isImageSelectRunning = false;
        if (!isAnyImageActionRunning()) setImageActionButtonsLocked(false);
    }
}

function ensureImageSelectButtons() {
    ensurePublishDialogStyles();
    let container = document.getElementById(IMAGE_SELECT_ACTIONS_ID);
    if (!container) {
        container = document.createElement('div');
        container.id = IMAGE_SELECT_ACTIONS_ID;
        container.style.cssText = 'display:inline-flex; align-items:center; gap:8px; margin-left:12px;';
    }

    let mainBtn = document.getElementById(MAIN_IMAGE_SELECT_BTN_ID);
    if (!mainBtn) {
        mainBtn = document.createElement('button');
        mainBtn.id = MAIN_IMAGE_SELECT_BTN_ID;
        mainBtn.type = 'button';
        mainBtn.innerText = '主图选择';
        mainBtn.style.cssText = getBlueButtonCss({ height: 32, minWidth: 92, fontSize: 13 });
        mainBtn.onmousedown = (e) => e.stopPropagation();
        mainBtn.onclick = runMainImageSelection;
    }

    let detailBtn = document.getElementById(DETAIL_IMAGE_SELECT_BTN_ID);
    if (!detailBtn) {
        detailBtn = document.createElement('button');
        detailBtn.id = DETAIL_IMAGE_SELECT_BTN_ID;
        detailBtn.type = 'button';
        detailBtn.innerText = '详情图选择';
        detailBtn.style.cssText = getBlueButtonCss({ height: 32, minWidth: 92, fontSize: 13 });
        detailBtn.onmousedown = (e) => e.stopPropagation();
        detailBtn.onclick = runDetailImageSelection;
    }

    let helpLink = document.getElementById(IMAGE_HELP_LINK_ID);
    if (!helpLink) {
        helpLink = document.createElement('a');
        helpLink.id = IMAGE_HELP_LINK_ID;
        helpLink.className = 'sxz-pdd-link';
        helpLink.textContent = '使用说明';
        helpLink.href = chrome.runtime.getURL('modules/publish/help.html');
        helpLink.target = '_blank';
        helpLink.rel = 'noopener noreferrer';
        helpLink.title = '打开商品发布功能简介';
        helpLink.onmousedown = (e) => e.stopPropagation();
    }

    if (!container.contains(mainBtn)) container.appendChild(mainBtn);
    if (!container.contains(detailBtn)) container.appendChild(detailBtn);
    if (!container.contains(helpLink)) container.appendChild(helpLink);
    return container;
}

function findImageSelectSubtitleAnchor() {
    let candidates = Array.from(document.querySelectorAll('div[class*="MmsUiMaterialModalV3___sub-title___"]'));
    for (let node of candidates) {
        if (!isVisibleElement(node)) continue;
        let text = cleanImageLabel(node.textContent);
        if (!text) continue;
        if (text.includes('宽高比') || text.includes('宽高比≥1:3') || text.includes('宽度480px以上')) {
            return node;
        }
    }
    return null;
}

function syncImageSelectButtons() {
    let anchor = findImageSelectSubtitleAnchor();
    if (!anchor) {
        document.getElementById(IMAGE_SELECT_ACTIONS_ID)?.remove();
        return;
    }
    let container = ensureImageSelectButtons();

    if (container.parentElement !== anchor.parentElement || container.previousElementSibling !== anchor) {
        anchor.insertAdjacentElement('afterend', container);
    }

    let holder = anchor.parentElement;
    if (holder) {
        holder.style.display = 'flex';
        holder.style.alignItems = 'center';
        holder.style.flexWrap = 'wrap';
        holder.style.gap = '8px';
    }
}

async function runCarouselKeepFirst() {
    if (isAnyImageActionRunning()) return showToast('⚠️ 图片任务进行中，请等待当前任务完成');

    isCarouselKeepFirstRunning = true;
    setImageActionButtonsLocked(true, CAROUSEL_KEEP_FIRST_BTN_ID);

    try {
        showToast('⚡ 开始删除主图，仅保留第1张');
        let deletedCount = await keepOnlyFirstImage({
            typeLabel: '主图',
            findRoot: findCarouselRoot,
            getItems: getCarouselItems
        });
        showToast(`✅ 主图清理完成，仅保留第1张（删除 ${deletedCount} 张）`);
    } catch (e) {
        showToast(`❌ 主图清理失败：${e.message}`);
    } finally {
        isCarouselKeepFirstRunning = false;
        if (!isAnyImageActionRunning()) setImageActionButtonsLocked(false);
    }
}

async function runDetailKeepFirst() {
    if (isAnyImageActionRunning()) return showToast('⚠️ 图片任务进行中，请等待当前任务完成');

    isDetailKeepFirstRunning = true;
    setImageActionButtonsLocked(true, DETAIL_KEEP_FIRST_BTN_ID);

    try {
        showToast('⚡ 开始删除详情图，仅保留第1张');
        let deletedCount = await keepOnlyFirstImage({
            typeLabel: '详情图',
            findRoot: findDetailRoot,
            getItems: getDetailItems
        });
        showToast(`✅ 详情图清理完成，仅保留第1张（删除 ${deletedCount} 张）`);
    } catch (e) {
        showToast(`❌ 详情图清理失败：${e.message}`);
    } finally {
        isDetailKeepFirstRunning = false;
        if (!isAnyImageActionRunning()) setImageActionButtonsLocked(false);
    }
}

function ensureCarouselKeepFirstButton() {
    let button = document.getElementById(CAROUSEL_KEEP_FIRST_BTN_ID);
    if (button) return button;

    button = document.createElement('button');
    button.id = CAROUSEL_KEEP_FIRST_BTN_ID;
    button.type = 'button';
    button.innerText = '只保留首图';
    button.style.cssText = `${getBlueButtonCss({ height: 32, minWidth: 96, fontSize: 13 })} margin-left: 12px;`;
    button.onmousedown = (e) => e.stopPropagation();
    button.onclick = runCarouselKeepFirst;
    return button;
}

function ensureDetailKeepFirstButton() {
    let button = document.getElementById(DETAIL_KEEP_FIRST_BTN_ID);
    if (button) return button;

    button = document.createElement('button');
    button.id = DETAIL_KEEP_FIRST_BTN_ID;
    button.type = 'button';
    button.innerText = '只保留首图';
    button.style.cssText = `${getBlueButtonCss({ height: 32, minWidth: 96, fontSize: 13 })} margin-right: 12px;`;
    button.onmousedown = (e) => e.stopPropagation();
    button.onclick = runDetailKeepFirst;
    return button;
}

function findCarouselModuleIntroduceAnchor() {
    let nodes = Array.from(document.querySelectorAll('div.moduleIntroduce'));
    for (let node of nodes) {
        if (!isVisibleElement(node)) continue;
        let text = cleanImageLabel(node.textContent);
        if (!text.includes('拖拽可调整顺序')) continue;

        let root = node.closest('div[data-testid="beast-core-grid-col-wrapper"]');
        if (root && root.querySelector(CAROUSEL_IMAGE_ITEM_SELECTOR)) {
            return node;
        }
    }
    return null;
}

function findUploadImageSpaceButton() {
    let buttons = Array.from(document.querySelectorAll('button[data-testid="beast-core-button"], button'));
    for (let button of buttons) {
        if (!isVisibleElement(button)) continue;
        let text = cleanImageLabel(button.innerText || button.textContent);
        if (!text.includes('图片空间上传')) continue;

        let row = button.closest('div[data-testid="beast-core-grid-row"]');
        if (row && row.querySelector(DETAIL_IMAGE_ITEM_SELECTOR)) {
            return button;
        }
    }
    return null;
}

function syncCarouselKeepFirstButton() {
    let anchor = findCarouselModuleIntroduceAnchor();
    if (!anchor) {
        document.getElementById(CAROUSEL_KEEP_FIRST_BTN_ID)?.remove();
        return;
    }
    let button = ensureCarouselKeepFirstButton();

    if (button.parentElement !== anchor.parentElement || button.previousElementSibling !== anchor) {
        anchor.insertAdjacentElement('afterend', button);
    }

    let holder = anchor.parentElement;
    if (holder) {
        holder.style.display = 'flex';
        holder.style.alignItems = 'center';
        holder.style.gap = '8px';
        holder.style.flexWrap = 'wrap';
    }
}

function syncDetailKeepFirstButton() {
    let uploadButton = findUploadImageSpaceButton();
    if (!uploadButton) {
        document.getElementById(DETAIL_KEEP_FIRST_BTN_ID)?.remove();
        return;
    }
    let button = ensureDetailKeepFirstButton();

    if (button.parentElement !== uploadButton.parentElement || button.nextElementSibling !== uploadButton) {
        uploadButton.insertAdjacentElement('beforebegin', button);
    }
}

function findColorSpecContainer() {
    let containers = Array.from(document.querySelectorAll(SPEC_CONTAINER_SELECTOR)).filter(isVisibleElement);
    if (containers.length === 0) return null;

    let best = null;
    let bestRows = -1;
    for (let container of containers) {
        let rowCount = container.querySelectorAll(SPEC_ROW_SELECTOR).length;
        if (rowCount > bestRows) {
            best = container;
            bestRows = rowCount;
        }
    }
    return best;
}

function getColorSpecRows(root) {
    if (!root) return [];
    return Array.from(root.querySelectorAll(SPEC_ROW_SELECTOR))
        .filter(row => row.closest(SPEC_CONTAINER_SELECTOR) === root && isVisibleElement(row));
}

function getSpecDeleteButton(row) {
    if (!row) return null;
    let links = Array.from(row.querySelectorAll('a[data-testid="beast-core-button-link"], a'));
    return links.find(link => cleanImageLabel(link.innerText || link.textContent).includes('删除')) || null;
}

function isPrefilledSpecRow(row) {
    let deleteBtn = getSpecDeleteButton(row);
    if (!deleteBtn) return false;

    let colorInput = row.querySelector('input[placeholder="选择或输入主色"]');
    if (!colorInput) return true;
    return cleanImageLabel(colorInput.value).length > 0;
}

function getFilledSkuRows() {
    let root = findColorSpecContainer();
    if (!root) return [];
    return getColorSpecRows(root).filter(isPrefilledSpecRow);
}

function findSkuImageUploadContainer(row) {
    if (!row) return null;
    return row.querySelector('[class*="MaterialModalButton_materialContainer"]');
}

function getSkuImagePickerTargets(row) {
    let container = findSkuImageUploadContainer(row);
    if (!container || !isVisibleElement(container)) return [];

    return uniqueElements([
        container.querySelector('[class*="sku-preview-img"]'),
        container.querySelector('.sku-preview-img'),
        container.querySelector('.upload-logic'),
        container.querySelector('[class*="upload-logic"]'),
        container
    ]).filter(isVisibleElement);
}

async function openSkuImagePickerFromFirstRow() {
    let firstRow = await waitForCondition(() => getFilledSkuRows()[0], 5000, 120);
    if (!firstRow) {
        throw new Error('未找到已生成的 SKU 行');
    }

    let targets = getSkuImagePickerTargets(firstRow);
    if (targets.length === 0) {
        throw new Error('未找到 SKU 图片上传入口');
    }

    firstRow.scrollIntoView({ block: 'center' });
    await sleep(160);

    for (let target of targets) {
        dispatchMouseClick(target);
        let opened = await waitForCondition(() => findImageSelectSubtitleAnchor(), 2200, 90);
        if (opened) {
            return opened;
        }
        await sleep(120);
    }

    throw new Error('未能打开 SKU 图片空间窗口');
}

async function autoAssignSkuImagesByNames(skuList) {
    let names = Array.isArray(skuList)
        ? skuList.map(name => cleanImageLabel(name)).filter(Boolean)
        : [];

    if (names.length === 0) {
        return '⚠️ 没有可用于自动选图的 SKU 名称。';
    }

    await openSkuImagePickerFromFirstRow();

    let loaded = await waitForCondition(() => {
        let scope = findImageSelectionScope();
        let cards = Array.from(scope.querySelectorAll(IMAGE_CARD_SELECTOR)).filter(isVisibleElement);
        return cards.length > 0 ? cards : null;
    }, 5000, 100);

    if (!loaded) {
        throw new Error('图片空间已打开，但未加载到图片列表');
    }

    return runNamedImageSelection(names);
}

async function waitForSpecRowChanged(findRoot, previousCount, oldRow, timeoutMs) {
    let start = Date.now();
    while (Date.now() - start < timeoutMs) {
        let root = findRoot();
        if (!root) return true;

        let rows = getColorSpecRows(root);
        if (rows.length < previousCount) return true;
        if (oldRow && !oldRow.isConnected) return true;
        if (oldRow && !rows.includes(oldRow)) return true;

        await sleep(60);
    }
    return false;
}

async function clearPrefilledSpecs(options = {}) {
    let deletedCount = 0;
    let start = Date.now();
    let hardTimeoutMs = Number(options.hardTimeoutMs || 45000);
    let settleChecks = Number(options.settleChecks || 6);
    let settleIntervalMs = Number(options.settleIntervalMs || 120);
    let emptyRounds = 0;

    while (Date.now() - start < hardTimeoutMs) {
        let root = findColorSpecContainer();
        if (!root) throw new Error('未找到颜色规格区域(package-container color)');

        let rows = getColorSpecRows(root);
        let targets = rows.filter(isPrefilledSpecRow);

        if (targets.length === 0) {
            emptyRounds += 1;
            if (emptyRounds >= settleChecks) return deletedCount;
            await sleep(settleIntervalMs);
            continue;
        }

        emptyRounds = 0;

        let row = targets[0];
        let deleteBtn = getSpecDeleteButton(row);
        if (!deleteBtn) {
            await sleep(120);
            continue;
        }

        let beforeCount = rows.length;
        dispatchMouseClick(deleteBtn);
        let changed = await waitForSpecRowChanged(findColorSpecContainer, beforeCount, row, 1200);

        if (!changed) {
            await sleep(100);
            dispatchMouseClick(deleteBtn);
            changed = await waitForSpecRowChanged(findColorSpecContainer, beforeCount, row, 1400);
        }

        if (changed) {
            deletedCount += 1;
            continue;
        }

        await sleep(240);
    }

    throw new Error('清理规格超时，请稍后重试');
}

async function runClearPrefilledSpecs() {
    if (isSpecClearRunning) return showToast('⚠️ 规格清理正在执行中');
    if (isSkuGenerateRunning) return showToast('⚠️ 正在执行生成SKU，请稍后');

    isSpecClearRunning = true;
    setInlineButtonState(document.getElementById(SPEC_CLEAR_BTN_ID), true, '清空预填规格');

    try {
        showToast('⚡ 开始删除预填规格');
        let deletedCount = await clearPrefilledSpecs({ settleChecks: 5, settleIntervalMs: 120 });
        showToast(`✅ 预填规格已清空（删除 ${deletedCount} 行）`);
    } catch (e) {
        showToast(`❌ 预填规格清理失败：${e.message}`);
    } finally {
        isSpecClearRunning = false;
        setInlineButtonState(document.getElementById(SPEC_CLEAR_BTN_ID), false, '清空预填规格');
    }
}

function ensureSpecClearButton() {
    let button = document.getElementById(SPEC_CLEAR_BTN_ID);
    if (button) return button;

    button = document.createElement('button');
    button.id = SPEC_CLEAR_BTN_ID;
    button.type = 'button';
    button.innerText = '清空预填规格';
    button.style.cssText = `${getBlueButtonCss()} margin-bottom: 8px;`;
    button.onmousedown = (e) => e.stopPropagation();
    button.onclick = runClearPrefilledSpecs;
    return button;
}

function syncSpecClearButton() {
    let container = findColorSpecContainer();
    let button = ensureSpecClearButton();

    if (!container) {
        button.remove();
        return;
    }

    if (button.parentElement !== container || container.firstElementChild !== button) {
        container.insertAdjacentElement('afterbegin', button);
    }
}

function setSkuGenerateButtonsLocked(locked, activeId = '') {
    const configs = [
        { id: SKU_GENERATE_INLINE_BTN_ID, idle: '生成sku' },
    ];

    configs.forEach(cfg => {
        let button = document.getElementById(cfg.id);
        if (!button) return;
        button.disabled = locked;
        button.innerText = locked && cfg.id === activeId ? '处理中...' : cfg.idle;
        button.style.opacity = locked ? '0.72' : '1';
        button.style.cursor = locked ? 'wait' : 'pointer';
    });
}

async function runGenerateSkuListWithCleanup(skuList, activeButtonId) {
    if (isSkuGenerateRunning) return showToast('⚠️ 生成SKU正在执行中');
    if (isSpecClearRunning) return showToast('⚠️ 正在清空规格，请稍后');

    if (!skuList || skuList.length === 0) {
        return showToast("❌ 没有保存的 SKU 列表，请先点 sku编辑器 添加！");
    }

    isSkuGenerateRunning = true;
    isSpecClearRunning = true;
    setSkuGenerateButtonsLocked(true, activeButtonId);

    try {
        showToast('⚡ 正在清空预填规格...');
        let deletedCount = await clearPrefilledSpecs({ settleChecks: 4, settleIntervalMs: 120 });
        showToast(`✅ 规格清空完成（删除 ${deletedCount} 行），开始生成SKU`);
        await executeSkuFill(skuList);
        try {
            let imageResult = await autoAssignSkuImagesByNames(skuList);
            showToast(imageResult);
        } catch (imageError) {
            showToast(`⚠️ SKU 已生成，但自动选图失败：${imageError.message}`);
        }
    } catch (e) {
        showToast(`❌ 生成SKU失败：${e.message}`);
    } finally {
        isSpecClearRunning = false;
        isSkuGenerateRunning = false;
        setSkuGenerateButtonsLocked(false);
    }
}

async function runGenerateSkuWithCleanup() {
    let savedData = loadSkusFromStorage();
    return runGenerateSkuListWithCleanup(savedData, SKU_GENERATE_INLINE_BTN_ID);
}

function ensureSkuInlineActions() {
    let wrap = document.getElementById(SKU_ACTIONS_WRAP_ID);
    if (!wrap) {
        wrap = document.createElement('div');
        wrap.id = SKU_ACTIONS_WRAP_ID;
        wrap.style.cssText = 'display:inline-flex; align-items:center; gap:8px; margin-left:8px;';
    }

    let editorBtn = document.getElementById(SKU_EDITOR_INLINE_BTN_ID);
    if (!editorBtn) {
        editorBtn = document.createElement('button');
        editorBtn.id = SKU_EDITOR_INLINE_BTN_ID;
        editorBtn.type = 'button';
        editorBtn.innerText = 'sku编辑器';
        editorBtn.style.cssText = getBlueButtonCss({ height: 30, minWidth: 96 });
        editorBtn.onmousedown = (e) => e.stopPropagation();
        editorBtn.onclick = showSkuEditor;
    }

    let genBtn = document.getElementById(SKU_GENERATE_INLINE_BTN_ID);
    if (!genBtn) {
        genBtn = document.createElement('button');
        genBtn.id = SKU_GENERATE_INLINE_BTN_ID;
        genBtn.type = 'button';
        genBtn.innerText = '生成sku';
        genBtn.style.cssText = getBlueButtonCss({ height: 30, minWidth: 96 });
        genBtn.onmousedown = (e) => e.stopPropagation();
        genBtn.onclick = runGenerateSkuWithCleanup;
    }

    if (!wrap.contains(editorBtn)) wrap.appendChild(editorBtn);
    if (!wrap.contains(genBtn)) wrap.appendChild(genBtn);
    return wrap;
}

function findColorCategoryNameTipsAnchor() {
    let tipsNodes = Array.from(document.querySelectorAll('div[data-testid="beast-core-grid-col-wrapper"].name-tips, div.name-tips'));
    for (let node of tipsNodes) {
        if (!isVisibleElement(node)) continue;
        let text = cleanImageLabel(node.textContent);
        if (!text.includes('选择标准颜色可增加搜索/导购机会')) continue;
        return node;
    }
    return null;
}

function syncSkuInlineActions() {
    let anchor = findColorCategoryNameTipsAnchor();
    if (!anchor) {
        document.getElementById(SKU_ACTIONS_WRAP_ID)?.remove();
        return;
    }
    let wrap = ensureSkuInlineActions();

    if (wrap.parentElement !== anchor.parentElement || wrap.previousElementSibling !== anchor) {
        anchor.insertAdjacentElement('afterend', wrap);
    }

    let row = anchor.closest('div[data-testid="beast-core-grid-row"]');
    if (row) {
        row.style.alignItems = 'center';
    }
    anchor.style.display = 'flex';
    anchor.style.alignItems = 'center';
    anchor.style.lineHeight = '1.4';
    wrap.style.alignSelf = 'center';
}

function readInlineBasePrice() {
    let input = document.getElementById(PRICE_INLINE_INPUT_ID);
    if (!input) {
        showToast('❌ 未找到价格输入框');
        return null;
    }

    let basePrice = parseFloat(input.value);
    if (isNaN(basePrice)) {
        showToast('❌ 请输入有效价格');
        return null;
    }

    return { input, basePrice };
}

function setPriceActionButtonsLocked(locked, activeId = '') {
    const configs = [
        { id: PRICE_INLINE_FILL_BTN_ID, idle: '计算并批量填充价格' },
    ];

    configs.forEach(cfg => {
        let button = document.getElementById(cfg.id);
        if (!button) return;
        button.disabled = locked;
        button.innerText = locked && cfg.id === activeId ? '处理中...' : cfg.idle;
        button.style.opacity = locked ? '0.72' : '1';
        button.style.cursor = locked ? 'wait' : 'pointer';
    });
}

function executeInlinePriceBatchFill(priceState, settings) {
    const { basePrice } = priceState;
    isPriceBatchRunning = true;
    setPriceActionButtonsLocked(true, PRICE_INLINE_FILL_BTN_ID);

    try {
        localStorage.setItem(PRICE_STORAGE_KEY, String(basePrice));
        const plan = settings.configured ? calculatePricePlan(basePrice, settings) : { targetActualPrice: basePrice, listingPrice: basePrice };
        if (settings.configured && settings.mode === 'margin') {
            priceState.input.value = plan.targetActualPrice.toFixed(2);
            localStorage.setItem(PRICE_STORAGE_KEY, priceState.input.value);
        }
        const finalPrice = Number(plan.listingPrice).toFixed(2);
        showToast(`✅ 目标实售价 ${Number(plan.targetActualPrice).toFixed(2)} 元，上架填充价 ${finalPrice} 元`);
        runBatchFillPrice(finalPrice);
    } finally {
        setTimeout(() => {
            isPriceBatchRunning = false;
            setPriceActionButtonsLocked(false);
        }, 800);
    }
}

function showUnconfiguredPricePrompt(priceState) {
    const { overlay, close } = openPublishDialog(
        'sxz-pdd-price-first-use-dialog',
        '还没有设置价格逻辑',
        '可以先配置折扣、优惠券或成本毛利，也可以本次直接使用输入价格。',
        `<div class="sxz-pdd-note" style="margin-top:0">当前输入的目标实售价为 <b>${Number(priceState.basePrice).toFixed(2)} 元</b>。直接使用时，插件会把该价格原样填入上架价格，不做公式换算。</div>`
    );
    const directButton = overlay.querySelector('[data-role="cancel"]');
    const setupButton = overlay.querySelector('[data-role="save"]');
    directButton.textContent = '直接使用输入价';
    setupButton.textContent = '去设置价格逻辑';
    directButton.onclick = () => {
        close();
        executeInlinePriceBatchFill(priceState, loadPriceLogic());
    };
    setupButton.onclick = () => {
        close();
        showPriceLogicDialog();
    };
}

function runInlinePriceBatchFill() {
    if (isPriceBatchRunning) return showToast('⚠️ 价格填充正在执行中');
    const priceState = readInlineBasePrice();
    if (!priceState) return;
    const settings = loadPriceLogic();
    if (!settings.configured) {
        showUnconfiguredPricePrompt(priceState);
        return;
    }
    executeInlinePriceBatchFill(priceState, settings);
}

function ensurePriceInlineActions() {
    ensurePublishDialogStyles();
    let wrap = document.getElementById(PRICE_ACTIONS_WRAP_ID);
    if (!wrap) {
        wrap = document.createElement('div');
        wrap.id = PRICE_ACTIONS_WRAP_ID;
        wrap.style.cssText = 'display:inline-flex; align-items:center; gap:8px; margin-left:10px;';
    }

    let input = document.getElementById(PRICE_INLINE_INPUT_ID);
    if (!input) {
        input = document.createElement('input');
        input.id = PRICE_INLINE_INPUT_ID;
        input.type = 'number';
        input.placeholder = '目标实售价';
        input.title = '填写最终希望用户实际支付的价格。可在“价格设置逻辑”中配置活动折扣、优惠券，或按成本与期望毛利率反推。';
        input.style.cssText = `
            width: 118px; height: 36px; padding: 0 10px; border-radius: 8px;
            border: 2px solid ${PRIMARY_BTN_COLOR}; font-size: 14px; font-weight: 600;
            box-sizing: border-box; background: #f5f8ff; color: #1e429f;
            box-shadow: 0 0 0 2px rgba(64, 125, 255, 0.18);
        `;
        let savedPrice = getMigratedStorageValue(PRICE_STORAGE_KEY, LEGACY_STORAGE_KEYS.price);
        if (savedPrice) input.value = savedPrice;
        input.onmousedown = (e) => e.stopPropagation();
        input.onchange = () => {
            if (input.value) localStorage.setItem(PRICE_STORAGE_KEY, input.value);
        };
    }

    let button = document.getElementById(PRICE_INLINE_FILL_BTN_ID);
    if (!button) {
        button = document.createElement('button');
        button.id = PRICE_INLINE_FILL_BTN_ID;
        button.type = 'button';
        button.innerText = '计算并批量填充价格';
        button.className = 'sxz-pdd-price-fill-primary';
        button.onmousedown = (e) => e.stopPropagation();
        button.onclick = runInlinePriceBatchFill;
    }

    let logicButton = document.getElementById(PRICE_LOGIC_BTN_ID);
    if (!logicButton) {
        logicButton = document.createElement('button');
        logicButton.id = PRICE_LOGIC_BTN_ID;
        logicButton.type = 'button';
        logicButton.innerText = '价格设置逻辑';
        logicButton.style.cssText = getBlueButtonCss({ height: 34, minWidth: 118, fontSize: 13 });
        logicButton.onmousedown = (e) => e.stopPropagation();
        logicButton.onclick = showPriceLogicDialog;
    }

    if (!wrap.contains(input)) wrap.appendChild(input);
    if (!wrap.contains(logicButton)) wrap.appendChild(logicButton);
    if (!wrap.contains(button)) wrap.appendChild(button);
    return wrap;
}

function findSizeSelectTipAnchor() {
    return Array.from(document.querySelectorAll('div.sizeStandardWrapper_selectTip__1hBM6, div[class*="sizeStandardWrapper_selectTip"]'))
        .find(isVisibleElement) || null;
}

function syncPriceInlineActions() {
    let anchor = findSizeSelectTipAnchor();
    if (!anchor) {
        document.getElementById(PRICE_ACTIONS_WRAP_ID)?.remove();
        return;
    }
    let wrap = ensurePriceInlineActions();

    if (wrap.parentElement !== anchor.parentElement || wrap.previousElementSibling !== anchor) {
        anchor.insertAdjacentElement('afterend', wrap);
    }

    let holder = anchor.parentElement;
    if (holder) {
        holder.style.display = 'flex';
        holder.style.alignItems = 'center';
        holder.style.flexWrap = 'wrap';
        holder.style.gap = '8px';
    }
}

function syncImageSpaceButtons() {
    if (isGoodsAddPage()) {
        document.getElementById(SPEC_CLEAR_BTN_ID)?.remove();
        syncCarouselKeepFirstButton();
        syncDetailKeepFirstButton();
        syncImageSelectButtons();
        syncSkuInlineActions();
        syncPriceInlineActions();
        document.getElementById(CODE_TEMPLATE_ACTIONS_ID)?.remove();
        return;
    }
    if (isGoodsListPage()) {
        syncGoodsCodeTemplateActions();
    }
}

// ==========================================
// 辅助工具与底层引擎
// ==========================================
function simulateInput(input, value) {
    if (!input) return;
    input.scrollIntoView({ block: "center" });
    input.focus();
    input.click(); 
    
    let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
    if (nativeInputValueSetter) {
        nativeInputValueSetter.call(input, value);
    } else {
        input.value = value;
    }
    
    input.dispatchEvent(new Event('input', { bubbles: true }));
    input.dispatchEvent(new Event('change', { bubbles: true })); 
    input.dispatchEvent(new Event('blur', { bubbles: true }));
}

function showToast(msg) {
    let old = document.getElementById('pdd-toast');
    if (old) old.remove();

    let div = document.createElement('div');
    div.id = 'pdd-toast';
    div.style.cssText = "position:fixed; top:40px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.85); color:#fff; padding:12px 24px; border-radius:6px; z-index:100000; font-size:15px; font-weight:bold; text-align:center; box-shadow: 0 4px 12px rgba(0,0,0,0.3); white-space: pre-line;";
    div.innerText = msg;
    document.body.appendChild(div);
    setTimeout(() => div.remove(), 2500);
}

// ==========================================
// 拖拽控制引擎 (防冲突版)
// ==========================================
function makeDraggable(el) {
    let isDragging = false;
    let diffX = 0; 
    let diffY = 0; 
    let startX = 0; 
    let startY = 0; 

    el.addEventListener('mousedown', function(e) {
        if (e.target.closest('input, textarea, button, .sort-row, .sku-sort-list, .close-btn, .drag-handle')) return;

        isDragging = true;
        el.dataset.isDragged = 'false'; 
        
        const rect = el.getBoundingClientRect();
        diffX = e.clientX - rect.left;
        diffY = e.clientY - rect.top;
        startX = e.clientX;
        startY = e.clientY;

        el.style.bottom = 'auto';
        el.style.right = 'auto';
        
        if(el.style.transform && el.style.transform.includes('translate')) {
            el.style.transform = 'none';
        }

        el.style.left = rect.left + 'px';
        el.style.top = rect.top + 'px';
        el.style.cursor = 'grabbing';
    });

    window.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        e.preventDefault();

        if (Math.abs(e.clientX - startX) > 3 || Math.abs(e.clientY - startY) > 3) {
            el.dataset.isDragged = 'true';
        }

        el.style.left = (e.clientX - diffX) + 'px';
        el.style.top = (e.clientY - diffY) + 'px';
    });

    window.addEventListener('mouseup', () => {
        if (isDragging) {
            isDragging = false;
            el.style.cursor = 'grab';
        }
    });
}

})();
