🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

Google SERP Scraper

Scrape Google SERP results. View, filter, and export (JSON, CSV, MD, URLs). Configurable.

Pada tanggal 16 Mei 2025. Lihat %(latest_version_link).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

// ==UserScript==
// @name         Google SERP Scraper
// @namespace    https://greasyfork.org/en/users/1467948-stonedkhajiit
// @version      0.1.0
// @description  Scrape Google SERP results. View, filter, and export (JSON, CSV, MD, URLs). Configurable.
// @author       StonedKhajiit
// @match        https://www.google.com/*search?*
// @match        https://www.google.*/*search?*
// @exclude      /^https:\/\/www\.google\.[^/]+\/search\?.*(?:tbm=(?:isch|nws|shop|vid|bks|fin|app)|udm=(?:2|7|28|36)).*$/
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Application Namespace ---
    const GSRS_App = {
        // Core State
        allResults: [],
        filteredResults: [],
        observer: null, // Will remain null
        currentSettings: {},

        // Script-wide state variables
        state: {
            filterTimeout: null,
            copyDownloadTarget: 'current',
            currentViewMode: 'list',
            selectedResultListItem: null,
            isMaximized: false,
            originalPanelState: { top: '', left: '', width: '', height: '', right: '' },
            originalTitleBarText: 'Google SERP Scraper',
            selectorTestHighlightTimeout: null,
            currentContextMenuItemData: null,
            contextMenuHighlightTimeout: null,
            lastListItemHighlightedElement: null,
        },

        // UI Element References
        uiElements: {
            uiContainer: null, resultsTextArea: null, uiMessageDiv: null, filterInput: null,
            resultsCountSpan: null, observerStatusSpan: null, resultsListContainer: null,
            resultPreviewArea: null, settingsPanel: null, contextMenuElement: null,
            settingsDOMElements: {},
        },

        DEFAULT_SETTINGS: {
            titleSelector: 'a h3',
            // observerTargetSelector is no longer actively used for observing, but kept for settings UI
            observerTargetSelector: '#rso, #search, #main, body',
            uiContentVisible: true, uiSettingsVisible: false, uiPanelTop: '20px', uiPanelLeft: 'auto', uiPanelRight: '20px', uiPanelWidth: '380px',
            debugMode: false, highlightParsed: true, highlightListItemOnPage: true,
            showPreviewInListMode: true, showFilterInputArea: true, showDownloadActionsArea: true,
            darkMode: false, lastFilterTerm: '',
            fetchTitle: true, fetchUrl: true, fetchSiteName: true, fetchBreadcrumbs: true, fetchDescription: true, fetchDescriptionKeywords: true, fetchDateInfo: true,
            decodeUrlsToReadable: true, hideDisabledFetchFields: true,
            exportCsvMdPosition: true, exportCsvMdTitle: true, exportCsvMdUrl: true, exportCsvMdSiteName: true, exportCsvMdBreadcrumbs: true, exportCsvMdDescription: true, exportCsvMdHighlightedSnippets: true, exportCsvMdOriginalDateText: true, exportCsvMdParsedDateISO: true,
        },

        INTERNAL_SELECTORS: {
            potentialResultBlockSelectors: ['div.xfX4Ac', 'div.MjjYud', 'div.g', 'div.tF2Cxc', 'div.Gx5Zad'],
            ancestorToExcludeBlockIfInside: '', parentContainerToExcludeBlockIfInside: '',
            knowledgePanelSelector: [ 'div.kp-wholepage', 'div[data-kpsecret]', 'div.kp-wholepage-osrp', 'div.bzXtMb.M8OgIe.dRpWwb', 'div.TQc1id.IVvPP' ].join(','),
            knowledgePanelCoreContentDirectChild: [ ':scope > div[jscontroller="sG005c"]', ':scope > div.mod', ':scope > div.kp-UID', ':scope > div[data-hveid="CAkQCQ"]', ':scope > div.yTFeqb.wp-ms.oJxARb', ':scope > div.xpdopen', ':scope > div.SALvLe.k29K0b' ].join(','),
            carouselStructureIndicator: 'div.XNfAUb, div.pla-carousel',
            relatedQuestionsBlockHeadingSpan: 'span.mgAbYb.OSrXXb.RES9jf.IFnjPb',
            relatedQuestionsBlockTextIndicators: ['相關問題', 'People also ask', 'Autres questions également posées', 'Ähnliche Fragen', 'Otras personas también preguntan', '関連する質問'],
            individualRelatedQuestionPair: '.related-question-pair, div[jscontroller="xfmZMb"]',
            outerDescriptionBlockSelector: 'div.kb0PBd[data-sncf^="1"]',
            directDescriptionContainer: 'div.VwiC3b.yXK7lf:not(:has(div.fzUZNc)):not(.yfStGF)',
            genericDescriptionContainer: 'div.VwiC3b:not([class*=" "]):not(:has(img)):not(:has(video)):not(:has(div.fzUZNc)):not(.yfStGF), div.VwiC3b.p4wth:not(.yXK7lf)',
            videoDescriptionSelector: 'div.fzUZNc > div.ITZIwc.p4wth',
            siteNameSelector: 'span.VuuXrf, .byrV5b .cHaqb:first-of-type',
            citeDisplay: 'cite, .qLRx3b',
            anchorInTitle: 'a',
            dataAttributesForCandidate: ['data-hveid', 'data-ved'],
            elementsToExcludeFromText: 'a, h1, h2, h3, h4, h5, h6, cite, button, form, input, script, style, nav, footer, .TbwUpd, .B6fmyf',
            datePrefixSpanSelector: 'span.YrbPuc, span[style*="color:#70757a"]',
            descriptionKeywordSelector: 'em.t55VCb, .VwiC3b span > em',
        },

        // Debounce timers (less critical now but kept for filter)
        observerDebounceTimer: null,
        URL_CHANGE_DEBOUNCE_DELAY: 500, // No longer used for auto-scraping
        OBSERVER_DEBOUNCE_DELAY: 400, // No longer used for auto-scraping
    };

    // --- URL Change Detection Logic (Simplified as it's not the primary auto-trigger now) ---
    GSRS_App.urlChangeDetector = {
        lastUrl: '',
        lastStartParam: undefined,

        // This function will now mainly serve to update lastUrl and lastStartParam.
        // Actual scraping on URL change is removed as per decision to abandon InfyScroll auto-load.
        checkUrlChange: function(source = "manual_or_init") {
            const currentUrl = window.location.href;
            const currentQueryString = window.location.search;
            const urlParams = new URLSearchParams(currentQueryString);
            const newStartParam = urlParams.get('start');

            if (GSRS_App.currentSettings.debugMode) {
                console.log(`GSRS_Debug (URLCheck - Info Only): Source: ${source}, Current URL: ${currentUrl}, Last URL: ${this.lastUrl}, NewStart: ${newStartParam}, LastStart: ${this.lastStartParam}`);
            }

            if (currentUrl !== this.lastUrl) {
                 if (GSRS_App.currentSettings.debugMode) {
                    console.log(`GSRS (URLChange - Info Only): URL recorded as changed from "${this.lastUrl}" to "${currentUrl}"`);
                }
            }
            this.lastUrl = currentUrl;
            this.lastStartParam = newStartParam;
        },

        init: function() {
            this.lastUrl = window.location.href; // Initialize with current URL
            const initialParams = new URLSearchParams(window.location.search);
            this.lastStartParam = initialParams.get('start'); // Initialize with current start param

            if (GSRS_App.currentSettings.debugMode) {
                console.log(`GSRS (URLChangeDetector Init - Info Only): Initial URL: ${this.lastUrl}, Initial 'start' param: ${this.lastStartParam}`);
            }

            // Override history methods to keep track of URL for informational purposes if needed,
            // but they won't trigger scrapes.
            const originalPushState = history.pushState;
            history.pushState = function() {
                const prev = window.location.href;
                const result = originalPushState.apply(this, arguments);
                if(window.location.href !== prev && GSRS_App.urlChangeDetector) GSRS_App.urlChangeDetector.checkUrlChange('history_pushstate');
                return result;
            };

            const originalReplaceState = history.replaceState;
            history.replaceState = function() {
                const prev = window.location.href;
                const result = originalReplaceState.apply(this, arguments);
                if(window.location.href !== prev && GSRS_App.urlChangeDetector) GSRS_App.urlChangeDetector.checkUrlChange('history_replacestate');
                return result;
            };

            window.addEventListener('popstate', () => {
                if(GSRS_App.urlChangeDetector) GSRS_App.urlChangeDetector.checkUrlChange('popstate');
            });

            if (GSRS_App.currentSettings.debugMode) {
                console.log("GSRS (URLChangeDetector - Info Only): Initialized. History API calls will be logged if debug mode is on.");
            }
             // Initial check to set the baseline URL and start param.
            this.checkUrlChange('initial_setup');
        }
    };


    // --- Settings Manager ---
    GSRS_App.settingsManager = {
        load: function() {
            GSRS_App.currentSettings = { ...GSRS_App.DEFAULT_SETTINGS };
            Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                GSRS_App.currentSettings[key] = GM_getValue(`gsrs_${key}`, GSRS_App.DEFAULT_SETTINGS[key]);
            });
            GSRS_App.state.currentViewMode = GM_getValue('gsrs_lastViewMode', GSRS_App.state.currentViewMode);
            if (GSRS_App.currentSettings.debugMode) console.log("GSRS: Settings & UI State loaded:", JSON.parse(JSON.stringify(GSRS_App.currentSettings)));
        },
        saveUIPrefs: function() {
            GM_setValue('gsrs_uiContentVisible', GSRS_App.currentSettings.uiContentVisible);
            GM_setValue('gsrs_uiSettingsVisible', GSRS_App.currentSettings.uiSettingsVisible);
            const uiContainer = GSRS_App.uiElements.uiContainer;
            if (uiContainer && !GSRS_App.state.isMaximized) {
                const currentTop = uiContainer.style.top;
                const currentLeft = uiContainer.style.left;
                const currentRight = uiContainer.style.right;
                GSRS_App.currentSettings.uiPanelTop = (currentTop && currentTop.endsWith('px')) ? currentTop : GSRS_App.DEFAULT_SETTINGS.uiPanelTop;
                GM_setValue('gsrs_uiPanelTop', GSRS_App.currentSettings.uiPanelTop);

                if (currentLeft && currentLeft !== 'auto') {
                    GSRS_App.currentSettings.uiPanelLeft = (currentLeft.endsWith('px')) ? currentLeft : GSRS_App.DEFAULT_SETTINGS.uiPanelLeft;
                    GSRS_App.currentSettings.uiPanelRight = 'auto';
                    GM_setValue('gsrs_uiPanelLeft', GSRS_App.currentSettings.uiPanelLeft);
                    GM_setValue('gsrs_uiPanelRight', 'auto');
                } else if (currentRight && currentRight !== 'auto') {
                    GSRS_App.currentSettings.uiPanelRight = (currentRight.endsWith('px')) ? currentRight : GSRS_App.DEFAULT_SETTINGS.uiPanelRight;
                    GSRS_App.currentSettings.uiPanelLeft = 'auto';
                    GM_setValue('gsrs_uiPanelRight', GSRS_App.currentSettings.uiPanelRight);
                    GM_setValue('gsrs_uiPanelLeft', 'auto');
                } else {
                    GSRS_App.currentSettings.uiPanelRight = GSRS_App.DEFAULT_SETTINGS.uiPanelRight;
                    GSRS_App.currentSettings.uiPanelLeft = 'auto';
                    GM_setValue('gsrs_uiPanelRight', GSRS_App.currentSettings.uiPanelRight);
                    GM_setValue('gsrs_uiPanelLeft', 'auto');
                }
            }
            GM_setValue('gsrs_lastViewMode', GSRS_App.state.currentViewMode);
        },
        updateInputs: function() {
            const elements = GSRS_App.uiElements.settingsDOMElements; if (!elements || Object.keys(elements).length === 0) { return; }
            if (elements.titleInput) elements.titleInput.value = GSRS_App.currentSettings.titleSelector;
            if (elements.observerInput) elements.observerInput.value = GSRS_App.currentSettings.observerTargetSelector;
            if (elements.debugCheckbox) elements.debugCheckbox.checked = GSRS_App.currentSettings.debugMode;
            if (elements.highlightCheckbox) elements.highlightCheckbox.checked = GSRS_App.currentSettings.highlightParsed;
            if (elements.highlightListItemOnPageCheckbox) elements.highlightListItemOnPageCheckbox.checked = GSRS_App.currentSettings.highlightListItemOnPage;
            if (elements.showPreviewInListModeCheckbox) elements.showPreviewInListModeCheckbox.checked = GSRS_App.currentSettings.showPreviewInListMode;
            if (elements.showFilterInputAreaCheckbox) elements.showFilterInputAreaCheckbox.checked = GSRS_App.currentSettings.showFilterInputArea;
            if (elements.showDownloadActionsAreaCheckbox) elements.showDownloadActionsAreaCheckbox.checked = GSRS_App.currentSettings.showDownloadActionsArea;
            if (elements.darkModeCheckbox) elements.darkModeCheckbox.checked = GSRS_App.currentSettings.darkMode;
            if (elements.fetchTitleCheckbox) elements.fetchTitleCheckbox.checked = GSRS_App.currentSettings.fetchTitle;
            if (elements.fetchUrlCheckbox) elements.fetchUrlCheckbox.checked = GSRS_App.currentSettings.fetchUrl;
            if (elements.fetchSiteNameCheckbox) elements.fetchSiteNameCheckbox.checked = GSRS_App.currentSettings.fetchSiteName;
            if (elements.fetchDescriptionCheckbox) elements.fetchDescriptionCheckbox.checked = GSRS_App.currentSettings.fetchDescription;
            if (elements.fetchDescriptionKeywordsCheckbox) elements.fetchDescriptionKeywordsCheckbox.checked = GSRS_App.currentSettings.fetchDescriptionKeywords;
            if (elements.fetchDateInfoCheckbox) elements.fetchDateInfoCheckbox.checked = GSRS_App.currentSettings.fetchDateInfo;
            if (elements.fetchBreadcrumbsCheckbox) elements.fetchBreadcrumbsCheckbox.checked = GSRS_App.currentSettings.fetchBreadcrumbs;
            if (elements.decodeUrlsCheckbox) elements.decodeUrlsCheckbox.checked = GSRS_App.currentSettings.decodeUrlsToReadable;
            if (elements.hideFieldsCheckbox) elements.hideFieldsCheckbox.checked = GSRS_App.currentSettings.hideDisabledFetchFields;

            Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                if (key.startsWith('exportCsvMd')) {
                    const checkboxId = `gsrs-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}-checkbox`;
                    const checkbox = document.getElementById(checkboxId);
                    if (checkbox) { checkbox.checked = GSRS_App.currentSettings[key]; }
                }
            });
            const fetchDescCheckbox = elements.fetchDescriptionCheckbox;
            const fetchKeywordsContainer = document.getElementById('gsrs-fetch-keywords-container');
            if (fetchDescCheckbox && fetchKeywordsContainer) {
                fetchKeywordsContainer.style.display = fetchDescCheckbox.checked ? 'block' : 'none';
            }
        },
        save: function() {
            const elements = GSRS_App.uiElements.settingsDOMElements; if (!elements || Object.keys(elements).length === 0) { GSRS_App.uiManager.showUIMessage('Error: Settings DOM elements not found.', 'error'); return; }
            const { titleInput, observerInput, debugCheckbox, highlightCheckbox, highlightListItemOnPageCheckbox, showPreviewInListModeCheckbox, showFilterInputAreaCheckbox, showDownloadActionsAreaCheckbox, darkModeCheckbox, fetchTitleCheckbox, fetchUrlCheckbox, fetchSiteNameCheckbox, fetchDescriptionCheckbox, fetchDescriptionKeywordsCheckbox, fetchBreadcrumbsCheckbox, decodeUrlsCheckbox, fetchDateInfoCheckbox, hideFieldsCheckbox } = elements;
            if (!titleInput || !observerInput || !debugCheckbox || !highlightCheckbox || !highlightListItemOnPageCheckbox || !showPreviewInListModeCheckbox || !showFilterInputAreaCheckbox || !showDownloadActionsAreaCheckbox || !darkModeCheckbox || !fetchTitleCheckbox || !fetchUrlCheckbox || !fetchSiteNameCheckbox || !fetchDescriptionCheckbox || !fetchDescriptionKeywordsCheckbox || !fetchBreadcrumbsCheckbox || !decodeUrlsCheckbox || !fetchDateInfoCheckbox || !hideFieldsCheckbox ) { GSRS_App.uiManager.showUIMessage('Error: One or more core settings input elements are missing.', 'error'); if(GSRS_App.currentSettings.debugMode) console.error("GSRS SaveSettings: Missing core DOM elements", elements); return; }

            const newTitleSelector = titleInput.value.trim();
            if (!newTitleSelector || !isValidSelector(newTitleSelector)) {
                GSRS_App.uiManager.showUIMessage(`Invalid or empty Title Selector: "${newTitleSelector.substring(0,50)}...". Using default.`, 'error', 7000);
                GSRS_App.currentSettings.titleSelector = GSRS_App.DEFAULT_SETTINGS.titleSelector;
            } else {
                GSRS_App.currentSettings.titleSelector = newTitleSelector;
            }
            const newObserverSelector = observerInput.value.trim();
            if (newObserverSelector && newObserverSelector.indexOf(',') === -1 && !isValidSelector(newObserverSelector)) {
                 GSRS_App.uiManager.showUIMessage(`Invalid Observer Target Selector (Legacy): ${newObserverSelector.substring(0,50)}...`, 'error', 5000); return;
            }
            if (newObserverSelector && newObserverSelector.includes(',')) {
                const selectors = newObserverSelector.split(',').map(s => s.trim());
                for (const sel of selectors) {
                    if (!sel || !isValidSelector(sel)) { GSRS_App.uiManager.showUIMessage(`Invalid selector in Observer Targets list (Legacy): ${sel.substring(0,50)}...`, 'error', 5000); return; }
                }
            }
            GSRS_App.currentSettings.observerTargetSelector = newObserverSelector || GSRS_App.DEFAULT_SETTINGS.observerTargetSelector;

            GSRS_App.currentSettings.debugMode = debugCheckbox.checked;
            GSRS_App.currentSettings.highlightParsed = highlightCheckbox.checked;
            GSRS_App.currentSettings.highlightListItemOnPage = highlightListItemOnPageCheckbox.checked;
            GSRS_App.currentSettings.showPreviewInListMode = showPreviewInListModeCheckbox.checked;
            GSRS_App.currentSettings.showFilterInputArea = showFilterInputAreaCheckbox.checked;
            GSRS_App.currentSettings.showDownloadActionsArea = showDownloadActionsAreaCheckbox.checked;
            GSRS_App.currentSettings.fetchTitle = fetchTitleCheckbox.checked;
            GSRS_App.currentSettings.fetchUrl = fetchUrlCheckbox.checked;
            GSRS_App.currentSettings.fetchSiteName = fetchSiteNameCheckbox.checked;
            GSRS_App.currentSettings.fetchDescription = fetchDescriptionCheckbox.checked;
            GSRS_App.currentSettings.fetchDescriptionKeywords = fetchDescriptionKeywordsCheckbox.checked;
            GSRS_App.currentSettings.fetchBreadcrumbs = fetchBreadcrumbsCheckbox.checked;
            GSRS_App.currentSettings.decodeUrlsToReadable = decodeUrlsCheckbox.checked;
            GSRS_App.currentSettings.fetchDateInfo = fetchDateInfoCheckbox.checked;
            GSRS_App.currentSettings.hideDisabledFetchFields = hideFieldsCheckbox.checked;

            Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                if (key === 'darkMode') {
                    GM_setValue(`gsrs_${key}`, darkModeCheckbox.checked);
                    GSRS_App.currentSettings[key] = darkModeCheckbox.checked;
                } else if (GSRS_App.currentSettings.hasOwnProperty(key)) {
                    GM_setValue(`gsrs_${key}`, GSRS_App.currentSettings[key]);
                }
            });
            Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                if (key.startsWith('exportCsvMd')) {
                    const checkboxId = `gsrs-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}-checkbox`;
                    const checkbox = document.getElementById(checkboxId);
                    if (checkbox) {
                        GSRS_App.currentSettings[key] = checkbox.checked;
                        GM_setValue(`gsrs_${key}`, GSRS_App.currentSettings[key]);
                    } else if (GSRS_App.currentSettings.debugMode) {
                        console.warn(`GSRS SaveSettings: Checkbox with ID ${checkboxId} for export setting ${key} not found.`);
                    }
                }
            });

            titleInput.value = GSRS_App.currentSettings.titleSelector;
            observerInput.value = GSRS_App.currentSettings.observerTargetSelector;

            const warningP = document.querySelector('#gsrs-settings-panel .gsrs-settings-warning');
            if (warningP) { warningP.innerHTML = `Extraction uses title selector '<code>${GSRS_App.currentSettings.titleSelector}</code>' to find parent blocks.`; }

            if (GSRS_App.uiElements.resultPreviewArea) {
                GSRS_App.uiElements.resultPreviewArea.style.display = (GSRS_App.currentSettings.showPreviewInListMode && GSRS_App.state.currentViewMode === 'list') ? 'block' : 'none';
            }
            const filterContainerEl = document.querySelector('.gsrs-filter-container');
            if (filterContainerEl) { filterContainerEl.style.display = GSRS_App.currentSettings.showFilterInputArea ? 'flex' : 'none'; }
            const downloadOptionsEl = document.querySelector('.gsrs-copy-download-options');
            const downloadBarEl = document.querySelector('.gsrs-action-button-bar');
            if (downloadOptionsEl) downloadOptionsEl.style.display = GSRS_App.currentSettings.showDownloadActionsArea ? 'block' : 'none';
            if (downloadBarEl) downloadBarEl.style.display = GSRS_App.currentSettings.showDownloadActionsArea ? 'flex' : 'none';

            if (GSRS_App.state.currentViewMode === 'list') {
                GSRS_App.uiManager.toggleViewMode('list');
            }
            GSRS_App.uiManager.toggleDarkMode(darkModeCheckbox.checked);
            GSRS_App.uiManager.showUIMessage('Settings saved! Re-scrape if needed.', 'success');
            if (GSRS_App.currentSettings.debugMode) console.log("GSRS: Settings saved.");

            if (GSRS_App.observer) { GSRS_App.observer.disconnect(); GSRS_App.uiManager.updateObserverStatus(false); GSRS_App.observer = null; }
            GSRS_App.uiManager.toggleSettingsView(false);
        },
        resetToDefaults: function() {
            if (confirm("Reset ALL settings to defaults (selectors, fetching, display, UI position)? Filter term won't change.")) {
                Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                    GSRS_App.currentSettings[key] = GSRS_App.DEFAULT_SETTINGS[key];
                    GM_setValue(`gsrs_${key}`, GSRS_App.DEFAULT_SETTINGS[key]);
                });
                GSRS_App.currentSettings.uiPanelTop = GSRS_App.DEFAULT_SETTINGS.uiPanelTop;
                GSRS_App.currentSettings.uiPanelLeft = GSRS_App.DEFAULT_SETTINGS.uiPanelLeft;
                GSRS_App.currentSettings.uiPanelRight = GSRS_App.DEFAULT_SETTINGS.uiPanelRight;
                GM_setValue('gsrs_uiPanelTop', GSRS_App.currentSettings.uiPanelTop);
                GM_setValue('gsrs_uiPanelLeft', GSRS_App.currentSettings.uiPanelLeft);
                GM_setValue('gsrs_uiPanelRight', GSRS_App.currentSettings.uiPanelRight);

                const uiContainer = GSRS_App.uiElements.uiContainer;
                if (uiContainer) {
                    uiContainer.style.top = GSRS_App.currentSettings.uiPanelTop;
                    uiContainer.style.left = GSRS_App.currentSettings.uiPanelLeft;
                    uiContainer.style.right = GSRS_App.currentSettings.uiPanelRight;
                    uiContainer.style.width = GSRS_App.DEFAULT_SETTINGS.uiPanelWidth;
                }

                this.updateInputs();

                const warningP = document.querySelector('#gsrs-settings-panel .gsrs-settings-warning');
                if (warningP) { warningP.innerHTML = `Extraction uses title selector '<code>${GSRS_App.DEFAULT_SETTINGS.titleSelector}</code>' to find parent blocks.`; }

                if (GSRS_App.uiElements.resultPreviewArea) {
                    GSRS_App.uiElements.resultPreviewArea.style.display = (GSRS_App.DEFAULT_SETTINGS.showPreviewInListMode && GSRS_App.state.currentViewMode === 'list') ? 'block' : 'none';
                }
                const filterContainerEl = document.querySelector('.gsrs-filter-container');
                if (filterContainerEl) { filterContainerEl.style.display = GSRS_App.DEFAULT_SETTINGS.showFilterInputArea ? 'flex' : 'none'; }
                const downloadOptionsEl = document.querySelector('.gsrs-copy-download-options');
                const downloadBarEl = document.querySelector('.gsrs-action-button-bar');
                if (downloadOptionsEl) downloadOptionsEl.style.display = GSRS_App.DEFAULT_SETTINGS.showDownloadActionsArea ? 'block' : 'none';
                if (downloadBarEl) downloadBarEl.style.display = GSRS_App.DEFAULT_SETTINGS.showDownloadActionsArea ? 'flex' : 'none';

                if (GSRS_App.state.currentViewMode === 'list') {
                    GSRS_App.uiManager.toggleViewMode('list');
                }
                GSRS_App.uiManager.toggleDarkMode(GSRS_App.currentSettings.darkMode);

                GSRS_App.uiManager.showUIMessage('All settings reset to defaults. Re-scrape if needed.', 'success');
                if (GSRS_App.currentSettings.debugMode) console.log("GSRS: All settings reset.");
                if (GSRS_App.observer) { GSRS_App.observer.disconnect(); GSRS_App.uiManager.updateObserverStatus(false); GSRS_App.observer = null;}
                GSRS_App.uiManager.toggleSettingsView(false);
            }
        }
    };

    // --- UI Manager ---
    GSRS_App.uiManager = {
        create: function() {
            if (document.getElementById('gsrs-ui-container')) return;
            GSRS_App.uiElements.uiContainer = document.createElement('div');
            GSRS_App.uiElements.uiContainer.id = 'gsrs-ui-container';
            if (GSRS_App.currentSettings.darkMode) { GSRS_App.uiElements.uiContainer.classList.add('gsrs-dark-theme'); }

            GSRS_App.uiElements.uiContainer.style.top = GSRS_App.currentSettings.uiPanelTop || GSRS_App.DEFAULT_SETTINGS.uiPanelTop;
            if (GSRS_App.currentSettings.uiPanelLeft && GSRS_App.currentSettings.uiPanelLeft !== 'auto') {
                GSRS_App.uiElements.uiContainer.style.left = GSRS_App.currentSettings.uiPanelLeft;
                GSRS_App.uiElements.uiContainer.style.right = 'auto';
            } else if (GSRS_App.currentSettings.uiPanelRight && GSRS_App.currentSettings.uiPanelRight !== 'auto') {
                GSRS_App.uiElements.uiContainer.style.right = GSRS_App.currentSettings.uiPanelRight;
                GSRS_App.uiElements.uiContainer.style.left = 'auto';
            } else {
                GSRS_App.uiElements.uiContainer.style.left = GSRS_App.DEFAULT_SETTINGS.uiPanelLeft;
                GSRS_App.uiElements.uiContainer.style.right = GSRS_App.DEFAULT_SETTINGS.uiPanelRight;
            }

            if (GSRS_App.currentSettings.uiSettingsVisible) GSRS_App.uiElements.uiContainer.classList.add('gsrs-settings-view-active');

            const titleBar = this.createTitleBar();
            GSRS_App.uiElements.uiContainer.appendChild(titleBar);

            const contentWrapper = document.createElement('div');
            contentWrapper.id = 'gsrs-content-wrapper';
            contentWrapper.style.display = GSRS_App.currentSettings.uiContentVisible ? 'flex' : 'none';
            if (!GSRS_App.currentSettings.uiContentVisible) { GSRS_App.uiElements.uiContainer.classList.add('gsrs-minimized');}

            contentWrapper.appendChild(this.createMainActions());
            contentWrapper.appendChild(this.createFilterArea());
            contentWrapper.appendChild(this.createResultsCountAndViews());
            contentWrapper.appendChild(this.createResultsViewAreaContainer());

            GSRS_App.uiElements.uiMessageDiv = document.createElement('div');
            GSRS_App.uiElements.uiMessageDiv.id = 'gsrs-ui-message';
            contentWrapper.appendChild(GSRS_App.uiElements.uiMessageDiv);

            contentWrapper.appendChild(this.createCopyDownloadOptions());
            contentWrapper.appendChild(this.createActionButtonBar());

            GSRS_App.uiElements.settingsPanel = this.createSettingsPanel();
            contentWrapper.appendChild(GSRS_App.uiElements.settingsPanel);

            GSRS_App.uiElements.uiContainer.appendChild(contentWrapper);

            GSRS_App.uiElements.contextMenuElement = this.createContextMenu();
            document.body.appendChild(GSRS_App.uiElements.contextMenuElement);

            document.body.appendChild(GSRS_App.uiElements.uiContainer);

            this.populateSettingsDOMElements();
            this.attachSettingsPanelEvents();
            this.attachActionLinkEvents();
            this.attachMinimizeToggle(titleBar.querySelector('#gsrs-minimize-btn'), contentWrapper);
            this.makeDraggable(GSRS_App.uiElements.uiContainer, titleBar);
            this.addStyles();
        },
        createTitleBar: function() {
            const titleBar = document.createElement('div'); titleBar.id = 'gsrs-title-bar';
            const settingsToggleTitleBar = document.createElement('button'); settingsToggleTitleBar.id = 'gsrs-settings-toggle-titlebar'; settingsToggleTitleBar.innerHTML = '⚙️'; settingsToggleTitleBar.title = 'Settings'; settingsToggleTitleBar.addEventListener('click', () => this.toggleSettingsView()); titleBar.appendChild(settingsToggleTitleBar);
            const titleBarText = document.createElement('span'); titleBarText.id = 'gsrs-title-bar-text'; titleBarText.textContent = GSRS_App.state.originalTitleBarText; titleBar.appendChild(titleBarText);
            const titleBarControls = document.createElement('div'); titleBarControls.id = 'gsrs-title-bar-controls';
            GSRS_App.uiElements.observerStatusSpan = document.createElement('span'); GSRS_App.uiElements.observerStatusSpan.id = 'gsrs-observer-status';
            GSRS_App.uiElements.observerStatusSpan.innerHTML = '○'; // Default to inactive
            GSRS_App.uiElements.observerStatusSpan.title = 'Dynamic loading detection inactive'; // Title updated
            GSRS_App.uiElements.observerStatusSpan.classList.add('gsrs-obs-inactive'); titleBarControls.appendChild(GSRS_App.uiElements.observerStatusSpan);
            const maximizeButton = document.createElement('button'); maximizeButton.id = 'gsrs-maximize-btn'; maximizeButton.innerHTML = '<span class="icon">🗖</span>'; maximizeButton.title = 'Maximize Panel'; maximizeButton.addEventListener('click', toggleMaximizePanel); titleBarControls.appendChild(maximizeButton);
            const minimizeButton = document.createElement('button'); minimizeButton.id = 'gsrs-minimize-btn'; minimizeButton.textContent = '-'; minimizeButton.title = 'Minimize/Restore Panel'; titleBarControls.appendChild(minimizeButton);
            titleBar.appendChild(titleBarControls);
            return titleBar;
        },

		        attachMinimizeToggle: function(minimizeButton, contentWrapper) {
            minimizeButton.addEventListener('click', () => {
                const titleBarTextEl = document.getElementById('gsrs-title-bar-text');
                if (!contentWrapper || !titleBarTextEl) return;
                const isCurrentlyHidden = getComputedStyle(contentWrapper).display === 'none';
                const newVisibility = isCurrentlyHidden;
                contentWrapper.style.display = newVisibility ? 'flex' : 'none';
                GSRS_App.uiElements.uiContainer.classList.toggle('gsrs-minimized', !newVisibility);
                minimizeButton.textContent = newVisibility ? '-' : '+';
                GSRS_App.currentSettings.uiContentVisible = newVisibility;
                titleBarTextEl.textContent = GSRS_App.state.originalTitleBarText;
                if (!newVisibility && GSRS_App.uiElements.uiContainer.classList.contains('gsrs-settings-view-active')) {
                    this.toggleSettingsView(false);
                }
                GSRS_App.settingsManager.saveUIPrefs();
                if (newVisibility && !GSRS_App.uiElements.uiContainer.classList.contains('gsrs-settings-view-active')) {
                    contentWrapper.style.display = 'none'; void contentWrapper.offsetHeight; contentWrapper.style.display = 'flex';
                }
            });
            if(!GSRS_App.currentSettings.uiContentVisible) {
                minimizeButton.textContent = '+';
                const titleBarTextEl = document.getElementById('gsrs-title-bar-text');
                if (titleBarTextEl) titleBarTextEl.textContent = GSRS_App.state.originalTitleBarText;
            }
        },
        createMainActions: function() {
            const mainActionsDiv = document.createElement('div'); mainActionsDiv.className = 'gsrs-main-actions';
            const startButton = document.createElement('button'); startButton.id = 'gsrs-start-btn'; startButton.className = 'gsrs-button';
            startButton.textContent = 'Scrape Page'; // Text reflects no ongoing observation
            startButton.addEventListener('click', handleStartParse); mainActionsDiv.appendChild(startButton);
            const clearButton = document.createElement('button'); clearButton.id = 'gsrs-clear-btn'; clearButton.className = 'gsrs-button'; clearButton.innerHTML = '<span class="icon">🗑️</span>'; clearButton.title = 'Clear Results'; clearButton.addEventListener('click', handleClearResults); mainActionsDiv.appendChild(clearButton);
            return mainActionsDiv;
        },
        createFilterArea: function() {
            const filterContainerEl = document.createElement('div'); filterContainerEl.className = 'gsrs-filter-container';
            filterContainerEl.style.display = GSRS_App.currentSettings.showFilterInputArea ? 'flex' : 'none';
            GSRS_App.uiElements.filterInput = document.createElement('input'); GSRS_App.uiElements.filterInput.type = 'text'; GSRS_App.uiElements.filterInput.id = 'gsrs-filter-input'; GSRS_App.uiElements.filterInput.placeholder = 'Filter results by keyword...';
            GSRS_App.uiElements.filterInput.value = GSRS_App.currentSettings.lastFilterTerm;
            GSRS_App.uiElements.filterInput.addEventListener('input', handleFilterResults);
            filterContainerEl.appendChild(GSRS_App.uiElements.filterInput);
            const clearFilterButton = document.createElement('button'); clearFilterButton.id = 'gsrs-clear-filter-btn'; clearFilterButton.className = 'gsrs-button'; clearFilterButton.innerHTML = 'X'; clearFilterButton.title = 'Clear Filter';
            clearFilterButton.addEventListener('click', () => { if (GSRS_App.uiElements.filterInput) GSRS_App.uiElements.filterInput.value = ''; handleFilterResults(); });
            filterContainerEl.appendChild(clearFilterButton);
            if (GSRS_App.currentSettings.lastFilterTerm && GSRS_App.currentSettings.lastFilterTerm.trim() !== '') {
                GSRS_App.uiElements.filterInput.classList.add('gsrs-filter-active');
            }
            return filterContainerEl;
        },
        createResultsCountAndViews: function() {
            const resultsCountWrapper = document.createElement('div'); resultsCountWrapper.id = 'gsrs-results-count-wrapper';
            GSRS_App.uiElements.resultsCountSpan = document.createElement('span'); GSRS_App.uiElements.resultsCountSpan.id = 'gsrs-results-count'; GSRS_App.uiElements.resultsCountSpan.textContent = 'Results: 0'; resultsCountWrapper.appendChild(GSRS_App.uiElements.resultsCountSpan);
            const viewToggleButtons = document.createElement('div'); viewToggleButtons.className = 'gsrs-view-toggle-buttons';
            const jsonViewBtn = document.createElement('button'); jsonViewBtn.id = 'gsrs-view-toggle-json'; jsonViewBtn.textContent = '📜 JSON'; jsonViewBtn.title = "View as JSON";
            const listViewBtn = document.createElement('button'); listViewBtn.id = 'gsrs-view-toggle-list'; listViewBtn.textContent = '📄 List'; listViewBtn.title = "View as List & Preview";
            jsonViewBtn.addEventListener('click', () => this.toggleViewMode('json'));
            listViewBtn.addEventListener('click', () => this.toggleViewMode('list'));
            viewToggleButtons.appendChild(jsonViewBtn); viewToggleButtons.appendChild(listViewBtn);
            resultsCountWrapper.appendChild(viewToggleButtons);
            return resultsCountWrapper;
        },
        createResultsViewAreaContainer: function() {
            const resultsViewAreaContainer = document.createElement('div'); resultsViewAreaContainer.id = 'gsrs-results-view-area';
            GSRS_App.uiElements.resultsTextArea = document.createElement('textarea'); GSRS_App.uiElements.resultsTextArea.id = 'gsrs-results-area'; GSRS_App.uiElements.resultsTextArea.placeholder = 'Scraped results will appear here...'; GSRS_App.uiElements.resultsTextArea.readOnly = true; resultsViewAreaContainer.appendChild(GSRS_App.uiElements.resultsTextArea);
            GSRS_App.uiElements.resultsListContainer = document.createElement('div'); GSRS_App.uiElements.resultsListContainer.id = 'gsrs-results-list-container'; resultsViewAreaContainer.appendChild(GSRS_App.uiElements.resultsListContainer);
            GSRS_App.uiElements.resultPreviewArea = document.createElement('div'); GSRS_App.uiElements.resultPreviewArea.id = 'gsrs-result-preview-area'; GSRS_App.uiElements.resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 10px;">Click an item from the list to see details.</p>'; resultsViewAreaContainer.appendChild(GSRS_App.uiElements.resultPreviewArea);
            return resultsViewAreaContainer;
        },
        createCopyDownloadOptions: function() {
            const copyDownloadOptionsDiv = document.createElement('div'); copyDownloadOptionsDiv.className = 'gsrs-copy-download-options';
            copyDownloadOptionsDiv.style.display = GSRS_App.currentSettings.showDownloadActionsArea ? 'block' : 'none';
            copyDownloadOptionsDiv.innerHTML = `<span>Action Target: </span><label><input type="radio" name="gsrsCopyDownloadTarget" value="current" checked> Current View</label><label><input type="radio" name="gsrsCopyDownloadTarget" value="all"> All Results</label>`;
            copyDownloadOptionsDiv.querySelectorAll('input[name="gsrsCopyDownloadTarget"]').forEach(radio => {
                radio.addEventListener('change', (event) => {
                    GSRS_App.state.copyDownloadTarget = event.target.value;
                    if(GSRS_App.currentSettings.debugMode) console.log("GSRS Debug: Copy/Download target set to:", GSRS_App.state.copyDownloadTarget);
                    this.updateActionLinkTitles();
                    updateActionButtonsState();
                });
            });
            const currentTargetRadio = copyDownloadOptionsDiv.querySelector(`input[value="${GSRS_App.state.copyDownloadTarget}"]`);
            if (currentTargetRadio) currentTargetRadio.checked = true;
            return copyDownloadOptionsDiv;
        },
        createActionButtonBar: function() {
            const actionButtonBar = document.createElement('div'); actionButtonBar.id = 'gsrs-action-button-bar'; actionButtonBar.className = 'gsrs-action-button-bar';
            actionButtonBar.style.display = GSRS_App.currentSettings.showDownloadActionsArea ? 'flex' : 'none';
            const copyActionSet = document.createElement('div'); copyActionSet.className = 'gsrs-action-set';
            const copyIcon = document.createElement('span'); copyIcon.className = 'icon gsrs-action-icon'; copyIcon.innerHTML = '📋'; copyActionSet.appendChild(copyIcon);
            ['json', 'urls', 'md'].forEach((format, index, arr) => {
                const link = document.createElement('span'); link.className = 'gsrs-action-format-link'; link.dataset.action = 'copy'; link.dataset.format = format; link.textContent = format.toUpperCase();
                if (format === 'urls') link.textContent = 'URLs'; if (format === 'md') link.textContent = 'MD';
                copyActionSet.appendChild(link); if (index < arr.length - 1) copyActionSet.appendChild(document.createTextNode(', '));
            });
            actionButtonBar.appendChild(copyActionSet);
            const downloadActionSet = document.createElement('div'); downloadActionSet.className = 'gsrs-action-set';
            const downloadIcon = document.createElement('span'); downloadIcon.className = 'icon gsrs-action-icon'; downloadIcon.innerHTML = '⬇️'; downloadActionSet.appendChild(downloadIcon);
            ['json', 'csv', 'urls', 'md'].forEach((format, index, arr) => {
                const link = document.createElement('span'); link.className = 'gsrs-action-format-link'; link.dataset.action = 'download'; link.dataset.format = format; link.textContent = format.toUpperCase();
                if (format === 'urls') link.textContent = 'URLs.txt'; if (format === 'md') link.textContent = 'MD';
                downloadActionSet.appendChild(link); if (index < arr.length - 1) downloadActionSet.appendChild(document.createTextNode(', '));
            });
            actionButtonBar.appendChild(downloadActionSet);
            this.updateActionLinkTitles();
            return actionButtonBar;
        },
        updateActionLinkTitles: function() {
            const actionButtonBar = document.getElementById('gsrs-action-button-bar'); if (!actionButtonBar) return;
            const targetText = GSRS_App.state.copyDownloadTarget === 'current' ? '(Current View)' : '(All Results)';
            actionButtonBar.querySelectorAll('.gsrs-action-format-link').forEach(link => {
                const action = link.dataset.action; const format = link.dataset.format.toUpperCase();
                let baseTitle = `${action.charAt(0).toUpperCase() + action.slice(1)} ${format === 'URLS' ? 'URLs' : format.replace('URLS.TXT', 'URLs.txt')}`;
                if (format === 'MD') baseTitle = `${action.charAt(0).toUpperCase() + action.slice(1)} MD`;
                link.title = `${baseTitle} ${targetText}`;
            });
        },
        createSettingsPanel: function() {
            const settingsPanel = document.createElement('div'); settingsPanel.id = 'gsrs-settings-panel';
            let exportOptionsHTML = '<div class="gsrs-export-options-grid">';
            const exportFieldLabels = { Position: "Position", Title: "Title", Url: "URL", SiteName: "Site Name", Breadcrumbs: "Breadcrumbs", Description: "Description", HighlightedSnippets: "Keywords", OriginalDateText: "Original Date", ParsedDateISO: "Parsed Date" };
            Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                if (key.startsWith('exportCsvMd')) {
                    const fieldName = key.substring('exportCsvMd'.length);
                    const checkboxId = `gsrs-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}-checkbox`;
                    const labelText = exportFieldLabels[fieldName] || fieldName;
                    exportOptionsHTML += `<div><input type="checkbox" id="${checkboxId}"><label for="${checkboxId}" style="font-weight:normal; cursor:pointer;">${labelText}</label></div>`;
                }
            });
            exportOptionsHTML += '</div>';
            settingsPanel.innerHTML = ` <details open> <summary>Selectors</summary> <div> <div class="gsrs-setting-item"> <label for="gsrs-input-title-selector">Title Element Selector:</label> <div class="gsrs-setting-input-group"> <input type="text" id="gsrs-input-title-selector" title="CSS selector for result titles."> <button class="gsrs-test-selector-btn" data-input-id="gsrs-input-title-selector" data-result-id="gsrs-title-test-result" data-preview-id="gsrs-title-test-preview">Test</button> <span class="gsrs-test-result-span" id="gsrs-title-test-result"></span> </div> <div class="gsrs-selector-test-preview" id="gsrs-title-test-preview"></div> </div> <div class="gsrs-setting-item"> <label for="gsrs-input-observer-selector">Observer Target Selector(s) (Legacy/Informational):</label> <div class="gsrs-setting-input-group"> <input type="text" id="gsrs-input-observer-selector" title="Legacy: Informational only, not used for active observation."> <button class="gsrs-test-selector-btn" data-input-id="gsrs-input-observer-selector" data-result-id="gsrs-observer-test-result" data-preview-id="gsrs-observer-test-preview">Test</button> <span class="gsrs-test-result-span" id="gsrs-observer-test-result"></span> </div> <div class="gsrs-selector-test-preview" id="gsrs-observer-test-preview"></div> </div> </div> </details> <details> <summary>Data Fetching & Processing</summary> <div> <div class="gsrs-setting-input-group" style="flex-direction: column; align-items: flex-start;"> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-fetch-title-checkbox"><label for="gsrs-fetch-title-checkbox" style="font-weight:normal; cursor:pointer;">Fetch Title</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-fetch-url-checkbox"><label for="gsrs-fetch-url-checkbox" style="font-weight:normal; cursor:pointer;">Fetch URL</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-fetch-sitename-checkbox"><label for="gsrs-fetch-sitename-checkbox" style="font-weight:normal; cursor:pointer;">Fetch Site Name</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-fetch-breadcrumbs-checkbox"><label for="gsrs-fetch-breadcrumbs-checkbox" style="font-weight:normal; cursor:pointer;">Fetch Breadcrumbs</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-fetch-description-checkbox"><label for="gsrs-fetch-description-checkbox" style="font-weight:normal; cursor:pointer;">Fetch Description</label></div> <div style="margin-left: 20px; margin-bottom: 5px; display: none;" id="gsrs-fetch-keywords-container"> <input type="checkbox" id="gsrs-fetch-description-keywords-checkbox"><label for="gsrs-fetch-description-keywords-checkbox" style="font-weight:normal; cursor:pointer;">Fetch Highlighted Keywords in Description</label> </div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-fetch-dateinfo-checkbox"><label for="gsrs-fetch-dateinfo-checkbox" style="font-weight:normal; cursor:pointer;">Fetch Date Information (from Description)</label></div> <div><input type="checkbox" id="gsrs-decode-urls-checkbox"><label for="gsrs-decode-urls-checkbox" style="font-weight:normal; cursor:pointer;">Decode URLs to Readable Format</label></div> </div> </div> </details> <details> <summary>CSV/Markdown Export Fields</summary> <div> ${exportOptionsHTML} </div> </details> <details> <summary>Interface Display Options</summary> <div> <div class="gsrs-setting-input-group" style="flex-direction: column; align-items: flex-start;"> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-dark-mode-checkbox"><label for="gsrs-dark-mode-checkbox" style="font-weight:normal; cursor:pointer;">Enable Dark Mode</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-show-preview-list-mode-checkbox"><label for="gsrs-show-preview-list-mode-checkbox" style="font-weight:normal; cursor:pointer;">Show preview in List mode</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-show-filter-area-checkbox"><label for="gsrs-show-filter-area-checkbox" style="font-weight:normal; cursor:pointer;">Show filter area</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-show-download-area-checkbox"><label for="gsrs-show-download-area-checkbox" style="font-weight:normal; cursor:pointer;">Show download actions area</label></div> </div> </div> </details> <details> <summary>Highlighting Options</summary> <div> <div class="gsrs-setting-input-group" style="flex-direction: column; align-items: flex-start;"> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-highlight-parsed-checkbox"><label for="gsrs-highlight-parsed-checkbox" style="font-weight:normal; cursor:pointer;">Highlight Scraped Results (temporary, during scrape)</label></div> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-highlight-list-item-on-page-checkbox"><label for="gsrs-highlight-list-item-on-page-checkbox" style="font-weight:normal; cursor:pointer;">Highlight selected list item on page</label></div> </div> </div> </details> <details> <summary>Debug & Output Options</summary> <div> <div class="gsrs-setting-input-group" style="flex-direction: column; align-items: flex-start;"> <div style="margin-bottom: 5px;"><input type="checkbox" id="gsrs-debug-mode-checkbox"><label for="gsrs-debug-mode-checkbox" style="font-weight:normal; cursor:pointer;">Enable Debug Mode Logging</label></div> <div><input type="checkbox" id="gsrs-hide-fields-checkbox"><label for="gsrs-hide-fields-checkbox" style="font-weight:normal; cursor:pointer;">Hide non-fetched fields in output</label></div> </div> </div> </details> <div class="gsrs-button-group" style="margin-top: 15px;"> <button class="gsrs-button" id="gsrs-btn-save-settings">Save Settings</button> <button class="gsrs-button" id="gsrs-btn-reset-settings">Reset to Defaults</button> </div> <p class="gsrs-settings-warning">Extraction uses title selector '<code>${GSRS_App.DEFAULT_SETTINGS.titleSelector}</code>' to find parent blocks.</p> `;
            return settingsPanel;
        },
        populateSettingsDOMElements: function() {
            GSRS_App.uiElements.settingsDOMElements = { titleInput: document.getElementById('gsrs-input-title-selector'), observerInput: document.getElementById('gsrs-input-observer-selector'), debugCheckbox: document.getElementById('gsrs-debug-mode-checkbox'), highlightCheckbox: document.getElementById('gsrs-highlight-parsed-checkbox'), highlightListItemOnPageCheckbox: document.getElementById('gsrs-highlight-list-item-on-page-checkbox'), showPreviewInListModeCheckbox: document.getElementById('gsrs-show-preview-list-mode-checkbox'), showFilterInputAreaCheckbox: document.getElementById('gsrs-show-filter-area-checkbox'), showDownloadActionsAreaCheckbox: document.getElementById('gsrs-show-download-area-checkbox'), darkModeCheckbox: document.getElementById('gsrs-dark-mode-checkbox'), fetchTitleCheckbox: document.getElementById('gsrs-fetch-title-checkbox'), fetchUrlCheckbox: document.getElementById('gsrs-fetch-url-checkbox'), fetchSiteNameCheckbox: document.getElementById('gsrs-fetch-sitename-checkbox'), fetchDescriptionCheckbox: document.getElementById('gsrs-fetch-description-checkbox'), fetchDescriptionKeywordsCheckbox: document.getElementById('gsrs-fetch-description-keywords-checkbox'), fetchDateInfoCheckbox: document.getElementById('gsrs-fetch-dateinfo-checkbox'), fetchBreadcrumbsCheckbox: document.getElementById('gsrs-fetch-breadcrumbs-checkbox'), decodeUrlsCheckbox: document.getElementById('gsrs-decode-urls-checkbox'), hideFieldsCheckbox: document.getElementById('gsrs-hide-fields-checkbox'), saveButton: document.getElementById('gsrs-btn-save-settings'), resetButton: document.getElementById('gsrs-btn-reset-settings'), };
            Object.keys(GSRS_App.DEFAULT_SETTINGS).forEach(key => {
                if (key.startsWith('exportCsvMd')) {
                    const checkboxId = `gsrs-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}-checkbox`;
                    GSRS_App.uiElements.settingsDOMElements[key + 'Checkbox'] = document.getElementById(checkboxId);
                }
            });
        },

        attachSettingsPanelEvents: function() {
            const elements = GSRS_App.uiElements.settingsDOMElements;
            if (elements.saveButton) elements.saveButton.addEventListener('click', () => GSRS_App.settingsManager.save());
            if (elements.resetButton) elements.resetButton.addEventListener('click', () => GSRS_App.settingsManager.resetToDefaults());
            if (elements.darkModeCheckbox) { elements.darkModeCheckbox.addEventListener('change', (event) => { this.toggleDarkMode(event.target.checked); }); }
            const settingsPanel = GSRS_App.uiElements.settingsPanel;
            if(settingsPanel) {
                settingsPanel.querySelectorAll('.gsrs-test-selector-btn').forEach(btn => { btn.addEventListener('click', () => testSelector(btn.dataset.inputId, btn.dataset.resultId, btn.dataset.previewId)); });
                const fetchDescCheckbox = elements.fetchDescriptionCheckbox;
                const fetchKeywordsContainer = settingsPanel.querySelector('#gsrs-fetch-keywords-container');
                const fetchKeywordsCheckbox = elements.fetchDescriptionKeywordsCheckbox;
                if (fetchDescCheckbox && fetchKeywordsContainer && fetchKeywordsCheckbox) {
                    const toggleKeywordsOption = () => { fetchKeywordsContainer.style.display = fetchDescCheckbox.checked ? 'block' : 'none'; if (!fetchDescCheckbox.checked) { fetchKeywordsCheckbox.checked = false; } };
                    fetchDescCheckbox.addEventListener('change', toggleKeywordsOption);
                    toggleKeywordsOption();
                }
                const detailsElements = settingsPanel.querySelectorAll('details');
                detailsElements.forEach(details => {
                    details.addEventListener('toggle', (event) => {
                        if (event.target.open) {
                            detailsElements.forEach(otherDetails => {
                                if (otherDetails !== event.target && otherDetails.open) {
                                    otherDetails.open = false;
                                }
                            });
                        }
                    });
                });
            }
        },
        attachActionLinkEvents: function() {
            const actionButtonBar = document.getElementById('gsrs-action-button-bar'); if (!actionButtonBar) return;
            actionButtonBar.querySelectorAll('.gsrs-action-format-link').forEach(link => {
                link.addEventListener('click', (event) => {
                    if (link.classList.contains('gsrs-action-disabled')) return;
                    const action = event.target.dataset.action; const format = event.target.dataset.format;
                    if (action === 'copy') {
                        if (format === 'json') handleCopyResults();
                        else if (format === 'urls') handleCopyUrls();
                        else if (format === 'md') handleCopyOrDownloadMarkdown('copy');
                    } else if (action === 'download') {
                        if (format === 'json') handleDownloadResults('json');
                        else if (format === 'csv') handleDownloadResults('csv');
                        else if (format === 'urls') handleDownloadUrls();
                        else if (format === 'md') handleCopyOrDownloadMarkdown('download');
                    }
                });
            });
        },
        createContextMenu: function() {
            const contextMenuElement = document.createElement('div'); contextMenuElement.id = 'gsrs-context-menu';
            if (GSRS_App.currentSettings.darkMode) { contextMenuElement.classList.add('gsrs-context-menu-dark'); }
            contextMenuElement.innerHTML = ` <div class="gsrs-context-menu-item" data-action="copy-json">Copy Item JSON</div> <div class="gsrs-context-menu-item" data-action="copy-title">Copy Title</div> <div class="gsrs-context-menu-item" data-action="copy-url">Copy URL</div> <div class="gsrs-context-menu-item" data-action="copy-description">Copy Description</div> <div class="gsrs-context-menu-separator"></div> <div class="gsrs-context-menu-item" data-action="open-url">Open URL in New Tab</div> <div class="gsrs-context-menu-item" data-action="highlight-on-page">Highlight Item on Page</div> `;
            contextMenuElement.querySelectorAll('.gsrs-context-menu-item').forEach(item => { if (item.dataset.action) { item.addEventListener('click', () => handleContextMenuAction(item.dataset.action)); } });
            document.addEventListener('click', (event) => { if (contextMenuElement && contextMenuElement.style.display === 'block') { if (!contextMenuElement.contains(event.target)) { contextMenuElement.style.display = 'none'; GSRS_App.state.currentContextMenuItemData = null; } } });
            contextMenuElement.addEventListener('contextmenu', e => e.preventDefault());
            return contextMenuElement;
        },
        toggleSettingsView: function(show) {
            const uiContainer = GSRS_App.uiElements.uiContainer; const settingsPanel = GSRS_App.uiElements.settingsPanel; if (!uiContainer || !settingsPanel) return;
            const shouldShow = typeof show === 'boolean' ? show : !uiContainer.classList.contains('gsrs-settings-view-active');
            GSRS_App.currentSettings.uiSettingsVisible = shouldShow;
            uiContainer.classList.toggle('gsrs-settings-view-active', shouldShow);
            const titleBarTextEl = document.getElementById('gsrs-title-bar-text');
            if (titleBarTextEl) { titleBarTextEl.textContent = GSRS_App.state.originalTitleBarText; }
            if (shouldShow) {
                const allDetails = settingsPanel.querySelectorAll('details');
                let firstOpenFound = false;
                allDetails.forEach(details => { if (details.hasAttribute('open')) { if (firstOpenFound) { details.removeAttribute('open'); } else { firstOpenFound = true; } } });
                if (!firstOpenFound && allDetails.length > 0 && !Array.from(allDetails).some(d => d.open)) { allDetails[0].open = true;}
            }
            GSRS_App.settingsManager.saveUIPrefs();
        },
        toggleDarkMode: function(enable) {
            if (typeof enable !== 'boolean') { enable = !GSRS_App.currentSettings.darkMode; }
            GSRS_App.currentSettings.darkMode = enable;
            if (GSRS_App.uiElements.uiContainer) { GSRS_App.uiElements.uiContainer.classList.toggle('gsrs-dark-theme', GSRS_App.currentSettings.darkMode); }
            if (GSRS_App.uiElements.contextMenuElement) { GSRS_App.uiElements.contextMenuElement.classList.toggle('gsrs-context-menu-dark', GSRS_App.currentSettings.darkMode); }
            GM_setValue('gsrs_darkMode', GSRS_App.currentSettings.darkMode);
            if (GSRS_App.uiElements.settingsDOMElements.darkModeCheckbox) { GSRS_App.uiElements.settingsDOMElements.darkModeCheckbox.checked = GSRS_App.currentSettings.darkMode; }
        },
        showUIMessage: function(message, type = 'success', duration = 3000, details = null) {
            const uiMessageDiv = GSRS_App.uiElements.uiMessageDiv; if (!uiMessageDiv) return;
            uiMessageDiv.textContent = message; uiMessageDiv.className = ''; uiMessageDiv.classList.add('gsrs-ui-message'); uiMessageDiv.classList.add(`gsrs-${type}`);
            uiMessageDiv.style.display = 'block';
            setTimeout(() => { if (uiMessageDiv) uiMessageDiv.style.display = 'none'; }, duration);
            if (details && (type === 'error' || GSRS_App.currentSettings.debugMode)) { console[type === 'error' ? 'error' : 'log'](`GSRS UI Message (${type}): ${message}`, details); }
        },
        makeDraggable: function(element, handle) { makeDraggable(element, handle); },
        toggleViewMode: function(mode) { toggleViewMode(mode); },
        updateObserverStatus: function(isActive) { // Simplified: always inactive for MutationObserver
            const observerStatusSpan = GSRS_App.uiElements.observerStatusSpan;
            if (observerStatusSpan) {
                observerStatusSpan.innerHTML = '○'; // Circle indicating general inactivity for dynamic loading
                observerStatusSpan.classList.remove('gsrs-obs-active');
                observerStatusSpan.classList.add('gsrs-obs-inactive');
                observerStatusSpan.title = 'Dynamic loading detection inactive';
            }
        },
        updateResultsDisplay: function() { updateResultsDisplay(); },
        populateResultsList: function() { populateResultsList(); },
        addStyles: function() { GM_addStyle(STYLES); }
    };

    // STYLES (Reformatted for readability)
    const STYLES = `
        /* --- GSRS UI Container & Wrapper --- */
        #gsrs-ui-container {
            position: fixed;
            width: ${GSRS_App.DEFAULT_SETTINGS.uiPanelWidth}; /* Injected from default settings */
            min-height: 480px;
            background-color: #f9f9f9;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 99999; /* High z-index to stay on top */
            font-family: Arial, sans-serif;
            font-size: 14px;
            color: #333;
            display: flex;
            flex-direction: column;
            transition: width 0.2s ease-out, height 0.2s ease-out, top 0.2s ease-out, left 0.2s ease-out, right 0.2s ease-out;
        }
        #gsrs-ui-container.gsrs-maximized-panel {
            min-height: unset; /* Allow full screen height */
        }
        #gsrs-ui-container.gsrs-minimized #gsrs-content-wrapper {
            display: none !important;
        }
        #gsrs-ui-container.gsrs-minimized {
            min-height: unset !important;
            height: auto !important; /* Collapse to title bar height */
        }
        #gsrs-content-wrapper {
            display: flex;
            flex-direction: column;
            flex-grow: 1; /* Take available vertical space */
            overflow: hidden; /* Prevent content spill */
            min-height: 0; /* For flex child proper shrinking */
            box-sizing: border-box;
            padding: 10px;
        }

        /* --- Title Bar --- */
        #gsrs-title-bar {
            padding: 8px 10px;
            background-color: #eee;
            border-bottom: 1px solid #ccc;
            cursor: move;
            user-select: none;
            border-top-left-radius: 5px;
            border-top-right-radius: 5px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-shrink: 0; /* Don't shrink title bar */
        }
        #gsrs-settings-toggle-titlebar {
            background: none;
            border: none;
            font-size: 18px;
            cursor: pointer;
            padding: 0 8px 0 0;
            color: #333;
            margin-right: auto; /* Pushes other controls to the right */
            flex-shrink: 0;
        }
        #gsrs-title-bar-text {
            text-align: center;
            flex-grow: 1;
            font-weight: bold;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
        }
        #gsrs-title-bar-controls {
            display: flex;
            align-items: center;
            flex-shrink: 0;
        }
        #gsrs-ui-container.gsrs-minimized #gsrs-settings-toggle-titlebar {
            margin-right: 5px !important; /* Adjust spacing when minimized */
        }
        #gsrs-ui-container.gsrs-minimized #gsrs-title-bar-text {
            display: block !important; /* Ensure it's visible */
            flex-grow: 1 !important;
            text-align: center !important;
            margin-left: 5px;
            margin-right: 5px;
        }
        #gsrs-observer-status {
            font-size: 18px;
            margin-right: 10px;
        }
        #gsrs-observer-status.gsrs-obs-active { color: green; } /* Kept for potential future use */
        #gsrs-observer-status.gsrs-obs-inactive { color: #FF8C00; } /* Orange for inactive/URL mode */
        #gsrs-maximize-btn,
        #gsrs-minimize-btn {
            cursor: pointer;
            background: none;
            border: none;
            font-size: 16px;
            padding: 0 5px;
            color: #333;
            margin-left: 5px;
        }

        /* --- Main Content Areas (Actions, Filter, Results Display etc.) --- */
        .gsrs-main-actions,
        .gsrs-filter-container,
        #gsrs-results-count-wrapper,
        .gsrs-copy-download-options,
        .gsrs-action-button-bar,
        #gsrs-ui-message,
        #gsrs-results-view-area {
            padding-left: 0; /* Reset any inherited padding */
            padding-right: 0;
        }
        .gsrs-main-actions {
            margin-bottom: 10px;
            display: flex;
            gap: 5px;
        }
        .gsrs-filter-container {
            margin-bottom: 5px;
            display: flex;
            flex-shrink: 0; /* Prevent filter area from shrinking too much */
        }
        #gsrs-results-count-wrapper {
            margin-bottom: 5px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-shrink: 0;
        }
        .gsrs-copy-download-options {
            margin-bottom: 5px;
            padding-top: 5px;
            padding-bottom: 5px;
            background-color: #f0f0f0;
            border-radius:3px;
            flex-shrink: 0;
            text-align: center;
            font-size: 12px;
        }
        .gsrs-action-button-bar {
            margin-top: 8px;
            display: flex;
            gap: 15px; /* Space between copy/download sets */
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
            flex-wrap: wrap; /* Allow wrapping if panel is too narrow */
            font-size: 13px;
        }
        #gsrs-ui-message {
            margin-top: 5px;
            font-size: 12px;
            padding-top: 5px;
            padding-bottom: 5px;
            border-radius: 3px;
            text-align: center;
            display: none; /* Shown by JS */
            flex-shrink: 0;
        }
        #gsrs-results-view-area {
            display: flex;
            flex-direction: column; /* Stack list and preview vertically */
            flex-grow: 1;
            min-height: 0; /* For flex child proper shrinking */
            overflow: hidden; /* Child elements will handle their own scroll */
            margin-bottom: 5px;
        }

        /* --- Buttons & Inputs --- */
        .gsrs-button {
            padding: 8px 12px;
            margin-right: 5px; /* Default spacing, overridden where needed */
            margin-bottom: 5px; /* Default spacing */
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 13px;
            text-align: center;
            display: inline-flex; /* For icon alignment */
            align-items: center;
            justify-content: center;
            line-height: 1; /* Consistent line height */
        }
        .gsrs-button:hover { opacity: 0.9; }
        #gsrs-start-btn {
            background-color: #4CAF50; /* Green */
            flex-grow: 1; /* Take available space in main-actions */
            margin-right: 0;
            margin-bottom: 0;
        }
        #gsrs-clear-btn {
            background-color: #f44336; /* Red */
            margin-right: 0;
            margin-bottom: 0;
            flex-shrink: 0;
        }
        #gsrs-filter-input {
            flex-grow: 1;
            padding: 6px 8px;
            border: 1px solid #ccc;
            border-radius: 3px;
            font-size: 13px;
            margin-right: 5px; /* Space before clear button */
            height: 34px; /* Match button height */
            box-sizing: border-box;
        }
        #gsrs-filter-input.gsrs-filter-active {
            border-color: #FF9800; /* Orange highlight */
            box-shadow: 0 0 3px #FF980080;
        }
        #gsrs-clear-filter-btn {
            padding: 0 10px; /* Horizontal padding only */
            font-size: 16px;
            background-color: #9E9E9E; /* Grey */
            color: white;
            min-width: auto; /* Allow natural width */
            flex-shrink: 0;
            height: 34px; /* Match input height */
            box-sizing: border-box;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            line-height: 1;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
        .gsrs-copy-download-options label {
            margin-right: 15px;
            cursor: pointer;
        }
        .gsrs-copy-download-options input[type="radio"] {
            margin-right: 4px;
            vertical-align: middle;
            cursor: pointer;
        }

        /* --- Results Display (JSON, List, Preview) --- */
        #gsrs-results-area { /* JSON View Textarea */
            width: 100%;
            box-sizing: border-box;
            flex-grow: 1;
            border: 1px solid #ddd;
            border-radius: 3px;
            padding: 5px;
            font-family: monospace;
            font-size: 12px;
            resize: none; /* Or 'vertical' if preferred */
        }
        #gsrs-results-count {
            font-size: 12px;
            color: #555;
        }
        .gsrs-view-toggle-buttons button {
            font-size: 11px;
            padding: 3px 6px;
            background-color: #e0e0e0;
            color: #333;
            border: 1px solid #ccc;
            margin-left: 5px;
        }
        .gsrs-view-toggle-buttons button.active {
            background-color: #c0c0c0;
            font-weight: bold;
        }
        #gsrs-results-list-container {
            display: none; /* Toggled by JS */
            flex-direction: column;
            width: 100%;
            flex-grow: 3; /* Takes more space than preview */
            flex-shrink: 1;
            flex-basis: 150px; /* Initial basis */
            min-height: 120px; /* Minimum scrollable area */
            max-height: 100%; /* Don't exceed parent */
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 3px;
            padding: 5px;
            margin-bottom:5px; /* Space between list and preview */
            background-color: #fff;
            box-sizing: border-box;
        }
        #gsrs-results-list-container .gsrs-list-item {
            padding: 3px 5px;
            cursor: pointer;
            border-bottom: 1px dotted #eee;
            font-size: 12px;
            line-height: 1.4;
            overflow-wrap: break-word;
            user-select: none;
        }
        #gsrs-results-list-container .gsrs-list-item:last-child {
            border-bottom: none;
        }
        #gsrs-results-list-container .gsrs-list-item:hover {
            background-color: #f0f0f0;
        }
        #gsrs-results-list-container .gsrs-list-item.selected {
            background-color: #d0e0ff; /* Light blue selection */
            font-weight: bold;
        }
        #gsrs-result-preview-area {
            display: none; /* Toggled by JS */
            width: 100%;
            flex-grow: 1; /* Takes less space than list */
            flex-shrink: 1;
            flex-basis: 80px; /* Initial basis */
            min-height: 80px; /* Minimum scrollable area */
            max-height: 30%; /* Limit height relative to parent */
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 3px;
            padding: 5px;
            background-color: #fff;
            font-size: 12px;
            box-sizing: border-box;
        }
        #gsrs-result-preview-area p {
            margin: 0 0 5px 0;
            word-break: break-all;
        }
        #gsrs-result-preview-area strong { font-weight: bold; }
        #gsrs-result-preview-area a { color: #1a0dab; text-decoration: none; }
        #gsrs-result-preview-area a:hover { text-decoration: underline; }

        /* --- UI Messages --- */
        #gsrs-ui-message.gsrs-success {
            background-color: #d4edda; /* Bootstrap success green */
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        #gsrs-ui-message.gsrs-error {
            background-color: #f8d7da; /* Bootstrap error red */
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        /* --- Settings Panel --- */
        #gsrs-settings-panel {
            display: none; /* Shown when .gsrs-settings-view-active on container */
            flex-direction: column;
            overflow-y: auto;
            padding: 10px;
            background-color: #fff;
            box-sizing: border-box;
        }
        #gsrs-ui-container.gsrs-settings-view-active #gsrs-content-wrapper > *:not(#gsrs-settings-panel) {
            display: none !important;
        }
        #gsrs-ui-container.gsrs-settings-view-active #gsrs-settings-panel {
            display: flex !important;
            flex-grow: 1;
            min-height: 0;
        }
        #gsrs-settings-panel details {
            border: 1px solid #eee;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        #gsrs-settings-panel summary {
            padding: 8px;
            background-color: #f7f7f7;
            cursor: pointer;
            font-weight: bold;
            list-style-position: inside;
            border-bottom: 1px solid #eee;
        }
        #gsrs-settings-panel details[open] summary {
            border-bottom: 1px solid #eee;
        }
        #gsrs-settings-panel details > div {
            padding: 10px;
        }
        .gsrs-export-options-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
            gap: 5px;
        }
        .gsrs-setting-item { margin-bottom: 8px; }
        .gsrs-setting-item.gsrs-setting-group-divider {
            margin-top: 15px;
            margin-bottom: 10px;
            border-top: 1px dashed #ccc;
            padding-top: 10px;
        }
        .gsrs-setting-item label { /* General labels in settings */
            display: block;
            margin-bottom: 4px;
            font-weight: bold;
            font-size: 12px;
        }
        .gsrs-setting-input-group {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
        }
        .gsrs-setting-item input[type="text"] {
            flex-grow: 1;
            min-width: 150px;
            padding: 4px;
            border: 1px solid #ccc;
            border-radius: 3px;
            font-size: 12px;
            margin-right: 5px;
        }
        .gsrs-setting-item input[type="checkbox"] {
            margin-right: 5px;
            vertical-align: middle;
        }
        #gsrs-btn-save-settings { background-color: #007bff; /* Blue */ }
        #gsrs-btn-reset-settings { background-color: #dc3545; /* Red */ }
        .gsrs-settings-warning {
            font-size: 11px;
            color: #777;
            margin-top: 10px;
        }
        .gsrs-test-selector-btn {
            background-color: #607D8B !important; /* Blue Grey */
            font-size: 11px !important;
            padding: 4px 8px !important;
            margin-left: 5px;
            flex-shrink: 0;
            color: white !important;
        }
        .gsrs-test-result-span {
            font-size: 11px;
            margin-left: 8px;
            color: #3F51B5; /* Indigo */
            white-space: nowrap;
        }
        .gsrs-selector-test-preview {
            max-height: 150px;
            overflow-y: auto;
            background-color: #f0f0f0;
            border: 1px solid #ccc;
            padding: 5px;
            margin-top: 5px;
            font-family: monospace;
            font-size: 11px;
            white-space: pre-wrap;
            word-break: break-all;
            display: none;
        }
        .gsrs-selector-test-preview strong {
            font-weight: bold;
            display: block;
            margin-bottom: 3px;
            font-size: 10px;
            color: #555;
        }
        .gsrs-dark-theme .gsrs-selector-test-preview div {
            background-color: #2c2c2c !important;
            color: #e0e0e0 !important;
            border: 1px solid #4a4a4a !important;
        }

        /* --- Action Links (Copy/Download) --- */
        .gsrs-action-set {
            display: flex;
            align-items: center;
            gap: 4px;
        }
        .gsrs-action-icon {
            font-size: 1.1em;
            margin-right: 2px;
        }
        .gsrs-action-format-link {
            color: #007bff;
            text-decoration: none;
            cursor: pointer;
        }
        .gsrs-action-format-link:hover {
            text-decoration: underline;
            color: #0056b3;
        }
        .gsrs-action-format-link.gsrs-action-disabled {
            color: #bbbbbb !important;
            cursor: default;
            pointer-events: none;
            text-decoration: none !important;
        }

        /* --- Highlighting Styles --- */
        .gsrs-highlighted {
            outline: 2px dashed #FF9800 !important; /* Orange */
            box-shadow: 0 0 10px #FF980080 !important;
            transition: outline 0.5s ease-out, box-shadow 0.5s ease-out;
        }
        .gsrs-selector-test-highlight {
            outline: 2px dashed #4CAF50 !important; /* Green */
            background-color: rgba(76, 175, 80, 0.1) !important;
            box-shadow: 0 0 8px rgba(76, 175, 80, 0.5) !important;
            transition: outline 0.3s ease-out, background-color 0.3s ease-out, box-shadow 0.3s ease-out;
        }
        .gsrs-context-highlight {
            outline: 3px solid #2196F3 !important; /* Blue */
            background-color: rgba(33, 150, 243, 0.15) !important;
            box-shadow: 0 0 10px rgba(33, 150, 243, 0.6) !important;
            transition: outline 0.4s ease-in-out, background-color 0.4s ease-in-out, box-shadow 0.4s ease-in-out;
        }
        .gsrs-list-item-page-highlight {
            outline: 3px solid #00BCD4 !important; /* Cyan */
            background-color: rgba(0, 188, 212, 0.15) !important;
            box-shadow: 0 0 10px rgba(0, 188, 212, 0.6) !important;
            transition: outline 0.4s ease-in-out, background-color 0.4s ease-in-out, box-shadow 0.4s ease-in-out;
        }

        /* --- Context Menu --- */
        #gsrs-context-menu {
            position: fixed;
            display: none;
            background-color: #ffffff;
            border: 1px solid #cccccc;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            border-radius: 4px;
            padding: 5px 0;
            z-index: 100000;
            font-size: 13px;
            min-width: 180px;
            color: #333333;
        }
        .gsrs-context-menu-item {
            padding: 7px 15px;
            cursor: pointer;
            user-select: none;
            color: #333333;
        }
        .gsrs-context-menu-item:hover {
            background-color: #f0f0f0;
            color: #111111;
        }
        .gsrs-context-menu-item.gsrs-cm-disabled {
            color: #aaaaaa !important;
            cursor: default !important;
            background-color: transparent !important;
        }
        .gsrs-context-menu-separator {
            height: 1px;
            background-color: #e0e0e0;
            margin: 4px 0;
        }

        /* --- Dark Theme --- */
        #gsrs-ui-container.gsrs-dark-theme {
            background-color: #2e2e2e;
            color: #e0e0e0;
            border: 1px solid #4a4a4a;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-title-bar {
            background-color: #202124;
            border-bottom: 1px solid #4a4a4a;
            color: #e0e0e0;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-settings-toggle-titlebar,
        #gsrs-ui-container.gsrs-dark-theme #gsrs-maximize-btn,
        #gsrs-ui-container.gsrs-dark-theme #gsrs-minimize-btn,
        #gsrs-ui-container.gsrs-dark-theme #gsrs-observer-status {
            color: #e0e0e0;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-observer-status.gsrs-obs-active { color: #6fbf73; /* Lighter green for dark bg */ }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-observer-status.gsrs-obs-inactive { color: #FFB74D; /* Lighter Orange */ }
        #gsrs-ui-container.gsrs-dark-theme input[type="text"],
        #gsrs-ui-container.gsrs-dark-theme textarea {
            background-color: #3c4043;
            color: #e0e0e0;
            border: 1px solid #5f6368;
        }
        #gsrs-ui-container.gsrs-dark-theme input[type="text"]::placeholder,
        #gsrs-ui-container.gsrs-dark-theme textarea::placeholder {
            color: #9e9e9e;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-filter-input.gsrs-filter-active {
            border-color: #fdd835; /* Yellow for dark theme */
            box-shadow: 0 0 3px #fdd83580;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-start-btn { background-color: #388e3c; /* Darker Green */ }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-clear-btn { background-color: #d32f2f; /* Darker Red */ }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-clear-filter-btn { background-color: #4a4a4a !important; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-test-selector-btn { background-color: #455A64 !important; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-copy-download-options {
            background-color: #3c4043;
            border: 1px solid #4a4a4a;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-results-count { color: #bdbdbd; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-view-toggle-buttons button {
            background-color: #424242;
            color: #e0e0e0;
            border: 1px solid #5f6368;
        }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-view-toggle-buttons button.active { background-color: #535353; }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-results-area,
        #gsrs-ui-container.gsrs-dark-theme #gsrs-results-list-container,
        #gsrs-ui-container.gsrs-dark-theme #gsrs-result-preview-area {
            background-color: #202124; /* Very dark grey, like Google's dark theme */
            border: 1px solid #4a4a4a;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-settings-panel { background-color: #2e2e2e; }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-settings-panel details { border: 1px solid #4a4a4a; }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-settings-panel summary {
            background-color: #3c4043;
            border-bottom: 1px solid #4a4a4a;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-settings-panel details[open] summary { border-bottom: 1px solid #4a4a4a; }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-results-list-container .gsrs-list-item {
            border-bottom: 1px dotted #4a4a4a;
            color: #e0e0e0;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-results-list-container .gsrs-list-item:hover { background-color: #3c4043; }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-results-list-container .gsrs-list-item.selected {
            background-color: #334e7c; /* Darker blue for selection */
            font-weight: bold;
        }
        #gsrs-ui-container.gsrs-dark-theme #gsrs-result-preview-area a { color: #8ab4f8; /* Google's dark theme link color */ }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-action-format-link { color: #8ab4f8; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-action-format-link:hover { color: #aecbfa; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-action-format-link.gsrs-action-disabled { color: #777777 !important; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-setting-item label { color: #e0e0e0; }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-setting-input-group input[type="checkbox"] + label {
            color: #e0e0e0 !important;
            font-weight: normal !important;
        }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-settings-warning { color: #bdbdbd; }
        .gsrs-dark-theme .gsrs-selector-test-preview {
            background-color: #3c4043;
            border: 1px solid #5f6368;
        }
        .gsrs-dark-theme .gsrs-selector-test-preview div {
            background-color: #2c2c2c !important;
            color: #e0e0e0 !important;
            border: 1px solid #4a4a4a !important;
        }
        .gsrs-dark-theme .gsrs-selector-test-preview strong { color: #bdbdbd; }
        .gsrs-dark-theme .gsrs-test-result-span { color: #81d4fa; /* Light blue */ }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-ui-message.gsrs-success {
            background-color: #2E7D32; color: #C8E6C9; border: 1px solid #388E3C;
        }
        #gsrs-ui-container.gsrs-dark-theme .gsrs-ui-message.gsrs-error {
            background-color: #C62828; color: #FFCDD2; border: 1px solid #D32F2F;
        }
        /* Dark theme scrollbar */
        #gsrs-ui-container.gsrs-dark-theme ::-webkit-scrollbar { width: 10px; height: 10px; }
        #gsrs-ui-container.gsrs-dark-theme ::-webkit-scrollbar-track { background: #2e2e2e; }
        #gsrs-ui-container.gsrs-dark-theme ::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; border: 2px solid #2e2e2e; }
        #gsrs-ui-container.gsrs-dark-theme ::-webkit-scrollbar-thumb:hover { background: #666; }
        #gsrs-ui-container.gsrs-dark-theme select {
            background-color: #3c4043;
            color: #e0e0e0;
            border: 1px solid #5f6368;
        }
        /* Dark theme context menu */
        #gsrs-context-menu.gsrs-context-menu-dark {
            background-color: #383838;
            border-color: #585858;
            color: #e0e0e0;
        }
        #gsrs-context-menu.gsrs-context-menu-dark .gsrs-context-menu-item { color: #e0e0e0; }
        #gsrs-context-menu.gsrs-context-menu-dark .gsrs-context-menu-item:hover { background-color: #4f4f4f; color: #ffffff; }
        #gsrs-context-menu.gsrs-context-menu-dark .gsrs-context-menu-item.gsrs-cm-disabled { color: #777777 !important; }
        #gsrs-context-menu.gsrs-context-menu-dark .gsrs-context-menu-separator { background-color: #5f6368; }
    `;

    // Punycode.js
    const punycode = (() => {
        const maxInt = 2147483647; const base = 36; const tMin = 1; const tMax = 26; const skew = 38; const damp = 700; const initialBias = 72; const initialN = 128; const delimiter = '-'; const errors = { 'overflow': 'Overflow: input needs wider integers to process', 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 'invalid-input': 'Invalid input' }; const baseMinusTMin = base - tMin; function error(type) { throw new RangeError(errors[type]); } function basicToDigit(codePoint) { if (codePoint - 0x30 < 0x0A) { return codePoint - 0x16; } if (codePoint - 0x41 < 0x1A) { return codePoint - 0x41; } if (codePoint - 0x61 < 0x1A) { return codePoint - 0x61; } return base; } function adapt(delta, numPoints, firstTime) { let k = 0; delta = firstTime ? Math.floor(delta / damp) : delta >> 1; delta += Math.floor(delta / numPoints); for (; delta > baseMinusTMin * tMax >> 1; k += base) { delta = Math.floor(delta / baseMinusTMin); } return Math.floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); } function decode(input) { const output = []; const inputLength = input.length; let i = 0; let n = initialN; let bias = initialBias; let basic = input.lastIndexOf(delimiter); if (basic < 0) { basic = 0; } for (let j = 0; j < basic; ++j) { if (input.charCodeAt(j) >= 0x80) { error('not-basic'); } output.push(input.charCodeAt(j)); } for (let index = basic > 0 ? basic + 1 : 0; index < inputLength;) { let oldi = i; let w = 1; for (let k = base; ; k += base) { if (index >= inputLength) { error('invalid-input'); } const digit = basicToDigit(input.charCodeAt(index++)); if (digit >= base || digit > Math.floor((maxInt - i) / w)) { error('overflow'); } i += digit * w; const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (digit < t) { break; } const baseMinusT = base - t; if (w > Math.floor(maxInt / baseMinusT)) { error('overflow'); } w *= baseMinusT; } const out = output.length + 1; bias = adapt(i - oldi, out, oldi == 0); if (Math.floor(i / out) > maxInt - n) { error('overflow'); } n += Math.floor(i / out); i %= out; output.splice(i++, 0, n); } return String.fromCodePoint(...output); } return { decode: decode, };
    })();

    // Date Parsing Functions
    function getPageLanguageSimple() { const htmlLang = document.documentElement.lang; if (htmlLang) { const langPart = htmlLang.split('-')[0].toLowerCase(); const regionPart = htmlLang.split('-')[1]?.toLowerCase(); if (langPart === "zh") { if (regionPart === "tw" || regionPart === "hk" || htmlLang.toLowerCase().includes("hant")) return "zh-tw"; return "zh-tw"; } if (langPart === "ja") return "ja"; if (langPart === "en") return "en"; } return "unknown"; }
    const englishMonths = { jan: 0, january: 0, feb: 1, february: 1, mar: 2, march: 2, apr: 3, april: 3, may: 4, jun: 5, june: 5, jul: 6, july: 6, aug: 7, august: 7, sep: 8, sept: 8, september: 8, oct: 9, october: 9, nov: 10, november: 10, dec: 11, december: 11 };
    function parseDateStringPureJS(textDate, refDate = new Date()) { if (!textDate || typeof textDate !== 'string' || textDate.trim() === '') { return null; } const originalTextDate = textDate.trim(); let textForMatching = originalTextDate.toLowerCase(); const debug = GSRS_App.currentSettings && GSRS_App.currentSettings.debugMode; const lang = getPageLanguageSimple(); let year, month, day, hour, minute, second; const tempDate = new Date(refDate.getTime()); if (debug) console.log(`GSRS (PureJSParse Attempt): "${originalTextDate}", Lang: ${lang}, RefDate: ${refDate.toISOString()}`); if (lang === 'en' || lang === 'unknown') { let match = textForMatching.match(/^(\d+)\s+(hour|minute|second|day|week|month|year)s?\s+ago$/); if (match) { const value = parseInt(match[1]); const unit = match[2]; if (unit === 'hour') tempDate.setHours(tempDate.getHours() - value); else if (unit === 'minute') tempDate.setMinutes(tempDate.getMinutes() - value); else if (unit === 'second') tempDate.setSeconds(tempDate.getSeconds() - value); else if (unit === 'day') tempDate.setDate(tempDate.getDate() - value); else if (unit === 'week') tempDate.setDate(tempDate.getDate() - (value * 7)); else if (unit === 'month') tempDate.setMonth(tempDate.getMonth() - value); else if (unit === 'year') tempDate.setFullYear(tempDate.getFullYear() - value); if (debug) console.log(`GSRS (PureJSParse EN Rel): "${originalTextDate}" -> ${tempDate.toISOString()}`); return tempDate; } if (textForMatching === "yesterday") { tempDate.setDate(tempDate.getDate() - 1); tempDate.setHours(0, 0, 0, 0); if (debug) console.log(`GSRS (PureJSParse EN Rel): "yesterday" -> ${tempDate.toISOString()}`); return tempDate; } if (textForMatching === "just now" || textForMatching === "now") { if (debug) console.log(`GSRS (PureJSParse EN Rel): "just now/now" -> ${refDate.toISOString()}`); return new Date(refDate.getTime()); } } if (lang === 'ja') { let match = originalTextDate.match(/^(\d+)\s*時間前$/); if (match) { tempDate.setHours(tempDate.getHours() - parseInt(match[1])); if (debug) console.log(`GSRS (PureJSParse JA Rel): hour -> ${tempDate.toISOString()}`); return tempDate; } match = originalTextDate.match(/^(\d+)\s*分前$/); if (match) { tempDate.setMinutes(tempDate.getMinutes() - parseInt(match[1])); if (debug) console.log(`GSRS (PureJSParse JA Rel): minute -> ${tempDate.toISOString()}`); return tempDate; } match = originalTextDate.match(/^(\d+)\s*日前$/); if (match) { tempDate.setDate(tempDate.getDate() - parseInt(match[1])); if (debug) console.log(`GSRS (PureJSParse JA Rel): day -> ${tempDate.toISOString()}`); return tempDate; } if (originalTextDate === "昨日") { tempDate.setDate(tempDate.getDate() - 1); tempDate.setHours(0, 0, 0, 0); if (debug) console.log(`GSRS (PureJSParse JA Rel): "昨日" -> ${tempDate.toISOString()}`); return tempDate; } if (originalTextDate === "たった今" || originalTextDate === "今さっき") { if (debug) console.log(`GSRS (PureJSParse JA Rel): "たった今/今さっき" -> ${refDate.toISOString()}`); return new Date(refDate.getTime()); } } if (lang === 'zh-tw') { let match = originalTextDate.match(/^(\d+)\s*小時前$/); if (match) { tempDate.setHours(tempDate.getHours() - parseInt(match[1])); if (debug) console.log(`GSRS (PureJSParse ZH-TW Rel): hour -> ${tempDate.toISOString()}`); return tempDate; } match = originalTextDate.match(/^(\d+)\s*分鐘前$/); if (match) { tempDate.setMinutes(tempDate.getMinutes() - parseInt(match[1])); if (debug) console.log(`GSRS (PureJSParse ZH-TW Rel): minute -> ${tempDate.toISOString()}`); return tempDate; } match = originalTextDate.match(/^(\d+)\s*天前$/); if (match) { tempDate.setDate(tempDate.getDate() - parseInt(match[1])); if (debug) console.log(`GSRS (PureJSParse ZH-TW Rel): day -> ${tempDate.toISOString()}`); return tempDate; } if (originalTextDate === "昨天") { tempDate.setDate(tempDate.getDate() - 1); tempDate.setHours(0, 0, 0, 0); if (debug) console.log(`GSRS (PureJSParse ZH-TW Rel): "昨天" -> ${tempDate.toISOString()}`); return tempDate; } if (originalTextDate === "剛剛" || originalTextDate === "剛") { if (debug) console.log(`GSRS (PureJSParse ZH-TW Rel): "剛剛/剛" -> ${refDate.toISOString()}`); return new Date(refDate.getTime());} } let parsed, isValid, hasTimePartInString; let engAbsMatch = originalTextDate.match(/^([a-z.]{3,9})\s+(\d{1,2})(?:st|nd|rd|th)?(?:,\s*|\s+)(\d{4})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)?)?$/i); if (engAbsMatch) { const monthName = engAbsMatch[1].toLowerCase().replace('.', ''); month = englishMonths[monthName]; day = parseInt(engAbsMatch[2]); year = parseInt(engAbsMatch[3]); hasTimePartInString = false; if (typeof month === 'number' && month >= 0 && month <= 11) { hour = 0; minute = 0; second = 0; if (engAbsMatch[4] && engAbsMatch[5]) { hasTimePartInString = true; hour = parseInt(engAbsMatch[4]); minute = parseInt(engAbsMatch[5]); second = engAbsMatch[6] ? parseInt(engAbsMatch[6]) : 0; const ampm = engAbsMatch[7] ? engAbsMatch[7].toLowerCase() : null; if (ampm === 'pm' && hour < 12) hour += 12; if (ampm === 'am' && hour === 12) hour = 0; } parsed = hasTimePartInString ? new Date(year, month, day, hour, minute, second) : new Date(Date.UTC(year, month, day)); isValid = hasTimePartInString ? (parsed.getFullYear() === year && parsed.getMonth() === month && parsed.getDate() === day && parsed.getHours() === hour && parsed.getMinutes() === minute) : (parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month && parsed.getUTCDate() === day); if (isValid) { if (debug) console.log(`GSRS (PureJSParse EN Abs MonthDDYYYY): "${originalTextDate}" -> ${parsed.toISOString()} (UTC: ${!hasTimePartInString})`); return parsed; } else if (debug) { console.log(`GSRS (PureJSParse EN Abs MonthDDYYYY): Invalid date components for "${originalTextDate}" -> Y:${year},M:${month},D:${day}, H:${hour},m:${minute} (UTC: ${!hasTimePartInString})`); } } } let cjkDateParts = originalTextDate.match(/(\d{4})年\s*(\d{1,2})月\s*(\d{1,2})日?(?:\s*(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/); if (cjkDateParts) { year = parseInt(cjkDateParts[1]); month = parseInt(cjkDateParts[2]) - 1; day = parseInt(cjkDateParts[3]); hasTimePartInString = !!(cjkDateParts[4] && cjkDateParts[5]); hour = hasTimePartInString ? parseInt(cjkDateParts[4]) : 0; minute = hasTimePartInString ? parseInt(cjkDateParts[5]) : 0; second = (hasTimePartInString && cjkDateParts[6]) ? parseInt(cjkDateParts[6]) : 0; parsed = hasTimePartInString ? new Date(year, month, day, hour, minute, second) : new Date(Date.UTC(year, month, day)); isValid = hasTimePartInString ? (parsed.getFullYear() === year && parsed.getMonth() === month && parsed.getDate() === day) : (parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month && parsed.getUTCDate() === day); if (isValid) { if (debug) console.log(`GSRS (PureJSParse CJK Abs YMD): "${originalTextDate}" -> ${parsed.toISOString()} (UTC: ${!hasTimePartInString})`); return parsed; } } cjkDateParts = !parsed ? originalTextDate.match(/(\d{1,2})月\s*(\d{1,2})日?/) : null; if (cjkDateParts) { month = parseInt(cjkDateParts[1]) - 1; day = parseInt(cjkDateParts[2]); hasTimePartInString = false; year = refDate.getFullYear(); const tempProspectiveDateThisYear = new Date(Date.UTC(year, month, day)); if (tempProspectiveDateThisYear.getTime() > refDate.getTime() && (month > refDate.getUTCMonth() || (month === refDate.getUTCMonth() && day > refDate.getUTCDate()))) { year--; } parsed = new Date(Date.UTC(year, month, day)); isValid = (parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month && parsed.getUTCDate() === day); if (isValid) { if (debug) console.log(`GSRS (PureJSParse CJK Abs MD): "${originalTextDate}" -> ${parsed.toISOString()} (UTC: true)`); return parsed; } } let isoDateParts = !parsed ? originalTextDate.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/) : null; if (isoDateParts) { year = parseInt(isoDateParts[1]); month = parseInt(isoDateParts[2]) - 1; day = parseInt(isoDateParts[3]); hasTimePartInString = !!(isoDateParts[4] && isoDateParts[5]); hour = hasTimePartInString ? parseInt(isoDateParts[4]) : 0; minute = hasTimePartInString ? parseInt(isoDateParts[5]) : 0; second = (hasTimePartInString && isoDateParts[6]) ? parseInt(isoDateParts[6]) : 0; parsed = hasTimePartInString ? new Date(year, month, day, hour, minute, second) : new Date(Date.UTC(year, month, day)); isValid = hasTimePartInString ? (parsed.getFullYear() === year && parsed.getMonth() === month && parsed.getDate() === day) : (parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month && parsed.getUTCDate() === day); if (isValid) { if (debug) console.log(`GSRS (PureJSParse ISO-like): "${originalTextDate}" -> ${parsed.toISOString()} (UTC: ${!hasTimePartInString})`); return parsed; } } let mdDateParts = !parsed ? originalTextDate.match(/^(\d{1,2})[./](\d{1,2})[./](\d{4})$/) : null; if (mdDateParts) { hasTimePartInString = false; let mAttempt, dAttempt; if (lang === 'en' || lang === 'unknown') { mAttempt = parseInt(mdDateParts[1]) - 1; dAttempt = parseInt(mdDateParts[2]); } else { dAttempt = parseInt(mdDateParts[1]); mAttempt = parseInt(mdDateParts[2]) - 1; } year = parseInt(mdDateParts[3]); parsed = new Date(Date.UTC(year, mAttempt, dAttempt)); isValid = (parsed.getUTCFullYear() === year && parsed.getUTCMonth() === mAttempt && parsed.getUTCDate() === dAttempt); if (isValid) { if (debug) console.log(`GSRS (PureJSParse M/D/YYYY or D.M.YYYY like): "${originalTextDate}" -> ${parsed.toISOString()} (UTC: true)`); return parsed; } } if (debug) console.log(`GSRS (PureJSParse): Failed to parse "${originalTextDate}" with all PureJS rules.`); return null; }

    // --- Utility Functions ---
    function isValidSelector(selector) { if (!selector || typeof selector !== 'string' || selector.trim() === '') return false; try { document.querySelector(selector); return true; } catch (e) { return false; } }
    function convertToMarkdownList(dataArray) { if (!dataArray || dataArray.length === 0) { return ''; } let mdString = ""; const headersOrder = [ 'position', 'title', 'url', 'siteName', 'breadcrumbs', 'description', 'highlightedSnippets', 'originalDateText', 'parsedDateISO' ]; dataArray.forEach(item => { let itemMd = ""; let hasContent = false; headersOrder.forEach(key => { const settingKey = `exportCsvMd${key.charAt(0).toUpperCase() + key.slice(1)}`; if (item.hasOwnProperty(key) && GSRS_App.currentSettings[settingKey]) { if (!hasContent && item.position) { itemMd += `- **Position:** ${item.position || 'N/A'}\n`; hasContent = true; } else if (key !== 'position') { let value = item[key] === null || typeof item[key] === 'undefined' ? 'N/A' : item[key]; if (key === 'url' && item[key] && item[key] !== 'N/A') { value = `[${item[key].replace(/([\[\]])/g, "\\$1")}](${item[key].replace(/\)/g, "%29")})`; } else if (typeof value === 'string') { value = value.replace(/\n/g, ' '); } let displayName = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()); if (key === 'highlightedSnippets') displayName = 'Keywords'; if (key === 'originalDateText') displayName = 'Original Date'; if (key === 'parsedDateISO') displayName = 'Parsed Date'; itemMd += `  - **${displayName}:** ${value}\n`; hasContent = true; } } }); if (hasContent) { mdString += itemMd + "\n"; } }); return mdString.trim(); }
    function convertToCSV(dataArray) { if (!dataArray || dataArray.length === 0) { return ''; } const escapeCSVField = (field) => { if (field === null || typeof field === 'undefined') { return ''; } const stringField = String(field); if (stringField.includes(',') || stringField.includes('"') || stringField.includes('\n') || stringField.includes('\r')) { return `"${stringField.replace(/"/g, '""')}"`; } return stringField; }; const firstItemKeys = Object.keys(dataArray[0]); const headers = firstItemKeys.filter(key => { const settingKey = `exportCsvMd${key.charAt(0).toUpperCase() + key.slice(1)}`; return GSRS_App.currentSettings[settingKey] === true; }); if (headers.length === 0) return ''; const csvRows = []; csvRows.push(headers.map(header => escapeCSVField(header)).join(',')); for (const item of dataArray) { const row = headers.map(header => { return escapeCSVField(item[header]); }); csvRows.push(row.join(',')); } return csvRows.join('\r\n'); }
    function decodeUrlIfEnabled(urlString) { if (!GSRS_App.currentSettings.decodeUrlsToReadable || !urlString || urlString === "Extraction Failed") { return urlString; } let decodedUrl = urlString; try { decodedUrl = decodeURIComponent(urlString.replace(/\+/g, '%20')); } catch (e) { if (GSRS_App.currentSettings.debugMode) console.warn(`GSRS: decodeURIComponent failed for "${urlString}". Using intermediate: "${decodedUrl}". Error: ${e.message}`); } try { const tempUrl = (decodedUrl.startsWith('http://') || decodedUrl.startsWith('https://') || decodedUrl.startsWith('//')) ? decodedUrl : 'http://' + decodedUrl; const urlObj = new URL(tempUrl); let processedHostname = urlObj.hostname; if (processedHostname.includes('xn--')) { const labels = processedHostname.split('.'); const decodedLabels = labels.map(label => { if (label.startsWith('xn--')) { try { return punycode.decode(label.substring(4)); } catch (punyError) { if (GSRS_App.currentSettings.debugMode) console.error(`GSRS: Punycode.js decoding failed for label "${label}" in "${processedHostname}": ${punyError.message}. Original error:`, punyError); return label; } } return label; }); processedHostname = decodedLabels.join('.'); } let path = urlObj.pathname; let search = urlObj.search; let hash = urlObj.hash; try { path = decodeURIComponent(path.replace(/\+/g, '%20')); } catch (e) { /* keep as is */ } try { search = decodeURIComponent(search.replace(/\+/g, '%20')); } catch (e) { /* keep as is */ } try { hash = decodeURIComponent(hash.replace(/\+/g, '%20')); } catch (e) { /* keep as is */ } let finalProtocol = urlObj.protocol; if (urlString.startsWith('//')) { finalProtocol = ''; } let newReconstructedUrl = `${finalProtocol}${finalProtocol ? '//' : ''}${processedHostname}`; if (urlObj.port) newReconstructedUrl += `:${urlObj.port}`; newReconstructedUrl += path + search + hash; decodedUrl = newReconstructedUrl; } catch (e) { if (GSRS_App.currentSettings.debugMode) console.error(`GSRS: Error during URL object-based processing for "${urlString}": ${e.message}. Current decodedUrl: "${decodedUrl}". Original error:`, e); } return decodedUrl; }
    function getOutputDisplayObject(fullDisplayData) { if (!GSRS_App.currentSettings.hideDisabledFetchFields) { const completeObject = { position: fullDisplayData.position, title: GSRS_App.currentSettings.fetchTitle ? fullDisplayData.title : null, url: GSRS_App.currentSettings.fetchUrl ? fullDisplayData.url : null, siteName: GSRS_App.currentSettings.fetchSiteName ? fullDisplayData.siteName : null, breadcrumbs: GSRS_App.currentSettings.fetchBreadcrumbs ? fullDisplayData.breadcrumbs : null, description: GSRS_App.currentSettings.fetchDescription ? fullDisplayData.description : null, highlightedSnippets: (GSRS_App.currentSettings.fetchDescription && GSRS_App.currentSettings.fetchDescriptionKeywords) ? fullDisplayData.highlightedSnippets : null, originalDateText: (GSRS_App.currentSettings.fetchDescription && GSRS_App.currentSettings.fetchDateInfo) ? fullDisplayData.originalDateText : null, parsedDateISO: (GSRS_App.currentSettings.fetchDescription && GSRS_App.currentSettings.fetchDateInfo) ? fullDisplayData.parsedDateISO : null, }; return completeObject; } const output = {}; if (fullDisplayData.hasOwnProperty('position')) output.position = fullDisplayData.position; if (GSRS_App.currentSettings.fetchTitle) output.title = fullDisplayData.title; if (GSRS_App.currentSettings.fetchUrl) output.url = fullDisplayData.url; if (GSRS_App.currentSettings.fetchSiteName) output.siteName = fullDisplayData.siteName; if (GSRS_App.currentSettings.fetchBreadcrumbs) output.breadcrumbs = fullDisplayData.breadcrumbs; if (GSRS_App.currentSettings.fetchDescription) { output.description = fullDisplayData.description; if (GSRS_App.currentSettings.fetchDescriptionKeywords) { output.highlightedSnippets = fullDisplayData.highlightedSnippets; } if (GSRS_App.currentSettings.fetchDateInfo) { output.originalDateText = fullDisplayData.originalDateText; output.parsedDateISO = fullDisplayData.parsedDateISO; } } return output; }


    // --- Enhanced testSelector ---
    function testSelector(inputId, resultSpanId, previewId) {
        const inputElement = document.getElementById(inputId);
        const resultSpan = document.getElementById(resultSpanId);
        const previewDiv = document.getElementById(previewId);

        clearTimeout(GSRS_App.state.selectorTestHighlightTimeout);
        document.querySelectorAll('.gsrs-selector-test-highlight').forEach(el => el.classList.remove('gsrs-selector-test-highlight'));

        if (!inputElement || !resultSpan || !previewDiv) { console.error(`GSRS: Test selector UI elements not found. Input ID: ${inputId}, Result Span ID: ${resultSpanId}, Preview ID: ${previewId}`); if(resultSpan) { resultSpan.textContent = "Error!"; resultSpan.style.color = "red"; } if(previewDiv) { previewDiv.style.display = 'none'; previewDiv.innerHTML = ''; } return; }
        const rawSelectorString = inputElement.value.trim();
        previewDiv.style.display = 'none'; previewDiv.innerHTML = '';

        if (!rawSelectorString) { resultSpan.textContent = "Selector is empty."; resultSpan.style.color = "red"; previewDiv.innerHTML = 'Selector is empty.'; previewDiv.style.display = 'block'; return; }

        const isObserverSelector = inputId === 'gsrs-input-observer-selector';
        const selectorsToTest = isObserverSelector ? rawSelectorString.split(',').map(s => s.trim()).filter(s => s) : [rawSelectorString];
        let totalFound = 0;
        let resultsTextArray = [];
        let allMatchedElementsForPreview = [];

        selectorsToTest.forEach((selector, index) => {
            if (!selector) { if (isObserverSelector) resultsTextArray.push(`Sel ${index+1}: Empty`); return; }
            try {
                const elements = Array.from(document.querySelectorAll(selector));
                const count = elements.length;
                totalFound += count;
                if (isObserverSelector) {
                    resultsTextArray.push(`Sel ${index+1} ("${selector.substring(0,15)}..."): ${count}`);
                } else {
                    resultsTextArray.push(`Found: ${count}`);
                }

                if (count > 0) {
                    allMatchedElementsForPreview.push(...elements);
                    elements.forEach(el => el.classList.add('gsrs-selector-test-highlight'));
                }
            } catch (e) { if (isObserverSelector) { resultsTextArray.push(`Sel ${index+1} ("${selector.substring(0,15)}..."): Invalid`); } else { resultsTextArray = ["Invalid selector syntax!"]; totalFound = -1; previewDiv.innerHTML = ''; const errorStrong = document.createElement('strong'); errorStrong.textContent = 'Error:'; previewDiv.appendChild(errorStrong); previewDiv.appendChild(document.createTextNode(" " + e.message)); previewDiv.style.display = 'block'; console.error(`GSRS Test Selector Error for "${selector}":`, e.message); } }
        });

        resultSpan.textContent = resultsTextArray.join('; ');
        resultSpan.style.color = totalFound > 0 ? "green" : (totalFound === 0 ? "orange" : "red");

        if (allMatchedElementsForPreview.length > 0) {
            if(GSRS_App.currentSettings.debugMode) console.log(`GSRS Test: Selector(s) "${rawSelectorString}" found ${totalFound} total elements. First previewable element:`, allMatchedElementsForPreview[0]);
            previewDiv.innerHTML = '';

            const MAX_PREVIEW_ITEMS = 5;
            const itemsToPreview = allMatchedElementsForPreview.slice(0, MAX_PREVIEW_ITEMS);

            itemsToPreview.forEach((el, idx) => {
                const itemPreviewContainer = document.createElement('div');
                itemPreviewContainer.style.borderBottom = idx < itemsToPreview.length - 1 ? '1px dashed #ccc' : 'none';
                itemPreviewContainer.style.paddingBottom = '5px';
                itemPreviewContainer.style.marginBottom = '5px';

                const itemTitle = document.createElement('strong');
                itemTitle.textContent = `Match ${idx + 1} (of ${totalFound > MAX_PREVIEW_ITEMS ? MAX_PREVIEW_ITEMS + ' shown' : totalFound}): <${el.tagName.toLowerCase()}${el.id ? '#' + el.id : ''}${el.className ? '.' + String(el.className).trim().replace(/\s+/g,'.') : ''}>`;
                itemPreviewContainer.appendChild(itemTitle);

                const innerTextStrong = document.createElement('strong');
                innerTextStrong.textContent = 'InnerText (truncated):';
                innerTextStrong.style.display = 'block'; innerTextStrong.style.marginTop = '3px';
                itemPreviewContainer.appendChild(innerTextStrong);
                const innerTextDiv = document.createElement('div');
                innerTextDiv.style.maxHeight = '40px'; innerTextDiv.style.overflowY = 'auto'; innerTextDiv.style.background = '#e9e9e9'; innerTextDiv.style.padding = '2px';
                const innerTextRaw = el.innerText || "";
                innerTextDiv.textContent = innerTextRaw.length > 150 ? innerTextRaw.substring(0, 150) + "..." : innerTextRaw;
                itemPreviewContainer.appendChild(innerTextDiv);

                const outerHTMLStrong = document.createElement('strong');
                outerHTMLStrong.textContent = 'OuterHTML (truncated):';
                outerHTMLStrong.style.display = 'block'; outerHTMLStrong.style.marginTop = '3px';
                itemPreviewContainer.appendChild(outerHTMLStrong);
                const outerHTMLDiv = document.createElement('div');
                outerHTMLDiv.style.maxHeight = '60px'; outerHTMLDiv.style.overflowY = 'auto'; outerHTMLDiv.style.background = '#e0e0e0'; outerHTMLDiv.style.padding = '2px';
                const outerHTMLRaw = el.outerHTML || "";
                outerHTMLDiv.textContent = outerHTMLRaw.length > 300 ? outerHTMLRaw.substring(0, 300) + "\n... (truncated)" : outerHTMLRaw;
                itemPreviewContainer.appendChild(outerHTMLDiv);
                previewDiv.appendChild(itemPreviewContainer);
            });
             if (totalFound > MAX_PREVIEW_ITEMS) {
                const moreInfo = document.createElement('p');
                moreInfo.textContent = `... and ${totalFound - MAX_PREVIEW_ITEMS} more match(es).`;
                moreInfo.style.fontSize = '10px';
                moreInfo.style.textAlign = 'center';
                previewDiv.appendChild(moreInfo);
            }
            previewDiv.style.display = 'block';
        } else if (totalFound === 0 && !resultsTextArray.some(s => s.includes("Invalid"))) {
            if(GSRS_App.currentSettings.debugMode) console.log(`GSRS Test: Selector(s) "${rawSelectorString}" found 0 elements.`);
            previewDiv.innerHTML = 'No elements matched this/these selector(s).';
            previewDiv.style.display = 'block';
        }

        if (totalFound > 0) {
            GSRS_App.state.selectorTestHighlightTimeout = setTimeout(() => {
                document.querySelectorAll('.gsrs-selector-test-highlight').forEach(el => el.classList.remove('gsrs-selector-test-highlight'));
            }, 3500);
        }
    }

    // --- Global functions needing GSRS_App.state ---
    function makeDraggable(element, handle) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const dragHandle = handle || element;
        let currentTransition = '';

        dragHandle.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            e = e || window.event;
            let target = e.target;
            let isInteractive = false;

            if (handle && handle.contains(target) && target !== handle) {
                if (target.closest('button, input, select, textarea, a')) {
                    if (!(target.id === 'gsrs-settings-toggle-titlebar' || target.id === 'gsrs-minimize-btn' || target.id === 'gsrs-maximize-btn' || target.closest('#gsrs-settings-toggle-titlebar'))) {
                        isInteractive = true;
                    }
                }
            } else if (dragHandle === element) {
                const nonDraggableSelectors = 'input, textarea, button, select, a, .gsrs-selector-test-preview, #gsrs-results-area, #gsrs-results-list-container, #gsrs-result-preview-area, .gsrs-copy-download-options label, .gsrs-view-toggle-buttons button, #gsrs-settings-panel > *:not(#gsrs-title-bar), .gsrs-list-item, .gsrs-action-format-link, #gsrs-context-menu, .gsrs-context-menu-item';
                 if (target.closest(nonDraggableSelectors) && target !== element && !target.closest('#gsrs-title-bar')) {
                    isInteractive = true;
                }
            }

            if (isInteractive) {
                if (GSRS_App.currentSettings.debugMode) console.log("GSRS Drag: Interactive element click, drag NOT initiated on:", target);
                return;
            }
            if (GSRS_App.state.isMaximized) return;

            e.preventDefault();
            currentTransition = element.style.transition || '';
            element.style.transition = 'none';
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;

            let newTop = element.offsetTop - pos2;
            let newLeft = element.offsetLeft - pos1;
            const panelWidth = element.offsetWidth;
            const panelHeight = element.offsetHeight;
            const winWidth = window.innerWidth;
            const winHeight = window.innerHeight;

            if (element.style.left !== 'auto' && GSRS_App.state.originalPanelState.left !== 'auto') {
                newLeft = Math.max(0, Math.min(newLeft, winWidth - panelWidth));
                element.style.left = newLeft + "px";
                element.style.right = 'auto';
            } else {
                let currentRight = parseFloat(getComputedStyle(element).right);
                 if (isNaN(currentRight)) { // If 'auto' or not set, calculate from offsetLeft
                    currentRight = winWidth - (element.offsetLeft + panelWidth);
                 }
                let newRightValue = currentRight + pos1;
                newRightValue = Math.max(0, Math.min(newRightValue, winWidth - panelWidth));
                element.style.right = newRightValue + "px";
                element.style.left = 'auto';
            }

            const actualHandleHeight = (dragHandle.offsetHeight > 0) ? dragHandle.offsetHeight : 20;
            const MIN_VISIBLE_HANDLE_PART = 10;
            if (panelHeight <= winHeight) {
                newTop = Math.max(0, Math.min(newTop, winHeight - panelHeight));
            } else {
                newTop = Math.max(MIN_VISIBLE_HANDLE_PART - actualHandleHeight, Math.min(newTop, winHeight - MIN_VISIBLE_HANDLE_PART));
            }
            element.style.top = newTop + "px";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
            if (element) element.style.transition = currentTransition;
            if (!GSRS_App.state.isMaximized) {
                GSRS_App.settingsManager.saveUIPrefs();
            }
        }
    }
    function toggleMaximizePanel() {
        const uiContainer = GSRS_App.uiElements.uiContainer; if (!uiContainer) return;
        const maximizeBtn = document.getElementById('gsrs-maximize-btn');
        const contentWrapper = document.getElementById('gsrs-content-wrapper');
        const prevTransition = getComputedStyle(uiContainer).transition;
        uiContainer.style.transition = 'none';

        if (GSRS_App.state.isMaximized) {
            uiContainer.style.width = GSRS_App.state.originalPanelState.width || GSRS_App.DEFAULT_SETTINGS.uiPanelWidth;
            uiContainer.style.height = GSRS_App.state.originalPanelState.height || '';
            uiContainer.style.top = GSRS_App.state.originalPanelState.top || GSRS_App.DEFAULT_SETTINGS.uiPanelTop;
            if (GSRS_App.state.originalPanelState.left && GSRS_App.state.originalPanelState.left !== 'auto') {
                uiContainer.style.left = GSRS_App.state.originalPanelState.left;
                uiContainer.style.right = 'auto';
            } else if (GSRS_App.state.originalPanelState.right && GSRS_App.state.originalPanelState.right !== 'auto') {
                uiContainer.style.right = GSRS_App.state.originalPanelState.right;
                uiContainer.style.left = 'auto';
            } else {
                uiContainer.style.left = GSRS_App.DEFAULT_SETTINGS.uiPanelLeft;
                uiContainer.style.right = GSRS_App.DEFAULT_SETTINGS.uiPanelRight;
            }
            uiContainer.classList.remove('gsrs-maximized-panel');
            if (maximizeBtn) { maximizeBtn.innerHTML = '<span class="icon">🗖</span>'; maximizeBtn.title = 'Maximize Panel'; }
            GSRS_App.state.isMaximized = false;
        } else {
            GSRS_App.state.originalPanelState = {
                top: uiContainer.style.top || getComputedStyle(uiContainer).top,
                left: uiContainer.style.left || getComputedStyle(uiContainer).left,
                right: uiContainer.style.right || getComputedStyle(uiContainer).right,
                width: uiContainer.style.width || getComputedStyle(uiContainer).width,
                height: uiContainer.style.height || getComputedStyle(uiContainer).height
            };
            const winWidth = window.innerWidth; const winHeight = window.innerHeight; const padding = 20;
            let newWidth = winWidth - (padding * 2); let newHeight = winHeight - (padding * 2);
            newWidth = Math.max(newWidth, parseInt(GSRS_App.DEFAULT_SETTINGS.uiPanelWidth, 10));
            newHeight = Math.max(newHeight, 480);

            uiContainer.style.width = newWidth + 'px';
            uiContainer.style.height = newHeight + 'px';
            uiContainer.style.top = padding + 'px';
            uiContainer.style.left = padding + 'px';
            uiContainer.style.right = 'auto';
            uiContainer.classList.add('gsrs-maximized-panel');
            if (maximizeBtn) { maximizeBtn.innerHTML = '<span class="icon">🗗</span>'; maximizeBtn.title = 'Restore Panel Size'; }
            GSRS_App.state.isMaximized = true;
        }
        void uiContainer.offsetWidth;
        uiContainer.style.transition = prevTransition;
        if (contentWrapper) {
            const isMinimizedState = uiContainer.classList.contains('gsrs-minimized');
            const currentDisplay = getComputedStyle(contentWrapper).display;
            if (!isMinimizedState && currentDisplay === 'none') {
                contentWrapper.style.display = 'none'; void contentWrapper.offsetHeight; contentWrapper.style.display = 'flex';
            } else if (isMinimizedState) {
                contentWrapper.style.display = 'none';
            }
        }
    }
    function toggleViewMode(mode) {
        if (mode === GSRS_App.state.currentViewMode && GSRS_App.uiElements.uiContainer && !GSRS_App.uiElements.uiContainer.classList.contains('gsrs-view-just-toggled')) {
            if (mode === 'list') GSRS_App.uiManager.populateResultsList();
            return;
        }
        GSRS_App.state.currentViewMode = mode;
        if (GSRS_App.uiElements.uiContainer) GSRS_App.uiElements.uiContainer.classList.add('gsrs-view-just-toggled');

        const jsonViewBtn = document.getElementById('gsrs-view-toggle-json');
        const listViewBtn = document.getElementById('gsrs-view-toggle-list');
        const resultsTextArea = GSRS_App.uiElements.resultsTextArea;
        const resultsListContainer = GSRS_App.uiElements.resultsListContainer;
        const resultPreviewArea = GSRS_App.uiElements.resultPreviewArea;

        if (GSRS_App.state.currentViewMode === 'list') {
            if (resultsTextArea) resultsTextArea.style.display = 'none';
            if (resultsListContainer) resultsListContainer.style.display = 'flex';
            if (resultPreviewArea) { resultPreviewArea.style.display = GSRS_App.currentSettings.showPreviewInListMode ? 'block' : 'none'; }
            if (jsonViewBtn) jsonViewBtn.classList.remove('active');
            if (listViewBtn) listViewBtn.classList.add('active');
            GSRS_App.uiManager.populateResultsList();
            if (resultPreviewArea && (!GSRS_App.state.selectedResultListItem || (resultsListContainer && resultsListContainer.children.length === 0 && !resultsListContainer.textContent.includes("No results")) )) {
                resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 20px;">Click an item from the list to see details.</p>';
            }
        } else { // JSON mode
            clearAllPageHighlights(false);
            if (resultsTextArea) resultsTextArea.style.display = 'block';
            if (resultsListContainer) resultsListContainer.style.display = 'none';
            if (resultPreviewArea) resultPreviewArea.style.display = 'none';
            if (jsonViewBtn) jsonViewBtn.classList.add('active');
            if (listViewBtn) listViewBtn.classList.remove('active');
            GSRS_App.uiManager.updateResultsDisplay();
        }
        GM_setValue('gsrs_lastViewMode', GSRS_App.state.currentViewMode);
        if (GSRS_App.uiElements.uiContainer) setTimeout(() => GSRS_App.uiElements.uiContainer.classList.remove('gsrs-view-just-toggled'), 0);
    }

    // --- Enhanced Context Menu Logic ---
    function handleContextMenuAction(action) {
        if (!GSRS_App.state.currentContextMenuItemData || !action) {
            if(GSRS_App.currentSettings.debugMode) console.warn("GSRS ContextMenu: No item data or action specified for action:", action);
            return;
        }

        const displayData = getOutputDisplayObject(GSRS_App.state.currentContextMenuItemData.display);
        const internalData = GSRS_App.state.currentContextMenuItemData.internal;
        const domElement = GSRS_App.state.currentContextMenuItemData.domElement;

        if(GSRS_App.currentSettings.debugMode) console.log(`GSRS ContextMenu: Action "${action}" on item:`, GSRS_App.state.currentContextMenuItemData);
        clearAllPageHighlights(false);

        switch (action) {
            case 'copy-json': GM_setClipboard(JSON.stringify(displayData, null, 2)); GSRS_App.uiManager.showUIMessage('Item JSON copied!', 'success', 2000); break;
            case 'copy-title': if (displayData.title && displayData.title !== "Extraction Failed" && displayData.title !== "Title Selector Failed") { GM_setClipboard(displayData.title); GSRS_App.uiManager.showUIMessage('Title copied!', 'success', 2000); } else { GSRS_App.uiManager.showUIMessage('No valid title to copy.', 'error', 2000); } break;
            case 'copy-url':
                let urlToCopy = (displayData.url && displayData.url !== "Extraction Failed" && displayData.url !== "URL Extraction Failed") ? displayData.url : null;
                if (!urlToCopy && internalData.rawUrl && internalData.rawUrl !== "Extraction Failed" && internalData.rawUrl !== "URL Extraction Failed") { urlToCopy = decodeUrlIfEnabled(internalData.rawUrl); }
                if (urlToCopy) { GM_setClipboard(urlToCopy); GSRS_App.uiManager.showUIMessage('URL copied!', 'success', 2000); }
                else { GSRS_App.uiManager.showUIMessage('No valid URL to copy.', 'error', 2000); }
                break;
            case 'copy-description':
                if (displayData.description && displayData.description !== "Extraction Failed" && displayData.description !== "Desc Extraction Failed") { GM_setClipboard(displayData.description); GSRS_App.uiManager.showUIMessage('Description copied!', 'success', 2000); }
                else { GSRS_App.uiManager.showUIMessage('No valid description to copy.', 'error', 2000); }
                break;
            case 'open-url':
                if (internalData.rawUrl && internalData.rawUrl !== "Extraction Failed" && internalData.rawUrl !== "URL Extraction Failed" && !internalData.rawUrl.startsWith('javascript:')) {
                    window.open(internalData.rawUrl, '_blank');
                } else {
                    GSRS_App.uiManager.showUIMessage('Invalid or no URL to open.', 'error', 2000);
                }
                break;
            case 'highlight-on-page':
                if (domElement && document.body.contains(domElement)) {
                    clearTimeout(GSRS_App.state.contextMenuHighlightTimeout);
                    if (GSRS_App.state.lastListItemHighlightedElement && GSRS_App.state.lastListItemHighlightedElement !== domElement && document.body.contains(GSRS_App.state.lastListItemHighlightedElement)) {
                        GSRS_App.state.lastListItemHighlightedElement.classList.remove('gsrs-list-item-page-highlight');
                    }
                    domElement.classList.remove('gsrs-highlighted', 'gsrs-context-highlight', 'gsrs-list-item-page-highlight');
                    domElement.classList.add('gsrs-context-highlight');
                    domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    GSRS_App.state.lastListItemHighlightedElement = domElement;
                    GSRS_App.state.contextMenuHighlightTimeout = setTimeout(() => {
                        if (domElement && document.body.contains(domElement)) domElement.classList.remove('gsrs-context-highlight');
                        if (GSRS_App.state.lastListItemHighlightedElement === domElement) GSRS_App.state.lastListItemHighlightedElement = null;
                    }, 3500);
                    GSRS_App.uiManager.showUIMessage('Item highlighted on page.', 'success', 2000);
                } else {
                    GSRS_App.uiManager.showUIMessage('Could not find item on page to highlight.', 'error', 2000);
                     if (GSRS_App.currentSettings.debugMode) console.warn("GSRS ContextMenu: domElement not found or not in body for highlight.", domElement);
                }
                break;
            default:
                if(GSRS_App.currentSettings.debugMode) console.warn(`GSRS ContextMenu: Unknown action "${action}"`);
        }
        if (GSRS_App.uiElements.contextMenuElement) GSRS_App.uiElements.contextMenuElement.style.display = 'none';
    }
    function handleResultListItemContextMenu(event) {
        const contextMenuElement = GSRS_App.uiElements.contextMenuElement;
        if (!contextMenuElement) return;
        event.preventDefault();
        GSRS_App.state.currentContextMenuItemData = null;

        const listItem = event.target.closest('.gsrs-list-item');
        if (!listItem || !listItem.dataset.originalFullArrayIndex) {
            contextMenuElement.style.display = 'none';
            return;
        }

        const originalIndex = parseInt(listItem.dataset.originalFullArrayIndex, 10);
        if (originalIndex >= 0 && originalIndex < GSRS_App.allResults.length) {
            GSRS_App.state.currentContextMenuItemData = GSRS_App.allResults[originalIndex];
        } else {
            contextMenuElement.style.display = 'none';
            return;
        }

        if (!GSRS_App.state.currentContextMenuItemData) {
             contextMenuElement.style.display = 'none';
             return;
        }

        const menuWidth = contextMenuElement.offsetWidth; const menuHeight = contextMenuElement.offsetHeight;
        const windowWidth = window.innerWidth; const windowHeight = window.innerHeight;
        let x = event.clientX; let y = event.clientY;
        if (x + menuWidth > windowWidth) { x = windowWidth - menuWidth - 5; }
        if (y + menuHeight > windowHeight) { y = windowHeight - menuHeight - 5; }
        x = Math.max(0, x); y = Math.max(0, y);

        contextMenuElement.style.left = `${x}px`; contextMenuElement.style.top = `${y}px`;
        contextMenuElement.style.display = 'block';

        const openUrlItem = contextMenuElement.querySelector('[data-action="open-url"]');
        const highlightItem = contextMenuElement.querySelector('[data-action="highlight-on-page"]');
        const copyDescItem = contextMenuElement.querySelector('[data-action="copy-description"]');
        const copyTitleItem = contextMenuElement.querySelector('[data-action="copy-title"]');
        const copyUrlItem = contextMenuElement.querySelector('[data-action="copy-url"]');

        const internalData = GSRS_App.state.currentContextMenuItemData.internal;
        const displayData = GSRS_App.state.currentContextMenuItemData.display;

        if (openUrlItem) {
            const urlIsInvalid = !internalData.rawUrl || internalData.rawUrl === "Extraction Failed" || internalData.rawUrl === "URL Extraction Failed" || internalData.rawUrl.startsWith('javascript:');
            openUrlItem.classList.toggle('gsrs-cm-disabled', urlIsInvalid);
        }
        if (highlightItem) {
            highlightItem.classList.toggle('gsrs-cm-disabled', !GSRS_App.state.currentContextMenuItemData.domElement || !document.body.contains(GSRS_App.state.currentContextMenuItemData.domElement));
        }
        if(copyDescItem){
            const descIsInvalid = !displayData.description || displayData.description === "Extraction Failed" || displayData.description === "Desc Extraction Failed";
            copyDescItem.classList.toggle('gsrs-cm-disabled', descIsInvalid);
        }
        if(copyTitleItem){
            const titleIsInvalid = !displayData.title || displayData.title === "Extraction Failed" || displayData.title === "Title Selector Failed";
            copyTitleItem.classList.toggle('gsrs-cm-disabled', titleIsInvalid);
        }
        if(copyUrlItem){
            const displayUrlIsInvalid = !displayData.url || displayData.url === "Extraction Failed" || displayData.url === "URL Extraction Failed";
            const rawUrlIsInvalid = !internalData.rawUrl || internalData.rawUrl === "Extraction Failed" || internalData.rawUrl === "URL Extraction Failed";
            copyUrlItem.classList.toggle('gsrs-cm-disabled', displayUrlIsInvalid && rawUrlIsInvalid);
        }
    }
    function populateResultsList() {
        const resultsListContainer = GSRS_App.uiElements.resultsListContainer; if (!resultsListContainer) return;
        clearAllPageHighlights(false);
        resultsListContainer.innerHTML = '';
        const resultsToDisplay = (GSRS_App.uiElements.filterInput && GSRS_App.uiElements.filterInput.value.trim() !== "") ? GSRS_App.filteredResults : GSRS_App.allResults;

        if (resultsToDisplay.length === 0) {
            resultsListContainer.innerHTML = '<div style="text-align:center; color:#777; padding:10px 0;">No results to display.</div>';
            if (GSRS_App.uiElements.resultPreviewArea) { GSRS_App.uiElements.resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 20px;">No results to preview.</p>'; }
            return;
        }

        const fragment = document.createDocumentFragment();
        let currentSelectedOriginalResult = null;
        if(GSRS_App.state.selectedResultListItem && GSRS_App.state.selectedResultListItem.dataset.originalFullArrayIndex) {
            const originalIdx = parseInt(GSRS_App.state.selectedResultListItem.dataset.originalFullArrayIndex, 10);
            if(originalIdx >= 0 && originalIdx < GSRS_App.allResults.length) {
                if (resultsToDisplay.includes(GSRS_App.allResults[originalIdx])) {
                    currentSelectedOriginalResult = GSRS_App.allResults[originalIdx];
                } else {
                    GSRS_App.state.selectedResultListItem = null;
                    if (GSRS_App.uiElements.resultPreviewArea) GSRS_App.uiElements.resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 20px;">Click an item from the list to see details.</p>';
                }
            }
        }

        resultsToDisplay.forEach((item, indexInCurrentList) => {
            const listItem = document.createElement('div'); listItem.className = 'gsrs-list-item';
            const originalIndex = GSRS_App.allResults.indexOf(item);
            const displayObjForTitle = getOutputDisplayObject(item.display);
            listItem.textContent = `[${displayObjForTitle.position || (originalIndex + 1)}] ${displayObjForTitle.title || item.internal.rawTitle || 'N/A'}`;
            listItem.dataset.resultIndexInCurrentList = indexInCurrentList;
            if (originalIndex !== -1) { listItem.dataset.originalFullArrayIndex = originalIndex; }
            listItem.addEventListener('click', handleResultListItemClick);
            listItem.addEventListener('contextmenu', handleResultListItemContextMenu);
            fragment.appendChild(listItem);

            if (item === currentSelectedOriginalResult) {
                listItem.classList.add('selected');
                GSRS_App.state.selectedResultListItem = listItem;
                if (GSRS_App.currentSettings.highlightListItemOnPage && item.domElement && document.body.contains(item.domElement)) {
                    if (GSRS_App.state.lastListItemHighlightedElement && GSRS_App.state.lastListItemHighlightedElement !== item.domElement) {
                        GSRS_App.state.lastListItemHighlightedElement.classList.remove('gsrs-list-item-page-highlight');
                    }
                    item.domElement.classList.add('gsrs-list-item-page-highlight');
                    GSRS_App.state.lastListItemHighlightedElement = item.domElement;
                }
            }
        });
        resultsListContainer.appendChild(fragment);
        if (!GSRS_App.state.selectedResultListItem && GSRS_App.uiElements.resultPreviewArea && resultsToDisplay.length > 0 && GSRS_App.currentSettings.showPreviewInListMode) {
            GSRS_App.uiElements.resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 20px;">Click an item from the list to see details.</p>';
        } else if (!GSRS_App.currentSettings.showPreviewInListMode && GSRS_App.uiElements.resultPreviewArea) {
            GSRS_App.uiElements.resultPreviewArea.innerHTML = '';
        }
    }
    function handleResultListItemClick(event) {
        const resultPreviewArea = GSRS_App.uiElements.resultPreviewArea;
        if (!resultPreviewArea && !GSRS_App.currentSettings.highlightListItemOnPage) return;
        clearAllPageHighlights(false);
        const listItem = event.currentTarget;
        let resultItem = null;
        const currentResultsSource = (GSRS_App.uiElements.filterInput && GSRS_App.uiElements.filterInput.value.trim() !== "") ? GSRS_App.filteredResults : GSRS_App.allResults;

        if (listItem.dataset.originalFullArrayIndex) {
            const originalIndex = parseInt(listItem.dataset.originalFullArrayIndex, 10);
            if (originalIndex >= 0 && originalIndex < GSRS_App.allResults.length) {
                if (currentResultsSource.includes(GSRS_App.allResults[originalIndex])) {
                    resultItem = GSRS_App.allResults[originalIndex];
                }
            }
        }
        if (!resultItem) {
            const resultIndexInCurrentList = parseInt(listItem.dataset.resultIndexInCurrentList, 10);
            if (resultIndexInCurrentList >= 0 && resultIndexInCurrentList < currentResultsSource.length) {
                resultItem = currentResultsSource[resultIndexInCurrentList];
            }
        }

        if (resultItem) {
            const resultData = getOutputDisplayObject(resultItem.display);
            const originalIndexGlobal = GSRS_App.allResults.indexOf(resultItem);
            if (resultPreviewArea && GSRS_App.currentSettings.showPreviewInListMode) {
                let previewHTML = `<p><strong>Position:</strong> ${resultData.position || (originalIndexGlobal + 1)}</p>`;
                if (resultData.hasOwnProperty('title')) { previewHTML += `<p><strong>Title:</strong> ${resultData.title ? resultData.title.replace(/</g, '&lt;') : 'Not Fetched/Available'}</p>`; }
                else if (resultItem.internal.rawTitle && resultItem.internal.rawTitle !== "Extraction Failed" && resultItem.internal.rawTitle !== "Title Selector Failed") { previewHTML += `<p><strong>Title (raw):</strong> ${resultItem.internal.rawTitle.replace(/</g, '&lt;')}</p>`; }
                if (resultData.hasOwnProperty('url')) { const urlToDisplayInText = (resultData.url && resultData.url !== "Extraction Failed" && resultData.url !== "URL Extraction Failed") ? resultData.url : "Not Fetched/Available"; const hrefForLink = (resultItem.internal.rawUrl && resultItem.internal.rawUrl !== "Extraction Failed" && resultItem.internal.rawUrl !== "URL Extraction Failed") ? resultItem.internal.rawUrl : '#'; previewHTML += `<p><strong>URL:</strong> ${resultData.url ? `<a href="${hrefForLink}" target="_blank" title="Raw URL: ${hrefForLink}">${urlToDisplayInText.replace(/</g, '&lt;')}</a>` : urlToDisplayInText}</p>`; }
                if (resultData.hasOwnProperty('siteName')) { previewHTML += `<p><strong>Site Name:</strong> ${resultData.siteName ? resultData.siteName.replace(/</g, '&lt;') : 'Not Fetched/Available'}</p>`; }
                if (resultData.hasOwnProperty('breadcrumbs')) { previewHTML += `<p><strong>Breadcrumbs:</strong> ${resultData.breadcrumbs ? resultData.breadcrumbs.replace(/</g, '&lt;') : 'Not Fetched/Available'}</p>`; }
                if (resultData.hasOwnProperty('originalDateText') || resultData.hasOwnProperty('parsedDateISO')) { let dateDisplay = "Not Fetched/Available"; if (resultData.originalDateText) { dateDisplay = resultData.originalDateText.replace(/</g, '&lt;'); if (resultData.parsedDateISO) { try { const d = new Date(resultData.parsedDateISO); if (!isNaN(d.getTime())) { dateDisplay += ` (Parsed: ${d.toLocaleString()})`; } } catch (e) {} } } else if (resultData.parsedDateISO) { try { const d = new Date(resultData.parsedDateISO); if (!isNaN(d.getTime())) { dateDisplay = `(Parsed: ${d.toLocaleString()})`; } else { dateDisplay = "Invalid Parsed Date"; } } catch (e) { dateDisplay = "Error parsing date"; } } previewHTML += `<p><strong>Date Info:</strong> ${dateDisplay}</p>`; }
                if (resultData.hasOwnProperty('description')) { previewHTML += `<p><strong>Description:</strong> ${resultData.description ? resultData.description.replace(/</g, '&lt;') : 'Not Fetched/Available'}</p>`; }
                if (resultData.hasOwnProperty('highlightedSnippets') && resultData.highlightedSnippets) { previewHTML += `<p><strong>Keywords:</strong> ${resultData.highlightedSnippets.replace(/</g, '&lt;')}</p>`; }
                resultPreviewArea.innerHTML = previewHTML;
            }
            if (GSRS_App.state.selectedResultListItem && GSRS_App.state.selectedResultListItem !== listItem) { GSRS_App.state.selectedResultListItem.classList.remove('selected'); }
            listItem.classList.add('selected');
            GSRS_App.state.selectedResultListItem = listItem;

            if (GSRS_App.currentSettings.highlightListItemOnPage && resultItem.domElement && document.body.contains(resultItem.domElement)) {
                resultItem.domElement.classList.remove('gsrs-highlighted', 'gsrs-context-highlight');
                resultItem.domElement.classList.add('gsrs-list-item-page-highlight');
                resultItem.domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                GSRS_App.state.lastListItemHighlightedElement = resultItem.domElement;
            }
        } else {
            if (resultPreviewArea && GSRS_App.currentSettings.showPreviewInListMode) { resultPreviewArea.innerHTML = '<p style="color: red;">Error: Could not retrieve result details.</p>'; }
            if (GSRS_App.state.selectedResultListItem) GSRS_App.state.selectedResultListItem.classList.remove('selected');
            GSRS_App.state.selectedResultListItem = null;
        }
    }
    function handleStartParse() {
        if (GSRS_App.currentSettings.debugMode) console.log("%cGSRS: handleStartParse called.", "color: orange; font-weight: bold;");
        GSRS_App.allResults = []; GSRS_App.filteredResults = [];
        const filterInput = GSRS_App.uiElements.filterInput;
        if (filterInput) { filterInput.value = ''; GSRS_App.currentSettings.lastFilterTerm = ''; GM_setValue('gsrs_lastFilterTerm', ''); filterInput.classList.remove('gsrs-filter-active'); }
        if (GSRS_App.state.selectedResultListItem) {GSRS_App.state.selectedResultListItem.classList.remove('selected'); GSRS_App.state.selectedResultListItem = null;}
        const resultPreviewArea = GSRS_App.uiElements.resultPreviewArea;
        if(resultPreviewArea) resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 20px;">Click an item from the list to see details.</p>';
        clearAllPageHighlights(true);
        try { document.querySelectorAll('[data-gsrs-block-parsed="true"]').forEach(el => {el.removeAttribute('data-gsrs-block-parsed');}); document.querySelectorAll('[data-gsrs-title-processed="true"]').forEach(el => {el.removeAttribute('data-gsrs-title-processed');}); }
        catch (e) { console.warn(`GSRS: Issue clearing parsed flags. Error: ${e.message}`); }

        const initialFoundCount = processPageForResults(document);

        if (GSRS_App.allResults.length === 0 && document.querySelectorAll(GSRS_App.currentSettings.titleSelector).length > 0) { const specificMessage = `Scrape initiated. Found 0 results. Title selector '${GSRS_App.currentSettings.titleSelector}' matched elements, but no valid result blocks were identified. Check parent block logic or exclusion rules.`; GSRS_App.uiManager.showUIMessage(specificMessage, 'error', 12000); if(GSRS_App.currentSettings.debugMode) console.warn(`GSRS: ${specificMessage}`); }
        else if (GSRS_App.allResults.length === 0 && document.querySelectorAll(GSRS_App.currentSettings.titleSelector).length === 0 && GSRS_App.uiElements.uiMessageDiv && !GSRS_App.uiElements.uiMessageDiv.textContent.includes("Warning: Title selector")) { const specificMessage = `Scrape initiated. Found 0 results. Title selector '${GSRS_App.currentSettings.titleSelector}' found NO elements. Please check your title selector.`; GSRS_App.uiManager.showUIMessage(specificMessage, 'error', 12000); if(GSRS_App.currentSettings.debugMode) console.warn(`GSRS: ${specificMessage}`); }
        else if (GSRS_App.allResults.length > 0) { GSRS_App.uiManager.showUIMessage(`Scraped ${GSRS_App.allResults.length} results from current page.`, 'success'); }
        else { GSRS_App.uiManager.showUIMessage('Scrape initiated. No results found on current page.', 'success', 4000); }

        GSRS_App.uiManager.updateResultsDisplay();
        GSRS_App.uiManager.updateObserverStatus(false); // Ensure observer status reflects no active dynamic detection

        setupMutationObserver(); // This will just log that MO is disabled

        const startButton = document.getElementById('gsrs-start-btn'); if (startButton) startButton.textContent = 'Re-Scrape Page';
    }
    function handleFilterResults() {
        const filterInput = GSRS_App.uiElements.filterInput;
        const searchTerm = filterInput ? filterInput.value.toLowerCase().trim() : "";
        clearTimeout(GSRS_App.state.filterTimeout);
        GSRS_App.state.filterTimeout = setTimeout(() => {
            GSRS_App.currentSettings.lastFilterTerm = searchTerm;
            GM_setValue('gsrs_lastFilterTerm', searchTerm);
            if (GSRS_App.currentSettings.debugMode) console.log("GSRS: Saved filter term:", searchTerm);
        }, 500);

        if (filterInput) { if (searchTerm) filterInput.classList.add('gsrs-filter-active'); else filterInput.classList.remove('gsrs-filter-active'); }

        if (!searchTerm) {
            GSRS_App.filteredResults = [];
        } else {
            GSRS_App.filteredResults = GSRS_App.allResults.filter(item => {
                const displayItem = getOutputDisplayObject(item.display); if (!displayItem) return false;
                let match = false;
                if (displayItem.hasOwnProperty('title') && displayItem.title && typeof displayItem.title === 'string') match = match || displayItem.title.toLowerCase().includes(searchTerm);
                if (displayItem.hasOwnProperty('url') && displayItem.url && typeof displayItem.url === 'string') match = match || displayItem.url.toLowerCase().includes(searchTerm);
                if (displayItem.hasOwnProperty('siteName') && displayItem.siteName && typeof displayItem.siteName === 'string') match = match || displayItem.siteName.toLowerCase().includes(searchTerm);
                if (displayItem.hasOwnProperty('breadcrumbs') && displayItem.breadcrumbs && typeof displayItem.breadcrumbs === 'string') match = match || displayItem.breadcrumbs.toLowerCase().includes(searchTerm);
                if (displayItem.hasOwnProperty('description') && displayItem.description && typeof displayItem.description === 'string') match = match || displayItem.description.toLowerCase().includes(searchTerm);
                if (displayItem.hasOwnProperty('highlightedSnippets') && displayItem.highlightedSnippets && typeof displayItem.highlightedSnippets === 'string') match = match || displayItem.highlightedSnippets.toLowerCase().includes(searchTerm);
                if (displayItem.hasOwnProperty('originalDateText') && displayItem.originalDateText && typeof displayItem.originalDateText === 'string') match = match || displayItem.originalDateText.toLowerCase().includes(searchTerm);
                return match;
            });
        }
        clearAllPageHighlights(false);
        if (GSRS_App.state.currentViewMode === 'list') { GSRS_App.uiManager.populateResultsList(); }
        GSRS_App.uiManager.updateResultsDisplay();
    }
    function handleClearResults() {
        const resultsTextArea = GSRS_App.uiElements.resultsTextArea;
        const noResultsYet = GSRS_App.allResults.length === 0 && (!resultsTextArea || !resultsTextArea.value || (resultsTextArea.placeholder && resultsTextArea.value === '' && resultsTextArea.placeholder.toLowerCase().includes('no results scraped yet')));
        const filterInput = GSRS_App.uiElements.filterInput;
        if (noResultsYet && (!filterInput || !filterInput.value)) { GSRS_App.uiManager.showUIMessage('Already empty.', 'error', 2000); return; }

        GSRS_App.allResults = []; GSRS_App.filteredResults = [];
        if(filterInput) { filterInput.value = ''; GSRS_App.currentSettings.lastFilterTerm = ''; GM_setValue('gsrs_lastFilterTerm', ''); filterInput.classList.remove('gsrs-filter-active'); }
        if (resultsTextArea) resultsTextArea.value = '';
        const resultPreviewArea = GSRS_App.uiElements.resultPreviewArea;
        if (resultPreviewArea) resultPreviewArea.innerHTML = '<p style="color: #777; text-align:center; margin-top: 20px;">Click an item from the list to see details.</p>';
        if (GSRS_App.state.selectedResultListItem) { GSRS_App.state.selectedResultListItem.classList.remove('selected'); GSRS_App.state.selectedResultListItem = null; }
        clearAllPageHighlights(true);
        GSRS_App.uiManager.updateResultsDisplay();
        GSRS_App.uiManager.showUIMessage('Results cleared.', 'success');
    }
    function updateActionButtonsState() {
        const actionButtonBar = document.getElementById('gsrs-action-button-bar');
        if (!actionButtonBar) { if (GSRS_App.currentSettings.debugMode && document.readyState === 'complete') { console.warn("GSRS: updateActionButtonsState - #gsrs-action-button-bar not found."); } return; }
        const actionLinks = actionButtonBar.querySelectorAll('.gsrs-action-format-link');
        if (!actionLinks || actionLinks.length === 0) { if (GSRS_App.currentSettings.debugMode && document.readyState === 'complete') { console.warn("GSRS: updateActionButtonsState - no .gsrs-action-format-link found."); } return; }

        let resultsToConsider;
        const filterInput = GSRS_App.uiElements.filterInput;
        const isFilterActive = filterInput && filterInput.value.trim() !== "";

        if (GSRS_App.state.copyDownloadTarget === 'all') {
            resultsToConsider = GSRS_App.allResults;
        } else {
            resultsToConsider = isFilterActive ? GSRS_App.filteredResults : GSRS_App.allResults;
        }
        const noResults = resultsToConsider.length === 0;
        actionLinks.forEach(link => { link.classList.toggle('gsrs-action-disabled', noResults); });

        if (GSRS_App.currentSettings.debugMode) {
            console.log(`GSRS: updateActionButtonsState - Target: ${GSRS_App.state.copyDownloadTarget}, FilterActive: ${isFilterActive}, ResultsToConsider: ${resultsToConsider.length}, NoResults: ${noResults}`);
            actionLinks.forEach(link => { console.log(` - Link "${link.textContent}" (Action: ${link.dataset.action}, Format: ${link.dataset.format}): Disabled = ${link.classList.contains('gsrs-action-disabled')}`); });
        }
    }
    function handleCopyOrDownloadMarkdown(actionType) {
        let baseResultsToConsider = GSRS_App.allResults;
        const filterInput = GSRS_App.uiElements.filterInput;
        const isFilterActive = filterInput && filterInput.value.trim() !== '';
        if (GSRS_App.state.copyDownloadTarget === 'current' && isFilterActive) { baseResultsToConsider = GSRS_App.filteredResults; }
        const resultsToProcess = baseResultsToConsider.map(item => getOutputDisplayObject(item.display));

        if (resultsToProcess.length === 0) { let msg = `No results to ${actionType} as Markdown.`; if (isFilterActive && GSRS_App.state.copyDownloadTarget === 'current') { msg = `Filter yielded no results to ${actionType} as Markdown.`; } GSRS_App.uiManager.showUIMessage(msg, 'error'); return; }

        const markdownString = convertToMarkdownList(resultsToProcess);
        if (actionType === 'copy') { GM_setClipboard(markdownString); GSRS_App.uiManager.showUIMessage(`Copied ${resultsToProcess.length} results as Markdown!`, 'success'); }
        else if (actionType === 'download') {
            const blob = new Blob([markdownString], {type: 'text/markdown;charset=utf-8;'}); const url = URL.createObjectURL(blob);
            const a = document.createElement('a'); a.href = url;
            const qParam = new URLSearchParams(window.location.search).get('q') || 'serp_results'; const filenameSafeQuery = qParam.replace(/[^a-z0-9_.-]/gi,'_').substring(0, 50);
            a.download = `${filenameSafeQuery}_${new Date().toISOString().slice(0,10)}.md`;
            document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
            GSRS_App.uiManager.showUIMessage(`Downloaded ${resultsToProcess.length} results as Markdown.`, 'success');
        }
    }
    function handleCopyResults() {
        let baseResultsToConsider = GSRS_App.allResults;
        const filterInput = GSRS_App.uiElements.filterInput;
        const isFilterActive = filterInput && filterInput.value.trim() !== '';
        if (GSRS_App.state.copyDownloadTarget === 'current' && isFilterActive) { baseResultsToConsider = GSRS_App.filteredResults; }
        const resultsToActOn = baseResultsToConsider.map(item => getOutputDisplayObject(item.display));

        if (resultsToActOn.length > 0) { GM_setClipboard(JSON.stringify(resultsToActOn, null, 2)); GSRS_App.uiManager.showUIMessage(`Copied ${resultsToActOn.length} results as JSON!`, 'success'); }
        else { let msg = 'No results to copy as JSON.'; if (isFilterActive && GSRS_App.state.copyDownloadTarget === 'current') { msg = 'Filter yielded no results to copy as JSON.'; } GSRS_App.uiManager.showUIMessage(msg, 'error'); }
    }
    function handleDownloadResults(format = 'json') {
        let baseResultsToConsider = GSRS_App.allResults;
        const filterInput = GSRS_App.uiElements.filterInput;
        const isFilterActive = filterInput && filterInput.value.trim() !== '';
        if (GSRS_App.state.copyDownloadTarget === 'current' && isFilterActive) { baseResultsToConsider = GSRS_App.filteredResults; }
        const resultsToDownload = baseResultsToConsider.map(item => getOutputDisplayObject(item.display));

        if (resultsToDownload.length === 0) { let msg = 'No results to download.'; if (isFilterActive && GSRS_App.state.copyDownloadTarget === 'current') { msg = 'Filter yielded no results to download.'; } GSRS_App.uiManager.showUIMessage(msg, 'error'); return; }

        let dataString, blobType, fileExtension;
        if (format === 'csv') {
            const csvData = convertToCSV(resultsToDownload);
            if (!csvData) { GSRS_App.uiManager.showUIMessage('No columns selected for CSV export. Please check settings.', 'error'); return; }
            dataString = '\uFEFF' + csvData; blobType = 'text/csv;charset=utf-8;'; fileExtension = 'csv';
        } else {
            dataString = JSON.stringify(resultsToDownload, null, 2); blobType = 'application/json;charset=utf-8;'; fileExtension = 'json';
        }
        const blob = new Blob([dataString], {type: blobType}); const url = URL.createObjectURL(blob);
        const a = document.createElement('a'); a.href = url;
        const q = new URLSearchParams(window.location.search).get('q')||'serp_results'; const filenameSafeQuery = q.replace(/[^a-z0-9_.-]/gi,'_').substring(0, 50);
        a.download = `${filenameSafeQuery}_${new Date().toISOString().slice(0,10)}.${fileExtension}`;
        document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
        GSRS_App.uiManager.showUIMessage(`Downloaded ${resultsToDownload.length} results as ${fileExtension.toUpperCase()}.`, 'success');
    }
    function handleCopyUrls() {
        let baseResultsToConsider = GSRS_App.allResults;
        const filterInput = GSRS_App.uiElements.filterInput;
        const isFilterActive = filterInput && filterInput.value.trim() !== '';
        if (GSRS_App.state.copyDownloadTarget === 'current' && isFilterActive) { baseResultsToConsider = GSRS_App.filteredResults; }
        const urls = baseResultsToConsider.map(item => { const displayObj = getOutputDisplayObject(item.display); return displayObj.url && displayObj.url !== "Extraction Failed" && displayObj.url !== "URL Extraction Failed" && !displayObj.url.startsWith('javascript:void(0)') ? displayObj.url : null; }).filter(url => url);

        if (urls.length > 0) { let message = `Copied ${urls.length} URLs!`; if (urls.length === 1 && urls[0].length < 70) { message = `Copied URL: ${urls[0]}`; } GM_setClipboard(urls.join('\n')); GSRS_App.uiManager.showUIMessage(message, 'success', urls.length === 1 && urls[0].length < 70 ? 3000 : 2000); }
        else { let msg = 'No valid URLs to copy.'; if (isFilterActive && GSRS_App.state.copyDownloadTarget === 'current') { msg = 'Filter yielded no URLs to copy.'; } else if (GSRS_App.allResults.length > 0 && urls.length === 0) { msg = 'No valid URLs found in current results (check fetch settings).'; } GSRS_App.uiManager.showUIMessage(msg, 'error'); }
    }
    function handleDownloadUrls() {
        let baseResultsToConsider = GSRS_App.allResults;
        const filterInput = GSRS_App.uiElements.filterInput;
        const isFilterActive = filterInput && filterInput.value.trim() !== '';
        if (GSRS_App.state.copyDownloadTarget === 'current' && isFilterActive) { baseResultsToConsider = GSRS_App.filteredResults; }
        const urls = baseResultsToConsider.map(item => { const displayObj = getOutputDisplayObject(item.display); return displayObj.url && displayObj.url !== "Extraction Failed" && displayObj.url !== "URL Extraction Failed" && !displayObj.url.startsWith('javascript:void(0)') ? displayObj.url : null; }).filter(url => url);

        if (urls.length === 0) { let msg = 'No valid URLs to download.'; if (isFilterActive && GSRS_App.state.copyDownloadTarget === 'current') { msg = 'Filter yielded no URLs to download.'; } else if (GSRS_App.allResults.length > 0 && urls.length === 0) { msg = 'No valid URLs found in current results (check fetch settings).'; } GSRS_App.uiManager.showUIMessage(msg, 'error'); return; }

        const dataString = urls.join('\r\n'); const blobType = 'text/plain;charset=utf-8;'; const fileExtension = 'txt';
        const blob = new Blob([dataString], {type: blobType}); const fileUrl = URL.createObjectURL(blob);
        const a = document.createElement('a'); a.href = fileUrl;
        const q = new URLSearchParams(window.location.search).get('q')||'serp_urls'; const filenameSafeQuery = q.replace(/[^a-z0-9_.-]/gi,'_').substring(0, 50);
        a.download = `${filenameSafeQuery}_${new Date().toISOString().slice(0,10)}_urls.${fileExtension}`;
        document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(fileUrl);
        GSRS_App.uiManager.showUIMessage(`Downloaded ${urls.length} URLs as ${fileExtension.toUpperCase()}.`, 'success');
    }
    function updateResultsDisplay() {
        const filterInput = GSRS_App.uiElements.filterInput;
        const resultsCountSpan = GSRS_App.uiElements.resultsCountSpan;
        const resultsTextArea = GSRS_App.uiElements.resultsTextArea;
        const isFilterActive = filterInput && filterInput.value.trim() !== "";
        const resultsToUseForDisplay = isFilterActive ? GSRS_App.filteredResults : GSRS_App.allResults;

        if (resultsCountSpan) {
            if (isFilterActive) { resultsCountSpan.textContent = `Showing ${resultsToUseForDisplay.length} of ${GSRS_App.allResults.length} results (filtered)`; }
            else { resultsCountSpan.textContent = `Results: ${GSRS_App.allResults.length}`; }
        }

        if (GSRS_App.state.currentViewMode === 'json') {
            if (resultsTextArea) {
                const displayableJsonResults = resultsToUseForDisplay.map(item => getOutputDisplayObject(item.display));
                if (displayableJsonResults.length === 0) {
                    if (isFilterActive && GSRS_App.allResults.length > 0) { resultsTextArea.value = 'No results match your filter.'; }
                    else { resultsTextArea.placeholder = 'No results scraped yet. Click "Scrape Page".'; resultsTextArea.value = ''; }
                } else {
                    resultsTextArea.value = JSON.stringify(displayableJsonResults, null, 2);
                    resultsTextArea.placeholder = 'Scraped results will appear here...';
                }
            }
        } else if (GSRS_App.state.currentViewMode === 'list') {
            GSRS_App.uiManager.populateResultsList();
        }
        updateActionButtonsState();
    }
    function isPAAContainer(blockElement) { if (!blockElement) return false; const headingSpan = blockElement.querySelector(GSRS_App.INTERNAL_SELECTORS.relatedQuestionsBlockHeadingSpan); if (headingSpan && headingSpan.innerText) { const headingText = headingSpan.innerText.trim(); return GSRS_App.INTERNAL_SELECTORS.relatedQuestionsBlockTextIndicators.some(indicator => headingText.includes(indicator)); } return false; }
    function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
    function _extractTitleAndUrl(resultBlock, debug, position) { let rawTitle = "Title Selector Failed"; let rawUrl = "URL Extraction Failed"; const titleElement = resultBlock.querySelector(GSRS_App.currentSettings.titleSelector); if (titleElement?.innerText) { rawTitle = titleElement.innerText.trim(); const anchorElement = titleElement.closest(GSRS_App.INTERNAL_SELECTORS.anchorInTitle); if (anchorElement?.href && !anchorElement.href.startsWith('javascript:')) { rawUrl = anchorElement.href; } if (debug) console.log(`GSRS (extractTitleUrl) [Pos ${position}]: Title found: "${rawTitle.substring(0,50)}...", URL from title anchor: "${rawUrl}"`); } else { if (debug) console.log(`GSRS (extractTitleUrl) [Pos ${position}]: titleElement (selector: ${GSRS_App.currentSettings.titleSelector}) not found or no innerText.`); } if (rawUrl === "URL Extraction Failed" || rawUrl === "Extraction Failed") { const firstLinkInBlock = resultBlock.querySelector('a[href] > h3'); if (firstLinkInBlock?.parentElement?.href && !firstLinkInBlock.parentElement.href.startsWith('javascript:')) { rawUrl = firstLinkInBlock.parentElement.href; if (rawTitle === "Title Selector Failed" && firstLinkInBlock.innerText) rawTitle = firstLinkInBlock.innerText.trim(); if (debug) console.log(`GSRS (extractTitleUrl) [Pos ${position}]: Fallback URL (a > h3): "${rawUrl}", Title: "${rawTitle.substring(0,50)}..."`); } else { const genericLink = resultBlock.querySelector('h3 > a[href], div > a[href] > h3, div > div > a[href] > h3'); if(genericLink?.href && !genericLink.href.startsWith('javascript:')) { rawUrl = genericLink.href; const h3InsideGeneric = genericLink.querySelector('h3'); if (rawTitle === "Title Selector Failed" && h3InsideGeneric?.innerText) { rawTitle = h3InsideGeneric.innerText.trim(); } else if (rawTitle === "Title Selector Failed" && genericLink.closest('h3')?.innerText) { rawTitle = genericLink.closest('h3').innerText.trim(); } else if (rawTitle === "Title Selector Failed" && genericLink.innerText) { rawTitle = genericLink.innerText.trim().split('\n')[0]; } if (debug) console.log(`GSRS (extractTitleUrl) [Pos ${position}]: Fallback URL (genericLink): "${rawUrl}", Title: "${rawTitle.substring(0,50)}..."`); } } } if (debug && (rawUrl === "URL Extraction Failed" || rawUrl === "Extraction Failed")) console.log(`GSRS (extractTitleUrl) [Pos ${position}]: URL extraction failed after all fallbacks.`); if (rawUrl.startsWith('javascript:void(0)')) rawUrl = "URL Extraction Failed"; if (rawTitle === "Title Selector Failed" && rawUrl !== "URL Extraction Failed" && rawUrl !== "Extraction Failed") { const urlAnchor = resultBlock.querySelector(`a[href="${CSS.escape(rawUrl)}"]`); if (urlAnchor?.innerText) { let potentialTitle = urlAnchor.innerText.trim(); const h3InAnchor = urlAnchor.querySelector('h3'); if (h3InAnchor?.innerText) potentialTitle = h3InAnchor.innerText.trim(); else if (urlAnchor.closest('h3')?.innerText) potentialTitle = urlAnchor.closest('h3').innerText.trim(); if(potentialTitle) rawTitle = potentialTitle.split('\n')[0]; if (debug && rawTitle !== "Title Selector Failed") console.log(`GSRS (extractTitleUrl) [Pos ${position}]: Title recovered from URL context: "${rawTitle.substring(0,50)}..."`); } } return { rawTitle, rawUrl }; }
    function _extractSiteName(resultBlock, rawUrl, debug, position) { if (!GSRS_App.currentSettings.fetchSiteName) return null; let siteNameText = "Extraction Failed"; const siteNameElement = resultBlock.querySelector(GSRS_App.INTERNAL_SELECTORS.siteNameSelector); if (siteNameElement?.innerText) { let tempSiteName = siteNameElement.innerText.trim(); if (tempSiteName.includes('http')) { try { if (rawUrl && rawUrl !== "URL Extraction Failed" && rawUrl !== "Extraction Failed") { const urlPart = new URL(rawUrl.startsWith('http') ? rawUrl : 'http://' + rawUrl); if (tempSiteName.toLowerCase().includes(urlPart.hostname.replace('www.','').toLowerCase())) { tempSiteName = tempSiteName.split(/·|>|\u203A/)[0].trim(); tempSiteName = tempSiteName.replace(urlPart.hostname, '').replace(/\(|\)/g,'').trim(); } } } catch(e){ if (debug) console.warn(`GSRS (extractSiteName) [Pos ${position}]: Error cleaning siteName from URL parts. Error: ${e.message}`); } } siteNameText = tempSiteName || "Extraction Failed"; if (debug) console.log(`GSRS (extractSiteName) [Pos ${position}]: Site name: "${siteNameText}"`); } else if (debug) { console.warn(`GSRS (extractSiteName) [Pos ${position}]: siteNameElement NOT found using selector: "${GSRS_App.INTERNAL_SELECTORS.siteNameSelector}" on block:`, resultBlock); siteNameText = null; } return siteNameText; }
    function _extractBreadcrumbs(resultBlock, rawUrl, debug, position) { if (!GSRS_App.currentSettings.fetchBreadcrumbs) return null; let breadcrumbsText = "Extraction Failed"; const citeElement = resultBlock.querySelector(GSRS_App.INTERNAL_SELECTORS.citeDisplay); if (citeElement?.innerText) { let tempBreadcrumbs = citeElement.innerText.trim(); if (rawUrl && rawUrl !== "URL Extraction Failed" && rawUrl !== "Extraction Failed") { try { const urlObj = new URL(rawUrl.startsWith('http') ? rawUrl : 'http://' + rawUrl); let hostnamePattern = urlObj.hostname.replace('www.', ''); hostnamePattern = escapeRegExp(hostnamePattern); const hostnameRegex = new RegExp(`(https?:\\/\\/)?(www\\.)?${hostnamePattern}\\s*([›>/\\s]|$)`, 'gi'); tempBreadcrumbs = tempBreadcrumbs.replace(hostnameRegex, '').trim(); } catch (e) { if (debug) console.warn(`GSRS (extractBreadcrumbs) [Pos ${position}]: Error cleaning breadcrumbs from URL. Error: ${e.message}`); } } tempBreadcrumbs = tempBreadcrumbs.replace(/^[\s›>\/\-\|]+|[\s›>\/\-\|]+$/g, '').replace(/\s+/g, ' ').trim(); if (tempBreadcrumbs && tempBreadcrumbs.length > 1) { const BREADCRUMB_NOISE_PATTERNS = [ /^\d+([.,]\d+)*\s*(萬|千)?\s*個?(追蹤者|粉絲|的說法|回應|評論|評分|評價|觀看次數|月前|天前|小時前|分鐘前|年前)/i, /^\d+年\d+月\d+日/i, /^\d{1,2}\/\d{1,2}\/\d{2,4}/, /^\w+\s\d{1,2},\s\d{4}/i, /重要時刻/i, /^\d+:\d+(?::\d+)?$/ ]; if (BREADCRUMB_NOISE_PATTERNS.some(p => p.test(tempBreadcrumbs))) { breadcrumbsText = null; if (debug) console.log(`GSRS (extractBreadcrumbs) [Pos ${position}]: Breadcrumb candidate "${tempBreadcrumbs}" filtered by noise pattern.`); } else if (!tempBreadcrumbs.includes(' ') && tempBreadcrumbs.includes('.') && !tempBreadcrumbs.includes('›') && !tempBreadcrumbs.includes('>')) { try { new URL('http://' + tempBreadcrumbs); breadcrumbsText = null; } catch(e){ breadcrumbsText = tempBreadcrumbs; } } else { breadcrumbsText = tempBreadcrumbs; } } else { breadcrumbsText = null; } if (debug) console.log(`GSRS (extractBreadcrumbs) [Pos ${position}]: Breadcrumbs: "${breadcrumbsText}" (Original cite: "${citeElement.innerText.trim().substring(0,100)}")`); } else if (debug) { console.warn(`GSRS (extractBreadcrumbs) [Pos ${position}]: citeElement for breadcrumbs NOT found using selector: "${GSRS_App.INTERNAL_SELECTORS.citeDisplay}" on block:`, resultBlock); breadcrumbsText = null; } return breadcrumbsText; }
    function _extractDescriptionDetails(resultBlock, debug, position) { if (!GSRS_App.currentSettings.fetchDescription) { return { descriptionText: null, highlightedSnippetsText: null, originalDateText: null, parsedDateISO: null }; } let descriptionText = "Desc Extraction Failed"; let highlightedSnippetsText = null; let originalDateText = null; let parsedDateISO = null; let fullDescriptionSourceText = null; let descriptionContainerElement = null; let foundBySelector = "None"; const descSelectorsAttemptOrder = [ { name: "directDescriptionContainer", selector: GSRS_App.INTERNAL_SELECTORS.directDescriptionContainer }, { name: "genericDescriptionContainer", selector: GSRS_App.INTERNAL_SELECTORS.genericDescriptionContainer }, { name: "videoDescriptionSelector", selector: GSRS_App.INTERNAL_SELECTORS.videoDescriptionSelector } ]; for (const attempt of descSelectorsAttemptOrder) { descriptionContainerElement = resultBlock.querySelector(attempt.selector); if (descriptionContainerElement) { foundBySelector = attempt.name; if (debug) console.log(`GSRS (extractDescDetails) [Pos ${position}]: Desc container found by ${foundBySelector}. HTML (brief): ${descriptionContainerElement.outerHTML.substring(0,100)}`); break; } } if (!descriptionContainerElement) { const outerDescBlock = resultBlock.querySelector(GSRS_App.INTERNAL_SELECTORS.outerDescriptionBlockSelector); if (outerDescBlock) { if (debug) console.log(`GSRS (extractDescDetails) [Pos ${position}]: OuterDescBlock found, trying selectors inside it.`); for (const attempt of descSelectorsAttemptOrder) { descriptionContainerElement = outerDescBlock.querySelector(attempt.selector); if (descriptionContainerElement) { foundBySelector = `${attempt.name} (in outer)`; if (debug) console.log(`GSRS (extractDescDetails) [Pos ${position}]: Desc container found by ${foundBySelector}. HTML (brief): ${descriptionContainerElement.outerHTML.substring(0,100)}`); break; } } } } if (descriptionContainerElement) { fullDescriptionSourceText = descriptionContainerElement.innerText.trim(); if (debug) console.log(`GSRS (extractDescDetails) [Pos ${position}]: Raw innerText from container (${foundBySelector}): "${fullDescriptionSourceText.substring(0, 200)}..."`); if (GSRS_App.currentSettings.fetchDescriptionKeywords) { const keywordElements = descriptionContainerElement.querySelectorAll(GSRS_App.INTERNAL_SELECTORS.descriptionKeywordSelector); if (keywordElements.length > 0) { highlightedSnippetsText = Array.from(keywordElements).map(em => em.innerText.trim()).filter(text => text).join(' ... '); if (debug) console.log(`GSRS (extractDescDetails) [Pos ${position}]: Highlighted snippets: "${highlightedSnippetsText}"`); } else if (debug) { console.log(`GSRS (extractDescDetails) [Pos ${position}]: No keyword elements found with selector: ${GSRS_App.INTERNAL_SELECTORS.descriptionKeywordSelector}`); } } } else { if (debug) console.warn(`GSRS (extractDescDetails) [Pos ${position}]: Description container NOT found. Searched with: ${descSelectorsAttemptOrder.map(s=>s.selector).join(' , ')} on block:`, resultBlock); descriptionText = null; } if (fullDescriptionSourceText) { let processedDescription = fullDescriptionSourceText; if (GSRS_App.currentSettings.fetchDateInfo) { const dateInfo = _extractDateFromDescription(processedDescription, descriptionContainerElement, debug, position); originalDateText = dateInfo.originalDateText; parsedDateISO = dateInfo.parsedDateISO; if(originalDateText) { processedDescription = dateInfo.remainingDescription; } } descriptionText = processedDescription.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim(); if (descriptionText === "" && fullDescriptionSourceText !== "") { if (originalDateText && fullDescriptionSourceText.toLowerCase().trim() === originalDateText.toLowerCase().trim()) { if (debug) console.log(`GSRS (extractDescDetails) [Pos ${position}]: Description is empty because original text was just the date.`); descriptionText = null; } else if (originalDateText) { descriptionText = (fullDescriptionSourceText.length > originalDateText.length + 5) ? "Desc Extraction Failed" : null; if (debug && descriptionText === "Desc Extraction Failed") console.warn(`GSRS (extractDescDetails) [Pos ${position}]: Description empty after date removal, but original was more. Original: "${fullDescriptionSourceText.substring(0,50)}...", Date: "${originalDateText}"`); else if (debug && descriptionText === null) console.log(`GSRS (extractDescDetails) [Pos ${position}]: Description considered null after date removal (original likely date + minor surrounding text).`); } } else if (descriptionText === "" && fullDescriptionSourceText === "" ) { descriptionText = null; } if (debug && descriptionText !== "Desc Extraction Failed") console.log(`GSRS (extractDescDetails) [Pos ${position}]: Final descriptionText: "${(descriptionText||"").substring(0,100)}..."`); } else if (!descriptionContainerElement) { descriptionText = null; } return { descriptionText, highlightedSnippetsText, originalDateText, parsedDateISO }; }
    function _extractDateFromDescription(currentDescription, descriptionContainerElement, debug, position) { let originalDateText = null; let parsedDateISO = null; let remainingDescription = currentDescription; const separatorPatternGeneral = /^\s*(?:—|-)\s+/; const datePrefixSpan = descriptionContainerElement ? descriptionContainerElement.querySelector(GSRS_App.INTERNAL_SELECTORS.datePrefixSpanSelector) : null; if (datePrefixSpan?.innerText) { const potentialDateStrFromSpan = datePrefixSpan.innerText.trim(); if (debug) console.log(`GSRS (_extractDate) [Pos ${position}]: Date prefix span found, text: "${potentialDateStrFromSpan}"`); if (remainingDescription.startsWith(potentialDateStrFromSpan)) { const textAfterSpan = remainingDescription.substring(potentialDateStrFromSpan.length); const separatorMatch = textAfterSpan.match(separatorPatternGeneral); if (separatorMatch) { originalDateText = potentialDateStrFromSpan; remainingDescription = textAfterSpan.substring(separatorMatch[0].length).trim(); if (debug) console.log(`GSRS (_extractDate SPAN) [Pos ${position}]: Date extracted: "${originalDateText}". Desc remaining: "${remainingDescription.substring(0,50)}..."`); } else { if (debug) console.log(`GSRS (_extractDate SPAN) [Pos ${position}]: Found span text "${potentialDateStrFromSpan}", but no standard separator in: "${textAfterSpan.substring(0,30)}"`); const tempParsedDate = parseDateStringPureJS(potentialDateStrFromSpan, new Date()); if (tempParsedDate) { originalDateText = potentialDateStrFromSpan; remainingDescription = textAfterSpan.trim(); if (debug) console.log(`GSRS (_extractDate SPAN, NO SEP) [Pos ${position}]: Date candidate from span: "${originalDateText}". Desc remaining: "${remainingDescription.substring(0,50)}..."`); } else if (debug) { console.log(`GSRS (_extractDate SPAN, NO SEP) [Pos ${position}]: Span text "${potentialDateStrFromSpan}" did not parse as date, not using it as date.`); } } } } if (!originalDateText && remainingDescription) { if (debug) console.log(`GSRS (_extractDate REGEX) [Pos ${position}]: Trying regex on: "${remainingDescription.substring(0,50)}..."`); const cjkAbsoluteDatePatterns = [ /^(\d{4}年\s*\d{1,2}月\s*\d{1,2}日?(?:\s*\d{1,2}:\d{1,2}(?::\d{1,2})?)?)\s*(?:—|-)\s+/i, /^((\d{1,2})月\s*(\d{1,2})日?)\s*(?:—|-)\s+/i, ]; const enAbsoluteDatePatterns = [ /^(([a-z.]{3,9})\s+(\d{1,2})(?:st|nd|rd|th)?(?:,\s*|\s+)(\d{4})(?:\s+\d{1,2}:\d{1,2}(?::\d{1,2})?\s*(?:am|pm)?)?)\s*(?:—|-)\s+/i, /^((\d{1,2})\s+([a-z.]{3,9})\s+(\d{4})(?:\s+\d{1,2}:\d{1,2}(?::\d{1,2})?\s*(?:am|pm)?)?)\s*(?:—|-)\s+/i, /^((\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[T\s]\d{1,2}:\d{1,2}(?::\d{1,2})?)?)\s*(?:—|-)\s+/i, /^((\d{1,2})[-/.](\d{1,2})[-/.](\d{4}))\s*(?:—|-)\s+/i, ]; const relativeDatePatterns = [ /^((?:\d+\s+)?(?:year|month|week|day|hour|minute|second)s?\s+ago)\s*(?:—|-)\s+/i, /^(yesterday|today|just now|now)\s*(?:—|-)\s+/i, /^(\d+\s*(?:年|ヶ月|个月|週間|週|日|天|時間|小時|分|分鐘)前)\s*(?:—|-)\s+/i, /^(昨日|たった今|今さっき|昨天|剛剛|剛)\s*(?:—|-)\s+/i, ]; const generalDatePrefixPatterns = [ ...cjkAbsoluteDatePatterns, ...enAbsoluteDatePatterns, ...relativeDatePatterns ]; for (const pattern of generalDatePrefixPatterns) { const match = remainingDescription.match(pattern); if (match && match[1]) { originalDateText = match[1].trim(); remainingDescription = remainingDescription.substring(match[0].length).trim(); if (debug) console.log(`GSRS (_extractDate REGEX) [Pos ${position}]: Date extracted: "${originalDateText}". Desc remaining: "${remainingDescription.substring(0,50)}..."`); break; } } } if (originalDateText) { const parsedDateObject = parseDateStringPureJS(originalDateText, new Date()); if (parsedDateObject) { parsedDateISO = parsedDateObject.toISOString(); if (debug) console.log(`GSRS (_extractDate PARSE) [Pos ${position}]: Parsed "${originalDateText}" to ISO: ${parsedDateISO}`); } else if (debug) { console.log(`GSRS (_extractDate PARSE) [Pos ${position}]: Failed to parse extracted date string "${originalDateText}" with PureJS.`); } } else if (debug) { if (currentDescription && currentDescription.trim() !== "") { console.log(`GSRS (_extractDate) [Pos ${position}]: No date string extracted from description starting with: "${currentDescription.substring(0,100)}..."`); } } return { originalDateText, parsedDateISO, remainingDescription }; }
	function extractSingleResultElement(resultBlock, position) { if (!resultBlock || resultBlock.dataset.gsrsBlockParsed === 'true') { return null; } const debug = GSRS_App.currentSettings.debugMode; if (resultBlock.id && (resultBlock.id.startsWith('infy-scroll-divider-') || resultBlock.id === 'infy-scroll-loading' || resultBlock.id === 'infy-scroll-bottom')) { if (debug) console.log(`GSRS (extractSingleResultElement) [Pos ${position}]: Ignoring Infy Scroll helper element:`, resultBlock.id); resultBlock.dataset.gsrsBlockParsed = 'true'; return null; } if (debug) console.log(`%cGSRS (extractSingleResultElement): Processing block for position ${position}. Element:`, "color: purple; font-weight: bold;", resultBlock); const { rawTitle, rawUrl } = _extractTitleAndUrl(resultBlock, debug, position); const siteNameText = _extractSiteName(resultBlock, rawUrl, debug, position); const breadcrumbsText = _extractBreadcrumbs(resultBlock, rawUrl, debug, position); const descDetails = _extractDescriptionDetails(resultBlock, debug, position); if (rawTitle === "Title Selector Failed" && (rawUrl === "URL Extraction Failed" || rawUrl === "Extraction Failed")) { if (debug) console.log(`GSRS (extractSingleResultElement) [Pos ${position}]: Returning NULL - Title selector failed AND URL extraction also failed.`); return null; } resultBlock.dataset.gsrsBlockParsed = 'true'; if (GSRS_App.currentSettings.highlightParsed) { if (!resultBlock.classList.contains('gsrs-list-item-page-highlight') && !resultBlock.classList.contains('gsrs-context-highlight')) { resultBlock.classList.add('gsrs-highlighted'); setTimeout(() => { if (resultBlock && resultBlock.classList.contains('gsrs-highlighted') && !resultBlock.classList.contains('gsrs-list-item-page-highlight') && !resultBlock.classList.contains('gsrs-context-highlight')) { resultBlock.classList.remove('gsrs-highlighted'); } }, 2500); } } const displayUrl = decodeUrlIfEnabled(rawUrl === "URL Extraction Failed" ? "Extraction Failed" : rawUrl); if (debug) { console.log(`%cGSRS (extractSingleResultElement Result) [Pos ${position}]:%c \nTitle: "${(rawTitle === "Title Selector Failed" ? "Extraction Failed" : rawTitle).substring(0,30)}..." \nRaw URL: "${rawUrl}" \nDisplay URL: "${displayUrl}" \nSiteName: "${siteNameText}" \nBreadcrumbs: "${breadcrumbsText}" \nOriginal Date: "${descDetails.originalDateText}" \nParsed Date ISO: "${descDetails.parsedDateISO}" \nDescription: "${(descDetails.descriptionText||"N/A").substring(0,50)}..." \nKeywords: "${(descDetails.highlightedSnippetsText||"N/A").substring(0,30)}..."`, "color: green; font-weight: bold;", "color: green;"); } return { internal: { rawTitle: (rawTitle === "Title Selector Failed" ? "Extraction Failed" : rawTitle), rawUrl }, display: { position, title: (rawTitle === "Title Selector Failed" ? "Extraction Failed" : rawTitle), url: displayUrl, siteName: siteNameText, breadcrumbs: breadcrumbsText, description: descDetails.descriptionText, highlightedSnippets: descDetails.highlightedSnippetsText, originalDateText: descDetails.originalDateText, parsedDateISO: descDetails.parsedDateISO }, domElement: resultBlock }; }
    function processPageForResults(rootElement = document) { if (GSRS_App.currentSettings.debugMode) console.log("%cGSRS: processPageForResults starting...", "color: blue; font-weight: bold;", "Root:", rootElement === document ? "document" : rootElement); let newResultsFoundInThisPass = 0; const titleQuerySelector = GSRS_App.currentSettings.titleSelector; if (GSRS_App.allResults.length === 0 && rootElement === document) { try { if (document.querySelectorAll(titleQuerySelector).length === 0) { GSRS_App.uiManager.showUIMessage(`Warning: Title selector "${titleQuerySelector}" found 0 elements on the page. Check selector settings.`, 'error', 10000); } } catch (e) { GSRS_App.uiManager.showUIMessage(`Error with title selector "${titleQuerySelector}": ${e.message}. Please correct it in settings.`, 'error', 10000); return 0; } } const titleElements = Array.from(rootElement.querySelectorAll(titleQuerySelector)) .filter(titleEl => { if (titleEl.dataset.gsrsTitleProcessed === 'true') return false; if (titleEl.closest(GSRS_App.INTERNAL_SELECTORS.individualRelatedQuestionPair)) { if (GSRS_App.currentSettings.debugMode) console.log(`GSRS (processPage): Filtering out PAA item title: "${titleEl.innerText.substring(0,30)}..."`, titleEl); titleEl.dataset.gsrsTitleProcessed = 'true'; return false; } return true; }); const potentialResultBlocks = new Set(); if (GSRS_App.currentSettings.debugMode) console.log(`GSRS (processPage): Found ${titleElements.length} potential title elements (after PAA filtering) in root:`, rootElement === document ? "document" : rootElement); titleElements.forEach((titleEl) => { if (GSRS_App.currentSettings.debugMode) console.log(`GSRS (processPage): Processing Title Element: "${titleEl.innerText.substring(0, 50)}..."`, titleEl); let bestCandidateBlock = null; for (const knownSelector of GSRS_App.INTERNAL_SELECTORS.potentialResultBlockSelectors) { const closestKnownBlock = titleEl.closest(knownSelector); if (closestKnownBlock) { bestCandidateBlock = closestKnownBlock; if (GSRS_App.currentSettings.debugMode) console.log(`GSRS (processPage): Title belongs to known block type "${knownSelector}"`, bestCandidateBlock); break; } } if (!bestCandidateBlock) { let tempCandidate = titleEl; const MAX_FALLBACK_TRACE = 4; for (let i = 0; i < MAX_FALLBACK_TRACE && tempCandidate.parentElement; i++) { tempCandidate = tempCandidate.parentElement; if (tempCandidate.tagName === 'DIV' && GSRS_App.INTERNAL_SELECTORS.dataAttributesForCandidate.some(attr => tempCandidate.hasAttribute(attr))) { if (tempCandidate.id !== 'search' && tempCandidate.id !== 'rso' && tempCandidate.id !== 'main' && tempCandidate.tagName.toLowerCase() !== 'body' && tempCandidate.tagName.toLowerCase() !== 'html') { bestCandidateBlock = tempCandidate; if (GSRS_App.currentSettings.debugMode) console.log(`GSRS (processPage): Title fallback found block with data attribute.`, bestCandidateBlock); break; } } } } if (bestCandidateBlock) { if (bestCandidateBlock.dataset.gsrsBlockParsed === 'true') { titleEl.dataset.gsrsTitleProcessed = 'true'; return; } let isExcluded = false; if (GSRS_App.INTERNAL_SELECTORS.carouselStructureIndicator && bestCandidateBlock.querySelector(GSRS_App.INTERNAL_SELECTORS.carouselStructureIndicator)) { isExcluded = true; if (GSRS_App.currentSettings.debugMode) console.log("GSRS (processPage exclude): Carousel structure detected in block.", bestCandidateBlock); } if (!isExcluded) { const closestKPShell = bestCandidateBlock.closest(GSRS_App.INTERNAL_SELECTORS.knowledgePanelSelector); if (closestKPShell) { const isCandidateTheKPShellItself = bestCandidateBlock.matches(GSRS_App.INTERNAL_SELECTORS.knowledgePanelSelector); const candidateHasKPCoreDirectChild = GSRS_App.INTERNAL_SELECTORS.knowledgePanelCoreContentDirectChild.split(',').some(sel => bestCandidateBlock.querySelector(sel.trim())); if (isCandidateTheKPShellItself || candidateHasKPCoreDirectChild) { isExcluded = true; if (GSRS_App.currentSettings.debugMode) console.log("GSRS (processPage exclude): Knowledge panel detected.", bestCandidateBlock); } } } if (!isExcluded && isPAAContainer(bestCandidateBlock)) { isExcluded = true; if (GSRS_App.currentSettings.debugMode) console.log("GSRS (processPage exclude): PAA container block detected.", bestCandidateBlock); bestCandidateBlock.querySelectorAll(GSRS_App.currentSettings.titleSelector).forEach(t => t.dataset.gsrsTitleProcessed = 'true'); } if (!isExcluded) { potentialResultBlocks.add(bestCandidateBlock); } else { bestCandidateBlock.dataset.gsrsBlockParsed = 'true'; } } titleEl.dataset.gsrsTitleProcessed = 'true'; }); potentialResultBlocks.forEach(blockElement => { if (blockElement.dataset.gsrsBlockParsed === 'true') return; const parsedResultContainer = extractSingleResultElement(blockElement, GSRS_App.allResults.length + 1); if (parsedResultContainer?.internal) { const { internal } = parsedResultContainer; let isDuplicate = false; if (internal.rawUrl && internal.rawUrl !== "URL Extraction Failed" && internal.rawUrl !== "Extraction Failed" && !internal.rawUrl.startsWith('javascript:void(0)')) { isDuplicate = GSRS_App.allResults.some(existingItem => existingItem.internal.rawUrl === internal.rawUrl); } else if (internal.rawTitle && internal.rawTitle !== "Title Selector Failed" && internal.rawTitle !== "Extraction Failed") { isDuplicate = GSRS_App.allResults.some(existingItem => existingItem.internal.rawTitle === internal.rawTitle && (!existingItem.internal.rawUrl || ["URL Extraction Failed", "Extraction Failed"].includes(existingItem.internal.rawUrl) || existingItem.internal.rawUrl.startsWith('javascript:void'))); } if (!isDuplicate) { parsedResultContainer.display.position = GSRS_App.allResults.length + 1; GSRS_App.allResults.push(parsedResultContainer); newResultsFoundInThisPass++; } else { if (GSRS_App.currentSettings.debugMode) console.log("GSRS (processPage): Duplicate result skipped:", internal.rawTitle, internal.rawUrl); blockElement.dataset.gsrsBlockParsed = 'true'; } } else { if (!blockElement.dataset.gsrsBlockParsed) blockElement.dataset.gsrsBlockParsed = 'true'; if (GSRS_App.currentSettings.debugMode && parsedResultContainer === null && !(blockElement.id && blockElement.id.startsWith('infy-scroll-'))) { console.log("GSRS (processPage): extractSingleResultElement returned null for block, marking as parsed.", blockElement); } } }); if (GSRS_App.currentSettings.debugMode) console.log(`GSRS: processPageForResults finished for root ${rootElement === document ? "document" : "node"}. New results found in this pass: ${newResultsFoundInThisPass}`); return newResultsFoundInThisPass; }
    function clearAllPageHighlights(includeScrapeTimeHighlight = true) { if (GSRS_App.state.lastListItemHighlightedElement && document.body.contains(GSRS_App.state.lastListItemHighlightedElement)) { GSRS_App.state.lastListItemHighlightedElement.classList.remove('gsrs-list-item-page-highlight'); GSRS_App.state.lastListItemHighlightedElement.classList.remove('gsrs-context-highlight'); } GSRS_App.state.lastListItemHighlightedElement = null; document.querySelectorAll('.gsrs-context-highlight').forEach(el => el.classList.remove('gsrs-context-highlight')); clearTimeout(GSRS_App.state.contextMenuHighlightTimeout); document.querySelectorAll('.gsrs-list-item-page-highlight').forEach(el => el.classList.remove('gsrs-list-item-page-highlight')); if (includeScrapeTimeHighlight) { document.querySelectorAll('.gsrs-highlighted').forEach(el => el.classList.remove('gsrs-highlighted')); } }

    // --- MutationObserver (Now Sidelined/Disabled) ---
    function setupMutationObserver() {
        if (GSRS_App.currentSettings.debugMode) {
            console.log("GSRS: MutationObserver setup is disabled as dynamic loading support for InfyScroll is removed.");
        }
        if (GSRS_App.observer) {
            try {
                GSRS_App.observer.disconnect();
                if (GSRS_App.currentSettings.debugMode) console.log("GSRS_Debug (ObserverSetup): Disconnected any pre-existing observer instance.");
            } catch (e) { /* silent */ }
            GSRS_App.observer = null;
        }
        GSRS_App.uiManager.updateObserverStatus(false); // Set status to inactive
    }

    function init() {
        GSRS_App.settingsManager.load();
        GSRS_App.uiManager.create();
        GSRS_App.settingsManager.updateInputs();
        GSRS_App.uiManager.toggleViewMode(GSRS_App.state.currentViewMode);
        GSRS_App.uiManager.updateObserverStatus(false); // MO is inactive

        // Initialize URL change detector (now informational only for debugging/logging)
        if (GSRS_App.urlChangeDetector && typeof GSRS_App.urlChangeDetector.init === 'function') {
            GSRS_App.urlChangeDetector.init();
        } else if (GSRS_App.currentSettings.debugMode) {
            console.error("GSRS: URLChangeDetector not found or init is not a function.");
        }

        // Perform an initial scrape on load.
        // This is effectively what handleStartParse does for the first time.
        if (GSRS_App.currentSettings.debugMode) console.log("GSRS (Init): Performing initial page scrape.");
        handleStartParse(); // Scrape current page content on init

        if(GSRS_App.currentSettings.lastFilterTerm) {
            handleFilterResults();
        } else {
            // updateResultsDisplay is called within handleStartParse
        }

        setTimeout(() => {
            if (GSRS_App.currentSettings.debugMode) console.log("GSRS: Delayed initial call to updateActionButtonsState from init().");
            updateActionButtonsState();
        }, 100);

        if (GSRS_App.currentSettings.debugMode) console.log("%cGSRS: Init complete. Dynamic loading via MutationObserver/URL changes is disabled.", "color: orange; font-weight: bold;");
    }

    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();