UI Scraper : Element Text Extractor

Advanced UI element text extractor with keyboard shortcuts, export formats, undo functionality, and optimized performance

// ==UserScript==
// @name         UI Scraper : Element Text Extractor
// @namespace    http://tampermonkey.net/
// @version      2.1
// @license      MIT
// @description  Advanced UI element text extractor with keyboard shortcuts, export formats, undo functionality, and optimized performance
// @author       MakMak
// @match        http://*/*
// @match        https://*/*
// @icon         https://i.ibb.co/Fk364jmW/table.png
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // --- STATE MANAGEMENT ---
    let isSelecting = false;
    let currentMode = 'list'; // 'list' or 'table'
    let listData = [];
    let tableData = [];
    let tableHeaders = [];
    let currentRow = [];
    let lastHoveredElement = null;
    let extractorEnabled = false;
    let panelEl = null;
    let stylesInjected = false;
    let abortController = null;
    let hoverDebounceTimer = null;
    let autoSaveTimer = null;
    let actionHistory = []; // For undo functionality
    let previewMode = false;
    let menuCommandText = ""; // To store the current menu command text for toggling

    // Enhanced table batch mode state
    let firstRowElements = []; // Store DOM elements from first complete row
    let columnSelectors = []; // Store selectors for each column
    let firstRowCompleted = false; // Track if first row is complete

    // Analyze first row elements to create column-specific selectors
    function analyzeColumnSelectors(elements) {
        const selectors = [];

        elements.forEach((element, index) => {
            const selector = generateElementSelector(element);
            selectors.push({
                columnIndex: index,
                columnName: tableHeaders[index],
                selector: selector,
                element: element,
                tagName: element.tagName.toLowerCase(),
                className: element.className,
                attributes: getRelevantAttributes(element)
            });
        });

        return selectors;
    }

    // Generate a specific selector for an element
    function generateElementSelector(element) {
        const tagName = element.tagName.toLowerCase();
        let selector = tagName;

        // Add class selector if available (excluding our highlight classes)
        const cleanClassName = element.className
            .split(' ')
            .filter(c => !c.startsWith('extractor-'))
            .join('.');

        if (cleanClassName) {
            selector += '.' + cleanClassName;
        }

        // Add attribute selectors for more specificity
        const relevantAttrs = getRelevantAttributes(element);
        relevantAttrs.forEach(attr => {
            if (attr.value && attr.value.length < 50) { // Avoid very long attribute values
                selector += `[${attr.name}="${attr.value}"]`;
            }
        });

        return selector;
    }

    // Get relevant attributes for selector generation
    function getRelevantAttributes(element) {
        const relevantAttrs = ['data-*', 'role', 'type', 'name', 'id'];
        const attrs = [];

        for (let attr of element.attributes) {
            if (relevantAttrs.some(pattern =>
                pattern.includes('*') ? attr.name.startsWith(pattern.replace('*', '')) : attr.name === pattern
            )) {
                attrs.push({ name: attr.name, value: attr.value });
            }
        }

        return attrs;
    }

    // Performance constants
    const HOVER_DEBOUNCE_DELAY = 50;
    const AUTO_SAVE_DELAY = 2000;
    const MAX_HISTORY_SIZE = 50;

    // Smart element selection - elements to ignore
    const IGNORED_ELEMENTS = new Set([
        'SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'TITLE', 'HEAD',
        'BR', 'HR', 'IMG', 'INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'
    ]);

    // --- UI PANEL HTML ---
    const panelHTML = `
        <div id="extractor-panel" class="extractor-panel-hidden">
            <div class="ex-header">
                📝 Text Extractor <span id="transparency-indicator" style="display: none;">👻</span>
                <div class="ex-header-controls">
                    <button class="ex-minimize-btn" title="Minimize">−</button>
                    <button class="ex-close-btn" title="Close">×</button>
                </div>
            </div>
            <div class="ex-body">
                <div class="ex-controls">
                    <button id="ex-toggle-selection" class="ex-primary-btn">Start Selecting</button>
                    <div class="ex-modes">
                        <label><input type="radio" name="ex-mode" value="list" checked> List</label>
                        <label><input type="radio" name="ex-mode" value="table"> Table</label>
                    </div>
                </div>
                <!-- Manual selector input for List mode -->
                <div id="ex-selector-input" class="ex-selector-controls" style="display:none; margin-top: 8px;">
                    <label for="ex-manual-selector" style="font-size: 12px; font-weight: 600;">CSS Selector:</label>
                    <div class="ex-selector-input-group" style="display:flex; gap: 4px; margin-top: 4px;">
                        <input type="text" id="ex-manual-selector" placeholder="e.g., .product-name, h2.title" title="Enter CSS selector and press Enter" style="flex:1; padding: 6px 8px; font-size: 13px; border: 1px solid #ced4da; border-radius: 6px;">
                        <button id="ex-apply-selector" class="ex-small-btn" title="Apply Selector" style="align-self: center; padding: 6px 10px;">→</button>
                    </div>
                    <div class="ex-selector-info" style="margin-top: 4px; font-size: 11px; color: #6c757d;">
                        <span id="ex-selector-count">0 elements found</span>
                    </div>
                </div>
                <div id="ex-table-setup" style="display: none;">
                    <label for="ex-column-names">Column Names (comma-separated):</label>
                    <input type="text" id="ex-column-names" placeholder="e.g., Name, Price, SKU">
                </div>
                <div class="ex-results">
                    <div class="ex-results-header">
                        <label>Collected Data:</label>
                        <div class="ex-data-controls">
                            <button id="ex-preview-btn" class="ex-small-btn" title="Toggle Preview">👁</button>
                            <button id="ex-undo-btn" class="ex-small-btn" title="Undo Last (Ctrl+Z)">↶</button>
                        </div>
                    </div>
                    <textarea id="ex-extracted-data" rows="8" readonly></textarea>
                </div>
                <div class="ex-actions">
                    <div class="ex-export-group">
                        <button id="ex-copy-btn">Copy</button>
                        <div class="ex-export-dropdown">
                            <button id="ex-export-btn" class="ex-dropdown-btn">Export ▼</button>
                            <div class="ex-export-menu">
                                <button data-format="text">Plain Text</button>
                                <button data-format="json">JSON</button>
                                <button data-format="csv">CSV</button>
                                <button data-format="html">HTML Table</button>
                            </div>
                        </div>
                    </div>
                    <button id="ex-clear-btn">Clear All</button>
                    <button id="ex-batch-btn" title="Select Similar Elements">Batch</button>
                </div>
                <div class="ex-status">
                    <span id="ex-status-text">Mode: List | Ready</span>
                    <span id="ex-shortcuts-hint">Ctrl+K: Toggle | Esc: Stop | Ctrl+Z: Undo | Ctrl+T: Transparency</span>
                </div>
            </div>
        </div>
    `;

    // --- CSS STYLES (Lazy loaded) ---
    const panelCSS = `
        #extractor-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 360px;
            background-color: #ffffff;
            border: 1px solid #e0e0e0;
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.15);
            z-index: 999999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            font-size: 14px;
            color: #333;
            user-select: none;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            backdrop-filter: blur(10px);
        }

        #extractor-panel * {
            box-sizing: border-box;
        }

        .extractor-panel-hidden {
            transform: translateX(100%);
            opacity: 0;
        }

        /* Enhanced dragging styles */
        #extractor-panel.dragging {
            box-shadow: 0 12px 48px rgba(0,0,0,0.25);
            z-index: 9999999;
        }

        #extractor-panel.dragging .ex-header {
            cursor: grabbing;
        }

        /* Semi-transparent mode for better element selection behind panel */
        #extractor-panel.semi-transparent {
            opacity: 0.7;
            pointer-events: none;
        }

        #extractor-panel.semi-transparent .ex-header,
        #extractor-panel.semi-transparent .ex-body {
            pointer-events: auto;
        }

        .ex-header {
            padding: 12px 16px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            font-weight: 600;
            cursor: move;
            border-top-left-radius: 12px;
            border-top-right-radius: 12px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .ex-header-controls {
            display: flex;
            gap: 8px;
        }

        /* Reworked buttons to be perfect circles with centered icons */
        .ex-minimize-btn, .ex-close-btn {
            width: 22px;
            height: 22px;
            border-radius: 50%;
            background: rgba(255,255,255,0.2);
            border: none;
            color: white;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            transition: background-color 0.2s;
            padding: 0; /* Remove default padding that skews the circle shape */
            line-height: 1; /* Normalize line-height for better centering */
        }
        .ex-minimize-btn {
            font-weight: bold; /* A bold minus/plus looks better */
        }
        .ex-close-btn {
            font-weight: normal; /* A normal weight '×' is cleaner */
        }
        .ex-minimize-btn:hover, .ex-close-btn:hover {
            background: rgba(255,255,255,0.3);
        }

        .ex-body {
            padding: 16px;
            display: flex;
            flex-direction: column;
            gap: 16px;
        }

        .ex-controls {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 12px;
        }

        .ex-modes {
            display: flex;
            gap: 12px;
            background: #f8f9fa;
            padding: 4px;
            border-radius: 8px;
        }

        .ex-modes label {
            padding: 6px 12px;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            font-size: 13px;
        }

        .ex-modes label:has(input:checked) {
            background: #667eea;
            color: white;
        }

        .ex-modes input {
            display: none;
        }

        #ex-table-setup {
            display: flex;
            flex-direction: column;
            gap: 8px;
            padding: 12px;
            background: #f8f9fa;
            border-radius: 8px;
        }

        #ex-column-names {
            padding: 8px 12px;
            border: 2px solid #e9ecef;
            border-radius: 6px;
            font-size: 14px;
            transition: border-color 0.2s;
        }

        #ex-column-names:focus {
            outline: none;
            border-color: #667eea;
        }

        .ex-results {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .ex-results-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .ex-data-controls {
            display: flex;
            gap: 4px;
        }

        #ex-extracted-data {
            width: 100%;
            border: 2px solid #e9ecef;
            border-radius: 8px;
            background-color: #fff;
            resize: vertical;
            min-height: 120px;
            font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
            font-size: 13px;
            padding: 12px;
            line-height: 1.5;
            transition: border-color 0.2s;
        }

        #ex-extracted-data:focus {
            outline: none;
            border-color: #667eea;
        }

        .ex-actions {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        .ex-export-group {
            display: flex;
            position: relative;
        }

        .ex-export-dropdown {
            position: relative;
        }

        .ex-export-menu {
            position: absolute;
            top: 100%;
            left: 0;
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            display: none;
            min-width: 120px;
            z-index: 1000000;
        }

        .ex-export-menu.show {
            display: block;
        }

        .ex-export-menu button {
            width: 100%;
            text-align: left;
            padding: 8px 12px;
            border: none;
            background: none;
            cursor: pointer;
            font-size: 13px;
            transition: background-color 0.2s;
        }

        .ex-export-menu button:hover {
            background: #f8f9fa;
        }

        .ex-export-menu button:first-child {
            border-top-left-radius: 8px;
            border-top-right-radius: 8px;
        }

        .ex-export-menu button:last-child {
            border-bottom-left-radius: 8px;
            border-bottom-right-radius: 8px;
        }

        button {
            padding: 8px 16px;
            border: 2px solid #e9ecef;
            border-radius: 8px;
            background-color: #ffffff;
            cursor: pointer;
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            font-weight: 500;
            font-size: 13px;
        }

        button:hover {
            border-color: #667eea;
            transform: translateY(-1px);
        }

        .ex-primary-btn {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            font-weight: 600;
        }

        .ex-primary-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
        }

        .ex-primary-btn.active {
            background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
        }

        .ex-small-btn {
            padding: 6px 8px;
            font-size: 12px;
            min-width: auto;
        }

        .ex-dropdown-btn {
            border-left: none;
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
            padding: 8px 12px;
        }

        #ex-copy-btn {
            border-top-right-radius: 0;
            border-bottom-right-radius: 0;
        }

        .ex-status {
            font-size: 11px;
            color: #6c757d;
            text-align: center;
            border-top: 1px solid #e9ecef;
            padding-top: 12px;
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        #ex-shortcuts-hint {
            font-size: 10px;
            opacity: 0.7;
        }

        /* Element Highlighting with improved animations */
        .extractor-hover-highlight {
            outline: 2px solid #667eea !important;
            outline-offset: 2px;
            cursor: crosshair !important;
            animation: pulse-hover 1.5s infinite;
        }

        .extractor-selected-highlight {
            outline: 2px solid #28a745 !important;
            outline-offset: 2px;
            background-color: rgba(40, 167, 69, 0.1) !important;
            animation: flash-select 0.6s ease-out;
        }

        .extractor-preview-highlight {
            outline: 2px dashed #ffc107 !important;
            outline-offset: 2px;
            background-color: rgba(255, 193, 7, 0.1) !important;
        }

        @keyframes pulse-hover {
            0%, 100% { outline-color: #667eea; }
            50% { outline-color: #764ba2; }
        }

        @keyframes flash-select {
            0% { background-color: rgba(40, 167, 69, 0.3) !important; }
            100% { background-color: rgba(40, 167, 69, 0.1) !important; }
        }

        /* Minimized state */
        .ex-body.minimized {
            display: none;
        }

        #extractor-panel.minimized {
            width: 200px;
        }

        /* Responsive adjustments */
        @media (max-width: 480px) {
            #extractor-panel {
                width: calc(100vw - 40px);
                right: 20px;
                left: 20px;
            }
        }

        /* Styles for manual selector input */
        .ex-selector-controls {
            margin: 12px 0;
            padding: 12px;
            background: #f8f9fa;
            border-radius: 6px;
            border: 1px solid #e9ecef;
        }

        .ex-selector-controls label {
            display: block;
            margin-bottom: 6px;
            font-weight: 500;
            color: #495057;
            font-size: 12px;
        }

        .ex-selector-input-group {
            display: flex;
            gap: 6px;
            margin-bottom: 6px;
        }

        #ex-manual-selector {
            flex: 1;
            padding: 8px 10px;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 12px;
            font-family: 'Courier New', monospace;
            background: white;
        }

        #ex-manual-selector:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
        }

        #ex-apply-selector {
            padding: 8px 12px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            min-width: 32px;
        }

        #ex-apply-selector:hover {
            background: #5a67d8;
        }

        .ex-selector-info {
            font-size: 11px;
            color: #6c757d;
        }

        #ex-selector-count {
            font-weight: 500;
        }

        /* Hide selector input in table mode */
        .ex-selector-controls.hidden {
            display: none;
        }
    `;

    // --- UTILITY FUNCTIONS ---
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Update selector input with current element's selector
    function updateSelectorInput(element) {
        const manualSelectorInput = document.getElementById('ex-manual-selector');
        const selectorCountSpan = document.getElementById('ex-selector-count');

        if (!manualSelectorInput || !selectorCountSpan) return;

        // Generate selector using same logic as batch mode
        const tagName = element.tagName.toLowerCase();
        const className = element.className;

        let selector = tagName;
        if (className) {
            const cleanClassName = className.split(' ').filter(c => !c.startsWith('extractor-')).join('.');
            if (cleanClassName) {
                selector += '.' + cleanClassName;
            }
        }

        // Update input field
        manualSelectorInput.value = selector;

        // Update count
        try {
            const elements = Array.from(document.querySelectorAll(selector))
                .filter(el => !isIgnorableElement(el) && el.innerText.trim().length > 0);
            const count = elements.length;
            selectorCountSpan.textContent = `${count} element${count !== 1 ? 's' : ''} found`;

            // Change color based on count
            if (count === 0) {
                selectorCountSpan.style.color = '#dc3545';
            } else if (count === 1) {
                selectorCountSpan.style.color = '#28a745';
            } else {
                selectorCountSpan.style.color = '#007bff';
            }
        } catch (err) {
            selectorCountSpan.textContent = 'Invalid selector';
            selectorCountSpan.style.color = '#dc3545';
        }
    }

    function isIgnorableElement(element) {
        if (!element || !element.tagName) return true;

        // Check if it's an ignored element type
        if (IGNORED_ELEMENTS.has(element.tagName)) return true;

        // Check if it's part of our extractor UI
        if (element.closest('#extractor-panel')) return true;

        // Check if element has no visible text content
        const text = element.innerText?.trim();
        if (!text || text.length === 0) return true;

        // Check if element is hidden
        const style = window.getComputedStyle(element);
        if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return true;

        return false;
    }

    function saveData() {
        const data = {
            listData,
            tableData,
            tableHeaders,
            currentRow,
            currentMode,
            timestamp: Date.now()
        };
        GM_setValue('extractorData', JSON.stringify(data));
    }

    function loadData() {
        try {
            const saved = GM_getValue('extractorData', null);
            if (saved) {
                const data = JSON.parse(saved);
                // Only load if data is recent (within 24 hours)
                if (Date.now() - data.timestamp < 24 * 60 * 60 * 1000) {
                    listData = data.listData || [];
                    tableData = data.tableData || [];
                    tableHeaders = data.tableHeaders || [];
                    currentRow = data.currentRow || [];
                    currentMode = data.currentMode || 'list';
                    return true;
                }
            }
        } catch (e) {
            console.warn('Failed to load saved data:', e);
        }
        return false;
    }

    function addToHistory(action) {
        actionHistory.push({
            action,
            timestamp: Date.now(),
            listData: [...listData],
            tableData: tableData.map(row => [...row]),
            tableHeaders: [...tableHeaders],
            currentRow: [...currentRow],
            // Include table batch state in history
            firstRowCompleted,
            columnSelectors: [...columnSelectors]
        });

        // Limit history size
        if (actionHistory.length > MAX_HISTORY_SIZE) {
            actionHistory.shift();
        }
    }

    function scheduleAutoSave() {
        if (autoSaveTimer) {
            clearTimeout(autoSaveTimer);
        }
        autoSaveTimer = setTimeout(saveData, AUTO_SAVE_DELAY);
    }

    // --- EXPORT FUNCTIONS ---
    function exportData(format) {
        let content = '';
        let filename = `extracted-data-${new Date().toISOString().split('T')[0]}`;
        let mimeType = 'text/plain';

        switch (format) {
            case 'json':
                if (currentMode === 'list') {
                    content = JSON.stringify({ items: listData }, null, 2);
                } else {
                    const tableObj = tableData.map(row => {
                        const obj = {};
                        tableHeaders.forEach((header, index) => {
                            obj[header] = row[index] || '';
                        });
                        return obj;
                    });
                    content = JSON.stringify({ headers: tableHeaders, data: tableObj }, null, 2);
                }
                filename += '.json';
                mimeType = 'application/json;charset=utf-8;';
                break;

            case 'csv':
                if (currentMode === 'list') {
                    content = listData.map(item => `"${item.replace(/"/g, '""')}"`).join('\n');
                } else {
                    const csvHeaders = tableHeaders.map(h => `"${h.replace(/"/g, '""')}"`).join(',');
                    const csvRows = tableData.map(row =>
                        row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')
                    ).join('\n');
                    content = csvHeaders + '\n' + csvRows;
                }
                filename += '.csv';
                // Set charset and prepend BOM for UTF-8 compatibility (e.g., in Excel)
                mimeType = 'text/csv;charset=utf-8;';
                content = '\uFEFF' + content; // Add UTF-8 Byte Order Mark
                break;

            case 'html':
                if (currentMode === 'list') {
                    const listItems = listData.map(item => `<li>${escapeHtml(item)}</li>`).join('\n');
                    content = `<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Extracted Data</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        ul { list-style-type: disc; padding-left: 20px; }
        li { margin: 5px 0; }
    </style>
</head>
<body>
    <h1>Extracted List Data</h1>
    <ul>
${listItems}
    </ul>
</body>
</html>`;
                } else {
                    const headerRow = tableHeaders.map(h => `<th>${escapeHtml(h)}</th>`).join('');
                    const dataRows = tableData.map(row =>
                        '<tr>' + row.map(cell => `<td>${escapeHtml(cell)}</td>`).join('') + '</tr>'
                    ).join('\n');
                    content = `<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Extracted Data</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; font-weight: bold; }
        tr:nth-child(even) { background-color: #f9f9f9; }
    </style>
</head>
<body>
    <h1>Extracted Table Data</h1>
    <table>
        <thead>
            <tr>${headerRow}</tr>
        </thead>
        <tbody>
${dataRows}
        </tbody>
    </table>
</body>
</html>`;
                }
                filename += '.html';
                mimeType = 'text/html;charset=utf-8;';
                break;

            default: // text
                if (currentMode === 'list') {
                    content = listData.join('\n');
                } else {
                    content = tableHeaders.join('\t') + '\n';
                    content += tableData.map(row => row.join('\t')).join('\n');
                }
                filename += '.txt';
                mimeType = 'text/plain;charset=utf-8;';
                break;
        }

        // Create and trigger download
        const blob = new Blob([content], { type: mimeType });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // --- GM MENU FUNCTIONS ---
    function toggleExtractor() {
        extractorEnabled = !extractorEnabled;

        if (extractorEnabled) {
            initExtractor();
        } else {
            destroyExtractor();
        }

        updateMenuCommand();
    }

    // This function now handles a single toggling menu command.
    function updateMenuCommand() {
        // Unregister the previous command to prevent duplicates in some script managers
        if (menuCommandText) {
            GM_unregisterMenuCommand(menuCommandText);
        }

        // Set the new command text based on the extractor's state
        menuCommandText = extractorEnabled ? "🟢 Disable Extractor" : "⚪ Enable Extractor";
        GM_registerMenuCommand(menuCommandText, toggleExtractor);
    }

    function initExtractor() {
        if (panelEl) return; // Already initialized

        // Lazy inject CSS
        if (!stylesInjected) {
            GM_addStyle(panelCSS);
            stylesInjected = true;
        }

        // Create UI
        const container = document.createElement('div');
        container.innerHTML = panelHTML;
        document.body.appendChild(container);

        // Get the actual panel element
        panelEl = document.getElementById('extractor-panel');

        // Load saved data
        if (loadData()) {
            // Update UI to reflect loaded data
            const modeRadio = document.querySelector(`input[name="ex-mode"][value="${currentMode}"]`);
            if (modeRadio) {
                modeRadio.checked = true;
                const tableSetupDiv = document.getElementById('ex-table-setup');
                const selectorInputDiv = document.getElementById('ex-selector-input');
                tableSetupDiv.style.display = currentMode === 'table' ? 'block' : 'none';
                // Initialize selector input visibility based on mode
                selectorInputDiv.style.display = currentMode === 'list' ? 'block' : 'none';
            }
            updateDisplay();
            updateStatus();
        }

        setupEventListeners();

        // Restore saved panel position
        const savedPosition = GM_getValue('panel_position', null);
        if (savedPosition) {
            // Ensure position is still within viewport bounds
            const maxTop = window.innerHeight - panelEl.offsetHeight - 10;
            const maxLeft = window.innerWidth - panelEl.offsetWidth - 10;

            const top = Math.max(10, Math.min(savedPosition.top, maxTop));
            const left = Math.max(10, Math.min(savedPosition.left, maxLeft));

            panelEl.style.top = top + 'px';
            panelEl.style.left = left + 'px';
        }

        // Make panel draggable
        makeDraggable(panelEl);

        // Show panel with animation
        setTimeout(() => {
            if (panelEl) panelEl.classList.remove('extractor-panel-hidden');
        }, 100);
    }

    function destroyExtractor() {
        if (!panelEl) return;

        // Stop any active selection
        if (isSelecting) {
            toggleSelection();
        }

        // Clear timers
        if (hoverDebounceTimer) clearTimeout(hoverDebounceTimer);
        if (autoSaveTimer) clearTimeout(autoSaveTimer);

        // Abort all event listeners
        if (abortController) abortController.abort();

        // Remove UI
        panelEl.parentElement.remove();
        panelEl = null;

        // Clear highlights
        document.querySelectorAll('.extractor-hover-highlight, .extractor-selected-highlight, .extractor-preview-highlight').forEach(el => {
            el.classList.remove('extractor-hover-highlight', 'extractor-selected-highlight', 'extractor-preview-highlight');
        });

        // Reset state
        isSelecting = false;
        lastHoveredElement = null;
        actionHistory = [];
    }

    function setupEventListeners() {
        // Create new AbortController for better event management
        abortController = new AbortController();
        const { signal } = abortController;

        // Get UI Elements
        const toggleBtn = document.getElementById('ex-toggle-selection');
        const modeRadios = document.querySelectorAll('input[name="ex-mode"]');
        const tableSetupDiv = document.getElementById('ex-table-setup');
        const selectorInputDiv = document.getElementById('ex-selector-input');
        const manualSelectorInput = document.getElementById('ex-manual-selector');
        const applySelectorBtn = document.getElementById('ex-apply-selector');
        const selectorCountSpan = document.getElementById('ex-selector-count');
        const copyBtn = document.getElementById('ex-copy-btn');
        const clearBtn = document.getElementById('ex-clear-btn');
        const closeBtn = document.querySelector('.ex-close-btn');
        const minimizeBtn = document.querySelector('.ex-minimize-btn');
        const undoBtn = document.getElementById('ex-undo-btn');
        const previewBtn = document.getElementById('ex-preview-btn');
        const batchBtn = document.getElementById('ex-batch-btn');
        const exportBtn = document.getElementById('ex-export-btn');
        const exportMenu = document.querySelector('.ex-export-menu');

        // Panel event listeners
        toggleBtn.addEventListener('click', toggleSelection, { signal });
        copyBtn.addEventListener('click', copyToClipboard, { signal });
        clearBtn.addEventListener('click', clearData, { signal });
        undoBtn.addEventListener('click', undoLastAction, { signal });
        previewBtn.addEventListener('click', togglePreview, { signal });
        batchBtn.addEventListener('click', toggleBatchMode, { signal });

        closeBtn.addEventListener('click', () => {
            // Automatically disable extractor when closing instead of just hiding
            extractorEnabled = false;
            destroyExtractor();
            updateMenuCommand();
        }, { signal });

        minimizeBtn.addEventListener('click', () => {
            const body = document.querySelector('.ex-body');
            const isMinimized = body.classList.contains('minimized');
            body.classList.toggle('minimized');
            panelEl.classList.toggle('minimized');
            minimizeBtn.textContent = isMinimized ? '−' : '+';
        }, { signal });

        // Export dropdown
        exportBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            exportMenu.classList.toggle('show');
        }, { signal });

        // Close export menu when clicking outside
        document.addEventListener('click', () => {
            if (exportMenu.classList.contains('show')) {
                 exportMenu.classList.remove('show');
            }
        }, { signal });

        // Export format buttons
        exportMenu.querySelectorAll('button').forEach(btn => {
            btn.addEventListener('click', (e) => {
                e.stopPropagation();
                const format = btn.dataset.format;
                exportData(format);
                exportMenu.classList.remove('show');

                // Visual feedback
                const originalText = exportBtn.textContent;
                exportBtn.textContent = 'Exported!';
                setTimeout(() => {
                    exportBtn.textContent = originalText;
                }, 1500);
            }, { signal });
        });

        // Mode change listeners
        modeRadios.forEach(radio => {
            radio.addEventListener('change', (e) => {
                currentMode = e.target.value;
                tableSetupDiv.style.display = currentMode === 'table' ? 'block' : 'none';

                // Show or hide manual selector input for list mode
                if (currentMode === 'list') {
                    selectorInputDiv.style.display = 'block';
                } else {
                    selectorInputDiv.style.display = 'none';
                    manualSelectorInput.value = '';
                    selectorCountSpan.textContent = '0 elements found';
                }

                clearData(); // Clear data when switching modes
                updateStatus();
                scheduleAutoSave();
            }, { signal });
        });

        // Manual selector input: apply selector button click
        applySelectorBtn.addEventListener('click', () => {
            applyManualSelector();
        }, { signal });

        // Manual selector input: enter key triggers apply
        manualSelectorInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                applyManualSelector();
            }
        }, { signal });

        // Real-time validation as user types
        manualSelectorInput.addEventListener('input', () => {
            const selector = manualSelectorInput.value.trim();
            if (!selector) {
                selectorCountSpan.textContent = '0 elements found';
                selectorCountSpan.style.color = '#6c757d';
                clearSelectorPreview();
                return;
            }

            try {
                const elements = Array.from(document.querySelectorAll(selector))
                    .filter(el => !isIgnorableElement(el) && el.innerText.trim().length > 0);
                const count = elements.length;
                selectorCountSpan.textContent = `${count} element${count !== 1 ? 's' : ''} found`;

                // Change color based on count
                if (count === 0) {
                    selectorCountSpan.style.color = '#dc3545';
                } else if (count === 1) {
                    selectorCountSpan.style.color = '#28a745';
                } else {
                    selectorCountSpan.style.color = '#007bff';
                }

                // Preview matching elements
                previewSelectorElements(elements);
            } catch (err) {
                selectorCountSpan.textContent = 'Invalid selector';
                selectorCountSpan.style.color = '#dc3545';
                clearSelectorPreview();
            }
        }, { signal });

        // Clear preview when input loses focus
        manualSelectorInput.addEventListener('blur', () => {
            setTimeout(() => clearSelectorPreview(), 200); // Small delay to allow clicking apply button
        }, { signal });

        // Show preview when input gains focus
        manualSelectorInput.addEventListener('focus', () => {
            const selector = manualSelectorInput.value.trim();
            if (selector) {
                try {
                    const elements = Array.from(document.querySelectorAll(selector))
                        .filter(el => !isIgnorableElement(el) && el.innerText.trim().length > 0);
                    previewSelectorElements(elements);
                } catch (err) {
                    // Ignore errors on focus
                }
            }
        }, { signal });

        // Keyboard shortcuts
        document.addEventListener('keydown', handleKeyboardShortcuts, { signal });

        // Global listeners for selection (with debouncing)
        const debouncedMouseOver = debounce(handleMouseOver, HOVER_DEBOUNCE_DELAY);
        document.addEventListener('mouseover', debouncedMouseOver, { signal });
        document.addEventListener('mouseout', handleMouseOut, { signal });
        document.addEventListener('click', handleElementClick, { capture: true, signal });

        // Apply manual selector function
        function applyManualSelector() {
            const selector = manualSelectorInput.value.trim();
            if (!selector) {
                alert('Please enter a CSS selector.');
                return;
            }
            let elements;
            try {
                elements = Array.from(document.querySelectorAll(selector))
                    .filter(el => !isIgnorableElement(el) && el.innerText.trim().length > 0);
            } catch (err) {
                alert('Invalid CSS selector.');
                return;
            }
            const count = elements.length;
            selectorCountSpan.textContent = `${count} element${count !== 1 ? 's' : ''} found`;

            if (count === 0) {
                alert('No matching elements found for the given selector.');
                return;
            }

            // Confirm adding elements to listData
            if (!confirm(`Found ${count} matching elements. Add all to the list?`)) {
                return;
            }

            addToHistory('manual_selector_add');

            elements.forEach(el => {
                const text = el.innerText.trim();
                if (!listData.includes(text)) {
                    listData.push(text);
                    el.classList.add('extractor-selected-highlight');
                }
            });

            updateDisplay();
            updateStatus();
            scheduleAutoSave();
        }
    }

    // Preview matching elements with highlight
    function previewSelectorElements(elements) {
        clearSelectorPreview();
        elements.forEach(el => {
            el.classList.add('extractor-preview-highlight');
        });
    }

    // Clear selector preview highlights
    function clearSelectorPreview() {
        document.querySelectorAll('.extractor-preview-highlight').forEach(el => {
            el.classList.remove('extractor-preview-highlight');
        });
    }

    // --- KEYBOARD SHORTCUTS ---
    function handleKeyboardShortcuts(e) {
        // Ctrl+K: Toggle selection
        if (e.ctrlKey && e.key === 'k') {
            e.preventDefault();
            toggleSelection();
            return;
        }

        // Escape: Stop selecting
        if (e.key === 'Escape' && isSelecting) {
            e.preventDefault();
            toggleSelection();
            return;
        }

        // Ctrl+Z: Undo
        if (e.ctrlKey && e.key === 'z' && !e.shiftKey) {
            e.preventDefault();
            undoLastAction();
            return;
        }

        // Ctrl+E: Toggle extractor
        if (e.ctrlKey && e.key === 'e') {
            e.preventDefault();
            toggleExtractor();
            return;
        }

        // Ctrl+T: Toggle panel transparency for better element selection
        if (e.ctrlKey && e.key === 't' && panelEl) {
            e.preventDefault();
            togglePanelTransparency();
            return;
        }

        // Ctrl+Shift+C: Copy data
        if (e.ctrlKey && e.shiftKey && (e.key === 'C' || e.key === 'c')) {
            e.preventDefault();
            copyToClipboard();
            return;
        }
    }

    // --- CORE LOGIC ---
    function toggleSelection() {
        isSelecting = !isSelecting;
        const toggleBtn = document.getElementById('ex-toggle-selection');

        if (isSelecting) {
            toggleBtn.textContent = 'Stop Selecting';
            toggleBtn.classList.add('active');
        } else {
            toggleBtn.textContent = 'Start Selecting';
            toggleBtn.classList.remove('active');
            if (lastHoveredElement) {
                lastHoveredElement.classList.remove('extractor-hover-highlight');
                lastHoveredElement = null;
            }
        }
        updateStatus();
    }

    function handleMouseOver(e) {
        if (!isSelecting || isIgnorableElement(e.target)) return;

        // Clear previous hover highlight
        if (lastHoveredElement && lastHoveredElement !== e.target) {
            lastHoveredElement.classList.remove('extractor-hover-highlight');
        }

        lastHoveredElement = e.target;
        lastHoveredElement.classList.add('extractor-hover-highlight');

        // Auto-populate selector input in List mode
        if (currentMode === 'list') {
            updateSelectorInput(e.target);
        }
    }

    function handleMouseOut(e) {
        if (!isSelecting || !e.target.classList) return;
        e.target.classList.remove('extractor-hover-highlight');
    }

    function handleElementClick(e) {
        if (!isSelecting || isIgnorableElement(e.target)) return;

        e.preventDefault();
        e.stopPropagation();

        const target = e.target;
        target.classList.remove('extractor-hover-highlight');

        const text = target.innerText.trim();
        if (!text) return;

        // Add to history before making changes
        addToHistory('add_element');

        target.classList.add('extractor-selected-highlight');

        if (currentMode === 'list') {
            listData.push(text);
        } else { // Table mode
            if (tableHeaders.length === 0) {
                const columnNames = document.getElementById('ex-column-names').value;
                if (!columnNames) {
                    alert('Please set the column names for the table first!');
                    target.classList.remove('extractor-selected-highlight');
                    // Re-enable selection for this element
                    isSelecting = true;
                    return;
                }
                tableHeaders = columnNames.split(',').map(h => h.trim());
            }

            // Track elements for first row to build column selectors
            if (!firstRowCompleted) {
                firstRowElements.push(target);
            }

            currentRow.push(text);
            if (currentRow.length === tableHeaders.length) {
                tableData.push([...currentRow]);

                // When first row is complete, analyze column selectors
                if (!firstRowCompleted) {
                    firstRowCompleted = true;
                    columnSelectors = analyzeColumnSelectors(firstRowElements);

                    // Update batch button to indicate it's ready for intelligent selection
                    const batchBtn = document.getElementById('ex-batch-btn');
                    if (batchBtn) {
                        batchBtn.title = 'Smart Batch: Select similar table rows';
                        batchBtn.style.background = '#28a745';
                        batchBtn.style.color = 'white';
                    }

                    console.log('First row completed. Column selectors ready:', columnSelectors);
                }

                currentRow = [];
                firstRowElements = []; // Reset for potential next row
            }
        }

        updateDisplay();
        updateStatus();
        scheduleAutoSave();
    }

    function togglePreview() {
        previewMode = !previewMode;
        const previewBtn = document.getElementById('ex-preview-btn');

        if (previewMode) {
            previewBtn.style.background = '#ffc107';
            previewBtn.style.color = 'black';
            showPreview();
        } else {
            previewBtn.style.background = '';
            previewBtn.style.color = '';
            hidePreview();
        }
    }

    function showPreview() {
        // Highlight all selected elements with preview style
        document.querySelectorAll('.extractor-selected-highlight').forEach(el => {
            el.classList.add('extractor-preview-highlight');
        });
    }

    function hidePreview() {
        document.querySelectorAll('.extractor-preview-highlight').forEach(el => {
            el.classList.remove('extractor-preview-highlight');
        });
    }

    // Toggle panel transparency for better element selection
    function togglePanelTransparency() {
        if (!panelEl) return;

        const isTransparent = panelEl.classList.contains('semi-transparent');

        if (isTransparent) {
            panelEl.classList.remove('semi-transparent');
            // Update status to show normal mode
            const statusText = document.getElementById('ex-status-text');
            if (statusText) {
                statusText.textContent = statusText.textContent.replace(' | Transparent', '');
            }
        } else {
            panelEl.classList.add('semi-transparent');
            // Update status to show transparent mode
            const statusText = document.getElementById('ex-status-text');
            if (statusText) {
                statusText.textContent += ' | Transparent';
            }
        }
    }

    function toggleBatchMode() {
        // Enhanced batch mode with different logic for list vs table mode
        if (currentMode === 'list') {
            // Original list mode batch logic
            if (!lastHoveredElement) {
                alert('Hover over an element first to select similar elements in batch mode.');
                return;
            }

            const tagName = lastHoveredElement.tagName;
            const className = lastHoveredElement.className;

            // Find similar elements
            let selector = tagName.toLowerCase();
            if (className) {
                // Filter out script-injected classes from the selector
                const cleanClassName = className.split(' ').filter(c => !c.startsWith('extractor-')).join('.');
                if(cleanClassName) {
                    selector += '.' + cleanClassName;
                }
            }

            const similarElements = Array.from(document.querySelectorAll(selector))
                .filter(el => !isIgnorableElement(el) && el.innerText.trim());

            if (similarElements.length === 0) {
                alert('No similar elements found.');
                return;
            }

            const confirmMsg = `Found ${similarElements.length} similar elements. Add all to selection?`;
            if (!confirm(confirmMsg)) return;

            addToHistory('batch_add');

            similarElements.forEach(el => {
                const text = el.innerText.trim();
                if (!text) return;

                el.classList.add('extractor-selected-highlight');
                if (!listData.includes(text)) {
                    listData.push(text);
                }
            });

        } else {
            // Simplified table mode batch logic using proven List mode approach
            if (!firstRowCompleted) {
                alert('Please complete the first table row manually before using batch mode.\\n\\nThis helps the system understand your table structure and find similar rows intelligently.');
                return;
            }

            if (columnSelectors.length === 0) {
                alert('Column selectors not available. Please try selecting the first row again.');
                return;
            }

            // Use the same proven logic as List mode for each column
            const allSimilarElements = [];

            columnSelectors.forEach((cs, columnIndex) => {
                const tagName = cs.element.tagName;
                const className = cs.element.className;

                // Build selector using same logic as List mode
                let selector = tagName.toLowerCase();
                if (className) {
                    const cleanClassName = className.split(' ').filter(c => !c.startsWith('extractor-')).join('.');
                    if(cleanClassName) {
                        selector += '.' + cleanClassName;
                    }
                }

                // Find all similar elements for this column
                const similarElements = Array.from(document.querySelectorAll(selector))
                    .filter(el => !isIgnorableElement(el) && el.innerText.trim())
                    .filter(el => el !== cs.element); // Exclude the original element

                console.log(`Column ${columnIndex + 1} (${cs.columnName}): Found ${similarElements.length} similar elements with selector "${selector}"`);

                allSimilarElements.push({
                    columnIndex,
                    columnName: cs.columnName,
                    elements: similarElements,
                    selector
                });
            });

            // Find the minimum number of elements across all columns to ensure complete rows
            const minElements = Math.min(...allSimilarElements.map(col => col.elements.length));

            if (minElements === 0) {
                alert('No similar elements found for table batch mode.\\n\\nTry hovering over elements from the same table structure.');
                return;
            }

            const totalElements = minElements * tableHeaders.length;
            const confirmMsg = `Found ${minElements} similar rows with ${tableHeaders.length} columns each.\\n\\nThis will add ${totalElements} elements to your table.\\n\\nContinue?`;
            if (!confirm(confirmMsg)) return;

            addToHistory('batch_add_table_rows');

            // Add elements row by row
            for (let rowIndex = 0; rowIndex < minElements; rowIndex++) {
                const rowData = [];

                allSimilarElements.forEach(columnData => {
                    const element = columnData.elements[rowIndex];
                    if (element) {
                        const text = element.innerText.trim();
                        if (text) {
                            element.classList.add('extractor-selected-highlight');
                            rowData.push(text);
                        }
                    }
                });

                // Only add complete rows
                if (rowData.length === tableHeaders.length) {
                    tableData.push(rowData);
                }
            }
        }

        updateDisplay();
        updateStatus();
        scheduleAutoSave();
    }

    // Find similar table rows based on column selectors from first row


    function undoLastAction() {
        if (actionHistory.length === 0) {
            alert('Nothing to undo.');
            return;
        }

        const lastState = actionHistory.pop();

        // Restore previous state
        listData = [...lastState.listData];
        tableData = lastState.tableData.map(row => [...row]);
        tableHeaders = [...lastState.tableHeaders];
        currentRow = [...lastState.currentRow];

        // Restore table batch mode state from history if available
        if (currentMode === 'table' && lastState.firstRowCompleted !== undefined) {
            firstRowCompleted = lastState.firstRowCompleted;
            columnSelectors = lastState.columnSelectors ? [...lastState.columnSelectors] : [];

            // Update batch button appearance based on restored state
            const batchBtn = document.getElementById('ex-batch-btn');
            if (batchBtn) {
                if (firstRowCompleted && columnSelectors.length > 0) {
                    batchBtn.title = 'Smart Batch: Select similar table rows';
                    batchBtn.style.background = '#28a745';
                    batchBtn.style.color = 'white';
                } else {
                    batchBtn.title = 'Select Similar Elements';
                    batchBtn.style.background = '';
                    batchBtn.style.color = '';
                }
            }
        } else if (currentMode === 'table') {
            // Reset if no history data available
            firstRowElements = [];
            columnSelectors = [];
            firstRowCompleted = false;

            const batchBtn = document.getElementById('ex-batch-btn');
            if (batchBtn) {
                batchBtn.title = 'Select Similar Elements';
                batchBtn.style.background = '';
                batchBtn.style.color = '';
            }
        }

        // This is complex, so for now we just clear all highlights
        // and let the user re-highlight if needed. A more advanced implementation
        // would track individual elements.
        document.querySelectorAll('.extractor-selected-highlight').forEach(el => {
            el.classList.remove('extractor-selected-highlight');
        });

        updateDisplay();
        updateStatus();
        scheduleAutoSave();

        // Visual feedback
        const undoBtn = document.getElementById('ex-undo-btn');
        const originalText = undoBtn.textContent;
        undoBtn.textContent = '✓';
        setTimeout(() => {
            undoBtn.textContent = originalText;
        }, 1000);
    }

    function updateDisplay() {
        const textarea = document.getElementById('ex-extracted-data');
        if (!textarea) return;

        let output = '';
        if (currentMode === 'list') {
            output = listData.join('\n');
        } else {
            if (tableHeaders.length > 0) {
                output = tableHeaders.join('\t') + '\n';
                output += tableData.map(row => row.join('\t')).join('\n');

                // Show current row progress
                if (currentRow.length > 0) {
                    output += (output.length > 0 ? '\n' : '') + 'Next row: ' + currentRow.join('\t');
                }
            }
        }
        textarea.value = output;
        textarea.scrollTop = textarea.scrollHeight;
    }

    function updateStatus() {
        const statusText = document.getElementById('ex-status-text');
        if (!statusText) return;
        let status = `Mode: ${currentMode.charAt(0).toUpperCase() + currentMode.slice(1)}`;

        if (isSelecting) {
            if (currentMode === 'table') {
                if (tableHeaders.length > 0) {
                    status += ` | Row ${tableData.length + 1}, Col ${currentRow.length + 1}/${tableHeaders.length}`;
                } else {
                    status += ` | Set column names first`;
                }
            } else {
                status += ` | Item ${listData.length + 1}`;
            }
        } else {
            const count = currentMode === 'list' ? listData.length : tableData.length;
            status += ` | ${count} items collected`;
        }
        statusText.textContent = status;
    }

    function clearData() {
        // Add to history before clearing
        if (listData.length > 0 || tableData.length > 0) {
            addToHistory('clear_all');
        }

        listData = [];
        tableData = [];
        tableHeaders = [];
        currentRow = [];

        // Reset table batch mode state
        firstRowElements = [];
        columnSelectors = [];
        firstRowCompleted = false;

        // Reset batch button appearance
        const batchBtn = document.getElementById('ex-batch-btn');
        if (batchBtn) {
            batchBtn.title = 'Select Similar Elements';
            batchBtn.style.background = '';
            batchBtn.style.color = '';
        }

        if (isSelecting) {
            toggleSelection();
        }

        document.querySelectorAll('.extractor-selected-highlight, .extractor-preview-highlight').forEach(el => {
            el.classList.remove('extractor-selected-highlight', 'extractor-preview-highlight');
        });

        if (panelEl) {
            document.getElementById('ex-extracted-data').value = '';
            document.getElementById('ex-column-names').value = '';
        }

        updateStatus();
        scheduleAutoSave();
    }

    function copyToClipboard() {
        const textarea = document.getElementById('ex-extracted-data');
        const copyBtn = document.getElementById('ex-copy-btn');
        if (!textarea || !copyBtn) return;

        if (!textarea.value) {
            alert('No data to copy!');
            return;
        }

        navigator.clipboard.writeText(textarea.value).then(() => {
            const originalText = copyBtn.textContent;
            copyBtn.textContent = 'Copied!';
            copyBtn.style.background = '#28a745';
            copyBtn.style.color = 'white';

            setTimeout(() => {
                copyBtn.textContent = originalText;
                copyBtn.style.background = '';
                copyBtn.style.color = '';
            }, 2000);
        }).catch(err => {
            console.error('Failed to copy text: ', err);

            // Fallback: select text for manual copy
            textarea.select();
            textarea.setSelectionRange(0, 99999);
            alert('Could not copy automatically. Text has been selected - press Ctrl+C to copy.');
        });
    }

    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        let isDragging = false;
        const header = element.querySelector(".ex-header");
        if (!header) return;

        // Enhanced dragging with better UX
        header.style.cursor = 'move';
        header.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            // Prevent dragging when clicking on buttons
            if (e.target.classList.contains('ex-close-btn') || e.target.classList.contains('ex-minimize-btn')) {
                return;
            }

            e.preventDefault();
            isDragging = true;
            pos3 = e.clientX;
            pos4 = e.clientY;

            // Add visual feedback during drag
            element.style.transition = 'none';
            element.style.opacity = '0.9';
            element.style.transform = 'scale(1.02)';

            // Temporarily reduce pointer events on body to prevent interference
            document.body.style.pointerEvents = 'none';
            element.style.pointerEvents = 'auto';

            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;

            // Add class for styling during drag
            element.classList.add('dragging');
        }

        function elementDrag(e) {
            if (!isDragging) return;

            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;

            const newTop = element.offsetTop - pos2;
            const newLeft = element.offsetLeft - pos1;

            // Better viewport boundary detection with padding
            const padding = 10;
            const maxTop = window.innerHeight - element.offsetHeight - padding;
            const maxLeft = window.innerWidth - element.offsetWidth - padding;

            element.style.top = Math.max(padding, Math.min(newTop, maxTop)) + "px";
            element.style.left = Math.max(padding, Math.min(newLeft, maxLeft)) + "px";
        }

        function closeDragElement() {
            isDragging = false;
            document.onmouseup = null;
            document.onmousemove = null;

            // Restore visual state and pointer events
            element.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
            element.style.opacity = '1';
            element.style.transform = 'scale(1)';
            document.body.style.pointerEvents = 'auto';

            // Remove drag class
            element.classList.remove('dragging');

            // Save position for next session
            const rect = element.getBoundingClientRect();
            GM_setValue('panel_position', {
                top: rect.top,
                left: rect.left
            });
        }

        // Restore last position if available
        const savedPosition = GM_getValue('panel_position', null);
        if (savedPosition) {
            // Use padding equal to that in drag to avoid stuck edges if window resized
            const padding = 10;
            const maxTop = window.innerHeight - element.offsetHeight - padding;
            const maxLeft = window.innerWidth - element.offsetWidth - padding;

            element.style.top = `${Math.min(Math.max(padding, savedPosition.top), maxTop)}px`;
            element.style.left = `${Math.min(Math.max(padding, savedPosition.left), maxLeft)}px`;
        }
    }

    // Toggle panel transparency for better element selection
    function togglePanelTransparency() {
        if (!panelEl) return;

        const isTransparent = panelEl.classList.contains('semi-transparent');
        const transparencyIndicator = document.getElementById('transparency-indicator');

        if (isTransparent) {
            panelEl.classList.remove('semi-transparent');
            if (transparencyIndicator) transparencyIndicator.style.display = 'none';
            // Update status to show normal mode
            const statusText = document.getElementById('ex-status-text');
            if (statusText) {
                statusText.textContent = statusText.textContent.replace(' | Transparent', '');
            }
        } else {
            panelEl.classList.add('semi-transparent');
            if (transparencyIndicator) transparencyIndicator.style.display = 'inline';
            // Update status to show transparent mode
            const statusText = document.getElementById('ex-status-text');
            if (statusText) {
                statusText.textContent += ' | Transparent';
            }
        }
    }

    // --- SCRIPT INITIALIZATION ---
    function init() {
        // Load saved state
        extractorEnabled = false;

        // Register menu command
        updateMenuCommand();

        // Initialize if enabled
        if (extractorEnabled) {
            // Delay initialization to ensure page is fully loaded
            setTimeout(initExtractor, 500);
        }
    }

    // Initialize when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();