pokespy

Help with eBay listings with TCGdex integration and PriceCharting data extraction

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         pokespy
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Help with eBay listings with TCGdex integration and PriceCharting data extraction
// @author       bobjoepie
// @match        https://www.ebay.com/*
// @match        https://www.ebay.co.uk/*
// @match        https://www.pricecharting.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ============================================================================
    // DEBUG MODE CONFIGURATION
    // ============================================================================
    const DEBUG_MODE = false; // Set to false for production/performance mode
    
    // Logging wrapper - only logs if DEBUG_MODE is true
    const debugLog = (...args) => {
        if (DEBUG_MODE) console.log(...args);
    };
    
    // Timing configuration based on mode
    const TIMING = {
        // PriceCharting data polling
        POLL_INTERVAL: DEBUG_MODE ? 500 : 200,        // How often to check for data (ms)
        POLL_MAX_ATTEMPTS: DEBUG_MODE ? 30 : 60,      // Max attempts before timeout
        
        // Button processing batches
        BATCH_PROCESSING_DELAY: DEBUG_MODE ? 100 : 50, // Delay between batches (ms)
        
        // Cache duration
        CACHE_DURATION: 60000, // 1 minute (same for both modes)
    };
    
    debugLog(`🔧 Debug mode: ${DEBUG_MODE ? 'ENABLED' : 'DISABLED'}`);
    debugLog(`⏱️ Timing config:`, TIMING);

    // Detect which site we're on
    const currentSite = window.location.hostname;
    const isEbay = currentSite.includes('ebay.com') || currentSite.includes('ebay.co.uk');
    const isPriceCharting = currentSite.includes('pricecharting.com');

    debugLog(`🔍 Script running on: ${currentSite}`);

    if (isEbay) {
        initializeEbayFunctionality();
    } else if (isPriceCharting) {
        initializePriceChartingFunctionality();
    }

    // ============================================================================
    // SHARED DATA STORAGE FUNCTIONS (using Tampermonkey's cross-site storage)
    // ============================================================================

    // Store card search data for cross-site access
    function storePriceChartingRequest(cardData) {
        const timestamp = Date.now();
        const key = `pc_request_${timestamp}`;
        GM_setValue(key, {
            ...cardData,
            timestamp: timestamp,
            source: 'ebay'
        });
        debugLog(`💾 Stored PriceCharting request:`, cardData);
        return key;
    }

    // Store extracted PriceCharting data
    function storePriceChartingData(cardKey, priceData) {
        GM_setValue(`${cardKey}_data`, {
            ...priceData,
            timestamp: Date.now(),
            source: 'pricecharting'
        });
        debugLog(`💾 Stored PriceCharting data for ${cardKey}:`, priceData);
    }

    // Get stored data
    function getStoredData(key) {
        return GM_getValue(key, null);
    }

    // Store listing display cache (persists across page reloads)
    // Only store raw data, not HTML - we'll reconstruct the display each time
    function storeListingDisplayCache(listingUrl, displayData) {
        const cacheKey = `listing_cache_${btoa(listingUrl).substring(0, 50)}`;
        GM_setValue(cacheKey, {
            cardName: displayData.cardName,
            setName: displayData.setName,
            prices: displayData.prices,
            detectedGrade: displayData.detectedGrade,
            extractedCardName: displayData.extractedCardName,
            extractedSetName: displayData.extractedSetName,
            extractedCardNumber: displayData.extractedCardNumber,
            lastUpdated: displayData.lastUpdated,
            url: displayData.url,
            imageUrl: displayData.imageUrl,
            timestamp: Date.now(),
            originalUrl: listingUrl
        });
        debugLog(`💾 Cached listing data for: ${listingUrl}`);
    }

    // Get listing display cache
    function getListingDisplayCache(listingUrl) {
        const cacheKey = `listing_cache_${btoa(listingUrl).substring(0, 50)}`;
        const cached = GM_getValue(cacheKey, null);
        
        if (cached) {
            const age = Date.now() - cached.timestamp;
            const ageMinutes = (age / 1000 / 60).toFixed(1);
            debugLog(`🔍 Cache found for key: ${cacheKey.substring(0, 30)}... (age: ${ageMinutes} min)`);
            
            // Check if cache is still valid (30 minutes)
            if (age < (30 * 60 * 1000)) {
                debugLog(`✅ Cache is valid (< 30 min)`);
                return cached;
            } else {
                debugLog(`❌ Cache expired (> 30 min)`);
            }
        }
        
        return null;
    }

    // Clean up old data (older than 1 hour for requests, 30 minutes for listing caches)
    function cleanupOldData() {
        const keys = GM_listValues();
        const oneHourAgo = Date.now() - (60 * 60 * 1000);
        const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);

        keys.forEach(key => {
            if (key.startsWith('pc_request_')) {
                const data = GM_getValue(key);
                if (data && data.timestamp < oneHourAgo) {
                    GM_deleteValue(key);
                    GM_deleteValue(`${key}_data`);
                }
            } else if (key.startsWith('listing_cache_')) {
                const data = GM_getValue(key);
                if (data && data.timestamp < thirtyMinutesAgo) {
                    GM_deleteValue(key);
                }
            }
        });
    }

    // ============================================================================
    // POPUP PERMISSION HELPER
    // ============================================================================

    function checkPopupPermissions() {
        // Check if user has been notified before
        const hasBeenNotified = GM_getValue('popup_permission_notified', false);
        
        if (!hasBeenNotified) {
            // Show notification on first use
            showPopupPermissionNotification();
            GM_setValue('popup_permission_notified', true);
        }
    }

    function showPopupPermissionNotification() {
        const notification = document.createElement('div');
        notification.id = 'pokespy-popup-notification';
        notification.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 24px 32px;
            border-radius: 12px;
            z-index: 100000;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            font-size: 14px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
            max-width: 500px;
            border: 2px solid #5a67d8;
        `;

        notification.innerHTML = `
            <div style="font-size: 18px; font-weight: bold; margin-bottom: 12px; text-align: center;">
                🚀 PokeSpy Setup Required
            </div>
            <div style="line-height: 1.6; margin-bottom: 16px;">
                <p style="margin: 0 0 12px 0;">
                    PokeSpy needs to open PriceCharting.com in popup windows to fetch card prices automatically.
                </p>
                <p style="margin: 0 0 12px 0; font-weight: bold;">
                    📌 Please allow popups for eBay in your browser settings.
                </p>
                <p style="margin: 0; font-size: 12px; opacity: 0.9;">
                    The popups will close automatically after fetching data (usually within 1-2 seconds).
                </p>
            </div>
            <div style="display: flex; gap: 12px; justify-content: center;">
                <button id="pokespy-popup-understood" style="
                    padding: 10px 24px;
                    background: #43b581;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    transition: all 0.2s;
                ">Got it!</button>
                <button id="pokespy-popup-help" style="
                    padding: 10px 24px;
                    background: rgba(255,255,255,0.2);
                    color: white;
                    border: 1px solid white;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    transition: all 0.2s;
                ">Show Me How</button>
            </div>
        `;

        document.body.appendChild(notification);

        // Got it button
        document.getElementById('pokespy-popup-understood').addEventListener('click', () => {
            notification.remove();
        });

        // Help button
        document.getElementById('pokespy-popup-help').addEventListener('click', () => {
            notification.innerHTML = `
                <div style="font-size: 18px; font-weight: bold; margin-bottom: 12px; text-align: center;">
                    📖 How to Allow Popups
                </div>
                <div style="line-height: 1.6; margin-bottom: 16px; text-align: left;">
                    <p style="margin: 0 0 8px 0; font-weight: bold;">Chrome / Edge:</p>
                    <ol style="margin: 0 0 12px 0; padding-left: 20px;">
                        <li>Click the popup blocked icon <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">🚫</span> in the address bar</li>
                        <li>Select "Always allow popups from [ebay.com]"</li>
                        <li>Click "Done"</li>
                    </ol>
                    
                    <p style="margin: 12px 0 8px 0; font-weight: bold;">Firefox:</p>
                    <ol style="margin: 0 0 12px 0; padding-left: 20px;">
                        <li>Click the popup blocked icon in the address bar</li>
                        <li>Click "Preferences" → "Allow popups for ebay.com"</li>
                    </ol>

                    <p style="margin: 12px 0 0 0; font-size: 12px; opacity: 0.9;">
                        💡 You only need to do this once!
                    </p>
                </div>
                <div style="text-align: center;">
                    <button id="pokespy-popup-close" style="
                        padding: 10px 24px;
                        background: #43b581;
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                    ">Close</button>
                </div>
            `;

            document.getElementById('pokespy-popup-close').addEventListener('click', () => {
                notification.remove();
            });
        });

        // Add hover effects
        const buttons = notification.querySelectorAll('button');
        buttons.forEach(btn => {
            btn.addEventListener('mouseenter', () => {
                btn.style.transform = 'translateY(-2px)';
                btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
            });
            btn.addEventListener('mouseleave', () => {
                btn.style.transform = 'translateY(0)';
                btn.style.boxShadow = 'none';
            });
        });
    }

    function showPopupBlockedWarning(listingElement) {
        // Show a small warning badge on the button
        const pcButton = listingElement?.querySelector('.pricecharting-direct-btn');
        if (pcButton) {
            pcButton.style.background = '#e74c3c';
            pcButton.title = '❌ Popup blocked! Please allow popups for eBay to use this feature.';
            pcButton.textContent = '🚫 Blocked';
        }

        // Check if we should show the full notification (only once per session)
        const hasShownWarning = sessionStorage.getItem('pokespy_popup_warning_shown');
        if (!hasShownWarning) {
            sessionStorage.setItem('pokespy_popup_warning_shown', 'true');
            
            const warning = document.createElement('div');
            warning.style.cssText = `
                position: fixed;
                top: 80px;
                right: 20px;
                background: #e74c3c;
                color: white;
                padding: 16px 20px;
                border-radius: 8px;
                z-index: 99999;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 13px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                max-width: 350px;
                animation: slideIn 0.3s ease-out;
            `;

            warning.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 8px; font-size: 15px;">
                    🚫 Popup Blocked!
                </div>
                <div style="line-height: 1.4; margin-bottom: 12px;">
                    PokeSpy needs to open popups to fetch prices. Please allow popups for eBay.
                </div>
                <button id="pokespy-warning-ok" style="
                    padding: 6px 16px;
                    background: white;
                    color: #e74c3c;
                    border: none;
                    border-radius: 4px;
                    font-size: 12px;
                    font-weight: bold;
                    cursor: pointer;
                ">OK</button>
            `;

            // Add CSS animation
            const style = document.createElement('style');
            style.textContent = `
                @keyframes slideIn {
                    from { transform: translateX(400px); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }
            `;
            document.head.appendChild(style);

            document.body.appendChild(warning);

            document.getElementById('pokespy-warning-ok').addEventListener('click', () => {
                warning.style.animation = 'slideIn 0.3s ease-out reverse';
                setTimeout(() => warning.remove(), 300);
            });

            // Auto-remove after 10 seconds
            setTimeout(() => {
                if (warning.parentNode) {
                    warning.style.animation = 'slideIn 0.3s ease-out reverse';
                    setTimeout(() => warning.remove(), 300);
                }
            }, 10000);
        }
    }

    // ============================================================================
    // EBAY FUNCTIONALITY
    // ============================================================================

    function initializeEbayFunctionality() {
        debugLog('🛒 Initializing eBay functionality...');

        // Clean up old data on startup
        cleanupOldData();

        // Check and notify about popup permissions
        checkPopupPermissions();

        // Cache for sets data
        let setsCache = null;
        let setsCacheLoaded = false;

        // Load and cache sets on startup
        async function loadSetsCache() {
            if (setsCacheLoaded) return setsCache;

            try {
                debugLog('Loading TCGdex sets cache...');
                const response = await fetch('https://api.tcgdex.net/v2/en/sets', {
                    method: 'GET',
                    headers: { 'Accept': 'application/json' }
                });

                if (response.ok) {
                    setsCache = await response.json();
                    setsCacheLoaded = true;
                    debugLog(`✓ Loaded ${Array.isArray(setsCache) ? setsCache.length : 'unknown number of'} sets into cache`);
                    return setsCache;
                } else {
                    console.warn(`Failed to load sets cache: ${response.status} ${response.statusText}`);
                    setsCache = [];
                    setsCacheLoaded = true;
                    return setsCache;
                }
            } catch (error) {
                console.warn('Error loading sets cache:', error);
                setsCache = [];
                setsCacheLoaded = true;
                return setsCache;
            }
        }

        // Find sets by card count from cache
        function findSetsByCardCount(cardCount) {
            if (!setsCache || !Array.isArray(setsCache)) {
                return [];
            }

            const matchingSets = [];
            // Convert to number to handle leading zeros (078 becomes 78)
            const targetCount = parseInt(cardCount, 10);

            for (let i = 0; i < setsCache.length; i++) {
                const set = setsCache[i];
                const official = set.cardCount?.official || set.cardCount?.total;
                if (official && parseInt(official, 10) === targetCount) {
                    matchingSets.push(set);
                }
            }

            return matchingSets;
        }

        // Listen for manual price setting messages from popup windows
        window.addEventListener('message', (event) => {
            if (event.data && event.data.type === 'POKESPY_SET_MANUAL_PRICE') {
                const { listingId, price, url } = event.data;
                debugLog(`📥 Received manual price: $${price} for listing ${listingId}`);
                
                // Find the listing element
                const listingElement = window.pokespyManualEdits?.[listingId];
                if (!listingElement) {
                    debugLog('⚠️ Could not find listing element for manual price');
                    return;
                }
                
                // Create manual price data
                const manualPriceData = {
                    cardName: 'Manual Entry',
                    setName: '',
                    prices: {
                        'Ungraded': price,
                        'PSA 9': null,
                        'PSA 10': null
                    },
                    detectedGrade: null,
                    extractedCardName: 'Manual',
                    extractedSetName: '',
                    imageUrl: null,
                    pcUrl: url,
                    isManual: true
                };
                
                // Update or create the price display
                updatePriceDisplay(listingElement, manualPriceData, null);
                
                // Color the eBay price
                colorEbayPriceBasedOnComparison(listingElement, manualPriceData, null);
                
                // Store in cache with manual flag
                const listingUrl = listingElement.querySelector('a.s-item__link')?.href || '';
                if (listingUrl) {
                    storeListingDisplayCache(listingUrl, {
                        ...manualPriceData,
                        timestamp: Date.now()
                    });
                }
                
                debugLog(`✅ Applied manual price: $${price}`);
            }
        });

        // Card number patterns - easy to add new ones
        const CARD_PATTERNS = [
            {
                name: 'numeric',
                regex: /\b(\d{1,4})\/(\d{1,4})\b/,
                description: 'Numeric pattern like 108/106'
            },
            {
                name: 'number-letter',
                regex: /\b(\d{1,4}[a-zA-Z])\/(\d{1,4})\b/,
                description: 'Number-letter pattern like 177a/168'
            },
            {
                name: 'letter-number',
                regex: /\b([A-Za-z]{1,4}\d{1,4})\/([A-Za-z]{1,4}\d{1,4})\b/i,
                description: 'Letter-number pattern like GG69/GG70 or Tg20/Tg30'
            },
            {
                name: 'hash-number',
                regex: /\#(\d{1,4})\b/,
                description: 'Hash pattern like #238'
            },
            {
                name: 'single-letter-number',
                regex: /\b([A-Za-z]{1,4})(\d{1,3})\b/i,
                description: 'Single letter-number pattern like SV107, RC5, DP45, SM158, SWSH184, TG03, XY121'
            },
            {
                name: 'hash-letter-number',
                regex: /\#([A-Za-z]{1,4})(\d{1,3})\b/i,
                description: 'Hash letter-number pattern like #SV107, #RC5'
            },
            {
                name: 'standalone-number',
                regex: /\b(\d{3}|\d{2}|\d{1})\b/,
                description: 'Standalone number like "164" (last resort - prefers 3 digits, then 2, then 1)',
                exclude: /\b(?:PSA|BGS|CGC|SGC)\s+(\d{1,2}(?:\.\d)?)\b/i // Exclude grade numbers like "PSA 10", "BGS 9.5"
            }
        ];

        // Extract title and card numbers from eBay listings
        function extractListingInfo(listingElement) {
            const info = {
                title: null,
                cardNumber: null,
                setNumber: null,
                fullCardNumber: null,
                matchedPattern: null,
                setName: null, // Extracted set name from title
                pokemonName: null // Extracted Pokemon name (when no card number found)
            };

            // Try multiple selectors at once for better performance
            const titleElement = listingElement.querySelector('.s-card__title .su-styled-text, [role="heading"] span, .s-item__title span, h3 span, .x-item-title-label span');

            if (titleElement) {
                info.title = titleElement.textContent.trim();

                // Special case: Check for Black Star Promo patterns first
                // "Mew ex SVP 053 Pokemon TCG Scarlet Violet 151 Black Star Promo" -> SVP = Scarlet & Violet Promo
                // "SWSH BLACK STAR PROMO ARCEUS V #204" -> SWSH Black Star Promo
                // Check if title contains "BLACK STAR PROMO" or just "PROMO" anywhere
                const hasPromo = /BLACK\s+STAR\s+PROMO|PROMO/i.test(info.title);
                if (hasPromo) {
                    // Look for promo prefix: SVP, SWSH, SM, XY, BW, DP, HGSS (check most specific first, EX last)
                    // Order matters! Check SVP before SM, SWSH before SM, etc.
                    const prefixMatch = info.title.match(/\b(SVP|SWSH|HGSS|XY|SM|BW|DP)\b/i) || 
                                       info.title.match(/\b(EX)\s+(?:BLACK\s+STAR\s+)?PROMO/i); // Only match EX if followed by PROMO
                    if (prefixMatch) {
                        const promoPrefix = prefixMatch[1].toUpperCase();
                        const promoSetNames = {
                            'SVP': 'SVP Black Star Promos',
                            'SWSH': 'SWSH Black Star Promos',
                            'SM': 'SM Black Star Promos',
                            'XY': 'XY Black Star Promos',
                            'BW': 'BW Black Star Promos',
                            'DP': 'DP Black Star Promos',
                            'HGSS': 'HGSS Black Star Promos',
                            'EX': 'EX Black Star Promos'
                        };
                        info.setName = promoSetNames[promoPrefix] || `${promoPrefix} Black Star Promos`;
                        debugLog(`🔍 Promo detected: "${info.setName}" (found "${prefixMatch[1]}" + "PROMO" in title)`);
                    }
                }

                // Special case: Check if "151" appears as a set name (not as card number)
                // "POKEMON 151 MEW EX #163" -> 151 is set name, 163 is card number
                // "Scarlet & Violet 151 Pokémon TCG" -> 151 is set name
                // "Poliwhirl Illustration Rare 2023 Scarlet & Violet 151 Pokémon TCG PSA 10" -> 151 is set name
                // "POKEMON SCARLET VIOLET MEW 151/165" -> 151 is card number (will be caught by numeric pattern)
                const set151Match = info.title.match(/(?:Scarlet\s*&\s*Violet|Pokemon|Pokémon)\s+151(?:\W|$)/i);
                const has151AsSlash = info.title.match(/151\s*\/\s*\d+/);
                
                if (set151Match && !has151AsSlash) {
                    // "151" appears after "Scarlet & Violet" or "Pokemon" - treat as set name
                    debugLog(`🔍 Special case: "151" detected as set name (found after Scarlet & Violet/Pokemon): ${set151Match[0]}`);
                    info.setName = "151";
                    // Mark that 151 should NOT be used as a card number
                    info.skip151AsCardNumber = true;
                }

                // Try each pattern until we find a match
                for (const pattern of CARD_PATTERNS) {
                    const match = info.title.match(pattern.regex);
                    if (match) {
                        // Check if this match should be excluded (for standalone-number pattern)
                        if (pattern.exclude) {
                            const excludeMatch = info.title.match(pattern.exclude);
                            if (excludeMatch && excludeMatch[1] === match[1]) {
                                debugLog(`🔍 Skipping "${match[1]}" - matched exclude pattern (grade number)`);
                                continue; // Skip this pattern, try next one
                            }
                        }
                        
                        // Skip if this is "151" standalone number and we've already identified it as a set name
                        if (pattern.name === 'standalone-number' && match[1] === '151' && info.skip151AsCardNumber) {
                            debugLog(`🔍 Skipping "151" as card number - already identified as set name`);
                            continue; // Skip this pattern, try next one
                        }
                        
                        if (pattern.name === 'single-letter-number' || pattern.name === 'hash-letter-number') {
                            // Special handling for single letter-number patterns: complete localId format
                            info.cardNumber = match[1] + match[2]; // "SV" + "107" = "SV107", "RC" + "5" = "RC5", "SM" + "241" = "SM241", etc.
                            info.setNumber = null; // No set number for these patterns
                            info.fullCardNumber = match[1] + match[2]; // "SV107", "RC5", "DP45", "SM241", "SWSH184", etc.
                        } else if (pattern.name === 'hash-number') {
                            // Special handling for hash patterns: #238 format
                            info.cardNumber = match[1]; // Just the number: "238"
                            info.setNumber = null; // No set number for hash patterns
                            info.fullCardNumber = match[0]; // Full match: "#238"
                            debugLog(`🔍 Hash-number pattern detected: ${match[0]}`);
                            
                            // Try to extract set name from title - check if already extracted "151" as set name
                            if (!info.setName) {
                                // Try to extract set name - special handling for PROMO sets
                                // For promos: "SWSH BLACK STAR PROMO ARCEUS V #204" -> "SWSH BLACK STAR PROMO"
                                // For sets: "SHROUDED FABLE KINGDRA EX #131" -> "SHROUDED FABLE"
                                let setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*((?:SWSH|SM|XY|SV|BW|DP|HGSS|EX)?\s*BLACK\s+STAR\s+PROMO(?:S)?)/i);
                                if (!setNameMatch) {
                                    // Extract 1-3 words after POKEMON/TCG (typical set names are 1-3 words)
                                    // "POKEMON SHROUDED FABLE KINGDRA" -> match "SHROUDED FABLE" (2 words)
                                    setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*([A-Z][A-Za-z&-]+(?:\s+[A-Z&][A-Za-z&-]+){0,2})/i);
                                }
                                if (setNameMatch) {
                                    info.setName = setNameMatch[1].trim();
                                    debugLog(`🔍 Extracted set name from title: "${info.setName}"`);
                                }
                            } else {
                                debugLog(`🔍 Using pre-extracted set name: "${info.setName}"`);
                            }
                        } else if (pattern.name === 'standalone-number') {
                            // Special handling for standalone numbers: "164 Secret PSA" format
                            info.cardNumber = match[1]; // Just the number: "164"
                            info.setNumber = null; // No set number for standalone patterns
                            info.fullCardNumber = match[1]; // Just the number
                            debugLog(`🔍 Standalone number detected: ${match[1]}`);
                            
                            // Try to extract set name from title - check if already extracted "151" as set name
                            if (!info.setName) {
                                // Try to extract set name - special handling for PROMO sets
                                // For promos: "SWSH BLACK STAR PROMO ARCEUS V #204" -> "SWSH BLACK STAR PROMO"
                                // For sets: "SHROUDED FABLE KINGDRA EX #131" -> "SHROUDED FABLE"
                                let setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*((?:SWSH|SM|XY|SV|BW|DP|HGSS|EX)?\s*BLACK\s+STAR\s+PROMO(?:S)?)/i);
                                if (!setNameMatch) {
                                    // Extract 1-3 words after POKEMON/TCG (typical set names are 1-3 words)
                                    // "POKEMON SHROUDED FABLE KINGDRA" -> match "SHROUDED FABLE" (2 words)
                                    setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*([A-Z][A-Za-z&-]+(?:\s+[A-Z&][A-Za-z&-]+){0,2})/i);
                                }
                                if (setNameMatch) {
                                    info.setName = setNameMatch[1].trim();
                                    debugLog(`🔍 Extracted set name from title: "${info.setName}"`);
                                }
                            } else {
                                debugLog(`🔍 Using pre-extracted set name: "${info.setName}"`);
                            }
                        } else if (pattern.name === 'letter-number') {
                            // Special handling for letter-number slash patterns: GG44/GG70, RC24/RC25 format
                            info.cardNumber = match[1]; // Full card identifier: "GG44", "RC24", "Tg20"
                            // Check if the second part is also a letter-number combination
                            if (/^[A-Za-z]+\d+$/i.test(match[2])) {
                                // Both parts are letter-number (like GG44/GG70, Tg20/Tg30) - treat as localId search
                                info.setNumber = null;
                                debugLog(`🔍 Both parts are letter-number format: ${match[1]}/${match[2]} - using localId search`);
                            } else {
                                // Second part is numeric (like RC24/25) - extract set number
                                const setTotalMatch = match[2].match(/(\d+)$/);
                                info.setNumber = setTotalMatch ? setTotalMatch[1] : match[2];
                            }
                            info.fullCardNumber = match[0]; // Full match: "GG44/GG70", "RC24/RC25", "Tg20/Tg30"
                        } else {
                            // Standard handling for slash patterns: 108/106 format
                            info.cardNumber = match[1]; // First capture group
                            info.setNumber = match[2];   // Second capture group
                            info.fullCardNumber = match[0]; // Full match
                        }
                        info.matchedPattern = pattern.name; // Track which pattern matched
                        break; // Stop after first match
                    }
                }
                
                // Always try to extract set name for potential fallback (even if card number was found)
                if (info.title && !info.setName) {
                    debugLog(`🔍 Extracting set name from title for potential fallback`);
                    
                    // Common set names that might appear in titles (most specific first)
                    const setPatterns = [
                        // XY Series sets (specific names to avoid matching "EX" in Pokemon names)
                        /\b(Phantom Forces|Ancient Origins|BREAKthrough|BREAKpoint|Roaring Skies|Primal Clash)\b/i,
                        /\b(Steam Siege|Fates Collide|Generations|Evolutions|Flashfire|Furious Fists)\b/i,
                        // Sun & Moon Series
                        /\b(Sun & Moon|Burning Shadows|Crimson Invasion|Ultra Prism|Forbidden Light)\b/i,
                        /\b(Celestial Storm|Lost Thunder|Team Up|Unbroken Bonds|Unified Minds|Guardians Rising)\b/i,
                        // Sword & Shield Series
                        /\b(Cosmic Eclipse|Sword & Shield|Rebel Clash|Darkness Ablaze|Vivid Voltage)\b/i,
                        /\b(Shining Fates|Battle Styles|Chilling Reign|Evolving Skies|Fusion Strike)\b/i,
                        /\b(Brilliant Stars|Astral Radiance|Lost Origin|Silver Tempest)\b/i,
                        // Scarlet & Violet Series
                        /\b(Prismatic Evolutions?|Phantasmal Flames|Paldea Evolved|Obsidian Flames|Paradox Rift|Paldean Fates|Temporal Forces)\b/i,
                        /\b(Twilight Masquerade|Shrouded Fable|Stellar Crown|Surging Sparks|Mega Evolutions?)\b/i,
                        /\b(151)\b/i,
                        // Older series (specific names only, no generic "EX")
                        /\b(XY|Black & White|HeartGold & SoulSilver|Diamond & Pearl)\b/i,
                        // EX series sets (must have "EX" followed by set name, not just "EX")
                        /\b(EX\s+(?:Deoxys|Emerald|Unseen Forces|Delta Species|Legend Maker|Holon Phantoms|Crystal Guardians|Dragon Frontiers|Power Keepers|Team Rocket Returns|FireRed & LeafGreen|Team Magma vs Team Aqua|Hidden Legends|Ruby & Sapphire|Sandstorm))\b/i,
                    ];
                    
                    // Try to find a set name
                    for (const pattern of setPatterns) {
                        const setMatch = info.title.match(pattern);
                        if (setMatch) {
                            info.setName = setMatch[1];
                            debugLog(`  Found set name: "${info.setName}"`);
                            debugLog(`  Will match full title against card names in this set`);
                            break;
                        }
                    }
                }
            }

            return info;
        }

        // Search for a card by matching eBay title against card names in a specific set
        async function searchByTitleInSet(setNameHint, ebayTitle) {
            try {
                debugLog(`\n🔍 Matching title against cards in set: "${setNameHint}"`);
                debugLog(`  eBay title: "${ebayTitle}"`);
                
                // Normalize set name and find matching set IDs
                await loadSetsCache();
                
                const normalizedSetHint = setNameHint.toUpperCase()
                    .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                    .replace(/PROMOS?$/, 'PROMO');
                
                const matchingSets = setsCache?.filter(set => {
                    const normalizedSetName = set.name.toUpperCase()
                        .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                        .replace(/PROMOS?$/, 'PROMO');
                    return normalizedSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedSetName);
                }) || [];
                
                if (matchingSets.length === 0) {
                    debugLog(`  No matching sets found for hint: "${setNameHint}"`);
                    return null;
                }
                
                debugLog(`  Found ${matchingSets.length} matching set(s)`);
                
                // Normalize eBay title for comparison (remove common descriptors but keep Pokemon name)
                const normalizedTitle = ebayTitle.toUpperCase()
                    .replace(/POKÉMON|POKEMON|TCG/gi, '')
                    .replace(/\b\d{4}\b/g, '') // Remove years
                    .replace(/\b(?:PSA|BGS|CGC|SGC)\s+\d+(?:\.\d)?\b/gi, '') // Remove grades
                    .replace(/\b(?:ENGLISH|JAPANESE|KOREAN|GERMAN|FRENCH|ITALIAN|SPANISH)\b/gi, '') // Remove languages
                    .replace(/[^A-Z0-9\s]/g, ' ') // Replace special chars with spaces
                    .replace(/\s+/g, ' ')
                    .trim();
                
                debugLog(`  Normalized title: "${normalizedTitle}"`);
                
                // Try each matching set
                for (const set of matchingSets) {
                    debugLog(`\n  Fetching cards from set: ${set.name} (${set.id})`);
                    
                    const response = await fetch(`https://api.tcgdex.net/v2/en/sets/${set.id}`);
                    if (!response.ok) continue;
                    
                    const setData = await response.json();
                    const cards = setData.cards || [];
                    
                    debugLog(`    Got ${cards.length} cards from set`);
                    
                    // Score each card based on title similarity
                    const scoredCards = [];
                    
                    for (const card of cards) {
                        const cardName = (card.name || '').toUpperCase();
                        const normalizedCardName = cardName
                            .replace(/[^A-Z0-9\s]/g, ' ')
                            .replace(/\s+/g, ' ')
                            .trim();
                        
                        // Calculate how much of the card name appears in the title
                        const cardNameWords = normalizedCardName.split(' ').filter(w => w.length > 0);
                        const titleWords = normalizedTitle.split(' ').filter(w => w.length > 0);
                        
                        // Count matching words
                        let matchedWords = 0;
                        for (const cardWord of cardNameWords) {
                            if (titleWords.some(titleWord => 
                                titleWord === cardWord || 
                                titleWord.includes(cardWord) || 
                                cardWord.includes(titleWord)
                            )) {
                                matchedWords++;
                            }
                        }
                        
                        // Score is percentage of card name words found in title
                        const score = cardNameWords.length > 0 ? (matchedWords / cardNameWords.length) * 100 : 0;
                        
                        if (score >= 50) { // Only consider cards with at least 50% word match
                            scoredCards.push({ card, score, cardName });
                            debugLog(`      ${cardName} - Match: ${score.toFixed(0)}% (${matchedWords}/${cardNameWords.length} words)`);
                        }
                    }
                    
                    if (scoredCards.length === 0) {
                        debugLog(`    No good matches found in this set`);
                        continue;
                    }
                    
                    // Sort by score descending
                    scoredCards.sort((a, b) => b.score - a.score);
                    
                    // Get top matches (within 10% of best score)
                    const bestScore = scoredCards[0].score;
                    const topMatches = scoredCards.filter(sc => sc.score >= bestScore - 10);
                    
                    debugLog(`\n    Top ${topMatches.length} match(es):`);
                    topMatches.forEach((sc, i) => {
                        debugLog(`      ${i + 1}. ${sc.cardName} - ${sc.score.toFixed(0)}%`);
                    });
                    
                    // If multiple top matches, use rarity indicators and pricing to pick the best one
                    if (topMatches.length > 1) {
                        debugLog(`\n    Fetching full details for tie-breaking...`);
                        
                        // Check if title has rarity indicators - be specific to distinguish SIR from IR
                        const hasMUR = /\b(MEGA\s+ULTRA\s+RARE|MUR)\b/i.test(ebayTitle);
                        const hasSIR = /\b(SPECIAL\s+ILLUSTRATION\s+RARE|SIR)\b/i.test(ebayTitle);
                        const hasIR = /\b(ILLUSTRATION\s+RARE|IR)\b/i.test(ebayTitle);
                        const hasSpecialRarity = hasMUR || hasSIR || hasIR;
                        
                        // Determine priority - MUR > SIR > IR (if multiple mentioned)
                        // Default to SIR if no rarity specified
                        let targetRarity = 'SIR'; // Default to SIR
                        if (hasMUR) {
                            targetRarity = 'MUR';
                        } else if (hasSIR) {
                            targetRarity = 'SIR';
                        } else if (hasIR) {
                            targetRarity = 'IR';
                        }
                        
                        if (hasSpecialRarity) {
                            debugLog(`    🌟 Title indicates special rarity (${targetRarity}) - will prioritize matching card`);
                        } else {
                            debugLog(`    🌟 No rarity specified - defaulting to ${targetRarity}`);
                        }
                        
                        const fullCards = await Promise.all(
                            topMatches.map(async sc => {
                                const detailResponse = await fetch(`https://api.tcgdex.net/v2/en/cards/${sc.card.id}`);
                                if (!detailResponse.ok) return null;
                                const fullCard = await detailResponse.json();
                                const titleSimilarity = calculateTitleSimilarity(ebayTitle, fullCard.name);
                                
                                // Check if card rarity matches the specific type we're looking for
                                const cardRarity = (fullCard.rarity?.name || fullCard.rarity || '').toUpperCase();
                                debugLog(`      Fetched ${fullCard.name} #${fullCard.localId}`);
                                debugLog(`        Rarity object:`, fullCard.rarity);
                                debugLog(`        Rarity string: "${cardRarity}"`);
                                
                                // Determine card's rarity type and priority
                                let cardRarityType = null;
                                let cardRarityPriority = 0;
                                
                                if (cardRarity.includes('MEGA') && cardRarity.includes('ULTRA')) {
                                    cardRarityType = 'MUR';
                                    cardRarityPriority = 3; // Highest priority
                                } else if (cardRarity.includes('SPECIAL') && cardRarity.includes('ILLUSTRATION')) {
                                    cardRarityType = 'SIR';
                                    cardRarityPriority = 2;
                                } else if (cardRarity.includes('ILLUSTRATION') && !cardRarity.includes('SPECIAL')) {
                                    cardRarityType = 'IR';
                                    cardRarityPriority = 1;
                                }
                                
                                // Match rarity based on target (if specified) or use priority
                                let rarityMatch = false;
                                if (targetRarity) {
                                    // Specific rarity requested in title
                                    rarityMatch = (cardRarityType === targetRarity);
                                } else if (cardRarityType) {
                                    // No specific rarity in title, but card has special rarity
                                    rarityMatch = true; // All special rarities match when not specified
                                }
                                
                                debugLog(`        Rarity type: ${cardRarityType || 'none'} (priority: ${cardRarityPriority})`);
                                debugLog(`        Rarity match: ${rarityMatch} (target: ${targetRarity || 'any'})`);
                                
                                return { 
                                    card: fullCard, 
                                    similarity: titleSimilarity,
                                    rarity: cardRarity,
                                    rarityType: cardRarityType,
                                    rarityPriority: cardRarityPriority,
                                    rarityMatch: rarityMatch
                                };
                            })
                        );
                        
                        const validCards = fullCards.filter(fc => fc !== null);
                        if (validCards.length > 0) {
                            // Always prefer cards with matching rarity (including default SIR)
                            const specialCards = validCards.filter(fc => fc.rarityMatch);
                            if (specialCards.length > 0) {
                                debugLog(`    Found ${specialCards.length} card(s) matching target rarity (${targetRarity}):`);
                                specialCards.forEach(fc => {
                                    debugLog(`      ${fc.card.name} - ${fc.rarity} (priority ${fc.rarityPriority})`);
                                });
                                
                                // Sort by rarity priority first (MUR > SIR > IR), then by similarity
                                specialCards.sort((a, b) => 
                                    (b.rarityPriority - a.rarityPriority) || (b.similarity - a.similarity)
                                );
                                const best = specialCards[0];
                                debugLog(`    🎯 BEST MATCH (${best.rarityType || 'rarity'} priority): ${best.card.name} - ${best.rarity} (${best.similarity.toFixed(1)}%)`);
                                return best.card;
                            }
                            
                            // Otherwise, sort by similarity
                            validCards.sort((a, b) => b.similarity - a.similarity);
                            const best = validCards[0];
                            debugLog(`    🎯 BEST MATCH: ${best.card.name} - ${best.rarity} (${best.similarity.toFixed(1)}%)`);
                            return best.card;
                        }
                    }
                    
                    // Single best match - fetch full details
                    const bestMatch = topMatches[0];
                    debugLog(`    Fetching full details for: ${bestMatch.cardName}`);
                    const detailResponse = await fetch(`https://api.tcgdex.net/v2/en/cards/${bestMatch.card.id}`);
                    if (!detailResponse.ok) continue;
                    
                    const fullCard = await detailResponse.json();
                    debugLog(`    ✅ Found card: ${fullCard.name} #${fullCard.localId}`);
                    return fullCard;
                }
                
                debugLog(`  ⚠ No matching cards found in any set`);
                return null;
                
            } catch (error) {
                console.error('Error in searchByTitleInSet:', error);
                return null;
            }
        }

        // Try to fetch cards directly from a specific set
        async function tryDirectSetSearch(cardNumber, setNameHint, ebayTitle = '') {
            try {
                // Normalize set name and find matching set IDs
                await loadSetsCache();
                
                const normalizedSetHint = setNameHint.toUpperCase()
                    .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                    .replace(/PROMOS?$/, 'PROMO');
                
                const matchingSets = setsCache?.filter(set => {
                    const normalizedSetName = set.name.toUpperCase()
                        .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                        .replace(/PROMOS?$/, 'PROMO');
                    return normalizedSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedSetName);
                }) || [];
                
                if (matchingSets.length === 0) {
                    debugLog(`  No matching sets found for hint: "${setNameHint}"`);
                    return null;
                }
                
                debugLog(`  🔍 Trying direct set search in: ${matchingSets.map(s => s.name).join(', ')}`);
                
                // Try each matching set
                for (const set of matchingSets) {
                    try {
                        const setUrl = `https://api.tcgdex.net/v2/en/sets/${set.id}`;
                        debugLog(`    Fetching all cards from set ${set.id}: ${setUrl}`);
                        
                        const response = await fetch(setUrl);
                        if (!response.ok) continue;
                        
                        const setData = await response.json();
                        const cards = setData.cards || [];
                        
                        debugLog(`    ✓ Loaded ${cards.length} cards from ${set.name}`);
                        
                        // Find card with matching localId
                        const matchingCards = cards.filter(card => 
                            card.localId === cardNumber || 
                            card.localId === cardNumber.toUpperCase() ||
                            parseInt(card.localId, 10) === parseInt(cardNumber, 10)
                        );
                        
                        if (matchingCards.length > 0) {
                            debugLog(`    ✓ Found ${matchingCards.length} card(s) with localId ${cardNumber} in ${set.name}`);
                            
                            // If multiple matches or we have eBay title, fetch full details for similarity matching
                            if (matchingCards.length > 1 && ebayTitle) {
                                // Fetch full details for each matching card
                                const fullCards = [];
                                for (const card of matchingCards) {
                                    try {
                                        const cardDetailUrl = `https://api.tcgdex.net/v2/en/cards/${card.id}`;
                                        const detailResponse = await fetch(cardDetailUrl);
                                        if (detailResponse.ok) {
                                            const fullCard = await detailResponse.json();
                                            fullCards.push(fullCard);
                                        }
                                    } catch (err) {
                                        debugLog(`      Error fetching details for ${card.id}`);
                                    }
                                }
                                
                                // Use similarity matching to find best card
                                let bestMatch = fullCards[0];
                                let bestSimilarity = 0;
                                
                                fullCards.forEach(card => {
                                    const similarity = calculateTitleSimilarity(ebayTitle, card.name);
                                    debugLog(`      ${card.name}: ${(similarity * 100).toFixed(1)}% similarity`);
                                    if (similarity > bestSimilarity) {
                                        bestSimilarity = similarity;
                                        bestMatch = card;
                                    }
                                });
                                
                                debugLog(`    🎯 Best match: ${bestMatch.name} (${(bestSimilarity * 100).toFixed(1)}%)`);
                                return bestMatch;
                            } else {
                                // Single match or no title - fetch full details and return
                                const cardDetailUrl = `https://api.tcgdex.net/v2/en/cards/${matchingCards[0].id}`;
                                const detailResponse = await fetch(cardDetailUrl);
                                if (detailResponse.ok) {
                                    const fullCard = await detailResponse.json();
                                    debugLog(`    ✓ ${fullCard.name} from ${fullCard.set?.name}`);
                                    return fullCard;
                                }
                            }
                        }
                    } catch (setError) {
                        debugLog(`    Error fetching set ${set.id}:`, setError.message);
                    }
                }
                
                return null; // No card found in any matching set
            } catch (error) {
                debugLog(`  Error in direct set search:`, error.message);
                return null;
            }
        }

        // Search TCGdex API by localId when no set number is available
        async function searchTCGdexByLocalId(cardNumber, ebayTitle = '', setNameHint = null, skipFallback = false) {
            try {
                if (setNameHint) {
                    debugLog(`🔍 Using set name hint for filtering: "${setNameHint}"`);
                    
                    // OPTIMIZATION: If we have a strong set hint, try to fetch directly from that set first
                    // This is much faster than searching by localId and filtering
                    const directSetResult = await tryDirectSetSearch(cardNumber, setNameHint, ebayTitle);
                    if (directSetResult) {
                        debugLog(`✅ Found card via direct set search!`);
                        return directSetResult;
                    }
                    debugLog(`⚠ Direct set search didn't find card, falling back to localId search`);
                }
                
                // Create variations of the card number to handle zero-padding issues
                let cardNumberVariations;

                // For promo cards (SWSH291, SM241, etc.), don't create variations - use exactly as-is
                if (/^(SWSH|SM)[0-9]+$/i.test(cardNumber)) {
                    debugLog(`  Promo card detected: ${cardNumber} - using exact match only`);
                    cardNumberVariations = [cardNumber.toUpperCase()]; // Normalize to uppercase for API
                } else if (/^[A-Za-z]+\d+$/i.test(cardNumber)) {
                    // For letter-number patterns (RC24, GG69, TG20, etc.), normalize to uppercase
                    const normalizedCardNumber = cardNumber.toUpperCase();
                    debugLog(`  Letter-number pattern detected: ${cardNumber} - normalizing to uppercase: ${normalizedCardNumber}`);
                    cardNumberVariations = [normalizedCardNumber];
                } else {
                    // For regular numeric cards, create variations
                    const baseCardNumber = cardNumber.replace(/[a-zA-Z]+$/g, ''); // Remove trailing letters
                    cardNumberVariations = [
                        baseCardNumber, // Original: "238"
                        parseInt(baseCardNumber, 10).toString(), // Remove leading zeros: "238"
                        baseCardNumber.padStart(3, '0') // Add leading zeros: "238" -> "238"
                    ].filter(variation => variation && !isNaN(variation) && variation !== 'NaN'); // Filter out invalid variations
                }

                // Remove duplicates and filter out invalid entries
                const uniqueCardNumbers = [...new Set(cardNumberVariations)].filter(num => num && num !== 'NaN');
                debugLog(`  Card number variations to try: ${uniqueCardNumbers.join(', ')}`);

                let allFoundCards = [];

                // Try each card number variation
                for (const cardNum of uniqueCardNumbers) {
                    try {
                        const localIdUrl = `https://api.tcgdex.net/v2/en/cards?localId=${cardNum}`;
                        debugLog(`  Fetching cards with localId ${cardNum}: ${localIdUrl}`);

                        const response = await fetch(localIdUrl, {
                            method: 'GET',
                            headers: {
                                'Accept': 'application/json',
                                'Content-Type': 'application/json'
                            }
                        });

                        if (response.ok) {
                            const cards = await response.json();

                            if (Array.isArray(cards) && cards.length > 0) {
                                debugLog(`    ✓ Found ${cards.length} card(s) with localId ${cardNum}`);

                                // If we have a set name hint, filter cards by set first to reduce API calls
                                let cardsToProcess = cards;
                                if (setNameHint && cards.length > 10) {
                                    // Normalize both hint and set names for matching
                                    const normalizedSetHint = setNameHint.toUpperCase()
                                        .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                        .replace(/PROMOS?$/, 'PROMO'); // Normalize "PROMO" vs "PROMOS"
                                    debugLog(`      Filtering by set hint: "${setNameHint}" (normalized: "${normalizedSetHint}")`);
                                    
                                    // Load sets cache to get set IDs
                                    await loadSetsCache();
                                    
                                    // Find matching set IDs
                                    const matchingSets = setsCache?.filter(set => {
                                        const normalizedSetName = set.name.toUpperCase()
                                            .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                            .replace(/PROMOS?$/, 'PROMO'); // Normalize "PROMO" vs "PROMOS"
                                        return normalizedSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedSetName);
                                    }) || [];
                                    
                                    if (matchingSets.length > 0) {
                                        const matchingSetIds = matchingSets.map(s => s.id);
                                        debugLog(`      Found matching sets: ${matchingSets.map(s => s.name).join(', ')}`);
                                        const filteredCards = cards.filter(card => matchingSetIds.some(setId => card.id.startsWith(setId + '-')));
                                        
                                        if (filteredCards.length > 0) {
                                            cardsToProcess = filteredCards;
                                            debugLog(`      Filtered to ${cardsToProcess.length} card(s) from matching sets`);
                                        } else {
                                            debugLog(`      ⚠ Filtering resulted in 0 cards - ignoring set hint and using all ${cards.length} cards`);
                                            cardsToProcess = cards;
                                        }
                                    }
                                }

                                // Add cards to list for similarity matching (use basic data for speed)
                                for (const card of cardsToProcess) {
                                    if (card && card.id) {
                                        allFoundCards.push(card);
                                    }
                                }
                                
                                if (cardsToProcess.length > 0 && cardsToProcess.length <= 10) {
                                    debugLog(`      Found ${cardsToProcess.length} card(s) to compare:${cardsToProcess.map(c => ' ' + c.name).join(',')}`);
                                }
                            } else {
                                debugLog(`    ✗ No cards found with localId ${cardNum}`);
                            }
                        } else {
                            debugLog(`    ✗ LocalId ${cardNum} not found: ${response.status} ${response.statusText}`);
                        }
                    } catch (error) {
                        debugLog(`    ✗ Error fetching localId ${cardNum}:`, error);
                    }
                }

                if (allFoundCards.length > 0) {
                    debugLog(`\n✓ Found ${allFoundCards.length} total card(s) with localId ${cardNumber}`);

                    // If multiple cards found and we have an eBay title, find the best match
                    let bestMatch = allFoundCards[0]; // Default to first card

                    if (allFoundCards.length > 1 && (ebayTitle || setNameHint)) {
                        debugLog(`\nComparing ${allFoundCards.length} cards against eBay title${setNameHint ? ' and set name' : ''}...`);

                        let bestSimilarity = -1;
                        let bestCard = null;
                        const MINIMUM_SIMILARITY_THRESHOLD = 0.25; // 25% minimum similarity

                        // First pass: Check for set identifier matches in eBay title
                        const ebayTitleUpper = ebayTitle ? ebayTitle.toUpperCase() : '';
                        const setNameHintUpper = setNameHint ? setNameHint.toUpperCase() : '';
                        let setMatchFound = false;

                        allFoundCards.forEach((card, index) => {
                            const similarity = ebayTitle ? calculateTitleSimilarity(ebayTitle, card.name) : 0;

                            // Extract set identifier from card ID (e.g., "svp-141" -> "SVP", "A4-141" -> "A4")
                            const setMatch = card.id.match(/^([^-]+)-/);
                            const setIdentifier = setMatch ? setMatch[1].toUpperCase() : null;
                            const cardSetName = (card.set?.name || '').toUpperCase();

                            // Normalize set names for comparison (remove prefixes and normalize PROMO/PROMOS)
                            const normalizedSetHint = setNameHintUpper
                                .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                .replace(/PROMOS?$/, 'PROMO');
                            const normalizedCardSetName = cardSetName
                                .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                .replace(/PROMOS?$/, 'PROMO');

                            // Check if the set identifier or set name matches
                            let setBonus = 0;
                            if (setIdentifier && ebayTitleUpper.includes(setIdentifier)) {
                                setBonus = 0.5; // Large bonus for set ID match in title
                                setMatchFound = true;
                                debugLog(`  ${index + 1}. ${card.name} from ${card.set?.name || 'Unknown Set'} - Similarity: ${(similarity * 100).toFixed(1)}% + SET MATCH (${setIdentifier}) = ${((similarity + setBonus) * 100).toFixed(1)}%`);
                            } else if (setNameHint && (normalizedCardSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedCardSetName))) {
                                setBonus = 0.6; // Even larger bonus for set name match
                                setMatchFound = true;
                                debugLog(`  ${index + 1}. ${card.name} from ${card.set?.name || 'Unknown Set'} - Similarity: ${(similarity * 100).toFixed(1)}% + SET NAME MATCH = ${((similarity + setBonus) * 100).toFixed(1)}%`);
                            } else {
                                debugLog(`  ${index + 1}. ${card.name} from ${card.set?.name || 'Unknown Set'} - Similarity: ${(similarity * 100).toFixed(1)}%`);
                            }

                            const totalScore = similarity + setBonus;
                            if (totalScore > bestSimilarity) {
                                bestSimilarity = totalScore;
                                bestCard = card;
                            }
                        });

                        if (bestCard) {
                            bestMatch = bestCard;
                        }

                        // Check if best match meets minimum threshold (or has set match)
                        if (bestSimilarity >= MINIMUM_SIMILARITY_THRESHOLD || setMatchFound) {
                            const matchType = setMatchFound ? "with SET MATCH" : "by similarity";
                            debugLog(`\n🎯 BEST MATCH: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'} (${(bestSimilarity * 100).toFixed(1)}% total score - ${matchType})`);
                        } else {
                            debugLog(`\n❌ NO GOOD MATCH: Best similarity was ${(bestSimilarity * 100).toFixed(1)}% (below ${(MINIMUM_SIMILARITY_THRESHOLD * 100).toFixed(1)}% threshold)`);
                            debugLog(`🔄 Falling back to highest similarity card: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'}`);
                            // bestMatch already contains the highest similarity card (bestCard)
                            bestSimilarity = -1; // Reset similarity to indicate poor match
                        }
                    } else if (allFoundCards.length === 1) {
                        debugLog(`\n🎯 SINGLE MATCH FOUND: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'}`);
                    } else {
                        debugLog(`\n🎯 USING FIRST MATCH: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'} (no eBay title for comparison)`);
                    }

                    // Fetch full details for the best match only (optimization - 1 API call instead of potentially hundreds)
                    debugLog(`\n📡 Fetching full details for best match: ${bestMatch.name}`);
                    try {
                        const cardDetailUrl = `https://api.tcgdex.net/v2/en/cards/${bestMatch.id}`;
                        const cardDetailResponse = await fetch(cardDetailUrl);
                        
                        if (cardDetailResponse.ok) {
                            const fullCard = await cardDetailResponse.json();
                            bestMatch = fullCard; // Replace with full card data
                            debugLog(`✓ Loaded complete card data with set info: ${fullCard.set?.name || 'Unknown Set'}`);
                        } else {
                            debugLog(`⚠ Could not fetch full details (${cardDetailResponse.status}), using basic data`);
                        }
                    } catch (error) {
                        debugLog(`⚠ Error fetching full details:`, error.message, '- using basic data');
                    }

                    // Display the best match
                    debugLog(`\n🎉 FINAL RESULT FOR localId ${cardNumber}:`);
                    debugLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
                    debugLog(`Name: ${bestMatch.name}`);
                    debugLog(`Card Number: ${bestMatch.localId}/${bestMatch.set?.cardCount?.official || bestMatch.set?.cardCount?.total || '???'}`);
                    debugLog(`Set: ${bestMatch.set?.name || 'Unknown Set'} (${bestMatch.set?.id || 'unknown-id'})`);
                    debugLog(`Rarity: ${bestMatch.rarity || 'Unknown'}`);
                    debugLog(`HP: ${bestMatch.hp || 'N/A'}`);
                    debugLog(`Types: ${bestMatch.types?.join(', ') || 'Unknown'}`);

                    // Price information (simplified version)
                    if (bestMatch.pricing && bestMatch.pricing.tcgplayer) {
                        debugLog(`\n💰 TCGPLAYER PRICES:`);
                        const tcgPricing = bestMatch.pricing.tcgplayer;

                        debugLog(`  Last Updated: ${tcgPricing.updated || 'Unknown'}`);
                        debugLog(`  Currency: ${tcgPricing.unit || 'USD'}`);

                        if (tcgPricing['holofoil']) {
                            const holo = tcgPricing['holofoil'];
                            debugLog(`  Holofoil - Low: $${holo.lowPrice || 'N/A'}, Mid: $${holo.midPrice || 'N/A'}, High: $${holo.highPrice || 'N/A'}, Market: $${holo.marketPrice || 'N/A'}`);
                        }

                        if (tcgPricing['normal']) {
                            const normal = tcgPricing['normal'];
                            debugLog(`  Normal - Low: $${normal.lowPrice || 'N/A'}, Mid: $${normal.midPrice || 'N/A'}, High: $${normal.highPrice || 'N/A'}, Market: $${normal.marketPrice || 'N/A'}`);
                        }
                    } else {
                        debugLog(`💰 TCGPLAYER PRICES: Not available`);
                    }

                    // High quality image
                    const imageUrl = bestMatch.image?.high || bestMatch.image?.large || bestMatch.images?.large || bestMatch.imageUrl || bestMatch.image;
                    if (imageUrl) {
                        debugLog(`\n🖼️ High-Res Image: ${imageUrl}`);
                    }

                    // Market URLs
                    if (bestMatch.tcgPlayer?.url) {
                        debugLog(`🔗 TCGPlayer: ${bestMatch.tcgPlayer.url}`);
                    }

                    debugLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);

                    // Return the best match
                    return bestMatch;

                } else {
                    debugLog(`No cards found with localId ${cardNumber}`);
                    debugLog('This could mean:');
                    debugLog('1. The card number doesn\'t exist in the API');
                    debugLog('2. The number format is different than expected');
                    debugLog('3. API connectivity issues');
                    
                    // Only open fallback search if not skipped (e.g., when called from PriceCharting URL generation)
                    if (!skipFallback) {
                        debugLog('\nTrying fallback search...');
                        await searchTCGdexFallback(cardNumber);
                    } else {
                        debugLog('Skipping fallback search (silent fail for PriceCharting)');
                    }
                    return null;
                }

            } catch (error) {
                console.error('Error searching TCGdex by localId:', error);
                debugLog('API request failed. This could be due to:');
                debugLog('1. Network connectivity issues');
                debugLog('2. API being temporarily down');
                debugLog('3. Rate limiting');

                debugLog('\nOpening TCGdex website as fallback...');
                const searchWindow = window.open(`https://www.tcgdex.dev/cards?search=${encodeURIComponent(cardNumber)}`, '_blank');
                if (searchWindow) {
                    debugLog('Opened TCGdex website search in new tab');
                }
                return null;
            }
        }

        // Add Google PriceCharting search button
        function addGooglePriceChartingButton(listingElement, cardNumber) {
            if (listingElement.querySelector('.google-pricecharting-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement || !cardNumber) return;

            const button = document.createElement('a');
            button.className = 'google-pricecharting-btn';
            button.href = `https://www.google.com/search?q=PriceCharting+${encodeURIComponent(cardNumber)}`;
            button.target = '_blank';
            button.textContent = '🔍';
            button.title = `Google search PriceCharting for ${cardNumber}`;

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '8px',
                padding: '2px 6px',
                background: '#e74c3c',
                color: 'white',
                textDecoration: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#google-pc-button-styles')) {
                const style = document.createElement('style');
                style.id = 'google-pc-button-styles';
                style.textContent = '.google-pricecharting-btn:hover { background-color: #c0392b !important; }';
                document.head.appendChild(style);
            }

            priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
        }

        // Enhanced PriceCharting Direct button with data sharing
        function addPriceChartingDirectButton(listingElement, cardNumber, setNumber) {
            if (listingElement.querySelector('.pricecharting-direct-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement) return;

            const button = document.createElement('button');
            button.className = 'pricecharting-direct-btn';
            button.textContent = cardNumber ? 'PC✓' : '🔍 PC';
            button.title = cardNumber ? `Direct PriceCharting link for ${cardNumber}` : 'Search PriceCharting by card name';

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '4px',
                padding: '2px 6px',
                background: '#9b59b6',
                color: 'white',
                border: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                cursor: 'pointer',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#pricecharting-direct-button-styles')) {
                const style = document.createElement('style');
                style.id = 'pricecharting-direct-button-styles';
                style.textContent = '.pricecharting-direct-btn:hover { background-color: #8e44ad !important; }';
                document.head.appendChild(style);
            }

            button.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();

                const originalText = button.textContent;
                button.textContent = '...';
                button.disabled = true;

                try {
                    await openPriceChartingDirectWithSharing(listingElement, cardNumber, setNumber);
                } finally {
                    button.textContent = originalText;
                    button.disabled = false;
                }
            });

            // Insert after the PC📊 (View) button
            const pcViewButton = listingElement.querySelector('.pricecharting-view-btn');
            if (pcViewButton && pcViewButton.parentNode) {
                pcViewButton.parentNode.insertBefore(button, pcViewButton.nextSibling);
            } else {
                // Fallback insertion logic
                const googlePcButton = listingElement.querySelector('.google-pricecharting-btn');
                if (googlePcButton) {
                    googlePcButton.parentNode.insertBefore(button, googlePcButton.nextSibling);
                } else {
                    priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
                }
            }
        }

        // Add PriceCharting View button to open the full pricing page (without #full-prices hash)
        function addPriceChartingViewButton(listingElement, cardNumber, setNumber) {
            if (listingElement.querySelector('.pricecharting-view-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement || !cardNumber) return;

            const button = document.createElement('button');
            button.className = 'pricecharting-view-btn';
            button.textContent = 'PC📊';
            button.title = `View full PriceCharting page for ${cardNumber}`;

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '4px',
                padding: '2px 6px',
                background: '#e67e22',
                color: 'white',
                border: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                cursor: 'pointer',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#pricecharting-view-button-styles')) {
                const style = document.createElement('style');
                style.id = 'pricecharting-view-button-styles';
                style.textContent = '.pricecharting-view-btn:hover { background-color: #d35400 !important; }';
                document.head.appendChild(style);
            }

            button.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();

                const originalText = button.textContent;
                button.textContent = '...';
                button.disabled = true;

                try {
                    await openPriceChartingViewPage(listingElement, cardNumber, setNumber);
                } finally {
                    button.textContent = originalText;
                    button.disabled = false;
                }
            });

            // Insert after the PC✓ button
            const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
            if (pcButton && pcButton.parentNode) {
                pcButton.parentNode.insertBefore(button, pcButton.nextSibling);
            } else {
                // Fallback insertion logic
                const googlePcButton = listingElement.querySelector('.google-pricecharting-btn');
                if (googlePcButton) {
                    googlePcButton.parentNode.insertBefore(button, googlePcButton.nextSibling);
                } else {
                    priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
                }
            }
        }

        // Add Manual Edit button to manually set price from PriceCharting
        function addManualEditButton(listingElement) {
            if (listingElement.querySelector('.pricecharting-manual-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement) return;

            const button = document.createElement('button');
            button.className = 'pricecharting-manual-btn';
            button.textContent = '✏️';
            button.title = 'Manually set price from PriceCharting';

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '4px',
                padding: '2px 6px',
                background: '#3498db',
                color: 'white',
                border: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                cursor: 'pointer',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#pricecharting-manual-button-styles')) {
                const style = document.createElement('style');
                style.id = 'pricecharting-manual-button-styles';
                style.textContent = '.pricecharting-manual-btn:hover { background-color: #2980b9 !important; }';
                document.head.appendChild(style);
            }

            button.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();

                const originalText = button.textContent;
                button.textContent = '...';
                button.disabled = true;

                try {
                    await openManualPriceEditor(listingElement);
                } finally {
                    button.textContent = originalText;
                    button.disabled = false;
                }
            });

            // Insert after the PC📊 (View) button or other buttons
            const pcViewButton = listingElement.querySelector('.pricecharting-view-btn');
            const pcDirectButton = listingElement.querySelector('.pricecharting-direct-btn');
            
            if (pcDirectButton && pcDirectButton.parentNode) {
                pcDirectButton.parentNode.insertBefore(button, pcDirectButton.nextSibling);
            } else if (pcViewButton && pcViewButton.parentNode) {
                pcViewButton.parentNode.insertBefore(button, pcViewButton.nextSibling);
            } else {
                priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
            }
        }

        // Calculate title similarity function (referenced in searchTCGdex)
        function calculateTitleSimilarity(ebayTitle, cardName) {
            if (!ebayTitle || !cardName) return 0;

            // Normalize "Mega" and "M " before comparison
            let title1 = ebayTitle.toLowerCase().replace(/\bmega[\s-]*/g, 'm ');
            let title2 = cardName.toLowerCase();

            // Extract Pokemon name from card name (before any modifiers like "ex", "V", "VSTAR", etc.)
            const pokemonNameMatch = title2.match(/^(.*?)\s*(?:ex|v|vstar|vmax|gx|tag team|&|break|prime|lv\.?x|δ|★|prism star)?\s*$/i);
            const pokemonName = pokemonNameMatch ? pokemonNameMatch[1].trim() : title2;

            // Check for exact Pokemon name match (case-insensitive)
            // Special handling for single-letter names (like "N") or trainer names
            let isMatch = false;
            if (pokemonName.length === 1) {
                // For single letter names, look for the letter as a standalone word
                const regex = new RegExp(`\\b${pokemonName}\\b`, 'i');
                isMatch = regex.test(title1);
                if (isMatch) {
                    debugLog(`🎯 Single-letter Pokemon match found: "${pokemonName}" as standalone word in "${title1}"`);
                }
            } else if (pokemonName.length >= 2) {
                // For multi-character names, use substring matching
                isMatch = title1.includes(pokemonName);
                if (isMatch) {
                    debugLog(`🎯 Direct Pokemon match found: "${pokemonName}" in "${title1}"`);
                }
            }

            if (isMatch) {
                let score = 0.85; // High base score for Pokemon name match

                // Bonus points for matching modifiers
                if (title2.includes('ex') && title1.includes('ex')) score += 0.1;
                if (title2.includes(' v') && title1.includes(' v')) score += 0.1;
                if (title2.includes('vstar') && title1.includes('vstar')) score += 0.1;
                if (title2.includes('vmax') && title1.includes('vmax')) score += 0.1;
                if (title2.includes('gx') && title1.includes('gx')) score += 0.1;

                return Math.min(score, 1.0);
            }

            // Fallback to word matching for non-Pokemon names or when Pokemon name doesn't match
            const words1 = title1.split(/\s+/);
            const words2 = title2.split(/\s+/);

            let matches = 0;
            let significantMatches = 0;

            words2.forEach(word => {
                if (word.length > 2) { // Only count significant words
                    if (words1.some(w => w === word)) {
                        matches++;
                        significantMatches++;
                    } else if (words1.some(w => w.includes(word) || word.includes(w))) {
                        matches += 0.5; // Partial match
                    }
                }
            });

            return significantMatches > 0 ? matches / Math.max(words1.length, words2.length) : 0;
        }

        // Fallback search function (referenced in searchTCGdex)
        async function searchTCGdexFallback(cardNumber) {
            debugLog(`🔄 Fallback: Opening TCGdex website search for ${cardNumber}`);
            const searchWindow = window.open(`https://www.tcgdex.dev/cards?search=${encodeURIComponent(cardNumber)}`, '_blank');
            if (searchWindow) {
                debugLog('Opened TCGdex website search in new tab');
            }
            return null;
        }

        // Function to open PriceCharting view page (without #full-prices hash)
        async function openPriceChartingViewPage(listingElement, cardNumber, setNumber) {
            try {
            // Store the initial card data for PriceCharting to access
            const listingInfo = extractListingInfo(listingElement);
            const cardData = {
                cardNumber: cardNumber,
                setNumber: setNumber,
                ebayTitle: listingInfo.title
            };

            const requestKey = storePriceChartingRequest(cardData);

            // Use the enhanced URL creation function that handles all the logic
            const finalUrl = await createPriceChartingUrl(cardNumber, setNumber, requestKey, listingElement, false);

            if (finalUrl) {
                debugLog(`🔗 Opening PriceCharting view URL: ${finalUrl}`);

                // Open in new tab for viewing
                window.open(finalUrl, '_blank');
            } else {
                debugLog('⚠️ Card number pattern failed - trying title-based fallback for view page');
                
                // Try title-based search as fallback if we have a set name
                if (listingInfo.setName) {
                    debugLog(`🔄 Falling back to title-based search in set: ${listingInfo.setName}`);
                    
                    // Search by matching title against card names in the set
                    const foundCard = await searchByTitleInSet(listingInfo.setName, listingInfo.title);
                    
                    if (foundCard) {
                        debugLog(`✅ Found card via title fallback: ${foundCard.name} #${foundCard.localId}`);
                        
                        // Build URL from the found card
                        const fallbackUrl = await buildPriceChartingUrlFromCard(foundCard, foundCard.set?.name, requestKey, false, listingElement);
                        
                        if (fallbackUrl) {
                            debugLog(`🔗 Opening PriceCharting view URL (fallback): ${fallbackUrl}`);
                            window.open(fallbackUrl, '_blank');
                            return;
                        }
                    }
                }
                
                // If all fallbacks failed, use search page with title
                debugLog('⚠️ All search methods failed - opening PriceCharting search page');
                const searchQuery = listingInfo.title || cardNumber;
                const fallbackUrl = `https://www.pricecharting.com/search?q=${encodeURIComponent(searchQuery)}&type=prices`;
                window.open(fallbackUrl, '_blank');
            }

            } catch (error) {
            debugLog('Error constructing PriceCharting view URL:', error);
            const listingInfo = extractListingInfo(listingElement);
            const searchQuery = listingInfo.title || cardNumber;
            const fallbackUrl = `https://www.pricecharting.com/search?q=${encodeURIComponent(searchQuery)}&type=prices`;
            window.open(fallbackUrl, '_blank');
            }
        }

        // PriceCharting URL set name mapping - hardcoded for known differences
        const PRICECHARTING_SET_MAPPING = {
            // Sets with & characters (must preserve the & to avoid redirects)
            '151': 'pokemon-scarlet-&-violet-151',
            'Scarlet & Violet': 'pokemon-scarlet-&-violet',
            'Sun & Moon': 'pokemon-sun-&-moon',
            'Black & White': 'pokemon-black-&-white',
            'Ruby & Sapphire': 'pokemon-ruby-&-sapphire',
            'Diamond & Pearl': 'pokemon-diamond-&-pearl',
            'HeartGold SoulSilver': 'pokemon-heartgold-&-soulsilver',
            'Sword & Shield': 'pokemon-sword-&-shield',
            'FireRed & LeafGreen': 'pokemon-firered-&-leafgreen',

            // Base Set variations
            'Base Set': 'pokemon-base-set',
            'Base Set 2': 'pokemon-base-set-2',

            // POP Series
            'POP Series 1': 'pokemon-pop-series-1',
            'POP Series 2': 'pokemon-pop-series-2',
            'POP Series 3': 'pokemon-pop-series-3',
            'POP Series 4': 'pokemon-pop-series-4',
            'POP Series 5': 'pokemon-pop-series-5',
            'POP Series 6': 'pokemon-pop-series-6',
            'POP Series 7': 'pokemon-pop-series-7',
            'POP Series 8': 'pokemon-pop-series-8',
            'POP Series 9': 'pokemon-pop-series-9',

            // Gym Series
            'Gym Heroes': 'pokemon-gym-heroes',
            'Gym Challenge': 'pokemon-gym-challenge',

            // Neo Series
            'Neo Genesis': 'pokemon-neo-genesis',
            'Neo Discovery': 'pokemon-neo-discovery',
            'Neo Revelation': 'pokemon-neo-revelation',
            'Neo Destiny': 'pokemon-neo-destiny',

            // E-Card Series
            'Expedition Base Set': 'pokemon-expedition',
            'Aquapolis': 'pokemon-aquapolis',
            'Skyridge': 'pokemon-skyridge',

            // EX Series
            'Team Magma vs Team Aqua': 'pokemon-team-magma-vs-team-aqua',
            'Hidden Legends': 'pokemon-hidden-legends',
            'Team Rocket Returns': 'pokemon-team-rocket-returns',
            'Deoxys': 'pokemon-deoxys',
            'EX Deoxys': 'pokemon-deoxys',
            'Emerald': 'pokemon-emerald',
            'EX Emerald': 'pokemon-emerald',
            'Unseen Forces': 'pokemon-unseen-forces',
            'EX Unseen Forces': 'pokemon-unseen-forces',
            'Delta Species': 'pokemon-delta-species',
            'Legend Maker': 'pokemon-legend-maker',
            'Holon Phantoms': 'pokemon-holon-phantoms',
            'Crystal Guardians': 'pokemon-crystal-guardians',
            'Dragon Frontiers': 'pokemon-dragon-frontiers',
            'Power Keepers': 'pokemon-power-keepers',

            // Diamond & Pearl Series
            'Mysterious Treasures': 'pokemon-mysterious-treasures',
            'Secret Wonders': 'pokemon-secret-wonders',
            'Great Encounters': 'pokemon-great-encounters',
            'Majestic Dawn': 'pokemon-majestic-dawn',
            'Legends Awakened': 'pokemon-legends-awakened',
            'Stormfront': 'pokemon-stormfront',

            // Platinum Series
            'Platinum': 'pokemon-platinum',
            'Rising Rivals': 'pokemon-rising-rivals',
            'Supreme Victors': 'pokemon-supreme-victors',
            'Arceus': 'pokemon-arceus',

            // HGSS Series
            'Unleashed': 'pokemon-unleashed',
            'Undaunted': 'pokemon-undaunted',
            'Triumphant': 'pokemon-triumphant',
            'Call of Legends': 'pokemon-call-of-legends',

            // Black & White Series
            'Emerging Powers': 'pokemon-emerging-powers',
            'Noble Victories': 'pokemon-noble-victories',
            'Next Destinies': 'pokemon-next-destinies',
            'Dark Explorers': 'pokemon-dark-explorers',
            'Dragons Exalted': 'pokemon-dragons-exalted',
            'Boundaries Crossed': 'pokemon-boundaries-crossed',
            'Plasma Storm': 'pokemon-plasma-storm',
            'Plasma Freeze': 'pokemon-plasma-freeze',
            'Plasma Blast': 'pokemon-plasma-blast',
            'Legendary Treasures': 'pokemon-legendary-treasures',

            // XY Series
            'XY': 'pokemon-xy',
            'Flashfire': 'pokemon-flashfire',
            'Furious Fists': 'pokemon-furious-fists',
            'Phantom Forces': 'pokemon-phantom-forces',
            'Primal Clash': 'pokemon-primal-clash',
            'Roaring Skies': 'pokemon-roaring-skies',
            'Ancient Origins': 'pokemon-ancient-origins',
            'BREAKthrough': 'pokemon-breakthrough',
            'BREAKpoint': 'pokemon-breakpoint',
            'Generations': 'pokemon-generations',
            'Fates Collide': 'pokemon-fates-collide',
            'Steam Siege': 'pokemon-steam-siege',
            'Evolutions': 'pokemon-evolutions',

            // Sun & Moon Series
            'Guardians Rising': 'pokemon-guardians-rising',
            'Burning Shadows': 'pokemon-burning-shadows',
            'Crimson Invasion': 'pokemon-crimson-invasion',
            'Ultra Prism': 'pokemon-ultra-prism',
            'Forbidden Light': 'pokemon-forbidden-light',
            'Celestial Storm': 'pokemon-celestial-storm',
            'Lost Thunder': 'pokemon-lost-thunder',
            'Team Up': 'pokemon-team-up',
            'Unbroken Bonds': 'pokemon-unbroken-bonds',
            'Unified Minds': 'pokemon-unified-minds',
            'Cosmic Eclipse': 'pokemon-cosmic-eclipse',

            // Sword & Shield Series
            'Rebel Clash': 'pokemon-rebel-clash',
            'Darkness Ablaze': 'pokemon-darkness-ablaze',
            'Vivid Voltage': 'pokemon-vivid-voltage',
            'Battle Styles': 'pokemon-battle-styles',
            'Chilling Reign': 'pokemon-chilling-reign',
            'Evolving Skies': 'pokemon-evolving-skies',
            'Fusion Strike': 'pokemon-fusion-strike',
            'Brilliant Stars': 'pokemon-brilliant-stars',
            'Astral Radiance': 'pokemon-astral-radiance',
            'Lost Origin': 'pokemon-lost-origin',
            'Silver Tempest': 'pokemon-silver-tempest',

            // Scarlet & Violet Series
            'Paldea Evolved': 'pokemon-paldea-evolved',
            'Obsidian Flames': 'pokemon-obsidian-flames',
            'Paradox Rift': 'pokemon-paradox-rift',
            'Temporal Forces': 'pokemon-temporal-forces',
            'Twilight Masquerade': 'pokemon-twilight-masquerade',
            'Shrouded Fable': 'pokemon-shrouded-fable',
            'Stellar Crown': 'pokemon-stellar-crown',
            'Surging Sparks': 'pokemon-surging-sparks',

            // Special Sets
            'Shining Legends': 'pokemon-shining-legends',
            'Hidden Fates': 'pokemon-hidden-fates',
            'Champion\'s Path': 'pokemon-champions-path',
            'Shining Fates': 'pokemon-shining-fates',
            'Celebrations': 'pokemon-celebrations',
            'Pokémon GO': 'pokemon-go',
            'Crown Zenith': 'pokemon-crown-zenith',
            'Paldean Fates': 'pokemon-paldean-fates',

            // Promos
            'SVP Black Star Promos': 'pokemon-promo',
            'SWSH Black Star Promos': 'pokemon-promo',
            'SM Black Star Promos': 'pokemon-promo',
            'XY Black Star Promos': 'pokemon-promo',
            'BW Black Star Promos': 'pokemon-promo',
            'DP Black Star Promos': 'pokemon-promo',
            'HGSS Black Star Promos': 'pokemon-promo',
            'EX Black Star Promos': 'pokemon-promo',
            'Nintendo Black Star Promos': 'pokemon-promo',
            'Wizards Black Star Promos': 'pokemon-promo',
            'Promo Cards': 'pokemon-promo',
            'Promos': 'pokemon-promo',
        };

        // Cache for recently fetched PriceCharting data to avoid duplicate requests
        const priceChartingCache = new Map();

        // Enhanced function to open PriceCharting with the specific URL format - Returns Promise
        async function openPriceChartingDirectWithSharing(listingElement, cardNumber, setNumber) {
            try {
                const listingInfo = extractListingInfo(listingElement);

                // Check if this is a title-based search (no card number but has set name)
                if (!cardNumber && listingInfo.setName) {
                    debugLog(`🔍 Title-based search in set: ${listingInfo.setName}`);
                    
                    // Update button to show searching
                    const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                    if (pcButton) {
                        pcButton.textContent = '🔍 Searching...';
                        pcButton.disabled = true;
                    }
                    
                    // Search by matching title against card names in the set
                    const foundCard = await searchByTitleInSet(listingInfo.setName, listingInfo.title);
                    
                    if (foundCard) {
                        debugLog(`✅ Found card via name search: ${foundCard.name} #${foundCard.localId}`);
                        
                        // Update button to show found
                        if (pcButton) {
                            pcButton.textContent = `💰 ${foundCard.name}`;
                            pcButton.title = `Found: ${foundCard.name} #${foundCard.localId} from ${foundCard.set?.name}`;
                        }
                        
                        // Store card data for PriceCharting to access
                        const cardData = {
                            cardNumber: foundCard.localId,
                            setNumber: foundCard.set?.cardCount?.total || null,
                            ebayTitle: listingInfo.title
                        };
                        
                        const requestKey = storePriceChartingRequest(cardData);
                        
                        // Build URL directly from the found card (bypass localId search)
                        // Params: foundCard, setName, requestKey, showPricePage, listingElement
                        const finalUrl = await buildPriceChartingUrlFromCard(foundCard, foundCard.set?.name, requestKey, true, listingElement);
                        
                        if (finalUrl) {
                            // Continue with opening PriceCharting window
                            const detectedGrade = detectGradeFromTitle(listingInfo.title);
                            const gradeKey = detectedGrade ? detectedGrade.key : 'ungraded';
                            const baseUrl = finalUrl.split('?')[0];
                            const cacheKey = `${foundCard.localId}_${foundCard.set?.cardCount?.total}_${gradeKey}_${baseUrl}`;
                            
                            const cached = priceChartingCache.get(cacheKey);
                            if (cached && (Date.now() - cached.timestamp < TIMING.CACHE_DURATION)) {
                                debugLog(`💾 Using cached PriceCharting data for ${cacheKey}`);
                                updateListingWithPriceChartingData(listingElement, cached.data);
                                
                                if (pcButton) {
                                    pcButton.style.background = '#27ae60';
                                    pcButton.title = `✅ PriceCharting data loaded (cached) - ${Object.keys(cached.data.prices || {}).length} prices found`;
                                    pcButton.disabled = false;
                                }
                                return true;
                            }
                            
                        debugLog(`🔗 Opening PriceCharting URL: ${finalUrl}`);
                        const windowName = `pricecharting_${requestKey}`;
                        // Create minimal resource popup (1x1 pixel, off-screen, all features disabled)
                        const pcWindow = window.open(finalUrl, windowName, 
                            'width=1,height=1,left=9999,top=9999,' +
                            'scrollbars=no,toolbar=no,menubar=no,location=no,status=no,' +
                            'resizable=no,directories=no');                            if (pcWindow) {
                                debugLog(`✅ PriceCharting window opened successfully (minimized off-screen)`);
                                window.focus();
                            } else {
                                debugLog(`⚠️ Failed to open PriceCharting window - popup may be blocked`);
                                showPopupBlockedWarning(listingElement);
                                if (pcButton) pcButton.disabled = false;
                                return false;
                            }
                            
                            await setupPriceChartingDataListener(requestKey, listingElement);
                            return true;
                        } else {
                            debugLog('⚠️ Could not create PriceCharting URL from found card');
                            if (pcButton) {
                                pcButton.style.background = '#95a5a6';
                                pcButton.textContent = '❓ URL Error';
                                pcButton.title = 'Could not create PriceCharting URL';
                                pcButton.disabled = false;
                            }
                            return false;
                        }
                    } else {
                        debugLog(`⚠️ Could not find card by name`);
                        if (pcButton) {
                            pcButton.style.background = '#95a5a6';
                            pcButton.textContent = '❓ Not Found';
                            pcButton.title = `Could not find "${listingInfo.pokemonName}" in ${listingInfo.setName}`;
                            pcButton.disabled = false;
                        }
                        return false;
                    }
                }

                // Store the initial card data for PriceCharting to access
                const cardData = {
                    cardNumber: cardNumber,
                    setNumber: setNumber,
                    ebayTitle: listingInfo.title
                };

                const requestKey = storePriceChartingRequest(cardData);

                // Use the enhanced URL creation function that handles all the logic
                const finalUrl = await createPriceChartingUrl(cardNumber, setNumber, requestKey, listingElement, true);

                if (finalUrl) {
                    // Detect grade from title to make cache key unique per grade
                    const detectedGrade = detectGradeFromTitle(listingInfo.title);
                    const gradeKey = detectedGrade ? detectedGrade.key : 'ungraded';
                    
                    // Create a cache key based on the card AND grade (without the unique request key)
                    const baseUrl = finalUrl.split('?')[0]; // URL without query params
                    const cacheKey = `${cardNumber}_${setNumber}_${gradeKey}_${baseUrl}`;
                    
                    // Check if we have cached data for this card with this specific grade
                    const cached = priceChartingCache.get(cacheKey);
                    if (cached && (Date.now() - cached.timestamp < TIMING.CACHE_DURATION)) {
                        debugLog(`💾 Using cached PriceCharting data for ${cacheKey}`);
                        // Use cached data immediately
                        updateListingWithPriceChartingData(listingElement, cached.data);
                        
                        // Update button to show success
                        const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                        if (pcButton) {
                            pcButton.style.background = '#27ae60';
                            pcButton.title = `✅ PriceCharting data loaded (cached) - ${Object.keys(cached.data.prices || {}).length} prices found`;
                        }
                        return true;
                    }

                    debugLog(`🔗 Opening PriceCharting URL: ${finalUrl}`);
                    debugLog(`🔑 Request key: ${requestKey}`);

                    // Use unique window name for each request to avoid reusing same window
                    const windowName = `pricecharting_${requestKey}`;
                    
                    // Open in new window/tab (will auto-close quickly)
                    // Create minimal resource popup (1x1 pixel, off-screen, all features disabled)
                    const pcWindow = window.open(finalUrl, windowName, 
                        'width=1,height=1,left=9999,top=9999,' +
                        'scrollbars=no,toolbar=no,menubar=no,location=no,status=no,' +
                        'resizable=no,directories=no');
                    if (pcWindow) {
                        debugLog(`✅ PriceCharting window opened successfully`);
                        window.focus(); // Try to keep focus on current eBay tab
                    } else {
                        debugLog(`⚠️ Failed to open PriceCharting window - popup may be blocked`);
                        // Show popup blocked warning
                        showPopupBlockedWarning(listingElement);
                        return false;
                    }

                    // Set up listener for returned data - now returns a Promise
                    await setupPriceChartingDataListener(requestKey, listingElement);
                    return true;
                } else {
                    debugLog('⚠️ Card number pattern failed to find card - trying title-based fallback');
                    
                    // Try title-based search as fallback if we have a set name
                    if (listingInfo.setName) {
                        debugLog(`🔄 Falling back to title-based search in set: ${listingInfo.setName}`);
                        
                        const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                        if (pcButton) {
                            pcButton.textContent = '🔍 Retry...';
                            pcButton.disabled = true;
                        }
                        
                        // Search by matching title against card names in the set
                        const foundCard = await searchByTitleInSet(listingInfo.setName, listingInfo.title);
                        
                        if (foundCard) {
                            debugLog(`✅ Found card via title fallback: ${foundCard.name} #${foundCard.localId}`);
                            
                            // Store new card data
                            const newCardData = {
                                cardNumber: foundCard.localId,
                                setNumber: foundCard.set?.cardCount?.total || null,
                                ebayTitle: listingInfo.title
                            };
                            const newRequestKey = storePriceChartingRequest(newCardData);
                            
                            // Build URL from the found card
                            const fallbackUrl = await buildPriceChartingUrlFromCard(foundCard, foundCard.set?.name, newRequestKey, true, listingElement);
                            
                            if (fallbackUrl) {
                                debugLog(`🔗 Opening PriceCharting URL (fallback): ${fallbackUrl}`);
                                const windowName = `pricecharting_${newRequestKey}`;
                                const pcWindow = window.open(fallbackUrl, windowName, 
                                    'width=1,height=1,left=9999,top=9999,' +
                                    'scrollbars=no,toolbar=no,menubar=no,location=no,status=no,' +
                                    'resizable=no,directories=no');
                                
                                if (pcWindow) {
                                    debugLog(`✅ PriceCharting window opened (fallback method)`);
                                    window.focus();
                                    await setupPriceChartingDataListener(newRequestKey, listingElement);
                                    return true;
                                }
                            }
                        }
                    }
                    
                    // If all fallbacks failed, show not found
                    debugLog('⚠️ All search methods failed - card not found');
                    const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                    if (pcButton) {
                        pcButton.style.background = '#95a5a6'; // Gray
                        pcButton.textContent = '❓ Not Found';
                        pcButton.title = 'Card not found in TCGdex database. Try manual search.';
                        pcButton.disabled = false;
                    }
                    
                    return false;
                }

            } catch (error) {
                debugLog('❌ Error opening PriceCharting:', error);
                
                // Update button to show error
                const pcButton = listingElement?.querySelector('.pricecharting-direct-btn');
                if (pcButton) {
                    pcButton.style.background = '#95a5a6'; // Gray
                    pcButton.textContent = '⚠️ Error';
                    pcButton.title = `Error: ${error.message}`;
                }
                
                return false;
            }
        }

        // Function to open manual price editor
        async function openManualPriceEditor(listingElement) {
            try {
                const listingInfo = extractListingInfo(listingElement);
                debugLog('🔧 Opening manual price editor for:', listingInfo.title);

                // Open PriceCharting in a new window
                const searchQuery = encodeURIComponent(listingInfo.title || '');
                const pcWindow = window.open(
                    `https://www.pricecharting.com/search?q=${searchQuery}&type=prices`,
                    'pcManualEditor',
                    'width=1200,height=800,scrollbars=yes,resizable=yes'
                );

                if (!pcWindow) {
                    alert('Please allow popups for this site to use the manual price editor.');
                    return;
                }

                // Create a unique ID for this listing
                const listingId = listingElement.getAttribute('data-gr') || 
                                 listingElement.getAttribute('id') || 
                                 `listing-${Date.now()}`;

                // Store the listing element reference
                window.pokespyManualEdits = window.pokespyManualEdits || {};
                window.pokespyManualEdits[listingId] = listingElement;

                // Inject a script into the popup window to add "Set Price" buttons
                const injectionScript = `
                    (function() {
                        const listingId = '${listingId}';
                        
                        function addSetPriceButtons() {
                            // Find all price elements on PriceCharting
                            const priceElements = document.querySelectorAll('.price, .used_price, td:has(a[href*="/game/"])');
                            
                            priceElements.forEach((el) => {
                                if (el.querySelector('.pokespy-set-price-btn')) return;
                                
                                // Try to extract price
                                const priceText = el.textContent.trim();
                                const priceMatch = priceText.match(/\\$([\\d,]+\\.?\\d*)/);
                                
                                if (priceMatch) {
                                    const price = priceMatch[1].replace(',', '');
                                    
                                    const btn = document.createElement('button');
                                    btn.className = 'pokespy-set-price-btn';
                                    btn.textContent = '✓ Use This';
                                    btn.style.cssText = \`
                                        margin-left: 8px;
                                        padding: 4px 8px;
                                        background: #27ae60;
                                        color: white;
                                        border: none;
                                        border-radius: 3px;
                                        font-size: 11px;
                                        font-weight: bold;
                                        cursor: pointer;
                                        transition: background 0.2s;
                                    \`;
                                    
                                    btn.onmouseover = () => btn.style.background = '#229954';
                                    btn.onmouseout = () => btn.style.background = '#27ae60';
                                    
                                    btn.onclick = () => {
                                        // Send message to opener window
                                        if (window.opener && !window.opener.closed) {
                                            window.opener.postMessage({
                                                type: 'POKESPY_SET_MANUAL_PRICE',
                                                listingId: listingId,
                                                price: parseFloat(price),
                                                url: window.location.href
                                            }, '*');
                                            
                                            btn.textContent = '✓ Set!';
                                            btn.style.background = '#2ecc71';
                                            setTimeout(() => window.close(), 1000);
                                        }
                                    };
                                    
                                    el.appendChild(btn);
                                }
                            });
                        }
                        
                        // Add buttons when page loads
                        if (document.readyState === 'loading') {
                            document.addEventListener('DOMContentLoaded', addSetPriceButtons);
                        } else {
                            addSetPriceButtons();
                        }
                        
                        // Re-add buttons after any DOM changes (for dynamic content)
                        setTimeout(addSetPriceButtons, 1000);
                        setTimeout(addSetPriceButtons, 2000);
                    })();
                `;

                // Wait for the popup to load, then inject the script
                const checkInterval = setInterval(() => {
                    if (pcWindow.closed) {
                        clearInterval(checkInterval);
                        return;
                    }
                    
                    try {
                        if (pcWindow.document && pcWindow.document.readyState === 'complete') {
                            clearInterval(checkInterval);
                            const script = pcWindow.document.createElement('script');
                            script.textContent = injectionScript;
                            pcWindow.document.body.appendChild(script);
                        }
                    } catch (e) {
                        // Cross-origin error - can't inject, but that's okay
                        clearInterval(checkInterval);
                    }
                }, 100);

                // Clean up after 5 minutes
                setTimeout(() => {
                    clearInterval(checkInterval);
                }, 300000);

            } catch (error) {
                debugLog('❌ Error opening manual price editor:', error);
                alert('Error opening price editor: ' + error.message);
            }
        }


        // --- Enhanced helper function for PriceCharting URL creation with all logic ---
        async function createPriceChartingUrl(cardNumber, setNumber, requestKey, listingElement, showPricePage) {
            try {
            // Load sets cache and find matching sets
            await loadSetsCache();
            const matchingSets = findSetsByCardCount(setNumber);

            let foundCard = null;
            let setName = null;
            let allFoundCards = [];

            // If no setNumber provided (hash patterns) OR no matching sets found, search by localId to get set info
            if (!setNumber || matchingSets.length === 0) {
                if (!setNumber) {
                    debugLog('🔍 No setNumber provided - using localId search to find set information for PriceCharting URL');
                } else {
                    debugLog(`🔍 No matching sets found for setNumber "${setNumber}" - falling back to localId search`);
                }

                try {
                    const listingInfo = extractListingInfo(listingElement);
                    const localIdResult = await searchTCGdexByLocalId(cardNumber, listingInfo.title, listingInfo.setName, true);

                    if (localIdResult && localIdResult.id) {
                        // Extract set ID from the card ID (e.g., "sv08-238" -> "sv08")
                        const setId = localIdResult.id.split('-')[0];
                        debugLog(`✅ Found card via localId: ${localIdResult.name} from set ID "${setId}"`);

                        // Find the set name from our cached sets using the set ID
                        await loadSetsCache();
                        const matchingSet = setsCache.find(set => set.id === setId);

                        if (matchingSet) {
                            foundCard = localIdResult;
                            setName = matchingSet.name;
                            debugLog(`✅ Found matching set: ${setName}`);

                            // Skip the normal set searching since we already have our card
                            // Jump directly to URL construction
                            return buildPriceChartingUrlFromCard(foundCard, setName, requestKey, showPricePage, listingElement);
                        } else {
                            debugLog(`❌ Could not find set with ID "${setId}" in cached sets`);
                            return null;
                        }
                    } else {
                        debugLog('❌ LocalId search failed - no card or ID found');
                        return null;
                    }
                } catch (error) {
                    debugLog('❌ Error in localId search for PriceCharting URL:', error);
                    return null;
                }
            }

            // Create variations of the card number to handle zero-padding issues
            // For alternate arts (like "177a"), strip the letter for API searches
            const baseCardNumber = cardNumber.replace(/[a-zA-Z]/g, ''); // Remove letters
            debugLog(`🔍 [PC URL] Original cardNumber: "${cardNumber}" (length: ${cardNumber.length})`);
            debugLog(`🔍 [PC URL] Base cardNumber after letter removal: "${baseCardNumber}"`);

            let cardNumberVariations = [];

            // Check if this is a letter-number pattern (like "RC24", "GG69") - letters at beginning
            if (/[A-Z]/.test(cardNumber)) {
                // For letter-number patterns, keep the original format
                cardNumberVariations = [cardNumber, baseCardNumber];
                debugLog(`🔍 Letter-number pattern detected: "${cardNumber}" - keeping original format`);
            } else {
                // For numeric patterns (like "177", "177a"), create variations
                cardNumberVariations = [
                    baseCardNumber, // Numeric part only: "177" from "177a"
                    parseInt(baseCardNumber, 10).toString(), // Remove leading zeros: "177"
                    baseCardNumber.padStart(3, '0') // Add leading zeros: "177" -> "177"
                ].filter(variation => variation && !isNaN(variation) && variation !== 'NaN'); // Filter out invalid variations
            }

            // Remove duplicates
            const uniqueCardNumbers = [...new Set(cardNumberVariations)];
            debugLog(`🔍 Card number variations to try: ${uniqueCardNumbers.join(', ')}`);
            if (cardNumber !== baseCardNumber && /[a-zA-Z]/.test(cardNumber)) {
                debugLog(`🔍 Alternate art detected: "${cardNumber}" -> using "${baseCardNumber}" for API search`);
            }

            // Search each target set to find the card and get set info
            for (const set of matchingSets) {
                // Try each card number variation
                for (const cardNum of uniqueCardNumbers) {
                    try {
                const fullCardUrl = `https://api.tcgdex.net/v2/en/cards/${set.id}-${cardNum}`;
                const response = await fetch(fullCardUrl, {
                    method: 'GET',
                    headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                    }
                });

                if (response.ok) {
                    const fullCard = await response.json();
                    if (fullCard && fullCard.name) {
                    allFoundCards.push({
                        card: fullCard,
                        setName: set.name
                    });
                    debugLog(`🎯 Found card: ${fullCard.name} in set ${set.name} with card number ${cardNum}`);
                    break; // Found card in this set, no need to try other variations
                    }
                }
                } catch (error) {
                // Continue to next variation
                }
                }
            }

            // Find the best match using title similarity
            if (allFoundCards.length > 0) {
                const listingInfo = extractListingInfo(listingElement);

                if (allFoundCards.length === 1) {
                foundCard = allFoundCards[0].card;
                setName = allFoundCards[0].setName;
                debugLog(`🎯 Single card found for PriceCharting: ${foundCard.name} from ${setName}`);
                } else if (listingInfo.title) {
                let bestSimilarity = -1;
                let bestMatch = allFoundCards[0];

                debugLog(`🔍 Comparing ${allFoundCards.length} cards for PriceCharting URL...`);

                allFoundCards.forEach((cardData, index) => {
                    const similarity = calculateTitleSimilarity(listingInfo.title, cardData.card.name);
                    debugLog(`  ${index + 1}. ${cardData.card.name} from ${cardData.setName} - Similarity: ${(similarity * 100).toFixed(1)}%`);

                    if (similarity > bestSimilarity) {
                    bestSimilarity = similarity;
                    bestMatch = cardData;
                    }
                });

                foundCard = bestMatch.card;
                setName = bestMatch.setName;
                debugLog(`🎯 Best match for PriceCharting: ${foundCard.name} from ${setName} (${(bestSimilarity * 100).toFixed(1)}% similarity)`);
                } else {
                foundCard = allFoundCards[0].card;
                setName = allFoundCards[0].setName;
                debugLog(`🎯 Using first card for PriceCharting (no title): ${foundCard.name} from ${setName}`);
                }
            }

            if (!foundCard || !setName) {
                debugLog('Card not found for PriceCharting URL construction');
                return null; // Let caller handle fallback
            }

            // Now construct the URL with the found card data
            let cleanSetName;

            // Check if we have a hardcoded mapping first
            if (PRICECHARTING_SET_MAPPING[setName]) {
                cleanSetName = PRICECHARTING_SET_MAPPING[setName];
                debugLog(`🔧 Using hardcoded mapping: "${setName}" -> "${cleanSetName}"`);
            } else {
                // Fall back to automatic cleaning
                cleanSetName = setName
                .toLowerCase()
                .replace(/pokémon/g, 'pokemon')
                .replace(/[^a-z0-9\s]/g, '')
                .replace(/\s+/g, '-')
                .replace(/-+/g, '-')
                .replace(/^-|-$/g, '');
                debugLog(`🔧 Using automatic cleaning: "${setName}" -> "${cleanSetName}"`);
            }

            // Check if this is a Mega Evolution card (starts with "M ")
            const isMegaCard = /^m\s+/i.test(foundCard.name);
            
            const cleanCardName = foundCard.name
                .toLowerCase()
                .replace(/^m\s+/i, 'm-') // Replace "M " prefix with "m-" (e.g., "M Charizard EX" -> "m-charizard ex")
                .replace(/'/g, '') // Remove apostrophes
                .replace(/&/g, '-&-') // Replace & with -&- to preserve it in URL
                .replace(/[^a-z0-9\s&-]/g, '') // Keep letters, numbers, spaces, &, and hyphens
                .replace(/\s+/g, '-')
                .replace(/-+/g, '-')
                .replace(/^-|-$/g, '');

            // For Mega cards, also create the "mega-" variant
            const megaVariantName = isMegaCard ? cleanCardName.replace(/^m-/, 'mega-') : null;

            // Check eBay title for special indicators and modifiers
            let finalCardName = cleanCardName;
            const listingInfo = extractListingInfo(listingElement);
            if (listingInfo.title) {
                const titleUpper = listingInfo.title.toUpperCase();

                // Check for Gold Star variants (highest priority for rare cards)
                if (titleUpper.includes('GOLD STAR') || titleUpper.includes('GOLDSTAR')) {
                    finalCardName = `${cleanCardName}-gold-star`;
                    debugLog(`🔧 Gold Star detected in title - adding "gold-star": "${cleanCardName}" -> "${finalCardName}"`);
                }
                // Check for Pokemon Center variants
                else if (['POKEMON CENTER', 'POKÉMON CENTER'].some(variant => titleUpper.includes(variant))) {
                    finalCardName = `${cleanCardName}-pokemon-center`;
                    debugLog(`🔧 Pokemon Center detected in title - adding "pokemon-center": "${cleanCardName}" -> "${finalCardName}"`);
                }
                // Check for standalone STAMPED (but not if part of "Stamped Reverse Holo")
                else if (titleUpper.includes('STAMPED') && !titleUpper.includes('REVERSE')) {
                    finalCardName = `${cleanCardName}-stamped`;
                    debugLog(`🔧 Stamped detected in title - adding "stamped": "${cleanCardName}" -> "${finalCardName}"`);
                }
                // Check for First Edition
                else if (['FIRST EDITION', '1ST EDITION'].some(variant => titleUpper.includes(variant))) {
                    finalCardName = `${cleanCardName}-1st-edition`;
                    debugLog(`🔧 First Edition detected in title - adding "1st-edition": "${cleanCardName}" -> "${finalCardName}"`);
                }
            }

            // Handle card number - preserve letters for alternate arts (like 177a) and promo cards (like SWSH291)
            let cleanCardNumber;
            if (/\d+[a-zA-Z]$/.test(cardNumber)) {
                // Card number ends with a letter (e.g., "177a") - preserve it for alternate arts
                cleanCardNumber = cardNumber.toLowerCase();
                debugLog(`🔧 Alternate art detected - preserving letter: "${cardNumber}" -> "${cleanCardNumber}"`);
            } else if (/^(SWSH|SM)[0-9]+$/i.test(cardNumber)) {
                // Promo card (e.g., "SWSH291", "SM241") - preserve exactly as-is
                cleanCardNumber = cardNumber.toLowerCase();
                debugLog(`🔧 Promo card detected - preserving format: "${cardNumber}" -> "${cleanCardNumber}"`);
            } else if (/^[A-Z]{1,4}\d+$/i.test(cardNumber)) {
                // Letter-number pattern (e.g., "TG29", "SV107", "RC5") - preserve exactly as-is
                cleanCardNumber = cardNumber.toLowerCase();
                debugLog(`🔧 Letter-number pattern detected - preserving format: "${cardNumber}" -> "${cleanCardNumber}"`);
            } else {
                // Standard numeric card number - normalize it
                cleanCardNumber = parseInt(cardNumber, 10).toString();
            }

            const setPrefix = cleanSetName.startsWith('pokemon-') ? '' : 'pokemon-';

            // Build base URL - don't add query parameters as they cause search redirects
            let finalUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${finalCardName}-${cleanCardNumber}`;
            
            // If this is a Mega card, store the alternative URL for retry
            let alternativeUrl = null;
            if (megaVariantName && showPricePage && requestKey) {
                alternativeUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${megaVariantName}-${cleanCardNumber}#full-prices&ebay_request=${requestKey}`;
                
                // Store the alternative URL in the request data
                const storedData = GM_getValue(requestKey);
                if (storedData) {
                    storedData.alternativeUrl = alternativeUrl;
                    storedData.triedAlternative = false;
                    GM_setValue(requestKey, storedData);
                    debugLog(`💾 Stored alternative URL for Mega card: ${alternativeUrl}`);
                }
            }
            
            debugLog(`🔍 [createPriceChartingUrl] showPricePage: ${showPricePage}`);
            debugLog(`🔍 [createPriceChartingUrl] requestKey: ${requestKey}`);
            
            if (showPricePage) {
                // Put ebay_request in the hash to survive redirects
                finalUrl += `#full-prices&ebay_request=${requestKey}`;
                debugLog(`✅ [createPriceChartingUrl] Added ebay_request to hash`);
            } else {
                debugLog(`ℹ️ [createPriceChartingUrl] Skipping ebay_request (view mode)`);
            }

            debugLog(`📋 Card: ${foundCard.name} from ${setName}`);
            debugLog(`🔗 Generated final URL: ${finalUrl}`);
            if (alternativeUrl) {
                debugLog(`🔗 Alternative URL available: ${alternativeUrl}`);
            }
            return finalUrl;

            } catch (error) {
                debugLog('Error in createPriceChartingUrl:', error);
                return null; // Let caller handle fallback
            }
        }

        // Helper function to build PriceCharting URL from a found card
        function buildPriceChartingUrlFromCard(foundCard, setName, requestKey, showPricePage, listingElement) {
            try {
                debugLog(`🔗 Building PriceCharting URL from found card: ${foundCard.name} from ${setName}`);

                // Now construct the URL with the found card data
                let cleanSetName;

                // Check if we have a hardcoded mapping first
                if (PRICECHARTING_SET_MAPPING[setName]) {
                    cleanSetName = PRICECHARTING_SET_MAPPING[setName];
                    debugLog(`🔧 Using hardcoded mapping: "${setName}" -> "${cleanSetName}"`);
                } else {
                    // Fall back to automatic cleaning
                    cleanSetName = setName
                    .toLowerCase()
                    .replace(/pokémon/g, 'pokemon')
                    .replace(/[^a-z0-9\s]/g, '')
                    .replace(/\s+/g, '-')
                    .replace(/-+/g, '-')
                    .replace(/^-|-$/g, '');
                    debugLog(`🔧 Using automatic cleaning: "${setName}" -> "${cleanSetName}"`);
                }

                // Check if this is a Mega Evolution card (starts with "M ")
                const isMegaCard = /^m\s+/i.test(foundCard.name);
                
                const cleanCardName = foundCard.name
                    .toLowerCase()
                    .replace(/^m\s+/i, 'm-') // Replace "M " prefix with "m-" (e.g., "M Charizard EX" -> "m-charizard ex")
                    .replace(/'/g, '') // Remove apostrophes
                    .replace(/&/g, '-&-') // Replace & with -&- to preserve it in URL
                    .replace(/[^a-z0-9\s&-]/g, '') // Keep letters, numbers, spaces, &, and hyphens
                    .replace(/\s+/g, '-')
                    .replace(/-+/g, '-')
                    .replace(/^-|-$/g, '');

                // For Mega cards, also create the "mega-" variant
                const megaVariantName = isMegaCard ? cleanCardName.replace(/^m-/, 'mega-') : null;

                // Check eBay title for special indicators and modifiers
                let finalCardName = cleanCardName;
                const listingInfo = extractListingInfo(listingElement);
                if (listingInfo.title) {
                    const titleUpper = listingInfo.title.toUpperCase();

                    // Check for Pokemon Center variants
                    if (titleUpper.includes('POKEMON CENTER') || titleUpper.includes('POKÉMON CENTER')) {
                        finalCardName = `${cleanCardName}-pokemon-center`;
                        debugLog(`🔧 Pokemon Center detected in title - adding "pokemon-center": "${cleanCardName}" -> "${finalCardName}"`);
                    }
                    // Check for standalone STAMPED (but not if part of "Stamped Reverse Holo")
                    else if (titleUpper.includes('STAMPED') && !titleUpper.includes('REVERSE')) {
                        finalCardName = `${cleanCardName}-stamped`;
                        debugLog(`🔧 Stamped detected in title - adding "stamped": "${cleanCardName}" -> "${finalCardName}"`);
                    }
                    // Check for First Edition
                    else if (titleUpper.includes('FIRST EDITION') || titleUpper.includes('1ST EDITION')) {
                        finalCardName = `${cleanCardName}-1st-edition`;
                        debugLog(`🔧 First Edition detected in title - adding "1st-edition": "${cleanCardName}" -> "${finalCardName}"`);
                    }
                }

                // Use the localId from the found card for the card number
                let cleanCardNumber = foundCard.localId;
                if (/\d+[a-zA-Z]$/.test(cleanCardNumber)) {
                    // Card number ends with a letter (e.g., "177a") - preserve it for alternate arts
                    cleanCardNumber = cleanCardNumber.toLowerCase();
                    debugLog(`🔧 Alternate art detected - preserving letter: "${foundCard.localId}" -> "${cleanCardNumber}"`);
                } else if (/^(SWSH|SM)[0-9]+$/i.test(cleanCardNumber)) {
                    // Promo card (e.g., "SWSH291", "SM241") - preserve exactly as-is
                    cleanCardNumber = cleanCardNumber.toLowerCase();
                    debugLog(`🔧 Promo card detected - preserving format: "${foundCard.localId}" -> "${cleanCardNumber}"`);
                } else if (/^[A-Z]{1,4}\d+$/i.test(cleanCardNumber)) {
                    // Letter-number pattern (e.g., "TG29", "SV107", "RC5") - preserve exactly as-is
                    cleanCardNumber = cleanCardNumber.toLowerCase();
                    debugLog(`🔧 Letter-number pattern detected - preserving format: "${foundCard.localId}" -> "${cleanCardNumber}"`);
                } else {
                    // Standard numeric card number - normalize it
                    cleanCardNumber = parseInt(foundCard.localId, 10).toString();
                }

                const setPrefix = cleanSetName.startsWith('pokemon-') ? '' : 'pokemon-';

                // Build base URL - don't add query parameters as they cause search redirects
                let finalUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${finalCardName}-${cleanCardNumber}`;
                
                // If this is a Mega card, store the alternative URL for retry
                let alternativeUrl = null;
                if (megaVariantName && showPricePage && requestKey) {
                    alternativeUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${megaVariantName}-${cleanCardNumber}#full-prices&ebay_request=${requestKey}`;
                    
                    // Store the alternative URL in the request data
                    const storedData = GM_getValue(requestKey);
                    if (storedData) {
                        storedData.alternativeUrl = alternativeUrl;
                        storedData.triedAlternative = false;
                        GM_setValue(requestKey, storedData);
                        debugLog(`💾 Stored alternative URL for Mega card: ${alternativeUrl}`);
                    }
                }
                
                debugLog(`🔍 [buildPriceChartingUrlFromCard] showPricePage: ${showPricePage}`);
                debugLog(`🔍 [buildPriceChartingUrlFromCard] requestKey: ${requestKey}`);
                
                if (showPricePage) {
                    // Put ebay_request in the hash to survive redirects
                    finalUrl += `#full-prices&ebay_request=${requestKey}`;
                    debugLog(`✅ [buildPriceChartingUrlFromCard] Added ebay_request to hash`);
                } else {
                    debugLog(`ℹ️ [buildPriceChartingUrlFromCard] Skipping ebay_request (view mode)`);
                }

                debugLog(`📋 Card: ${foundCard.name} from ${setName}`);
                debugLog(`🔗 Generated PriceCharting URL: ${finalUrl}`);
                if (alternativeUrl) {
                    debugLog(`🔗 Alternative URL available: ${alternativeUrl}`);
                }
                return finalUrl;

            } catch (error) {
                debugLog('Error in buildPriceChartingUrlFromCard:', error);
                return null;
            }
        }

        // Track active listeners to prevent duplicates and allow cancellation
        const activeListeners = new Map();

        // Enhanced listener with Promise-based async/await for better control
        function setupPriceChartingDataListener(requestKey, listingElement) {
            // Cancel any existing listener for this request key
            if (activeListeners.has(requestKey)) {
                debugLog(`🛑 Cancelling existing listener for ${requestKey}`);
                const existingListener = activeListeners.get(requestKey);
                existingListener.cancelled = true;
                clearTimeout(existingListener.timeoutId);
            }

            return new Promise((resolve, reject) => {
                let attempts = 0;
                const maxAttempts = TIMING.POLL_MAX_ATTEMPTS;
                let timeoutId = null;
                const listenerControl = { cancelled: false, timeoutId: null };

                // Store this listener
                activeListeners.set(requestKey, listenerControl);

                debugLog(`🔄 Setting up listener for request key: ${requestKey}`);

                const checkForData = () => {
                    // Check if this listener was cancelled
                    if (listenerControl.cancelled) {
                        debugLog(`🛑 Listener for ${requestKey} was cancelled`);
                        activeListeners.delete(requestKey);
                        reject(new Error('Listener cancelled'));
                        return;
                    }

                    attempts++;
                    debugLog(`📡 Checking for PriceCharting data... attempt ${attempts}/${maxAttempts}`);

                    const data = getStoredData(`${requestKey}_data`);

                    if (data) {
                        debugLog(`📊 Received PriceCharting data after ${attempts} attempts:`, data);

                        // Clean up
                        activeListeners.delete(requestKey);

                        // Cache the data for future use (extract card info from stored request)
                        const storedRequest = getStoredData(requestKey);
                        if (storedRequest && data.url) {
                            // Detect grade from the original eBay title
                            const detectedGrade = detectGradeFromTitle(storedRequest.ebayTitle || '');
                            const gradeKey = detectedGrade ? detectedGrade.key : 'ungraded';
                            
                            const baseUrl = data.url.split('?')[0];
                            const cacheKey = `${storedRequest.cardNumber}_${storedRequest.setNumber}_${gradeKey}_${baseUrl}`;
                            priceChartingCache.set(cacheKey, {
                                data: data,
                                timestamp: Date.now()
                            });
                            debugLog(`💾 Cached data for key: ${cacheKey} (grade: ${gradeKey})`);
                        }

                        // Verify the listing element still exists
                        if (listingElement && listingElement.parentNode) {
                            updateListingWithPriceChartingData(listingElement, data);

                            // Add a success indicator to the PC button
                            const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                            if (pcButton) {
                                pcButton.style.background = '#27ae60'; // Green to indicate success
                                pcButton.title = `✅ PriceCharting data loaded - ${Object.keys(data.prices || {}).length} prices found`;
                            }
                        } else {
                            console.warn('⚠️ Listing element no longer exists in DOM');
                        }
                        resolve(data);
                        return;
                    }

                    if (attempts < maxAttempts) {
                        timeoutId = setTimeout(checkForData, TIMING.POLL_INTERVAL);
                        listenerControl.timeoutId = timeoutId;
                    } else {
                        debugLog('⏰ Timeout waiting for PriceCharting data');

                        // Clean up
                        activeListeners.delete(requestKey);

                        // Update button to show timeout
                        const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                        if (pcButton) {
                            pcButton.style.background = '#e67e22'; // Orange to indicate timeout
                            pcButton.title = '⏰ Timeout waiting for PriceCharting data';
                        }
                        reject(new Error('Timeout waiting for PriceCharting data'));
                    }
                };

                // Start checking after a short delay to allow PriceCharting page to load
                timeoutId = setTimeout(checkForData, 1000); // Wait 1 second before first check
                listenerControl.timeoutId = timeoutId;
            });
        }

        // Wrapper function for updating price display (used by manual edit and automatic updates)
        function updatePriceDisplay(listingElement, pcData, detectedGrade) {
            updateListingWithPriceChartingData(listingElement, pcData);
        }

        // Update listing with PriceCharting data - Enhanced version with grade detection
        function updateListingWithPriceChartingData(listingElement, pcData) {
            debugLog('📊 Updating eBay listing with PriceCharting data:', pcData);

            // Create or update a PriceCharting info display
            let pcInfoDisplay = listingElement.querySelector('.pc-info-display');

            if (!pcInfoDisplay) {
                pcInfoDisplay = document.createElement('div');
                pcInfoDisplay.className = 'pc-info-display';

                Object.assign(pcInfoDisplay.style, {
                    display: 'block',
                    marginTop: '4px',
                    padding: '6px 10px',
                    background: '#9b59b6',
                    color: 'white',
                    borderRadius: '4px',
                    fontSize: '12px',
                    fontWeight: 'bold',
                    textAlign: 'left',
                    lineHeight: '1.4',
                    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
                    border: '1px solid #8e44ad'
                });

                // Find the best place to insert the display
                const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');

                if (pcButton && pcButton.parentNode) {
                    pcButton.parentNode.insertBefore(pcInfoDisplay, pcButton.nextSibling);
                } else if (priceElement && priceElement.parentNode) {
                    priceElement.parentNode.insertBefore(pcInfoDisplay, priceElement.nextSibling);
                } else {
                    // Fallback: append to the listing element itself
                    listingElement.appendChild(pcInfoDisplay);
                }

                debugLog('✅ Created new PC info display element');
            }

            // Get eBay title to detect grade
            const listingInfo = extractListingInfo(listingElement);
            const detectedGrade = detectGradeFromTitle(listingInfo.title);

            debugLog('🔍 Detected grade from eBay title:', detectedGrade);
            debugLog('🔍 eBay title:', listingInfo.title);

            // Format the PriceCharting data to show only the relevant grade
            let displayLines = [];
            let hasValidPrices = false;

            if (pcData.prices && Object.keys(pcData.prices).length > 0) {
                debugLog('Available price keys:', Object.keys(pcData.prices));

                // If grade detected from title, show that grade + ungraded as baseline
                if (detectedGrade) {
                    debugLog(`Looking for detected grade key: ${detectedGrade.key}`);

                    // Show the specific detected grade (highlighted)
                    if (pcData.prices[detectedGrade.key]) {
                        const gradePrice = pcData.prices[detectedGrade.key];
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: ${gradePrice.price}</span>`);
                        hasValidPrices = true;
                        debugLog(`Added detected grade: ${detectedGrade.displayName}: ${gradePrice.price}`);
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: Unpriced</span>`);
                        debugLog(`Detected grade ${detectedGrade.displayName} not found - showing Unpriced`);
                    }

                    // Show ungraded as baseline reference
                    if (pcData.prices['ungraded']) {
                        const ungradedPrice = pcData.prices['ungraded'];
                        displayLines.push(`Ungraded: ${ungradedPrice.price}`);
                        hasValidPrices = true;
                        debugLog(`Added ungraded baseline: ${ungradedPrice.price}`);
                    } else {
                        displayLines.push(`Ungraded: Unpriced`);
                        debugLog('Ungraded price not found for baseline');
                    }
                } else {
                    // No grade detected, show only ungraded price (highlighted)
                    if (pcData.prices['ungraded']) {
                        const ungradedPrice = pcData.prices['ungraded'];
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: ${ungradedPrice.price}</span>`);
                        hasValidPrices = true;
                        debugLog(`Added ungraded: ${ungradedPrice.price}`);
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: Unpriced</span>`);
                        debugLog('Ungraded price not found - showing Unpriced');
                    }
                }

                // Add total count if there are more prices available
                const totalPrices = Object.keys(pcData.prices).length;
                const shownPrices = displayLines.length;
                if (totalPrices > shownPrices) {
                    displayLines.push(`+${totalPrices - shownPrices} more grades available`);
                }
            } else {
                if (detectedGrade) {
                    displayLines.push(`<strong>${detectedGrade.displayName}: Unpriced</strong>`);
                } else {
                    displayLines.push('<strong>Ungraded: Unpriced</strong>');
                }
                debugLog('⚠️ No valid prices found in data');
            }

            // Add card name if extracted
            let headerText = 'PC:';
            const displayCardName = pcData.extractedCardName || pcData.cardName || 'Unknown Card';
            if (displayCardName && displayCardName !== 'Unknown' && displayCardName !== 'Unknown Card') {
                // Add detected grade to the name if available
                if (detectedGrade) {
                    if (detectedGrade.key.toUpperCase().includes('BGS') && parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (👑${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (💎${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.5) {
                        headerText = `${displayCardName} (⭐${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.0) {
                        headerText = `${displayCardName} (✨${detectedGrade.displayName})`;
                    } else {
                        headerText = `${displayCardName} (${detectedGrade.displayName})`;
                    }
                } else {
                    headerText = `${displayCardName}`;
                }
            }

            // Add set name if available
            let setNameLine = '';
            if (pcData.extractedSetName) {
                setNameLine = `<div style="font-size: 12px; opacity: 0.8; margin-top: 2px; font-weight: bold;">${pcData.extractedSetName}</div>`;
            }

            // Add card image if available (on the right side)
            let imageHtml = '';
            if (pcData.imageUrl) {
                imageHtml = `<img src="${pcData.imageUrl}" alt="Card Image" style="float: right; width: 60px; height: auto; margin-left: 8px; margin-top: -4px; border-radius: 3px; border: 1px solid rgba(255,255,255,0.3);">`;
            }

            // Update the display content
            pcInfoDisplay.innerHTML = `${imageHtml}<strong>${headerText}</strong>${setNameLine}<br>${displayLines.join('<br>')}`;

            // Create detailed tooltip with all prices
            let tooltipContent = `PriceCharting Data:\n`;
            tooltipContent += `Card: ${pcData.extractedCardName || pcData.cardName || 'Unknown'}\n`;
            if (pcData.extractedCardNumber) {
                tooltipContent += `Number: #${pcData.extractedCardNumber}\n`;
            }
            if (detectedGrade) {
                tooltipContent += `Detected Grade: ${detectedGrade.displayName}\n`;
            }
            tooltipContent += `URL: ${pcData.url}\n\n`;

            if (pcData.prices && Object.keys(pcData.prices).length > 0) {
                tooltipContent += `All Available Prices:\n`;
                Object.entries(pcData.prices).forEach(([key, priceInfo]) => {
                    tooltipContent += `  ${priceInfo.grade}: ${priceInfo.price}\n`;
                });
            } else {
                tooltipContent += `No prices currently available\n`;
            }

            if (pcData.lastUpdated) {
                tooltipContent += `\nLast Updated: ${pcData.lastUpdated}`;
            }

            tooltipContent += `\nExtracted: ${new Date(pcData.timestamp).toLocaleString()}`;

            pcInfoDisplay.title = tooltipContent;

            // Make the display more visible with a subtle animation
            pcInfoDisplay.style.opacity = '0';
            setTimeout(() => {
                pcInfoDisplay.style.transition = 'opacity 0.3s ease-in-out';
                pcInfoDisplay.style.opacity = '1';
            }, 100);

            debugLog(`✅ Updated PC display with ${displayLines.length} lines:`, displayLines);

            // Cache the data (not HTML) for future page loads
            const listingUrl = getListingUrl(listingElement);
            debugLog(`🔑 Attempting to cache - listingUrl: ${listingUrl ? listingUrl.substring(0, 80) : 'NULL'}`);
            if (listingUrl) {
                debugLog(`💾 Storing cache with keys:`, {
                    cardName: pcData.cardName,
                    setName: pcData.setName,
                    hasPrices: !!pcData.prices,
                    hasGrade: !!detectedGrade
                });
                storeListingDisplayCache(listingUrl, {
                    cardName: pcData.cardName,
                    setName: pcData.setName,
                    prices: pcData.prices,
                    detectedGrade: detectedGrade,
                    extractedCardName: pcData.extractedCardName,
                    extractedSetName: pcData.extractedSetName,
                    extractedCardNumber: pcData.extractedCardNumber,
                    lastUpdated: pcData.lastUpdated,
                    url: pcData.url,
                    imageUrl: pcData.imageUrl
                });
                debugLog(`✅ Cache stored successfully!`);
            } else {
                debugLog(`❌ Cannot cache - listingUrl is null!`);
            }

            // Color the eBay price based on PriceCharting comparison
            colorEbayPriceBasedOnComparison(listingElement, pcData, detectedGrade);
        }

        // Helper function to build display HTML from cached data
        function buildDisplayFromCache(cachedData) {
            const displayLines = [];
            const detectedGrade = cachedData.detectedGrade;
            let hasValidPrices = false;

            if (cachedData.prices && Object.keys(cachedData.prices).length > 0) {
                if (detectedGrade) {
                    // Show detected grade price first (highlighted)
                    const gradePrice = cachedData.prices[detectedGrade.key];
                    if (gradePrice) {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: ${gradePrice.price}</span>`);
                        hasValidPrices = true;
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: Unpriced</span>`);
                    }

                    // Show ungraded as baseline reference
                    if (cachedData.prices['ungraded']) {
                        displayLines.push(`Ungraded: ${cachedData.prices['ungraded'].price}`);
                        hasValidPrices = true;
                    } else {
                        displayLines.push(`Ungraded: Unpriced`);
                    }
                } else {
                    // No grade detected, show only ungraded price (highlighted)
                    if (cachedData.prices['ungraded']) {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: ${cachedData.prices['ungraded'].price}</span>`);
                        hasValidPrices = true;
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: Unpriced</span>`);
                    }
                }

                // Add total count if there are more prices available
                const totalPrices = Object.keys(cachedData.prices).length;
                const shownPrices = displayLines.length;
                if (totalPrices > shownPrices) {
                    displayLines.push(`+${totalPrices - shownPrices} more grades available`);
                }
            } else {
                if (detectedGrade) {
                    displayLines.push(`<strong>${detectedGrade.displayName}: Unpriced</strong>`);
                } else {
                    displayLines.push('<strong>Ungraded: Unpriced</strong>');
                }
            }

            // Build header text
            let headerText = 'PC:';
            const displayCardName = cachedData.extractedCardName || cachedData.cardName || 'Unknown Card';
            if (displayCardName && displayCardName !== 'Unknown' && displayCardName !== 'Unknown Card') {
                if (detectedGrade) {
                    if (detectedGrade.key.toUpperCase().includes('BGS') && parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (👑${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (💎${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.5) {
                        headerText = `${displayCardName} (⭐${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.0) {
                        headerText = `${displayCardName} (✨${detectedGrade.displayName})`;
                    } else {
                        headerText = `${displayCardName} (${detectedGrade.displayName})`;
                    }
                } else {
                    headerText = `${displayCardName}`;
                }
            }

            // Add set name if available
            let setNameLine = '';
            if (cachedData.extractedSetName) {
                setNameLine = `<div style="font-size: 12px; opacity: 0.8; margin-top: 2px; font-weight: bold;">${cachedData.extractedSetName}</div>`;
            }

            // Add card image if available (on the right side)
            let imageHtml = '';
            if (cachedData.imageUrl) {
                imageHtml = `<img src="${cachedData.imageUrl}" alt="Card Image" style="float: right; width: 60px; height: auto; margin-left: 8px; margin-top: -4px; border-radius: 3px; border: 1px solid rgba(255,255,255,0.3);">`;
            }

            // Build tooltip
            let tooltipContent = `PriceCharting Data:\n`;
            tooltipContent += `Card: ${cachedData.extractedCardName || cachedData.cardName || 'Unknown'}\n`;
            if (cachedData.extractedCardNumber) {
                tooltipContent += `Number: #${cachedData.extractedCardNumber}\n`;
            }
            if (detectedGrade) {
                tooltipContent += `Detected Grade: ${detectedGrade.displayName}\n`;
            }
            tooltipContent += `URL: ${cachedData.url}\n\n`;

            if (cachedData.prices && Object.keys(cachedData.prices).length > 0) {
                tooltipContent += `All Available Prices:\n`;
                Object.entries(cachedData.prices).forEach(([key, priceInfo]) => {
                    tooltipContent += `  ${priceInfo.grade}: ${priceInfo.price}\n`;
                });
            } else {
                tooltipContent += `No prices currently available\n`;
            }

            if (cachedData.lastUpdated) {
                tooltipContent += `\nLast Updated: ${cachedData.lastUpdated}`;
            }

            tooltipContent += `\nExtracted: ${new Date(cachedData.timestamp).toLocaleString()}`;

            return {
                html: `${imageHtml}<strong>${headerText}</strong>${setNameLine}<br>${displayLines.join('<br>')}`,
                tooltip: tooltipContent
            };
        }

        // Function to color eBay price based on PriceCharting data comparison
        function colorEbayPriceBasedOnComparison(listingElement, pcData, detectedGrade) {
            try {
                // Find the eBay price element
                const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .su-styled-text.primary.bold.large-1.s-card__price');
                if (!priceElement) {
                    debugLog('🔍 eBay price element not found for comparison');
                    return;
                }
                
                // Always check and remove existing modifications before updating
                const existingPercentage = priceElement.querySelector('.price-percentage');
                if (existingPercentage) {
                    existingPercentage.remove();
                    debugLog('🧹 Removed existing .price-percentage');
                }
                
                // If the price element has been modified, reset it to get the raw price
                const styledPriceSpan = Array.from(priceElement.children).find(el => 
                    el.tagName === 'SPAN' && el.style.color && el.textContent.includes('$')
                );
                if (styledPriceSpan) {
                    // Extract just the price number
                    const priceMatch = priceElement.textContent.match(/\$[\d,]+\.?\d*/);
                    if (priceMatch) {
                        priceElement.textContent = priceMatch[0];
                        debugLog(`🧹 Reset price element to: ${priceMatch[0]}`);
                    }
                }

                // Extract the eBay price value
                const priceText = priceElement.textContent.trim();
                const priceMatch = priceText.match(/\$?([\d,]+\.?\d*)/);
                if (!priceMatch) {
                    debugLog('🔍 Could not parse eBay price:', priceText);
                    return;
                }

                const ebayPrice = parseFloat(priceMatch[1].replace(/,/g, ''));
                debugLog(`💰 eBay price: $${ebayPrice}`);

                // Determine which PriceCharting price to compare against
                let comparisonPrice = null;
                let comparisonGrade = 'Ungraded';

                if (pcData.prices && Object.keys(pcData.prices).length > 0) {
                    if (detectedGrade && pcData.prices[detectedGrade.key]) {
                        // Use detected grade price
                        const gradeData = pcData.prices[detectedGrade.key];
                        const gradePriceMatch = gradeData.price.match(/\$?([\d,]+\.?\d*)/);
                        if (gradePriceMatch) {
                            comparisonPrice = parseFloat(gradePriceMatch[1].replace(/,/g, ''));
                            comparisonGrade = detectedGrade.displayName;
                        }
                    } else if (pcData.prices['ungraded']) {
                        // Use ungraded price as fallback
                        const ungradedData = pcData.prices['ungraded'];
                        const ungradedPriceMatch = ungradedData.price.match(/\$?([\d,]+\.?\d*)/);
                        if (ungradedPriceMatch) {
                            comparisonPrice = parseFloat(ungradedPriceMatch[1].replace(/,/g, ''));
                            comparisonGrade = 'Ungraded';
                        }
                    } else {
                        // Use first available price
                        const firstPrice = Object.entries(pcData.prices)[0];
                        if (firstPrice) {
                            const [key, priceData] = firstPrice;
                            const firstPriceMatch = priceData.price.match(/\$?([\d,]+\.?\d*)/);
                            if (firstPriceMatch) {
                                comparisonPrice = parseFloat(firstPriceMatch[1].replace(/,/g, ''));
                                comparisonGrade = priceData.grade;
                            }
                        }
                    }
                }

                if (comparisonPrice === null) {
                    debugLog('🔍 No valid PriceCharting price found for comparison');
                    return;
                }

                debugLog(`📊 Comparing eBay $${ebayPrice} vs PriceCharting ${comparisonGrade} $${comparisonPrice}`);

                // Calculate percentage difference
                const percentageDifference = Math.abs((ebayPrice - comparisonPrice) / comparisonPrice) * 100;
                debugLog(`📊 Percentage difference: ${percentageDifference.toFixed(1)}%`);

                // Determine color based on comparison (within 5% = orange)
                let color;
                let comparison;
                if (percentageDifference <= 5) {
                    color = '#f39c12'; // Orange - within 5% (close to market value)
                    comparison = 'near';
                } else if (ebayPrice < comparisonPrice) {
                    color = '#27ae60'; // Green - good deal (more than 5% below)
                    comparison = 'below';
                } else {
                    color = '#e74c3c'; // Red - expensive (more than 5% above)
                    comparison = 'above';
                }

                // Calculate the price difference
                const priceDifference = ebayPrice - comparisonPrice;
                const percentageSign = priceDifference > 0 ? '+' : '-';
                const differenceText = priceDifference > 0 
                    ? `(+$${priceDifference.toFixed(2)})` 
                    : `(-$${Math.abs(priceDifference).toFixed(2)})`;

                // Update price element (always, since we cleaned it above if needed)
                const originalPriceText = priceElement.textContent.trim();
                
                // Clear the price element and rebuild it with styled components
                priceElement.textContent = '';
                
                // Add the original price with color
                const priceSpan = document.createElement('span');
                priceSpan.textContent = `${originalPriceText} ${differenceText}`;
                priceSpan.style.color = color;
                priceSpan.style.fontWeight = 'bold';
                priceSpan.style.textShadow = `0 0 2px ${color}`;
                
                // Add the percentage in smaller, black font
                const percentageSpan = document.createElement('span');
                percentageSpan.className = 'price-percentage';
                percentageSpan.textContent = ` ${percentageSign}${percentageDifference.toFixed(1)}%`;
                percentageSpan.style.color = 'black';
                percentageSpan.style.fontSize = '0.85em';
                percentageSpan.style.fontWeight = 'normal';
                
                priceElement.appendChild(priceSpan);
                priceElement.appendChild(percentageSpan);

                debugLog(`🎨 Colored eBay price ${comparison} PriceCharting ${comparisonGrade}: ${color} with differential: ${differenceText}`);

                // Update tooltip to include comparison info
                const originalTitle = priceElement.title || '';
                const newTitle = `${originalTitle}${originalTitle ? '\n' : ''}eBay: $${ebayPrice} (${comparison} PC ${comparisonGrade}: $${comparisonPrice})`;
                priceElement.title = newTitle;

            } catch (error) {
                debugLog('❌ Error in price comparison:', error);
            }
        }

        // Get unique identifier for a listing
        function getListingUrl(listingElement) {
            // Try to find the listing URL
            const linkElement = listingElement.querySelector('a[href*="/itm/"]');
            if (linkElement) {
                return linkElement.href.split('?')[0]; // Remove query params for consistent caching
            }
            
            // Fallback: use title as identifier
            const titleElement = listingElement.querySelector('.s-card__title .su-styled-text, [role="heading"] span, .s-item__title span, h3 span, .x-item-title-label span');
            if (titleElement) {
                return `title_${titleElement.textContent.trim()}`;
            }
            
            return null;
        }

        // Check if listing has already been processed
        function isListingProcessed(listingElement) {
            // Check if display already exists
            if (listingElement.querySelector('.pc-info-display')) {
                return true;
            }
            
            // Check if buttons already exist
            if (listingElement.querySelector('.pricecharting-direct-btn')) {
                return true;
            }
            
            return false;
        }

        // Update the addPriceChartingButtons function
        function addPriceChartingButtons() {
            const listings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card, [data-testid="listing-card"]');

            debugLog(`Found ${listings.length} listings to process`);

            if (listings.length === 0) return;

            // Process in smaller batches
            const batchSize = 5;
            let processed = 0;
            let skipped = 0;

            function processBatch() {
                const endIndex = Math.min(processed + batchSize, listings.length);

                for (let i = processed; i < endIndex; i++) {
                    const listing = listings[i];
                    
                    const listingUrl = getListingUrl(listing);
                    const info = extractListingInfo(listing);
                    
                    // Try to restore from cache first (even if buttons exist)
                    if (listingUrl) {
                        const cached = getListingDisplayCache(listingUrl);
                        if (cached && cached.prices) {
                            debugLog(`📦 Found cache for: ${listingUrl.substring(0, 80)}...`);
                            // Check if display already exists
                            if (!listing.querySelector('.pc-info-display')) {
                                debugLog(`📦 Restoring display from cached data for: ${listingUrl.substring(0, 80)}...`);
                                
                                // Build display from cached data
                                const displayContent = buildDisplayFromCache(cached);
                                
                                // Create display element
                                const pcInfoDisplay = document.createElement('div');
                                pcInfoDisplay.className = 'pc-info-display';
                                pcInfoDisplay.innerHTML = displayContent.html;
                                pcInfoDisplay.title = displayContent.tooltip;
                                
                                Object.assign(pcInfoDisplay.style, {
                                    display: 'block',
                                    marginTop: '4px',
                                    padding: '6px 10px',
                                    background: '#9b59b6',
                                    color: 'white',
                                    borderRadius: '4px',
                                    fontSize: '12px',
                                    fontWeight: 'bold',
                                    textAlign: 'left',
                                    lineHeight: '1.4',
                                    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
                                    border: '1px solid #8e44ad',
                                    opacity: '0',
                                    transition: 'opacity 0.3s ease-in-out'
                                });
                                
                                // Find the best place to insert the display
                                const priceElement = listing.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
                                if (priceElement && priceElement.parentNode) {
                                    priceElement.parentNode.insertBefore(pcInfoDisplay, priceElement.nextSibling);
                                } else {
                                    listing.appendChild(pcInfoDisplay);
                                }
                                
                                // Animate in
                                setTimeout(() => {
                                    pcInfoDisplay.style.opacity = '1';
                                }, 100);
                                
                                // Update price comparison (since eBay price can change)
                                const pcData = {
                                    cardName: cached.cardName,
                                    setName: cached.setName,
                                    prices: cached.prices,
                                    extractedCardName: cached.extractedCardName,
                                    extractedSetName: cached.extractedSetName
                                };
                                colorEbayPriceBasedOnComparison(listing, pcData, cached.detectedGrade);
                                debugLog(`🔄 Updated price comparison from cached data`);
                                
                                // Still add buttons for functionality (if not already present)
                                if (info.fullCardNumber) {
                                    addGooglePriceChartingButton(listing, info.title);
                                    addPriceChartingViewButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                                    addPriceChartingDirectButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                                    addManualEditButton(listing);
                                } else if (info.setName) {
                                    addGooglePriceChartingButton(listing, info.title);
                                    addPriceChartingDirectButton(listing, null, null, 'title-based');
                                    addManualEditButton(listing);
                                } else {
                                    addGooglePriceChartingButton(listing, info.title);
                                    addManualEditButton(listing);
                                }
                            } else {
                                // Display exists, but update price comparison dynamically
                                const pcData = {
                                    cardName: cached.cardName,
                                    setName: cached.setName,
                                    prices: cached.prices,
                                    extractedCardName: cached.extractedCardName,
                                    extractedSetName: cached.extractedSetName
                                };
                                colorEbayPriceBasedOnComparison(listing, pcData, cached.detectedGrade);
                                debugLog(`🔄 Updated price comparison from cache (display already exists)`);
                            }
                            
                            skipped++;
                            continue;
                        } else {
                            debugLog(`⚠️ No cache found for: ${listingUrl.substring(0, 80)}...`);
                        }
                    }
                    
                    // Skip if already processed and no cache available
                    if (isListingProcessed(listing)) {
                        debugLog(`⏭️ Skipping already-processed listing (has buttons/display, no cache)`);
                        skipped++;
                        continue;
                    }

                    // Debug logging for button creation
                    if (!info.fullCardNumber && info.setName) {
                        debugLog(`📋 Card info - cardNumber: ${info.cardNumber}, setName: ${info.setName}`);
                    }

                    // Add buttons if we have card number OR set name (for title-based matching)
                    if (info.fullCardNumber) {
                        addGooglePriceChartingButton(listing, info.title);
                        addPriceChartingViewButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                        addPriceChartingDirectButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                        addManualEditButton(listing);
                    } else if (info.setName) {
                        // No card number, but we have set name - add title-based search button
                        debugLog(`✅ Adding title-based search button for set: ${info.setName}`);
                        addGooglePriceChartingButton(listing, info.title);
                        addPriceChartingDirectButton(listing, null, null, 'title-based');
                        addManualEditButton(listing);
                    } else {
                        addGooglePriceChartingButton(listing, info.title);
                        addManualEditButton(listing);
                    }
                }

                processed = endIndex;

                // Schedule next batch if there are more items
                if (processed < listings.length) {
                    setTimeout(() => requestAnimationFrame(processBatch), TIMING.BATCH_PROCESSING_DELAY);
                } else if (skipped > 0) {
                    debugLog(`✅ Processing complete! Skipped ${skipped} already-processed listing(s)`);
                }
            }

            // Start processing
            processBatch();
        }

        // Create floating control panel for PriceCharting functionality
        function createPCControlPanel() {
            // Don't add multiple panels
            if (document.getElementById('pokespy-pc-panel')) return;

            const panel = document.createElement('div');
            panel.id = 'pokespy-pc-panel';
            panel.style.cssText = `
                position: fixed;
                top: 10px;
                right: 10px;
                background: #2f3136;
                color: #ffffff;
                padding: 12px;
                border-radius: 8px;
                z-index: 10000;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 12px;
                border: 2px solid #9b59b6;
                min-width: 220px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            `;

            panel.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 10px; color: #9b59b6; font-size: 14px;">💎 PokeSpy PC Control</div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-check-all-btn" style="
                        width: 100%;
                        padding: 8px 12px;
                        background: linear-gradient(45deg, #9b59b6, #8e44ad);
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 13px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">🚀 Check All Prices</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-stop-check-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #e74c3c;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">⏹️ Stop</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-clear-cache-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #95a5a6;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">🗑️ Clear Cache</button>
                </div>
                <div style="font-size: 11px; opacity: 0.8; margin-top: 8px; padding-top: 8px; border-top: 1px solid #444;">
                    <div>Status: <span id="pokespy-status" style="color: #43b581; font-weight: bold;">Ready</span></div>
                    <div id="pokespy-progress" style="margin-top: 4px; display: none;">
                        Progress: <span id="pokespy-progress-text" style="font-weight: bold;">0/0</span>
                    </div>
                </div>
            `;

            document.body.appendChild(panel);

            // Add hover effect for check all button
            const checkAllButton = document.getElementById('pokespy-check-all-btn');
            checkAllButton.addEventListener('mouseenter', () => {
                checkAllButton.style.transform = 'translateY(-2px)';
                checkAllButton.style.boxShadow = '0 4px 8px rgba(155,89,182,0.4)';
            });

            checkAllButton.addEventListener('mouseleave', () => {
                checkAllButton.style.transform = 'translateY(0)';
                checkAllButton.style.boxShadow = 'none';
            });

            // Store stop flag
            let shouldStop = false;

            // Stop button handler
            document.getElementById('pokespy-stop-check-btn').addEventListener('click', () => {
                shouldStop = true;
                document.getElementById('pokespy-status').textContent = 'Stopping...';
                document.getElementById('pokespy-status').style.color = '#e67e22';
            });

            // Clear cache button handler
            document.getElementById('pokespy-clear-cache-btn').addEventListener('click', () => {
                const confirmClear = confirm('Clear all cached listing displays? This will remove saved price data from previous sessions.');
                if (confirmClear) {
                    const keys = GM_listValues();
                    let clearedCount = 0;
                    
                    keys.forEach(key => {
                        if (key.startsWith('listing_cache_')) {
                            GM_deleteValue(key);
                            clearedCount++;
                        }
                    });
                    
                    // Also remove all existing displays from current page
                    document.querySelectorAll('.pc-info-display').forEach(display => display.remove());
                    
                    alert(`✅ Cleared ${clearedCount} cached listing(s). Page will reload.`);
                    location.reload();
                }
            });

            // Check all button handler
            checkAllButton.addEventListener('click', async () => {
                shouldStop = false;
                const statusElement = document.getElementById('pokespy-status');
                const progressElement = document.getElementById('pokespy-progress');
                const progressText = document.getElementById('pokespy-progress-text');
                const originalText = checkAllButton.innerHTML;
                const pcButtons = document.querySelectorAll('.pricecharting-direct-btn');

                if (pcButtons.length === 0) {
                    statusElement.textContent = '⚠️ No PC buttons found';
                    statusElement.style.color = '#e67e22';
                    setTimeout(() => {
                        statusElement.textContent = 'Ready';
                        statusElement.style.color = '#43b581';
                    }, 3000);
                    return;
                }

                checkAllButton.disabled = true;
                checkAllButton.innerHTML = '⏳ Processing...';
                checkAllButton.style.opacity = '0.6';
                statusElement.textContent = 'Running';
                statusElement.style.color = '#3498db';
                progressElement.style.display = 'block';
                progressText.textContent = `0/${pcButtons.length}`;

                // Log the order of buttons for debugging
                debugLog(`Processing ${pcButtons.length} PC buttons in document order:`);
                pcButtons.forEach((btn, index) => {
                    const listingTitle = btn.closest('.s-item')?.querySelector('.s-item__title')?.textContent?.slice(0, 50) || 'Unknown';
                    debugLog(`${index + 1}. ${listingTitle}... (${btn.title})`);
                });

                let processed = 0;
                let successful = 0;

                // Process buttons one by one, waiting for each to show ✅
                for (const button of pcButtons) {
                    if (shouldStop) {
                        statusElement.textContent = 'Stopped by user';
                        statusElement.style.color = '#e74c3c';
                        break;
                    }

                    if (button.disabled) continue; // Skip already processed buttons

                    try {
                        const listingElement = button.closest('.s-item, .s-card');
                        // Try multiple selectors for the title
                        const titleElement = listingElement?.querySelector('.s-item__title span, .s-item__title, .s-card__title span, .s-card__title, [role="heading"] span');
                        const listingTitle = titleElement?.textContent?.trim()?.slice(0, 40) || 'Unknown';
                        progressText.textContent = `${processed + 1}/${pcButtons.length}`;
                        debugLog(`\n🎯 Processing ${processed + 1}/${pcButtons.length}: ${listingTitle}`);

                        // Skip if already processed (has green background or cached display exists)
                        const hasCachedDisplay = listingElement?.querySelector('.pc-info-display');
                        if (button.style.background === 'rgb(39, 174, 96)' || hasCachedDisplay) {
                            debugLog(`⏭️ Skipping - ${hasCachedDisplay ? 'has cached display' : 'already processed'}`);
                            successful++;
                            processed++;
                            continue;
                        }
                        
                        // Store the original button text and state
                        const originalText = button.textContent;
                        const originalDisabled = button.disabled;
                        
                        // Create a promise that resolves when the button click completes
                        const clickPromise = new Promise(async (resolve, reject) => {
                            try {
                                // Add a one-time event listener to the button to detect when processing completes
                                let completed = false;
                                const observer = new MutationObserver((mutations) => {
                                    // Check if button background turned green (success)
                                    if (button.style.background === 'rgb(39, 174, 96)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`✅ Button ${processed + 1} completed successfully`);
                                        resolve(true);
                                    }
                                    // Check if button background turned orange (timeout/error)
                                    else if (button.style.background === 'rgb(230, 126, 34)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`⚠️ Button ${processed + 1} timed out`);
                                        resolve(false);
                                    }
                                    // Check if button background turned gray (not found/error) - skip and continue
                                    else if (button.style.background === 'rgb(149, 165, 166)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`⏭️ Button ${processed + 1} - card not found, skipping`);
                                        resolve(false);
                                    }
                                    // Check if button shows red (popup blocked)
                                    else if (button.style.background === 'rgb(231, 76, 60)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`🚫 Button ${processed + 1} - popup blocked, skipping`);
                                        resolve(false);
                                    }
                                    // Check if button is re-enabled (finished processing)
                                    else if (!button.disabled && button.textContent === originalText && !completed) {
                                        const displayExists = listingElement?.querySelector('.pc-info-display');
                                        if (displayExists) {
                                            completed = true;
                                            observer.disconnect();
                                            debugLog(`✅ Button ${processed + 1} completed (display shown)`);
                                            resolve(true);
                                        }
                                    }
                                });

                                // Observe button style and state changes
                                observer.observe(button, {
                                    attributes: true,
                                    attributeFilter: ['style', 'disabled']
                                });

                                // Also observe the listing for PC display appearance
                                if (listingElement) {
                                    const listingObserver = new MutationObserver((mutations) => {
                                        const pcDisplay = listingElement.querySelector('.pc-info-display');
                                        if (pcDisplay && pcDisplay.style.opacity === '1' && !completed) {
                                            const displayText = pcDisplay.textContent || '';
                                            if (displayText.includes('$') || displayText.includes('Unpriced')) {
                                                completed = true;
                                                observer.disconnect();
                                                listingObserver.disconnect();
                                                debugLog(`✅ Button ${processed + 1} completed (PC display visible)`);
                                                resolve(true);
                                            }
                                        }
                                    });
                                    listingObserver.observe(listingElement, {
                                        childList: true,
                                        subtree: true,
                                        attributes: true,
                                        attributeFilter: ['style']
                                    });
                                }

                                // Set a timeout in case something goes wrong
                                setTimeout(() => {
                                    if (!completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`⏰ Button ${processed + 1} timed out after 20 seconds - skipping`);
                                        resolve(false); // Resolve instead of reject to continue processing
                                    }
                                }, 20000); // Reduced to 20 second timeout per item

                                // Now click the button
                                debugLog(`🖱️ Clicking button ${processed + 1}...`);
                                button.click();
                                
                            } catch (error) {
                                debugLog(`❌ Error in click promise for button ${processed + 1}:`, error);
                                resolve(false); // Resolve instead of reject to continue
                            }
                        });

                        // Wait for the click to complete (or fail)
                        try {
                            const success = await clickPromise;
                            if (success) {
                                successful++;
                            }
                        } catch (error) {
                            debugLog(`❌ Click promise rejected for button ${processed + 1}, continuing anyway:`, error);
                            // Continue processing even if one fails
                        }

                        // Progressive delay - longer after every few items to prevent rate limiting
                        let delay = 1000; // Base delay of 1 second
                        if (processed > 0 && processed % 5 === 0) {
                            // Every 5 items, add an extra delay
                            delay = 3000;
                            debugLog(`⏸️ Taking a 3-second break after ${processed} items to prevent rate limiting...`);
                        }
                        
                        await new Promise(resolve => setTimeout(resolve, delay));

                    } catch (error) {
                        debugLog(`❌ Error processing button ${processed + 1}:`, error);
                        // Continue processing even if one item fails
                    }

                    processed++;
                }

                // Show completion status
                checkAllButton.disabled = false;
                checkAllButton.innerHTML = originalText;
                checkAllButton.style.opacity = '1';
                
                if (shouldStop) {
                    statusElement.textContent = `Stopped (${successful}/${processed})`;
                    statusElement.style.color = '#e74c3c';
                } else {
                    statusElement.textContent = `✅ Complete! ${successful}/${processed}`;
                    statusElement.style.color = '#43b581';
                }

                // Reset status after 5 seconds
                setTimeout(() => {
                    statusElement.textContent = 'Ready';
                    statusElement.style.color = '#43b581';
                    progressElement.style.display = 'none';
                }, 5000);
            });

            debugLog('✅ PokeSpy PC Control Panel created');
        }

        function addPriceResearchTools() {
            // Try immediately first
            addPriceChartingButtons();

            // Add the control panel after buttons are added
            setTimeout(() => {
                createPCControlPanel();
            }, 1000);


            // Then try a few more times quickly if no results found initially
            let attempts = 0;
            const maxAttempts = 5;

            function tryAddButtons() {
                const existingButtons = document.querySelectorAll('.google-pricecharting-btn');
                const totalListings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card').length;

                if (existingButtons.length < totalListings && attempts < maxAttempts) {
                    attempts++;
                    addPriceChartingButtons();
                    createPCControlPanel(); // Also try to add the control panel
                    setTimeout(tryAddButtons, 200);
                }
            }

            setTimeout(tryAddButtons, 100);

            // Re-add buttons when new items load (infinite scroll, etc.)
            const observer = new MutationObserver((mutations) => {
                let shouldUpdate = false;
                mutations.forEach((mutation) => {
                    if (mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach((node) => {
                            if (node.nodeType === 1 && (node.classList?.contains('s-item') || node.classList?.contains('s-card'))) {
                                shouldUpdate = true;
                            }
                        });
                    }
                });

                if (shouldUpdate) {
                    setTimeout(addPriceChartingButtons, 50);
                }
            });

            const searchResults = document.querySelector('#srp-river-results, .srp-river-results, .s-results, body');
            if (searchResults) {
                observer.observe(searchResults, { childList: true, subtree: true });
                debugLog('Observer attached to search results container');
            }
        }

        // Initialize eBay functionality
        loadSetsCache();

        // Detect what page we're on and run appropriate functions
        const currentUrl = window.location.href;
        if (currentUrl.includes('/sch/') || currentUrl.includes('/b/')) {
            debugLog('eBay search page detected');
            addPriceResearchTools();
        }
    }

    // ============================================================================
    // PRICECHARTING FUNCTIONALITY
    // ============================================================================

    // Enhanced PriceCharting functionality with better URL parameter detection
    function initializePriceChartingFunctionality() {
        debugLog('💰 Initializing PriceCharting functionality...');

        // Check multiple ways for eBay request parameter
        const fullUrl = window.location.href;
        const url = new URL(fullUrl);
        const hash = window.location.hash;
        const search = window.location.search;

        debugLog(`🔍 Looking for eBay request parameter...`);
        debugLog(`Full URL: ${fullUrl}`);
        debugLog(`Search params: ${search}`);
        debugLog(`Hash: ${hash}`);

        let requestKey = null;

        // Method 1: Check URL search parameters
        requestKey = url.searchParams.get('ebay_request');
        if (requestKey) {
            debugLog(`✅ Found ebay_request in URL params: ${requestKey}`);
        }

        // Method 2: Check hash fragment for ebay_request
        if (!requestKey && hash) {
            const ebayRequestMatch = hash.match(/[&#]ebay_request=([^&\s]+)/);
            if (ebayRequestMatch) {
                requestKey = ebayRequestMatch[1];
                debugLog(`✅ Found ebay_request in hash: ${requestKey}`);
            }
        }

        // Method 3: Check if it's anywhere in the URL (fallback)
        if (!requestKey) {
            const urlRequestMatch = fullUrl.match(/[?&#]ebay_request=([^&\s#]+)/);
            if (urlRequestMatch) {
                requestKey = urlRequestMatch[1];
                debugLog(`✅ Found ebay_request in full URL: ${requestKey}`);
            }
        }

        debugLog(`Final request key: ${requestKey || 'None found'}`);

        if (requestKey) {
            debugLog(`🔗 PriceCharting opened from eBay with request key: ${requestKey}`);

            // Get the card data from eBay
            const cardData = getStoredData(requestKey);
            if (cardData) {
                debugLog(`📋 Retrieved card data from eBay:`, cardData);
                setupPriceChartingExtraction(requestKey, cardData);
            } else {
                debugLog(`❌ No card data found for request key: ${requestKey}`);
                // List all available keys for debugging
                const allKeys = GM_listValues();
                debugLog(`Available storage keys:`, allKeys.filter(key => key.includes('pc_request')));
            }
        } else if (window.location.hash === '#full-prices' || window.location.hash.includes('full-prices')) {
            debugLog(`ℹ️ No eBay request parameter found initially - checking if it's in the hash...`);
            
            // Sometimes the parameter gets moved into the hash, check there
            const hashMatch = window.location.href.match(/ebay_request=([^&\s#]+)/);
            if (hashMatch) {
                requestKey = hashMatch[1];
                debugLog(`✅ Found ebay_request in hash/URL: ${requestKey}`);
                
                // Retry extraction with the found key
                const cardData = getStoredData(requestKey);
                if (cardData) {
                    debugLog(`📋 Retrieved card data from eBay (hash match):`, cardData);
                    setupPriceChartingExtraction(requestKey, cardData);
                    return; // Exit early, extraction is set up
                }
            }

            debugLog(`⚠️ This appears to be a manual visit or the request key was lost`);

            // Auto-close window if opened programmatically but no request key found
            // This happens when the eBay script tries to open PriceCharting but the URL parameters get lost
            if (document.referrer.includes('ebay.com') || document.referrer.includes('ebay.co.uk')) {
                debugLog(`🔄 Auto-closing PriceCharting window in 35 seconds - no request key found from eBay (waiting for manual extraction)`);
                setTimeout(() => {
                    debugLog(`🔄 Closing window now (no request key, likely lost in URL)`);
                    window.close();
                }, 35000); // Wait 35 seconds to allow manual extraction to complete and debugging
            }
        }

        // Always add general extraction for manually opened PriceCharting pages
        setTimeout(() => {
            debugLog(`🔍 Running manual extraction fallback after 2 seconds...`);
            const manualData = extractPriceChartingDataFromPage();
            if (manualData && Object.keys(manualData.prices).length > 0) {
                debugLog('📊 Manual extraction successful:', manualData);

                // Debug logging for storage decision
                debugLog(`🔍 Request key: ${requestKey || 'NONE'}`);
                const existingData = requestKey ? getStoredData(`${requestKey}_data`) : null;
                debugLog(`🔍 Existing data check: ${existingData ? 'YES' : 'NO'}`);

                // If we have a request key, ALWAYS store the data (override any existing data)
                if (requestKey) {
                    debugLog(`🔄 Storing manual extraction data for eBay request: ${requestKey}`);
                    storePriceChartingData(requestKey, manualData);

                    // Show notification that data was stored
                    showExtractionNotification(manualData, { cardName: manualData.extractedCardName });
                    
                    // Auto-close if this was an eBay request
                    if (window.location.hash.includes('full-prices')) {
                        setTimeout(() => {
                            debugLog('🔄 Auto-closing after manual extraction success');
                            window.close();
                        }, 800);
                    }
                } else {
                    debugLog(`⚠️ No request key found - cannot send data back to eBay`);
                }
            } else {
                debugLog(`⚠️ Manual extraction failed or no prices found`);
                
                // If we have a request key and this is a #full-prices page, something went wrong
                if (requestKey && window.location.hash.includes('full-prices')) {
                    debugLog(`❌ Extraction failed for request ${requestKey} - keeping window open for 30 seconds for debugging`);
                    
                    // Keep window open longer for debugging
                    setTimeout(() => {
                        debugLog('🔄 Auto-closing after extraction failure (30s delay for debugging)');
                        window.close();
                    }, 30000); // 30 seconds
                }
            }
        }, 2000); // Increased to 2 seconds to ensure page is fully loaded
    }

    // Set up automatic data extraction on PriceCharting - Enhanced with better error handling
    function setupPriceChartingExtraction(requestKey, cardData) {
        debugLog('🔄 Setting up PriceCharting data extraction...');
        debugLog(`🔑 Request key: ${requestKey}`);
        debugLog(`📋 Card data:`, cardData);
        debugLog(`📍 Current URL: ${window.location.href}`);

        // Wait for page to load
        let attempts = 0;
        const maxAttempts = 20;
        
        const extractData = () => {
            attempts++;
            const readyState = document.readyState;
            debugLog(`📄 Document ready state: ${readyState} (attempt ${attempts}/${maxAttempts})`);

            if (readyState !== 'complete' && attempts < maxAttempts) {
                debugLog(`⏳ Waiting for page to complete loading...`);
                setTimeout(extractData, 200);
                return;
            }

            if (attempts >= maxAttempts) {
                debugLog(`⚠️ Max attempts reached, proceeding with extraction anyway...`);
            }

            debugLog(`🔍 Page loaded, starting data extraction...`);
            debugLog(`🔍 Page title: ${document.title}`);

            const extractedData = extractPriceChartingDataFromPage(cardData);

            if (extractedData && Object.keys(extractedData.prices).length > 0) {
                debugLog(`✅ Data extraction successful!`);
                debugLog(`📊 Extracted data:`, extractedData);

                // Store the extracted data for eBay to retrieve
                debugLog(`💾 Storing data with key: ${requestKey}_data`);
                storePriceChartingData(requestKey, extractedData);

                // Verify the data was stored
                const verifyData = getStoredData(`${requestKey}_data`);
                if (verifyData) {
                    debugLog(`✅ Data storage verified successfully`);
                } else {
                    debugLog(`❌ Data storage verification failed!`);
                }

                // Show notification on PriceCharting page
                showExtractionNotification(extractedData, cardData);

                // Auto-close the tab after successful data extraction (only for direct PC checks)
                if (window.location.hash === '#full-prices') {
                    setTimeout(() => {
                        debugLog('🔄 Auto-closing PriceCharting tab after successful data extraction...');
                        // Give a bit more time to ensure data is fully stored
                        setTimeout(() => {
                            window.close();
                        }, 300);
                    }, 500); // Wait 500ms before closing to ensure storage completes
                } else {
                    debugLog('📊 View page - not auto-closing, user can browse manually');
                }
            } else {
                debugLog(`❌ Data extraction failed or no prices found - keeping window open for 30 seconds for debugging`);

                // Still try to store something so eBay knows we tried
                const fallbackData = {
                    url: window.location.href,
                    title: document.title,
                    cardName: cardData?.cardName || 'Unknown',
                    prices: {},
                    extractedCardName: cardData?.cardName,
                    timestamp: Date.now(),
                    error: 'No prices found on page'
                };

                debugLog(`💾 Storing fallback data:`, fallbackData);
                storePriceChartingData(requestKey, fallbackData);
                
                // Keep window open longer for debugging
                if (window.location.hash === '#full-prices') {
                    setTimeout(() => {
                        debugLog('🔄 Auto-closing after extraction failure (30s delay for debugging)');
                        window.close();
                    }, 30000); // 30 seconds
                }
            }
        };

        // Start extraction with a slight delay
        setTimeout(extractData, 200);
    }

    // Extract data from current PriceCharting page - Enhanced to handle more grade variations
    function extractPriceChartingDataFromPage(cardData = null) {
        try {
            const data = {
                url: window.location.href,
                title: document.title,
                cardName: cardData?.cardName || 'Unknown',
                prices: {},
                availability: '',
                lastUpdated: '',
                timestamp: Date.now()
            };

            debugLog('🔍 Searching for prices on PriceCharting full-prices page...');
            debugLog(`🔍 Current page URL: ${window.location.href}`);
            debugLog(`🔍 Has #full-prices hash: ${window.location.hash === '#full-prices'}`);

            // Extract requestKey from URL for alternative URL checking
            let requestKey = null;
            const requestKeyMatch = window.location.href.match(/ebay_request=([^&\s#]+)/);
            if (requestKeyMatch) {
                requestKey = requestKeyMatch[1];
                debugLog(`🔑 Extracted request key from URL: ${requestKey}`);
            }

            // Check if we're on a search results page (not a specific card page)
            if (window.location.href.includes('/search-products') || window.location.href.includes('/search?')) {
                debugLog('⚠️ Landed on search results page - card URL not found');
                
                // Check if there's an alternative URL to try (for Mega cards)
                if (requestKey) {
                    const storedData = GM_getValue(requestKey);
                    if (storedData && storedData.alternativeUrl && !storedData.triedAlternative) {
                        debugLog('🔄 Trying alternative URL format for Mega card...');
                        debugLog(`🔗 Alternative URL: ${storedData.alternativeUrl}`);
                        storedData.triedAlternative = true;
                        GM_setValue(requestKey, storedData);
                        
                        // Redirect to alternative URL and stop further processing
                        debugLog('⏳ Redirecting now...');
                        window.location.replace(storedData.alternativeUrl); // Use replace to avoid back button issues
                        
                        // Don't return data - wait for the redirect
                        throw new Error('Redirecting to alternative URL'); // Stop execution
                    }
                }
                
                debugLog('⚠️ No alternative URL available, treating as not found');
                data.error = 'Card not found - redirected to search results';
                return data; // Return empty prices
            }

            // Check if we're on a 404 or error page
            const pageText = document.body.textContent.toLowerCase();
            if (pageText.includes('404') || pageText.includes('not found') || pageText.includes('no results')) {
                debugLog('⚠️ Appears to be a 404 or error page');
            }

            // Target the specific full-prices table structure
            const fullPricesTable = document.querySelector('#full-prices table');
            debugLog(`🔍 Full-prices table found: ${!!fullPricesTable}`);

            if (fullPricesTable) {
                debugLog('✓ Found full-prices table');

                // Extract data from each row
                const rows = fullPricesTable.querySelectorAll('tbody tr');
                debugLog(`Found ${rows.length} price rows`);

                rows.forEach((row, index) => {
                    const gradeCell = row.querySelector('td:first-child');
                    const priceCell = row.querySelector('td.price.js-price, .price, td:last-child');

                    if (gradeCell && priceCell) {
                        const grade = gradeCell.textContent.trim();
                        const priceText = priceCell.textContent.trim();

                        // Only store if price is not empty and not just a dash
                        if (priceText && priceText !== '-' && priceText !== '') {
                            const priceMatch = priceText.match(/\$[\d,]+\.?\d*/);
                            if (priceMatch) {
                                // Create clean key from grade (remove spaces and special chars)
                                let cleanGrade = grade.toLowerCase().replace(/[^a-z0-9]/g, '_');

                                // Special handling for specific grade formats
                                if (grade === 'Ungraded') {
                                    cleanGrade = 'ungraded';
                                } else if (grade.startsWith('Grade ')) {
                                    // Convert "Grade 9.5" to "grade_9_5"
                                    cleanGrade = grade.toLowerCase().replace('grade ', 'grade_').replace('.', '_');
                                } else if (grade.includes(' 10') && grade.includes('Black')) {
                                    cleanGrade = 'bgs_10_black';
                                } else if (grade.includes(' 10') && grade.includes('Pristine')) {
                                    cleanGrade = 'cgc_10_pristine';
                                }

                                data.prices[cleanGrade] = {
                                    price: priceMatch[0],
                                    grade: grade,
                                    rawText: priceText
                                };
                                debugLog(`  ✓ ${grade} (${cleanGrade}): ${priceMatch[0]}`);
                            }
                        } else {
                            debugLog(`  - ${grade}: No price available`);
                        }
                    }
                });
            } else {
                debugLog('✗ Full-prices table not found, trying fallback selectors...');

                // Fallback: Try general price extraction
                const priceSelectors = [
                    '.price.js-price',
                    '.price',
                    '[class*="price"]',
                    '.used-price',
                    '.new-price'
                ];

                priceSelectors.forEach(selector => {
                    const elements = document.querySelectorAll(selector);
                    elements.forEach((el, index) => {
                        const text = el.textContent.trim();
                        const priceMatch = text.match(/\$[\d,]+\.?\d*/);
                        if (priceMatch && parseFloat(priceMatch[0].replace(/[$,]/g, '')) > 0) {
                            const key = `${selector.replace(/[^a-zA-Z]/g, '')}_${index}`;
                            data.prices[key] = {
                                price: priceMatch[0],
                                grade: 'Unknown',
                                rawText: text
                            };
                        }
                    });
                });
            }

            // Extract card title from the full-prices heading
            const fullPricesHeading = document.querySelector('#full-prices h2');
            if (fullPricesHeading) {
                const headingText = fullPricesHeading.textContent.trim();
                const titleMatch = headingText.match(/Full Price Guide: (.+?) #(\d+)/);
                if (titleMatch) {
                    data.extractedCardName = titleMatch[1];
                    data.extractedCardNumber = titleMatch[2];
                    debugLog(`📋 Extracted from heading: ${data.extractedCardName} #${data.extractedCardNumber}`);
                }
            }

            // Extract card image from product_details div
            const productDetailsDiv = document.querySelector('#product_details');
            if (productDetailsDiv) {
                const cardImage = productDetailsDiv.querySelector('.cover img[src*="storage.googleapis.com"]');
                if (cardImage && cardImage.src) {
                    data.imageUrl = cardImage.src;
                    debugLog(`🖼️ Extracted card image: ${data.imageUrl}`);
                } else {
                    debugLog(`⚠️ No card image found in #product_details`);
                }
            }

            // Always try to extract set name from page title regardless of card name source
            if (document.title) {
                const pageTitle = document.title.trim();
                // Match patterns like "CardName #SM210 Prices | Pokemon Promo | Pokemon Cards"
                const setNameMatch = pageTitle.match(/Prices\s*\|\s*(.+?)\s*\|/);
                if (setNameMatch) {
                    data.extractedSetName = setNameMatch[1];
                    debugLog(`📋 Extracted set name from page title: ${data.extractedSetName}`);
                }
            }

            // If no card name found from heading, try extracting from page title
            if (!data.extractedCardName && document.title) {
                const pageTitle = document.title.trim();
                // Match patterns like "CardName #SM210 Prices | Pokemon Promo | Pokemon Cards"
                const pageTitleMatch = pageTitle.match(/^(.+?)\s+#([A-Z0-9]+)\s+Prices\s*\|/);
                if (pageTitleMatch) {
                    data.extractedCardName = pageTitleMatch[1];
                    data.extractedCardNumber = pageTitleMatch[2];
                    debugLog(`📋 Extracted from page title: ${data.extractedCardName} #${data.extractedCardNumber}`);
                }
            }

            // Look for last updated date anywhere on the page
            const dateSelectors = [
                '[class*="updated"]',
                '[class*="date"]',
                '.last-updated',
                '.data-date',
                'small', // Sometimes dates are in small tags
                '.text-muted' // Or muted text
            ];

            for (const selector of dateSelectors) {
                const elements = document.querySelectorAll(selector);
                for (const el of elements) {
                    const text = el.textContent.trim();
                    if (text.includes('202') || text.includes('updated') || text.includes('last')) {
                        data.lastUpdated = text;
                        break;
                    }
                }
                if (data.lastUpdated) break;
            }

            const priceCount = Object.keys(data.prices).length;
            debugLog(`💎 Extracted ${priceCount} prices from PriceCharting:`);

            // Log all extracted prices for debugging
            Object.entries(data.prices).forEach(([key, priceData]) => {
                debugLog(`  ${priceData.grade}: ${priceData.price}`);
            });

            if (priceCount > 0) {
                debugLog('✅ Price extraction successful');
            } else {
                debugLog('⚠️ No prices found - check selectors');
            }

            return data;

        } catch (error) {
            console.error('❌ Error extracting PriceCharting data:', error);
            return null;
        }
    }

    // Show notification on PriceCharting page - Updated with better messaging
    function showExtractionNotification(extractedData, cardData) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            background: #27ae60;
            color: white;
            padding: 15px;
            border-radius: 8px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            max-width: 300px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
        `;

        const totalPrices = Object.keys(extractedData.prices || {}).length;
        const pricedGrades = Object.values(extractedData.prices || {}).filter(p => p.hasPrice !== false && p.price !== 'Unpriced').length;

        let message = '';
        if (totalPrices > 0) {
            message = `
                <strong>✅ Data Extracted for eBay</strong><br>
                Card: ${extractedData.extractedCardName || cardData?.cardName || 'Unknown'}<br>
                Total grades: ${totalPrices}<br>
                With prices: ${pricedGrades}<br>
                <small>This data will appear in your eBay listing</small>
            `;
        } else {
            message = `
                <strong>⚠️ Data Attempted for eBay</strong><br>
                Card: ${extractedData.extractedCardName || cardData?.cardName || 'Unknown'}<br>
                No prices found on this page<br>
                <small>eBay will show "Unpriced"</small>
            `;
        }

        notification.innerHTML = message;
        document.body.appendChild(notification);

        // Auto-remove after 6 seconds (slightly longer to read)
        setTimeout(() => {
            if (notification.parentNode) {
                notification.parentNode.removeChild(notification);
            }
        }, 6000);
    }

    // Enhanced function to detect grade from eBay title
    function detectGradeFromTitle(title) {
        if (!title) return null;

        const titleUpper = title.toUpperCase();
        debugLog(`🔍 Grade detection for title: "${title}"`);

        // PSA grades
        const psaMatch = titleUpper.match(/PSA\s*(\d+(?:\.\d+)?)/);
        if (psaMatch) {
            const grade = psaMatch[1];
            let key, displayName;

            if (grade === '10') {
                key = `psa_${grade}`;
                displayName = `PSA ${grade}`;
            } else {
                // PSA grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'PSA',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // BGS grades
        const bgsMatch = titleUpper.match(/BGS\s*(\d+(?:\.\d+)?)/);
        if (bgsMatch) {
            const grade = bgsMatch[1];
            let key, displayName;

            if (grade === '10' && titleUpper.includes('BLACK')) {
                key = 'bgs_10_black';
                displayName = 'BGS 10 Black';
            } else if (grade === '10') {
                key = 'bgs_10';
                displayName = 'BGS 10';
            } else {
                // BGS grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'BGS',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // CGC grades - handle both "CGC 10" and "CGC PRISTINE 10" formats
        const cgcMatch = titleUpper.match(/CGC\s*(?:PRISTINE\s*)?(\d+(?:\.\d+)?)/);
        if (cgcMatch) {
            debugLog(`🎯 CGC grade detected: match = "${cgcMatch[0]}", grade = "${cgcMatch[1]}"`);
            const grade = cgcMatch[1];
            let key, displayName;

            if (grade === '10' && titleUpper.includes('PRISTINE')) {
                key = 'cgc_10_pristine';
                displayName = 'CGC 10 Pristine';
                debugLog(`🏆 CGC Pristine 10 detected - using key: ${key}`);
            } else if (grade === '10') {
                key = 'cgc_10';
                displayName = 'CGC 10';
            } else {
                // CGC grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'CGC',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // SGC grades
        const sgcMatch = titleUpper.match(/SGC\s*(\d+(?:\.\d+)?)/);
        if (sgcMatch) {
            const grade = sgcMatch[1];
            let key, displayName;

            if (grade === '10') {
                key = 'sgc_10';
                displayName = 'SGC 10';
            } else {
                // SGC grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'SGC',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // Generic Grade patterns (for Grade 9.5, etc.)
        const gradeMatch = titleUpper.match(/GRADE\s*(\d+(?:\.\d+)?)/);
        if (gradeMatch) {
            const grade = gradeMatch[1];
            return {
                company: 'Generic',
                grade: grade,
                key: `grade_${grade.replace('.', '_')}`,
                displayName: `Grade ${grade}`
            };
        }

        return null;
    }

})();