Dynamic Include Sites Script (Protocol-Independent)

Run on default & user-defined sites using wildcard patterns (ignores protocols), with full management features.

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/526770/1670393/Dynamic%20Include%20Sites%20Script%20%28Protocol-Independent%29.js

// ==UserScript==
// @name         Site Filter (Protocol-Independent)
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  Manage allowed sites dynamically and reference this in other scripts. (Optimized)
// @author       blvdmd
// @match        *://*/*
// @noframes
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_download
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

     // Exit if the script is running in an iframe or embedded context
     if (window.self !== window.top) {
        return; // Stop execution for embedded pages
    }

    const USE_EMOJI_FOR_STATUS = true; // Configurable flag to use emoji for true/false status
    const SHOW_STATUS_ONLY_IF_TRUE = true; // Configurable flag to show status only if any value is true

    // ✅ Wait for `SCRIPT_STORAGE_KEY` to be set
    function waitForScriptStorageKey(maxWait = 1000) {
        return new Promise(resolve => {
            const startTime = Date.now();
            const interval = setInterval(() => {
                if (typeof window.SCRIPT_STORAGE_KEY !== 'undefined') {
                    clearInterval(interval);
                    resolve(window.SCRIPT_STORAGE_KEY);
                } else if (Date.now() - startTime > maxWait) {
                    clearInterval(interval);
                    console.error("🚨 SCRIPT_STORAGE_KEY is not set! Make sure your script sets it **before** @require.");
                    resolve(null);
                }
            }, 50);
        });
    }

    (async function initialize() {
        async function waitForDocumentReady() {
            if (document.readyState === "complete") return;
            return new Promise(resolve => {
                window.addEventListener("load", resolve, { once: true });
            });
        }
        
        // ✅ Wait for the script storage key
        const key = await waitForScriptStorageKey();
        if (!key) return;

        // ✅ Ensure the document is fully loaded before setting `shouldRunOnThisSite`
        await waitForDocumentReady();

        const STORAGE_KEY = `additionalSites_${key}`;

        // ====== OPTIMIZATION: CACHES ======
        const regexCache = new Map();           // Cache compiled regexes
        const patternMatchCache = {             // Cache pattern match results
            url: null,
            entry: null
        };
        let currentUrlCache = {                 // Cache current URL
            href: null,
            normalized: null
        };

        function getDefaultList() {
            return typeof window.GET_DEFAULT_LIST === "function" ? window.GET_DEFAULT_LIST() : [];
        }

        function normalizeUrl(url) {
            if (typeof url !== 'string') {
                url = String(url);
            }
            return url.replace(/^https?:\/\//, '');
        }

        // ====== OPTIMIZATION: Cached URL getter ======
        function getCurrentFullPath() {
            const currentHref = window.top.location.href;
            if (currentUrlCache.href !== currentHref) {
                currentUrlCache.href = currentHref;
                currentUrlCache.normalized = normalizeUrl(currentHref);
            }
            return currentUrlCache.normalized;
        }

        let additionalSites = GM_getValue(STORAGE_KEY, []);
        let mergedSites = buildMergedSites();

        // ====== REFACTORED: Deduplicated mergedSites building logic ======
        function buildMergedSites() {
            return [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
                if (typeof item === 'string') {
                    return { 
                        pattern: normalizeUrl(item), 
                        preProcessingRequired: false, 
                        postProcessingRequired: false, 
                        onDemandFloatingButtonRequired: false, 
                        backgroundChangeObserverRequired: false 
                    };
                }
                return { 
                    ...item, 
                    pattern: normalizeUrl(item.pattern),
                    preProcessingRequired: item.preProcessingRequired || false,
                    postProcessingRequired: item.postProcessingRequired || false,
                    onDemandFloatingButtonRequired: item.onDemandFloatingButtonRequired || false,
                    backgroundChangeObserverRequired: item.backgroundChangeObserverRequired || false
                };
            });
        }

        function refreshMergedSites() {
            mergedSites = buildMergedSites();
            // Clear caches when patterns change
            regexCache.clear();
            patternMatchCache.url = null;
            patternMatchCache.entry = null;
            buildPatternIndex();
        }

        // ====== OPTIMIZATION: Pattern Index for faster lookups ======
        const patternIndex = {
            exact: new Map(),
            suffix: new Map(),
            prefix: new Map(),
            wildcard: []
        };

        function buildPatternIndex() {
            patternIndex.exact.clear();
            patternIndex.suffix.clear();
            patternIndex.prefix.clear();
            patternIndex.wildcard = [];

            mergedSites.forEach(item => {
                const p = item.pattern;
                if (!p.includes('*')) {
                    // Exact match
                    patternIndex.exact.set(p, item);
                } else if (p.startsWith('*') && p.indexOf('*', 1) === -1) {
                    // Suffix match: *.example.com
                    patternIndex.suffix.set(p.substring(1), item);
                } else if (p.endsWith('*') && p.indexOf('*') === p.length - 1) {
                    // Prefix match: example.com/*
                    patternIndex.prefix.set(p.substring(0, p.length - 1), item);
                } else {
                    // Complex wildcard
                    patternIndex.wildcard.push(item);
                }
            });
        }

        // Initial index build
        buildPatternIndex();

        // ====== OPTIMIZATION: Cached regex with Map ======
        function wildcardToRegex(pattern) {
            // Check cache first
            if (regexCache.has(pattern)) {
                return regexCache.get(pattern);
            }
            
            // Create and cache regex
            const regex = new RegExp("^" + pattern
                    .replace(/[-[\]{}()+^$|#\s]/g, '\\$&')
                    .replace(/\./g, '\\.')
                    .replace(/\?/g, '\\?')
                    .replace(/\*/g, '.*')
                + "$");
            
            regexCache.set(pattern, regex);
            return regex;
        }

        // ====== OPTIMIZATION: Unified pattern matching with index lookup ======
        function findMatchingEntry() {
            const currentPath = getCurrentFullPath();
            
            // Return cached result if URL hasn't changed
            if (patternMatchCache.url === currentPath && patternMatchCache.entry !== null) {
                return patternMatchCache.entry;
            }

            let matchedEntry = false;

            // 1. Try exact match first (O(1))
            if (patternIndex.exact.has(currentPath)) {
                matchedEntry = patternIndex.exact.get(currentPath);
            }
            
            // 2. Try suffix matches (O(n) where n = number of suffix patterns)
            if (!matchedEntry) {
                for (const [suffix, item] of patternIndex.suffix) {
                    if (currentPath.endsWith(suffix)) {
                        matchedEntry = item;
                        break;
                    }
                }
            }
            
            // 3. Try prefix matches (O(n) where n = number of prefix patterns)
            if (!matchedEntry) {
                for (const [prefix, item] of patternIndex.prefix) {
                    if (currentPath.startsWith(prefix)) {
                        matchedEntry = item;
                        break;
                    }
                }
            }
            
            // 4. Try wildcard patterns (O(n) where n = number of complex wildcards)
            if (!matchedEntry) {
                for (const item of patternIndex.wildcard) {
                    if (wildcardToRegex(item.pattern).test(currentPath)) {
                        matchedEntry = item;
                        break;
                    }
                }
            }

            // Cache the result
            patternMatchCache.url = currentPath;
            patternMatchCache.entry = matchedEntry;
            
            return matchedEntry;
        }

        async function shouldRunOnThisSite() {
            return !!findMatchingEntry();
        }

        // ====== UTILITY: Count wildcard matches for current site ======
        function countWildcardMatches() {
            const currentPath = getCurrentFullPath();
            let count = 0;

            // Check all pattern types
            if (patternIndex.exact.has(currentPath)) count++;
            
            for (const [suffix] of patternIndex.suffix) {
                if (currentPath.endsWith(suffix)) count++;
            }
            
            for (const [prefix] of patternIndex.prefix) {
                if (currentPath.startsWith(prefix)) count++;
            }
            
            for (const item of patternIndex.wildcard) {
                if (wildcardToRegex(item.pattern).test(currentPath)) count++;
            }

            return count;
        }

        // ====== OPTIMIZATION: Debounce helper ======
        function debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        }

        // ====== HTML UI HELPER FUNCTIONS ======
        function createModal(title, content, options = {}) {
            const modal = document.createElement('div');
            modal.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.7);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 999999;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            `;

            const dialog = document.createElement('div');
            dialog.style.cssText = `
                background: white;
                border-radius: 12px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
                max-width: ${options.maxWidth || '600px'};
                width: 90%;
                max-height: 85vh;
                display: flex;
                flex-direction: column;
                overflow: hidden;
            `;

            const header = document.createElement('div');
            header.style.cssText = `
                padding: 20px;
                border-bottom: 1px solid #e0e0e0;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
            `;

            const titleEl = document.createElement('h2');
            titleEl.textContent = title;
            titleEl.style.cssText = 'margin: 0; font-size: 20px; font-weight: 600;';

            const closeBtn = document.createElement('button');
            closeBtn.innerHTML = '✕';
            closeBtn.style.cssText = `
                background: transparent;
                border: none;
                color: white;
                font-size: 24px;
                cursor: pointer;
                padding: 0;
                width: 30px;
                height: 30px;
                display: flex;
                align-items: center;
                justify-content: center;
                border-radius: 50%;
                transition: background 0.2s;
            `;

            // ====== OPTIMIZATION: Event cleanup function ======
            const closeModal = () => {
                // Clean up event listeners
                closeBtn.onmouseover = null;
                closeBtn.onmouseout = null;
                closeBtn.onclick = null;
                modal.onclick = null;
                
                document.body.removeChild(modal);
                if (options.onClose) options.onClose();
            };

            closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
            closeBtn.onmouseout = () => closeBtn.style.background = 'transparent';
            closeBtn.onclick = closeModal;

            header.appendChild(titleEl);
            header.appendChild(closeBtn);

            const body = document.createElement('div');
            body.style.cssText = `
                padding: 20px;
                overflow-y: auto;
                flex: 1;
            `;
            if (typeof content === 'string') {
                body.innerHTML = content;
            } else {
                body.appendChild(content);
            }

            dialog.appendChild(header);
            dialog.appendChild(body);
            modal.appendChild(dialog);

            // Close on backdrop click
            modal.onclick = (e) => {
                if (e.target === modal) {
                    closeModal();
                }
            };

            document.body.appendChild(modal);
            return { modal, body, closeBtn, cleanup: closeModal };
        }

        function createButton(text, onClick, style = 'primary') {
            const btn = document.createElement('button');
            btn.textContent = text;
            const baseStyle = `
                padding: 10px 20px;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
                transition: all 0.2s;
                margin: 5px;
            `;
            
            const styles = {
                primary: `${baseStyle} background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;`,
                success: `${baseStyle} background: #10b981; color: white;`,
                danger: `${baseStyle} background: #ef4444; color: white;`,
                secondary: `${baseStyle} background: #6b7280; color: white;`,
                outline: `${baseStyle} background: transparent; color: #667eea; border: 2px solid #667eea;`
            };

            btn.style.cssText = styles[style] || styles.primary;
            btn.onmouseover = () => btn.style.transform = 'scale(1.05)';
            btn.onmouseout = () => btn.style.transform = 'scale(1)';
            btn.onclick = onClick;
            return btn;
        }

        function createInput(type, value, placeholder = '') {
            const input = document.createElement(type === 'textarea' ? 'textarea' : 'input');
            if (type !== 'textarea') input.type = type;
            input.value = value || '';
            input.placeholder = placeholder;
            input.style.cssText = `
                width: 100%;
                padding: 10px;
                border: 2px solid #e0e0e0;
                border-radius: 6px;
                font-size: 14px;
                font-family: inherit;
                margin: 5px 0;
                box-sizing: border-box;
                transition: border-color 0.2s;
            `;
            input.onfocus = () => input.style.borderColor = '#667eea';
            input.onblur = () => input.style.borderColor = '#e0e0e0';
            return input;
        }

        function createCheckbox(label, checked = false) {
            const container = document.createElement('label');
            container.style.cssText = `
                display: flex;
                align-items: center;
                margin: 10px 0;
                cursor: pointer;
                user-select: none;
            `;

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.checked = checked;
            checkbox.style.cssText = `
                width: 20px;
                height: 20px;
                margin-right: 10px;
                cursor: pointer;
            `;

            const labelText = document.createElement('span');
            labelText.textContent = label;
            labelText.style.cssText = 'font-size: 14px;';

            container.appendChild(checkbox);
            container.appendChild(labelText);
            return { container, checkbox };
        }

        function formatStatus(preProcessingRequired, postProcessingRequired, onDemandFloatingButtonRequired, backgroundChangeObserverRequired) {
            if (SHOW_STATUS_ONLY_IF_TRUE && !preProcessingRequired && !postProcessingRequired && !onDemandFloatingButtonRequired && !backgroundChangeObserverRequired) {
                return '';
            }
            const preStatus = USE_EMOJI_FOR_STATUS ? (preProcessingRequired ? '✅' : '✖️') : (preProcessingRequired ? 'true' : 'false');
            const postStatus = USE_EMOJI_FOR_STATUS ? (postProcessingRequired ? '✅' : '✖️') : (postProcessingRequired ? 'true' : 'false');
            const floatingButtonStatus = USE_EMOJI_FOR_STATUS ? (onDemandFloatingButtonRequired ? '✅' : '✖️') : (onDemandFloatingButtonRequired ? 'true' : 'false');
            const backgroundObserverStatus = USE_EMOJI_FOR_STATUS ? (backgroundChangeObserverRequired ? '✅' : '✖️') : (backgroundChangeObserverRequired ? 'true' : 'false');
            return `Pre: ${preStatus}, Post: ${postStatus}, FB: ${floatingButtonStatus}, BO: ${backgroundObserverStatus}`;
        }

        // ====== MENU COMMAND 1: Add Current Site ======
        function addCurrentSiteMenu() {
            const matchCount = countWildcardMatches();
            const currentHost = window.top.location.hostname;
            const currentPath = window.top.location.pathname;
            const domainParts = currentHost.split('.');
            const baseDomain = domainParts.length > 2 ? domainParts.slice(-2).join('.') : domainParts.join('.');
            const secondLevelDomain = domainParts.length > 2 ? domainParts.slice(-2, -1)[0] : domainParts[0];
        
            // Reordered: Custom Wildcard Pattern first, then others
            const options = [
                { name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.top.location.href}`) },
                { name: `Preferred Domain Match (*${secondLevelDomain}.*)`, pattern: `*${secondLevelDomain}.*` },
                { name: `Base Hostname (*.${baseDomain}*)`, pattern: `*.${baseDomain}*` },
                { name: `Base Domain (*.${secondLevelDomain}.*)`, pattern: `*.${secondLevelDomain}.*` },
                { name: `Host Contains (*${secondLevelDomain}*)`, pattern: `*${secondLevelDomain}*` },
                { name: `Exact Path (${currentHost}${currentPath})`, pattern: normalizeUrl(`${window.top.location.href}`) }
            ];

            const content = document.createElement('div');
            
            const infoBox = document.createElement('div');
            infoBox.style.cssText = `
                background: #f0f9ff;
                border: 2px solid #0ea5e9;
                border-radius: 8px;
                padding: 15px;
                margin-bottom: 20px;
            `;
            infoBox.innerHTML = `
                <div style="font-weight: 600; color: #0c4a6e; margin-bottom: 5px;">
                    📊 Current Page Wildcard Matches: ${matchCount}
                </div>
                <div style="font-size: 13px; color: #075985;">
                    This page matches ${matchCount} existing rule${matchCount !== 1 ? 's' : ''} in your include list.
                </div>
            `;
            content.appendChild(infoBox);

            const sectionTitle = document.createElement('h3');
            sectionTitle.textContent = 'Pattern to add:';
            sectionTitle.style.cssText = 'margin: 20px 0 10px 0; font-size: 16px; color: #333;';
            content.appendChild(sectionTitle);

            let selectedOption = 0; // Default to first option (Custom Wildcard Pattern)
            const optionButtons = [];

            // Custom Wildcard Pattern input (always visible, selected by default)
            const patternInput = createInput('text', normalizeUrl(`${window.top.location.href}`), 'Enter custom wildcard pattern');
            patternInput.style.marginBottom = '12px';
            content.appendChild(patternInput);

            // Container for other pattern options (initially hidden)
            const otherPatternsContainer = document.createElement('div');
            otherPatternsContainer.style.cssText = 'display: none; margin-top: 10px;';

            // Create buttons for other patterns (indices 1-5)
            for (let index = 1; index < options.length; index++) {
                const opt = options[index];
                const optBtn = document.createElement('button');
                optBtn.textContent = opt.name;
                optBtn.style.cssText = `
                    display: block;
                    width: 100%;
                    padding: 12px;
                    margin: 8px 0;
                    border: 2px solid #e0e0e0;
                    border-radius: 8px;
                    background: white;
                    cursor: pointer;
                    text-align: left;
                    font-size: 14px;
                    transition: all 0.2s;
                `;
                optBtn.onclick = () => {
                    selectedOption = index;
                    patternInput.value = normalizeUrl(opt.pattern);
                    // Hide other patterns after selection
                    otherPatternsContainer.style.display = 'none';
                    toggleBtn.textContent = '▼ Show Other Patterns';
                };
                optionButtons.push(optBtn);
                otherPatternsContainer.appendChild(optBtn);
            }

            // Toggle button for other patterns
            const toggleBtn = document.createElement('button');
            toggleBtn.textContent = '▼ Show Other Patterns';
            toggleBtn.style.cssText = `
                width: 100%;
                padding: 10px;
                margin: 8px 0 12px 0;
                border: 2px solid #667eea;
                border-radius: 6px;
                background: white;
                color: #667eea;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
                transition: all 0.2s;
            `;
            toggleBtn.onmouseover = () => toggleBtn.style.background = '#f5f3ff';
            toggleBtn.onmouseout = () => toggleBtn.style.background = 'white';
            toggleBtn.onclick = () => {
                if (otherPatternsContainer.style.display === 'none') {
                    otherPatternsContainer.style.display = 'block';
                    toggleBtn.textContent = '▲ Hide Other Patterns';
                } else {
                    otherPatternsContainer.style.display = 'none';
                    toggleBtn.textContent = '▼ Show Other Patterns';
                }
            };
            content.appendChild(toggleBtn);
            content.appendChild(otherPatternsContainer);

            const configTitle = document.createElement('h3');
            configTitle.textContent = 'Configuration Options:';
            configTitle.style.cssText = 'margin: 25px 0 15px 0; font-size: 16px; color: #333;';
            content.appendChild(configTitle);

            const preCheck = createCheckbox('Pre-processing Required', false);
            const postCheck = createCheckbox('Post-processing Required', false);
            const floatingBtnCheck = createCheckbox('On-demand Floating Button Required', false);
            const backgroundObsCheck = createCheckbox('Background Change Observer Required', false);

            content.appendChild(preCheck.container);
            content.appendChild(postCheck.container);
            content.appendChild(floatingBtnCheck.container);
            content.appendChild(backgroundObsCheck.container);

            const buttonContainer = document.createElement('div');
            buttonContainer.style.cssText = 'margin-top: 25px; display: flex; justify-content: flex-end; gap: 10px;';

            const { modal, cleanup } = createModal('➕ Add Current Site to Include List', content, { maxWidth: '650px' });

            const cancelBtn = createButton('Cancel', () => {
                cleanup();
            }, 'secondary');

            const addBtn = createButton('Add Site', () => {
                // Get pattern from input (now always from patternInput since it's the primary control)
                const pattern = normalizeUrl(patternInput.value.trim());
                if (!pattern) {
                    alert('⚠️ Invalid pattern. Operation canceled.');
                    return;
                }

                const entry = { 
                    pattern, 
                    preProcessingRequired: preCheck.checkbox.checked,
                    postProcessingRequired: postCheck.checkbox.checked,
                    onDemandFloatingButtonRequired: floatingBtnCheck.checkbox.checked,
                    backgroundChangeObserverRequired: backgroundObsCheck.checkbox.checked
                };
                
                if (!additionalSites.some(item => item.pattern === pattern)) {
                    additionalSites.push(entry);
                    GM_setValue(STORAGE_KEY, additionalSites);
                    refreshMergedSites();
                    cleanup();
                    alert(`✅ Added site with pattern: ${pattern}`);
                } else {
                    alert(`⚠️ Pattern "${pattern}" is already in the list.`);
                }
            }, 'success');

            buttonContainer.appendChild(cancelBtn);
            buttonContainer.appendChild(addBtn);
            content.appendChild(buttonContainer);
        }

        // ====== MENU COMMAND 2: Advanced Management ======
        function showAdvancedManagement() {
            const content = document.createElement('div');

            // Get current site for matching
            const currentFullPath = getCurrentFullPath();

            // Compact stats header
            const stats = document.createElement('div');
            stats.style.cssText = `
                background: #667eea;
                color: white;
                padding: 10px 15px;
                border-radius: 6px;
                margin-bottom: 12px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            `;
            stats.innerHTML = `
                <div><span style="font-size: 20px; font-weight: 700;">${additionalSites.length}</span> <span style="font-size: 13px;">Sites</span></div>
                <div style="font-size: 12px; opacity: 0.9;">${countWildcardMatches()} matching current page</div>
            `;
            content.appendChild(stats);

            // Compact button bar
            const buttonBar = document.createElement('div');
            buttonBar.style.cssText = `
                display: flex;
                gap: 8px;
                margin-bottom: 12px;
                flex-wrap: wrap;
            `;

            const refreshList = () => {
                document.body.querySelectorAll('[data-modal-advanced]').forEach(m => {
                    // Clean up before removing
                    m.onclick = null;
                    document.body.removeChild(m);
                });
                showAdvancedManagement();
            };

            const compactBtn = (text, onClick, style) => {
                const btn = createButton(text, onClick, style);
                btn.style.padding = '6px 12px';
                btn.style.fontSize = '13px';
                btn.style.margin = '0';
                return btn;
            };

            buttonBar.appendChild(compactBtn('📤 Export', exportAdditionalSites, 'primary'));
            buttonBar.appendChild(compactBtn('📥 Import', () => {
                importAdditionalSites(refreshList);
            }, 'primary'));
            buttonBar.appendChild(compactBtn('🗑️ Clear All', () => {
                clearAllEntriesConfirm(refreshList);
            }, 'danger'));

            content.appendChild(buttonBar);

            if (additionalSites.length === 0) {
                const emptyMsg = document.createElement('div');
                emptyMsg.style.cssText = `
                    text-align: center;
                    padding: 30px;
                    color: #9ca3af;
                    font-size: 14px;
                `;
                emptyMsg.textContent = 'No user-defined sites added yet.';
                content.appendChild(emptyMsg);
            } else {
                // Search/Filter input
                const searchContainer = document.createElement('div');
                searchContainer.style.cssText = 'margin-bottom: 12px;';
                
                const searchInput = createInput('text', '', '🔍 Search patterns...');
                searchInput.style.margin = '0';
                searchInput.style.padding = '8px 12px';
                searchInput.style.fontSize = '13px';
                searchInput.readOnly = false;
                searchInput.disabled = false;
                searchInput.autocomplete = 'off';
                searchContainer.appendChild(searchInput);
                content.appendChild(searchContainer);

                // ====== OPTIMIZATION: Memory-efficient sorting with minimal augmentation ======
                const sortedSites = additionalSites.map((item, index) => {
                    // Test match using optimized index lookup
                    let isMatch = false;
                    const p = item.pattern;
                    
                    if (p === currentFullPath) {
                        isMatch = true;
                    } else if (p.startsWith('*') && p.indexOf('*', 1) === -1) {
                        isMatch = currentFullPath.endsWith(p.substring(1));
                    } else if (p.endsWith('*') && p.indexOf('*') === p.length - 1) {
                        isMatch = currentFullPath.startsWith(p.substring(0, p.length - 1));
                    } else if (p.includes('*')) {
                        isMatch = wildcardToRegex(p).test(currentFullPath);
                    }
                    
                    return {
                        item,
                        originalIndex: index,
                        isMatch
                    };
                }).sort((a, b) => {
                    // First by match status (matching first)
                    if (a.isMatch !== b.isMatch) return b.isMatch ? 1 : -1;
                    // Then alphabetically by pattern
                    return a.item.pattern.localeCompare(b.item.pattern);
                });

                // ====== OPTIMIZATION: Virtual scrolling for large lists ======
                const listContainer = document.createElement('div');
                listContainer.style.cssText = `
                    max-height: 450px;
                    overflow-y: auto;
                    border: 1px solid #e0e0e0;
                    border-radius: 6px;
                `;

                // Track rendered items for virtual scrolling
                let allCards = [];

                // Function to render a site entry
                function renderSiteEntry(siteData) {
                    const item = siteData.item;
                    const siteCard = document.createElement('div');
                    siteCard.style.cssText = `
                        padding: 8px 12px;
                        border-bottom: 1px solid #f0f0f0;
                        background: ${siteData.isMatch ? '#f0f9ff' : 'white'};
                    `;
                    siteCard.setAttribute('data-pattern', item.pattern.toLowerCase());

                    const topRow = document.createElement('div');
                    topRow.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;';

                    const patternDiv = document.createElement('div');
                    patternDiv.style.cssText = `
                        font-weight: 600;
                        color: #1f2937;
                        word-break: break-all;
                        font-size: 13px;
                        flex: 1;
                    `;
                    patternDiv.textContent = (siteData.isMatch ? '✓ ' : '') + item.pattern;

                    const actionBar = document.createElement('div');
                    actionBar.style.cssText = 'display: flex; gap: 6px; flex-shrink: 0; margin-left: 10px;';

                    const editBtn = document.createElement('button');
                    editBtn.textContent = '✏️';
                    editBtn.title = 'Edit';
                    editBtn.style.cssText = `
                        padding: 4px 8px;
                        border: 1px solid #d1d5db;
                        background: white;
                        border-radius: 4px;
                        cursor: pointer;
                        font-size: 12px;
                    `;
                    editBtn.onclick = () => editEntryDialog(siteData.originalIndex, refreshList);

                    const deleteBtn = document.createElement('button');
                    deleteBtn.textContent = '🗑️';
                    deleteBtn.title = 'Delete';
                    deleteBtn.style.cssText = `
                        padding: 4px 8px;
                        border: 1px solid #fecaca;
                        background: #fee2e2;
                        color: #991b1b;
                        border-radius: 4px;
                        cursor: pointer;
                        font-size: 12px;
                    `;
                    deleteBtn.onclick = () => deleteEntryConfirm(siteData.originalIndex, refreshList);

                    actionBar.appendChild(editBtn);
                    actionBar.appendChild(deleteBtn);

                    topRow.appendChild(patternDiv);
                    topRow.appendChild(actionBar);

                    const statusDiv = document.createElement('div');
                    statusDiv.style.cssText = 'font-size: 11px; color: #6b7280;';
                    const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired, item.onDemandFloatingButtonRequired, item.backgroundChangeObserverRequired);
                    statusDiv.textContent = status || 'Default settings';

                    siteCard.appendChild(topRow);
                    if (status) siteCard.appendChild(statusDiv);

                    return siteCard;
                }

                // Render all sorted sites
                sortedSites.forEach(siteData => {
                    const card = renderSiteEntry(siteData);
                    allCards.push(card);
                    listContainer.appendChild(card);
                });

                content.appendChild(listContainer);

                // ====== OPTIMIZATION: Debounced search filter ======
                const debouncedFilter = debounce((searchTerm) => {
                    const lowerSearchTerm = searchTerm.toLowerCase();
                    let visibleCount = 0;
                    
                    allCards.forEach(card => {
                        const pattern = card.getAttribute('data-pattern');
                        if (pattern.includes(lowerSearchTerm)) {
                            card.style.display = '';
                            visibleCount++;
                        } else {
                            card.style.display = 'none';
                        }
                    });

                    // Show "no results" message if needed
                    let noResultsMsg = listContainer.querySelector('[data-no-results]');
                    if (visibleCount === 0 && searchTerm) {
                        if (!noResultsMsg) {
                            noResultsMsg = document.createElement('div');
                            noResultsMsg.setAttribute('data-no-results', 'true');
                            noResultsMsg.style.cssText = 'padding: 20px; text-align: center; color: #9ca3af; font-size: 13px;';
                            noResultsMsg.textContent = 'No patterns match your search';
                            listContainer.appendChild(noResultsMsg);
                        }
                    } else if (noResultsMsg) {
                        noResultsMsg.remove();
                    }
                }, 150); // 150ms debounce

                searchInput.oninput = () => {
                    debouncedFilter(searchInput.value);
                };
            }

            const { modal } = createModal('⚙️ IncludeSites-Advanced', content, { maxWidth: '700px' });
            modal.setAttribute('data-modal-advanced', 'true');
        }

        function editEntryDialog(index, onComplete) {
            const entry = additionalSites[index];
            const content = document.createElement('div');

            const label1 = document.createElement('label');
            label1.textContent = 'Pattern:';
            label1.style.cssText = 'display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 600; color: #374151;';
            content.appendChild(label1);

            const patternInput = createInput('text', entry.pattern, 'Enter pattern');
            content.appendChild(patternInput);

            const configTitle = document.createElement('h3');
            configTitle.textContent = 'Configuration Options:';
            configTitle.style.cssText = 'margin: 25px 0 15px 0; font-size: 16px; color: #333;';
            content.appendChild(configTitle);

            const preCheck = createCheckbox('Pre-processing Required', entry.preProcessingRequired);
            const postCheck = createCheckbox('Post-processing Required', entry.postProcessingRequired);
            const floatingBtnCheck = createCheckbox('On-demand Floating Button Required', entry.onDemandFloatingButtonRequired);
            const backgroundObsCheck = createCheckbox('Background Change Observer Required', entry.backgroundChangeObserverRequired);

            content.appendChild(preCheck.container);
            content.appendChild(postCheck.container);
            content.appendChild(floatingBtnCheck.container);
            content.appendChild(backgroundObsCheck.container);

            const buttonContainer = document.createElement('div');
            buttonContainer.style.cssText = 'margin-top: 25px; display: flex; justify-content: flex-end; gap: 10px;';

            const { modal, cleanup } = createModal('✏️ Edit Entry', content, { maxWidth: '600px' });

            const cancelBtn = createButton('Cancel', () => {
                cleanup();
            }, 'secondary');

            const saveBtn = createButton('Save Changes', () => {
                const newPattern = normalizeUrl(patternInput.value.trim());
                if (!newPattern) {
                    alert('⚠️ Invalid pattern. Operation canceled.');
                    return;
                }

                entry.pattern = newPattern;
                entry.preProcessingRequired = preCheck.checkbox.checked;
                entry.postProcessingRequired = postCheck.checkbox.checked;
                entry.onDemandFloatingButtonRequired = floatingBtnCheck.checkbox.checked;
                entry.backgroundChangeObserverRequired = backgroundObsCheck.checkbox.checked;

                GM_setValue(STORAGE_KEY, additionalSites);
                refreshMergedSites();
                cleanup();
                alert('✅ Entry updated successfully.');
                if (onComplete) onComplete();
            }, 'success');

            buttonContainer.appendChild(cancelBtn);
            buttonContainer.appendChild(saveBtn);
            content.appendChild(buttonContainer);
        }

        function deleteEntryConfirm(index, onComplete) {
            const entry = additionalSites[index];
            if (confirm(`🗑️ Are you sure you want to delete this entry?\n\nPattern: ${entry.pattern}`)) {
                additionalSites.splice(index, 1);
                GM_setValue(STORAGE_KEY, additionalSites);
                refreshMergedSites();
                alert('✅ Entry deleted successfully.');
                if (onComplete) onComplete();
            }
        }

        function clearAllEntriesConfirm(onComplete) {
            if (additionalSites.length === 0) {
                alert('⚠️ No user-defined entries to clear.');
                return;
            }
            if (confirm(`🚨 You have ${additionalSites.length} entries. Clear all?`)) {
                additionalSites = [];
                GM_setValue(STORAGE_KEY, additionalSites);
                refreshMergedSites();
                alert('✅ All user-defined entries cleared.');
                if (onComplete) onComplete();
            }
        }

        function exportAdditionalSites() {
            const data = JSON.stringify(additionalSites, null, 2);
            const blob = new Blob([data], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'additionalSites_backup.json';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            alert('📤 Additional sites exported as JSON.');
        }

        function importAdditionalSites(onComplete) {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json';
            input.style.display = 'none';
            input.onchange = event => {
                const reader = new FileReader();
                reader.onload = e => {
                    try {
                        const importedData = JSON.parse(e.target.result);
                        if (Array.isArray(importedData)) {
                            additionalSites = importedData.map(item => {
                                if (typeof item === 'string') {
                                    return {
                                        pattern: normalizeUrl(item),
                                        preProcessingRequired: false,
                                        postProcessingRequired: false,
                                        onDemandFloatingButtonRequired: false,
                                        backgroundChangeObserverRequired: false
                                    };
                                } else if (typeof item === 'object' && item.pattern) {
                                    return { 
                                        pattern: normalizeUrl(item.pattern),
                                        preProcessingRequired: item.preProcessingRequired || false,
                                        postProcessingRequired: item.postProcessingRequired || false,
                                        onDemandFloatingButtonRequired: item.onDemandFloatingButtonRequired || false,
                                        backgroundChangeObserverRequired: item.backgroundChangeObserverRequired || false
                                    };
                                }
                                throw new Error('Invalid data format');
                            });
                            GM_setValue(STORAGE_KEY, additionalSites);
                            refreshMergedSites();
                            alert('📥 Sites imported successfully.');
                            if (onComplete) onComplete();
                        } else {
                            throw new Error('Invalid data format');
                        }
                    } catch (error) {
                        alert('❌ Failed to import sites: ' + error.message);
                    }
                };
                reader.readAsText(event.target.files[0]);
            };
            document.body.appendChild(input);
            input.click();
            document.body.removeChild(input);
        }

        // ====== REGISTER MENU COMMANDS (Simplified to 2) ======
        GM_registerMenuCommand(`➕ Add Current Site to Include List (Included via ${countWildcardMatches()} wildcard matches)`, addCurrentSiteMenu);
        GM_registerMenuCommand("⚙️ IncludeSites-Advanced (View/Edit/Delete/Import/Export)", showAdvancedManagement);

        // ====== EXPOSE PUBLIC API (OPTIMIZED with unified pattern matching) ======
        window.shouldRunOnThisSite = shouldRunOnThisSite;
        
        window.isPreProcessingRequired = function() {
            const entry = findMatchingEntry();
            return entry ? entry.preProcessingRequired : false;
        };
        
        window.isPostProcessingRequired = function() {
            const entry = findMatchingEntry();
            return entry ? entry.postProcessingRequired : false;
        };
        
        window.isOnDemandFloatingButtonRequired = function() {
            const entry = findMatchingEntry();
            return entry ? entry.onDemandFloatingButtonRequired : false;
        };

        window.isBackgroundChangeObserverRequired = function() {
            const entry = findMatchingEntry();
            return entry ? entry.backgroundChangeObserverRequired : false;
        };
    })();
})();

//To use this in another script use @require

// // @run-at       document-end
// // ==/UserScript==

// window.SCRIPT_STORAGE_KEY = "magnetLinkHashChecker"; // UNIQUE STORAGE KEY


// window.GET_DEFAULT_LIST = function() {
//      return [
//         { pattern: "*1337x.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
//         { pattern: "*yts.*", preProcessingRequired: true, postProcessingRequired: true, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
//         { pattern: "*torrentgalaxy.*", preProcessingRequired: false, postProcessingRequired: true, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
//         { pattern: "*bitsearch.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
//         { pattern: "*thepiratebay.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
//         { pattern: "*ext.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false }
//     ];
// };


// (async function () {
//     'use strict';

//     // ✅ Wait until `shouldRunOnThisSite` is available
//     while (typeof shouldRunOnThisSite === 'undefined') {
//         await new Promise(resolve => setTimeout(resolve, 50));
//     }

//     if (!(await shouldRunOnThisSite())) return;
//     //alert("running");

//     console.log("Pre-Customization enabled for this site: " + isPreProcessingRequired() );
//     console.log("Post-Customization enabled for this site: " + isPostProcessingRequired() );

//     const OFFCLOUD_CACHE_API_URL = 'https://offcloud.com/api/cache';