AMEX Code Helper (通知设置保存优化)

自动提交AMEX offer code和WOC code,防止会话超时,支持批量测试,自动识别链接有效性,并记录结果 (已优化同步逻辑和通知设置保存)

// ==UserScript==
// @name         AMEX Code Helper (通知设置保存优化)
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  自动提交AMEX offer code和WOC code,防止会话超时,支持批量测试,自动识别链接有效性,并记录结果 (已优化同步逻辑和通知设置保存)
// @author       Your Name & Gemini
// @match        https://www.americanexpress.com/*
// @match        https://global.americanexpress.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 调试模式开关 (可通过设置面板控制)
    let AMEX_DEBUG = GM_getValue('AMEX_DEBUG', false);

    // --- 样式设置 ---
    GM_addStyle(`
        /* --- 基本样式 --- */
        .amex-helper {
            position: fixed;
            top: 10px;
            right: 10px;
            background: #fff;
            border: 2px solid #006fcf;
            border-radius: 5px;
            padding: 10px;
            z-index: 9999;
            width: 350px;
            box-shadow: 0 0 10px rgba(0,0,0,0.3);
            max-height: 90vh;
            overflow-y: auto;
            font-family: sans-serif; /* 使用更通用的字体 */
        }
        .amex-helper input, .amex-helper textarea {
            width: 100%;
            padding: 8px; /* 增加内边距 */
            margin: 5px 0;
            border: 1px solid #ccc;
            border-radius: 4px; /* 添加圆角 */
            box-sizing: border-box; /* 避免宽度超出 */
        }
        .amex-helper textarea {
            height: 80px;
            font-family: monospace;
        }
        .amex-helper button {
            background: #006fcf;
            color: white;
            border: none;
            padding: 8px 12px; /* 调整按钮大小 */
            margin: 5px 2px;
            border-radius: 4px; /* 统一圆角 */
            cursor: pointer;
            transition: background-color 0.2s; /* 添加悬停效果 */
        }
        .amex-helper button:hover {
            background: #004f93;
        }
        .amex-helper h3 {
            margin: 5px 0 10px 0; /* 调整标题边距 */
            color: #006fcf;
            font-size: 1.1em; /* 稍微增大标题 */
        }
        .amex-helper .status {
            font-size: 0.9em; /* 调整状态文字大小 */
            color: #555; /* 调整颜色 */
            margin-top: 10px;
            padding: 5px;
            background-color: #f0f0f0; /* 添加背景色 */
            border-radius: 3px;
        }
        .amex-helper .section {
            border-top: 1px solid #eee;
            margin-top: 10px;
            padding-top: 10px;
        }

        /* --- Tabs --- */
        .amex-helper .tabs {
            display: flex;
            border-bottom: 1px solid #ddd;
            margin-bottom: 10px;
        }
        .amex-helper .tab {
            padding: 8px 12px; /* 调整Tab大小 */
            cursor: pointer;
            border: 1px solid transparent;
            border-bottom: none; /* 初始无下边框 */
            margin-bottom: -1px; /* 与下边框重叠 */
            color: #006fcf; /* Tab文字颜色 */
        }
        .amex-helper .tab:hover {
            background-color: #f0f8ff; /* 悬停背景色 */
        }
        .amex-helper .tab.active {
            border: 1px solid #ddd;
            border-bottom-color: white;
            border-radius: 4px 4px 0 0; /* 圆角 */
            background: white; /* 激活背景色 */
            font-weight: bold; /* 激活加粗 */
            color: #333; /* 激活文字颜色 */
        }
        .amex-helper .tab-content {
            display: none;
        }
        .amex-helper .tab-content.active {
            display: block;
        }

        /* --- 结果表格 --- */
        .amex-helper .attempts {
            max-height: 300px; /* 增加最大高度 */
            overflow-y: auto;
            margin-top: 10px;
            border: 1px solid #ddd; /* 添加边框 */
            border-radius: 4px;
        }
        .amex-helper .results-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 12px;
        }
        .amex-helper .results-table th, .amex-helper .results-table td {
            border: 1px solid #eee; /* 调整表格线颜色 */
            padding: 6px 8px; /* 调整单元格内边距 */
            text-align: left;
            vertical-align: middle; /* 垂直居中 */
        }
        .amex-helper .results-table th {
            background: #f8f8f8; /* 表头背景色 */
            font-weight: bold; /* 表头加粗 */
            position: sticky; /* 表头吸顶 */
            top: 0;
            z-index: 1;
        }
        .amex-helper .results-table tbody tr:nth-child(even) {
            background-color: #f9f9f9; /* 斑马纹 */
        }
        .amex-helper .results-table tbody tr:hover {
            background-color: #f0f8ff; /* 行悬停效果 */
        }
        .amex-helper .success { color: #28a745; font-weight: bold; }
        .amex-helper .fail { color: #dc3545; font-weight: bold; }
        .amex-helper .pending { color: #ffc107; font-weight: bold; }
        .amex-helper .verified { background-color: #e6ffe6; }
        .amex-helper .rejected { background-color: #ffe6e6; }

        /* --- 模态窗口 --- */
        .amex-helper-overlay { /* 添加遮罩层 */
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.5);
            z-index: 9998;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .amex-helper .modal {
            position: relative; /* 改为相对定位,由遮罩层控制居中 */
            transform: none;
            top: auto; left: auto;
            z-index: 10000;
            background-color: white; /* 背景移到这里 */
            border-radius: 8px; /* 增加圆角 */
            padding: 20px; /* 增加内边距 */
            width: auto; /* 宽度自适应 */
            min-width: 300px;
            max-width: 90vw; /* 最大宽度 */
            box-shadow: 0 5px 15px rgba(0,0,0,0.3); /* 调整阴影 */
            max-height: 80vh; /* 最大高度 */
            overflow-y: auto; /* 内容过多时滚动 */
        }
        .amex-helper .modal-content {
            /* 移除背景色等,已移到 .modal */
            padding: 0; /* 移除内边距 */
            width: 100%;
            box-shadow: none; /* 移除阴影 */
        }
        .amex-helper .modal-title {
            margin-top: 0;
            margin-bottom: 15px; /* 增加下边距 */
            color: #006fcf;
            font-size: 1.2em; /* 增大标题 */
            border-bottom: 1px solid #eee; /* 添加下划线 */
            padding-bottom: 10px;
        }
        .amex-helper .modal button {
            margin: 5px; /* 调整按钮间距 */
        }

        /* --- 设置 --- */
        .amex-helper .settings-row {
            margin: 15px 0; /* 增加行间距 */
        }
        .amex-helper .settings-label {
            display: block;
            margin-bottom: 5px; /* 调整标签下边距 */
            font-weight: bold;
            color: #333;
        }
        .amex-helper .settings-help {
            font-size: 0.85em;
            color: #666;
            margin-top: 5px;
        }

        /* --- 统计 & 批量信息 --- */
        .amex-helper .stats, .amex-helper .batch-info, .amex-helper .combo-info {
            font-size: 12px;
            margin: 8px 0;
            padding: 8px;
            background: #f5f5f5;
            border: 1px solid #e0e0e0; /* 添加边框 */
            border-radius: 4px;
        }
        .amex-helper .batch-info { color: #666; }
        .amex-helper .combo-info { background: #e9f5ff; border-color: #cce7ff; } /* 调整颜色 */

        /* --- 按钮类型 --- */
        .amex-helper .btn-test { background-color: #006fcf; }
        .amex-helper .btn-control { background-color: #28a745; }
        .amex-helper .btn-export { background-color: #6f42c1; }
        .amex-helper .btn-danger { background-color: #dc3545; }
        .amex-helper .btn-warning { background-color: #ffc107; color: black; } /* 添加警告按钮 */
        .amex-helper .verify-btn {
            padding: 3px 6px; /* 调整验证按钮大小 */
            font-size: 11px;
            background-color: #17a2b8; /* 调整颜色 */
            margin-left: 5px;
        }
        .amex-helper .verify-btn:hover { background-color: #138496; }
        .amex-helper .close-btn {
            position: absolute;
            top: 8px;
            right: 10px;
            background: none;
            border: none;
            font-size: 24px; /* 增大关闭按钮 */
            color: #aaa; /* 调整颜色 */
            cursor: pointer;
            padding: 0;
            margin: 0;
            line-height: 1;
        }
        .amex-helper .close-btn:hover { color: #333; }

        /* --- 悬浮按钮 --- */
        .amex-float-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: #006fcf;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 20px; /* 调整字体大小 */
            font-weight: bold;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3); /* 调整阴影 */
            cursor: pointer;
            z-index: 10000;
            border: none;
            transition: background-color 0.2s, transform 0.2s; /* 添加动画 */
        }
        .amex-float-btn:hover {
            background: #004f93;
            transform: scale(1.1); /* 悬停放大 */
        }

        /* --- 移动端适配 --- */
        @media (max-width: 768px) {
            .amex-helper {
                width: 95%; /* 调整宽度 */
                max-width: none; /* 移除最大宽度 */
                left: 2.5%;
                right: 2.5%;
                top: 5px; /* 调整位置 */
                bottom: 5px; /* 允许占满高度 */
                max-height: calc(100vh - 10px); /* 调整最大高度 */
            }
            .amex-helper button {
                padding: 10px 12px; /* 增大移动端按钮 */
            }
            .amex-helper .tab {
                padding: 10px 8px; /* 调整Tab大小 */
                font-size: 0.9em; /* 缩小Tab字体 */
            }
            .amex-helper .results-table th, .amex-helper .results-table td {
                padding: 5px; /* 缩小单元格内边距 */
                font-size: 11px; /* 缩小表格字体 */
            }
            .amex-helper .modal {
                width: 90%; /* 模态框宽度 */
            }
            .amex-float-btn {
                 bottom: 15px;
                 right: 15px;
                 width: 45px;
                 height: 45px;
                 font-size: 18px;
            }
        }

        /* --- 页面结果通知 --- */
        .amex-page-notification {
            position: fixed;
            top: 10px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 10005; /* 比助手面板高 */
            padding: 12px 25px; /* 调整内边距 */
            border-radius: 6px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.25);
            font-size: 14px;
            font-weight: bold;
            color: white;
            text-align: center;
            max-width: 90%;
            opacity: 0; /* 初始透明 */
            transition: opacity 0.5s, transform 0.5s; /* 添加动画 */
            pointer-events: none; /* 不阻挡下方点击 */
        }
        .amex-page-notification.show {
            opacity: 1;
            transform: translate(-50%, 10px); /* 向下移动一点 */
        }
        .amex-page-notification.success { background-color: #28a745; }
        .amex-page-notification.error { background-color: #dc3545; }
        .amex-page-notification.warning { background-color: #ffc107; color: black; }

        /* --- iOS 优化链接按钮 --- */
        .ios-link-button {
            display: block;
            background-color: #007bff; /* 调整颜色 */
            color: white;
            font-weight: bold;
            text-align: center;
            padding: 15px;
            margin: 15px 0;
            border-radius: 10px;
            text-decoration: none;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            transition: background-color 0.2s;
        }
        .ios-link-button:hover { background-color: #0056b3; }

        /* --- 跨标签页通知 (占位符,方案2使用) --- */
        .amex-notification-container { /* ... */ }
        .amex-notification { /* ... */ }
        @keyframes slideIn { /* ... */ }

        /* --- 调试信息表格 --- */
        .debug-storage-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
            font-size: 11px;
        }
        .debug-storage-table th, .debug-storage-table td {
            border: 1px solid #ddd;
            padding: 4px;
            text-align: left;
        }
        .debug-storage-table th { background-color: #f0f0f0; }
    `);

    // --- 全局变量和状态 ---
    let attempts = GM_getValue('attempts', []);
    const lastOfferCode = GM_getValue('lastOfferCode', '');
    const lastWOCCode = GM_getValue('lastWOCCode', '');
    const lastBatchCodes = GM_getValue('lastBatchCodes', '');
    const serverUrl = GM_getValue('serverUrl', '');
    const scriptDomains = GM_getValue('scriptDomains', 'americanexpress.com');
    let stats = GM_getValue('stats', { totalTested: 0, verified: 0, rejected: 0 });
    let refreshTimerId = null;
    let batchTesting = false;
    let batchQueue = [];
    let batchResults = [];
    let offerCodes = [];
    let wocCodes = [];
    let skipExisting = GM_getValue('skipExisting', true);
    let isPanelVisible = true;
    // 通知设置对象
    let notificationSettings = GM_getValue('notificationSettings', {
        enableSound: true,
        enableMobilePopup: true,
        enableDesktop: true,
        enableEmail: false
    });


    // --- 核心功能函数 ---

    // 清理URL中的特殊字符
    function cleanUrl(url) {
        if (!url) return '';
        return url.replace(/[\u200B-\u200D\uFEFF\u00A0]/g, '')
                  .replace(/%E2%80%8[A-F0-9]/g, '')
                  .replace(/[^\x20-\x7E]/g, '').trim();
    }

    // 更新链接结果 (自动检测时调用)
    function autoUpdateLinkResult(wocCode, linkType, isValid, reason, fullUrl) {
        if (AMEX_DEBUG) console.log(`[DEBUG] autoUpdateLinkResult called: woc=${wocCode}, type=${linkType}, valid=${isValid}, url=${fullUrl}`);

        let updated = false;
        // 优先精确匹配未完成的记录
        let matchingAttempt = attempts.find(a =>
            !a.verified && !a.rejected &&
            a.wocCode === wocCode &&
            (a.testedType === linkType || !a.testedType || a.testedType === '未知')
        );

        // 如果精确匹配不到,尝试更宽松的匹配 (woc码包含,链接类型包含)
        if (!matchingAttempt && wocCode) {
            matchingAttempt = attempts.find(a =>
                !a.verified && !a.rejected &&
                a.wocCode && (a.wocCode.includes(wocCode) || wocCode.includes(a.wocCode)) &&
                (a.testedType === linkType || !a.testedType || a.testedType === '未知' || (linkType && a.testedType && linkType.includes(a.testedType)))
            );
            if (AMEX_DEBUG && matchingAttempt) console.log(`[DEBUG] Found loose match (WOC includes):`, matchingAttempt);
        }

        // 如果还是没有,尝试通过URL匹配 (如果URL存在)
        if (!matchingAttempt && fullUrl) {
             matchingAttempt = attempts.find(a =>
                !a.verified && !a.rejected &&
                a.testedUrl && cleanUrl(a.testedUrl) === cleanUrl(fullUrl)
            );
             if (AMEX_DEBUG && matchingAttempt) console.log(`[DEBUG] Found URL match:`, matchingAttempt);
        }

        // 如果还是没有,尝试匹配最近一个未完成且类型匹配的记录
        if (!matchingAttempt) {
             matchingAttempt = attempts.find(a =>
                !a.verified && !a.rejected &&
                (a.testedType === linkType || !a.testedType || a.testedType === '未知')
            );
             if (AMEX_DEBUG && matchingAttempt) console.log(`[DEBUG] Found latest pending match by type:`, matchingAttempt);
        }


        if (matchingAttempt) {
            if (AMEX_DEBUG) console.log(`[DEBUG] Updating attempt:`, matchingAttempt);
            // 避免重复更新统计数据
            const needsStatUpdate = !matchingAttempt.verified && !matchingAttempt.rejected;

            matchingAttempt.status = isValid ? 'success' : 'error';
            matchingAttempt.verified = isValid;
            matchingAttempt.rejected = !isValid;
            // 只有当本地记录没有类型时才更新类型,防止覆盖手动测试的类型
            if (!matchingAttempt.testedType || matchingAttempt.testedType === '未知') {
                 matchingAttempt.testedType = linkType;
            }
            matchingAttempt.verificationReason = reason;
            matchingAttempt.verifiedAt = Date.now();
            matchingAttempt.testedUrl = fullUrl; // 记录测试过的URL

            if (needsStatUpdate) {
                stats.totalTested++;
                if (isValid) {
                    stats.verified++;
                } else {
                    stats.rejected++;
                }
            }

            GM_setValue('attempts', attempts);
            GM_setValue('stats', stats);
            updateAttemptsList();
            updateStats();
            updateStatus(`自动检测到 ${wocCode || '未知WOC'} 的 ${linkType} 结果: ${isValid ? '有效' : '无效'}`);
            updated = true;
        } else {
             if (AMEX_DEBUG) console.log(`[DEBUG] No matching attempt found for woc=${wocCode}, type=${linkType}`);
        }

        return updated;
    }

    // 检测当前页面结果
    function checkCurrentPageForResults() {
        const currentUrl = cleanUrl(window.location.href);
        if (AMEX_DEBUG) console.log('[DEBUG] Checking current page:', currentUrl);

        // 简单判断是否可能是结果页 (包含 apply 或 card-application)
        if (!currentUrl.includes('apply') && !currentUrl.includes('card-application')) {
             if (AMEX_DEBUG) console.log('[DEBUG] Not an application page, skipping check.');
            return;
        }

        // **修改点:增加延迟时间**
        setTimeout(() => {
            if (AMEX_DEBUG) console.log('[DEBUG] Running page check after delay.');
            const pageContent = document.body.innerText || '';
            // const pageHtml = document.body.innerHTML || ''; // 通常innerText足够
            let pageType = 'unknown';
            let isValid = false;
            let reason = '';

            // --- 结果判断逻辑 (与原版一致) ---
            if (pageContent.includes('Upgrade Now') || pageContent.includes('upgrade now') || pageContent.includes('upgrade your Card')) {
                pageType = '升级链接';
                isValid = true;
                reason = '找到升级选项';
            } else if (pageContent.includes('You may add up to 99 total Employee Cards') || pageContent.includes('Employee Cards') || pageContent.match(/add.*Employee Cards/i) || pageContent.includes('Additional Card')) {
                pageType = '副卡链接';
                isValid = true;
                reason = '找到副卡选项';
            } else if (pageContent.includes('temporarily down') || pageContent.includes('service is temporarily unavailable')) {
                pageType = 'IP被限制/服务不可用';
                isValid = false;
                reason = '页面提示服务暂时不可用或IP受限';
            } else if (currentUrl.includes('/error') || pageContent.includes('Page Not Found') || pageContent.includes('Error') || pageContent.includes('unable to process your request')) {
                pageType = '错误页面';
                isValid = false;
                reason = '页面显示错误或无法处理请求';
            } else {
                pageType = '无效链接';
                isValid = false;
                reason = '未找到明确的有效或错误内容';
            }
            // --- 结束结果判断 ---

            if (AMEX_DEBUG) console.log(`[DEBUG] Page check result: type=${pageType}, valid=${isValid}, reason=${reason}`);

            // 显示页面顶部通知
            showPageResultNotification(pageType, isValid, reason);

            // 提取WOC码
            const wocMatches = currentUrl.match(/(?:[-\/])([A-Z][A-Z0-9]{4,20})(?:\?|$|\s|\/|&|#)/i) ||
                               currentUrl.match(/WOC[A-Z0-9]+/i) ||
                               // 尝试从页面内容提取 (如果URL没有)
                               pageContent.match(/WOC=([A-Z0-9]+)/i) ||
                               currentUrl.match(/[A-Z0-9]{5,15}/i); // 最后的通用匹配

            let detectedWocCode = '';
            if (wocMatches && wocMatches[1]) {
                detectedWocCode = wocMatches[1];
            } else if (wocMatches && wocMatches[0]) {
                 // 检查是否是完整匹配,避免提取到非WOC码
                 if(/^[A-Z][A-Z0-9]{4,}$/i.test(wocMatches[0]) || wocMatches[0].startsWith('WOC')) {
                    detectedWocCode = wocMatches[0];
                 }
            }
            if (AMEX_DEBUG) console.log(`[DEBUG] Detected WOC: ${detectedWocCode}`);


            // 判断链接类型
            let linkType = '未知链接';
            if (currentUrl.includes('upgrade')) {
                linkType = '升级链接';
            } else if (currentUrl.includes('supps') || currentUrl.includes('supplementary')) {
                linkType = '副卡链接';
            } else if (isValid) { // 如果页面有效但URL没特征,根据内容判断
                if (pageType === '升级链接') linkType = '升级链接';
                if (pageType === '副卡链接') linkType = '副卡链接';
            }
            if (AMEX_DEBUG) console.log(`[DEBUG] Detected Link Type: ${linkType}`);

            // 尝试更新本地记录
            const updatedLocally = autoUpdateLinkResult(detectedWocCode, linkType, isValid, reason, currentUrl);

            // 存储到localStorage供其他标签页同步
            storeTestResult(detectedWocCode, linkType, isValid, reason, currentUrl);

            // 如果找到有效链接,尝试发送邮件
            if (isValid) {
                tryToSendEmailNotification(detectedWocCode, linkType, reason, currentUrl);
            }

        }, 3000); // **修改点:增加到3秒延迟**
    }

    // 显示页面顶部通知
    function showPageResultNotification(pageType, isValid, reason) {
        // 移除旧通知
        const oldNotification = document.getElementById('amex-page-notification');
        if (oldNotification) {
            oldNotification.remove();
        }

        const notification = document.createElement('div');
        notification.id = 'amex-page-notification';
        notification.className = 'amex-page-notification';

        let notificationClass = isValid ? 'success' : (pageType.includes('IP') ? 'warning' : 'error');
        notification.classList.add(notificationClass);

        const icon = isValid ? '✅' : (notificationClass === 'warning' ? '⚠️' : '❌');
        notification.innerHTML = `${icon} ${pageType}: ${reason}`;

        document.body.appendChild(notification);

        // 触发显示动画
        setTimeout(() => notification.classList.add('show'), 50);

        // 触发综合通知 (声音、桌面等)
        const notificationTitle = isValid ? `发现有效的AMEX ${pageType}` :
                                (notificationClass === 'warning' ? 'AMEX 警告' : 'AMEX链接无效');
        triggerNotifications(notificationTitle, `${pageType}: ${reason}`, isValid);

        // 自动消失
        setTimeout(() => {
            if(notification.parentNode) {
                 notification.classList.remove('show');
                 // 等待动画完成再移除
                 setTimeout(() => {
                     if(notification.parentNode) notification.remove();
                 }, 500);
            }
        }, 6000); // 显示6秒
    }

    // 存储测试结果到localStorage
    function storeTestResult(wocCode, linkType, isValid, reason, url) {
        try {
            let testResults = JSON.parse(localStorage.getItem('amex_test_results') || '[]');
            const newResult = {
                wocCode: wocCode || '', // 确保有值
                linkType: linkType || '未知链接',
                isValid,
                reason,
                timestamp: Date.now(),
                url: cleanUrl(url) || ''
            };

            // 检查是否已存在几乎相同的结果 (防止短时间重复添加)
            const exists = testResults.some(r =>
                r.wocCode === newResult.wocCode &&
                r.linkType === newResult.linkType &&
                r.isValid === newResult.isValid &&
                Math.abs(r.timestamp - newResult.timestamp) < 2000 // 2秒内相同结果
            );

            if (!exists) {
                testResults.push(newResult);
                // 最多保存50条结果
                if (testResults.length > 50) {
                    testResults = testResults.slice(-50);
                }
                localStorage.setItem('amex_test_results', JSON.stringify(testResults));
                 if (AMEX_DEBUG) console.log('[DEBUG] Stored result to localStorage:', newResult);
            } else {
                 if (AMEX_DEBUG) console.log('[DEBUG] Duplicate result detected, not storing again:', newResult);
            }
        } catch (e) {
            console.error('存储测试结果到localStorage失败', e);
            updateStatus('错误: 存储localStorage失败');
        }
    }

    // 从其他窗口同步测试结果
    function syncTestResultsFromOtherWindows() {
        try {
            const testResults = JSON.parse(localStorage.getItem('amex_test_results') || '[]');
            if (testResults.length === 0) return;

            if (AMEX_DEBUG) console.log(`[DEBUG] Syncing from localStorage, found ${testResults.length} results.`);

            let updatedCount = 0;
            let processedTimestamps = new Set(); // 跟踪已处理的时间戳,避免重复处理

            // 反向遍历,优先处理最新的结果
            for (let i = testResults.length - 1; i >= 0; i--) {
                const result = testResults[i];

                // 检查是否已处理过这个结果
                const resultKey = `${result.timestamp}-${result.wocCode}-${result.linkType}`;
                if (processedTimestamps.has(resultKey)) {
                    continue; // 跳过已处理
                }

                if (AMEX_DEBUG) console.log('[DEBUG] Processing result from storage:', result);

                // **修改点:改进匹配逻辑**
                // 1. 尝试精确匹配 (WOC + Type) 未完成的
                let matchingAttempt = attempts.find(a =>
                    !a.verified && !a.rejected &&
                    a.wocCode === result.wocCode &&
                    (a.testedType === result.linkType || !a.testedType || a.testedType === '未知链接')
                );

                // 2. 如果没有,尝试宽松匹配 (WOC包含 + Type包含) 未完成的
                if (!matchingAttempt && result.wocCode) {
                    matchingAttempt = attempts.find(a =>
                        !a.verified && !a.rejected &&
                        a.wocCode && (a.wocCode.includes(result.wocCode) || result.wocCode.includes(a.wocCode)) &&
                        (a.testedType === result.linkType || !a.testedType || a.testedType === '未知链接' || (result.linkType && a.testedType && result.linkType.includes(a.testedType)))
                    );
                     if (AMEX_DEBUG && matchingAttempt) console.log(`[DEBUG] Sync: Found loose match (WOC includes):`, matchingAttempt);
                }

                 // 3. 如果没有,尝试URL匹配 未完成的
                if (!matchingAttempt && result.url) {
                     matchingAttempt = attempts.find(a =>
                        !a.verified && !a.rejected &&
                        a.testedUrl && cleanUrl(a.testedUrl) === cleanUrl(result.url)
                    );
                     if (AMEX_DEBUG && matchingAttempt) console.log(`[DEBUG] Sync: Found URL match:`, matchingAttempt);
                }

                // 4. 如果还没有,尝试匹配最近一个未完成且类型匹配的
                if (!matchingAttempt) {
                     matchingAttempt = attempts.find(a =>
                        !a.verified && !a.rejected &&
                        (a.testedType === result.linkType || !a.testedType || a.testedType === '未知链接')
                    );
                     if (AMEX_DEBUG && matchingAttempt) console.log(`[DEBUG] Sync: Found latest pending match by type:`, matchingAttempt);
                }


                if (matchingAttempt) {
                     if (AMEX_DEBUG) console.log('[DEBUG] Sync: Found matching attempt, updating:', matchingAttempt);
                    // 避免重复更新统计
                    const needsStatUpdate = !matchingAttempt.verified && !matchingAttempt.rejected;

                    matchingAttempt.status = result.isValid ? 'success' : 'error';
                    matchingAttempt.verified = result.isValid;
                    matchingAttempt.rejected = !result.isValid;
                     // 只有当本地记录没有类型时才更新类型
                    if (!matchingAttempt.testedType || matchingAttempt.testedType === '未知链接') {
                         matchingAttempt.testedType = result.linkType;
                    }
                    matchingAttempt.verificationReason = result.reason;
                    matchingAttempt.verifiedAt = result.timestamp;
                    matchingAttempt.testedUrl = result.url; // 更新URL

                    if (needsStatUpdate) {
                        stats.totalTested++;
                        if (result.isValid) {
                            stats.verified++;
                        } else {
                            stats.rejected++;
                        }
                    }
                    updatedCount++;
                    processedTimestamps.add(resultKey); // 标记为已处理
                } else {
                     if (AMEX_DEBUG) console.log(`[DEBUG] Sync: No matching attempt found for result:`, result);
                }
            }

            if (updatedCount > 0) {
                GM_setValue('attempts', attempts);
                GM_setValue('stats', stats);
                updateAttemptsList();
                updateStats();
                updateStatus(`从其他窗口同步了 ${updatedCount} 个结果`);
                 if (AMEX_DEBUG) console.log(`[DEBUG] Sync finished, updated ${updatedCount} attempts.`);

                // **优化:只移除被成功匹配处理的结果**
                const remainingResults = testResults.filter(result => {
                     const resultKey = `${result.timestamp}-${result.wocCode}-${result.linkType}`;
                     return !processedTimestamps.has(resultKey);
                });
                localStorage.setItem('amex_test_results', JSON.stringify(remainingResults));
                 if (AMEX_DEBUG) console.log(`[DEBUG] Cleaned localStorage, remaining: ${remainingResults.length}`);

            } else {
                 if (AMEX_DEBUG) console.log('[DEBUG] Sync finished, no attempts updated.');
                 // 如果长时间没有更新,可以考虑清理旧的localStorage记录
                 if(testResults.length > 20) { // 例如,超过20条且没匹配到
                     const recentResults = testResults.slice(-20); // 只保留最近20条
                     localStorage.setItem('amex_test_results', JSON.stringify(recentResults));
                      if (AMEX_DEBUG) console.log('[DEBUG] Cleaned old localStorage results as no match found.');
                 }
            }

        } catch (e) {
            console.error('同步其他窗口测试结果失败', e);
            updateStatus('错误: 同步localStorage结果失败');
        }
    }

    // 清理localStorage中的测试结果 (现在主要由sync函数处理,保留此函数用于手动清理)
    function clearLocalStorageResults() {
        localStorage.removeItem('amex_test_results');
        updateStatus('已手动清除LocalStorage中的同步缓存');
    }

    // **修改点:缩短同步间隔**
    setInterval(syncTestResultsFromOtherWindows, 2000); // 每2秒同步一次

    // --- UI 和交互函数 ---

    // 创建悬浮按钮
    function createFloatButton() {
        if (document.getElementById('amex-float-btn')) return;
        const floatBtn = document.createElement('button');
        floatBtn.id = 'amex-float-btn';
        floatBtn.className = 'amex-float-btn';
        floatBtn.innerHTML = 'A';
        floatBtn.title = '打开AMEX助手';
        floatBtn.addEventListener('click', showPanel);
        document.body.appendChild(floatBtn);
    }

    // 显示面板
    function showPanel() {
        const floatBtn = document.getElementById('amex-float-btn');
        if (floatBtn) floatBtn.remove();
        const panel = document.getElementById('amex-helper-panel');
        if (panel) {
            panel.style.display = 'block';
        } else {
            createPanel(); // 如果面板不存在则创建
        }
        isPanelVisible = true;
    }

    // 隐藏面板
    function hidePanel() {
        const panel = document.getElementById('amex-helper-panel');
        if (panel) panel.style.display = 'none';
        isPanelVisible = false;
        createFloatButton(); // 显示悬浮按钮
    }

    // 解析批量输入 (与原版一致)
    function parseCodesInput(input) {
        const lines = input.split('\n');
        const offerCodes = new Set();
        const wocCodes = new Set();

        lines.forEach(line => {
            line = line.trim();
            if (!line) return;

            const cleanLine = line.replace(/\s+/g, ' ').trim();
            const compositePattern = /^(\d{5}-\d+-\d+)-([A-Z][A-Z0-9]+)/;
            const compositeMatch = line.match(compositePattern);
            if (compositeMatch) {
                offerCodes.add(compositeMatch[1]);
                wocCodes.add(compositeMatch[2]);
                return;
            }

            const isOfferCodePattern = /^\d{5}-\d+-\d+$/;
            const isWOCCodePattern = /^([A-Z][A-Z0-9]+)$/;

            if (line.includes(',')) {
                const [offerPart, wocPart] = line.split(',').map(s => s.trim());
                if (offerPart) offerCodes.add(offerPart);
                if (wocPart) wocCodes.add(wocPart);
                return;
            }

            if (line.includes('\t') || /\s{2,}/.test(line)) {
                const parts = line.split(/\s+/).filter(p => p.trim());
                parts.forEach(part => {
                    part = part.trim();
                    if (!part) return;
                    if (isOfferCodePattern.test(part)) {
                        offerCodes.add(part);
                    } else if (part.length > 4 && (part.startsWith('WOC') || isWOCCodePattern.test(part))) { // WOC长度放宽到5
                        wocCodes.add(part);
                    }
                });
                return;
            }

            if (isOfferCodePattern.test(line)) {
                offerCodes.add(line);
            } else if (line.length > 4 && (line.startsWith('WOC') || isWOCCodePattern.test(line))) {
                wocCodes.add(line);
            } else if (/^\d/.test(line) && line.includes('-')) { // 稍微放宽Offer Code判断
                offerCodes.add(line);
            } else if (/^[A-Z]/.test(line) && line.length > 4) { // 字母开头且长度足够,认为是WOC
                wocCodes.add(line);
            }
        });

        return {
            offerCodes: Array.from(offerCodes),
            wocCodes: Array.from(wocCodes)
        };
    }

    // 更新批量组合预览
    function updateCombinations() {
        const batchInput = document.getElementById('batchCodes').value.trim();
        const infoElement = document.getElementById('comboInfo');
        if (!infoElement) return; // 确保元素存在

        if (!batchInput) {
            infoElement.innerHTML = `组合预览: <b>0</b> 种组合`;
            offerCodes = []; // 清空全局变量
            wocCodes = [];   // 清空全局变量
            return;
        }

        const parsedCodes = parseCodesInput(batchInput);
        offerCodes = parsedCodes.offerCodes; // 更新全局变量
        wocCodes = parsedCodes.wocCodes;   // 更新全局变量

        let html = `<div>识别到 <b>${offerCodes.length}</b> Offer Code, <b>${wocCodes.length}</b> WOC Code</div>`;
        let totalCombos = 0;

        if (offerCodes.length === 0 && wocCodes.length > 0) {
            totalCombos = wocCodes.length;
            html += `<div>将使用空 Offer Code 进行测试</div>`;
        } else if (wocCodes.length === 0 && offerCodes.length > 0) {
            totalCombos = offerCodes.length;
            html += `<div>将使用空 WOC Code 进行测试</div>`;
        } else {
            totalCombos = offerCodes.length * wocCodes.length;
        }

        if (totalCombos === 0 && (offerCodes.length > 0 || wocCodes.length > 0)) {
             // 处理只有一个列表有代码的情况
             totalCombos = Math.max(offerCodes.length, wocCodes.length);
             if(offerCodes.length === 0) html += `<div>将使用空 Offer Code 进行测试</div>`;
             if(wocCodes.length === 0) html += `<div>将使用空 WOC Code 进行测试</div>`;
        }


        let skippedCombos = 0;
        if (skipExisting && totalCombos > 0) {
            const currentOfferCodes = offerCodes.length > 0 ? offerCodes : [''];
            const currentWocCodes = wocCodes.length > 0 ? wocCodes : [''];
            currentOfferCodes.forEach(offerCode => {
                currentWocCodes.forEach(wocCode => {
                    if (combinationExists(offerCode, wocCode)) {
                        skippedCombos++;
                    }
                });
            });
        }

        const newCombos = totalCombos - skippedCombos;
        html += `<div>总组合: <b>${totalCombos}</b>`;
        if (skipExisting && skippedCombos > 0) {
            html += ` (跳过 ${skippedCombos} 个已测试, 新增 ${newCombos} 个)`;
        } else if (!skipExisting && skippedCombos > 0) {
             html += ` (重新测试 ${skippedCombos} 个已存在)`;
        }
        html += `</div>`;

        if (offerCodes.length > 0) {
            html += `<div style="font-size:10px;margin-top:3px;">Offer Codes: ${offerCodes.slice(0, 5).join(', ')}${offerCodes.length > 5 ? '...' : ''}</div>`;
        }
        if (wocCodes.length > 0) {
            html += `<div style="font-size:10px;margin-top:3px;">WOC Codes: ${wocCodes.slice(0, 5).join(', ')}${wocCodes.length > 5 ? '...' : ''}</div>`;
        }

        infoElement.innerHTML = html;
    }

    // 检查组合是否存在 (与原版一致)
    function combinationExists(offerCode, wocCode) {
        return attempts.some(attempt =>
            attempt.offerCode === offerCode &&
            attempt.wocCode === wocCode
        );
    }

    // 添加批量工具 (与原版一致)
    function addBatchTools() {
        const batchTab = document.getElementById('batch-tab');
        if (!batchTab || document.getElementById('cleanFormatBtn')) return; // 防止重复添加

        const toolsDiv = document.createElement('div');
        toolsDiv.className = 'batch-tools section';
        toolsDiv.innerHTML = `
            <div style="font-weight: bold; margin-bottom: 5px;">批量工具</div>
            <button id="cleanFormatBtn" class="btn-control" style="font-size: 11px; padding: 3px 8px;">清理格式</button>
            <button id="extractOffersBtn" class="btn-control" style="font-size: 11px; padding: 3px 8px;">提取Offer Codes</button>
            <button id="extractWOCsBtn" class="btn-control" style="font-size: 11px; padding: 3px 8px;">提取WOC Codes</button>
            <button id="extractFromLinkBtn" class="btn-control" style="font-size: 11px; padding: 3px 8px;">从链接提取</button>
        `;

        const comboInfo = document.getElementById('comboInfo');
        if (comboInfo && comboInfo.parentNode) {
            comboInfo.parentNode.insertBefore(toolsDiv, comboInfo.nextSibling);
        } else {
            batchTab.appendChild(toolsDiv);
        }

        document.getElementById('cleanFormatBtn').addEventListener('click', cleanInputFormat);
        document.getElementById('extractOffersBtn').addEventListener('click', () => extractCodes('offer'));
        document.getElementById('extractWOCsBtn').addEventListener('click', () => extractCodes('woc'));
        document.getElementById('extractFromLinkBtn').addEventListener('click', extractFromLinks);
    }

    // 从链接提取代码 (与原版一致)
    function extractFromLinks() {
        const batchInput = document.getElementById('batchCodes');
        const input = batchInput.value.trim();
        if (!input) {
            updateStatus('请先输入包含链接的文本');
            return;
        }

        const extractedOffers = new Set();
        const extractedWOCs = new Set();
        const lines = input.split('\n');

        lines.forEach(line => {
            line = cleanUrl(line); // 清理每一行
            // 1. 升级链接
            const upgradePattern = /upgrade\/.*?\/(\d{5}-\d+-\d+)[-\/]([A-Z0-9]+)/i;
            let match = line.match(upgradePattern);
            if (match) {
                if (match[1]) extractedOffers.add(match[1]);
                if (match[2]) extractedWOCs.add(match[2]);
            }
            // 2. 副卡链接
            const suppPattern = /stand-alone-supps\/(\d{5}-\d+-\d+)[-\/]([A-Z0-9]+)/i;
            match = line.match(suppPattern);
            if (match) {
                if (match[1]) extractedOffers.add(match[1]);
                if (match[2]) extractedWOCs.add(match[2]);
            }
            // 3. 通用WOC提取
            const wocInUrlPattern = /[-=\/]([A-Z][A-Z0-9]{4,})(?:[?&]|$|\s|"|'|#)/i; // 改进正则
            const wocMatches = line.matchAll(wocInUrlPattern);
            for (const wocMatch of wocMatches) {
                 if (wocMatch[1] && (wocMatch[1].startsWith('WOC') || /^[A-Z]{2,}/.test(wocMatch[1]))) { // 确保是字母开头
                    extractedWOCs.add(wocMatch[1]);
                 }
            }
             // 4. 尝试提取URL参数中的Offer Code
            const offerParamMatch = line.match(/[?&]offerid=(\d{5}-\d+-\d+)/i);
             if(offerParamMatch && offerParamMatch[1]) {
                 extractedOffers.add(offerParamMatch[1]);
             }
        });

        const offerCodesArray = Array.from(extractedOffers);
        const wocCodesArray = Array.from(extractedWOCs);
        let output = '';

        if (offerCodesArray.length > 0 && wocCodesArray.length > 0) {
            offerCodesArray.forEach(offer => {
                wocCodesArray.forEach(woc => {
                    output += `${offer},${woc}\n`;
                });
            });
        } else if (offerCodesArray.length > 0) {
            offerCodesArray.forEach(code => output += `${code}\n`);
        } else if (wocCodesArray.length > 0) {
            wocCodesArray.forEach(code => output += `${code}\n`);
        }

        batchInput.value = output;
        updateStatus(`已从链接中提取: ${offerCodesArray.length} Offer Code, ${wocCodesArray.length} WOC Code`);
        updateCombinations();
    }

    // 清理输入格式 (与原版一致)
    function cleanInputFormat() {
        const batchInput = document.getElementById('batchCodes');
        const input = batchInput.value.trim();
        if (!input) return;

        const parsedCodes = parseCodesInput(input);
        const offerCodes = parsedCodes.offerCodes;
        const wocCodes = parsedCodes.wocCodes;
        let output = '';

        if (offerCodes.length > 0 && wocCodes.length > 0) {
            offerCodes.forEach(offerCode => {
                wocCodes.forEach(wocCode => {
                    output += `${offerCode},${wocCode}\n`;
                });
            });
        } else if (offerCodes.length > 0) {
            offerCodes.forEach(offerCode => output += `${offerCode}\n`);
        } else if (wocCodes.length > 0) {
            wocCodes.forEach(wocCode => output += `${wocCode}\n`);
        }

        batchInput.value = output;
        updateStatus('格式已清理,识别了 ' + offerCodes.length + ' 个Offer Code和 ' + wocCodes.length + ' 个WOC Code');
        updateCombinations();
    }

    // 提取特定类型代码 (与原版一致)
    function extractCodes(type) {
        const batchInput = document.getElementById('batchCodes');
        const input = batchInput.value.trim();
        if (!input) return;

        const parsedCodes = parseCodesInput(input);
        let output = '';
        if (type === 'offer') {
            parsedCodes.offerCodes.forEach(code => output += code + '\n');
            updateStatus('已提取 ' + parsedCodes.offerCodes.length + ' 个Offer Code');
        } else {
            parsedCodes.wocCodes.forEach(code => output += code + '\n');
            updateStatus('已提取 ' + parsedCodes.wocCodes.length + ' 个WOC Code');
        }
        batchInput.value = output;
        updateCombinations();
    }

    // 保存设置 (合并通知设置保存逻辑)
    function saveSettings() {
        // 读取所有设置项的值
        const serverUrlVal = document.getElementById('serverUrl').value.trim();
        const scriptDomainsVal = document.getElementById('scriptDomains').value.trim();
        const forceDesktopModeVal = document.getElementById('forceDesktopMode').checked;
        const emailNotificationVal = document.getElementById('emailNotification').value.trim();
        const smtpServerVal = document.getElementById('smtpServer').value.trim();
        const smtpUserVal = document.getElementById('smtpUser').value.trim();
        const smtpPasswordVal = document.getElementById('smtpPassword').value.trim();
        const smtpPortVal = document.getElementById('smtpPort').value.trim();
        const smtpSslVal = document.getElementById('smtpSsl').checked;
        const debugModeVal = document.getElementById('enableDebug').checked;

        // 读取通知设置项的值
        const enableSoundVal = document.getElementById('enableSound').checked;
        const enableMobilePopupVal = document.getElementById('enableMobilePopup').checked;
        const enableDesktopVal = document.getElementById('enableDesktopNotif').checked;
        const enableEmailVal = document.getElementById('enableEmailNotif').checked;

        // 保存常规设置
        GM_setValue('serverUrl', serverUrlVal);
        GM_setValue('scriptDomains', scriptDomainsVal);
        GM_setValue('forceDesktopMode', forceDesktopModeVal);
        GM_setValue('emailNotification', emailNotificationVal);
        GM_setValue('smtpServer', smtpServerVal);
        GM_setValue('smtpUser', smtpUserVal);
        GM_setValue('smtpPassword', smtpPasswordVal); // 安全提示:明文存储密码不安全
        GM_setValue('smtpPort', smtpPortVal);
        GM_setValue('smtpSsl', smtpSslVal);
        GM_setValue('AMEX_DEBUG', debugModeVal);
        AMEX_DEBUG = debugModeVal; // 更新当前会话状态

        // 更新并保存通知设置对象
        notificationSettings.enableSound = enableSoundVal;
        notificationSettings.enableMobilePopup = enableMobilePopupVal;
        notificationSettings.enableDesktop = enableDesktopVal;
        notificationSettings.enableEmail = enableEmailVal;
        GM_setValue('notificationSettings', notificationSettings); // **直接保存对象**

        updateStatus('设置已保存');
        if (AMEX_DEBUG) console.log('[DEBUG] Settings saved:', GM_listValues());
    }

    // 生成脚本设置 (与原版一致,但使用更健壮的模态框关闭)
    function generateScriptSettings() {
        const domains = document.getElementById('scriptDomains').value.trim();
        if (!domains) {
            updateStatus('请先输入域名');
            return;
        }
        const domainList = domains.split(',').map(d => d.trim()).filter(d => d);
        if (domainList.length === 0) {
            updateStatus('没有有效的域名');
            return;
        }

        const matchRules = domainList.map(domain => `// @match        *://*.${domain}/*`).join('\n');
        const scriptHeader =
`// ==UserScript==
// @name         AMEX Code Helper (通知设置保存优化)
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  自动提交AMEX offer code和WOC code,防止会话超时,支持批量测试,自动识别链接有效性,并记录结果 (已优化同步逻辑和通知设置保存)
// @author       Your Name & Gemini
${matchRules}
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// ==/UserScript==`;

        // 创建模态窗口
        const overlay = document.createElement('div');
        overlay.className = 'amex-helper-overlay'; // 使用遮罩层
        overlay.id = 'settings-modal-overlay';

        const modal = document.createElement('div');
        modal.className = 'amex-helper modal'; // 使用标准模态框样式
        modal.innerHTML = `
            <div class="modal-content">
                <button class="close-btn" id="closeSettingsModalBtn" title="关闭">&times;</button>
                <h3 class="modal-title">脚本设置已生成</h3>
                <p>请将以下设置复制到Tampermonkey脚本的头部,替换旧的设置:</p>
                <div style="background-color:#f5f5f5;padding:10px;border-radius:5px;margin:10px 0;max-height:300px;overflow-y:auto;border: 1px solid #ddd;">
                    <pre style="margin:0;white-space:pre-wrap;word-break:break-all;font-family:monospace;font-size:12px;">${scriptHeader}</pre>
                </div>
                <div style="text-align:center;margin-top:15px;">
                    <button id="copySettingsBtn" class="btn-control">复制设置</button>
                </div>
            </div>
        `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // 添加复制功能
        document.getElementById('copySettingsBtn').addEventListener('click', () => {
            navigator.clipboard.writeText(scriptHeader).then(() => {
                const copyBtn = document.getElementById('copySettingsBtn');
                copyBtn.textContent = '✓ 已复制';
                copyBtn.style.backgroundColor = '#218838';
                setTimeout(() => {
                    copyBtn.textContent = '复制设置';
                    copyBtn.style.backgroundColor = '#28a745';
                }, 1500);
            }).catch(err => {
                updateStatus('复制失败: ' + err);
                // Fallback for older browsers
                try {
                    const tempTextarea = document.createElement('textarea');
                    tempTextarea.value = scriptHeader;
                    document.body.appendChild(tempTextarea);
                    tempTextarea.select();
                    document.execCommand('copy');
                    document.body.removeChild(tempTextarea);
                     const copyBtn = document.getElementById('copySettingsBtn');
                     copyBtn.textContent = '✓ 已复制 (Fallback)';
                     copyBtn.style.backgroundColor = '#218838';
                     setTimeout(() => {
                         copyBtn.textContent = '复制设置';
                         copyBtn.style.backgroundColor = '#28a745';
                     }, 1500);
                } catch(fallbackErr) {
                     console.error("Fallback copy failed:", fallbackErr);
                     updateStatus('复制失败');
                }
            });
        });

        // 添加关闭功能
        const closeBtn = document.getElementById('closeSettingsModalBtn');
        const closeOverlay = () => {
             if(overlay.parentNode) overlay.remove();
        };
        closeBtn.addEventListener('click', closeOverlay);
        overlay.addEventListener('click', (e) => { // 点击遮罩关闭
            if (e.target === overlay) {
                closeOverlay();
            }
        });

        GM_setValue('scriptDomains', domains); // 保存设置
        updateStatus('已生成脚本设置');
    }

    // 更新统计数据显示
    function updateStats() {
        const totalEl = document.getElementById('totalTested');
        const verifiedEl = document.getElementById('verified');
        const rejectedEl = document.getElementById('rejected');
        if (totalEl) totalEl.textContent = stats.totalTested;
        if (verifiedEl) verifiedEl.textContent = stats.verified;
        if (rejectedEl) rejectedEl.textContent = stats.rejected;
    }

    // 导出到服务器 (与原版一致)
    function exportToServer() {
        const currentServerUrl = GM_getValue('serverUrl', ''); // 获取当前设置的URL
        if (!currentServerUrl) {
            updateStatus('错误: 请在设置中设定服务器URL');
            return;
        }
        if (attempts.length === 0) {
            updateStatus('没有可导出的尝试记录');
            return;
        }
        updateStatus('正在导出到服务器...');
        const dataToSend = { attempts: attempts, stats: stats, timestamp: Date.now() };
        GM_xmlhttpRequest({
            method: 'POST',
            url: currentServerUrl,
            data: JSON.stringify(dataToSend),
            headers: { 'Content-Type': 'application/json' },
            onload: function(response) {
                if (response.status >= 200 && response.status < 300) {
                    updateStatus('数据已成功导出到服务器');
                } else {
                    updateStatus(`导出失败: ${response.status} ${response.statusText}`);
                    console.error("导出失败:", response);
                }
            },
            onerror: function(error) {
                updateStatus('导出失败: 网络错误');
                 console.error("导出网络错误:", error);
            },
            ontimeout: function() {
                updateStatus('导出失败: 请求超时');
            }
        });
    }

    // 更新尝试记录列表显示
    function updateAttemptsList() {
        const listBody = document.getElementById('attemptsList');
        if (!listBody) return;

        listBody.innerHTML = ''; // 清空列表

        // 只显示最近 N 条记录,或根据需要添加分页
        const displayAttempts = attempts; //.slice(0, 50); // 例如只显示最近50条

        displayAttempts.forEach((attempt, index) => {
            const row = document.createElement('tr');
            if (attempt.verified) row.classList.add('verified');
            if (attempt.rejected) row.classList.add('rejected');

            // Helper to create cell
            const createCell = (text, className = '') => {
                const cell = document.createElement('td');
                cell.textContent = text || '-';
                if (className) cell.className = className;
                return cell;
            };

            row.appendChild(createCell(attempt.offerCode));
            row.appendChild(createCell(attempt.wocCode));
            row.appendChild(createCell(new Date(attempt.timestamp).toLocaleString()));
            row.appendChild(createCell(attempt.testedType || '未知'));

            // Status Cell
            let statusText = '⏳ 待测试';
            let statusClass = 'pending';
            if (attempt.verified) {
                statusText = '✅ 已验证'; statusClass = 'success';
            } else if (attempt.rejected) {
                statusText = '❌ 已否决'; statusClass = 'fail';
            } else if (attempt.status === 'success') {
                 // 如果通过页面检测成功,但未手动验证
                 statusText = '✔️ 检测成功'; statusClass = 'success';
            } else if (attempt.status === 'error') {
                 // 如果通过页面检测失败,但未手动验证
                 statusText = '✖️ 检测失败'; statusClass = 'fail';
            }
            row.appendChild(createCell(statusText, statusClass));

            // Action Cell
            const actionCell = document.createElement('td');
            if (!attempt.verified && !attempt.rejected) {
                const verifyBtn = document.createElement('button');
                verifyBtn.textContent = '验证';
                verifyBtn.className = 'verify-btn';
                // 使用 data-* 属性传递索引,避免闭包问题
                verifyBtn.dataset.index = attempts.indexOf(attempt); // 获取在完整attempts数组中的索引
                verifyBtn.addEventListener('click', (e) => {
                    const attemptIndex = parseInt(e.target.dataset.index, 10);
                    if (!isNaN(attemptIndex)) {
                        showVerificationModal(attemptIndex);
                    }
                });
                actionCell.appendChild(verifyBtn);
            }
             // 添加查看原因按钮
             if (attempt.verificationReason) {
                 const reasonBtn = document.createElement('button');
                 reasonBtn.textContent = '原因';
                 reasonBtn.className = 'verify-btn'; // 复用样式
                 reasonBtn.style.backgroundColor = '#6c757d'; // 灰色
                 reasonBtn.dataset.reason = attempt.verificationReason;
                 reasonBtn.dataset.url = attempt.testedUrl || '';
                 reasonBtn.addEventListener('click', (e) => {
                     alert(`验证原因: ${e.target.dataset.reason}\n测试URL: ${e.target.dataset.url || 'N/A'}`);
                 });
                 actionCell.appendChild(reasonBtn);
             }

            row.appendChild(actionCell);
            listBody.appendChild(row);
        });
    }

    // 显示验证模态窗口 (使用遮罩层)
    function showVerificationModal(attemptIndex) {
        const attempt = attempts[attemptIndex];
        if (!attempt) return;

        closeVerificationModal(); // 关闭可能存在的旧模态框

        const overlay = document.createElement('div');
        overlay.className = 'amex-helper-overlay';
        overlay.id = 'verification-modal-overlay';

        const modal = document.createElement('div');
        modal.className = 'amex-helper modal';
        modal.innerHTML = `
            <div class="modal-content">
                 <button class="close-btn" id="verifyCancelBtn" title="取消">&times;</button>
                <h3 class="modal-title">手动验证代码组合</h3>
                <p>Offer Code: <strong>${attempt.offerCode || '-'}</strong></p>
                <p>WOC Code: <strong>${attempt.wocCode || '-'}</strong></p>
                <p>链接类型: <strong>${attempt.testedType || '未知'}</strong></p>
                 <p>测试时间: <strong>${new Date(attempt.timestamp).toLocaleString()}</strong></p>
                <p>请根据您打开链接后的实际情况确认:</p>
                <div style="text-align: center; margin-top: 20px;">
                    <button id="verifyYesBtn" class="btn-control">有效 (✅)</button>
                    <button id="verifyNoBtn" class="btn-danger">无效 (❌)</button>
                </div>
            </div>
        `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // 添加事件监听器
        document.getElementById('verifyYesBtn').addEventListener('click', () => {
            markAttemptVerified(attemptIndex, true);
            closeVerificationModal();
        });
        document.getElementById('verifyNoBtn').addEventListener('click', () => {
            markAttemptVerified(attemptIndex, false);
            closeVerificationModal();
        });
        document.getElementById('verifyCancelBtn').addEventListener('click', closeVerificationModal);
        overlay.addEventListener('click', (e) => { // 点击遮罩关闭
            if (e.target === overlay) {
                closeVerificationModal();
            }
        });
    }

    // 关闭验证模态窗口
    function closeVerificationModal() {
        const overlay = document.getElementById('verification-modal-overlay');
        if (overlay) overlay.remove();
    }

    // 标记尝试为已验证/已否决
    function markAttemptVerified(index, isValid) {
        if (index < 0 || index >= attempts.length) return;

        const attempt = attempts[index];
        // 只有在未验证/未否决时才更新统计
        const needsStatUpdate = !attempt.verified && !attempt.rejected;

        if (needsStatUpdate) {
            stats.totalTested++;
        }

        if (isValid) {
            attempt.verified = true;
            attempt.rejected = false;
            attempt.status = 'success'; // 标记为成功
            if (needsStatUpdate) stats.verified++;
            updateStatus(`已标记 ${attempt.offerCode || ''} + ${attempt.wocCode || ''} 为有效`);
        } else {
            attempt.rejected = true;
            attempt.verified = false;
            attempt.status = 'error'; // 标记为失败
            if (needsStatUpdate) stats.rejected++;
            updateStatus(`已标记 ${attempt.offerCode || ''} + ${attempt.wocCode || ''} 为无效`);
        }
        // 记录手动验证时间
        attempt.verifiedAt = Date.now();
        // 清除可能存在的自动验证原因
        attempt.verificationReason = isValid ? '手动验证有效' : '手动验证无效';


        GM_setValue('attempts', attempts);
        GM_setValue('stats', stats);
        updateAttemptsList();
        updateStats();
    }

    // 应用代码(单个测试)
    function applyCode(linkType = 'both') {
        const offerCodeInput = document.getElementById('offerCode');
        const wocCodeInput = document.getElementById('wocCode');
        let offerCode = offerCodeInput.value.trim();
        let wocCode = wocCodeInput.value.trim();

        if (!offerCode && lastOfferCode) offerCode = lastOfferCode;
        if (!wocCode && lastWOCCode) wocCode = lastWOCCode;

        if (!offerCode && !wocCode) {
            updateStatus('错误: 请至少输入一个代码');
            return;
        }

        offerCodeInput.value = offerCode; // 更新输入框显示
        wocCodeInput.value = wocCode;

        GM_setValue('lastOfferCode', offerCode); // 保存以便下次使用
        GM_setValue('lastWOCCode', wocCode);

        visitLinks(offerCode, wocCode, linkType); // 生成链接并访问
    }

    // 访问链接并记录尝试
    function visitLinks(offerCode, wocCode, linkType = 'both') {
        // 修正:确保offerCode和wocCode至少有一个有值
        if (!offerCode && !wocCode) {
             console.warn("visitLinks called with empty codes, skipping.");
             return;
        }

        // 记录尝试 (确保不重复记录完全相同的待处理项)
        const existingPending = attempts.find(a =>
            a.offerCode === offerCode &&
            a.wocCode === wocCode &&
            a.status === 'pending' &&
            (a.testedType === linkType || !a.testedType) // 考虑类型
        );

        if (!existingPending) {
            const attempt = {
                offerCode: offerCode || '', // 确保有值
                wocCode: wocCode || '',   // 确保有值
                timestamp: Date.now(),
                status: 'pending',
                testedType: getLinkTypeLabel(linkType), // 记录测试类型
                verified: false,
                rejected: false,
                testedUrl: '' // 初始化测试URL
            };
            attempts.unshift(attempt); // 添加到开头
            GM_setValue('attempts', attempts);
            updateAttemptsList(); // 更新列表显示
        } else {
             if (AMEX_DEBUG) console.log("[DEBUG] Skipping duplicate pending attempt:", offerCode, wocCode, linkType);
        }


        // 构建链接 (使用更标准的URL构建)
        const baseUrl = 'https://www.americanexpress.com/us/credit-cards/card-application/apply';
        const upgradePath = `upgrade/business-platinum-charge-card/${offerCode || '63453-9-1'}-${wocCode || ''}`;
        const supplementaryPath = `stand-alone-supps/${offerCode || '64399-9-1'}-${wocCode || ''}`;

        const upgradeLink = `${baseUrl}/${upgradePath}`;
        const supplementaryLink = `${baseUrl}/${supplementaryPath}`;

        let linksToOpen = [];

        if (linkType === 'both' || linkType === 'upgrade') {
            updateStatus(`准备测试升级链接: ${offerCode || '默认'} + ${wocCode}`);
             // 查找或创建对应的记录来存储URL
             let attemptRecord = findOrCreateAttempt(offerCode, wocCode, '升级链接');
             attemptRecord.testedUrl = upgradeLink; // 记录将要测试的URL
             linksToOpen.push({ url: upgradeLink, type: '升级链接' });
        }

        if (linkType === 'both' || linkType === 'supplementary') {
            updateStatus(`准备测试副卡链接: ${offerCode || '默认'} + ${wocCode}`);
             let attemptRecord = findOrCreateAttempt(offerCode, wocCode, '副卡链接');
             attemptRecord.testedUrl = supplementaryLink;
             linksToOpen.push({ url: supplementaryLink, type: '副卡链接' });
        }

        // 保存更新后的attempts (包含URL)
        GM_setValue('attempts', attempts);
        updateAttemptsList();

        // 依次打开链接
        linksToOpen.forEach(linkInfo => {
             testLink(linkInfo.url, offerCode, wocCode, linkInfo.type);
        });

        return { upgradeLink, supplementaryLink, linkType };
    }

     // 查找或创建尝试记录
     function findOrCreateAttempt(offerCode, wocCode, linkType) {
         let attempt = attempts.find(a =>
             a.offerCode === offerCode &&
             a.wocCode === wocCode &&
             (a.testedType === linkType || !a.testedType || a.testedType === '未知链接') && // 匹配类型
             a.status === 'pending' // 仅查找待处理的
         );

         if (!attempt) {
             // 如果是测试 'both',并且已经有一个 '升级链接' 的 pending 记录,
             // 我们需要为 '副卡链接' 创建一个新的记录
             if (linkType === '副卡链接') {
                 const upgradePendingExists = attempts.some(a =>
                     a.offerCode === offerCode &&
                     a.wocCode === wocCode &&
                     a.testedType === '升级链接' &&
                     a.status === 'pending'
                 );
                 if (upgradePendingExists) {
                     attempt = null; // 强制创建新记录
                 } else {
                      // 否则,尝试查找任何一个未完成的记录
                      attempt = attempts.find(a =>
                          a.offerCode === offerCode &&
                          a.wocCode === wocCode &&
                          !a.verified && !a.rejected
                      );
                 }
             } else {
                  // 尝试查找任何一个未完成的记录
                  attempt = attempts.find(a =>
                      a.offerCode === offerCode &&
                      a.wocCode === wocCode &&
                      !a.verified && !a.rejected
                  );
             }


             // 如果还是找不到,或者需要强制创建
             if (!attempt) {
                 attempt = {
                     offerCode: offerCode || '',
                     wocCode: wocCode || '',
                     timestamp: Date.now(),
                     status: 'pending',
                     testedType: linkType, // 设置明确的类型
                     verified: false,
                     rejected: false,
                     testedUrl: ''
                 };
                 attempts.unshift(attempt);
                 // 不需要立即保存,会在visitLinks结束时一起保存
             } else {
                  // 如果找到了一个已完成的记录,则创建一个新的pending记录
                  if(attempt.verified || attempt.rejected) {
                       attempt = {
                           offerCode: offerCode || '',
                           wocCode: wocCode || '',
                           timestamp: Date.now(),
                           status: 'pending',
                           testedType: linkType,
                           verified: false,
                           rejected: false,
                           testedUrl: ''
                       };
                       attempts.unshift(attempt);
                  } else {
                       // 更新现有未完成记录的类型
                       if (!attempt.testedType || attempt.testedType === '未知链接') {
                           attempt.testedType = linkType;
                       }
                  }
             }
         }
         return attempt;
     }


    // 测试链接 (与原版一致,使用 cleanUrl)
    function testLink(url, offerCode, wocCode, type) {
        const forceDesktopMode = GM_getValue('forceDesktopMode', false);
        const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
        const isMac = /Mac/.test(navigator.userAgent);
        const cleanedUrl = cleanUrl(url); // 清理URL

        if ((isIOS || isMac) && !forceDesktopMode) {
            // iOS/Mac 优化模式:不实际请求,直接显示链接
            // 查找对应的记录并标记为待处理 (如果需要)
            let attempt = findOrCreateAttempt(offerCode, wocCode, type);
            attempt.status = 'pending'; // 确保是pending状态
            attempt.testedUrl = cleanedUrl;
            GM_setValue('attempts', attempts); // 保存更新
            updateAttemptsList();
            setTimeout(() => showClickableLink(cleanedUrl, offerCode, wocCode, type), 100);
            return;
        }

        // 其他设备或强制桌面模式:使用 HEAD 请求预检
        GM_xmlhttpRequest({
            method: 'HEAD',
            url: cleanedUrl + (cleanedUrl.includes('?') ? '&' : '?') + '_t=' + Date.now(), // 加时间戳防缓存
            timeout: 10000,
            onload: function(response) {
                // 2xx 或 3xx 都认为链接本身可访问
                const statusOk = response.status >= 200 && response.status < 400;
                // 查找对应的记录并更新状态(但不标记为 verified/rejected)
                let attempt = findOrCreateAttempt(offerCode, wocCode, type);
                attempt.status = statusOk ? 'success' : 'error'; // 只更新检测状态
                attempt.testedUrl = cleanedUrl;
                attempt.verificationReason = statusOk ? 'HEAD请求成功' : `HEAD请求失败 (${response.status})`;
                GM_setValue('attempts', attempts);
                updateAttemptsList();
                updateStatus(`${type} HEAD预检${statusOk ? '成功' : '失败'}: ${offerCode || ''} + ${wocCode || ''}`);

                // 只有HEAD成功才打开链接
                if (statusOk) {
                    window.open(cleanedUrl, '_blank');
                }
                 // 批量测试时,无论成功失败都继续下一个
                 if (batchTesting) {
                     const selectedLinkType = document.querySelector('input[name="linkType"]:checked').value;
                     const isLastOfType = (selectedLinkType === 'both' && type === '副卡链接') || (selectedLinkType !== 'both');
                     if (isLastOfType) {
                         // **修改点:使用动态间隔**
                         const interval = calculateBatchInterval();
                         if (AMEX_DEBUG) console.log(`[DEBUG] Batch interval: ${interval}ms`);
                         setTimeout(processNextBatch, interval);
                     }
                 }
            },
            onerror: function(error) {
                let attempt = findOrCreateAttempt(offerCode, wocCode, type);
                attempt.status = 'error';
                attempt.testedUrl = cleanedUrl;
                attempt.verificationReason = 'HEAD请求网络错误';
                GM_setValue('attempts', attempts);
                updateAttemptsList();
                updateStatus(`${type} HEAD预检失败(网络错误): ${offerCode || ''} + ${wocCode || ''}`);
                console.error("HEAD request error:", error);
                 // 批量测试时继续
                 if (batchTesting) {
                     const selectedLinkType = document.querySelector('input[name="linkType"]:checked').value;
                     const isLastOfType = (selectedLinkType === 'both' && type === '副卡链接') || (selectedLinkType !== 'both');
                      if (isLastOfType) {
                         const interval = calculateBatchInterval();
                          if (AMEX_DEBUG) console.log(`[DEBUG] Batch interval (onerror): ${interval}ms`);
                         setTimeout(processNextBatch, interval);
                     }
                 }
            },
            ontimeout: function() {
                let attempt = findOrCreateAttempt(offerCode, wocCode, type);
                attempt.status = 'error';
                attempt.testedUrl = cleanedUrl;
                attempt.verificationReason = 'HEAD请求超时';
                GM_setValue('attempts', attempts);
                updateAttemptsList();
                updateStatus(`${type} HEAD预检失败(超时): ${offerCode || ''} + ${wocCode || ''}`);
                 // 批量测试时继续
                 if (batchTesting) {
                     const selectedLinkType = document.querySelector('input[name="linkType"]:checked').value;
                     const isLastOfType = (selectedLinkType === 'both' && type === '副卡链接') || (selectedLinkType !== 'both');
                      if (isLastOfType) {
                         const interval = calculateBatchInterval();
                          if (AMEX_DEBUG) console.log(`[DEBUG] Batch interval (ontimeout): ${interval}ms`);
                         setTimeout(processNextBatch, interval);
                     }
                 }
            }
        });
    }

    // 显示可点击链接 (iOS优化) (使用遮罩层和标准模态框)
    function showClickableLink(url, offerCode, wocCode, type) {
        closeVerificationModal(); // 关闭可能存在的其他模态框

        const overlay = document.createElement('div');
        overlay.className = 'amex-helper-overlay';
        overlay.id = 'ios-link-overlay';

        const modal = document.createElement('div');
        modal.className = 'amex-helper modal';
        modal.innerHTML = `
            <div class="modal-content">
                <button class="close-btn" id="closeIOSLinkBtn" title="关闭">&times;</button>
                <h3 class="modal-title">链接准备就绪 (iOS/Mac优化)</h3>
                <p>请点击下面的按钮或复制链接在新标签页中打开:</p>
                <p><strong>${type}</strong><br>
                   Offer: ${offerCode || '-'}<br>
                   WOC: ${wocCode || '-'}</p>
                <a href="${url}" target="_blank" rel="noopener noreferrer" class="ios-link-button">点击打开链接</a>
                <div style="margin: 10px 0; padding: 10px; background-color: #f5f5f5; border-radius: 5px; word-break: break-all; font-size: 12px; border: 1px solid #ddd;">${url}</div>
                <button id="copyIOSLinkBtn" class="btn-control" style="width:100%; margin-top: 10px;">复制链接</button>
                <p style="font-size: 11px; color: #666; margin-top: 15px;">提示: 您需要手动打开此链接进行测试。</p>
            </div>
        `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // 添加复制功能
        document.getElementById('copyIOSLinkBtn').addEventListener('click', () => {
            navigator.clipboard.writeText(url).then(() => {
                const copyBtn = document.getElementById('copyIOSLinkBtn');
                copyBtn.textContent = '✓ 复制成功';
                copyBtn.style.backgroundColor = '#218838';
                setTimeout(() => {
                    copyBtn.textContent = '复制链接';
                    copyBtn.style.backgroundColor = '#28a745';
                }, 1500);
            }).catch(err => updateStatus('复制失败: ' + err));
        });

        // 添加关闭功能
        const closeBtn = document.getElementById('closeIOSLinkBtn');
        const closeOverlay = () => {
             if(overlay.parentNode) overlay.remove();
             // 批量测试时继续下一个
             if (batchTesting) {
                 const selectedLinkType = document.querySelector('input[name="linkType"]:checked').value;
                 const isLastOfType = (selectedLinkType === 'both' && type === '副卡链接') || (selectedLinkType !== 'both');
                 if (isLastOfType) {
                     const interval = calculateBatchInterval();
                      if (AMEX_DEBUG) console.log(`[DEBUG] Batch interval (iOS closed): ${interval}ms`);
                     setTimeout(processNextBatch, interval); // 关闭后处理下一个
                 }
             }
        };
        closeBtn.addEventListener('click', closeOverlay);
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) closeOverlay();
        });

        updateStatus(`已显示可点击链接 (iOS优化): ${type}`);
    }

    // 更新链接状态 (此函数不再直接更新 verified/rejected)
    function updateLinkStatus(offerCode, wocCode, success, type) {
        // 查找对应的尝试记录
        let attempt = findOrCreateAttempt(offerCode, wocCode, type);
        attempt.status = success ? 'success' : 'error'; // 更新检测状态
        attempt.verificationReason = success ? 'HEAD请求成功' : `HEAD请求失败 (${type.includes('超时') ? '超时' : type.includes('网络错误') ? '网络错误' : '状态码错误'})`;
        GM_setValue('attempts', attempts);
        updateAttemptsList();
        updateStatus(`${type} HEAD预检${success ? '成功' : '失败'}: ${offerCode || ''} + ${wocCode || ''}`);

        // 批量测试逻辑移到 testLink 的回调中处理
    }

    // 开始批量测试
    function startBatchTest() {
        const batchInput = document.getElementById('batchCodes').value.trim();
        if (!batchInput) {
            updateStatus('请输入批量测试代码');
            return;
        }
        GM_setValue('lastBatchCodes', batchInput); // 保存输入

        const selectedLinkType = document.querySelector('input[name="linkType"]:checked').value;
        skipExisting = document.getElementById('skipExistingCheck').checked; // 获取当前设置
        GM_setValue('skipExisting', skipExisting); // 保存设置

        // 重新解析以确保使用最新的代码列表
        updateCombinations(); // 这会更新全局的 offerCodes 和 wocCodes

        if (offerCodes.length === 0 && wocCodes.length === 0) {
            updateStatus('没有有效的代码可供测试');
            return;
        }

        // 确保至少有一个offer code或woc code
        const currentOfferCodes = offerCodes.length > 0 ? offerCodes : [''];
        const currentWocCodes = wocCodes.length > 0 ? wocCodes : [''];

        batchQueue = [];
        let skippedCount = 0;
        let totalCombos = 0;

        currentOfferCodes.forEach(offerCode => {
            currentWocCodes.forEach(wocCode => {
                 // 跳过完全为空的组合
                 if (!offerCode && !wocCode) return;

                 totalCombos++;
                if (skipExisting && combinationExists(offerCode, wocCode)) {
                    skippedCount++;
                } else {
                    batchQueue.push({ offerCode, wocCode, linkType: selectedLinkType });
                }
            });
        });


        if (batchQueue.length === 0) {
            updateStatus(skippedCount > 0 ? '所有组合都已测试过' : '没有生成有效的测试组合');
            return;
        }

        batchTesting = true;
        batchResults = []; // 清空上次结果
        const initialQueueLength = batchQueue.length;
        updateStatus(`开始批量测试 ${initialQueueLength} 组代码组合 (类型: ${getLinkTypeLabel(selectedLinkType)})${skippedCount > 0 ? `,跳过 ${skippedCount} 个` : ''}`);

        document.getElementById('batchInfo').textContent = `排队中: ${initialQueueLength} / 已完成: 0 / 跳过: ${skippedCount} / 总计: ${totalCombos}`;

        // 切换到结果标签页
        const resultsTabButton = document.querySelector('.amex-helper .tab[data-tab="results"]');
        if (resultsTabButton) resultsTabButton.click();

        processNextBatch(); // 开始处理第一个
    }

    // **修改点:批量处理间隔计算**
    function calculateBatchInterval() {
         const baseInterval = 800; // 基础间隔ms
         const queueLengthMultiplier = 50; // 每增加一个队列项增加的毫秒数
         const maxLengthThreshold = 30; // 超过这个长度,间隔增加更快
         const maxInterval = 4000; // 最大间隔ms

         let interval = baseInterval + batchQueue.length * queueLengthMultiplier;

         if (batchQueue.length > maxLengthThreshold) {
             interval += (batchQueue.length - maxLengthThreshold) * 100; // 超过阈值后加速增加
         }

         return Math.min(interval, maxInterval); // 不超过最大间隔
    }


    // 处理下一个批量队列项
    function processNextBatch() {
        if (!batchTesting || batchQueue.length === 0) {
            if (batchTesting) {
                updateStatus(`批量测试完成`);
                batchTesting = false;
                document.getElementById('batchInfo').textContent = '批量测试已完成';
            }
            return;
        }

        const nextItem = batchQueue.shift();
        const { offerCode, wocCode, linkType } = nextItem;

        // 更新批量信息显示
        const totalCombos = (offerCodes.length > 0 ? offerCodes.length : 1) * (wocCodes.length > 0 ? wocCodes.length : 1);
        const completedCount = totalCombos - batchQueue.length - (skipExisting ? (totalCombos - batchQueue.length) : 0) -1; // 估算完成数
        const skippedCount = skipExisting ? totalCombos - batchQueue.length - completedCount -1 : 0; // 估算跳过数
        document.getElementById('batchInfo').textContent = `排队中: ${batchQueue.length} / 已完成: ${completedCount} / 跳过: ${skippedCount} / 总计: ${totalCombos}`;

        // 访问链接 (visitLinks内部会处理HEAD请求和可能的window.open)
        // testLink 的回调函数会负责调用下一个 processNextBatch
        visitLinks(offerCode, wocCode, linkType);

        // **注意:** 下一个 processNextBatch 的调用已移至 testLink 的回调函数中,以确保在前一个链接处理(包括超时或错误)完成后再进行下一个。
    }

    // 停止批量测试
    function stopBatchTest() {
        if (batchTesting) {
            batchTesting = false;
            batchQueue = []; // 清空队列
            updateStatus('批量测试已手动停止');
            document.getElementById('batchInfo').textContent = '批量测试已停止';
        }
    }

    // 清除结果
    function clearResults() {
        if (confirm('确定要清除所有本地存储的测试结果和统计数据吗?此操作不可恢复。')) {
            const oldAttempts = [...attempts]; // 深拷贝备份
            const oldStats = {...stats};     // 深拷贝备份

            attempts = [];
            stats = { totalTested: 0, verified: 0, rejected: 0 }; // 重置统计

            GM_setValue('attempts', attempts);
            GM_setValue('stats', stats);
            clearLocalStorageResults(); // 同时清除localStorage缓存

            updateAttemptsList();
            updateStats();
            updateStatus('已清除所有测试结果和统计数据');

            showRestoreOptionWithStats(oldAttempts, oldStats); // 显示恢复选项
        }
    }

    // 切换自动刷新 (与原版一致)
    function toggleAutoRefresh() {
        const button = document.getElementById('toggleRefreshBtn');
        if (!button) return;

        if (refreshTimerId) {
            clearTimeout(refreshTimerId); // 使用 clearTimeout
            refreshTimerId = null;
            button.textContent = '启动自动刷新';
            button.classList.remove('btn-danger');
            button.classList.add('btn-control');
            updateStatus('自动刷新已停止');
             GM_setValue('autoRefreshEnabled', false); // 保存状态
        } else {
            button.textContent = '停止自动刷新';
            button.classList.remove('btn-control');
            button.classList.add('btn-danger');
            updateStatus('自动刷新已启动');
            refreshPage(); // 立即调用一次以设置定时器
             GM_setValue('autoRefreshEnabled', true); // 保存状态
        }
    }

    // 刷新页面 (与原版一致)
    function refreshPage() {
        if (refreshTimerId) clearTimeout(refreshTimerId); // 清除旧的

        const minTime = 3 * 60 * 1000;
        const maxTime = 4 * 60 * 1000;
        const randomTime = Math.floor(Math.random() * (maxTime - minTime + 1)) + minTime;

        updateStatus(`页面将在 ${Math.round(randomTime / 1000 / 60)} 分钟后自动刷新`);

        refreshTimerId = setTimeout(() => {
            updateStatus('正在刷新页面...');
            location.reload();
        }, randomTime);
    }

    // 导出尝试记录为CSV (与原版一致)
    function exportAttempts() {
        if (attempts.length === 0) {
            updateStatus('没有可导出的尝试记录');
            return;
        }
        let csv = 'Offer Code,WOC Code,Timestamp,Date,Status,测试类型,已验证,已否决,验证原因,测试URL\n'; // 添加列
        attempts.forEach(attempt => {
            const date = new Date(attempt.timestamp).toLocaleString();
            let status = '待测试';
            if(attempt.verified) status = '已验证';
            else if(attempt.rejected) status = '已否决';
            else if(attempt.status === 'success') status = '检测成功';
            else if(attempt.status === 'error') status = '检测失败';

            const reason = (attempt.verificationReason || '').replace(/"/g, '""'); // 处理引号
            const url = (attempt.testedUrl || '').replace(/"/g, '""');

            csv += `"${attempt.offerCode || ''}","${attempt.wocCode || ''}","${attempt.timestamp}","${date}","${status}","${attempt.testedType || ''}","${attempt.verified ? '是' : '否'}","${attempt.rejected ? '是' : '否'}","${reason}","${url}"\n`;
        });

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        const filename = `amex-attempts-${new Date().toISOString().slice(0, 10)}.csv`;
        const url = URL.createObjectURL(blob);
        try {
             GM_download(url, filename);
             updateStatus('尝试记录已导出');
        } catch (e) {
             console.error("导出失败:", e);
             updateStatus('导出失败,请检查浏览器下载设置或权限');
             // 提供备选下载方式
             const link = document.createElement('a');
             link.href = url;
             link.download = filename;
             link.style.display = 'none';
             document.body.appendChild(link);
             link.click();
             document.body.removeChild(link);
             updateStatus('尝试通过备选方式下载...');
        }
        URL.revokeObjectURL(url); // 释放内存
    }

    // 获取链接类型标签 (与原版一致)
    function getLinkTypeLabel(linkType) {
        switch (linkType) {
            case 'upgrade': return '仅升级';
            case 'supplementary': return '仅副卡';
            case 'both': default: return '全部';
        }
    }

    // 更新状态消息
    function updateStatus(message) {
        const statusEl = document.getElementById('status');
        if (statusEl) {
            statusEl.textContent = `状态: ${message}`;
        }
        // 同时输出到控制台(如果调试模式开启)
        if (AMEX_DEBUG) {
            console.log('[AMEX Helper Status]', message);
        }
    }

    // --- 初始化和事件绑定 ---

    // 页面加载完成后创建面板
    window.addEventListener('load', () => {
        // 延迟创建,确保页面元素加载完成
        setTimeout(createPanel, 1500);
        // 页面加载时也尝试同步一次
        setTimeout(syncTestResultsFromOtherWindows, 2500);
    });

    // 菜单命令 (与原版一致)
    GM_registerMenuCommand('显示/隐藏AMEX助手', function() {
        if (isPanelVisible) hidePanel();
        else showPanel();
    });

    // 创建控制面板 (主函数)
    function createPanel() {
        if (document.getElementById('amex-helper-panel')) return; // 防止重复创建

        const panel = document.createElement('div');
        panel.className = 'amex-helper';
        panel.id = 'amex-helper-panel';

        // 读取保存的通知设置来初始化复选框状态
        const currentNotificationSettings = GM_getValue('notificationSettings', {
            enableSound: true, enableMobilePopup: true, enableDesktop: true, enableEmail: false
        });
        // 更新全局变量
        notificationSettings = currentNotificationSettings;


        panel.innerHTML = `
            <button class="close-btn" id="closeBtn" title="隐藏面板">&times;</button>
            <h3>AMEX Code Helper</h3>
            <div class="tabs">
                <div class="tab active" data-tab="single">单个测试</div>
                <div class="tab" data-tab="batch">批量测试</div>
                <div class="tab" data-tab="results">测试结果</div>
                <div class="tab" data-tab="settings">设置</div>
            </div>

            <div id="single-tab" class="tab-content active">
                <input type="text" id="offerCode" placeholder="Offer Code (可选)" value="${lastOfferCode}">
                <input type="text" id="wocCode" placeholder="WOC Code (必填)" value="${lastWOCCode}">
                <div class="section">
                    <button id="applyUpgradeBtn" class="btn-test">测试升级</button>
                    <button id="applySupplementaryBtn" class="btn-test">测试副卡</button>
                    <button id="applyAllBtn" class="btn-test">测试全部</button>
                </div>
                <div class="section">
                    <button id="toggleRefreshBtn" class="btn-control">启动自动刷新</button>
                    <button id="clearCacheBtn" class="btn-danger">清除缓存</button>
                </div>
                <div class="stats">
                    总测试: <span id="totalTested">${stats.totalTested}</span> |
                    已验证: <span id="verified">${stats.verified}</span> |
                    已否决: <span id="rejected">${stats.rejected}</span>
                </div>
            </div>

            <div id="batch-tab" class="tab-content">
                <textarea id="batchCodes" placeholder="批量输入格式: OFFERCODE,WOCCODE (每行一组) 或仅 WOCCODE (每行一个) 或 链接列表&#10;系统将自动组合或提取代码">${lastBatchCodes}</textarea>
                <div class="combo-info" id="comboInfo">组合预览: 0 种组合</div>
                <div style="margin: 5px 0;">
                    <label><input type="checkbox" id="skipExistingCheck" ${skipExisting ? 'checked' : ''}> 跳过已测试的组合</label>
                </div>
                <div class="link-type-selector" style="margin: 5px 0;">
                    <span>测试链接类型: </span>
                    <label><input type="radio" name="linkType" value="both" checked> 全部</label>
                    <label><input type="radio" name="linkType" value="upgrade"> 仅升级</label>
                    <label><input type="radio" name="linkType" value="supplementary"> 仅副卡</label>
                </div>
                <div class="section">
                    <button id="startBatchBtn" class="btn-test">开始批量测试</button>
                    <button id="stopBatchBtn" class="btn-warning">停止测试</button>
                </div>
                <div class="batch-info" id="batchInfo"></div>
                </div>

            <div id="results-tab" class="tab-content">
                <div class="section" style="display: flex; flex-wrap: wrap; gap: 5px;">
                    <button id="syncResultsBtn" class="btn-control">手动同步结果</button> <button id="exportBtn" class="btn-export">导出CSV</button>
                    <button id="exportServerBtn" class="btn-export">导出到服务器</button>
                    <button id="clearResultsBtn" class="btn-danger">清除全部结果</button>
                </div>
                <div class="attempts">
                    <table class="results-table" id="resultsTable">
                        <thead>
                            <tr>
                                <th>Offer</th>
                                <th>WOC</th>
                                <th>时间</th>
                                <th>类型</th>
                                <th>状态</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody id="attemptsList">
                            </tbody>
                    </table>
                </div>
            </div>

            <div id="settings-tab" class="tab-content">
                <div class="settings-row">
                    <label class="settings-label">服务器URL (用于导出和邮件)</label>
                    <input type="text" id="serverUrl" placeholder="https://your-server.com/api/amex" value="${serverUrl}">
                </div>
                <div class="settings-row">
                    <label class="settings-label">脚本作用域域名</label>
                    <input type="text" id="scriptDomains" placeholder="americanexpress.com,amex.com" value="${scriptDomains}">
                    <button id="generateScriptBtn" class="btn-export" style="margin-left: 5px; padding: 5px 10px; font-size: 11px;">生成脚本设置</button>
                    <p class="settings-help">多个域名用逗号分隔。修改后需在Tampermonkey中更新脚本头部的 @match 规则。</p>
                </div>
                <div class="settings-row">
                    <label class="settings-label">设备类型检测</label>
                    <label><input type="checkbox" id="forceDesktopMode" ${GM_getValue('forceDesktopMode', false) ? 'checked' : ''}> 强制使用桌面模式打开链接</label>
                    <p class="settings-help">勾选后,在Mac/iOS上也会尝试自动打开链接,而不是显示按钮。</p>
                </div>
                 <div class="settings-row section">
                     <label class="settings-label">调试选项</label>
                     <div>
                         <label><input type="checkbox" id="enableDebug" ${AMEX_DEBUG ? 'checked' : ''}> 启用调试模式 (控制台输出详细信息)</label>
                         <button id="checkLocalStorageBtn" class="btn-control" style="margin-left:10px; padding: 5px 10px; font-size: 11px;">检查LocalStorage缓存</button>
                     </div>
                 </div>
                <div class="settings-row section">
                    <label class="settings-label">邮件通知设置 (需服务器支持)</label>
                    <input type="email" id="emailNotification" placeholder="接收通知的邮箱" value="${GM_getValue('emailNotification', '')}">
                    <div class="smtp-settings" style="margin-top:10px; padding: 10px; border: 1px solid #eee; border-radius: 4px;">
                        <label class="settings-label" style="font-size: 0.9em;">SMTP服务配置 (可选, 优先使用)</label>
                        <input type="text" id="smtpServer" placeholder="SMTP服务器地址" value="${GM_getValue('smtpServer', '')}">
                        <input type="text" id="smtpUser" placeholder="邮箱用户名" value="${GM_getValue('smtpUser', '')}">
                        <input type="password" id="smtpPassword" placeholder="邮箱密码或授权码" value="${GM_getValue('smtpPassword', '')}">
                        <div style="display:flex; gap:10px; margin-top:5px; align-items: center;">
                            <input type="number" id="smtpPort" placeholder="端口" value="${GM_getValue('smtpPort', '587')}" style="width:80px;">
                            <label><input type="checkbox" id="smtpSsl" ${GM_getValue('smtpSsl', true) ? 'checked' : ''}> 使用SSL/TLS</label>
                            <button id="testEmailBtn" class="btn-control" style="padding: 5px 10px; font-size: 11px;">测试邮件</button>
                        </div>
                    </div>
                    <p class="settings-help">找到有效链接时发送通知。需配合服务器URL或填写SMTP配置。</p>
                </div>
                 <div class="settings-row section">
                    <label class="settings-label">通知方式</label>
                    <div class="notification-controls" style="margin:10px 0;padding:10px;background:#f8f8f8;border-radius:5px;border:1px solid #ddd;">
                        <div><label><input type="checkbox" id="enableSound" ${currentNotificationSettings.enableSound ? 'checked' : ''}> 声音通知</label></div>
                        <div><label><input type="checkbox" id="enableMobilePopup" ${currentNotificationSettings.enableMobilePopup ? 'checked' : ''}> 移动端页内弹窗</label></div>
                        <div><label><input type="checkbox" id="enableDesktopNotif" ${currentNotificationSettings.enableDesktop ? 'checked' : ''}> 桌面通知 (电脑)</label></div>
                        <div><label><input type="checkbox" id="enableEmailNotif" ${currentNotificationSettings.enableEmail ? 'checked' : ''}> 邮件通知 (需配置邮箱)</label></div>
                        <div class="notification-test-btns" style="display:flex;gap:5px;margin-top:10px;">
                            <button id="testNotifBtn" class="btn-control" style="padding:3px 8px;font-size:11px;">测试通知</button>
                            <button id="requestPermissionBtn" class="btn-control" style="padding:3px 8px;font-size:11px;">请求桌面通知权限</button>
                        </div>
                    </div>
                </div>
                <div class="section">
                    <button id="saveSettingsBtn" class="btn-test">保存全部设置</button>
                </div>
            </div>

            <div id="status" class="status">状态: 初始化中...</div>
        `;

        document.body.appendChild(panel);

        // --- 动态内容和事件绑定 ---
        updateAttemptsList();
        updateStats();
        updateCombinations(); // 初始化批量预览
        addBatchTools(); // 添加批量工具按钮

        // 标签切换
        panel.querySelectorAll('.tab').forEach(tab => {
            tab.addEventListener('click', function() {
                panel.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
                panel.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
                this.classList.add('active');
                panel.querySelector(`#${this.dataset.tab}-tab`).classList.add('active');
            });
        });

        // 按钮事件绑定
        panel.querySelector('#closeBtn').addEventListener('click', hidePanel);
        panel.querySelector('#applyUpgradeBtn').addEventListener('click', () => applyCode('upgrade'));
        panel.querySelector('#applySupplementaryBtn').addEventListener('click', () => applyCode('supplementary'));
        panel.querySelector('#applyAllBtn').addEventListener('click', () => applyCode('both'));
        panel.querySelector('#toggleRefreshBtn').addEventListener('click', toggleAutoRefresh);
        panel.querySelector('#clearCacheBtn').addEventListener('click', clearAllCache);
        panel.querySelector('#exportBtn').addEventListener('click', exportAttempts);
        panel.querySelector('#exportServerBtn').addEventListener('click', exportToServer);
        panel.querySelector('#startBatchBtn').addEventListener('click', startBatchTest);
        panel.querySelector('#stopBatchBtn').addEventListener('click', stopBatchTest);
        panel.querySelector('#clearResultsBtn').addEventListener('click', clearResults);
        panel.querySelector('#saveSettingsBtn').addEventListener('click', saveSettings); // 主保存按钮
        panel.querySelector('#generateScriptBtn').addEventListener('click', generateScriptSettings);
        panel.querySelector('#testEmailBtn').addEventListener('click', testEmailNotification);
        panel.querySelector('#syncResultsBtn').addEventListener('click', () => { // **新增**
            updateStatus('正在手动同步结果...');
            syncTestResultsFromOtherWindows();
        });

        // 批量输入和选项更改时更新预览
        panel.querySelector('#batchCodes').addEventListener('input', updateCombinations);
        panel.querySelector('#skipExistingCheck').addEventListener('change', function() {
            skipExisting = this.checked;
            GM_setValue('skipExisting', skipExisting); // 保存设置
            updateCombinations();
        });
         panel.querySelectorAll('input[name="linkType"]').forEach(radio => {
             radio.addEventListener('change', updateCombinations); // 类型改变也更新预览
         });

        // **新增调试事件绑定**
        panel.querySelector('#enableDebug').addEventListener('change', function() {
            AMEX_DEBUG = this.checked;
            GM_setValue('AMEX_DEBUG', AMEX_DEBUG);
            updateStatus('调试模式: ' + (AMEX_DEBUG ? '已启用' : '已禁用'));
            console.log('[AMEX Helper] Debug mode:', AMEX_DEBUG ? 'Enabled' : 'Disabled');
        });
        panel.querySelector('#checkLocalStorageBtn').addEventListener('click', showLocalStorageModal);

         // 通知设置事件绑定
         panel.querySelector('#testNotifBtn').addEventListener('click', () => {
             triggerNotifications('AMEX通知测试', '这是一条测试通知消息', true);
             updateStatus('已发送测试通知');
         });
         panel.querySelector('#requestPermissionBtn').addEventListener('click', () => {
             if ("Notification" in window) {
                 Notification.requestPermission().then(permission => {
                     updateStatus(`桌面通知权限: ${permission}`);
                 });
             } else {
                 updateStatus('此浏览器不支持桌面通知');
             }
         });
         // **修改:通知复选框改变时也调用 saveSettings**
         panel.querySelectorAll('.notification-controls input[type="checkbox"]').forEach(checkbox => {
             checkbox.addEventListener('change', saveSettings); // 每次更改都保存所有设置
         });


        // 默认启动自动刷新 (如果之前是启动状态)
        if (GM_getValue('autoRefreshEnabled', false)) { // 读取保存的状态
             // 延迟启动,避免影响页面加载
             setTimeout(() => {
                  if (!refreshTimerId) { // 检查是否已启动
                      toggleAutoRefresh();
                  }
             }, 2000);
        } else {
             // 更新按钮状态为“启动”
             const btn = panel.querySelector('#toggleRefreshBtn');
             if(btn) {
                  btn.textContent = '启动自动刷新';
                  btn.classList.remove('btn-danger');
                  btn.classList.add('btn-control');
             }
        }

        // 检测当前页面结果
        checkCurrentPageForResults();

        updateStatus('就绪'); // 初始化完成
    }

    // **新增:显示LocalStorage内容的模态框**
    function showLocalStorageModal() {
        closeVerificationModal(); // 关闭其他模态框

        const overlay = document.createElement('div');
        overlay.className = 'amex-helper-overlay';
        overlay.id = 'storage-modal-overlay';

        const modal = document.createElement('div');
        modal.className = 'amex-helper modal';
        modal.style.width = '600px'; // 稍宽一点
        modal.style.maxWidth = '95vw';

        const testResults = JSON.parse(localStorage.getItem('amex_test_results') || '[]');
        if (AMEX_DEBUG) console.table(testResults); // 在控制台也输出表格

        let resultsHTML = '<p>LocalStorage 中没有缓存的测试结果。</p>';
        if (testResults.length > 0) {
            resultsHTML = `
                <table class="debug-storage-table">
                    <thead>
                        <tr>
                            <th>时间戳</th>
                            <th>WOC码</th>
                            <th>链接类型</th>
                            <th>结果</th>
                            <th>原因</th>
                            <th>URL</th>
                        </tr>
                    </thead>
                    <tbody>
            `;
            // 只显示最近 N 条
            testResults.slice(-50).reverse().forEach(r => {
                resultsHTML += `
                    <tr>
                        <td>${new Date(r.timestamp).toLocaleString()}</td>
                        <td>${r.wocCode || '-'}</td>
                        <td>${r.linkType || '-'}</td>
                        <td>${r.isValid ? '✅ 有效' : '❌ 无效'}</td>
                        <td>${r.reason || '-'}</td>
                        <td style="word-break: break-all;">${r.url || '-'}</td>
                    </tr>`;
            });
            resultsHTML += '</tbody></table>';
        }

        modal.innerHTML = `
            <div class="modal-content">
                <button class="close-btn" id="closeStorageModalBtn" title="关闭">&times;</button>
                <h3 class="modal-title">LocalStorage 同步缓存 (${testResults.length}项)</h3>
                <div style="max-height: 400px; overflow-y: auto;">
                    ${resultsHTML}
                </div>
                <div style="text-align: center; margin-top: 15px;">
                     <button id="clearStorageBtn" class="btn-danger">清除LocalStorage缓存</button>
                </div>
            </div>
        `;
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // 关闭按钮
        const closeBtn = document.getElementById('closeStorageModalBtn');
        const closeOverlay = () => {
             if(overlay.parentNode) overlay.remove();
        };
        closeBtn.addEventListener('click', closeOverlay);
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) closeOverlay();
        });
         // 清除按钮
         document.getElementById('clearStorageBtn').addEventListener('click', () => {
              if(confirm('确定要清除LocalStorage中的同步缓存吗?这不会影响已保存的测试结果列表。')) {
                   clearLocalStorageResults();
                   closeOverlay(); // 关闭模态框
              }
         });

        updateStatus(`LocalStorage缓存已在弹窗和控制台显示: ${testResults.length} 项`);
    }


    // 清除所有缓存数据 (修正版,确保恢复时也恢复统计)
    function clearAllCache() {
        if(confirm('确定要清除所有本地存储的测试结果和统计数据吗?此操作不可恢复。')) {
            const oldAttempts = [...attempts]; // 深拷贝备份
            const oldStats = {...stats};     // 深拷贝备份

            attempts = [];
            stats = { totalTested: 0, verified: 0, rejected: 0 }; // 重置统计

            GM_setValue('attempts', attempts);
            GM_setValue('stats', stats);
            clearLocalStorageResults(); // 同时清除localStorage缓存

            updateAttemptsList();
            updateStats();
            updateStatus('已清除所有测试结果和统计数据');

            showRestoreOptionWithStats(oldAttempts, oldStats); // 显示恢复选项
        }
    }

    // 显示恢复选项 (修正版,确保恢复统计)
    function showRestoreOptionWithStats(oldData, oldStatsData) { // 参数名修改避免冲突
        const restoreBar = document.createElement('div');
        // ... (样式设置不变) ...
        restoreBar.style.position = 'fixed';
        restoreBar.style.bottom = '10px';
        restoreBar.style.left = '50%';
        restoreBar.style.transform = 'translateX(-50%)';
        restoreBar.style.backgroundColor = '#ffc107';
        restoreBar.style.color = 'black';
        restoreBar.style.padding = '10px 15px';
        restoreBar.style.borderRadius = '5px';
        restoreBar.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
        restoreBar.style.zIndex = '10000';
        restoreBar.id = 'amex-restore-bar';

        restoreBar.innerHTML = `
            数据已清除!
            <button id="restoreDataBtn" style="background:#28a745;color:white;border:none;padding:3px 8px;border-radius:3px;margin:0 10px;cursor:pointer;">恢复数据</button>
            <button id="dismissRestoreBtn" style="background:#6c757d;color:white;border:none;padding:3px 8px;border-radius:3px;cursor:pointer;">忽略</button>
        `;
        document.body.appendChild(restoreBar);

        document.getElementById('restoreDataBtn').addEventListener('click', () => {
            attempts = oldData;
            stats = oldStatsData; // **修正:恢复传入的统计数据**
            GM_setValue('attempts', attempts);
            GM_setValue('stats', stats);
            updateAttemptsList();
            updateStats();
            updateStatus('数据已恢复');
            if(restoreBar.parentNode) restoreBar.remove();
        });

        document.getElementById('dismissRestoreBtn').addEventListener('click', () => {
             if(restoreBar.parentNode) restoreBar.remove();
        });

        setTimeout(() => {
             if(document.getElementById('amex-restore-bar')) {
                 document.getElementById('amex-restore-bar').remove();
             }
        }, 30000);
    }

    // --- 邮件和通知相关 (与原版类似,略作调整) ---
    function testEmailNotification() {
        const email = document.getElementById('emailNotification').value.trim();
        if (!email) {
            updateStatus('请先在设置中输入接收通知的邮箱地址');
            return;
        }
        const testInfo = { offerCode: 'TEST-OFFER', wocCode: 'TESTWOC', linkType: '测试链接', url: 'https://www.americanexpress.com/test', reason: '测试邮件通知功能' };
        sendEmailNotification(testInfo.offerCode, testInfo.wocCode, testInfo.linkType, testInfo.reason, testInfo.url, true);
        updateStatus('正在尝试发送测试邮件...');
    }

    function tryToSendEmailNotification(wocCode, linkType, reason, url) {
         // 检查邮件通知是否启用
         if (!notificationSettings.enableEmail) return;

        const email = GM_getValue('emailNotification', '');
        if (!email) return; // 邮箱为空时不发送

        let offerCode = '';
        const offerMatch = url.match(/\/(\d{5}-\d+-\d+)-/);
        if (offerMatch && offerMatch[1]) offerCode = offerMatch[1];

        sendEmailNotification(offerCode, wocCode, linkType, reason, url);
    }

    function sendEmailNotification(offerCode, wocCode, linkType, reason, url, isTest = false) {
        const email = GM_getValue('emailNotification', '');
        if (!email) return;

        const currentServerUrl = GM_getValue('serverUrl', ''); // 使用当前设置
        const smtpServer = GM_getValue('smtpServer', '');
        const smtpUser = GM_getValue('smtpUser', '');
        const smtpPassword = GM_getValue('smtpPassword', '');
        const smtpPort = GM_getValue('smtpPort', '587');
        const smtpSsl = GM_getValue('smtpSsl', true);

        // 如果配置了SMTP,优先使用SMTP;否则尝试通过服务器URL发送
        const useSmtp = smtpServer && smtpUser && smtpPassword;
        const endpoint = useSmtp ? `${currentServerUrl || 'https://default-server.com'}/send-smtp-email` : `${currentServerUrl}/send-email`; // 假设服务器有不同端点

        if (!currentServerUrl && !useSmtp) {
             updateStatus('未设置服务器URL且未配置SMTP,无法发送邮件');
             return;
        }


        const emailData = {
            to: email,
            subject: isTest ? '测试通知 - AMEX有效链接' : `发现有效的AMEX ${linkType}`,
            body: `... (邮件内容与原版一致) ...`, // 省略重复内容
            isTest: isTest,
            smtpConfig: useSmtp ? { server: smtpServer, user: smtpUser, password: smtpPassword, port: smtpPort, ssl: smtpSsl } : null
        };
         emailData.body = `
                <h3>发现有效的AMEX ${linkType}</h3>
                <p><strong>Offer Code:</strong> ${offerCode || '未提取'}</p>
                <p><strong>WOC Code:</strong> ${wocCode || '未提取'}</p>
                <p><strong>链接类型:</strong> ${linkType}</p>
                <p><strong>识别原因:</strong> ${reason}</p>
                <p><strong>链接URL:</strong> <a href="${url}" target="_blank">${url}</a></p>
                <hr>
                <p>此邮件由AMEX Code Helper (${new Date().toLocaleString()}) 自动发送。</p>
            `;


        GM_xmlhttpRequest({
            method: 'POST',
            url: endpoint,
            data: JSON.stringify(emailData),
            headers: { 'Content-Type': 'application/json' },
            onload: function(response) {
                if (response.status >= 200 && response.status < 300) {
                    updateStatus(isTest ? '测试邮件已发送成功' : `有效链接通知邮件已发送至 ${email}`);
                } else {
                    updateStatus(`邮件发送失败: ${response.statusText}`);
                    console.error('邮件发送失败:', response.responseText);
                }
            },
            onerror: function(error) {
                updateStatus('邮件发送失败: 网络错误');
                console.error('邮件发送错误:', error);
            },
             ontimeout: function() {
                 updateStatus('邮件发送失败: 请求超时');
             }
        });
    }

    // --- 通知功能 (声音、桌面、移动端弹窗) ---
    const isMobileDevice = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

    function playNotificationSound(type = 'success') {
        if (!notificationSettings.enableSound) return;
        try {
            const AudioContext = window.AudioContext || window.webkitAudioContext;
            if (!AudioContext) return;
            const audioContext = new AudioContext();
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();
            gainNode.gain.setValueAtTime(0.2, audioContext.currentTime); // 减小音量

            if (type === 'success') {
                oscillator.type = 'sine';
                oscillator.frequency.setValueAtTime(600, audioContext.currentTime);
                oscillator.frequency.linearRampToValueAtTime(900, audioContext.currentTime + 0.15);
                gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4);
            } else if (type === 'error') {
                oscillator.type = 'square';
                oscillator.frequency.setValueAtTime(300, audioContext.currentTime);
                oscillator.frequency.linearRampToValueAtTime(150, audioContext.currentTime + 0.25);
                gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4);
            } else { // warning or default
                oscillator.type = 'triangle';
                oscillator.frequency.setValueAtTime(440, audioContext.currentTime); // A4
                gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
            }

            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);
            oscillator.start(audioContext.currentTime);
            oscillator.stop(audioContext.currentTime + 0.5);
        } catch (e) { console.error('播放通知音效失败:', e); }
    }

    function showDesktopNotification(title, message, isValid) {
        if (!notificationSettings.enableDesktop || isMobileDevice || !("Notification" in window)) return;

        const createNotification = () => {
            const notification = new Notification(title, {
                body: message,
                icon: isValid ? 'https://www.google.com/s2/favicons?domain=americanexpress.com&sz=64' : 'https://www.google.com/s2/favicons?domain=americanexpress.com&sz=64', // 使用Google获取favicon
                requireInteraction: false,
                tag: 'amex-helper-notification' // 使用标签避免重复通知
            });
            notification.onclick = () => { window.focus(); notification.close(); };
            setTimeout(() => notification.close(), 6000); // 6秒后关闭
        };

        if (Notification.permission === "granted") {
            createNotification();
        } else if (Notification.permission !== "denied") {
            Notification.requestPermission().then(permission => {
                if (permission === "granted") createNotification();
            });
        }
    }

    function showMobilePopup(message, isValid, duration = 5000) {
         if (!notificationSettings.enableMobilePopup || !isMobileDevice) return;

         // 移除旧弹窗
         const existingAlert = document.querySelector('.amex-mobile-alert');
         if (existingAlert) existingAlert.remove();

         const alert = document.createElement('div');
         // ... (样式设置与原版一致,确保 .amex-mobile-alert 样式存在) ...
         alert.className = 'amex-mobile-alert'; // 确保应用了CSS
         alert.style.position = 'fixed';
         alert.style.bottom = '-100px'; // Start off-screen
         alert.style.left = '50%';
         alert.style.transform = 'translateX(-50%)';
         alert.style.width = '90%';
         alert.style.maxWidth = '320px';
         alert.style.padding = '15px';
         alert.style.backgroundColor = isValid ? 'rgba(40, 167, 69, 0.95)' : 'rgba(220, 53, 69, 0.95)';
         alert.style.color = 'white';
         alert.style.borderRadius = '8px';
         alert.style.boxShadow = '0 4px 15px rgba(0,0,0,0.3)';
         alert.style.zIndex = '10010';
         alert.style.textAlign = 'center';
         alert.style.fontSize = '14px'; // 调整字体大小
         alert.style.fontWeight = 'bold';
         alert.style.transition = 'bottom 0.5s ease-out, opacity 0.5s ease-out'; // 添加动画
         alert.style.opacity = '0';

         const icon = isValid ? '✅' : '❌';
         alert.innerHTML = `${icon} ${message}`;
         document.body.appendChild(alert);

         // Slide in animation
         setTimeout(() => {
             alert.style.bottom = '20px';
             alert.style.opacity = '1';
         }, 50);


         alert.addEventListener('click', () => { if (alert.parentNode) alert.remove(); });

         setTimeout(() => {
             if (alert.parentNode) {
                 alert.style.opacity = '0';
                 alert.style.bottom = '-100px'; // Slide out
                 setTimeout(() => { if (alert.parentNode) alert.remove(); }, 500);
             }
         }, duration);
    }

    // 触发综合通知
    function triggerNotifications(title, message, isValid) {
        playNotificationSound(isValid ? 'success' : 'error');
        showDesktopNotification(title, message, isValid);
        if (isMobileDevice) showMobilePopup(message, isValid);
         // 尝试邮件通知 (如果启用且配置)
         // 注意:这里不直接调用 tryToSendEmailNotification,因为它需要更多上下文
         // 邮件通知应该在 checkCurrentPageForResults 中触发
    }

})(); // End of UserScript