pokespy

Help with eBay listings with TCGdex integration and PriceCharting data extraction

// ==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;
    }

})();