// ==UserScript==
// @name         종합 웹 크롤러 8.6 초격차 개선 버전
// @namespace    http://tampermonkey.net/
// @version      9.1.0
// @description  모듈화된 고급 기능을 갖춘 웹 크롤러
// @author       YourName
// @match        *://www.google.com/search*
// @match        *://www.bing.com/search*
// @match        *://search.daum.net/search*
// @match        *://search.naver.com/search*
// @match        *://search.yahoo.com/search*
// @match        *://duckduckgo.com/*
// @match        *://www.startpage.com/do/search*
// @match        *://www.ecosia.org/search*
// @match        *://www.qwant.com/*
// @match        *://ahmia.fi/*
// @match        *://torchsearch.de/*
// @match        *://notracker.org/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require      https://cdn.jsdelivr.net/npm/chart.js
// ==/UserScript==

(function() {
    'use strict';

    /**
     * 유틸리티 모듈
     */
    const Utils = {
        escapeHTML: function(str) {
            if (!str) return '';
            return str.replace(/&/g, "&amp;")
                      .replace(/</g, "&lt;")
                      .replace(/>/g, "&gt;")
                      .replace(/"/g, "&quot;")
                      .replace(/'/g, "&#039;");
        },
        extractDomain: function(url) {
            try {
                const urlObj = new URL(url);
                return urlObj.hostname.replace('www.', '');
            } catch (e) {
                return null;
            }
        },
        capitalizeFirstLetter: function(string) {
            if (!string) return '';
            return string.charAt(0).toUpperCase() + string.slice(1);
        },
        formatTime: function(seconds) {
            const hrs = String(Math.floor(seconds / 3600)).padStart(2, '0');
            const mins = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
            const secs = String(seconds % 60).padStart(2, '0');
            return `${hrs}:${mins}:${secs}`;
        },
        toastNotification: function(message, title = '웹 크롤러 8.6') {
            GM_notification({
                text: message,
                title: title,
                timeout: 3000
            });
        }
    };

    /**
     * 설정 관리 모듈
     */
    const Settings = {
        keys: {
            searchEngine: 'searchEngine',
            whoisApiKey: 'whoisApiKey',
            toggleHTML: 'toggleHTML',
            toggleImages: 'toggleImages',
            toggleAds: 'toggleAds',
            toggleTechStack: 'toggleTechStack',
            toggleOpenGraph: 'toggleOpenGraph',
            toggleSecurityHeaders: 'toggleSecurityHeaders',
            toggleDomainAge: 'toggleDomainAge',
            toggleBacklinks: 'toggleBacklinks',
            togglePageStructure: 'togglePageStructure',
            duplicateThreshold: 'duplicateThreshold',
            retryAttempts: 'retryAttempts',
            filterInput: 'filterInput',
            excludeDomainsInput: 'excludeDomainsInput',
            crawlerLogs: 'crawlerLogs',
            crawlerData: 'crawlerData'
        },
        load: function() {
            return {
                searchEngine: GM_getValue(this.keys.searchEngine, 'google'),
                whoisApiKey: GM_getValue(this.keys.whoisApiKey, 'YOUR_JSONWHOIS_API_KEY'),
                toggleHTML: GM_getValue(this.keys.toggleHTML, true),
                toggleImages: GM_getValue(this.keys.toggleImages, true),
                toggleAds: GM_getValue(this.keys.toggleAds, true),
                toggleTechStack: GM_getValue(this.keys.toggleTechStack, true),
                toggleOpenGraph: GM_getValue(this.keys.toggleOpenGraph, true),
                toggleSecurityHeaders: GM_getValue(this.keys.toggleSecurityHeaders, true),
                toggleDomainAge: GM_getValue(this.keys.toggleDomainAge, true),
                toggleBacklinks: GM_getValue(this.keys.toggleBacklinks, false),
                togglePageStructure: GM_getValue(this.keys.togglePageStructure, false),
                duplicateThreshold: GM_getValue(this.keys.duplicateThreshold, 2),
                retryAttempts: GM_getValue(this.keys.retryAttempts, 3),
                filterInput: GM_getValue(this.keys.filterInput, ''),
                excludeDomainsInput: GM_getValue(this.keys.excludeDomainsInput, ''),
            };
        },
        save: function(settings) {
            GM_setValue(this.keys.searchEngine, settings.searchEngine);
            GM_setValue(this.keys.whoisApiKey, settings.whoisApiKey);
            GM_setValue(this.keys.toggleHTML, settings.toggleHTML);
            GM_setValue(this.keys.toggleImages, settings.toggleImages);
            GM_setValue(this.keys.toggleAds, settings.toggleAds);
            GM_setValue(this.keys.toggleTechStack, settings.toggleTechStack);
            GM_setValue(this.keys.toggleOpenGraph, settings.toggleOpenGraph);
            GM_setValue(this.keys.toggleSecurityHeaders, settings.toggleSecurityHeaders);
            GM_setValue(this.keys.toggleDomainAge, settings.toggleDomainAge);
            GM_setValue(this.keys.toggleBacklinks, settings.toggleBacklinks);
            GM_setValue(this.keys.togglePageStructure, settings.togglePageStructure);
            GM_setValue(this.keys.duplicateThreshold, settings.duplicateThreshold);
            GM_setValue(this.keys.retryAttempts, settings.retryAttempts);
            GM_setValue(this.keys.filterInput, settings.filterInput);
            GM_setValue(this.keys.excludeDomainsInput, settings.excludeDomainsInput);
        }
    };

    /**
     * 로그 관리 모듈
     */
    const Logger = {
        logContainer: null,
        logMessages: [],
        init: function(container) {
            this.logContainer = container;
            this.loadLogs();
        },
        loadLogs: function() {
            const storedLogs = GM_getValue(Settings.keys.crawlerLogs, []);
            if (storedLogs.length > 0) {
                this.logMessages = JSON.parse(storedLogs);
                this.render();
            }
        },
        render: function() {
            this.logContainer.innerHTML = `<strong>로그:</strong><br>` + this.logMessages.join('<br>');
            this.logContainer.scrollTop = this.logContainer.scrollHeight;
        },
        add: function(message) {
            const time = new Date().toLocaleTimeString();
            const entry = `[${time}] ${message}`;
            this.logMessages.push(entry);
            this.render();
            GM_setValue(Settings.keys.crawlerLogs, JSON.stringify(this.logMessages));
        },
        clear: function() {
            this.logMessages = [];
            this.render();
            GM_setValue(Settings.keys.crawlerLogs, JSON.stringify(this.logMessages));
        }
    };

    /**
     * UI 관리 모듈
     */
    const UI = {
        container: null,
        elements: {},
        tooltip: null,
        init: function(htmlContent) {
            this.container = document.createElement('div');
            this.container.id = 'crawler-container';
            this.container.innerHTML = htmlContent;
            document.body.appendChild(this.container);

            this.setupDraggable();
            this.setupTabs();
            this.bindElements();
            this.setupTooltip();
        },
        setupDraggable: function() {
            const header = this.container.querySelector('header');
            const closeBtn = this.container.querySelector('.close-btn');
            let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

            header.onmousedown = dragMouseDown;
            closeBtn.onclick = () => {
                this.container.style.display = 'none';
                Logger.add('🗑 크롤러 창을 닫았습니다.');
            };

            function dragMouseDown(e) {
                e = e || window.event;
                e.preventDefault();
                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;
                UI.container.style.top = (UI.container.offsetTop - pos2) + "px";
                UI.container.style.left = (UI.container.offsetLeft - pos1) + "px";
            }

            function closeDragElement() {
                document.onmouseup = null;
                document.onmousemove = null;
            }
        },
        setupTabs: function() {
            const tabs = this.container.querySelectorAll('.tab');
            const tabContents = this.container.querySelectorAll('.tab-content');

            tabs.forEach(tab => {
                tab.addEventListener('click', () => {
                    tabs.forEach(t => t.classList.remove('active'));
                    tab.classList.add('active');

                    const target = tab.getAttribute('data-tab');
                    tabContents.forEach(content => {
                        content.classList.toggle('active', content.id === target);
                    });

                    if (target === 'analytics') {
                        Crawler.toggleAdvancedResults();
                    }
                });
            });
        },
        bindElements: function() {
            this.elements = {
                startBtn: this.container.querySelector('#startBtn'),
                pauseBtn: this.container.querySelector('#pauseBtn'),
                stopBtn: this.container.querySelector('#stopBtn'),
                resetBtn: this.container.querySelector('#resetBtn'),
                exportBtn: this.container.querySelector('#exportBtn'),
                clearLogsBtn: this.container.querySelector('#clearLogsBtn'),
                progressBar: this.container.querySelector('#progressBar'),
                progressText: this.container.querySelector('#progressText'),
                resultsBody: this.container.querySelector('#resultsBody'),
                logContainer: this.container.querySelector('#log'),
                currentPageElem: this.container.querySelector('#currentPage'),
                totalPagesElem: this.container.querySelector('#totalPages'),
                toggleHTML: this.container.querySelector('#toggleHTML'),
                toggleImages: this.container.querySelector('#toggleImages'),
                toggleAds: this.container.querySelector('#toggleAds'),
                toggleTechStack: this.container.querySelector('#toggleTechStack'),
                toggleOpenGraph: this.container.querySelector('#toggleOpenGraph'),
                toggleSecurityHeaders: this.container.querySelector('#toggleSecurityHeaders'),
                toggleDomainAge: this.container.querySelector('#toggleDomainAge'),
                toggleBacklinks: this.container.querySelector('#toggleBacklinks'),
                togglePageStructure: this.container.querySelector('#togglePageStructure'),
                filterInput: this.container.querySelector('#filterInput'),
                excludeDomainsInput: this.container.querySelector('#excludeDomainsInput'),
                duplicateThresholdInput: this.container.querySelector('#duplicateThreshold'),
                retryAttemptsInput: this.container.querySelector('#retryAttempts'),
                visitedCountElem: this.container.querySelector('#visitedCount'),
                elapsedTimeElem: this.container.querySelector('#elapsedTime'),
                remainingTimeElem: this.container.querySelector('#remainingTime'),
                crawlChart: this.container.querySelector('#crawlChart'),
                loadTimeChart: this.container.querySelector('#loadTimeChart'),
                domainDistributionChart: this.container.querySelector('#domainDistributionChart'),
                errorRateChart: this.container.querySelector('#errorRateChart'),
                cumulativeVisitsChart: this.container.querySelector('#cumulativeVisitsChart'),
                keywordDistributionChart: this.container.querySelector('#keywordDistributionChart'),
                languageDistributionChart: this.container.querySelector('#languageDistributionChart'),
                techStackChart: this.container.querySelector('#techStackChart'),
                domainAgeChart: this.container.querySelector('#domainAgeChart'),
                backlinksChart: this.container.querySelector('#backlinksChart'),
                pageStructureChart: this.container.querySelector('#pageStructureChart'),
                searchEngineSelect: this.container.querySelector('#searchEngineSelect'),
                searchResultsInput: this.container.querySelector('#searchResultsInput'),
                whoisModal: this.container.querySelector('#whois-modal'),
                whoisContent: this.container.querySelector('#whoisContent'),
                closeModalBtn: this.container.querySelector('.close-modal-btn'),
                whoisApiKeyInput: this.container.querySelector('#whoisApiKey')
            };
        },
        setupTooltip: function() {
            this.tooltip = document.createElement('div');
            this.tooltip.className = 'preview-tooltip';
            document.body.appendChild(this.tooltip);
        },
        showTooltip: function(event, content) {
            this.tooltip.style.left = (event.pageX + 15) + 'px';
            this.tooltip.style.top = (event.pageY + 15) + 'px';
            this.tooltip.style.display = 'block';
            this.tooltip.innerHTML = content;
        },
        hideTooltip: function() {
            this.tooltip.style.display = 'none';
            this.tooltip.innerHTML = '';
        }
    };

    /**
     * 크롤링 및 데이터 관리 모듈
     */
    const Crawler = {
        state: {
            isRunning: false,
            isPaused: false,
            currentPage: 1,
            totalPages: 0,
            results: [],
            searchQuery: '',
            searchEngine: 'google',
            visitedLinks: new Map(),
            filterPatterns: [],
            excludeDomains: [],
            duplicateThreshold: 2,
            retryAttempts: 3,
            startTime: null,
            elapsedInterval: null,
            charts: {}
        },
        init: function() {
            this.loadSettings();
            this.setupCharts();
            this.bindEvents();
            Logger.init(UI.elements.logContainer);
        },
        loadSettings: function() {
            const settings = Settings.load();
            UI.elements.searchEngineSelect.value = settings.searchEngine;
            UI.elements.whoisApiKeyInput.value = settings.whoisApiKey;
            UI.elements.toggleHTML.checked = settings.toggleHTML;
            UI.elements.toggleImages.checked = settings.toggleImages;
            UI.elements.toggleAds.checked = settings.toggleAds;
            UI.elements.toggleTechStack.checked = settings.toggleTechStack;
            UI.elements.toggleOpenGraph.checked = settings.toggleOpenGraph;
            UI.elements.toggleSecurityHeaders.checked = settings.toggleSecurityHeaders;
            UI.elements.toggleDomainAge.checked = settings.toggleDomainAge;
            UI.elements.toggleBacklinks.checked = settings.toggleBacklinks;
            UI.elements.togglePageStructure.checked = settings.togglePageStructure;
            UI.elements.duplicateThresholdInput.value = settings.duplicateThreshold;
            UI.elements.retryAttemptsInput.value = settings.retryAttempts;
            UI.elements.filterInput.value = settings.filterInput;
            UI.elements.excludeDomainsInput.value = settings.excludeDomainsInput;

            this.state.searchEngine = settings.searchEngine;
            this.state.duplicateThreshold = settings.duplicateThreshold;
            this.state.retryAttempts = settings.retryAttempts;
            this.updateFilterPatterns();
            this.updateExcludeDomains();
        },
        saveSettings: function() {
            const settings = {
                searchEngine: this.state.searchEngine,
                whoisApiKey: UI.elements.whoisApiKeyInput.value.trim(),
                toggleHTML: UI.elements.toggleHTML.checked,
                toggleImages: UI.elements.toggleImages.checked,
                toggleAds: UI.elements.toggleAds.checked,
                toggleTechStack: UI.elements.toggleTechStack.checked,
                toggleOpenGraph: UI.elements.toggleOpenGraph.checked,
                toggleSecurityHeaders: UI.elements.toggleSecurityHeaders.checked,
                toggleDomainAge: UI.elements.toggleDomainAge.checked,
                toggleBacklinks: UI.elements.toggleBacklinks.checked,
                togglePageStructure: UI.elements.togglePageStructure.checked,
                duplicateThreshold: this.state.duplicateThreshold,
                retryAttempts: this.state.retryAttempts,
                filterInput: this.state.filterPatterns.map(regex => regex.toString()).join(', '),
                excludeDomainsInput: this.state.excludeDomains.join(', ')
            };
            Settings.save(settings);
        },
        bindEvents: function() {
            UI.elements.startBtn.addEventListener('click', this.startCrawling.bind(this));
            UI.elements.pauseBtn.addEventListener('click', this.togglePause.bind(this));
            UI.elements.stopBtn.addEventListener('click', this.stopCrawling.bind(this));
            UI.elements.resetBtn.addEventListener('click', this.resetCrawling.bind(this));
            UI.elements.exportBtn.addEventListener('click', this.exportResults.bind(this));
            UI.elements.clearLogsBtn.addEventListener('click', Logger.clear.bind(Logger));
            UI.elements.filterInput.addEventListener('input', this.updateFilterPatterns.bind(this));
            UI.elements.excludeDomainsInput.addEventListener('input', this.updateExcludeDomains.bind(this));
            UI.elements.duplicateThresholdInput.addEventListener('change', this.updateDuplicateThreshold.bind(this));
            UI.elements.retryAttemptsInput.addEventListener('change', this.updateRetryAttempts.bind(this));
            UI.elements.searchEngineSelect.addEventListener('change', this.updateSearchEngine.bind(this));
            UI.elements.searchResultsInput.addEventListener('input', this.filterResults.bind(this));
            UI.elements.closeModalBtn.addEventListener('click', this.closeWhoisModal.bind(this));
            UI.elements.whoisApiKeyInput.addEventListener('change', this.updateWhoisApiKey.bind(this));

            window.addEventListener('beforeunload', this.resetCrawling.bind(this));
        },
        setupCharts: function() {
            this.state.charts = {
                crawlChart: new Chart(UI.elements.crawlChart.getContext('2d'), {
                    type: 'bar',
                    data: {
                        labels: [],
                        datasets: [{
                            label: '방문 횟수',
                            data: [],
                            backgroundColor: 'rgba(33, 150, 243, 0.6)',
                            borderColor: 'rgba(33, 150, 243, 1)',
                            borderWidth: 1
                        },
                        {
                            label: '평균 페이지 로딩 시간 (ms)',
                            data: [],
                            backgroundColor: 'rgba(76, 175, 80, 0.6)',
                            borderColor: 'rgba(76, 175, 80, 1)',
                            borderWidth: 1
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        scales: {
                            y: {
                                beginAtZero: true,
                                ticks: { precision: 0 }
                            }
                        },
                        plugins: {
                            legend: { display: true, position: 'top' },
                            tooltip: { enabled: true }
                        }
                    }
                }),
                // 다른 차트들도 유사하게 초기화
                // 예: loadTimeChart, domainDistributionChart 등
                loadTimeChart: new Chart(UI.elements.loadTimeChart.getContext('2d'), { /* 설정 */ }),
                domainDistributionChart: new Chart(UI.elements.domainDistributionChart.getContext('2d'), { /* 설정 */ }),
                errorRateChart: new Chart(UI.elements.errorRateChart.getContext('2d'), { /* 설정 */ }),
                cumulativeVisitsChart: new Chart(UI.elements.cumulativeVisitsChart.getContext('2d'), { /* 설정 */ }),
                keywordDistributionChart: new Chart(UI.elements.keywordDistributionChart.getContext('2d'), { /* 설정 */ }),
                languageDistributionChart: new Chart(UI.elements.languageDistributionChart.getContext('2d'), { /* 설정 */ }),
                techStackChart: new Chart(UI.elements.techStackChart.getContext('2d'), { /* 설정 */ }),
                domainAgeChart: new Chart(UI.elements.domainAgeChart.getContext('2d'), { /* 설정 */ }),
                backlinksChart: UI.elements.backlinksChart ? new Chart(UI.elements.backlinksChart.getContext('2d'), { /* 설정 */ }) : null,
                pageStructureChart: UI.elements.pageStructureChart ? new Chart(UI.elements.pageStructureChart.getContext('2d'), { /* 설정 */ }) : null
            };
        },
        bindChartEvents: function() {
            // 차트 업데이트 관련 이벤트가 필요하면 추가
        },
        toggleAdvancedResults: function() {
            const backlinksSection = document.getElementById('backlinksSection');
            const pageStructureSection = document.getElementById('pageStructureSection');
            const backlinksChartContainer = document.getElementById('backlinksChartContainer');
            const pageStructureChartContainer = document.getElementById('pageStructureChartContainer');

            if (UI.elements.toggleBacklinks.checked) {
                backlinksSection.style.display = 'block';
                backlinksChartContainer.style.display = 'block';
            } else {
                backlinksSection.style.display = 'none';
                backlinksChartContainer.style.display = 'none';
            }

            if (UI.elements.togglePageStructure.checked) {
                pageStructureSection.style.display = 'block';
                pageStructureChartContainer.style.display = 'block';
            } else {
                pageStructureSection.style.display = 'none';
                pageStructureChartContainer.style.display = 'none';
            }
        },
        filterResults: function() {
            const filter = UI.elements.searchResultsInput.value.toLowerCase();
            const rows = UI.elements.resultsBody.getElementsByTagName('tr');
            for (let i = 0; i < rows.length; i++) {
                const cells = rows[i].getElementsByTagName('td');
                let match = false;
                for (let j = 0; j < cells.length; j++) {
                    if (cells[j].innerText.toLowerCase().includes(filter)) {
                        match = true;
                        break;
                    }
                }
                rows[i].style.display = match ? '' : 'none';
            }
        },
        setupPreviewEvents: function(row, link) {
            const htmlBtn = row.querySelector('.html-view-btn');
            if (htmlBtn) {
                htmlBtn.addEventListener('click', () => {
                    this.openPreview(decodeURIComponent(htmlBtn.getAttribute('data-link')));
                });

                // 마우스 호버 시 미리보기 표시
                htmlBtn.addEventListener('mouseover', (e) => {
                    this.showTooltip(e, `<iframe src="${decodeURIComponent(htmlBtn.getAttribute('data-link'))}" title="미리보기"></iframe>`);
                });
                htmlBtn.addEventListener('mouseout', this.hideTooltip.bind(this));
            }

            const whoisBtn = row.querySelector('.whois-view-btn');
            if (whoisBtn) {
                whoisBtn.addEventListener('click', () => {
                    const encodedDomain = whoisBtn.getAttribute('data-domain');
                    const decodedDomain = decodeURIComponent(encodedDomain);
                    Crawler.fetchWhoisData(decodedDomain);
                });
            }
        },
        openPreview: function(link) {
            const analyticsTab = UI.container.querySelector('#analytics');
            const previewContainerId = 'site-preview-container';
            let previewContainer = UI.container.querySelector(`#${previewContainerId}`);

            if (!previewContainer) {
                previewContainer = document.createElement('div');
                previewContainer.id = previewContainerId;
                previewContainer.className = 'chart-container';
                previewContainer.style.height = '450px';
                analyticsTab.appendChild(previewContainer);
            }

            previewContainer.innerHTML = `
                <h3>사이트 미리보기: <a href="${link}" target="_blank">${link}</a></h3>
                <iframe src="${link}" style="width: 100%; height: 400px; border: none; border-radius: 4px;"></iframe>
            `;

            // 분석 탭으로 이동
            const analyticsTabBtn = UI.container.querySelector('.tab[data-tab="analytics"]');
            analyticsTabBtn.click();
        },
        closeWhoisModal: function() {
            UI.elements.whoisModal.style.display = 'none';
            UI.elements.whoisContent.innerHTML = '';
        }
    };

    /**
     * 크롤링 로직 및 데이터 처리 모듈
     */
    const CrawlerLogic = {
        crawlPage: async function() {
            if (!this.state.isRunning || this.state.isPaused) return;

            let url = this.constructURL(this.state.currentPage);
            if (!url) {
                Logger.add('❌ 지원하지 않는 검색 엔진입니다.');
                alert('지원하지 않는 검색 엔진입니다.');
                this.state.isRunning = false;
                return;
            }

            Logger.add(`📄 페이지 ${this.state.currentPage} 크롤링 중: ${url}`);

            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                headers: {
                    "User-Agent": navigator.userAgent,
                    "Accept": "text/html"
                },
                onload: (response) => {
                    if (response.status !== 200) {
                        this.handleCrawlError(this.state.currentPage, this.state.retryAttempts);
                        UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                        return;
                    }

                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');
                    let items = this.extractItems(doc);
                    if (this.state.currentPage === 1) {
                        this.state.totalPages = this.determineTotalPages(doc);
                        UI.elements.totalPagesElem.innerText = this.state.totalPages;
                    }

                    Logger.add(`📊 페이지 ${this.state.currentPage}에서 ${items.length}개 항목 발견.`);
                    UI.elements.totalRequests.innerText = parseInt(UI.elements.totalRequests.innerText) + items.length;

                    items.forEach(item => {
                        this.processItem(item);
                    });

                    this.updateCharts();
                    this.updateProgress();
                    this.state.currentPage++;
                    UI.elements.currentPage.innerText = this.state.currentPage;

                    if (this.state.currentPage <= this.state.totalPages && this.state.isRunning && !this.state.isPaused) {
                        setTimeout(() => this.crawlPage(), this.fixedRetryInterval);
                    } else {
                        this.finalizeCrawling();
                    }
                },
                onerror: () => {
                    this.handleCrawlError(this.state.currentPage, this.state.retryAttempts);
                    UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                }
            });
        },
        startCrawling: async function() {
            if (this.state.isRunning) {
                alert('이미 크롤링이 진행 중입니다.');
                return;
            }

            this.getSearchQueryAndEngine();
            if (!this.state.searchQuery) {
                alert('현재 페이지에서 검색어를 찾을 수 없습니다.');
                Logger.add('⚠️ 검색어 미입력 또는 인식 실패.');
                return;
            }

            this.state.isRunning = true;
            this.state.isPaused = false;
            this.state.currentPage = 1;
            this.state.totalPages = 0;
            this.state.results = [];
            this.state.visitedLinks.clear();
            UI.elements.resultsBody.innerHTML = '';
            UI.elements.visitedCount.innerText = '0';
            UI.elements.totalRequests.innerText = '0';
            UI.elements.successfulRequests.innerText = '0';
            UI.elements.failedRequests.innerText = '0';
            UI.elements.elapsedTime.innerText = '00:00:00';
            UI.elements.remainingTime.innerText = '00:00:00';
            Logger.logContainer.innerHTML = `<strong>로그:</strong><br>`;
            GM_setValue(Settings.keys.crawlerLogs, JSON.stringify(Logger.logMessages));
            Logger.add('✅ 크롤링을 시작합니다.');

            this.updateDuplicateThreshold();
            this.updateRetryAttempts();
            await this.crawlPage();
            this.state.startTime = Date.now();
            this.state.elapsedInterval = setInterval(this.updateElapsedTime.bind(this), 1000);
        },
        togglePause: function() {
            if (!this.state.isRunning) {
                alert('일시정지/재개할 크롤링이 없습니다.');
                return;
            }
            this.state.isPaused = !this.state.isPaused;
            if (this.state.isPaused) {
                Logger.add('⏸️ 크롤링을 일시정지하였습니다.');
                UI.elements.pauseBtn.innerText = '▶️ 재개';
                UI.elements.pauseBtn.title = '크롤링 재개';
            } else {
                Logger.add('▶️ 크롤링을 재개하였습니다.');
                UI.elements.pauseBtn.innerText = '⏸️ 일시정지';
                UI.elements.pauseBtn.title = '크롤링 일시정지';
                this.crawlPage();
            }
        },
        stopCrawling: function() {
            if (!this.state.isRunning) {
                alert('중지할 크롤링이 없습니다.');
                return;
            }
            this.state.isRunning = false;
            this.state.isPaused = false;
            UI.elements.pauseBtn.innerText = '⏸️ 일시정지';
            UI.elements.pauseBtn.title = '크롤링 일시정지';
            clearInterval(this.state.elapsedInterval);
            this.updateProgress();
            Logger.add('🚫 크롤링을 중지하였습니다.');
            Utils.toastNotification('크롤링이 중지되었습니다.');
        },
        resetCrawling: function() {
            this.stopCrawling();
            this.state.currentPage = 1;
            this.state.totalPages = 0;
            this.state.results = [];
            this.state.visitedLinks.clear();
            UI.elements.resultsBody.innerHTML = '';
            UI.elements.visitedCount.innerText = '0';
            UI.elements.totalRequests.innerText = '0';
            UI.elements.successfulRequests.innerText = '0';
            UI.elements.failedRequests.innerText = '0';
            UI.elements.progressBar.value = 0;
            UI.elements.progressText.innerText = '0%';
            UI.elements.elapsedTime.innerText = '00:00:00';
            UI.elements.remainingTime.innerText = '00:00:00';
            Logger.clear();

            // 차트 데이터 초기화
            this.resetCharts();
            Logger.add('🔄 크롤링을 초기화하였습니다.');
        },
        resetCharts: function() {
            // 차트 데이터 및 업데이트 초기화
            for (let chartKey in this.state.charts) {
                if (this.state.charts[chartKey]) {
                    this.state.charts[chartKey].destroy();
                }
            }
            UI.setupCharts();
        },
        exportResults: function() {
            if (this.state.results.length === 0) {
                alert('내보낼 결과가 없습니다.');
                return;
            }
            const format = prompt('내보낼 형식을 선택하세요 (xlsx, csv, json):', 'xlsx');
            if (!format) return;
            if (format.toLowerCase() === 'xlsx') {
                const ws = XLSX.utils.json_to_sheet(this.state.results);
                const wb = XLSX.utils.book_new();
                XLSX.utils.book_append_sheet(wb, ws, "결과");
                XLSX.writeFile(wb, "크롤링_결과.xlsx");
                Logger.add('💾 결과를 "크롤링_결과.xlsx" 파일로 내보냈습니다.');
                Utils.toastNotification('결과를 "크롤링_결과.xlsx" 파일로 내보냈습니다.');
            } else if (format.toLowerCase() === 'csv') {
                const headers = Object.keys(this.state.results[0]).join(',');
                const csvContent = this.state.results.map(row => 
                    Object.values(row).map(value => `"${value.toString().replace(/"/g, '""')}"`).join(',')
                ).join('\n');
                const blob = new Blob([headers + '\n' + csvContent], { type: 'text/csv;charset=utf-8;' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = '크롤링_결과.csv';
                a.click();
                Logger.add('💾 결과를 "크롤링_결과.csv" 파일로 내보냈습니다.');
                Utils.toastNotification('결과를 "크롤링_결과.csv" 파일로 내보냈습니다.');
            } else if (format.toLowerCase() === 'json') {
                const json = JSON.stringify(this.state.results, null, 2);
                const blob = new Blob([json], { type: 'application/json;charset=utf-8;' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = '크롤링_결과.json';
                a.click();
                Logger.add('💾 결과를 "크롤링_결과.json" 파일로 내보냈습니다.');
                Utils.toastNotification('결과를 "크롤링_결과.json" 파일로 내보냈습니다.');
            } else {
                alert('지원하지 않는 형식입니다.');
            }
        },
        updateFilterPatterns: function() {
            const input = UI.elements.filterInput.value.trim();
            this.state.filterPatterns = input ? input.split(',').map(word => {
                word = word.trim();
                if (word.startsWith('/') && word.endsWith('/')) {
                    try {
                        return new RegExp(word.slice(1, -1), 'i');
                    } catch (e) {
                        Logger.add(`⚠️ 유효하지 않은 정규식: ${word}`);
                        return null;
                    }
                } else {
                    return new RegExp(word, 'i');
                }
            }).filter(regex => regex !== null) : [];
            Logger.add(`🛠 필터 패턴 업데이트: [${this.state.filterPatterns.map(r => r.toString()).join(', ')}]`);
            this.saveSettings();
        },
        updateExcludeDomains: function() {
            const input = UI.elements.excludeDomainsInput.value.trim();
            this.state.excludeDomains = input ? input.split(',').map(domain => domain.trim().toLowerCase()) : [];
            Logger.add(`🚫 제외 도메인 업데이트: [${this.state.excludeDomains.join(', ')}]`);
            this.saveSettings();
        },
        updateDuplicateThreshold: function() {
            const value = parseInt(UI.elements.duplicateThresholdInput.value);
            this.state.duplicateThreshold = isNaN(value) || value < 1 ? 2 : value;
            Logger.add(`🔄 중복 허용 횟수 설정: ${this.state.duplicateThreshold}`);
            this.saveSettings();
        },
        updateRetryAttempts: function() {
            const value = parseInt(UI.elements.retryAttemptsInput.value);
            this.state.retryAttempts = isNaN(value) || value < 1 ? 3 : value;
            Logger.add(`🔄 재시도 횟수 설정: ${this.state.retryAttempts}`);
            this.saveSettings();
        },
        updateSearchEngine: function() {
            this.state.searchEngine = UI.elements.searchEngineSelect.value;
            Settings.save({ ...Settings.load(), searchEngine: this.state.searchEngine });
            Logger.add(`🔄 검색 엔진 변경: ${Utils.capitalizeFirstLetter(this.state.searchEngine)}`);
        },
        getSearchQueryAndEngine: function() {
            const url = window.location.href;
            const params = new URLSearchParams(window.location.search);
            if (url.includes('google.com/search')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'google';
            } else if (url.includes('bing.com/search')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'bing';
            } else if (url.includes('daum.net/search')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'daum';
            } else if (url.includes('search.naver.com/search')) {
                this.state.searchQuery = params.get('query') || '';
                this.state.searchEngine = 'naver';
            } else if (url.includes('search.yahoo.com/search')) {
                this.state.searchQuery = params.get('p') || '';
                this.state.searchEngine = 'yahoo';
            } else if (url.includes('duckduckgo.com/')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'duckduckgo';
            } else if (url.includes('startpage.com/do/search')) {
                this.state.searchQuery = params.get('query') || '';
                this.state.searchEngine = 'startpage';
            } else if (url.includes('ecosia.org/search')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'ecosia';
            } else if (url.includes('qwant.com/')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'qwant';
            } else if (url.includes('ahmia.fi/')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'ahmia';
            } else if (url.includes('torchsearch.de/')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'torch';
            } else if (url.includes('notracker.org/')) {
                this.state.searchQuery = params.get('q') || '';
                this.state.searchEngine = 'notracker';
            } else {
                this.state.searchQuery = '';
                this.state.searchEngine = '';
            }
            Logger.add(`🔍 검색 엔진: ${Utils.capitalizeFirstLetter(this.state.searchEngine)}, 검색어: "${this.state.searchQuery}"`);
        },
        constructURL: function(page) {
            switch(this.state.searchEngine) {
                case 'google':
                    return `https://www.google.com/search?q=${encodeURIComponent(this.state.searchQuery)}&start=${(page-1)*10}`;
                case 'bing':
                    return `https://www.bing.com/search?q=${encodeURIComponent(this.state.searchQuery)}&first=${(page-1)*10 +1}`;
                case 'daum':
                    return `https://search.daum.net/search?q=${encodeURIComponent(this.state.searchQuery)}&page=${page}`;
                case 'naver':
                    return `https://search.naver.com/search.naver?query=${encodeURIComponent(this.state.searchQuery)}&start=${(page-1)*10 +1}`;
                case 'yahoo':
                    return `https://search.yahoo.com/search?p=${encodeURIComponent(this.state.searchQuery)}&b=${(page-1)*10 +1}`;
                case 'duckduckgo':
                    return `https://duckduckgo.com/html/?q=${encodeURIComponent(this.state.searchQuery)}&s=${(page-1)*30}`;
                case 'startpage':
                    return `https://www.startpage.com/do/search?query=${encodeURIComponent(this.state.searchQuery)}&b=${(page-1)*10 +1}`;
                case 'ecosia':
                    return `https://www.ecosia.org/search?q=${encodeURIComponent(this.state.searchQuery)}&page=${page}`;
                case 'qwant':
                    return `https://www.qwant.com/?q=${encodeURIComponent(this.state.searchQuery)}&t=web&s=${page}`;
                case 'ahmia':
                    return `http://ahmia.fi/search/?q=${encodeURIComponent(this.state.searchQuery)}&page=${page}`;
                case 'torch':
                    return `http://torchsearch.de/search/?q=${encodeURIComponent(this.state.searchQuery)}&page=${page}`;
                case 'notracker':
                    return `http://notracker.org/search/?q=${encodeURIComponent(this.state.searchQuery)}&page=${page}`;
                default:
                    return '';
            }
        },
        extractItems: function(doc) {
            let items = [];
            switch(this.state.searchEngine) {
                case 'google':
                    items = doc.querySelectorAll('div.g');
                    break;
                case 'bing':
                    items = doc.querySelectorAll('li.b_algo');
                    break;
                case 'daum':
                    items = doc.querySelectorAll('div.wrap_cont');
                    break;
                case 'naver':
                    items = doc.querySelectorAll('div.basic1');
                    break;
                case 'yahoo':
                    items = doc.querySelectorAll('div.compTitle');
                    break;
                case 'duckduckgo':
                    items = doc.querySelectorAll('div.result');
                    break;
                case 'startpage':
                    items = doc.querySelectorAll('div.w-gl__result');
                    break;
                case 'ecosia':
                    items = doc.querySelectorAll('div.result');
                    break;
                case 'qwant':
                    items = doc.querySelectorAll('div.results div.result');
                    break;
                case 'ahmia':
                    items = doc.querySelectorAll('div#main div.result');
                    break;
                case 'torch':
                    items = doc.querySelectorAll('div.result');
                    break;
                case 'notracker':
                    items = doc.querySelectorAll('div.result');
                    break;
                default:
                    break;
            }
            return items;
        },
        determineTotalPages: function(doc) {
            let pages = 1;
            switch(this.state.searchEngine) {
                case 'google':
                    const googlePager = doc.querySelectorAll('a[href*="start="]');
                    pages = Math.min(googlePager.length, 10) || 1;
                    break;
                case 'bing':
                    const bingPager = doc.querySelectorAll('a.sb_pagS');
                    pages = bingPager.length || 1;
                    break;
                case 'daum':
                    const daumPager = doc.querySelectorAll('a.pagination__link');
                    pages = daumPager.length || 1;
                    break;
                case 'naver':
                    const naverPager = doc.querySelectorAll('a.nav');
                    pages = naverPager.length || 1;
                    break;
                case 'yahoo':
                    const yahooPager = doc.querySelectorAll('a.fz-l.tco-pagination__anchor');
                    pages = yahooPager.length || 1;
                    break;
                case 'duckduckgo':
                    const ddgPager = doc.querySelectorAll('a.result--more__btn');
                    pages = ddgPager.length || 1;
                    break;
                case 'startpage':
                    const spPager = doc.querySelectorAll('a.paginator__link');
                    pages = spPager.length || 1;
                    break;
                case 'ecosia':
                    const ecosiaPager = doc.querySelectorAll('a.pagination__link');
                    pages = ecosiaPager.length || 1;
                    break;
                case 'qwant':
                    const qwantPager = doc.querySelectorAll('a.pagination__link');
                    pages = qwantPager.length || 1;
                    break;
                case 'ahmia':
                    const ahmiaPager = doc.querySelectorAll('a.pagination__link');
                    pages = ahmiaPager.length || 1;
                    break;
                case 'torch':
                    const torchPager = doc.querySelectorAll('a.pagination__link');
                    pages = torchPager.length || 1;
                    break;
                case 'notracker':
                    const ntPager = doc.querySelectorAll('a.pagination__link');
                    pages = ntPager.length || 1;
                    break;
                default:
                    pages = 1;
                    break;
            }
            return pages;
        },
        processItem: function(item) {
            let data = {
                title: 'N/A',
                link: 'N/A',
                description: 'N/A',
                image: 'N/A',
                htmlLink: 'N/A',
                whoisInfo: 'N/A',
                pageLoadTime: 'N/A',
                language: 'N/A',
                metaKeywords: 'N/A',
                metaRobots: 'N/A',
                openGraph: 'N/A',
                securityHeaders: 'N/A',
                domainAge: 'N/A',
                techStack: 'N/A',
                backlinks: 'N/A',
                pageStructure: 'N/A'
            };

            // 항목별 정보 추출 (검색 엔진별로 분리)
            switch(this.state.searchEngine) {
                case 'google':
                    this.extractGoogleData(item, data);
                    break;
                case 'bing':
                    this.extractBingData(item, data);
                    break;
                // 다른 검색 엔진별 데이터 추출 함수 추가
                default:
                    break;
            }

            // 메타 정보 및 추가 분석
            this.extractAdditionalData(item, data);

            // 광고 제외 필터링
            if (UI.elements.toggleAds.checked) {
                const combinedText = `${data.title} ${data.description}`.toLowerCase();
                if (combinedText.includes('advertisement') || combinedText.includes('광고') || combinedText.includes('ad ')) {
                    Logger.add(`🚫 광고 항목 제외: "${data.title}"`);
                    return;
                }
            }

            // 필터 패턴 적용
            if (this.state.filterPatterns.length > 0) {
                const combinedText = `${data.title} ${data.description}`;
                if (this.state.filterPatterns.some(regex => regex.test(combinedText))) {
                    Logger.add(`❌ 필터 단어 포함 항목 제외: "${data.title}"`);
                    return;
                }
            }

            // 도메인 추출 및 제외 도메인 적용
            const domain = Utils.extractDomain(data.link);
            if (!domain) return;

            if (this.state.excludeDomains.includes(domain.toLowerCase())) {
                Logger.add(`🚫 제외 도메인 항목 제외: "${data.link}"`);
                return;
            }

            // 중복 방문 제한
            let visitCount = this.state.visitedLinks.get(domain) || 0;
            if (visitCount >= this.state.duplicateThreshold) {
                Logger.add(`🔄 중복 방문 제한: "${domain}"`);
                return;
            }

            this.state.visitedLinks.set(domain, visitCount + 1);
            UI.elements.visitedCount.innerText = this.state.visitedLinks.size.toString();

            // 페이지 로딩 시간 측정
            const startLoad = performance.now();
            fetch(data.link, { mode: 'no-cors' })
                .then(response => {
                    const endLoad = performance.now();
                    data.pageLoadTime = Math.round(endLoad - startLoad);
                    UI.elements.successfulRequests.innerText = parseInt(UI.elements.successfulRequests.innerText) + 1;
                    this.updateRow(data);
                })
                .catch(err => {
                    data.pageLoadTime = '계산 불가';
                    UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                    this.updateRow(data);
                });

            // 결과 배열에 추가
            this.state.results.push(data);

            // 결과 테이블에 행 추가
            const row = document.createElement('tr');
            row.innerHTML = `
                <td>${Utils.escapeHTML(data.title)}</td>
                <td><a href="${Utils.escapeHTML(data.link)}" target="_blank">${Utils.escapeHTML(data.link)}</a></td>
                <td>${Utils.escapeHTML(data.description)}</td>
                <td>${data.image}</td>
                <td>${data.htmlLink}</td>
                <td>${data.whoisInfo}</td>
                <td>${data.pageLoadTime}</td>
                <td>${Utils.escapeHTML(data.language)}</td>
                <td>${Utils.escapeHTML(data.metaKeywords)}</td>
                <td>${Utils.escapeHTML(data.metaRobots)}</td>
                <td>${Utils.escapeHTML(data.openGraph)}</td>
                <td>${Utils.escapeHTML(data.securityHeaders)}</td>
                <td>${Utils.escapeHTML(data.domainAge)}</td>
                <td>${Utils.escapeHTML(data.techStack)}</td>
                <td>${data.backlinks}</td>
                <td>${data.pageStructure}</td>
            `;
            UI.elements.resultsBody.appendChild(row);

            // 미리보기 이벤트 추가
            UI.setupPreviewEvents(row, data.link);
        },
        extractGoogleData: function(item, data) {
            const titleElem = item.querySelector('h3');
            const linkElem = item.querySelector('a');
            const descElem = item.querySelector('.IsZvec') || item.querySelector('.VwiC3b');

            if (titleElem) data.title = titleElem.innerText;
            if (linkElem) data.link = linkElem.href;
            if (descElem) data.description = descElem.innerText;

            const imgElem = item.querySelector('img');
            if (imgElem && imgElem.src) {
                data.image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
            }

            if (UI.elements.toggleHTML.checked && data.link !== 'N/A') {
                data.htmlLink = `<button class="html-view-btn" data-link="${encodeURIComponent(data.link)}">보기</button>`;
            }
        },
        extractBingData: function(item, data) {
            const titleElem = item.querySelector('h2 a');
            const descElem = item.querySelector('p');

            if (titleElem) {
                data.title = titleElem.innerText;
                data.link = titleElem.href;
            }
            if (descElem) data.description = descElem.innerText;

            const imgElem = item.querySelector('img');
            if (imgElem && imgElem.src) {
                data.image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
            }

            if (UI.elements.toggleHTML.checked && data.link !== 'N/A') {
                data.htmlLink = `<button class="html-view-btn" data-link="${encodeURIComponent(data.link)}">보기</button>`;
            }
        },
        // 다른 검색 엔진별 데이터 추출 함수 추가
        extractAdditionalData: function(item, data) {
            try {
                const metaKeywordElem = item.querySelector('meta[name="keywords"]');
                if (metaKeywordElem) {
                    data.metaKeywords = metaKeywordElem.getAttribute('content') || 'N/A';
                    if (data.metaKeywords !== 'N/A') {
                        data.metaKeywords = data.metaKeywords.split(',').map(k => k.trim()).join(', ');
                    }
                }

                const metaRobotsElem = item.querySelector('meta[name="robots"]');
                if (metaRobotsElem) {
                    data.metaRobots = metaRobotsElem.getAttribute('content') || 'N/A';
                }

                const ogTitle = item.querySelector('meta[property="og:title"]')?.getAttribute('content') || 'N/A';
                const ogDescription = item.querySelector('meta[property="og:description"]')?.getAttribute('content') || 'N/A';
                const ogImage = item.querySelector('meta[property="og:image"]')?.getAttribute('content') || 'N/A';
                data.openGraph = `Title: ${ogTitle}, Description: ${ogDescription}, Image: ${ogImage}`;

                // 보안 헤더 추출 (간단한 예시)
                let securityHeadersList = [];
                const hsts = item.querySelector('meta[http-equiv="Strict-Transport-Security"]');
                if (hsts) securityHeadersList.push('HSTS');
                const contentSecurityPolicy = item.querySelector('meta[http-equiv="Content-Security-Policy"]');
                if (contentSecurityPolicy) securityHeadersList.push('CSP');
                data.securityHeaders = securityHeadersList.length > 0 ? securityHeadersList.join(', ') : 'N/A';

                const domainInfo = this.getDomainInfo(data.link);
                data.domainAge = domainInfo ? domainInfo.age : 'N/A';

                const htmlLang = item.querySelector('html');
                if (htmlLang && htmlLang.lang) {
                    data.language = htmlLang.lang;
                }

                // 기술 스택 정보 수집 (예시: 간단한 스택 감지)
                const techs = [];
                if (item.querySelector('.wordpress')) techs.push('WordPress');
                if (item.querySelector('.shopify')) techs.push('Shopify');
                if (item.querySelector('.react')) techs.push('React');
                if (item.querySelector('.angular')) techs.push('Angular');
                if (item.querySelector('.vue')) techs.push('Vue.js');
                data.techStack = techs.length > 0 ? techs.join(', ') : 'N/A';

                // 백링크 정보 수집 (미지원)
                if (UI.elements.toggleBacklinks.checked) {
                    data.backlinks = '현재 지원되지 않습니다.';
                }

                // 페이지 구조 분석 (미지원)
                if (UI.elements.togglePageStructure.checked) {
                    data.pageStructure = '현재 지원되지 않습니다.';
                }
            } catch (e) {
                Logger.add(`⚠️ 메타 정보 수집 중 오류 발생: ${e.message}`);
            }
        },
        getDomainInfo: function(url) {
            try {
                const urlObj = new URL(url);
                const now = new Date();
                // 실제 WHOIS 데이터를 통해 도메인 생성일을 가져와야 하지만, 예시로 임의의 연령을 설정
                // 본인의 JSONWHOIS API를 활용하여 실제 데이터를 받아오도록 구현 필요
                // 예시:
                // return { age: calculatedAge };
                return { age: 'N/A' };
            } catch (e) {
                return null;
            }
        },
        handleCrawlError: function(page, retriesLeft) {
            if (retriesLeft > 0 && this.state.isRunning) {
                Logger.add(`🔄 페이지 ${page} 재시도 (${retriesLeft}회 남음).`);
                setTimeout(() => {
                    const url = this.constructURL(page);
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: url,
                        headers: {
                            "User-Agent": navigator.userAgent,
                            "Accept": "text/html"
                        },
                        onload: (response) => {
                            if (response.status === 200) {
                                Logger.add(`✅ 페이지 ${page} 재시도 성공.`);
                                UI.elements.successfulRequests.innerText = parseInt(UI.elements.successfulRequests.innerText) + 1;
                                this.crawlPage();
                            } else {
                                this.handleCrawlError(page, retriesLeft - 1);
                                UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                            }
                        },
                        onerror: () => {
                            this.handleCrawlError(page, retriesLeft - 1);
                            UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                        }
                    });
                }, this.fixedRetryInterval);
            } else {
                Logger.add(`❌ 페이지 ${page} 로드 실패: 최대 재시도 횟수 초과.`);
                UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                this.state.currentPage++;
                UI.elements.currentPage.innerText = this.state.currentPage;
                if (this.state.currentPage <= this.state.totalPages && this.state.isRunning && !this.state.isPaused) {
                    setTimeout(() => this.crawlPage(), this.fixedRetryInterval);
                } else {
                    this.finalizeCrawling();
                }
            }
        },
        finalizeCrawling: function() {
            this.state.isRunning = false;
            clearInterval(this.state.elapsedInterval);
            Logger.add('✅ 크롤링이 완료되었습니다.');
            Utils.toastNotification('크롤링이 완료되었습니다.');
        },
        extractAdditionalData: function(item, data) {
            // 추가 데이터 추출 로직 (이미 분리됨)
        },
        updateRow: function(data) {
            // 테이블의 해당 행을 업데이트
            // 이 부분은 필요에 따라 구현
            this.updateCharts();
            this.updateProgress();
        },
        updateCharts: function() {
            // 차트 업데이트 로직
            // 예시:
            // updateCrawlChart(), updateLoadTimeChart() 등
        },
        updateProgress: function() {
            if (this.state.totalPages === 0) {
                UI.elements.progressBar.value = 0;
                UI.elements.progressText.innerText = '0%';
                return;
            }
            const progress = Math.min((this.state.currentPage - 1) / this.state.totalPages * 100, 100);
            UI.elements.progressBar.value = progress;
            UI.elements.progressText.innerText = `${Math.round(progress)}%`;
        },
        updateElapsedTime: function() {
            if (!this.state.startTime) return;
            const now = Date.now();
            const elapsed = Math.floor((now - this.state.startTime) / 1000);
            UI.elements.elapsedTime.innerText = Utils.formatTime(elapsed);
            const estimatedTotal = this.state.totalPages * ((elapsed > 0) ? (elapsed / (this.state.currentPage - 1)) : 0.5);
            const remaining = Math.max(Math.round(estimatedTotal - elapsed), 0);
            UI.elements.remainingTime.innerText = Utils.formatTime(remaining);
        },
        fetchWhoisData: function(domain) {
            Logger.add(`📡 WHOIS 정보를 가져오는 중: ${domain}`);
            const apiUrl = `https://jsonwhois.com/api/v1/whois?domain=${encodeURIComponent(domain)}`;

            GM_xmlhttpRequest({
                method: "GET",
                url: apiUrl,
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Token token=${UI.elements.whoisApiKeyInput.value.trim()}`
                },
                onload: (response) => {
                    if (response.status === 200) {
                        const data = JSON.parse(response.responseText);
                        this.displayWhoisData(data, domain);
                        Logger.add(`✅ WHOIS 정보를 성공적으로 가져왔습니다: ${domain}`);
                        UI.elements.successfulRequests.innerText = parseInt(UI.elements.successfulRequests.innerText) + 1;
                    } else {
                        Logger.add(`❌ WHOIS 정보 가져오기 실패: ${domain}, 상태 코드: ${response.status}`);
                        UI.elements.whoisContent.innerHTML = `<p>WHOIS 정보를 가져오는 데 실패했습니다.</p>`;
                        UI.elements.whoisModal.style.display = 'block';
                    }
                },
                onerror: () => {
                    Logger.add(`❌ WHOIS 정보 요청 중 오류 발생: ${domain}`);
                    UI.elements.failedRequests.innerText = parseInt(UI.elements.failedRequests.innerText) + 1;
                    UI.elements.whoisContent.innerHTML = `<p>WHOIS 정보를 가져오는 데 오류가 발생했습니다.</p>`;
                    UI.elements.whoisModal.style.display = 'block';
                }
            });
        },
        displayWhoisData: function(data, domain) {
            if (!data) {
                UI.elements.whoisContent.innerHTML = `<p>No WHOIS data available.</p>`;
                UI.elements.whoisModal.style.display = 'block';
                return;
            }

            const whoisHtml = `
                <h3>${domain}의 WHOIS 정보</h3>
                <pre>${JSON.stringify(data, null, 2)}</pre>
            `;
            UI.elements.whoisContent.innerHTML = whoisHtml;
            UI.elements.whoisModal.style.display = 'block';
        },
        /**
         * 기타 필요한 함수들 (예: 차트 업데이트 등)을 추가
         */
    };

    /**
     * 초기화 및 실행
     */
    const Initialization = {
        initialize: function() {
            // 스타일 추가 (기존 코드 유지)
            GM_addStyle(`
                /* 기존 스타일 코드 */
                /* ... */
                /* Tooltip 스타일 */
                .preview-tooltip {
                    position: absolute;
                    background-color: #ffffff;
                    border: 1px solid #90CAF9;
                    border-radius: 5px;
                    padding: 8px;
                    z-index: 10002;
                    box-shadow: 0 3px 8px rgba(0,0,0,0.2);
                    display: none;
                    width: 280px;
                }
                .preview-tooltip iframe {
                    width: 100%;
                    height: 180px;
                    border: none;
                    border-radius: 4px;
                }
            `);

            // UI 초기화
            const uiHtml = `
                <header>
                    <span class="title">🔥 웹 크롤러 8.6</span>
                    <span class="close-btn" title="닫기">&times;</span>
                </header>
                <div class="tabs">
                    <div class="tab active" data-tab="settings">설정</div>
                    <div class="tab" data-tab="results">결과</div>
                    <div class="tab" data-tab="logs">로그</div>
                    <div class="tab" data-tab="analytics">분석</div>
                    <div class="tab" data-tab="about">정보</div>
                </div>
                <div class="tab-content active" id="settings">
                    <!-- 설정 탭 내용 -->
                    <!-- ... -->
                </div>
                <div class="tab-content" id="results">
                    <!-- 결과 탭 내용 -->
                    <!-- ... -->
                </div>
                <div class="tab-content" id="logs">
                    <div class="log" id="log">
                        <strong>로그:</strong>
                    </div>
                </div>
                <div class="tab-content" id="analytics">
                    <!-- 분석 탭 내용 -->
                    <!-- ... -->
                    <div id="whois-modal">
                        <header>
                            <span>WHOIS 정보</span>
                            <span class="close-modal-btn" title="닫기">&times;</span>
                        </header>
                        <div class="modal-content" id="whoisContent">
                            <!-- WHOIS 정보가 여기에 표시됩니다 -->
                        </div>
                    </div>
                </div>
                <div class="tab-content" id="about">
                    <!-- 정보 탭 내용 -->
                    <!-- ... -->
                </div>
            `;
            UI.init(uiHtml);

            // 크롤러 로직 초기화
            CrawlerLogic.init();
        }
    };

    // 초기화 실행
    Initialization.initialize();

})();                                  이거 기능들 중에서 외부 라이브러리에서 대체 할 수 있는 건 전부다 대체하셈. 코드에서 라이브러리로 전환