검색 엔진별 핸들러 확장 필요:
사용자가 추가하려는 consolex.ai, novelpia.com, munpia.com 등의 사이트 기능을 통합하려면 각 사이트에 맞는 검색 엔진 핸들러를 별도로 정의하고, 해당 사이트의 구조에 맞는 결과 추출 로직을 구현해야 합니다.
동적 콘텐츠 처리:
일부 검색 엔진이나 웹사이트는 JavaScript를 사용하여 동적으로 콘텐츠를 로드합니다. 현재 스크립트는 정적 HTML을 기반으로 크롤링을 수행하므로, 동적 콘텐츠를 제대로 수집하지 못할 수 있습니다. Headless 브라우저(예: Puppeteer)와의 통합을 고려해볼 수 있습니다.
로봇 배제 표준(robots.txt) 파서 개선:
현재 단순화된 robots.txt 파서가 사용되고 있습니다. 복잡한 규칙이나 와일드카드 패턴을 제대로 처리하지 못할 수 있으므로, 더 정교한 robots.txt 파서 라이브러리의 도입을 검토하는 것이 좋습니다.
CAPTCHA 처리 강화:
CAPTCHA 감지 및 처리 로직이 매우 기본적입니다. 실제 CAPTCHA는 자동화된 처리가 어려울 수 있으며, 사용자에게 수동으로 해결을 요청하는 방법이나 CAPTCHA 우회 서비스를 활용할 필요가 있습니다.
성능 및 리소스 관리:
최대 동시 요청 수(maxConcurrentRequests)가 높을 경우 시스템 리소스를 과도하게 사용할 수 있습니다. 사용자 설정에 따라 동시 요청 수를 적절히 제한하거나, 리소스 사용량을 모니터링하는 기능을 추가하는 것이 바람직합니다.
보안 고려사항:
크롤러가 수집한 데이터가 브라우저 컨텍스트 내에 저장되고 처리되므로, 데이터 보호와 프라이버시 측면에서 주의가 필요합니다. 민감한 데이터가 포함될 가능성이 있는 경우, 데이터 암호화나 접근 제어를 강화할 필요가 있습니다.
유저 경험(UX) 향상:
UI가 기능적으로 풍부하지만, 사용자가 처음 접할 때 다소 복잡하게 느껴질 수 있습니다. 직관적인 인터페이스 설계와 사용자 가이드 또는 툴팁을 추가하여 사용성을 높이는 것이 좋습니다.
에러 핸들링 및 안정성 강화:
일부 예외 상황에서 크롤러가 예상치 못하게 중단되거나 오류 로그가 누락될 수 있습니다. 더 포괄적인 에러 핸들링과 안정적인 복구 메커니즘을 구현하는 것이 필요합니다.
테스트 범위 확장:
현재 기본적인 유닛 테스트가 포함되어 있으나, 크롤링 로직, 데이터 추출, UI 상호작용 등 다양한 기능에 대한 통합 테스트와 엔드 투 엔드 테스트를 추가하여 코드의 신뢰성을 더욱 높일 수 있습니다.

추가 사이트 기능 통합
사용자가 추가하려는 consolex.ai, novelpia.com, munpia.com 등의 사이트 기능을 통합하려면 다음과 같은 작업이 필요합니다:

사이트별 핸들러 정의:
각 사이트의 HTML 구조와 데이터를 이해한 후, 해당 사이트에 맞는 결과 추출 로직을 searchEngineHandlers 객체에 추가해야 합니다.
예를 들어, novelpia.com과 munpia.com은 소설 플랫폼이므로, 책 제목, 저자, 장르, 설명 등의 정보를 추출하는 핸들러를 구현합니다.
동적 콘텐츠 처리:
일부 사이트는 JavaScript를 사용하여 동적으로 콘텐츠를 로드할 수 있습니다. 이러한 경우, Tampermonkey 스크립트만으로는 모든 데이터를 추출하기 어려울 수 있으므로, Headless 브라우저(예: Puppeteer)와의 연동을 고려해보는 것이 좋습니다.
API 연동:
일부 사이트는 공식 API를 제공할 수 있습니다. API를 활용하면 더 안정적이고 효율적으로 데이터를 수집할 수 있습니다.
2. 에러 핸들링 및 복구 메커니즘 강화
현재 스크립트는 기본적인 에러 핸들링을 제공하지만, 더 복잡한 상황에서도 안정적으로 동작하기 위해 다음과 같은 개선이 필요합니다:

전역 에러 핸들러 추가:
window.onerror 또는 try-catch 블록을 통해 예상치 못한 에러를 포착하고, 사용자에게 알림을 제공하거나 자동으로 재시도하는 로직을 추가합니다.
재시도 로직 개선:
특정 에러 유형에 따라 재시도 전략을 다르게 설정하거나, 점진적 지수 백오프(Exponential Backoff) 방식으로 재시도 간격을 조정합니다.
3. 성능 최적화
크롤링 성능을 개선하고 리소스 사용을 최적화하기 위해 다음과 같은 방안을 고려할 수 있습니다:

비동기 처리 최적화:
비동기 요청과 데이터 처리를 효율적으로 관리하여, 비동기 병목 현상을 줄이고 전체적인 처리 속도를 향상시킵니다.
메모리 관리:
크롤링 중 사용되는 데이터 구조(예: visitedUrls, requestQueue, data)를 최적화하여 메모리 누수를 방지하고, 장기 실행 시 성능 저하를 예방합니다.
캐싱 메커니즘 도입:
이미 크롤링한 페이지나 자주 참조되는 데이터를 캐싱하여 불필요한 네트워크 요청을 줄입니다.
4. 보안 강화
크롤러가 다루는 데이터와 요청의 보안을 강화하기 위해 다음과 같은 조치를 취할 수 있습니다:

프록시 보안 강화:
사용자 입력 프록시 URL의 유효성을 더욱 철저히 검사하고, 신뢰할 수 없는 프록시 서버 사용을 제한합니다.
데이터 암호화:
수집된 데이터를 암호화하여 저장하거나 전송함으로써, 민감한 정보가 노출되지 않도록 보호합니다.
CORS 정책 준수:
크롤러가 타 도메인에 요청을 보낼 때, Cross-Origin Resource Sharing (CORS) 정책을 준수하도록 설정하고, 필요한 경우 프록시 서버를 통해 요청을 우회합니다.
5. 사용자 경험(UX) 향상
더 나은 사용자 경험을 제공하기 위해 UI와 인터페이스를 개선할 수 있습니다:

가이드 및 도움말 추가:
각 설정 항목에 대한 자세한 설명과 사용 가이드를 제공하여, 사용자가 스크립트를 쉽게 이해하고 활용할 수 있도록 지원합니다.
반응형 디자인 개선:
다양한 디바이스와 화면 크기에서도 UI가 제대로 표시되도록, 반응형 디자인을 더욱 세밀하게 조정합니다.
실시간 피드백 제공:
크롤링 진행 상황을 실시간으로 시각화하거나, 진행 중인 작업에 대한 피드백을 제공하여 사용자가 현재 상태를 쉽게 파악할 수 있도록 합니다.
6. 다국어 지원 및 지역화(Localization)
더 넓은 사용자 층을 대상으로 하기 위해 다국어 지원을 추가할 수 있습니다:

다국어 인터페이스:
UI 텍스트, 로그 메시지, 알림 등을 다양한 언어로 제공하여, 사용자가 자신의 언어로 스크립트를 사용할 수 있도록 합니다.
지역화 설정:
날짜, 시간, 숫자 형식 등을 사용자의 지역에 맞게 자동으로 조정하는 기능을 추가합니다.
7. 데이터 관리 및 저장소 최적화
수집된 데이터를 효율적으로 관리하고 저장하기 위한 개선 사항입니다:

데이터 페이징 및 정렬:
UI에서 대용량 데이터를 페이징 처리하거나, 다양한 기준으로 정렬할 수 있는 기능을 추가하여 데이터를 더 효과적으로 탐색할 수 있게 합니다.
데이터 필터링 강화:
복잡한 필터 조건을 지원하거나, 여러 필터를 조합하여 데이터를 세밀하게 검색할 수 있는 기능을 제공합니다.
8. 확장 가능한 아키텍처 설계
미래의 확장성과 유지보수성을 고려한 아키텍처 설계가 필요합니다:

플러그인 시스템 도입:
새로운 검색 엔진이나 사이트를 쉽게 추가할 수 있도록, 플러그인 기반의 구조를 도입합니다.
모듈 간 의존성 관리:
각 모듈 간의 의존성을 명확히 하고, 변경 시 다른 모듈에 최소한의 영향을 미치도록 설계합니다.
9. 로그 및 데이터 보관 관리
로그와 데이터의 효율적인 보관 및 관리 방안입니다:

로그 레벨 설정:
정보, 경고, 오류 등 다양한 로그 레벨을 설정하여, 사용자가 필요한 수준의 로그만을 확인할 수 있도록 지원합니다.
데이터 보관 기간 설정:
수집된 데이터를 일정 기간 후 자동으로 삭제하거나 아카이빙하는 기능을 추가하여, 로컬 저장소의 용량을 관리합니다.
10. 테스트 및 지속적 통합(CI)
코드의 신뢰성을 높이고 지속적인 품질 관리를 위해 테스트 및 CI를 강화할 수 있습니다:

자동화된 테스트:
유닛 테스트 외에도 통합 테스트와 엔드 투 엔드 테스트를 추가하여, 전체 시스템의 안정성을 검증합니다.
지속적 통합 도구와 연동:
GitHub Actions, Travis CI 등과 같은 지속적 통합 도구와 연동하여, 코드 변경 시 자동으로 테스트를 실행하고 릴리즈를 관리합니다.
11. 사용자 권한 및 접근 제어
크롤러의 사용을 보다 안전하게 관리하기 위해 권한 및 접근 제어를 도입합니다:

사용자 인증:
크롤러를 사용하는 사용자에 대한 인증 절차를 추가하여, 승인된 사용자만이 크롤러를 사용할 수 있도록 합니다.
역할 기반 접근 제어(RBAC):
사용자에게 역할을 부여하고, 각 역할에 따라 접근 가능한 기능을 제한함으로써, 크롤러의 오용을 방지합니다.
12. 알림 및 통지 시스템
크롤링 과정에서 중요한 이벤트가 발생했을 때 사용자에게 알림을 제공하는 기능입니다:

실시간 알림:
크롤링이 완료되었거나, 오류가 발생했을 때 브라우저 알림이나 이메일을 통해 사용자에게 실시간으로 통지합니다.
대시보드 통합:
크롤링 상태와 주요 이벤트를 한눈에 확인할 수 있는 대시보드를 추가하여, 전반적인 진행 상황을 쉽게 파악할 수 있게 합니다.
13. 데이터 중복 및 무결성 관리
수집된 데이터의 중복을 방지하고 무결성을 유지하기 위한 개선 사항입니다:

중복 검사 로직 강화:
visitedUrls 외에도 데이터 항목 간의 고유성을 확인하여, 동일한 데이터가 여러 번 저장되지 않도록 합니다.
데이터 무결성 검증:
저장된 데이터의 형식과 내용을 정기적으로 검증하여, 데이터 손상이나 오류를 사전에 방지합니다.
14. 알기 쉬운 코드 주석 및 문서화
개발 및 유지보수를 용이하게 하기 위해 코드의 가독성과 문서화를 강화합니다:

상세한 코드 주석:
주요 함수와 모듈에 대한 설명을 주석으로 추가하여, 다른 개발자나 사용자가 코드를 쉽게 이해할 수 있도록 합니다.
공식 문서 작성:
스크립트의 설치 방법, 사용법, 설정 옵션 등을 포함한 공식 문서를 작성하여, 사용자에게 체계적인 가이드를 제공합니다.
15. 라이브러리 및 종속성 관리
사용되는 외부 라이브러리와 종속성을 효율적으로 관리하여, 코드의 안정성과 보안성을 높입니다:

최신 라이브러리 사용:
사용 중인 외부 라이브러리(예: jQuery, Chart.js)의 최신 버전을 사용하여, 보안 취약점을 최소화하고 최신 기능을 활용합니다.
종속성 버전 고정:
라이브러리 버전을 고정하여, 업데이트 시 발생할 수 있는 호환성 문제를 방지합니다.
16. 멀티 태스킹 및 우선순위 관리
여러 작업을 동시에 처리하면서, 중요한 작업에 우선순위를 부여하는 기능입니다:

작업 큐 관리:
크롤링 요청을 우선순위 큐로 관리하여, 중요한 사이트나 데이터가 먼저 처리되도록 조정합니다.
백그라운드 작업 처리:
크롤링과 데이터 처리 작업을 백그라운드에서 비동기적으로 실행하여, UI의 반응성을 유지합니다.
17. 캐싱 및 로컬 스토리지 활용
데이터 수집 및 처리 속도를 향상시키기 위해 캐싱과 로컬 스토리지를 효율적으로 활용합니다:

결과 캐싱:
이미 수집한 데이터를 캐싱하여, 동일한 요청 시 다시 수집하지 않고 캐시된 데이터를 사용합니다.
로컬 스토리지 최적화:
크롤링 데이터와 설정을 로컬 스토리지에 효율적으로 저장하고 불러오는 로직을 최적화하여, 데이터 접근 속도를 향상시킵니다.
18. 환경 설정 파일 지원
환경 설정을 외부 파일 또는 클라우드 설정과 연동하여 관리할 수 있도록 지원합니다:

환경 설정 파일:
JSON, YAML 등의 형식으로 환경 설정 파일을 지원하여, 설정 변경을 더 간편하게 합니다.
클라우드 설정 연동:
Google Drive, Dropbox 등 클라우드 서비스를 통해 설정을 저장하고 동기화할 수 있도록 지원합니다.
19. 로그 저장 및 분석 기능 강화
크롤링 과정에서 생성된 로그를 체계적으로 저장하고 분석할 수 있는 기능입니다:

로그 파일 내보내기:
로그를 파일로 내보내거나, 클라우드 스토리지에 저장하여 나중에 분석할 수 있도록 지원합니다.
로그 분석 도구 통합:
로그 데이터를 분석하기 위한 도구(예: ELK 스택)와의 통합을 고려하여, 로그 데이터를 시각화하고 인사이트를 도출할 수 있습니다.
20. 유저 권장사항 및 피드백 수집
사용자의 의견을 반영하여 스크립트를 지속적으로 개선합니다:

피드백 폼 추가:
UI에 피드백 폼을 추가하여, 사용자가 개선점이나 버그를 쉽게 보고할 수 있도록 합니다.
사용자 통계 분석:
사용자의 설정 선택이나 크롤링 패턴을 분석하여, 스크립트의 기능을 사용자의 요구에 맞게 조정합니다.

여기에 나와있는 걸 바탕으로 개선한 코드를 전체 코드에 넣으셈. 그리고 코드만 적으셈                                                            // ==UserScript==
// @name         Advanced Web Crawler Enhanced Pro Optimized v6.4.3
// @namespace    http://tampermonkey.net/
// @version      6.4.4
// @description  Further optimized and enhanced version with responsive design, loading indicators, enhanced scheduling, data analysis tools integration, automated testing, improved UI management, and multiple crawling strategies selectable from settings.
// @author       Improved by ChatGPT
// @match        https://www.google.com/search*
// @match        https://www.bing.com/search*
// @match        https://search.yahoo.com/search*
// @match        https://duckduckgo.com/*
// @match        https://www.baidu.com/s*
// @match        https://www.yandex.com/search/*
// @match        https://search.naver.com/search*
// @match        https://search.daum.net/search*
// @match        https://www.ask.com/*
// @match        https://www.ecosia.org/search*
// @match        https://www.startpage.com/*
// @match        https://searx.me/*
// @match        https://www.dogpile.com/*
// @match        https://www.bing.com/images*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/chart.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/mocha/10.2.0/mocha.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.7/chai.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js
// ==/UserScript==

(() => {
    'use strict';

    const Config = (() => {
        const DEFAULT_SETTINGS = {
            delay: 2000,
            delayRandom: 500,
            maxPages: 5,
            userAgents: [
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)',
                'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
                'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
                'AdvancedWebCrawler/6.4 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
            ],
            userAgentIndex: 0,
            blacklist: [],
            whitelist: [],
            contentTypes: {
                include: ['text/html'],
                exclude: ['application/pdf', 'image/jpeg', 'image/png', 'video/mp4']
            },
            timeout: 8000,
            includeKeywords: [],
            excludeKeywords: [],
            language: [],
            minWordCount: 100,
            maxWordCount: 10000,
            maxDepth: 2,
            proxy: '',
            regexInclude: [],
            regexExclude: [],
            dataFields: {
                name: true,
                url: true,
                description: true,
                html: true,
                category: true,
                quality: true,
                language: true,
                wordCount: true,
                metaTags: false,
                headings: false
            },
            schedule: {
                enabled: false,
                startTime: '00:00',
                interval: 3600000,
                specificDays: []
            },
            searchEngines: {
                google: true,
                bing: true,
                yahoo: true,
                duckduckgo: true,
                baidu: true,
                yandex: true,
                naver: true,
                daum: true,
                ask: true,
                ecosia: true,
                startpage: true,
                searx: true,
                dogpile: true,
                bingimages: true
            },
            retryAttempts: 3,
            retryDelay: 2000,
            respectRobots: true,
            maxConcurrentRequests: 3,
            dataVisualization: true,
            exportFormats: {
                csv: true,
                json: true,
                xml: true,
                xls: true
            },
            advancedLogging: true,
            enableCaptchas: false,
            customKeywords: {},
            templates: {},
            currentTab: 'logs',
            isCrawling: false,
            darkMode: false,
            crawlingStrategies: [
                'Basic Crawling',
                'Focused Crawling',
                'Incremental Crawling',
                'Parallel Crawling',
                'Distributed Crawling',
                'Adaptive Crawling',
                'Semantic Crawling',
                'Deep Crawling',
                'Shallow Crawling',
                'Politeness Crawling',
                'Reinforcement Learning-based Crawling',
                'Hybrid Crawling',
                'Parallel-incremental Crawling',
                'Data-driven Crawling',
                'Constraint-based Crawling',
                'URL Frontier-based Crawling',
                'Distributed Queue Frontier',
                'Reverse Crawling',
                'Sitemap-based Crawling',
                'Incremental Adaptive Crawling'
            ],
            selectedCrawlingStrategies: ['Basic Crawling']
        };

        let settings = { ...DEFAULT_SETTINGS, ...getSavedSettings() };

        function getSavedSettings() {
            return JSON.parse(GM_getValue('settings', JSON.stringify(DEFAULT_SETTINGS)));
        }

        function saveSettings(newSettings) {
            settings = { ...settings, ...newSettings };
            GM_setValue('settings', JSON.stringify(settings));
        }

        function resetSettings() {
            settings = { ...DEFAULT_SETTINGS };
            GM_setValue('settings', JSON.stringify(settings));
        }

        function saveTemplate(name) {
            settings.templates[name] = { ...settings };
            saveSettings({});
            Logger.log(`설정 템플릿 '${name}'이(가) 저장되었습니다.`, 'success');
        }

        function loadTemplate(name) {
            if (name in settings.templates) {
                settings = { ...settings, ...settings.templates[name] };
                saveSettings({});
                Logger.log(`설정 템플릿 '${name}'이(가) 불러와졌습니다.`, 'success');
            } else {
                Logger.log(`설정 템플릿 '${name}'을(를) 찾을 수 없습니다.`, 'error');
            }
        }

        function validateProxy(proxyUrl) {
            try {
                const url = new URL(proxyUrl);
                return ['http:', 'https:'].includes(url.protocol);
            } catch {
                return false;
            }
        }

        return {
            DEFAULT_SETTINGS,
            get settings() { return settings; },
            saveSettings,
            resetSettings,
            saveTemplate,
            loadTemplate,
            validateProxy
        };
    })();

    const Logger = (() => {
        let logs = JSON.parse(GM_getValue('logs', '[]'));

        function log(message, type = 'info') {
            const timestamp = new Date().toLocaleString();
            logs.push({ timestamp, message, type });
            if (logs.length > 1000) logs.shift();
            GM_setValue('logs', JSON.stringify(logs));
            UI.updateLogs();
            if (Config.settings.advancedLogging) {
                const styles = {
                    info: 'color: blue;',
                    warning: 'color: orange;',
                    error: 'color: red;',
                    success: 'color: green; font-weight: bold;',
                    debug: 'color: purple;'
                };
                console.log(`%c[${timestamp}] [${type.toUpperCase()}] ${message}`, styles[type] || '');
            }
        }

        function getLogs() {
            return logs;
        }

        function clearLogs() {
            logs = [];
            GM_setValue('logs', JSON.stringify(logs));
            UI.updateLogs();
        }

        return {
            log,
            getLogs,
            clearLogs
        };
    })();

    const Utils = (() => {
        const escapeMap = {
            html: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' },
            csv: { '"': '""' },
            xml: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }
        };

        function escape(str, type = 'html') {
            return String(str).replace(new RegExp(`[${Object.keys(escapeMap[type]).join('')}]`, 'g'), match => escapeMap[type][match]);
        }

        function capitalize(str) {
            return str.charAt(0).toUpperCase() + str.slice(1);
        }

        function delay(ms) {
            return new Promise(res => setTimeout(res, ms));
        }

        function generateColors(count) {
            return Array.from({ length: count }, (_, i) => `hsl(${i * (360 / count)}, 70%, 50%)`);
        }

        function parseCustomKeywords(text) {
            const lines = text.split('\n').map(l => l.trim()).filter(l => l);
            return lines.reduce((acc, line) => {
                const [cat, kws] = line.split(':').map(s => s.trim());
                if (cat && kws) acc[cat] = kws.split(',').map(kw => kw.trim()).filter(Boolean);
                return acc;
            }, {});
        }

        function formatCustomKeywords(ck) {
            return Object.entries(ck).map(([c, kws]) => `${c}:${kws.join(',')}`).join('\n');
        }

        function isValidURL(url) {
            try { new URL(url); return true; }
            catch { return false; }
        }

        return {
            escape,
            capitalize,
            delay,
            generateColors,
            parseCustomKeywords,
            formatCustomKeywords,
            isValidURL
        };
    })();

    const UI = (() => {
        const selectors = {
            container: '#crawler-container',
            header: '#crawler-header',
            tabs: '#crawler-tabs',
            content: '#crawler-content',
            minimizeBtn: '#crawler-minimize',
            darkModeToggle: '#dark-mode-toggle',
            tabsButtons: '.crawler-tab',
            logTab: '#tab-logs',
            dataTab: '#tab-data',
            settingsTab: '#tab-settings',
            previousCrawlsTab: '#tab-previous-crawls',
            templatesTab: '#tab-templates',
            testsTab: '#tab-tests',
            aiAssistantTab: '#tab-ai-assistant',
            strategiesTab: '#tab-strategies',
            logMessages: '#log-messages',
            dataTableBody: '#data-table-body',
            previousCrawlsBody: '#previous-crawls-body',
            templateSelect: '#template-select',
            newTemplateName: '#new-template-name',
            searchFilter: '#search-filter',
            errorMessage: '#error-message',
            successMessage: '#success-message',
            htmlModal: '#html-modal',
            modalHtmlContent: '#modal-html-content-rendered',
            testModal: '#test-modal',
            mochaContainer: '#mocha',
            dataChart: '#data-chart'
        };

        function createUI() {
            GM_addStyle(`
                /* Highlight.js default style */
                .hljs {
                    display: block;
                    overflow-x: auto;
                    padding: 0.5em;
                    background: #f0f0f0;
                }
                /* Dark mode adjustments for Highlight.js */
                ${selectors.container}.dark .hljs {
                    background: #2b2b2b;
                    color: #f1f1f1;
                }
                /* Container */
                ${selectors.container} {
                    position: fixed;
                    top: 50px;
                    right: 20px;
                    width: 400px;
                    max-width: 90%;
                    height: 70vh;
                    background: var(--bg-color, #fff);
                    border: 1px solid #ccc;
                    z-index: 10000;
                    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    overflow: hidden;
                    resize: both;
                    border-radius: 10px;
                    min-width: 300px;
                    min-height: 300px;
                    display: flex;
                    flex-direction: column;
                }
                ${selectors.container}.dark {
                    --bg-color: #2b2b2b;
                    color: #f1f1f1;
                    border-color: #555;
                }
                /* Header */
                ${selectors.header} {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    background: var(--header-bg, #f1f1f1);
                    border-bottom: 1px solid #ccc;
                    padding: 10px 15px;
                    cursor: move;
                    user-select: none;
                }
                ${selectors.container}.dark ${selectors.header} {
                    --header-bg: #3c3c3c;
                    border-color: #555;
                }
                /* Tabs */
                ${selectors.tabs} {
                    display: flex;
                    flex-wrap: wrap;
                    gap: 5px;
                }
                .crawler-tab {
                    padding: 8px 12px;
                    cursor: pointer;
                    font-weight: bold;
                    border-radius: 4px;
                    background: #e0e0e0;
                    transition: background 0.3s;
                }
                .crawler-tab:hover { background: #d5d5d5; }
                .crawler-tab.active { background: var(--active-tab-bg, #2196F3); color: #fff; }
                ${selectors.container}.dark .crawler-tab.active { background: #1E88E5; }
                ${selectors.container}.dark .crawler-tab:hover { background: #3c3c3c; }
                /* Control Buttons and Content */
                ${selectors.content} {
                    padding: 15px;
                    overflow-y: auto;
                    flex: 1;
                    background: var(--content-bg, #fff);
                }
                ${selectors.container}.dark ${selectors.content} {
                    --content-bg: #2b2b2b;
                }
                /* Tables */
                table {
                    width: 100%;
                    border-collapse: collapse;
                    table-layout: fixed;
                    word-wrap: break-word;
                }
                th, td {
                    padding: 8px;
                    text-align: left;
                    border-bottom: 1px solid #ddd;
                }
                th {
                    background-color: var(--th-bg, #f2f2f2);
                    cursor: pointer;
                    position: sticky;
                    top: 0;
                    z-index: 1;
                }
                ${selectors.container}.dark th { background-color: #444; }
                tr:hover { background-color: #f5f5f5; }
                ${selectors.container}.dark tr:hover { background-color: #555; }
                /* Progress Bar */
                .progress-bar {
                    width: 100%;
                    background-color: #f3f3f3;
                    border: 1px solid #ccc;
                    border-radius: 5px;
                    overflow: hidden;
                    margin-bottom: 10px;
                }
                .progress-bar-fill {
                    height: 20px;
                    background-color: #4caf50;
                    width: 0%;
                    transition: width 0.2s;
                }
                /* Spinner */
                .spinner {
                    border: 4px solid #f3f3f3;
                    border-top: 4px solid #3498db;
                    border-radius: 50%;
                    width: 24px;
                    height: 24px;
                    animation: spin 2s linear infinite;
                    margin-right: 10px;
                }
                @keyframes spin {
                    0% { transform: rotate(0deg); }
                    100% { transform: rotate(360deg); }
                }
                /* Modal */
                .modal {
                    display: none;
                    position: fixed;
                    z-index: 10001;
                    left: 0;
                    top: 0;
                    width: 100%;
                    height: 100%;
                    overflow: auto;
                    background-color: rgba(0,0,0,0.6);
                }
                .modal-content {
                    background-color: var(--modal-bg, #fff);
                    margin: 5% auto;
                    padding: 20px;
                    border: 1px solid #888;
                    width: 90%;
                    max-width: 800px;
                    max-height: 80%;
                    overflow-y: auto;
                    border-radius: 8px;
                    position: relative;
                }
                ${selectors.container}.dark .modal-content {
                    --modal-bg: #3c3c3c;
                    color: #f1f1f1;
                    border-color: #555;
                }
                .close {
                    color: #aaa;
                    position: absolute;
                    top: 10px;
                    right: 20px;
                    font-size: 28px;
                    font-weight: bold;
                    cursor: pointer;
                }
                .close:hover, .close:focus {
                    color: #000;
                    text-decoration: none;
                    cursor: pointer;
                }
                /* Control Buttons */
                .control-buttons {
                    margin: 10px 0;
                    display: flex;
                    flex-wrap: wrap;
                    gap: 10px;
                }
                .control-buttons button {
                    padding: 10px 15px;
                    cursor: pointer;
                    border: none;
                    background-color: #2196F3;
                    color: white;
                    border-radius: 4px;
                    font-size: 14px;
                    flex: 1 1 auto;
                    min-width: 80px;
                    transition: background-color 0.3s;
                }
                .control-buttons button:hover { background-color: #1976D2; }
                .control-buttons button:disabled {
                    background-color: #a5d6a7;
                    cursor: not-allowed;
                }
                /* Status Text */
                .status-text {
                    margin-top: 10px;
                    font-weight: bold;
                    font-size: 14px;
                }
                /* Settings Sections */
                .settings-section {
                    margin-bottom: 15px;
                }
                .settings-section label {
                    display: block;
                    margin-bottom: 5px;
                    font-weight: bold;
                }
                .settings-section input[type="text"],
                .settings-section input[type="number"],
                .settings-section textarea,
                .settings-section select {
                    width: 100%;
                    padding: 8px;
                    box-sizing: border-box;
                    border: 1px solid #ccc;
                    border-radius: 4px;
                    font-size: 14px;
                    background-color: var(--input-bg, #fff);
                    color: var(--input-color, #000);
                    transition: background-color 0.3s, color 0.3s;
                }
                ${selectors.container}.dark .settings-section input,
                ${selectors.container}.dark .settings-section select,
                ${selectors.container}.dark .settings-section textarea {
                    background-color: #555;
                    color: #f1f1f1;
                    border-color: #777;
                }
                .checkbox-group, .radio-group {
                    display: flex;
                    flex-wrap: wrap;
                    gap: 10px;
                }
                .checkbox-group label, .radio-group label {
                    font-size: 14px;
                }
                /* Tooltip */
                .tooltip {
                    position: relative;
                    display: inline-block;
                    cursor: help;
                    font-size: 14px;
                    margin-left: 5px;
                }
                .tooltip .tooltiptext {
                    visibility: hidden;
                    width: 220px;
                    background-color: #555;
                    color: #fff;
                    text-align: center;
                    border-radius: 6px;
                    padding: 5px;
                    position: absolute;
                    z-index: 1;
                    bottom: 125%;
                    left: 50%;
                    margin-left: -110px;
                    opacity: 0;
                    transition: opacity 0.3s;
                }
                .tooltip:hover .tooltiptext {
                    visibility: visible;
                    opacity: 1;
                }
                /* Resizer */
                ${selectors.container}::-webkit-resizer {
                    background: #f1f1f1;
                    border: none;
                }
                /* Full URL Display */
                td a.full-url {
                    display: block;
                    word-break: break-all;
                    position: relative;
                }
                td a.full-url:hover::after {
                    content: attr(href);
                    position: absolute;
                    background: #333;
                    color: #fff;
                    padding: 5px;
                    border-radius: 5px;
                    white-space: nowrap;
                    z-index: 10002;
                    top: 100%;
                    left: 0;
                }
                /* AI Assistant */
                #tab-ai-assistant {
                    display: none;
                }
                #ai-assistant-iframe {
                    width: 100%;
                    height: 600px;
                    border: none;
                    border-radius: 4px;
                }
                /* Responsive Design */
                @media (max-width: 768px) {
                    ${selectors.container} {
                        width: 95%;
                        height: 80vh;
                        top: 10px;
                        right: 2.5%;
                    }
                    .control-buttons {
                        flex-direction: column;
                    }
                }
                @media (max-width: 480px) {
                    ${selectors.header} {
                        flex-direction: column;
                        align-items: flex-start;
                    }
                    ${selectors.tabs} {
                        flex-wrap: nowrap;
                        overflow-x: auto;
                        scrollbar-width: thin;
                    }
                    .crawler-tab {
                        flex: 0 0 auto;
                    }
                }
                /* Enhanced Scrollbars */
                #crawler-tabs, #log-messages, #data-table-body, #previous-crawls-body, #mocha {
                    scrollbar-width: thin;
                    scrollbar-color: #888 #f1f1f1;
                }
                #crawler-tabs::-webkit-scrollbar,
                #log-messages::-webkit-scrollbar,
                #data-table-body::-webkit-scrollbar,
                #previous-crawls-body::-webkit-scrollbar,
                #mocha::-webkit-scrollbar {
                    width: 8px;
                    height: 8px;
                }
                #crawler-tabs::-webkit-scrollbar-track,
                #log-messages::-webkit-scrollbar-track,
                #data-table-body::-webkit-scrollbar-track,
                #previous-crawls-body::-webkit-scrollbar-track,
                #mocha::-webkit-scrollbar-track {
                    background: #f1f1f1;
                    border-radius: 4px;
                }
                #crawler-tabs::-webkit-scrollbar-thumb,
                #log-messages::-webkit-scrollbar-thumb,
                #data-table-body::-webkit-scrollbar-thumb,
                #previous-crawls-body::-webkit-scrollbar-thumb,
                #mocha::-webkit-scrollbar-thumb {
                    background: #888;
                    border-radius: 4px;
                }
                #crawler-tabs::-webkit-scrollbar-thumb:hover,
                #log-messages::-webkit-scrollbar-thumb:hover,
                #data-table-body::-webkit-scrollbar-thumb:hover,
                #previous-crawls-body::-webkit-scrollbar-thumb:hover,
                #mocha::-webkit-scrollbar-thumb:hover {
                    background: #555;
                }
            `);

            const container = $(`
                <div id="crawler-container">
                    <div id="crawler-header">
                        <div id="crawler-tabs">
                            <div class="crawler-tab" data-tab="logs" title="로그 보기">📜 로그</div>
                            <div class="crawler-tab" data-tab="data" title="수집된 데이터 보기">📊 데이터</div>
                            <div class="crawler-tab" data-tab="settings" title="설정 변경">⚙️ 설정</div>
                            <div class="crawler-tab" data-tab="previous-crawls" title="이전 크롤링 기록">📁 기록</div>
                            <div class="crawler-tab" data-tab="templates" title="설정 템플릿">📂 템플릿</div>
                            <div class="crawler-tab" data-tab="tests" title="테스트 실행">🧪 테스트</div>
                            <div class="crawler-tab" data-tab="ai-assistant" title="AI 어시스턴트">🧠 AI</div>
                            <div class="crawler-tab" data-tab="strategies" title="크롤링 전략">🌀 전략</div>
                        </div>
                        <div>
                            <button id="dark-mode-toggle" class="dark-mode-toggle" title="테마 전환">🌙</button>
                            <button id="crawler-minimize" title="창 최소화">━</button>
                        </div>
                    </div>
                    <div id="crawler-content">
                        <div id="tab-logs">
                            <div class="progress-bar">
                                <div class="progress-bar-fill" style="width: 0%;"></div>
                            </div>
                            <div class="status-text">진행률: 0% (0/0 페이지)</div>
                            <div class="control-buttons">
                                <button id="start-crawling" title="크롤링을 시작합니다.">▶ 시작</button>
                                <button id="stop-crawling" title="크롤링을 중지합니다." disabled>■ 중지</button>
                                ${Config.settings.exportFormats.csv ? `<button id="download-csv" title="CSV 다운로드">📥 CSV</button>` : ''}
                                ${Config.settings.exportFormats.json ? `<button id="download-json" title="JSON 다운로드">📥 JSON</button>` : ''}
                                ${Config.settings.exportFormats.xml ? `<button id="download-xml" title="XML 다운로드">📥 XML</button>` : ''}
                                ${Config.settings.exportFormats.xls ? `<button id="download-xls" title="XLS 다운로드">📥 XLS</button>` : ''}
                                <button id="reset-data" title="모든 크롤링 데이터를 초기화합니다.">🗑️ 초기화</button>
                            </div>
                            <div id="log-messages" style="height: 200px; overflow-y: auto; background: var(--log-bg, #f9f9f9); padding: 10px; border: 1px solid #ccc; border-radius: 4px;">
                                로그 내용이 여기에 표시됩니다.
                            </div>
                            <div id="error-message" class="error-message" style="display: none;"></div>
                            <div id="success-message" class="success-message" style="display: none;"></div>
                        </div>
                        <div id="tab-data" style="display: none;">
                            <input type="text" id="search-filter" placeholder="검색 필터..." style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;">
                            <table>
                                <thead>
                                    <tr>
                                        ${Config.settings.dataFields.name ? '<th data-sort="name">사이트 이름</th>' : ''}
                                        ${Config.settings.dataFields.url ? '<th data-sort="url">주소</th>' : ''}
                                        ${Config.settings.dataFields.description ? '<th data-sort="description">설명</th>' : ''}
                                        ${Config.settings.dataFields.html ? '<th>📄 HTML 내용</th>' : ''}
                                        ${Config.settings.dataFields.category ? '<th data-sort="category">분류</th>' : ''}
                                        ${Config.settings.dataFields.quality ? '<th data-sort="quality">품질점수</th>' : ''}
                                        ${Config.settings.dataFields.language ? '<th data-sort="language">언어</th>' : ''}
                                        ${Config.settings.dataFields.wordCount ? '<th data-sort="wordCount">단어 수</th>' : ''}
                                        ${Config.settings.dataFields.metaTags ? '<th>메타 태그</th>' : ''}
                                        ${Config.settings.dataFields.headings ? '<th>헤딩</th>' : ''}
                                    </tr>
                                </thead>
                                <tbody id="data-table-body"></tbody>
                            </table>
                            ${Config.settings.dataVisualization ? `
                                <div id="data-visualization">
                                    <h3>데이터 시각화</h3>
                                    <canvas id="data-chart"></canvas>
                                </div>
                            ` : ''}
                        </div>
                        <div id="tab-settings" style="display: none;">
                            <h3>설정</h3>
                            ${createSettingsSections()}
                            <div class="settings-section" style="display: flex; gap: 10px;">
                                <button id="save-settings" style="padding: 10px 20px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;">저장</button>
                                <button id="reset-settings" style="padding: 10px 20px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;">초기화</button>
                            </div>
                        </div>
                        <div id="tab-templates" style="display: none;">
                            <h3>설정 템플릿</h3>
                            <div class="settings-section">
                                <label for="new-template-name">새 템플릿 이름:</label>
                                <input type="text" id="new-template-name" placeholder="템플릿 이름을 입력하세요">
                                <button id="save-template" style="margin-top: 10px; padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;">📁 템플릿 저장</button>
                            </div>
                            <div class="settings-section">
                                <label for="template-select">기존 템플릿:</label>
                                <select id="template-select" style="width: 100%; padding: 8px; margin-top: 10px; border: 1px solid #ccc; border-radius: 4px;">
                                    <option value="" disabled selected>템플릿 선택</option>
                                </select>
                                <button id="load-template" style="margin-top: 10px; padding: 8px 12px; background-color: #2196F3; color: white; border-radius: 4px; font-size: 14px;">📂 템플릿 불러오기</button>
                                <button id="delete-template" style="margin-top: 5px; padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;">템플릿 삭제</button>
                            </div>
                        </div>
                        <div id="tab-previous-crawls" style="display: none;">
                            <h3>이전 크롤링 기록</h3>
                            <button id="clear-previous-crawls" style="padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">기록 초기화</button>
                            <table>
                                <thead>
                                    <tr>
                                        <th>날짜</th>
                                        <th>크롤링 수</th>
                                        <th>상태</th>
                                    </tr>
                                </thead>
                                <tbody id="previous-crawls-body"></tbody>
                            </table>
                        </div>
                        <div id="tab-tests" style="display: none;">
                            <button id="run-tests" style="padding: 12px 24px; background-color: #FFC107; color: white; border-radius: 4px; font-size: 16px;">테스트 실행</button>
                        </div>
                        <div id="tab-ai-assistant" style="display: none;">
                            <h3>AI 어시스턴트</h3>
                            <iframe id="ai-assistant-iframe" src="https://huggingface.co/spaces/gpt4all/chat"></iframe>
                        </div>
                        <div id="tab-strategies" style="display: none;">
                            <h3>크롤링 전략</h3>
                            <div class="settings-section">
                                <label for="crawling-strategies-select">사용할 크롤링 전략 선택:</label>
                                <select id="crawling-strategies-select" multiple size="10">
                                </select>
                                <button id="update-strategies" style="margin-top: 10px; padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;">전략 업데이트</button>
                            </div>
                            <p>선택한 크롤링 전략에 따라 크롤링 방식을 조정합니다. 여러 전략을 조합할 수 있습니다.</p>
                        </div>
                        <div id="html-modal" class="modal">
                            <div class="modal-content">
                                <span class="close">&times;</span>
                                <iframe id="modal-html-content-rendered"></iframe>
                            </div>
                        </div>
                        <div id="test-modal" class="modal">
                            <div class="modal-content">
                                <span class="close">&times;</span>
                                <div id="mocha"></div>
                            </div>
                        </div>
                    </div>
                </div>
            `);
            $('body').append(container);
            populateCrawlingStrategies();
            bindEvents();
            makeDraggable();
        }

        function createSettingsSections() {
            const sections = [
                {
                    label: '크롤링 딜레이 (ms)',
                    id: 'delay-input',
                    type: 'number',
                    value: Config.settings.delay,
                    min: 500,
                    step: 500,
                    tooltip: '기본 딜레이과 딜레이 랜덤 값을 조합하여 요청 사이의 딜레이를 설정합니다.'
                },
                {
                    label: '딜레이 랜덤 범위 (ms)',
                    id: 'delay-random-input',
                    type: 'number',
                    value: Config.settings.delayRandom,
                    min: 0,
                    step: 500,
                    tooltip: '딜레이에 무작위 요소를 추가하여 보다 자연스러운 요청을 합니다.'
                },
                {
                    label: '최대 크롤링 페이지 수',
                    id: 'max-pages-input',
                    type: 'number',
                    value: Config.settings.maxPages,
                    min: 1,
                    step: 1,
                    tooltip: '크롤링할 최대 페이지 수를 설정하세요.'
                },
                {
                    label: '사용자 에이전트(User-Agent)',
                    id: 'user-agent-select',
                    type: 'select-multiple',
                    options: Config.settings.userAgents.map((ua, idx) => ({
                        value: idx,
                        text: ua,
                        selected: idx === Config.settings.userAgentIndex
                    })),
                    buttons: [
                        { id: 'add-user-agent', text: '추가', bgColor: '#2196F3' },
                        { id: 'remove-user-agent', text: '제거', bgColor: '#f44336' }
                    ],
                    tooltip: '크롤링에 사용할 사용자 에이전트를 선택하거나 추가할 수 있습니다.'
                },
                {
                    label: '콘텐츠 유형 포함',
                    id: 'content-include-input',
                    type: 'text',
                    value: Config.settings.contentTypes.include.join(', '),
                    tooltip: '크롤링할 콘텐츠 유형을 쉼표로 구분하여 입력하세요.'
                },
                {
                    label: '콘텐츠 유형 제외',
                    id: 'content-exclude-input',
                    type: 'text',
                    value: Config.settings.contentTypes.exclude.join(', '),
                    tooltip: '크롤링에서 제외할 콘텐츠 유형을 쉼표로 구분하여 입력하세요.'
                },
                {
                    label: '크롤링 타임아웃 (ms)',
                    id: 'timeout-input',
                    type: 'number',
                    value: Config.settings.timeout,
                    min: 1000,
                    step: 1000,
                    tooltip: '요청이 완료되기까지 기다릴 최대 시간을 밀리초 단위로 설정합니다.'
                },
                {
                    label: '포함할 키워드',
                    id: 'include-keywords-input',
                    type: 'textarea',
                    value: Config.settings.includeKeywords.join('\n'),
                    tooltip: '크롤링할 페이지에 반드시 포함되어야 하는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.'
                },
                {
                    label: '제외할 키워드',
                    id: 'exclude-keywords-input',
                    type: 'textarea',
                    value: Config.settings.excludeKeywords.join('\n'),
                    tooltip: '크롤링할 페이지에 포함되면 안 되는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.'
                },
                {
                    label: '포함할 키워드 (정규 표현식)',
                    id: 'regex-include-input',
                    type: 'textarea',
                    value: Config.settings.regexInclude.join('\n'),
                    tooltip: '포함할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.'
                },
                {
                    label: '제외할 키워드 (정규 표현식)',
                    id: 'regex-exclude-input',
                    type: 'textarea',
                    value: Config.settings.regexExclude.join('\n'),
                    tooltip: '제외할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.'
                },
                {
                    label: '언어 필터링',
                    id: 'language-select',
                    type: 'select-multiple',
                    options: [
                        { value: 'en', text: '영어 (en)' }, { value: 'ko', text: '한국어 (ko)' },
                        { value: 'zh', text: '중국어 (zh)' }, { value: 'ja', text: '일본어 (ja)' },
                        { value: 'ru', text: '러시아어 (ru)' }, { value: 'es', text: '스페인어 (es)' },
                        { value: 'fr', text: '프랑스어 (fr)' }, { value: 'de', text: '독일어 (de)' },
                        { value: 'it', text: '이탈리아어 (it)' }, { value: 'pt', text: '포르투갈어 (pt)' },
                        { value: 'ar', text: '아랍어 (ar)' }, { value: 'hi', text: '힌디어 (hi)' },
                        { value: 'bn', text: '벵골어 (bn)' }, { value: 'pa', text: '펀자브어 (pa)' },
                        { value: 'nl', text: '네덜란드어 (nl)' }, { value: 'sv', text: '스웨덴어 (sv)' },
                        { value: 'tr', text: '터키어 (tr)' }, { value: 'vi', text: '베트남어 (vi)' },
                        { value: 'pl', text: '폴란드어 (pl)' }, { value: 'th', text: '태국어 (th)' }
                    ].map(lang => ({
                        value: lang.value,
                        text: lang.text,
                        selected: Config.settings.language.includes(lang.value)
                    })),
                    tooltip: '크롤링할 언어를 선택하세요. (복수 선택 가능)'
                },
                {
                    label: '최소 단어 수',
                    id: 'min-word-count-input',
                    type: 'number',
                    value: Config.settings.minWordCount,
                    min: 10,
                    step: 10,
                    tooltip: '수집할 페이지의 최소 단어 수를 설정하세요.'
                },
                {
                    label: '최대 단어 수',
                    id: 'max-word-count-input',
                    type: 'number',
                    value: Config.settings.maxWordCount,
                    min: 100,
                    step: 100,
                    tooltip: '수집할 페이지의 최대 단어 수를 설정하세요.'
                },
                {
                    label: '최대 크롤링 깊이',
                    id: 'max-depth-input',
                    type: 'number',
                    value: Config.settings.maxDepth,
                    min: 1,
                    step: 1,
                    tooltip: '링크를 추적할 최대 깊이를 설정하세요.'
                },
                {
                    label: '프록시 서버',
                    id: 'proxy-input',
                    type: 'text',
                    value: Config.settings.proxy,
                    placeholder: 'http://proxyserver:port',
                    tooltip: '프록시 서버 주소를 입력하세요. 예: http://proxyserver:port'
                },
                {
                    label: '수집할 데이터 필드',
                    id: '',
                    type: 'checkbox-group',
                    options: Object.keys(Config.settings.dataFields).map(field => ({
                        id: `field-${field}`,
                        text: Utils.capitalize(getFieldName(field)),
                        checked: Config.settings.dataFields[field]
                    })),
                    tooltip: '수집할 데이터 필드를 선택하세요.'
                },
                {
                    label: '재시도 횟수',
                    id: 'retry-attempts-input',
                    type: 'number',
                    value: Config.settings.retryAttempts,
                    min: 0,
                    step: 1,
                    tooltip: '요청 실패 시 재시도할 횟수를 설정하세요.'
                },
                {
                    label: '재시도 딜레이 (ms)',
                    id: 'retry-delay-input',
                    type: 'number',
                    value: Config.settings.retryDelay,
                    min: 1000,
                    step: 500,
                    tooltip: '재시도 시 대기할 시간을 밀리초 단위로 설정하세요.'
                },
                {
                    label: '크롤링 스케줄링',
                    id: 'schedule-enabled',
                    type: 'checkbox',
                    checked: Config.settings.schedule.enabled,
                    tooltip: '크롤링 스케줄링을 활성화합니다.'
                },
                {
                    label: '시작 시간 (HH:MM)',
                    id: 'start-time-input',
                    type: 'time',
                    value: Config.settings.schedule.startTime,
                    display: Config.settings.schedule.enabled,
                    tooltip: '크롤링을 시작할 시간을 설정하세요.'
                },
                {
                    label: '크롤링 간격 (분)',
                    id: 'interval-input',
                    type: 'number',
                    value: Config.settings.schedule.interval / 60000,
                    min: 1,
                    step: 1,
                    display: Config.settings.schedule.enabled,
                    tooltip: '크롤링 작업 간의 간격을 분 단위로 설정하세요.'
                },
                {
                    label: '특정 요일',
                    id: 'specific-days-input',
                    type: 'select-multiple',
                    options: [
                        { value: '0', text: '일요일' },
                        { value: '1', text: '월요일' },
                        { value: '2', text: '화요일' },
                        { value: '3', text: '수요일' },
                        { value: '4', text: '목요일' },
                        { value: '5', text: '금요일' },
                        { value: '6', text: '토요일' }
                    ].map(day => ({
                        value: day.value,
                        text: day.text,
                        selected: Config.settings.schedule.specificDays.includes(parseInt(day.value))
                    })),
                    display: Config.settings.schedule.enabled,
                    tooltip: '크롤링을 수행할 특정 요일을 선택하세요. (복수 선택 가능)'
                },
                {
                    label: '크롤링 전략 선택',
                    id: '',
                    type: 'checkbox-group',
                    options: Config.settings.crawlingStrategies.map(strategy => ({
                        id: `strategy-${strategy}`,
                        text: strategy,
                        checked: Config.settings.selectedCrawlingStrategies.includes(strategy)
                    })),
                    tooltip: '사용할 크롤링 전략을 선택하세요. 여러 전략을 동시에 사용할 수 있습니다.'
                },
                {
                    label: '크롤링할 검색 엔진 선택',
                    id: '',
                    type: 'checkbox-group',
                    options: Object.keys(Config.settings.searchEngines).map(engine => ({
                        id: `engine-${engine}`,
                        text: Utils.capitalize(engine),
                        checked: Config.settings.searchEngines[engine]
                    })),
                    tooltip: '크롤링에 사용할 검색 엔진을 선택하세요.'
                },
                {
                    label: '블랙리스트 도메인',
                    id: 'blacklist-input',
                    type: 'textarea',
                    value: Config.settings.blacklist.join('\n'),
                    tooltip: '크롤링에서 제외할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.'
                },
                {
                    label: '화이트리스트 도메인',
                    id: 'whitelist-input',
                    type: 'textarea',
                    value: Config.settings.whitelist.join('\n'),
                    tooltip: '크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.'
                },
                {
                    label: 'robots.txt 준수',
                    id: 'respect-robots-checkbox',
                    type: 'checkbox',
                    checked: Config.settings.respectRobots,
                    tooltip: '대상 사이트의 robots.txt 규칙을 준수하여 크롤링을 진행합니다.'
                },
                {
                    label: '동시 요청 최대 수',
                    id: 'max-concurrent-requests-input',
                    type: 'number',
                    value: Config.settings.maxConcurrentRequests,
                    min: 1,
                    step: 1,
                    tooltip: '한 번에 요청할 수 있는 최대 동시 요청 수를 설정하세요.'
                },
                {
                    label: '데이터 시각화 활성화',
                    id: 'data-visualization-checkbox',
                    type: 'checkbox',
                    checked: Config.settings.dataVisualization,
                    tooltip: '수집된 데이터를 시각화하여 그래프로 표시합니다.'
                },
                {
                    label: 'CAPTCHA 처리 활성화',
                    id: 'enable-captchas-checkbox',
                    type: 'checkbox',
                    checked: Config.settings.enableCaptchas,
                    tooltip: 'CAPTCHA가 감지되었을 때 자동 처리를 활성화합니다. (제한적)'
                },
                {
                    label: '데이터 내보내기 형식',
                    id: '',
                    type: 'checkbox-group',
                    options: Object.keys(Config.settings.exportFormats).map(format => ({
                        id: `export-${format}`,
                        text: format.toUpperCase(),
                        checked: Config.settings.exportFormats[format]
                    })),
                    tooltip: '데이터 내보내기 형식을 선택하세요.'
                },
                {
                    label: '고급 로깅 활성화',
                    id: 'advanced-logging-checkbox',
                    type: 'checkbox',
                    checked: Config.settings.advancedLogging,
                    tooltip: '추가적인 로깅 정보를 활성화합니다.'
                },
                {
                    label: '사용자 지정 키워드 분류',
                    id: 'custom-keywords-input',
                    type: 'textarea',
                    value: Utils.formatCustomKeywords(Config.settings.customKeywords),
                    tooltip: '사용자 지정 키워드를 추가하여 데이터 분류를 향상시킵니다. 형식: 카테고리:키워드1,키워드2,...'
                }
            ];

            return sections.map(sec => {
                if (sec.type === 'checkbox-group') {
                    return `
                        <div class="settings-section">
                            <label>${sec.label}:
                                ${sec.tooltip ? `<span class="tooltip">ℹ️
                                    <span class="tooltiptext">${sec.tooltip}</span>
                                </span>` : ''}
                            </label>
                            <div class="${sec.label === '크롤링 전략 선택' ? 'checkbox-group strategies-group' : 'checkbox-group'}">
                                ${sec.options.map(opt => `<label><input type="checkbox" id="${opt.id}" ${opt.checked ? 'checked' : ''}> ${opt.text}</label>`).join('')}
                            </div>
                        </div>
                    `;
                }
                if (sec.type === 'select-multiple') {
                    return `
                        <div class="settings-section">
                            <label for="${sec.id}">${sec.label}:
                                ${sec.tooltip ? `<span class="tooltip">ℹ️
                                    <span class="tooltiptext">${sec.tooltip}</span>
                                </span>` : ''}
                            </label>
                            <select id="${sec.id}" multiple size="${sec.options.length || 5}">
                                ${sec.options.map(opt => `<option value="${opt.value}" ${opt.selected ? 'selected' : ''}>${opt.text}</option>`).join('')}
                            </select>
                        </div>
                    `;
                }
                if (sec.type === 'checkbox') {
                    return `
                        <div class="settings-section">
                            <label>
                                <input type="checkbox" id="${sec.id}" ${sec.checked ? 'checked' : ''}>
                                ${sec.label}
                                ${sec.tooltip ? `<span class="tooltip">ℹ️
                                    <span class="tooltiptext">${sec.tooltip}</span>
                                </span>` : ''}
                            </label>
                        </div>
                    `;
                }
                if (sec.type === 'textarea') {
                    return `
                        <div class="settings-section">
                            <label for="${sec.id}">${sec.label}:
                                ${sec.tooltip ? `<span class="tooltip">ℹ️
                                    <span class="tooltiptext">${sec.tooltip}</span>
                                </span>` : ''}
                            </label>
                            <textarea id="${sec.id}" rows="3">${sec.value}</textarea>
                        </div>
                    `;
                }
                if (sec.type === 'select') {
                    return `
                        <div class="settings-section">
                            <label for="${sec.id}">${sec.label}:</label>
                            <select id="${sec.id}" ${sec.multiple ? 'multiple' : ''}>
                                ${sec.options.map(opt => `<option value="${opt.value}" ${opt.selected ? 'selected' : ''}>${opt.text}</option>`).join('')}
                            </select>
                        </div>
                    `;
                }
                return `
                    <div class="settings-section">
                        <label for="${sec.id}">${sec.label}:
                            ${sec.tooltip ? `<span class="tooltip">ℹ️
                                <span class="tooltiptext">${sec.tooltip}</span>
                            </span>` : ''}
                        </label>
                        <input type="${sec.type}" id="${sec.id}" value="${sec.value}" ${sec.min ? `min="${sec.min}"` : ''} ${sec.step ? `step="${sec.step}"` : ''} ${sec.placeholder ? `placeholder="${sec.placeholder}"` : ''} ${sec.display === false ? 'style="display: none;"' : ''}>
                        ${sec.buttons ? sec.buttons.map(btn => `<button id="${btn.id}" style="margin-top: 10px; padding: 6px 10px; background-color: ${btn.bgColor}; color: white; border-radius: 4px; font-size: 12px;">${btn.text}</button>`).join('') : ''}
                    </div>
                `;
            }).join('');
        }

        function bindEvents() {
            $(selectors.minimizeBtn).on('click', toggleMinimize);
            $(selectors.tabsButtons).on('click', switchTab);
            $('#save-settings').on('click', saveSettingsHandler);
            $('#reset-settings').on('click', resetSettingsHandler);
            $('#add-user-agent').on('click', addUserAgent);
            $('#remove-user-agent').on('click', removeUserAgent);
            $('#download-csv').on('click', Download.downloadCSV);
            $('#download-json').on('click', Download.downloadJSON);
            $('#download-xml').on('click', Download.downloadXML);
            $('#download-xls').on('click', Download.downloadXLS);
            $(selectors.searchFilter).on('input', filterData);
            $('#schedule-enabled').on('change', toggleScheduleOptions);
            $('#data-visualization-checkbox').on('change', toggleDataVisualization);
            $('#enable-captchas-checkbox').on('change', toggleCaptchaHandling);
            $('#save-template').on('click', saveTemplateHandler);
            $('#load-template').on('click', loadTemplateHandler);
            $('#delete-template').on('click', deleteTemplateHandler);
            $(selectors.templateSelect).on('change', selectTemplateHandler);
            $('#run-tests').on('click', Testing.runTests);
            $('#clear-previous-crawls').on('click', clearPreviousCrawls);
            $('#reset-data').on('click', resetData);
            $(selectors.darkModeToggle).on('click', toggleDarkMode);
            $('.close').on('click', () => $('.modal').hide());
            $(window).on('click', e => { if ($(e.target).hasClass('modal')) $('.modal').hide(); });
            $('#start-crawling').on('click', Crawling.startCrawling);
            $('#stop-crawling').on('click', Crawling.stopCrawling);
            $('#update-strategies').on('click', updateCrawlingStrategies);
            $(document).on('keydown', handleShortcuts);
            $('#proxy-input').on('input', validateProxyInput);
            $('input[type="checkbox"], input[type="radio"]').on('change', toggleDataField);
            $('.view-html').off('click').on('click', function() {
                const htmlContent = decodeURIComponent($(this).data('html'));
                $('#modal-html-content-rendered').attr('srcdoc', htmlContent);
                $('#html-modal').show();
            });
        }

        function toggleMinimize() {
            $(selectors.content).toggle();
            $(selectors.minimizeBtn).text($(selectors.content).is(':hidden') ? '☐' : '━');
            Logger.log(`크롤러 창이 ${$(selectors.content).is(':hidden') ? '최소화' : '확대'}되었습니다.`, 'info');
        }

        function switchTab() {
            const tab = $(this).data('tab');
            $(selectors.tabsButtons).removeClass('active');
            $(this).addClass('active');
            $(selectors.content).children('div').hide();
            $(`#tab-${tab.replace(/\s+/g, '-').toLowerCase()}`).show();
            Config.saveSettings({ currentTab: tab.replace(/\s+/g, '-').toLowerCase() });
        }

        function populateCrawlingStrategies() {
            Config.settings.crawlingStrategies.forEach(strategy => {
                $('#crawling-strategies-select').append(`<option value="${strategy}" ${Config.settings.selectedCrawlingStrategies.includes(strategy) ? 'selected' : ''}>${strategy}</option>`);
            });
        }

        function updateUI() {
            const currentTab = Config.settings.currentTab || 'logs';
            $(`.crawler-tab[data-tab="${currentTab.replace(/-/g, ' ')}"]`).addClass('active');
            $(selectors.content).children('div').hide();
            $(`#tab-${currentTab}`).show();
            updateLogs();
            updateDataTable();
            updatePreviousCrawlsUI();
            updateTemplatesUI();
            populateCrawlingStrategies();
            if (Config.settings.dataVisualization) DataVisualization.renderChart();
            applyDarkMode();
        }

        function updateLogs() {
            const logs = Logger.getLogs();
            $(selectors.logMessages).html(logs.map(l => `<div>[${l.timestamp}] [${l.type.toUpperCase()}] ${Utils.escape(l.message)}</div>`).join(''));
            const progress = GM_getValue('progress', 0);
            const currentPage = GM_getValue('currentPage', 0);
            const totalPages = Config.settings.maxPages;
            $('.progress-bar-fill').css('width', `${progress.toFixed(2)}%`);
            $('.status-text').text(`진행률: ${progress.toFixed(2)}% (${currentPage}/${totalPages} 페이지)`);
            const latestLog = logs[logs.length -1];
            if (latestLog) {
                if (['error', 'warning'].includes(latestLog.type)) {
                    $(selectors.errorMessage).text(latestLog.message).show().delay(5000).fadeOut();
                }
                if (['success', 'info'].includes(latestLog.type)) {
                    $(selectors.successMessage).text(latestLog.message).show().delay(5000).fadeOut();
                }
            }
        }

        function updateDataTable() {
            const data = JSON.parse(GM_getValue('data', '[]'));
            const filter = $(selectors.searchFilter).val().toLowerCase();
            const filtered = filter ? data.filter(item => Object.keys(Config.settings.dataFields).some(f => Config.settings.dataFields[f] && String(item[f]).toLowerCase().includes(filter))) : data;
            $(selectors.dataTableBody).html(filtered.map(item => `
                <tr>
                    ${Config.settings.dataFields.name ? `<td>${Utils.escape(item.name)}</td>` : ''}
                    ${Config.settings.dataFields.url ? `<td><a href="${Utils.escape(item.url)}" target="_blank" class="full-url">${Utils.escape(item.url)}</a></td>` : ''}
                    ${Config.settings.dataFields.description ? `<td>${Utils.escape(item.description)}</td>` : ''}
                    ${Config.settings.dataFields.html ? `<td><button class="view-html" data-html="${encodeURIComponent(item.html)}">📄 보기</button></td>` : ''}
                    ${Config.settings.dataFields.category ? `<td>${Utils.escape(item.category)}</td>` : ''}
                    ${Config.settings.dataFields.quality ? `<td>${Utils.escape(item.quality.toString())}</td>` : ''}
                    ${Config.settings.dataFields.language ? `<td>${Utils.escape(item.language)}</td>` : ''}
                    ${Config.settings.dataFields.wordCount ? `<td>${Utils.escape(item.wordCount.toString())}</td>` : ''}
                    ${Config.settings.dataFields.metaTags ? `<td>${Utils.escape(JSON.stringify(item.metaTags))}</td>` : ''}
                    ${Config.settings.dataFields.headings ? `<td>${Utils.escape(item.headings.join(', '))}</td>` : ''}
                </tr>
            `).join(''));
            $('.view-html').off('click').on('click', function() {
                const htmlContent = decodeURIComponent($(this).data('html'));
                $(selectors.modalHtmlContent).attr('srcdoc', htmlContent);
                $(selectors.htmlModal).show();
            });
            if (Config.settings.dataVisualization) DataVisualization.renderChart();
        }

        function updatePreviousCrawlsUI() {
            const previousCrawls = JSON.parse(GM_getValue('previousCrawls', '[]'));
            $(selectors.previousCrawlsBody).html(previousCrawls.map(crawl => `
                <tr>
                    <td>${Utils.escape(crawl.date)}</td>
                    <td>${Utils.escape(crawl.count.toString())}</td>
                    <td>${Utils.escape(crawl.status)}</td>
                </tr>
            `).join(''));
        }

        function updateTemplatesUI() {
            $(selectors.templateSelect).empty().append(`<option value="" disabled selected>템플릿 선택</option>`);
            Object.keys(Config.settings.templates).forEach(t => {
                $(selectors.templateSelect).append(`<option value="${t}">${t}</option>`);
            });
        }

        function addUserAgent() {
            const ua = prompt("새 사용자 에이전트를 입력하세요:");
            if (ua) {
                Config.saveSettings({ userAgents: [...Config.settings.userAgents, ua] });
                updateUserAgentSelect();
                Logger.log('새 사용자 에이전트가 추가되었습니다.', 'success');
            }
        }

        function removeUserAgent() {
            const selected = $('#user-agent-select').val();
            if (selected && selected.length) {
                selected.sort((a, b) => b - a).forEach(idx => Config.settings.userAgents.splice(idx, 1));
                Config.saveSettings({ userAgents: Config.settings.userAgents, userAgentIndex: 0 });
                updateUserAgentSelect();
                Logger.log('선택한 사용자 에이전트가 제거되었습니다.', 'success');
            } else {
                alert('제거할 사용자 에이전트를 선택하세요.');
            }
        }

        function updateUserAgentSelect() {
            $('#user-agent-select').empty();
            Config.settings.userAgents.forEach((ua, idx) => {
                $('#user-agent-select').append(`<option value="${idx}" ${idx === Config.settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`);
            });
        }

        function saveSettingsHandler() {
            const newSettings = {
                delay: parseInt($('#delay-input').val(), 10) || Config.DEFAULT_SETTINGS.delay,
                delayRandom: parseInt($('#delay-random-input').val(), 10) || Config.DEFAULT_SETTINGS.delayRandom,
                maxPages: parseInt($('#max-pages-input').val(), 10) || Config.DEFAULT_SETTINGS.maxPages,
                userAgentIndex: parseInt($('#user-agent-select').val(), 10) || 0,
                contentTypes: {
                    include: $('#content-include-input').val().split(',').map(ct => ct.trim()).filter(Boolean),
                    exclude: $('#content-exclude-input').val().split(',').map(ct => ct.trim()).filter(Boolean)
                },
                timeout: parseInt($('#timeout-input').val(), 10) || Config.DEFAULT_SETTINGS.timeout,
                includeKeywords: $('#include-keywords-input').val().split('\n').map(k => k.trim()).filter(Boolean),
                excludeKeywords: $('#exclude-keywords-input').val().split('\n').map(k => k.trim()).filter(Boolean),
                regexInclude: $('#regex-include-input').val().split('\n').map(r => r.trim()).filter(Boolean),
                regexExclude: $('#regex-exclude-input').val().split('\n').map(r => r.trim()).filter(Boolean),
                language: $('#language-select').val() || [],
                minWordCount: parseInt($('#min-word-count-input').val(), 10) || Config.DEFAULT_SETTINGS.minWordCount,
                maxWordCount: parseInt($('#max-word-count-input').val(), 10) || Config.DEFAULT_SETTINGS.maxWordCount,
                maxDepth: parseInt($('#max-depth-input').val(), 10) || Config.DEFAULT_SETTINGS.maxDepth,
                proxy: $('#proxy-input').val().trim(),
                dataFields: Object.fromEntries(['name','url','description','html','category','quality','language','wordCount','metaTags','headings'].map(f => [`${f}`, $(`#field-${f}`).is(':checked') ])),
                retryAttempts: parseInt($('#retry-attempts-input').val(), 10) || Config.DEFAULT_SETTINGS.retryAttempts,
                retryDelay: parseInt($('#retry-delay-input').val(), 10) || Config.DEFAULT_SETTINGS.retryDelay,
                schedule: {
                    enabled: $('#schedule-enabled').is(':checked'),
                    startTime: $('#start-time-input').val() || Config.DEFAULT_SETTINGS.schedule.startTime,
                    interval: (parseInt($('#interval-input').val(), 10) || (Config.DEFAULT_SETTINGS.schedule.interval / 60000)) * 60000,
                    specificDays: $('#specific-days-input').val() ? $('#specific-days-input').val().map(d => parseInt(d, 10)) : []
                },
                selectedCrawlingStrategies: $('#crawling-strategies-select').val() || Config.DEFAULT_SETTINGS.selectedCrawlingStrategies,
                searchEngines: Object.fromEntries(Object.keys(Config.settings.searchEngines).map(engine => [`${engine}`, $(`#engine-${engine}`).is(':checked') ])),
                blacklist: $('#blacklist-input').val().split('\n').map(d => d.trim()).filter(Boolean),
                whitelist: $('#whitelist-input').val().split('\n').map(d => d.trim()).filter(Boolean),
                respectRobots: $('#respect-robots-checkbox').is(':checked'),
                maxConcurrentRequests: parseInt($('#max-concurrent-requests-input').val(), 10) || Config.DEFAULT_SETTINGS.maxConcurrentRequests,
                dataVisualization: $('#data-visualization-checkbox').is(':checked'),
                exportFormats: Object.fromEntries(['csv','json','xml','xls'].map(fmt => [`${fmt}`, $(`#export-${fmt}`).is(':checked') ])),
                advancedLogging: $('#advanced-logging-checkbox').is(':checked'),
                enableCaptchas: $('#enable-captchas-checkbox').is(':checked'),
                customKeywords: Utils.parseCustomKeywords($('#custom-keywords-input').val())
            };

            if (newSettings.proxy && !Config.validateProxy(newSettings.proxy)) {
                $(selectors.errorMessage).text('유효하지 않은 프록시 URL입니다.').show();
                setTimeout(() => $(selectors.errorMessage).fadeOut(), 5000);
                return;
            }

            Config.saveSettings(newSettings);
            Crawling.updateCrawlSettings();
            Logger.log('설정이 저장되었습니다.', 'success');
            if (newSettings.schedule.enabled) Scheduler.scheduleCrawling();
            updateDataTable();
            if (newSettings.dataVisualization) DataVisualization.renderChart();
        }

        function resetSettingsHandler() {
            if (confirm("기본 설정으로 초기화하시겠습니까?")) {
                Config.resetSettings();
                location.reload();
            }
        }

        function toggleScheduleOptions() {
            $('#start-time-input, #interval-input, #specific-days-input').toggle($('#schedule-enabled').is(':checked'));
        }

        function toggleDataVisualization() {
            const enabled = $('#data-visualization-checkbox').is(':checked');
            Config.saveSettings({ dataVisualization: enabled });
            if (enabled) {
                $('#data-visualization').show();
                DataVisualization.renderChart();
            } else {
                $('#data-visualization').hide();
            }
            Logger.log(`데이터 시각화가 ${enabled ? '활성화' : '비활성화'}되었습니다.`, 'success');
        }

        function toggleCaptchaHandling() {
            const enabled = $('#enable-captchas-checkbox').is(':checked');
            Config.saveSettings({ enableCaptchas: enabled });
            Logger.log(`CAPTCHA 처리 ${enabled ? '활성화' : '비활성화'}되었습니다.`, 'success');
        }

        function filterData() {
            updateDataTable();
        }

        function clearPreviousCrawls() {
            if (confirm("이전 크롤링 기록을 모두 초기화하시겠습니까?")) {
                GM_setValue('previousCrawls', JSON.stringify([]));
                updatePreviousCrawlsUI();
                Logger.log('이전 크롤링 기록이 초기화되었습니다.', 'success');
            }
        }

        function resetData() {
            if (confirm("모든 크롤링 데이터를 초기화하시겠습니까?")) {
                GM_setValue('data', JSON.stringify([]));
                GM_setValue('visitedUrls', JSON.stringify([]));
                updateDataTable();
                Logger.log('크롤링 데이터가 초기화되었습니다.', 'success');
            }
        }

        function handleShortcuts(e) {
            if (e.ctrlKey) {
                const actions = {
                    '1': 'logs',
                    '2': 'data',
                    '3': 'settings',
                    '4': 'previous-crawls',
                    '5': 'templates',
                    '6': 'tests',
                    '7': 'ai-assistant',
                    '8': 'strategies',
                    's': 'save',
                    'r': 'reset',
                    'p': 'start',
                    'q': 'stop',
                    'c': 'csv',
                    'j': 'json',
                    'x': 'xml',
                    'l': 'xls',
                    'd': 'dark-mode'
                };
                const action = actions[e.key];
                if (action) {
                    e.preventDefault();
                    switch(action) {
                        case 'logs': $('.crawler-tab[data-tab="logs"]').click(); break;
                        case 'data': $('.crawler-tab[data-tab="data"]').click(); break;
                        case 'settings': $('.crawler-tab[data-tab="settings"]').click(); break;
                        case 'previous-crawls': $('.crawler-tab[data-tab="previous-crawls"]').click(); break;
                        case 'templates': $('.crawler-tab[data-tab="templates"]').click(); break;
                        case 'tests': $('.crawler-tab[data-tab="tests"]').click(); break;
                        case 'ai-assistant': $('.crawler-tab[data-tab="ai-assistant"]').click(); break;
                        case 'strategies': $('.crawler-tab[data-tab="strategies"]').click(); break;
                        case 'save': saveSettingsHandler(); break;
                        case 'reset': resetSettingsHandler(); break;
                        case 'start': if (!Crawling.isCrawling()) Crawling.startCrawling(); break;
                        case 'stop': if (Crawling.isCrawling()) Crawling.stopCrawling(); break;
                        case 'csv': Config.settings.exportFormats.csv && Download.downloadCSV(); break;
                        case 'json': Config.settings.exportFormats.json && Download.downloadJSON(); break;
                        case 'xml': Config.settings.exportFormats.xml && Download.downloadXML(); break;
                        case 'xls': Config.settings.exportFormats.xls && Download.downloadXLS(); break;
                        case 'dark-mode': toggleDarkMode(); break;
                    }
                }
            }
        }

        function makeDraggable() {
            const container = document.getElementById('crawler-container');
            const header = document.getElementById('crawler-header');
            let isDragging = false, startX, startY, initialX, initialY;
            header.onmousedown = function(e) {
                isDragging = true;
                startX = e.clientX;
                startY = e.clientY;
                initialX = container.offsetLeft;
                initialY = container.offsetTop;
                document.onmousemove = drag;
                document.onmouseup = stopDrag;
                return false;
            };
            function drag(e) {
                if (isDragging) {
                    container.style.left = `${initialX + e.clientX - startX}px`;
                    container.style.top = `${initialY + e.clientY - startY}px`;
                }
            }
            function stopDrag() {
                isDragging = false;
                document.onmousemove = null;
                document.onmouseup = null;
            }
        }

        function getFieldName(field) {
            const names = {
                name: '이름',
                url: '주소',
                description: '설명',
                html: 'HTML 내용',
                category: '분류',
                quality: '품질점수',
                language: '언어',
                wordCount: '단어 수',
                metaTags: '메타 태그',
                headings: '헤딩'
            };
            return names[field] || field;
        }

        function saveTemplateHandler() {
            const templateName = $(selectors.newTemplateName).val().trim();
            if (!templateName) {
                alert('템플릿 이름을 입력하세요.');
                return;
            }
            Config.saveTemplate(templateName);
            updateTemplatesUI();
            $(selectors.newTemplateName).val('');
        }

        function loadTemplateHandler() {
            const selected = $(selectors.templateSelect).val();
            if (!selected) {
                alert('불러올 템플릿을 선택하세요.');
                return;
            }
            Config.loadTemplate(selected);
            updateUI();
            $('.crawler-tab[data-tab="settings"]').click();
        }

        function deleteTemplateHandler() {
            const selected = $(selectors.templateSelect).val();
            if (!selected) {
                alert('삭제할 템플릿을 선택하세요.');
                return;
            }
            delete Config.settings.templates[selected];
            Config.saveSettings({ templates: Config.settings.templates });
            Logger.log(`템플릿 ${selected}이(가) 삭제되었습니다.`, 'success');
            updateTemplatesUI();
        }

        function selectTemplateHandler() {
            const selected = $(this).val();
            if (selected) {
                Config.loadTemplate(selected);
                updateUI();
                $('.crawler-tab[data-tab="settings"]').click();
            }
        }

        function toggleDarkMode() {
            $(selectors.container).toggleClass('dark');
            const isDark = $(selectors.container).hasClass('dark');
            $(selectors.darkModeToggle).text(isDark ? '☀️' : '🌙');
            Config.saveSettings({ darkMode: isDark });
            Logger.log(`다크 모드가 ${isDark ? '활성화' : '비활성화'}되었습니다.`, 'success');
        }

        function applyDarkMode() {
            if (Config.settings.darkMode) {
                $(selectors.container).addClass('dark');
                $(selectors.darkModeToggle).text('☀️');
            } else {
                $(selectors.container).removeClass('dark');
                $(selectors.darkModeToggle).text('🌙');
            }
        }

        function toggleDataField() {
            const field = $(this).attr('id').replace('field-', '');
            Config.settings.dataFields[field] = $(this).is(':checked');
            Config.saveSettings({ dataFields: Config.settings.dataFields });
            Download.updateExportButtons();
        }

        function validateProxyInput() {
            const proxy = $('#proxy-input').val().trim();
            if (proxy && !Config.validateProxy(proxy)) {
                $(selectors.errorMessage).text('유효하지 않은 프록시 URL입니다.').show();
            } else {
                $(selectors.errorMessage).hide();
            }
        }

        function updateCrawlingStrategies() {
            const selected = $('#crawling-strategies-select').val() || [];
            Config.saveSettings({ selectedCrawlingStrategies: selected });
            Logger.log('크롤링 전략이 업데이트되었습니다.', 'success');
        }

        return {
            createUI,
            updateUI,
            updateLogs,
            updateDataTable,
            updatePreviousCrawlsUI,
            updateTemplatesUI,
            applyDarkMode
        };
    })();

    const Download = (() => {
        function downloadCSV() {
            const data = JSON.parse(GM_getValue('data', '[]'));
            if (!data.length) { Logger.log('다운로드할 데이터가 없습니다.', 'warning'); return; }
            const headers = Object.keys(Config.settings.dataFields).filter(f => Config.settings.dataFields[f]).map(f => Utils.capitalize(getFieldName(f)));
            const csvContent = [headers.join(",")]
                .concat(data.map(item => Object.keys(Config.settings.dataFields).filter(f => Config.settings.dataFields[f]).map(f => `"${Utils.escape(item[f], 'csv')}"`).join(",")))
                .join("\n");
            const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = "data.csv";
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            Logger.log('CSV 파일이 다운로드되었습니다.', 'success');
        }

        function downloadJSON() {
            const data = JSON.parse(GM_getValue('data', '[]'));
            if (!data.length) { Logger.log('다운로드할 데이터가 없습니다.', 'warning'); return; }
            const jsonContent = JSON.stringify(data, null, 2);
            const blob = new Blob([jsonContent], { type: "application/json" });
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = "data.json";
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            Logger.log('JSON 파일이 다운로드되었습니다.', 'success');
        }

        function downloadXML() {
            const data = JSON.parse(GM_getValue('data', '[]'));
            if (!data.length) { Logger.log('다운로드할 데이터가 없습니다.', 'warning'); return; }
            let xmlContent = '<?xml version="1.0" encoding="UTF-8"?><data>';
            data.forEach(item => {
                xmlContent += '<entry>';
                if (Config.settings.dataFields.name) xmlContent += `<name>${Utils.escape(item.name || 'No Title', 'xml')}</name>`;
                if (Config.settings.dataFields.url) xmlContent += `<url>${Utils.escape(item.url || '', 'xml')}</url>`;
                if (Config.settings.dataFields.description) xmlContent += `<description>${Utils.escape(item.description || 'No Description', 'xml')}</description>`;
                if (Config.settings.dataFields.html) xmlContent += `<html><![CDATA[${item.html || ''}]]></html>`;
                if (Config.settings.dataFields.category) xmlContent += `<category>${Utils.escape(item.category || '', 'xml')}</category>`;
                if (Config.settings.dataFields.quality) xmlContent += `<quality>${Utils.escape(item.quality)}</quality>`;
                if (Config.settings.dataFields.language) xmlContent += `<language>${Utils.escape(item.language || 'unknown', 'xml')}</language>`;
                if (Config.settings.dataFields.wordCount) xmlContent += `<wordCount>${Utils.escape(item.wordCount)}</wordCount>`;
                if (Config.settings.dataFields.metaTags) xmlContent += `<metaTags>${Utils.escape(JSON.stringify(item.metaTags), 'xml')}</metaTags>`;
                if (Config.settings.dataFields.headings) xmlContent += `<headings>${Utils.escape(item.headings.join('; '), 'xml')}</headings>`;
                xmlContent += '</entry>';
            });
            xmlContent += '</data>';
            const blob = new Blob([xmlContent], { type: "application/xml" });
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = "data.xml";
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            Logger.log('XML 파일이 다운로드되었습니다.', 'success');
        }

        function downloadXLS() {
            const data = JSON.parse(GM_getValue('data', '[]'));
            if (!data.length) { Logger.log('다운로드할 데이터가 없습니다.', 'warning'); return; }
            const worksheetData = data.map(item => {
                const entry = {};
                Object.keys(Config.settings.dataFields).forEach(f => {
                    if (Config.settings.dataFields[f]) {
                        if (f === 'metaTags') entry['메타 태그'] = JSON.stringify(item[f]);
                        else if (f === 'headings') entry['헤딩'] = item[f].join('; ');
                        else entry[Utils.capitalize(getFieldName(f))] = item[f];
                    }
                });
                return entry;
            });
            const worksheet = XLSX.utils.json_to_sheet(worksheetData);
            const workbook = XLSX.utils.book_new();
            XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
            XLSX.writeFile(workbook, "data.xlsx");
            Logger.log('XLS 파일이 다운로드되었습니다.', 'success');
        }

        function getFieldName(field) {
            const names = {
                name: '이름',
                url: '주소',
                description: '설명',
                html: 'HTML 내용',
                category: '분류',
                quality: '품질점수',
                language: '언어',
                wordCount: '단어 수',
                metaTags: '메타 태그',
                headings: '헤딩'
            };
            return names[field] || field;
        }

        function updateExportButtons() {
            // Placeholder for dynamic enabling/disabling of export buttons based on data fields
        }

        return {
            downloadCSV,
            downloadJSON,
            downloadXML,
            downloadXLS,
            updateExportButtons
        };
    })();

    const DataVisualization = (() => {
        let chartInstance = null;

        function renderChart() {
            const ctx = document.getElementById('data-chart').getContext('2d');
            if (chartInstance) chartInstance.destroy();
            const data = JSON.parse(GM_getValue('data', '[]'));
            const categories = data.reduce((acc, item) => {
                const cat = item.category || '기타';
                acc[cat] = (acc[cat] || 0) + 1;
                return acc;
            }, {});
            const chartData = {
                labels: Object.keys(categories),
                datasets: [{
                    label: '카테고리별 데이터 수',
                    data: Object.values(categories),
                    backgroundColor: Utils.generateColors(Object.keys(categories).length)
                }]
            };
            chartInstance = new Chart(ctx, { type: 'pie', data: chartData, options: { responsive: true } });
        }

        return {
            renderChart
        };
    })();

    const Crawling = (() => {
        let isCrawlingFlag = Config.settings.isCrawling;
        let currentPage = parseInt(GM_getValue('currentPage', '1'), 10);
        let totalPages = Config.settings.maxPages;
        let progress = parseFloat(GM_getValue('progress', '0'));
        let currentDepth = parseInt(GM_getValue('currentDepth', '0'), 10);
        let activeRequests = 0;
        let requestQueue = [];
        let previousCrawls = JSON.parse(GM_getValue('previousCrawls', '[]'));
        let visitedUrls = new Set(JSON.parse(GM_getValue('visitedUrls', '[]')));
        let data = JSON.parse(GM_getValue('data', '[]'));
        let navigationObserver = null;

        const searchEngineHandlers = {
            google: {
                extractResults: doc => {
                    const results = [];
                    doc.querySelectorAll('div.g, div[data-hveid]').forEach(item => {
                        const name = item.querySelector('h3')?.innerText.trim() || 'No Title';
                        const url = item.querySelector('a')?.href || '';
                        const desc = item.querySelector('.aCOpRe, .VwiC3b')?.innerText.trim() || 'No Description';
                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });
                    });
                    return results;
                },
                getNextPageUrl: doc => doc.querySelector('a#pnnext')?.href || null,
                getContentContainer: () => '#search'
            },
            bing: {
                extractResults: doc => {
                    const results = [];
                    doc.querySelectorAll('.b_algo').forEach(item => {
                        const name = item.querySelector('h2')?.innerText.trim() || 'No Title';
                        const url = item.querySelector('h2 a')?.href || '';
                        const desc = item.querySelector('.b_caption p')?.innerText.trim() || 'No Description';
                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });
                    });
                    return results;
                },
                getNextPageUrl: doc => doc.querySelector('a.sb_pagN')?.href || null,
                getContentContainer: () => '#b_content'
            },
            yahoo: {
                extractResults: doc => {
                    const results = [];
                    doc.querySelectorAll('.dd.algo').forEach(item => {
                        const name = item.querySelector('h3')?.innerText.trim() || 'No Title';
                        const url = item.querySelector('a')?.href || '';
                        const desc = item.querySelector('.compText a')?.innerText.trim() || 'No Description';
                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });
                    });
                    return results;
                },
                getNextPageUrl: doc => doc.querySelector('a.next')?.href || null,
                getContentContainer: () => '#web'
            },
            duckduckgo: {
                extractResults: doc => {
                    const results = [];
                    doc.querySelectorAll('.result').forEach(item => {
                        const name = item.querySelector('h2')?.innerText.trim() || 'No Title';
                        const url = item.querySelector('a.result__a')?.href || '';
                        const desc = item.querySelector('.result__snippet')?.innerText.trim() || 'No Description';
                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });
                    });
                    return results;
                },
                getNextPageUrl: doc => doc.querySelector('a.result--more__btn')?.href || null,
                getContentContainer: () => '#links'
            },
            // Additional search engines can be defined here
        };

        async function startCrawling() {
            if (isCrawlingFlag) {
                Logger.log('이미 크롤링이 진행 중입니다.', 'warning');
                return;
            }
            if (!Object.values(Config.settings.searchEngines).some(v => v)) {
                Logger.log('사용할 검색 엔진이 하나도 선택되지 않았습니다.', 'error');
                return;
            }
            isCrawlingFlag = true;
            Config.saveSettings({ isCrawling: isCrawlingFlag });
            Logger.log('크롤링을 시작합니다.', 'info');
            UI.updateLogs();
            if (!previousCrawls.some(crawl => crawl.status === '진행 중')) {
                previousCrawls.push({ date: new Date().toLocaleString(), count: 0, status: '진행 중' });
                GM_setValue('previousCrawls', JSON.stringify(previousCrawls));
                UI.updatePreviousCrawlsUI();
            }
            setButtonStates();
            await crawlPage(currentDepth);
        }

        function stopCrawling() {
            if (!isCrawlingFlag) {
                Logger.log('크롤링이 진행 중이지 않습니다.', 'warning');
                return;
            }
            isCrawlingFlag = false;
            Config.saveSettings({ isCrawling: isCrawlingFlag });
            Logger.log('크롤링이 중지되었습니다.', 'warning');
            finalizeCrawl('중지됨');
            UI.updateLogs();
            setButtonStates();
        }

        function setButtonStates() {
            $('#start-crawling').prop('disabled', isCrawlingFlag);
            $('#stop-crawling').prop('disabled', !isCrawlingFlag);
        }

        async function crawlPage(depth) {
            try {
                while (isCrawlingFlag && currentPage <= totalPages && depth <= Config.settings.maxDepth) {
                    Logger.log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${depth})`, 'info');
                    if (Config.settings.respectRobots && !(await Robots.checkRobotsTxt(window.location.href))) {
                        Logger.log(`robots.txt에 의해 크롤링이 금지된 페이지: ${window.location.href}`, 'warning');
                        break;
                    }
                    const searchEngine = identifySearchEngine();
                    if (!searchEngine || !searchEngineHandlers[searchEngine]) {
                        Logger.log(`지원되지 않는 검색 엔진: ${window.location.hostname}`, 'error');
                        break;
                    }
                    const doc = document;
                    const handler = searchEngineHandlers[searchEngine];
                    const results = handler.extractResults(doc);
                    if (!results) { Logger.log('검색 결과를 찾을 수 없습니다.', 'error'); break; }
                    results.forEach(result => {
                        if (!isCrawlingFlag) return;
                        const { name, url, description } = result;
                        if (shouldSkip(url, description)) return;
                        requestQueue.push({ url, depth });
                        if (activeRequests >= Config.settings.maxConcurrentRequests) return;
                    });
                    if (previousCrawls.length && previousCrawls[previousCrawls.length -1].status === '진행 중') {
                        previousCrawls[previousCrawls.length -1].count += results.length;
                        GM_setValue('previousCrawls', JSON.stringify(previousCrawls));
                        UI.updatePreviousCrawlsUI();
                    }
                    await processQueue();
                    progress = (currentPage / totalPages) * 100;
                    Config.saveSettings({ progress });
                    UI.updateLogs();
                    if (currentPage < totalPages && isCrawlingFlag) {
                        const nextUrl = handler.getNextPageUrl(doc);
                        if (nextUrl) {
                            currentPage++;
                            Config.saveSettings({ currentPage });
                            depth++;
                            Config.saveSettings({ currentDepth: depth });
                            Logger.log(`다음 페이지로 이동: ${currentPage}`, 'info');
                            await Utils.delay(Config.settings.delay + Math.random() * Config.settings.delayRandom);
                            const nextPageContent = await Fetch.fetchPageContent(nextUrl);
                            if (nextPageContent) {
                                // Parse the fetched content without altering the current page's DOM
                                const parser = new DOMParser();
                                const fetchedDoc = parser.parseFromString(nextPageContent, 'text/html');
                                // Temporarily replace document's body with fetched content for extraction
                                const originalBody = document.body.innerHTML;
                                document.body.innerHTML = fetchedDoc.body.innerHTML;
                                continue;
                            } else {
                                Logger.log('다음 페이지 내용을 가져올 수 없습니다.', 'error');
                                break;
                            }
                        } else { Logger.log('다음 페이지 링크를 찾을 수 없습니다.', 'error'); break; }
                    } else { break; }
                }
                if (isCrawlingFlag && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {
                    Logger.log('크롤링이 완료되었습니다.', 'success');
                    finalizeCrawl('완료됨');
                    isCrawlingFlag = false;
                    Config.saveSettings({ isCrawling: isCrawlingFlag });
                    setButtonStates();
                    Config.saveSettings({ progress: 100 });
                    UI.updateLogs();
                }
            } catch (error) {
                Logger.log(`크롤링 중 오류 발생: ${error.message}`, 'error');
                finalizeCrawl('오류 발생');
            }
        }

        function shouldSkip(url, desc) {
            if (visitedUrls.has(url) || !Utils.isValidURL(url)) return true;
            if (Config.settings.whitelist.length && !Config.settings.whitelist.some(d => url.includes(d))) return true;
            if (Config.settings.blacklist.some(d => url.includes(d))) return true;
            if (Config.settings.includeKeywords.length && !Config.settings.includeKeywords.some(k => url.includes(k) || desc.includes(k))) return true;
            if (Config.settings.excludeKeywords.some(k => url.includes(k) || desc.includes(k))) return true;
            if (Config.settings.regexInclude.length && !Config.settings.regexInclude.some(r => new RegExp(r).test(url) || new RegExp(r).test(desc))) return true;
            if (Config.settings.regexExclude.length && Config.settings.regexExclude.some(r => new RegExp(r).test(url) || new RegExp(r).test(desc))) return true;
            return false;
        }

        async function processQueue() {
            while (requestQueue.length && activeRequests < Config.settings.maxConcurrentRequests && isCrawlingFlag) {
                const { url, depth } = requestQueue.shift();
                activeRequests++;
                handleRequest(url, depth).finally(() => { activeRequests--; processQueue(); });
            }
        }

        async function handleRequest(url, depth) {
            if (!isCrawlingFlag || visitedUrls.has(url)) return;
            try {
                const response = await Fetch.fetchWithRetry(url, Config.settings.retryAttempts, Config.settings.retryDelay);
                const contentType = Fetch.getContentType(response);
                if (!Fetch.isContentTypeAllowed(contentType, Config.settings.contentTypes)) {
                    Logger.log(`허용되지 않은 콘텐츠 유형(${contentType})의 URL: ${url}`, 'warning');
                    return;
                }
                const parser = new DOMParser();
                const doc = parser.parseFromString(response.responseText, 'text/html');
                const text = doc.body?.innerText || '';
                if (Config.settings.enableCaptchas && Robots.detectCaptcha(doc)) {
                    await Robots.handleCaptcha(url);
                    return;
                }
                const wc = text.split(/\s+/).length;
                if (wc < Config.settings.minWordCount || wc > Config.settings.maxWordCount) {
                    Logger.log(`단어 수 조건에 맞지 않는 URL: ${url} (${wc} 단어)`, 'warning');
                    return;
                }
                const quality = Evaluate.evaluateContentQuality(text);
                const lang = doc.documentElement.lang || 'unknown';
                const category = Classifier.classifyKeywords(text);
                const meta = Extractor.extractMetaTags(doc);
                const headings = Extractor.extractHeadings(doc);
                if (category !== '기타' || quality > 100) {
                    const entry = { url, quality, language: lang, wordCount: wc, category };
                    if (Config.settings.dataFields.name) entry.name = Extractor.extractName(doc) || 'No Title';
                    if (Config.settings.dataFields.description) entry.description = Extractor.extractDescription(doc) || 'No Description';
                    if (Config.settings.dataFields.html) entry.html = response.responseText;
                    if (Config.settings.dataFields.metaTags) entry.metaTags = meta;
                    if (Config.settings.dataFields.headings) entry.headings = headings;
                    data.push(entry);
                    GM_setValue('data', JSON.stringify(data));
                    UI.updateDataTable();
                    Logger.log(`데이터 수집 완료: ${url}`, 'success');
                    if (Config.settings.dataVisualization) DataVisualization.renderChart();
                }
                if (depth < Config.settings.maxDepth) {
                    Extractor.extractLinks(doc).forEach(link => {
                        if (!visitedUrls.has(link) && Utils.isValidURL(link)) requestQueue.push({ url: link, depth: depth + 1 });
                    });
                }
            } catch (error) {
                Logger.log(`데이터 수집 실패: ${url} - ${error.message}`, 'error');
            }
            visitedUrls.add(url);
            GM_setValue('visitedUrls', JSON.stringify([...visitedUrls]));
        }

        function finalizeCrawl(status) {
            if (previousCrawls.length) {
                previousCrawls[previousCrawls.length -1].status = status;
                GM_setValue('previousCrawls', JSON.stringify(previousCrawls));
                UI.updatePreviousCrawlsUI();
            }
            if (navigationObserver) {
                navigationObserver.disconnect();
                navigationObserver = null;
            }
        }

        function updateCrawlSettings() {
            // 필요한 경우 크롤링 설정 업데이트 로직을 여기에 추가하세요.
        }

        function identifySearchEngine() {
            const host = window.location.hostname;
            if (host.includes('google.com')) return 'google';
            if (host.includes('bing.com') && !window.location.pathname.includes('/images')) return 'bing';
            if (host.includes('yahoo.com')) return 'yahoo';
            if (host.includes('duckduckgo.com')) return 'duckduckgo';
            // Additional search engine identification logic can be added here
            return null;
        }

        return {
            startCrawling,
            stopCrawling,
            isCrawling: () => isCrawlingFlag,
            updateCrawlSettings
        };
    })();

    const Fetch = (() => {
        async function fetchWithRetry(url, attempts, delayMs) {
            for (let i = 1; i <= attempts; i++) {
                try {
                    return await GM_xmlhttpRequestPromise(url);
                } catch (e) {
                    Logger.log(`요청 실패 (${i}/${attempts}): ${url} - ${e.message}`, 'warning');
                    if (i < attempts) await Utils.delay(delayMs);
                }
            }
            throw new Error('모든 재시도 시도가 실패했습니다.');
        }

        function GM_xmlhttpRequestPromise(url) {
            return new Promise((resolve, reject) => {
                const reqUrl = Config.settings.proxy ? `${Config.settings.proxy}?url=${encodeURIComponent(url)}` : url;
                GM_xmlhttpRequest({
                    method: "GET",
                    url: reqUrl,
                    headers: { 'User-Agent': getCurrentUserAgent(), 'Accept': '*/*' },
                    timeout: Config.settings.timeout,
                    onload: res => (res.status >= 200 && res.status < 300) ? resolve(res) : reject(new Error(`HTTP ${res.status}`)),
                    onerror: () => reject(new Error('Request failed')),
                    ontimeout: () => reject(new Error('Request timed out'))
                });
            });
        }

        function getCurrentUserAgent() {
            const index = Config.settings.userAgentIndex % Config.settings.userAgents.length;
            return Config.settings.userAgents[index];
        }

        function getContentType(res) {
            return res.responseHeaders.split('\n').find(h => h.toLowerCase().startsWith('content-type'))?.split(':')[1].trim().split(';')[0] || '';
        }

        function isContentTypeAllowed(ct, contentTypes) {
            if (contentTypes.include.length && !contentTypes.include.includes(ct)) return false;
            if (contentTypes.exclude.includes(ct)) return false;
            return true;
        }

        async function fetchPageContent(url) {
            try {
                const response = await fetchWithRetry(url, Config.settings.retryAttempts, Config.settings.retryDelay);
                return response.responseText;
            } catch (error) {
                Logger.log(`페이지 내용 가져오기 실패: ${url} - ${error.message}`, 'error');
                return null;
            }
        }

        return {
            fetchWithRetry,
            getCurrentUserAgent,
            getContentType,
            isContentTypeAllowed,
            fetchPageContent
        };
    })();

    const Robots = (() => {
        async function checkRobotsTxt(url) {
            try {
                const parsed = new URL(url);
                const robotsUrl = `${parsed.origin}/robots.txt`;
                const res = await Fetch.fetchWithRetry(robotsUrl, 2, 2000);
                return new RobotsParser(res.responseText).isAllowed(url, Fetch.getCurrentUserAgent());
            } catch {
                Logger.log('robots.txt 확인 실패, 기본 허용.', 'warning');
                return true;
            }
        }

        function detectCaptcha(doc) {
            return !!doc.querySelector('iframe[src*="captcha"], form[action*="captcha"]');
        }

        async function handleCaptcha(url) {
            Logger.log(`CAPTCHA 감지됨: ${url}`, 'warning');
            await Utils.delay(5000);
            Logger.log(`CAPTCHA 처리 완료: ${url}`, 'info');
        }

        class RobotsParser {
            constructor(text) { this.rules = []; this.parse(text); }
            parse(text) {
                let agents = [];
                text.split('\n').forEach(line => {
                    const [key, ...val] = line.split(':').map(s => s.trim());
                    if (!key) return;
                    if (key.toLowerCase() === 'user-agent') agents = [val.join(':').toLowerCase()];
                    else if (['disallow','allow'].includes(key.toLowerCase())) this.rules.push({ agents, type: key.toLowerCase(), path: val.join(':') });
                });
            }
            isAllowed(url, ua) {
                const parsedUrl = new URL(url);
                const path = parsedUrl.pathname;
                let allowed = true;
                this.rules.forEach(rule => {
                    if (rule.agents.includes('*') || rule.agents.includes(ua.toLowerCase())) {
                        if (rule.type === 'disallow' && path.startsWith(rule.path)) allowed = false;
                        if (rule.type === 'allow' && path.startsWith(rule.path)) allowed = true;
                    }
                });
                return allowed;
            }
        }

        return {
            checkRobotsTxt,
            detectCaptcha,
            handleCaptcha
        };
    })();

    const Extractor = (() => {
        function extractMetaTags(doc) {
            const metas = {};
            doc.querySelectorAll('meta[name], meta[property]').forEach(meta => {
                const name = meta.getAttribute('name') || meta.getAttribute('property');
                const content = meta.getAttribute('content');
                if (name && content) metas[name.toLowerCase()] = content;
            });
            return metas;
        }

        function extractHeadings(doc) {
            const hs = [];
            for (let i = 1; i <= 6; i++) doc.querySelectorAll(`h${i}`).forEach(h => hs.push(h.innerText.trim()));
            return hs;
        }

        function extractName(doc) { return doc.querySelector('title')?.innerText || 'No Title'; }
        function extractDescription(doc) { return doc.querySelector('meta[name="description"]')?.getAttribute('content') || 'No Description'; }
        function extractLinks(doc) { return Array.from(doc.querySelectorAll('a[href]')).map(a => a.href).filter(Utils.isValidURL); }

        return {
            extractMetaTags,
            extractHeadings,
            extractName,
            extractDescription,
            extractLinks
        };
    })();

    const Classifier = (() => {
        function classifyKeywords(text) {
            const kws = combineKeywords();
            for (const [cat, keywords] of Object.entries(kws)) {
                if (keywords.some(kw => text.includes(kw))) return cat;
            }
            return '기타';
        }

        function combineKeywords() {
            const standard = {
                '교육': ['학교', '교육', '학생', '교수', '강의'],
                '기술': ['기술', '소프트웨어', '하드웨어', '프로그래밍', '개발'],
                '뉴스': ['뉴스', '속보', '기사', '보도'],
                '엔터테인먼트': ['영화', '음악', '연예', '게임', '드라마'],
                '스포츠': ['축구', '야구', '농구', '경기', '토너먼트'],
                '비즈니스': ['비즈니스', '경제', '주식', '투자', '시장'],
                '건강': ['건강', '의료', '헬스', '운동', '영양'],
                '과학': ['과학', '연구', '실험', '발견', '이론'],
                '문학': ['소설', '시', '문학', '작가', '책'],
                '여행': ['여행', '관광', '숙박', '비행기', '여권']
            };
            const custom = Config.settings.customKeywords || {};
            for (const [cat, kws] of Object.entries(custom)) {
                standard[cat] = standard[cat] ? [...new Set([...standard[cat], ...kws])] : kws;
            }
            return standard;
        }

        return {
            classifyKeywords
        };
    })();

    const Evaluate = (() => {
        function evaluateContentQuality(text) { return text.split(/\s+/).length; }

        return {
            evaluateContentQuality
        };
    })();

    const Scheduler = (() => {
        let scheduleInterval = null;

        function scheduleCrawling() {
            clearInterval(scheduleInterval);
            const now = new Date();
            const [startHour, startMinute] = Config.settings.schedule.startTime.split(':').map(Number);
            const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), startHour, startMinute, 0);
            if (startTime < now) startTime.setDate(startTime.getDate() + 1);
            const initialDelay = startTime - now;
            setTimeout(() => {
                if (Config.settings.schedule.specificDays.length === 0 || Config.settings.schedule.specificDays.includes(startTime.getDay())) {
                    Crawling.startCrawling();
                }
                scheduleInterval = setInterval(() => {
                    const currentDay = new Date().getDay();
                    if (Config.settings.schedule.specificDays.length === 0 || Config.settings.schedule.specificDays.includes(currentDay)) {
                        Crawling.startCrawling();
                    }
                }, Config.settings.schedule.interval);
            }, initialDelay);
            Logger.log(`크롤링 스케줄이 설정되었습니다. 시작 시간: ${startTime.toLocaleString()}, 간격: ${Config.settings.schedule.interval / 60000}분`, 'info');
        }

        return {
            scheduleCrawling
        };
    })();

    const Testing = (() => {
        function runTests() {
            $(selectors.testModal).show();
            $(selectors.mochaContainer).html('');
            $('head').append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/10.2.0/mocha.min.css" />');
            mocha.setup('bdd');
            const assert = chai.assert;

            describe('Utils Module', function() {
                it('should escape HTML correctly', function() {
                    const input = '<div>Hello & "World"!</div>';
                    const expected = '&lt;div&gt;Hello &amp; &quot;World&quot;!&lt;/div&gt;';
                    assert.equal(Utils.escape(input, 'html'), expected);
                });
                it('should capitalize strings correctly', function() {
                    const input = 'test';
                    const expected = 'Test';
                    assert.equal(Utils.capitalize(input), expected);
                });
                it('should validate URLs correctly', function() {
                    assert.isTrue(Utils.isValidURL('https://www.example.com'));
                    assert.isFalse(Utils.isValidURL('htp:/invalid-url'));
                });
            });

            describe('Config Module', function() {
                it('should save and load settings correctly', function() {
                    const originalDelay = Config.settings.delay;
                    Config.saveSettings({ delay: 3000 });
                    assert.equal(Config.settings.delay, 3000);
                    Config.saveSettings({ delay: originalDelay });
                });
                it('should add and remove templates correctly', function() {
                    const templateName = 'TestTemplate';
                    Config.saveTemplate(templateName);
                    assert.property(Config.settings.templates, templateName);
                    Config.loadTemplate(templateName);
                    Config.settings.templates[templateName] = undefined;
                    delete Config.settings.templates[templateName];
                    assert.notProperty(Config.settings.templates, templateName);
                });
            });

            describe('Logger Module', function() {
                it('should log messages correctly', function() {
                    Logger.log('Test log message', 'info');
                    const logs = Logger.getLogs();
                    assert.isAtLeast(logs.length, 1);
                    assert.equal(logs[logs.length -1].message, 'Test log message');
                });
                it('should clear logs correctly', function() {
                    Logger.clearLogs();
                    assert.equal(Logger.getLogs().length, 0);
                });
            });

            describe('Crawling Module', function() {
                it('should start and stop crawling correctly', function(done) {
                    Crawling.startCrawling();
                    assert.isTrue(Crawling.isCrawling());
                    Crawling.stopCrawling();
                    setTimeout(() => {
                        assert.isFalse(Crawling.isCrawling());
                        done();
                    }, 1000);
                });
                // Additional integration tests can be added here
            });

            mocha.run();
        }

        return {
            runTests
        };
    })();

    function init() {
        UI.createUI();
        UI.updateUI();
        if (Config.settings.dataVisualization) DataVisualization.renderChart();
        if (Config.settings.schedule.enabled) Scheduler.scheduleCrawling();
        if (Config.settings.isCrawling) {
            Logger.log('크롤링 재개됨.', 'info');
            Crawling.startCrawling();
        } else {
            Logger.log('크롤러 초기화됨.', 'info');
        }
    }

    window.addEventListener('load', init);

})();