// @name Advanced Web Crawler Enhanced Pro
// @namespace http://tampermonkey.net/
// @version 6.0
// @description 고급 웹 크롤러 스크립트 with Enhanced Settings, Improved Accuracy, Resizable UI, and Additional Features
// @author Your Name
// @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
// @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
// ==/UserScript==

(function() {
'use strict';


// 기본 설정
const defaultSettings = {
    delay: 2000, // 초기 딜레이 2초
    delayRandom: 500, // 딜레이 랜덤 범위 ±0.5초
    maxPages: 3, // 기본 최대 크롤링 페이지 수
    userAgents: [
        'AdvancedWebCrawler/6.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
        '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',
        // 추가 사용자 에이전트
    ],
    userAgentIndex: 0,
    blacklist: [],
    whitelist: [],
    contentTypes: {
        include: ['text/html'],
        exclude: ['application/pdf', 'image/jpeg', 'image/png', 'video/mp4']
    },
    timeout: 8000, // 기본 타임아웃 8초
    includeKeywords: [],
    excludeKeywords: [],
    language: [],
    minWordCount: 100,
    maxWordCount: 10000,
    maxDepth: 2,
    proxy: '',
    regexInclude: [],
    regexExclude: [],
    dataFields: {
        name: true,
        url: true,
        description: true,
        html: true, // HTML 내용 수집 활성화
        category: true,
        quality: true,
        language: true,
        wordCount: true,
        metaTags: false,
        headings: false
    },
    schedule: {
        enabled: false,
        startTime: '00:00',
        interval: 3600000
    },
    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 // CAPTCHA 처리 옵션 추가
};

// 현재 설정 로드
const settings = {
    delay: parseInt(GM_getValue('delay', defaultSettings.delay), 10),
    delayRandom: parseInt(GM_getValue('delayRandom', defaultSettings.delayRandom), 10),
    maxPages: parseInt(GM_getValue('maxPages', defaultSettings.maxPages), 10),
    userAgents: GM_getValue('userAgents', defaultSettings.userAgents),
    userAgentIndex: parseInt(GM_getValue('userAgentIndex', defaultSettings.userAgentIndex), 10),
    blacklist: GM_getValue('blacklist', defaultSettings.blacklist),
    whitelist: GM_getValue('whitelist', defaultSettings.whitelist),
    contentTypes: GM_getValue('contentTypes', defaultSettings.contentTypes),
    timeout: parseInt(GM_getValue('timeout', defaultSettings.timeout), 10),
    includeKeywords: GM_getValue('includeKeywords', defaultSettings.includeKeywords),
    excludeKeywords: GM_getValue('excludeKeywords', defaultSettings.excludeKeywords),
    regexInclude: GM_getValue('regexInclude', defaultSettings.regexInclude),
    regexExclude: GM_getValue('regexExclude', defaultSettings.regexExclude),
    language: GM_getValue('language', defaultSettings.language),
    minWordCount: parseInt(GM_getValue('minWordCount', defaultSettings.minWordCount), 10),
    maxWordCount: parseInt(GM_getValue('maxWordCount', defaultSettings.maxWordCount), 10),
    maxDepth: parseInt(GM_getValue('maxDepth', defaultSettings.maxDepth), 10),
    proxy: GM_getValue('proxy', defaultSettings.proxy),
    dataFields: GM_getValue('dataFields', defaultSettings.dataFields),
    schedule: GM_getValue('schedule', defaultSettings.schedule),
    searchEngines: GM_getValue('searchEngines', defaultSettings.searchEngines),
    retryAttempts: parseInt(GM_getValue('retryAttempts', defaultSettings.retryAttempts), 10),
    retryDelay: parseInt(GM_getValue('retryDelay', defaultSettings.retryDelay), 10),
    respectRobots: GM_getValue('respectRobots', defaultSettings.respectRobots),
    maxConcurrentRequests: parseInt(GM_getValue('maxConcurrentRequests', defaultSettings.maxConcurrentRequests), 10),
    dataVisualization: GM_getValue('dataVisualization', defaultSettings.dataVisualization),
    exportFormats: GM_getValue('exportFormats', defaultSettings.exportFormats),
    advancedLogging: GM_getValue('advancedLogging', defaultSettings.advancedLogging),
    enableCaptchas: GM_getValue('enableCaptchas', defaultSettings.enableCaptchas),
    customKeywords: GM_getValue('customKeywords', {})
};

// 크롤링 상태 복원
let visitedUrls = GM_getValue('visitedUrls', []);
let logs = GM_getValue('logs', []);
let data = GM_getValue('data', []);
let isCrawling = GM_getValue('isCrawling', false);
let currentPage = GM_getValue('currentPage', 1);
let totalPages = GM_getValue('totalPages', settings.maxPages);
let progress = GM_getValue('progress', 0);
let currentDepth = GM_getValue('currentDepth', 0);
let activeRequests = 0;
let requestQueue = [];

// UI 생성
function createUI() {
    const style = `
        /* 스타일 추가 */
        #crawler-container {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 800px; /* 크기 증가 */
            height: 800px; /* 크기 증가 */
            background: #fff;
            border: 1px solid #ccc;
            z-index: 10000;
            font-family: Arial, sans-serif;
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
            overflow: hidden;
            resize: both;
            overflow: auto;
            border-radius: 8px;
            min-width: 500px; /* 최소 너비 조정 */
            min-height: 600px; /* 최소 높이 조정 */
        }
        #crawler-tabs { display: flex; background: #f1f1f1; border-bottom: 1px solid #ccc; }
        .crawler-tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; font-weight: bold; }
        .crawler-tab.active { background: #ddd; }
        #crawler-content { padding: 10px; overflow: auto; height: calc(100% - 50px); }
        /* 추가적인 스타일 */
        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
        th { background-color: #f2f2f2; cursor: pointer; }
        tr:hover { background-color: #f5f5f5; }
        .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; }
        .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.4); }
        .modal-content { background-color: #fff; margin: 5% auto; padding: 20px; border: 1px solid #888; width: 80%; max-height: 80%; overflow-y: auto; border-radius: 8px; }
        .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
        .control-buttons { margin: 10px 0; }
        .control-buttons button { padding: 8px 12px; margin-right: 8px; cursor: pointer; border: none; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px; }
        .control-buttons button:disabled { background-color: #a5d6a7; cursor: not-allowed; }
        .status-text { margin-top: 10px; font-weight: bold; font-size: 14px; }
        /* 설정 섹션 스타일 */
        .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: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
        .settings-section .checkbox-group { display: flex; flex-wrap: wrap; }
        .settings-section .checkbox-group label { margin-right: 10px; font-size: 14px; }
        .settings-section .radio-group { display: flex; flex-wrap: wrap; }
        .settings-section .radio-group label { margin-right: 10px; font-size: 14px; }
        /* Tooltip 스타일 */
        .tooltip {
            position: relative;
            display: inline-block;
            cursor: help;
            font-size: 14px;
        }
        .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;
        }
        /* 추가 설정 */
        #crawler-container::-webkit-resizer {
            background: #f1f1f1;
            border: none;
        }
        /* 데이터 시각화 섹션 */
        #data-visualization {
            margin-top: 20px;
        }
        #data-chart {
            width: 100%;
            height: 300px; /* 높이 조정 */
        }
        /* 모달 내 스타일 */
        #modal-html-content {
            white-space: pre-wrap;
            word-wrap: break-word;
            background-color: #f9f9f9;
            padding: 10px;
            border-radius: 4px;
        }
    `;
    $('head').append(`<style>${style}</style>`);

    const container = $(`
        <div id="crawler-container">
            <div id="crawler-tabs">
                <div class="crawler-tab ${currentTab() === 'logs' ? 'active' : ''}" data-tab="logs" title="로그 보기">📜 로그</div>
                <div class="crawler-tab ${currentTab() === 'data' ? 'active' : ''}" data-tab="data" title="수집된 데이터 보기">📊 데이터</div>
                <div class="crawler-tab ${currentTab() === 'settings' ? 'active' : ''}" data-tab="settings" title="설정 변경">⚙️ 설정</div>
            </div>
            <div id="crawler-content">
                <div id="tab-logs" style="display: ${currentTab() === 'logs' ? 'block' : 'none'};">
                    <div class="progress-bar">
                        <div class="progress-bar-fill" style="width: ${progress.toFixed(2)}%;"></div>
                    </div>
                    <div class="status-text">진행률: ${progress.toFixed(2)}% (${currentPage}/${totalPages} 페이지)</div>
                    <div class="control-buttons">
                        <button id="start-crawling" title="크롤링을 시작합니다." ${isCrawling ? 'disabled' : ''}>▶ 시작</button>
                        <button id="stop-crawling" title="크롤링을 중지합니다." ${isCrawling ? '' : 'disabled'}>■ 중지</button>
                        ${settings.exportFormats.csv ? `<button id="download-csv" title="수집된 데이터를 CSV 파일로 다운로드합니다.">📥 CSV</button>` : ''}
                        ${settings.exportFormats.json ? `<button id="download-json" title="수집된 데이터를 JSON 파일로 다운로드합니다.">📥 JSON</button>` : ''}
                        ${settings.exportFormats.xml ? `<button id="download-xml" title="수집된 데이터를 XML 파일로 다운로드합니다.">📥 XML</button>` : ''}
                        ${settings.exportFormats.xls ? `<button id="download-xls" title="수집된 데이터를 XLS 파일로 다운로드합니다.">📥 XLS</button>` : ''}
                    </div>
                    <div id="log-messages" style="height: 250px; overflow-y: auto; background: #f9f9f9; padding: 10px; border: 1px solid #ccc; border-radius: 4px;">로그 내용이 여기에 표시됩니다.</div>
                </div>
                <div id="tab-data" style="display: ${currentTab() === 'data' ? 'block' : 'none'};">
                    <input type="text" id="search-filter" placeholder="검색 필터..." title="데이터 필터링을 위한 키워드를 입력하세요." style="width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;">
                    <table>
                        <thead>
                            <tr>
                                ${settings.dataFields.name ? '<th data-sort="name">사이트 이름</th>' : ''}
                                ${settings.dataFields.url ? '<th data-sort="url">주소</th>' : ''}
                                ${settings.dataFields.description ? '<th data-sort="description">설명</th>' : ''}
                                ${settings.dataFields.html ? '<th>📄 HTML 내용</th>' : ''}
                                ${settings.dataFields.category ? '<th data-sort="category">분류</th>' : ''}
                                ${settings.dataFields.quality ? '<th data-sort="quality">품질점수</th>' : ''}
                                ${settings.dataFields.language ? '<th data-sort="language">언어</th>' : ''}
                                ${settings.dataFields.wordCount ? '<th data-sort="wordCount">단어 수</th>' : ''}
                                ${settings.dataFields.metaTags ? '<th>메타 태그</th>' : ''}
                                ${settings.dataFields.headings ? '<th>헤딩</th>' : ''}
                            </tr>
                        </thead>
                        <tbody id="data-table-body">
                            <!-- 데이터 행이 여기에 추가됩니다. -->
                        </tbody>
                    </table>
                    <!-- 데이터 시각화 섹션 -->
                    ${settings.dataVisualization ? `
                        <div id="data-visualization">
                            <h3>데이터 시각화</h3>
                            <canvas id="data-chart"></canvas>
                        </div>
                    ` : ''}
                </div>
                <div id="tab-settings" style="display: ${currentTab() === 'settings' ? 'block' : 'none'};">
                    <h3>설정</h3>
                    <!-- 크롤링 설정 섹션 -->
                    <div class="settings-section">
                        <label for="delay-input">크롤링 딜레이 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">기본 딜레이과 딜레이 랜덤 값을 조합하여 요청 사이의 딜레이를 설정합니다.</span></span>:</label>
                        <input type="number" id="delay-input" value="${settings.delay}" title="크롤링 요청 사이의 딜레이를 밀리초 단위로 설정하세요." min="500" step="500">
                        <label for="delay-random-input">딜레이 랜덤 범위 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">딜레이에 무작위 요소를 추가하여 보다 자연스러운 요청을 합니다.</span></span>:</label>
                        <input type="number" id="delay-random-input" value="${settings.delayRandom}" title="크롤링 요청 딜레이에 추가할 랜덤 범위를 밀리초 단위로 설정하세요." min="0" step="500">
                    </div>
                    <div class="settings-section">
                        <label for="max-pages-input">최대 크롤링 페이지 수 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 최대 페이지 수를 설정하세요.</span></span>:</label>
                        <input type="number" id="max-pages-input" value="${settings.maxPages}" title="크롤링할 최대 페이지 수를 설정하세요." min="1">
                    </div>
                    <div class="settings-section">
                        <label for="user-agent-select">사용자 에이전트(User-Agent) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링에 사용할 사용자 에이전트를 선택하거나 추가할 수 있습니다.</span></span>:</label>
                        <select id="user-agent-select" multiple size="6" title="크롤링에 사용할 사용자 에이전트를 선택하세요.">
                            ${settings.userAgents.map((ua, idx) => `<option value="${idx}" ${idx === settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`).join('')}
                        </select>
                        <button id="add-user-agent" title="새 사용자 에이전트를 추가합니다." style="margin-top: 5px; padding: 5px 10px; border: none; background-color: #2196F3; color: white; border-radius: 4px; font-size: 12px;">추가</button>
                        <button id="remove-user-agent" title="선택한 사용자 에이전트를 제거합니다." style="margin-top: 5px; padding: 5px 10px; border: none; background-color: #f44336; color: white; border-radius: 4px; font-size: 12px;">제거</button>
                    </div>
                    <!-- 콘텐츠 유형 섹션 -->
                    <div class="settings-section">
                        <label>콘텐츠 유형:</label>
                        <div>
                            <strong>포함할 유형:</strong>
                            <textarea id="content-include-input" rows="3" title="크롤링할 콘텐츠 유형을 입력하세요. 각 유형은 쉼표로 구분됩니다. 예: text/html, application/json">${settings.contentTypes.include.join(', ')}</textarea>
                        </div>
                        <div>
                            <strong>제외할 유형:</strong>
                            <textarea id="content-exclude-input" rows="3" title="크롤링에서 제외할 콘텐츠 유형을 입력하세요. 각 유형은 쉼표로 구분됩니다. 예: image/jpeg, video/mp4">${settings.contentTypes.exclude.join(', ')}</textarea>
                        </div>
                    </div>
                    <!-- 타임아웃 섹션 -->
                    <div class="settings-section">
                        <label for="timeout-input">크롤링 타임아웃 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">요청이 완료되기까지 기다릴 최대 시간을 밀리초 단위로 설정합니다.</span></span>:</label>
                        <input type="number" id="timeout-input" value="${settings.timeout}" title="크롤링 요청의 타임아웃을 밀리초 단위로 설정하세요." min="1000" step="1000">
                    </div>
                    <!-- 키워드 섹션 -->
                    <div class="settings-section">
                        <label for="include-keywords-input">포함할 키워드 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 페이지에 반드시 포함되어야 하는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.</span></span>:</label>
                        <textarea id="include-keywords-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링할 페이지에 반드시 포함되어야 하는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.">${settings.includeKeywords.join('\n')}</textarea>
                    </div>
                    <div class="settings-section">
                        <label for="exclude-keywords-input">제외할 키워드 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 페이지에 포함되면 안 되는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.</span></span>:</label>
                        <textarea id="exclude-keywords-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링할 페이지에 포함되면 안 되는 키워드를 입력하세요. 각 키워드는 줄바꿈으로 구분됩니다.">${settings.excludeKeywords.join('\n')}</textarea>
                    </div>
                    <!-- 정규 표현식 키워드 섹션 -->
                    <div class="settings-section">
                        <label for="regex-include-input">포함할 키워드 (정규 표현식) <span class="tooltip">ℹ️<span class="tooltiptext">포함할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.</span></span>:</label>
                        <textarea id="regex-include-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="포함할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.">${settings.regexInclude.join('\n')}</textarea>
                    </div>
                    <div class="settings-section">
                        <label for="regex-exclude-input">제외할 키워드 (정규 표현식) <span class="tooltip">ℹ️<span class="tooltiptext">제외할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.</span></span>:</label>
                        <textarea id="regex-exclude-input" rows="3" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="제외할 키워드의 정규 표현식을 입력하세요. 각 패턴은 줄바꿈으로 구분됩니다.">${settings.regexExclude.join('\n')}</textarea>
                    </div>
                    <!-- 언어 필터링 섹션 -->
                    <div class="settings-section">
                        <label for="language-select">언어 필터링 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링할 언어를 선택하세요. 선택하지 않으면 모든 언어가 포함됩니다.</span></span>:</label>
                        <select id="language-select" multiple size="10" title="크롤링할 언어를 선택하세요." style="padding: 5px; border: 1px solid #ccc; border-radius: 4px;">
                            <option value="en" ${settings.language.includes('en') ? 'selected' : ''}>영어 (en)</option>
                            <option value="ko" ${settings.language.includes('ko') ? 'selected' : ''}>한국어 (ko)</option>
                            <option value="zh" ${settings.language.includes('zh') ? 'selected' : ''}>중국어 (zh)</option>
                            <option value="ja" ${settings.language.includes('ja') ? 'selected' : ''}>일본어 (ja)</option>
                            <option value="ru" ${settings.language.includes('ru') ? 'selected' : ''}>러시아어 (ru)</option>
                            <option value="es" ${settings.language.includes('es') ? 'selected' : ''}>스페인어 (es)</option>
                            <option value="fr" ${settings.language.includes('fr') ? 'selected' : ''}>프랑스어 (fr)</option>
                            <option value="de" ${settings.language.includes('de') ? 'selected' : ''}>독일어 (de)</option>
                            <option value="it" ${settings.language.includes('it') ? 'selected' : ''}>이탈리아어 (it)</option>
                            <option value="pt" ${settings.language.includes('pt') ? 'selected' : ''}>포르투갈어 (pt)</option>
                            <option value="ar" ${settings.language.includes('ar') ? 'selected' : ''}>아랍어 (ar)</option>
                            <option value="hi" ${settings.language.includes('hi') ? 'selected' : ''}>힌디어 (hi)</option>
                            <option value="bn" ${settings.language.includes('bn') ? 'selected' : ''}>벵골어 (bn)</option>
                            <option value="pa" ${settings.language.includes('pa') ? 'selected' : ''}>펀자브어 (pa)</option>
                            <option value="nl" ${settings.language.includes('nl') ? 'selected' : ''}>네덜란드어 (nl)</option>
                            <option value="sv" ${settings.language.includes('sv') ? 'selected' : ''}>스웨덴어 (sv)</option>
                            <option value="tr" ${settings.language.includes('tr') ? 'selected' : ''}>터키어 (tr)</option>
                            <option value="vi" ${settings.language.includes('vi') ? 'selected' : ''}>베트남어 (vi)</option>
                            <option value="pl" ${settings.language.includes('pl') ? 'selected' : ''}>폴란드어 (pl)</option>
                            <option value="th" ${settings.language.includes('th') ? 'selected' : ''}>태국어 (th)</option>
                            <!-- 추가 언어 옵션 -->
                        </select>
                    </div>
                    <!-- 단어 수 섹션 -->
                    <div class="settings-section">
                        <label for="min-word-count-input">최소 단어 수 <span class="tooltip">ℹ️<span class="tooltiptext">수집할 페이지의 최소 단어 수를 설정하세요.</span></span>:</label>
                        <input type="number" id="min-word-count-input" value="${settings.minWordCount}" title="수집할 페이지의 최소 단어 수를 설정하세요." min="10" step="10">
                    </div>
                    <div class="settings-section">
                        <label for="max-word-count-input">최대 단어 수 <span class="tooltip">ℹ️<span class="tooltiptext">수집할 페이지의 최대 단어 수를 설정하세요.</span></span>:</label>
                        <input type="number" id="max-word-count-input" value="${settings.maxWordCount}" title="수집할 페이지의 최대 단어 수를 설정하세요." min="100" step="100">
                    </div>
                    <!-- 심층 크롤링 섹션 -->
                    <div class="settings-section">
                        <label for="max-depth-input">최대 크롤링 깊이 <span class="tooltip">ℹ️<span class="tooltiptext">링크를 추적할 최대 깊이를 설정하세요.</span></span>:</label>
                        <input type="number" id="max-depth-input" value="${settings.maxDepth}" title="링크를 추적할 최대 깊이를 설정하세요." min="1" step="1">
                    </div>
                    <!-- 프록시 섹션 -->
                    <div class="settings-section">
                        <label for="proxy-input">프록시 서버 <span class="tooltip">ℹ️<span class="tooltiptext">프록시 서버 주소를 입력하세요. 예: http://proxyserver:port</span></span>:</label>
                        <input type="text" id="proxy-input" value="${settings.proxy}" placeholder="http://proxyserver:port" title="프록시 서버 주소를 입력하세요. (옵션)" style="padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;">
                    </div>
                    <!-- 데이터 필드 섹션 -->
                    <div class="settings-section">
                        <label>수집할 데이터 필드 <span class="tooltip">ℹ️<span class="tooltiptext">수집할 데이터의 필드를 선택하세요.</span></span>:</label>
                        <div class="checkbox-group">
                            <label><input type="checkbox" id="field-name" ${settings.dataFields.name ? 'checked' : ''}> 사이트 이름</label>
                            <label><input type="checkbox" id="field-url" ${settings.dataFields.url ? 'checked' : ''}> 주소</label>
                            <label><input type="checkbox" id="field-description" ${settings.dataFields.description ? 'checked' : ''}> 설명</label>
                            <label><input type="checkbox" id="field-html" ${settings.dataFields.html ? 'checked' : ''}> HTML 내용</label>
                            <label><input type="checkbox" id="field-category" ${settings.dataFields.category ? 'checked' : ''}> 분류</label>
                            <label><input type="checkbox" id="field-quality" ${settings.dataFields.quality ? 'checked' : ''}> 품질점수</label>
                            <label><input type="checkbox" id="field-language" ${settings.dataFields.language ? 'checked' : ''}> 언어</label>
                            <label><input type="checkbox" id="field-wordCount" ${settings.dataFields.wordCount ? 'checked' : ''}> 단어 수</label>
                            <label><input type="checkbox" id="field-metaTags" ${settings.dataFields.metaTags ? 'checked' : ''}> 메타 태그</label>
                            <label><input type="checkbox" id="field-headings" ${settings.dataFields.headings ? 'checked' : ''}> 헤딩</label>
                        </div>
                    </div>
                    <!-- 재시도 섹션 -->
                    <div class="settings-section">
                        <label for="retry-attempts-input">재시도 횟수 <span class="tooltip">ℹ️<span class="tooltiptext">요청 실패 시 재시도할 횟수를 설정하세요.</span></span>:</label>
                        <input type="number" id="retry-attempts-input" value="${settings.retryAttempts}" title="요청 실패 시 재시도할 횟수를 설정하세요." min="0" step="1">
                    </div>
                    <div class="settings-section">
                        <label for="retry-delay-input">재시도 딜레이 (ms) <span class="tooltip">ℹ️<span class="tooltiptext">재시도 시 대기할 시간을 밀리초 단위로 설정하세요.</span></span>:</label>
                        <input type="number" id="retry-delay-input" value="${settings.retryDelay}" title="재시도 시 대기할 시간을 밀리초 단위로 설정하세요." min="1000" step="500">
                    </div>
                    <!-- 스케줄링 섹션 -->
                    <div class="settings-section">
                        <label>크롤링 스케줄링 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링 작업을 예약하려면 활성화하세요.</span></span>:</label>
                        <div class="checkbox-group">
                            <label><input type="checkbox" id="schedule-enabled" ${settings.schedule.enabled ? 'checked' : ''} title="크롤링 작업을 예약하려면 체크하세요."> 스케줄링 활성화</label>
                        </div>
                        <div id="schedule-options" style="display: ${settings.schedule.enabled ? 'block' : 'none'}; margin-top: 10px;">
                            <label for="start-time-input">시작 시간 (HH:MM) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링을 시작할 시간을 설정하세요.</span></span>:</label>
                            <input type="time" id="start-time-input" value="${settings.schedule.startTime}" title="크롤링을 시작할 시간을 설정하세요." style="padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;">
                            <label for="interval-input">크롤링 간격 (분) <span class="tooltip">ℹ️<span class="tooltiptext">크롤링 작업 간의 간격을 분 단위로 설정하세요.</span></span>:</label>
                            <input type="number" id="interval-input" value="${settings.schedule.interval / 60000}" title="크롤링 작업 간의 간격을 분 단위로 설정하세요." min="1" step="1">
                        </div>
                    </div>
                    <!-- 검색 엔진 선택 섹션 -->
                    <div class="settings-section">
                        <label>크롤링할 검색 엔진 선택 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링에 사용할 검색 엔진을 선택하세요.</span></span>:</label>
                        <div class="checkbox-group">
                            <label><input type="checkbox" id="engine-google" ${settings.searchEngines.google ? 'checked' : ''}> Google</label>
                            <label><input type="checkbox" id="engine-bing" ${settings.searchEngines.bing ? 'checked' : ''}> Bing</label>
                            <label><input type="checkbox" id="engine-yahoo" ${settings.searchEngines.yahoo ? 'checked' : ''}> Yahoo</label>
                            <label><input type="checkbox" id="engine-duckduckgo" ${settings.searchEngines.duckduckgo ? 'checked' : ''}> DuckDuckGo</label>
                            <label><input type="checkbox" id="engine-baidu" ${settings.searchEngines.baidu ? 'checked' : ''}> Baidu</label>
                            <label><input type="checkbox" id="engine-yandex" ${settings.searchEngines.yandex ? 'checked' : ''}> Yandex</label>
                            <label><input type="checkbox" id="engine-naver" ${settings.searchEngines.naver ? 'checked' : ''}> Naver</label>
                            <label><input type="checkbox" id="engine-daum" ${settings.searchEngines.daum ? 'checked' : ''}> Daum</label>
                            <label><input type="checkbox" id="engine-ask" ${settings.searchEngines.ask ? 'checked' : ''}> Ask.com</label>
                            <label><input type="checkbox" id="engine-ecosia" ${settings.searchEngines.ecosia ? 'checked' : ''}> Ecosia</label>
                            <label><input type="checkbox" id="engine-startpage" ${settings.searchEngines.startpage ? 'checked' : ''}> Startpage</label>
                            <label><input type="checkbox" id="engine-searx" ${settings.searchEngines.searx ? 'checked' : ''}> Searx</label>
                            <label><input type="checkbox" id="engine-dogpile" ${settings.searchEngines.dogpile ? 'checked' : ''}> Dogpile</label>
                            <label><input type="checkbox" id="engine-bingimages" ${settings.searchEngines.bingimages ? 'checked' : ''}> Bing Images</label>
                            <!-- 추가 검색 엔진 옵션 -->
                        </div>
                    </div>
                    <!-- 블랙리스트/화이트리스트 섹션 -->
                    <div class="settings-section">
                        <label for="blacklist-input">블랙리스트 도메인 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링에서 제외할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.</span></span>:</label>
                        <textarea id="blacklist-input" rows="5" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링에서 제외할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.blacklist.join('\n')}</textarea>
                    </div>
                    <div class="settings-section">
                        <label for="whitelist-input">화이트리스트 도메인 <span class="tooltip">ℹ️<span class="tooltiptext">크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.</span></span>:</label>
                        <textarea id="whitelist-input" rows="5" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.whitelist.join('\n')}</textarea>
                    </div>
                    <!-- robots.txt 준수 섹션 -->
                    <div class="settings-section">
                        <label for="respect-robots-checkbox">robots.txt 준수 <span class="tooltip">ℹ️<span class="tooltiptext">대상 사이트의 robots.txt 규칙을 준수하여 크롤링을 진행합니다.</span></span>:</label>
                        <input type="checkbox" id="respect-robots-checkbox" ${settings.respectRobots ? 'checked' : ''} title="대상 사이트의 robots.txt 규칙을 준수합니다.">
                    </div>
                    <!-- 동시 요청 섹션 -->
                    <div class="settings-section">
                        <label for="max-concurrent-requests-input">동시 요청 최대 수 <span class="tooltip">ℹ️<span class="tooltiptext">한 번에 요청할 수 있는 최대 동시 요청 수를 설정하세요.</span></span>:</label>
                        <input type="number" id="max-concurrent-requests-input" value="${settings.maxConcurrentRequests}" title="한 번에 요청할 수 있는 최대 동시 요청 수를 설정하세요." min="1" step="1">
                    </div>
                    <!-- 데이터 시각화 섹션 -->
                    <div class="settings-section">
                        <label for="data-visualization-checkbox">데이터 시각화 활성화 <span class="tooltip">ℹ️<span class="tooltiptext">수집된 데이터를 시각화하여 그래프로 표시합니다.</span></span>:</label>
                        <input type="checkbox" id="data-visualization-checkbox" ${settings.dataVisualization ? 'checked' : ''} title="수집된 데이터를 시각화합니다.">
                    </div>
                    <!-- CAPTCHA 처리 섹션 -->
                    <div class="settings-section">
                        <label for="enable-captchas-checkbox">CAPTCHA 처리 활성화 <span class="tooltip">ℹ️<span class="tooltiptext">CAPTCHA가 감지되었을 때 자동 처리를 활성화합니다. (제한적)</span></span>:</label>
                        <input type="checkbox" id="enable-captchas-checkbox" ${settings.enableCaptchas ? 'checked' : ''} title="CAPTCHA가 감지되었을 때 자동 처리를 활성화합니다.">
                    </div>
                    <!-- 내보내기 형식 섹션 -->
                    <div class="settings-section">
                        <label>데이터 내보내기 형식 <span class="tooltip">ℹ️<span class="tooltiptext">수집된 데이터를 내보낼 형식을 선택하세요.</span></span>:</label>
                        <div class="checkbox-group">
                            <label><input type="checkbox" id="export-csv" ${settings.exportFormats.csv ? 'checked' : ''}> CSV</label>
                            <label><input type="checkbox" id="export-json" ${settings.exportFormats.json ? 'checked' : ''}> JSON</label>
                            <label><input type="checkbox" id="export-xml" ${settings.exportFormats.xml ? 'checked' : ''}> XML</label>
                            <label><input type="checkbox" id="export-xls" ${settings.exportFormats.xls ? 'checked' : ''}> XLS</label>
                        </div>
                    </div>
                    <!-- 추가 로깅 섹션 -->
                    <div class="settings-section">
                        <label for="advanced-logging-checkbox">고급 로깅 활성화 <span class="tooltip">ℹ️<span class="tooltiptext">추가적인 로깅 정보를 활성화합니다.</span></span>:</label>
                        <input type="checkbox" id="advanced-logging-checkbox" ${settings.advancedLogging ? 'checked' : ''} title="추가적인 로깅 정보를 활성화합니다.">
                    </div>
                    <!-- 사용자 지정 키워드 분류 섹션 -->
                    <div class="settings-section">
                        <label for="custom-keywords-input">사용자 지정 키워드 분류 <span class="tooltip">ℹ️<span class="tooltiptext">사용자 지정 키워드를 추가하여 데이터 분류를 향상시킵니다. 형식: 카테고리:키워드1,키워드2,...</span></span>:</label>
                        <textarea id="custom-keywords-input" rows="5" style="width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;" title="사용자 지정 키워드를 추가하여 데이터 분류를 향상시킵니다. 형식: 카테고리:키워드1,키워드2,...\n예시:\n'IT:AI,Machine Learning,Blockchain'">${getCustomKeywordsText()}</textarea>
                    </div>
                    <button id="save-settings" title="설정을 저장합니다." style="margin-top: 10px; padding: 10px 20px; border: none; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;">저장</button>
                    <button id="reset-settings" title="기본 설정으로 초기화합니다." style="margin-top: 10px; padding: 10px 20px; border: none; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;">초기화</button>
                </div>
            </div>

            <!-- 모달 창 -->
            <div id="html-modal" class="modal">
                <div class="modal-content">
                    <span class="close">&times;</span>
                    <pre id="modal-html-content"></pre>
                </div>
            </div>
        </div>
    `);
    $('body').append(container);

    // 탭 전환 이벤트
    $('.crawler-tab').on('click', function() {
        const tab = $(this).data('tab');
        $('.crawler-tab').removeClass('active');
        $(this).addClass('active');
        $('#crawler-content > div').hide();
        $(`#tab-${tab}`).show();
        saveCurrentTab(tab);
    });

    // 단축키 이벤트
    $(document).on('keydown', function(e) {
        if (e.ctrlKey) {
            switch (e.key) {
                case '1':
                    $('.crawler-tab[data-tab="logs"]').click();
                    break;
                case '2':
                    $('.crawler-tab[data-tab="data"]').click();
                    break;
                case '3':
                    $('.crawler-tab[data-tab="settings"]').click();
                    break;
                case 's':
                    e.preventDefault();
                    saveSettings();
                    break;
                case 'p':
                    e.preventDefault();
                    if (!isCrawling) startCrawling();
                    break;
                case 'q':
                    e.preventDefault();
                    if (isCrawling) stopCrawling();
                    break;
                case 'd':
                    e.preventDefault();
                    downloadCSV();
                    break;
                case 'j':
                    e.preventDefault();
                    downloadJSON();
                    break;
                case 'x':
                    e.preventDefault();
                    downloadXML();
                    break;
                case 'l':
                    e.preventDefault();
                    downloadXLS();
                    break;
            }
        }
    });

    // 설정 저장 버튼 이벤트
    $('#save-settings').on('click', saveSettings);
    $('#reset-settings').on('click', resetSettings);

    // 추가/제거 사용자 에이전트 버튼 이벤트
    $('#add-user-agent').on('click', function() {
        const newUA = prompt("새 사용자 에이전트를 입력하세요:");
        if (newUA) {
            settings.userAgents.push(newUA);
            GM_setValue('userAgents', settings.userAgents);
            updateUserAgentSelect();
            log('새 사용자 에이전트가 추가되었습니다.', 'success');
        }
    });

    $('#remove-user-agent').on('click', function() {
        const selectedOptions = $('#user-agent-select').val();
        if (selectedOptions && selectedOptions.length > 0) {
            settings.userAgents = settings.userAgents.filter((_, idx) => !selectedOptions.includes(idx.toString()));
            GM_setValue('userAgents', settings.userAgents);
            settings.userAgentIndex = 0;
            GM_setValue('userAgentIndex', settings.userAgentIndex);
            updateUserAgentSelect();
            log('선택한 사용자 에이전트가 제거되었습니다.', 'success');
        } else {
            alert('제거할 사용자 에이전트를 선택하세요.');
        }
    });

    // 다운로드 버튼 이벤트
    $('#download-csv').on('click', downloadCSV);
    $('#download-json').on('click', downloadJSON);
    $('#download-xml').on('click', downloadXML);
    $('#download-xls').on('click', downloadXLS);

    // 검색 필터 기능
    $('#search-filter').on('input', function() {
        filterTable($(this).val());
    });

    // 스케줄링 옵션 표시/숨기기
    $('#schedule-enabled').on('change', function() {
        if ($(this).is(':checked')) {
            $('#schedule-options').slideDown();
        } else {
            $('#schedule-options').slideUp();
        }
    });

    // 데이터 시각화 토글
    $('#data-visualization-checkbox').on('change', function() {
        settings.dataVisualization = $(this).is(':checked');
        GM_setValue('dataVisualization', settings.dataVisualization);
        $('#data-visualization').toggle(settings.dataVisualization);
        log('데이터 시각화 설정이 변경되었습니다.', 'success');
        if (settings.dataVisualization) {
            renderChart();
        } else {
            $('#data-chart').remove();
        }
    });

    // CAPTCHA 처리 토글
    $('#enable-captchas-checkbox').on('change', function() {
        settings.enableCaptchas = $(this).is(':checked');
        GM_setValue('enableCaptchas', settings.enableCaptchas);
        log('CAPTCHA 처리 설정이 변경되었습니다.', 'success');
    });

    // 사용자 지정 키워드 분류 섹션 추가
    // 이미 textarea에 설정됨

    // 모달 창 기능
    const modal = document.getElementById("html-modal");
    const span = document.getElementsByClassName("close")[0];
    span.onclick = function() { modal.style.display = "none"; }
    window.onclick = function(event) {
        if (event.target == modal) {
            modal.style.display = "none";
        }
    }

    // 시작 및 중지 버튼 이벤트
    $('#start-crawling').on('click', startCrawling);
    $('#stop-crawling').on('click', stopCrawling);

    // export 버튼 추가 이벤트
    // XML 다운로드
    $('#download-xml').on('click', downloadXML);
    // XLS 다운로드
    $('#download-xls').on('click', downloadXLS);

    // 초기 UI 업데이트
    updateLogsUI();
    updateDataUI();
    if (settings.dataVisualization) {
        renderChart();
    }
}

function currentTab() {
    return GM_getValue('currentTab', 'logs');
}

function saveCurrentTab(tab) {
    GM_setValue('currentTab', tab);
}

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

// 사용자 지정 키워드 텍스트 생성 함수
function getCustomKeywordsText() {
    const customKeywords = settings.customKeywords || {};
    return Object.entries(customKeywords).map(([category, kws]) => `${category}:${kws.join(',')}`).join('\n');
}

// 사용자 지정 키워드 저장 및 로드 함수
function parseCustomKeywords(text) {
    const lines = text.split('\n').map(line => line.trim()).filter(line => line);
    const customKeywords = {};
    lines.forEach(line => {
        const [category, kws] = line.split(':');
        if (category && kws) {
            customKeywords[category.trim()] = kws.split(',').map(kw => kw.trim()).filter(kw => kw);
        }
    });
    return customKeywords;
}

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

    for (const [category, kws] of Object.entries(customKeywords)) {
        if (standardKeywords[category]) {
            standardKeywords[category] = [...new Set([...standardKeywords[category], ...kws])];
        } else {
            standardKeywords[category] = kws;
        }
    }

    return standardKeywords;
}

// 로깅 함수
function log(message, type = 'info') {
    const timestamp = new Date().toLocaleString();
    logs.push({ timestamp, message, type });
    GM_setValue('logs', logs);
    updateLogsUI();

    if (settings.advancedLogging) {
        // type을 콘솔 메서드로 매핑
        switch(type) {
            case 'info':
                console.info(`[${timestamp}] [INFO] ${message}`);
                break;
            case 'warning':
                console.warn(`[${timestamp}] [WARNING] ${message}`);
                break;
            case 'error':
                console.error(`[${timestamp}] [ERROR] ${message}`);
                break;
            case 'success':
                console.log(`%c[${timestamp}] [SUCCESS] ${message}`, 'color: green; font-weight: bold;');
                break;
            default:
                console.log(`[${timestamp}] [${type.toUpperCase()}] ${message}`);
        }
    }
}

function updateLogsUI() {
    const logHtml = logs.map(log => `<div>[${log.timestamp}] [${log.type.toUpperCase()}] ${log.message}</div>`).join('');
    $('#log-messages').html(logHtml);
    $('.progress-bar-fill').css('width', `${progress.toFixed(2)}%`);
    $('.status-text').text(`진행률: ${progress.toFixed(2)}% (${currentPage}/${totalPages} 페이지)`);
}

// 데이터 업데이트 함수
function updateDataUI() {
    const filter = $('#search-filter').val().toLowerCase();
    let filteredData = data;
    if (filter) {
        filteredData = data.filter(item =>
            (settings.dataFields.name && item.name.toLowerCase().includes(filter)) ||
            (settings.dataFields.url && item.url.toLowerCase().includes(filter)) ||
            (settings.dataFields.description && item.description.toLowerCase().includes(filter)) ||
            (settings.dataFields.html && item.html.toLowerCase().includes(filter)) ||
            (settings.dataFields.category && item.category.toLowerCase().includes(filter)) ||
            (settings.dataFields.language && item.language && item.language.toLowerCase().includes(filter)) ||
            (settings.dataFields.wordCount && item.wordCount.toString().includes(filter)) ||
            (settings.dataFields.metaTags && item.metaTags && Object.values(item.metaTags).some(v => v.toLowerCase().includes(filter))) ||
            (settings.dataFields.headings && item.headings && item.headings.some(h => h.toLowerCase().includes(filter)))
        );
    }

    let tableHtml = filteredData.map(item => `
        <tr>
            ${settings.dataFields.name ? `<td>${escapeHTML(item.name)}</td>` : ''}
            ${settings.dataFields.url ? `<td><a href="${escapeHTML(item.url)}" target="_blank">${escapeHTML(item.url)}</a></td>` : ''}
            ${settings.dataFields.description ? `<td>${escapeHTML(item.description)}</td>` : ''}
            ${settings.dataFields.html ? `<td><button class="view-html" data-html="${encodeURIComponent(item.html)}" title="HTML 내용을 봅니다.">📄 보기</button></td>` : ''}
            ${settings.dataFields.category ? `<td>${escapeHTML(item.category)}</td>` : ''}
            ${settings.dataFields.quality ? `<td>${escapeHTML(item.quality.toString())}</td>` : ''}
            ${settings.dataFields.language ? `<td>${escapeHTML(item.language)}</td>` : ''}
            ${settings.dataFields.wordCount ? `<td>${escapeHTML(item.wordCount.toString())}</td>` : ''}
            ${settings.dataFields.metaTags ? `<td>${escapeHTML(JSON.stringify(item.metaTags))}</td>` : ''}
            ${settings.dataFields.headings ? `<td>${escapeHTML(item.headings.join(', '))}</td>` : ''}
        </tr>
    `).join('');
    $('#data-table-body').html(tableHtml);

    // HTML 보기 모달
    $('.view-html').off('click').on('click', function() {
        const htmlContent = decodeURIComponent($(this).data('html'));
        $('#modal-html-content').text(htmlContent);
        $('#html-modal').show();
    });

    if (settings.dataVisualization) {
        renderChart();
    }
}

// 차트 렌더링 함수
function renderChart() {
    if (!document.getElementById('data-chart').getContext) return;

    const ctx = document.getElementById('data-chart').getContext('2d');

    // 기존 차트 제거
    if (window.dataChartInstance) {
        window.dataChartInstance.destroy();
    }

    const categories = {};
    data.forEach(item => {
        const category = item.category || '기타';
        if (categories[category]) {
            categories[category]++;
        } else {
            categories[category] = 1;
        }
    });

    const chartData = {
        labels: Object.keys(categories),
        datasets: [{
            label: '카테고리별 데이터 수',
            data: Object.values(categories),
            backgroundColor: generateColors(Object.keys(categories).length)
        }]
    };

    window.dataChartInstance = new Chart(ctx, {
        type: 'pie',
        data: chartData,
        options: {
            responsive: true
        }
    });
}

// 색상 생성 함수
function generateColors(count) {
    const colors = [];
    for(let i=0; i<count; i++) {
        const hue = i * (360 / count);
        colors.push(`hsl(${hue}, 70%, 50%)`);
    }
    return colors;
}

// HTML 이스케이프 함수
function escapeHTML(str) {
    if (!str) return '';
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
}

// CSV, JSON, XML, XLS 다운로드 함수
function downloadCSV() {
    if (data.length === 0) {
        log('다운로드할 데이터가 없습니다.', 'warning');
        return;
    }
    const headers = [];
    if (settings.dataFields.name) headers.push("사이트 이름");
    if (settings.dataFields.url) headers.push("주소");
    if (settings.dataFields.description) headers.push("설명");
    if (settings.dataFields.html) headers.push("📄 HTML 내용");
    if (settings.dataFields.category) headers.push("분류");
    if (settings.dataFields.quality) headers.push("품질점수");
    if (settings.dataFields.language) headers.push("언어");
    if (settings.dataFields.wordCount) headers.push("단어 수");
    if (settings.dataFields.metaTags) headers.push("메타 태그");
    if (settings.dataFields.headings) headers.push("헤딩");

    const csvRows = [
        headers.join(",")
    ];

    data.forEach(d => {
        const row = [];
        if (settings.dataFields.name) row.push(`"${escapeCSV(d.name)}"`);
        if (settings.dataFields.url) row.push(`"${escapeCSV(d.url)}"`);
        if (settings.dataFields.description) row.push(`"${escapeCSV(d.description)}"`);
        if (settings.dataFields.html) row.push(`"${escapeCSV(d.html)}"`);
        if (settings.dataFields.category) row.push(`"${escapeCSV(d.category)}"`);
        if (settings.dataFields.quality) row.push(`"${d.quality}"`);
        if (settings.dataFields.language) row.push(`"${escapeCSV(d.language)}"`);
        if (settings.dataFields.wordCount) row.push(`"${d.wordCount}"`);
        if (settings.dataFields.metaTags) row.push(`"${escapeCSV(JSON.stringify(d.metaTags))}"`);
        if (settings.dataFields.headings) row.push(`"${escapeCSV(d.headings.join('; '))}"`);
        csvRows.push(row.join(","));
    });

    const csvContent = "data:text/csv;charset=utf-8," + csvRows.join("\n");
    const encodedUri = encodeURI(csvContent);
    const link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    const filename = "data.csv";
    link.setAttribute("download", filename);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    log('CSV 파일이 다운로드되었습니다.', 'success');
}

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

function downloadXML() {
    if (data.length === 0) {
        log('다운로드할 데이터가 없습니다.', 'warning');
        return;
    }
    let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<data>\n';
    data.forEach(d => {
        xmlContent += `  <entry>\n`;
        if (settings.dataFields.name) xmlContent += `    <name>${escapeXML(d.name)}</name>\n`;
        if (settings.dataFields.url) xmlContent += `    <url>${escapeXML(d.url)}</url>\n`;
        if (settings.dataFields.description) xmlContent += `    <description>${escapeXML(d.description)}</description>\n`;
        if (settings.dataFields.html) xmlContent += `    <html><![CDATA[${d.html}]]></html>\n`;
        if (settings.dataFields.category) xmlContent += `    <category>${escapeXML(d.category)}</category>\n`;
        if (settings.dataFields.quality) xmlContent += `    <quality>${d.quality}</quality>\n`;
        if (settings.dataFields.language) xmlContent += `    <language>${escapeXML(d.language)}</language>\n`;
        if (settings.dataFields.wordCount) xmlContent += `    <wordCount>${d.wordCount}</wordCount>\n`;
        if (settings.dataFields.metaTags) xmlContent += `    <metaTags>${escapeXML(JSON.stringify(d.metaTags))}</metaTags>\n`;
        if (settings.dataFields.headings) xmlContent += `    <headings>${escapeXML(d.headings.join('; '))}</headings>\n`;
        xmlContent += `  </entry>\n`;
    });
    xmlContent += '</data>';

    const blob = new Blob([xmlContent], { type: "application/xml" });
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.setAttribute("href", url);
    const filename = "data.xml";
    link.setAttribute("download", filename);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    log('XML 파일이 다운로드되었습니다.', 'success');
}

function downloadXLS() {
    if (data.length === 0) {
        log('다운로드할 데이터가 없습니다.', 'warning');
        return;
    }
    const worksheet = XLSX.utils.json_to_sheet(data.map(d => {
        const entry = {};
        if (settings.dataFields.name) entry['사이트 이름'] = d.name;
        if (settings.dataFields.url) entry['주소'] = d.url;
        if (settings.dataFields.description) entry['설명'] = d.description;
        if (settings.dataFields.html) entry['📄 HTML 내용'] = d.html;
        if (settings.dataFields.category) entry['분류'] = d.category;
        if (settings.dataFields.quality) entry['품질점수'] = d.quality;
        if (settings.dataFields.language) entry['언어'] = d.language;
        if (settings.dataFields.wordCount) entry['단어 수'] = d.wordCount;
        if (settings.dataFields.metaTags) entry['메타 태그'] = JSON.stringify(d.metaTags);
        if (settings.dataFields.headings) entry['헤딩'] = d.headings.join('; ');
        return entry;
    }));
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
    XLSX.writeFile(workbook, "data.xlsx");
    log('XLS 파일이 다운로드되었습니다.', 'success');
}

// XML 이스케이프 함수
function escapeXML(str) {
    if (!str) return '';
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
}

// CSV 이스케이프 함수
function escapeCSV(str) {
    if (!str) return '';
    return str.replace(/"/g, '""');
}

// 테이블 필터링 함수
function filterTable(keyword) {
    $('#data-table-body tr').each(function() {
        const rowText = $(this).text().toLowerCase();
        if (rowText.includes(keyword.toLowerCase())) {
            $(this).show();
        } else {
            $(this).hide();
        }
    });
}

// 설정 저장 함수
function saveSettings() {
    const delay = parseInt($('#delay-input').val(), 10);
    settings.delay = isNaN(delay) ? defaultSettings.delay : delay;

    const delayRandom = parseInt($('#delay-random-input').val(), 10);
    settings.delayRandom = isNaN(delayRandom) ? defaultSettings.delayRandom : delayRandom;

    const maxPages = parseInt($('#max-pages-input').val(), 10);
    settings.maxPages = isNaN(maxPages) ? defaultSettings.maxPages : maxPages;

    // 사용자 에이전트 선택
    const selectedUAIndex = parseInt($('#user-agent-select').val(), 10);
    settings.userAgentIndex = isNaN(selectedUAIndex) ? 0 : selectedUAIndex;

    const contentInclude = $('#content-include-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);
    const contentExclude = $('#content-exclude-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);
    settings.contentTypes.include = contentInclude.length > 0 ? contentInclude : defaultSettings.contentTypes.include;
    settings.contentTypes.exclude = contentExclude.length > 0 ? contentExclude : defaultSettings.contentTypes.exclude;

    const timeout = parseInt($('#timeout-input').val(), 10);
    settings.timeout = isNaN(timeout) ? defaultSettings.timeout : timeout;

    const includeKeywords = $('#include-keywords-input').val().split('\n').map(kw => kw.trim()).filter(kw => kw);
    settings.includeKeywords = includeKeywords;

    const excludeKeywords = $('#exclude-keywords-input').val().split('\n').map(kw => kw.trim()).filter(kw => kw);
    settings.excludeKeywords = excludeKeywords;

    const regexInclude = $('#regex-include-input').val().split('\n').map(rx => rx.trim()).filter(rx => rx);
    settings.regexInclude = regexInclude;

    const regexExclude = $('#regex-exclude-input').val().split('\n').map(rx => rx.trim()).filter(rx => rx);
    settings.regexExclude = regexExclude;

    const language = $('#language-select').val() || [];
    settings.language = language;

    const minWordCount = parseInt($('#min-word-count-input').val(), 10);
    settings.minWordCount = isNaN(minWordCount) ? defaultSettings.minWordCount : minWordCount;

    const maxWordCount = parseInt($('#max-word-count-input').val(), 10);
    settings.maxWordCount = isNaN(maxWordCount) ? defaultSettings.maxWordCount : maxWordCount;

    const maxDepth = parseInt($('#max-depth-input').val(), 10);
    settings.maxDepth = isNaN(maxDepth) ? defaultSettings.maxDepth : maxDepth;

    const proxy = $('#proxy-input').val().trim();
    settings.proxy = proxy || defaultSettings.proxy;

    // 데이터 필드 설정
    settings.dataFields.name = $('#field-name').is(':checked');
    settings.dataFields.url = $('#field-url').is(':checked');
    settings.dataFields.description = $('#field-description').is(':checked');
    settings.dataFields.html = $('#field-html').is(':checked');
    settings.dataFields.category = $('#field-category').is(':checked');
    settings.dataFields.quality = $('#field-quality').is(':checked');
    settings.dataFields.language = $('#field-language').is(':checked');
    settings.dataFields.wordCount = $('#field-wordCount').is(':checked');
    settings.dataFields.metaTags = $('#field-metaTags').is(':checked');
    settings.dataFields.headings = $('#field-headings').is(':checked');

    // 재시도 설정
    const retryAttempts = parseInt($('#retry-attempts-input').val(), 10);
    settings.retryAttempts = isNaN(retryAttempts) ? defaultSettings.retryAttempts : retryAttempts;

    const retryDelay = parseInt($('#retry-delay-input').val(), 10);
    settings.retryDelay = isNaN(retryDelay) ? defaultSettings.retryDelay : retryDelay;

    // 스케줄링 설정
    settings.schedule.enabled = $('#schedule-enabled').is(':checked');
    const startTime = $('#start-time-input').val();
    settings.schedule.startTime = startTime || defaultSettings.schedule.startTime;
    const interval = parseInt($('#interval-input').val(), 10);
    settings.schedule.interval = isNaN(interval) ? defaultSettings.schedule.interval : interval * 60000; // 분을 밀리초로 변환

    // 검색 엔진 설정
    const searchEngines = {
        google: $('#engine-google').is(':checked'),
        bing: $('#engine-bing').is(':checked'),
        yahoo: $('#engine-yahoo').is(':checked'),
        duckduckgo: $('#engine-duckduckgo').is(':checked'),
        baidu: $('#engine-baidu').is(':checked'),
        yandex: $('#engine-yandex').is(':checked'),
        naver: $('#engine-naver').is(':checked'),
        daum: $('#engine-daum').is(':checked'),
        ask: $('#engine-ask').is(':checked'),
        ecosia: $('#engine-ecosia').is(':checked'),
        startpage: $('#engine-startpage').is(':checked'),
        searx: $('#engine-searx').is(':checked'),
        dogpile: $('#engine-dogpile').is(':checked'),
        bingimages: $('#engine-bingimages').is(':checked')
        // 추가 검색 엔진 옵션
    };
    settings.searchEngines = searchEngines;

    // robots.txt 준수 설정
    settings.respectRobots = $('#respect-robots-checkbox').is(':checked');

    const blacklist = $('#blacklist-input').val().split('\n').map(d => d.trim()).filter(d => d);
    settings.blacklist = blacklist;

    const whitelist = $('#whitelist-input').val().split('\n').map(d => d.trim()).filter(d => d);
    settings.whitelist = whitelist;

    const maxConcurrentRequests = parseInt($('#max-concurrent-requests-input').val(), 10);
    settings.maxConcurrentRequests = isNaN(maxConcurrentRequests) ? defaultSettings.maxConcurrentRequests : maxConcurrentRequests;

    // 데이터 시각화 설정
    settings.dataVisualization = $('#data-visualization-checkbox').is(':checked');
    GM_setValue('dataVisualization', settings.dataVisualization);

    // CAPTCHA 처리 설정
    settings.enableCaptchas = $('#enable-captchas-checkbox').is(':checked');
    GM_setValue('enableCaptchas', settings.enableCaptchas);

    // 내보내기 형식 설정
    settings.exportFormats.csv = $('#export-csv').is(':checked');
    settings.exportFormats.json = $('#export-json').is(':checked');
    settings.exportFormats.xml = $('#export-xml').is(':checked');
    settings.exportFormats.xls = $('#export-xls').is(':checked');

    // 고급 로깅 설정
    settings.advancedLogging = $('#advanced-logging-checkbox').is(':checked');
    GM_setValue('advancedLogging', settings.advancedLogging);

    // 사용자 지정 키워드 분류 저장
    const customKeywordsText = $('#custom-keywords-input').val();
    const customKeywords = parseCustomKeywords(customKeywordsText);
    settings.customKeywords = customKeywords;
    GM_setValue('customKeywords', customKeywords);

    // 저장
    GM_setValue('delay', settings.delay);
    GM_setValue('delayRandom', settings.delayRandom);
    GM_setValue('maxPages', settings.maxPages);
    GM_setValue('userAgentIndex', settings.userAgentIndex);
    GM_setValue('contentTypes', settings.contentTypes);
    GM_setValue('timeout', settings.timeout);
    GM_setValue('includeKeywords', settings.includeKeywords);
    GM_setValue('excludeKeywords', settings.excludeKeywords);
    GM_setValue('regexInclude', settings.regexInclude);
    GM_setValue('regexExclude', settings.regexExclude);
    GM_setValue('language', settings.language);
    GM_setValue('minWordCount', settings.minWordCount);
    GM_setValue('maxWordCount', settings.maxWordCount);
    GM_setValue('maxDepth', settings.maxDepth);
    GM_setValue('proxy', settings.proxy);
    GM_setValue('dataFields', settings.dataFields);
    GM_setValue('retryAttempts', settings.retryAttempts);
    GM_setValue('retryDelay', settings.retryDelay);
    GM_setValue('schedule', settings.schedule);
    GM_setValue('searchEngines', settings.searchEngines);
    GM_setValue('blacklist', settings.blacklist);
    GM_setValue('whitelist', settings.whitelist);
    GM_setValue('respectRobots', settings.respectRobots);
    GM_setValue('maxConcurrentRequests', settings.maxConcurrentRequests);
    GM_setValue('exportFormats', settings.exportFormats);

    // totalPages 갱신
    totalPages = settings.maxPages;
    GM_setValue('totalPages', totalPages);

    log('설정이 저장되었습니다.', 'success');

    // 스케줄링 설정이 활성화된 경우 스케줄링 시작
    if (settings.schedule.enabled) {
        scheduleCrawling();
    }

    // UI 업데이트
    updateDataUI();
    if (settings.dataVisualization) {
        renderChart();
    }
}

// 설정 초기화 함수
function resetSettings() {
    if (confirm("기본 설정으로 초기화하시겠습니까?")) {
        // 기본 설정으로 되돌리기
        GM_deleteValue('delay');
        GM_deleteValue('delayRandom');
        GM_deleteValue('maxPages');
        GM_deleteValue('userAgents');
        GM_deleteValue('userAgentIndex');
        GM_deleteValue('blacklist');
        GM_deleteValue('whitelist');
        GM_deleteValue('contentTypes');
        GM_deleteValue('timeout');
        GM_deleteValue('includeKeywords');
        GM_deleteValue('excludeKeywords');
        GM_deleteValue('regexInclude');
        GM_deleteValue('regexExclude');
        GM_deleteValue('language');
        GM_deleteValue('minWordCount');
        GM_deleteValue('maxWordCount');
        GM_deleteValue('maxDepth');
        GM_deleteValue('proxy');
        GM_deleteValue('dataFields');
        GM_deleteValue('retryAttempts');
        GM_deleteValue('retryDelay');
        GM_deleteValue('schedule');
        GM_deleteValue('searchEngines');
        GM_deleteValue('exportFormats');
        GM_deleteValue('respectRobots');
        GM_deleteValue('maxConcurrentRequests');
        GM_deleteValue('dataVisualization');
        GM_deleteValue('advancedLogging');
        GM_deleteValue('enableCaptchas');
        GM_deleteValue('customKeywords');

        // 기본 설정 적용
        Object.assign(settings, defaultSettings);
        GM_setValue('userAgents', settings.userAgents);
        GM_setValue('userAgentIndex', settings.userAgentIndex);
        GM_setValue('contentTypes', settings.contentTypes);
        GM_setValue('dataFields', settings.dataFields);
        GM_setValue('exportFormats', settings.exportFormats);
        GM_setValue('advancedLogging', settings.advancedLogging);
        GM_setValue('enableCaptchas', settings.enableCaptchas);

        // UI 업데이트
        $('input, textarea, select').each(function() {
            const id = $(this).attr('id');
            if (settings.hasOwnProperty(id)) {
                $(this).val(settings[id]);
            }
        });

        // 사용자 지정 키워드 초기화
        $('#custom-keywords-input').val('');

        updateUserAgentSelect();
        updateDataUI();
        renderChart();
        log('기본 설정으로 초기화되었습니다.', 'success');
    }
}

// 크롤링 시작 함수
async function startCrawling() {
    if (isCrawling) {
        log('이미 크롤링이 진행 중입니다.', 'warning');
        return;
    }
    // 활성화된 검색 엔진 확인
    if (!isAnySearchEngineEnabled()) {
        log('사용할 검색 엔진이 하나도 선택되지 않았습니다.', 'error');
        return;
    }
    isCrawling = true;
    GM_setValue('isCrawling', isCrawling);
    log('크롤링을 시작합니다.', 'info');
    setButtonStates();
    currentPage = GM_getValue('currentPage', 1);
    totalPages = GM_getValue('totalPages', settings.maxPages);
    GM_setValue('totalPages', totalPages);
    currentDepth = GM_getValue('currentDepth', 0);
    await crawlPage(currentDepth);
}

// 크롤링 중지 함수
function stopCrawling() {
    if (!isCrawling) {
        log('크롤링이 진행 중이지 않습니다.', 'warning');
        return;
    }
    isCrawling = false;
    GM_setValue('isCrawling', isCrawling);
    log('크롤링이 중지되었습니다.', 'warning');
    setButtonStates();
}

// 버튼 상태 설정 함수
function setButtonStates() {
    if (isCrawling) {
        $('#start-crawling').prop('disabled', true);
        $('#stop-crawling').prop('disabled', false);
    } else {
        $('#start-crawling').prop('disabled', false);
        $('#stop-crawling').prop('disabled', true);
    }
}

// 크롤링 함수
async function crawlPage(currentDepthLevel) {
    try {
        while (isCrawling && currentPage <= totalPages && currentDepthLevel <= settings.maxDepth) {
            log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${currentDepthLevel})`, 'info');

            // robots.txt 준수 체크
            if (settings.respectRobots) {
                const robotsAllowed = await checkRobotsTxt(window.location.href);
                if (!robotsAllowed) {
                    log(`robots.txt에 의해 크롤링이 금지된 페이지: ${window.location.href}`, 'warning');
                    break;
                }
            }

            const searchResults = extractSearchResults();
            if (!searchResults) {
                log('검색 결과를 찾을 수 없습니다.', 'error');
                break;
            }

            for (let result of searchResults) {
                if (!isCrawling) break;

                const name = result.name;
                const link = result.url;
                const description = result.description;

                // 중복 검사
                if (visitedUrls.includes(link)) {
                    log(`중복된 URL 발견: ${link}`, 'warning');
                    continue;
                }

                // 블랙리스트/화이트리스트 검사
                if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => link.includes(domain))) {
                    log(`화이트리스트에 없는 도메인: ${link}`, 'warning');
                    continue;
                }
                if (settings.blacklist.some(domain => link.includes(domain))) {
                    log(`블랙리스트에 있는 도메인: ${link}`, 'warning');
                    continue;
                }

                // 언어 필터링 검사
                if (settings.language.length > 0) {
                    const pageLanguage = await getPageLanguage(link);
                    if (!settings.language.includes(pageLanguage)) {
                        log(`선택된 언어에 맞지 않는 페이지: ${link} (${pageLanguage})`, 'warning');
                        continue;
                    }
                }

                // 키워드 포함/제외 검사
                if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                    log(`포함 키워드가 없는 URL: ${link}`, 'warning');
                    continue;
                }
                if (settings.excludeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                    log(`제외 키워드가 포함된 URL: ${link}`, 'warning');
                    continue;
                }

                // 정규 표현식 필터링
                if (settings.regexInclude.length > 0) {
                    const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                    if (!matchesInclude) {
                        log(`정규 표현식 포함 조건에 맞지 않는 URL: ${link}`, 'warning');
                        continue;
                    }
                }
                if (settings.regexExclude.length > 0) {
                    const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                    if (matchesExclude) {
                        log(`정규 표현식 제외 조건에 맞는 URL: ${link}`, 'warning');
                        continue;
                    }
                }

                // 큐에 추가
                requestQueue.push({ url: link, depth: currentDepthLevel });

                // 최대 동시 요청 도달 시 대기
                if (activeRequests >= settings.maxConcurrentRequests) {
                    break;
                }
            }

            // 큐 처리 시작
            processQueue();

            // 진행률 업데이트
            progress = (currentPage / totalPages) * 100;
            GM_setValue('progress', progress);
            updateLogsUI();

            // 다음 페이지로 이동
            if (currentPage < totalPages && isCrawling) {
                const nextPageUrl = getNextPageUrl();
                if (nextPageUrl) {
                    currentPage++;
                    GM_setValue('currentPage', currentPage);
                    currentDepth++;
                    GM_setValue('currentDepth', currentDepth);
                    log(`다음 페이지로 이동: ${currentPage}`, 'info');
                    await dynamicDelay();
                    window.location.href = nextPageUrl;
                    return; // 페이지 이동 후 크롤링 이어서 하기 위해 함수 종료
                } else {
                    log('다음 페이지 링크를 찾을 수 없습니다.', 'error');
                    break;
                }
            } else {
                break;
            }
        }

        // 큐가 비어있고 모든 요청이 완료된 경우 크롤링 완료
        if (isCrawling && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {
            log('크롤링이 완료되었습니다.', 'success');
            isCrawling = false;
            GM_setValue('isCrawling', isCrawling);
            setButtonStates();
            GM_setValue('progress', 100);
            $('.progress-bar-fill').css('width', `100%`);
            $('.status-text').text(`진행률: 100% (${currentPage}/${totalPages} 페이지)`);
        }
    } catch (error) {
        log(`크롤링 중 오류 발생: ${error.message}`, 'error');
    } finally {
        // 크롤링이 아직 진행 중인 경우 state를 유지
        if (!isCrawling) {
            // 크롤링이 중지된 경우에만 상태를 초기화
            GM_setValue('isCrawling', isCrawling);
            setButtonStates();
            log('크롤링이 중지되었습니다.', 'info');
        }
        // 페이지 이동 시에는 상태를 유지하고, 다음 페이지에서 계속 크롤링
    }
}

// 동시 요청 제어 함수
async function processQueue() {
    while (requestQueue.length > 0 && activeRequests < settings.maxConcurrentRequests) {
        const { url, depth } = requestQueue.shift();
        activeRequests++;
        try {
            await handleRequest(url, depth);
        } catch (error) {
            log(`요청 처리 중 오류 발생: ${url} - ${error.message}`, 'error');
        } finally {
            activeRequests--;
            processQueue();
        }
    }
}

async function handleRequest(url, depthLevel) {
    if (!isCrawling) return;

    // 중복 검사
    if (visitedUrls.includes(url)) {
        log(`중복된 URL 발견: ${url}`, 'warning');
        return;
    }

    // 블랙리스트/화이트리스트 검사
    if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => url.includes(domain))) {
        log(`화이트리스트에 없는 도메인: ${url}`, 'warning');
        return;
    }
    if (settings.blacklist.some(domain => url.includes(domain))) {
        log(`블랙리스트에 있는 도메인: ${url}`, 'warning');
        return;
    }

    // 언어 필터링 검사
    if (settings.language.length > 0) {
        const pageLanguage = await getPageLanguage(url);
        if (!settings.language.includes(pageLanguage)) {
            log(`선택된 언어에 맞지 않는 페이지: ${url} (${pageLanguage})`, 'warning');
            return;
        }
    }

    // 키워드 포함/제외 검사
    if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => url.includes(kw))) {
        log(`포함 키워드가 없는 URL: ${url}`, 'warning');
        return;
    }
    if (settings.excludeKeywords.some(kw => url.includes(kw))) {
        log(`제외 키워드가 포함된 URL: ${url}`, 'warning');
        return;
    }

    // 정규 표현식 필터링
    if (settings.regexInclude.length > 0) {
        const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(url));
        if (!matchesInclude) {
            log(`정규 표현식 포함 조건에 맞지 않는 URL: ${url}`, 'warning');
            return;
        }
    }
    if (settings.regexExclude.length > 0) {
        const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(url));
        if (matchesExclude) {
            log(`정규 표현식 제외 조건에 맞는 URL: ${url}`, 'warning');
            return;
        }
    }

    // 데이터 수집
    try {
        const response = await fetchWithRetry(url, settings.retryAttempts, settings.retryDelay);
        const contentType = getContentType(response);
        if (!isContentTypeAllowed(contentType)) {
            log(`허용되지 않은 콘텐츠 유형(${contentType})의 URL: ${url}`, 'warning');
            return;
        }

        const pageHtml = response.responseText;
        const parser = new DOMParser();
        const pageDoc = parser.parseFromString(pageHtml, 'text/html');
        const textContent = pageDoc.body ? pageDoc.body.innerText : '';

        // CAPTCHA 감지 및 처리
        if (settings.enableCaptchas && detectCaptcha(pageDoc)) {
            await handleCaptcha(url);
            return;
        }

        // 단어 수 검사
        const wordCount = textContent.split(/\s+/).length;
        if (wordCount < settings.minWordCount) {
            log(`단어 수가 최소 기준 (${settings.minWordCount})에 미치지 못하는 URL: ${url} (${wordCount} 단어)`, 'warning');
            return;
        }
        if (wordCount > settings.maxWordCount) {
            log(`단어 수가 최대 기준 (${settings.maxWordCount})을 초과하는 URL: ${url} (${wordCount} 단어)`, 'warning');
            return;
        }

        // 콘텐츠 품질 평가
        const qualityScore = evaluateContentQuality(textContent);

        // 언어 추출
        const language = pageDoc.documentElement.lang || 'unknown';

        // 키워드 분류
        const category = classifyKeywords(textContent);

        // 메타 태그 및 헤딩 추출
        const metaTags = extractMetaTags(pageDoc);
        const headings = extractHeadings(pageDoc);

        // 조건에 맞는 경우에만 데이터 추가
        if (category !== '기타' || qualityScore > 100) { // 예시 조건
            const dataEntry = {};
            if (settings.dataFields.name) dataEntry.name = extractName(pageDoc) || 'No Title';
            if (settings.dataFields.url) dataEntry.url = url;
            if (settings.dataFields.description) dataEntry.description = extractDescription(pageDoc) || 'No Description';
            if (settings.dataFields.html) dataEntry.html = pageHtml;
            if (settings.dataFields.category) dataEntry.category = category;
            if (settings.dataFields.quality) dataEntry.quality = qualityScore;
            if (settings.dataFields.language) dataEntry.language = language;
            if (settings.dataFields.wordCount) dataEntry.wordCount = wordCount;
            if (settings.dataFields.metaTags) dataEntry.metaTags = metaTags;
            if (settings.dataFields.headings) dataEntry.headings = headings;

            data.push(dataEntry);
            GM_setValue('data', data);
            updateDataUI();
            log(`데이터 수집 완료: ${url}`, 'success');

            // 데이터 시각화 업데이트
            if (settings.dataVisualization) {
                renderChart();
            }
        } else {
            log(`조건에 맞지 않아 데이터에 추가되지 않은 URL: ${url}`, 'info');
        }

        // 링크 추적을 위한 추가 로직 (심층 크롤링)
        if (depthLevel < settings.maxDepth) {
            const additionalLinks = extractLinks(pageDoc);
            for (let additionalLink of additionalLinks) {
                if (!visitedUrls.includes(additionalLink) && isValidURL(additionalLink)) {
                    requestQueue.push({ url: additionalLink, depth: depthLevel + 1 });
                }
            }
        }

    } catch (error) {
        log(`데이터 수집 실패: ${url} - ${error.message}`, 'error');
        // 실패한 경우 data에 추가하지 않음
    }

    visitedUrls.push(url);
    GM_setValue('visitedUrls', visitedUrls);
}

// 크롤링 함수에 재시도 추가
async function fetchWithRetry(url, attempts, delayMs) {
    for (let attempt = 1; attempt <= attempts; attempt++) {
        try {
            const response = await GM_xmlhttpRequestPromise(url);
            return response;
        } catch (error) {
            log(`요청 실패 (${attempt}/${attempts}): ${url} - ${error.message}`, 'warning');
            if (attempt < attempts) {
                await delay(delayMs);
            } else {
                throw error;
            }
        }
    }
}

// 활성화된 검색 엔진이 있는지 확인
function isAnySearchEngineEnabled() {
    return Object.values(settings.searchEngines).some(enabled => enabled);
}

// 사용자 지정 키워드 분류 통합
function classifyKeywords(text) {
    const standardKeywords = combineKeywords();
    for (const [category, kws] of Object.entries(standardKeywords)) {
        if (kws.some(kw => text.includes(kw))) {
            return category;
        }
    }
    return '기타';
}

// 검색 결과 추출 함수 (각 검색 엔진에 맞게 수정됨)
function extractSearchResults() {
    let results = [];
    // 구글 검색 결과 추출 (업데이트된 셀렉터)
    if (window.location.hostname.includes('google.com') && settings.searchEngines.google) {
        const items = document.querySelectorAll('div.g, div[data-hveid]');
        items.forEach(item => {
            const nameElement = item.querySelector('h3');
            const linkElement = item.querySelector('a');
            let descElement = item.querySelector('.aCOpRe');
            if (!descElement) {
                descElement = item.querySelector('.VwiC3b');
            }

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement.href : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // 빙 검색 결과 추출 (업데이트된 셀렉터)
    else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bing) {
        const items = document.querySelectorAll('.b_algo');
        items.forEach(item => {
            const nameElement = item.querySelector('h2');
            const linkElement = nameElement ? nameElement.querySelector('a') : null;
            const descElement = item.querySelector('.b_caption p');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement.href : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // 야후 검색 결과 추출 (업데이트된 셀렉터)
    else if (window.location.hostname.includes('search.yahoo.com') && settings.searchEngines.yahoo) {
        const items = document.querySelectorAll('div.dd.algo');
        items.forEach(item => {
            const nameElement = item.querySelector('h3.title');
            const linkElement = nameElement ? nameElement.querySelector('a') : null;
            const descElement = item.querySelector('.compText p');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement.href : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // 네이버 검색 결과 추출 (업데이트된 셀렉터)
    else if (window.location.hostname.includes('naver.com') && settings.searchEngines.naver) {
        const items = document.querySelectorAll('.news.mynews.section._prs_nws');
        items.forEach(item => {
            const nameElement = item.querySelector('.news_area .news_tit');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.dsc_wrap');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // 다음 검색 결과 추출 (업데이트된 셀렉터)
    else if (window.location.hostname.includes('daum.net') && settings.searchEngines.daum) {
        const items = document.querySelectorAll('.wrap_cont div');
        items.forEach(item => {
            const nameElement = item.querySelector('a.f_link_b');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.desc');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // DuckDuckGo 검색 결과 추출 (업데이트된 셀렉터)
    else if (window.location.hostname.includes('duckduckgo.com') && settings.searchEngines.duckduckgo) {
        const items = document.querySelectorAll('.result');
        items.forEach(item => {
            const nameElement = item.querySelector('h2 a.result__a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.result__snippet');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Baidu 검색 결과 추출 (예시)
    else if (window.location.hostname.includes('baidu.com') && settings.searchEngines.baidu) {
        const items = document.querySelectorAll('.result');
        items.forEach(item => {
            const nameElement = item.querySelector('h3 a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.c-abstract');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Yandex 검색 결과 추출 (예시)
    else if (window.location.hostname.includes('yandex.com') && settings.searchEngines.yandex) {
        const items = document.querySelectorAll('.serp-item');
        items.forEach(item => {
            const nameElement = item.querySelector('.organic__title-link');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.text-container');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Ask.com 검색 결과 추출 (추가된 검색 엔진)
    else if (window.location.hostname.includes('ask.com') && settings.searchEngines.ask) {
        const items = document.querySelectorAll('.PartialSearchResults-item');
        items.forEach(item => {
            const nameElement = item.querySelector('h2 a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.PartialSearchResults-item-abstract');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Ecosia 검색 결과 추출 (추가된 검색 엔진)
    else if (window.location.hostname.includes('ecosia.org') && settings.searchEngines.ecosia) {
        const items = document.querySelectorAll('.result-item');
        items.forEach(item => {
            const nameElement = item.querySelector('.result-title a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.result-description');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Startpage 검색 결과 추출 (추가된 검색 엔진)
    else if (window.location.hostname.includes('startpage.com') && settings.searchEngines.startpage) {
        const items = document.querySelectorAll('.w-gl__result');
        items.forEach(item => {
            const nameElement = item.querySelector('.w-gl__result-title a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.w-gl__result-text');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Searx 검색 결과 추출 (추가된 검색 엔진)
    else if (window.location.hostname.includes('searx') && settings.searchEngines.searx) {
        const items = document.querySelectorAll('.result');
        items.forEach(item => {
            const nameElement = item.querySelector('.result_title a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.result_desc');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Dogpile 검색 결과 추출 (추가된 검색 엔진)
    else if (window.location.hostname.includes('dogpile.com') && settings.searchEngines.dogpile) {
        const items = document.querySelectorAll('.result_info');
        items.forEach(item => {
            const nameElement = item.querySelector('h3 a');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.result_snippet');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // Bing Images 검색 결과 추출 (추가된 검색 엔진)
    else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bingimages) {
        const items = document.querySelectorAll('.iusc');
        items.forEach(item => {
            const nameElement = item.querySelector('a.iusc');
            const linkElement = nameElement ? nameElement.href : '';
            const descElement = item.querySelector('.txt');

            const name = nameElement ? nameElement.innerText.trim() : 'No Title';
            const link = linkElement ? linkElement : '';
            const description = descElement ? descElement.innerText.trim() : 'No Description';

            if (link && isValidURL(link)) results.push({ name, url: link, description });
        });
    }
    // 기타 검색 엔진에 대한 추가 로직 필요
    else {
        log('지원되지 않거나 비활성화된 검색 엔진입니다.', 'warning');
    }

    return results.length > 0 ? results : null;
}

// 다음 페이지 URL 얻는 함수 (각 검색 엔진에 맞게 수정됨)
function getNextPageUrl() {
    let nextPageUrl = null;
    // 구글 다음 페이지 링크
    if (window.location.hostname.includes('google.com') && settings.searchEngines.google) {
        const nextLink = document.querySelector('a#pnnext');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // 빙 다음 페이지 링크
    else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bing) {
        const nextLink = document.querySelector('a.sb_pagN');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // 야후 다음 페이지 링크
    else if (window.location.hostname.includes('search.yahoo.com') && settings.searchEngines.yahoo) {
        const nextLink = document.querySelector('a.next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // 네이버 다음 페이지 링크
    else if (window.location.hostname.includes('naver.com') && settings.searchEngines.naver) {
        const nextLink = document.querySelector('.sc_page_inner a.btn_next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // 다음 다음 페이지 링크
    else if (window.location.hostname.includes('daum.net') && settings.searchEngines.daum) {
        const nextLink = document.querySelector('.paging a.btn_next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // DuckDuckGo 다음 페이지 링크 (예시)
    else if (window.location.hostname.includes('duckduckgo.com') && settings.searchEngines.duckduckgo) {
        const nextLink = document.querySelector('a.js-next-page');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Baidu 다음 페이지 링크 (예시)
    else if (window.location.hostname.includes('baidu.com') && settings.searchEngines.baidu) {
        const nextLink = document.querySelector('.n');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Yandex 다음 페이지 링크 (예시)
    else if (window.location.hostname.includes('yandex.com') && settings.searchEngines.yandex) {
        const nextLink = document.querySelector('.pager__item_kind_next a');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Ask.com 다음 페이지 링크 (추가된 검색 엔진)
    else if (window.location.hostname.includes('ask.com') && settings.searchEngines.ask) {
        const nextLink = document.querySelector('.pagination a.next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Ecosia 다음 페이지 링크 (추가된 검색 엔진)
    else if (window.location.hostname.includes('ecosia.org') && settings.searchEngines.ecosia) {
        const nextLink = document.querySelector('.pagination a.next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Startpage 다음 페이지 링크 (추가된 검색 엔진)
    else if (window.location.hostname.includes('startpage.com') && settings.searchEngines.startpage) {
        const nextLink = document.querySelector('.pagination a.next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Searx 다음 페이지 링크 (추가된 검색 엔진)
    else if (window.location.hostname.includes('searx') && settings.searchEngines.searx) {
        const nextLink = document.querySelector('.pagination a.next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Dogpile 다음 페이지 링크 (추가된 검색 엔진)
    else if (window.location.hostname.includes('dogpile.com') && settings.searchEngines.dogpile) {
        const nextLink = document.querySelector('.pagination a.next');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // Bing Images 다음 페이지 링크 (추가된 검색 엔진)
    else if (window.location.hostname.includes('bing.com') && settings.searchEngines.bingimages) {
        const nextLink = document.querySelector('a.sb_pagN');
        if (nextLink) nextPageUrl = nextLink.href;
    }
    // 기타 검색 엔진에 대한 추가 로직 필요

    log(`다음 페이지 URL: ${nextPageUrl || '없음'}`, 'info');
    return nextPageUrl;
}

// GM_xmlhttpRequest를 Promise로 래핑
function GM_xmlhttpRequestPromise(url) {
    return new Promise((resolve, reject) => {
        let requestUrl = url;
        if (settings.proxy) {
            // 프록시 요청을 위한 URL 구성
            requestUrl = `${settings.proxy}?url=${encodeURIComponent(url)}`;
        }

        GM_xmlhttpRequest({
            method: "GET",
            url: requestUrl,
            headers: {
                'User-Agent': getCurrentUserAgent(),
                'Accept': '*/*'
            },
            timeout: settings.timeout,
            onload: function(response) {
                if (response.status >= 200 && response.status < 300) {
                    resolve(response);
                } else {
                    reject(new Error(`HTTP Status ${response.status}`));
                }
            },
            onerror: function(error) {
                reject(new Error('Request failed'));
            },
            ontimeout: function() {
                reject(new Error('Request timed out'));
            }
        });
    });
}

// 현재 사용자 에이전트 얻는 함수
function getCurrentUserAgent() {
    return settings.userAgents[settings.userAgentIndex % settings.userAgents.length];
}

// 콘텐츠 유형 얻는 함수
function getContentType(response) {
    const contentTypeHeader = response.responseHeaders.split('\n').find(header => header.toLowerCase().startsWith('content-type'));
    return contentTypeHeader ? contentTypeHeader.split(':')[1].trim().split(';')[0] : '';
}

// 콘텐츠 유형 허용 여부 검사
function isContentTypeAllowed(contentType) {
    if (settings.contentTypes.include.length > 0 && !settings.contentTypes.include.includes(contentType)) {
        return false;
    }
    if (settings.contentTypes.exclude.length > 0 && settings.contentTypes.exclude.includes(contentType)) {
        return false;
    }
    return true;
}

// 콘텐츠 품질 평가 함수
function evaluateContentQuality(text) {
    const wordCount = text.split(/\s+/).length;
    return wordCount;
}

// 키워드 분류 함수 (사용자 지정 키워드 포함)
function classifyKeywords(text) {
    const standardKeywords = combineKeywords();
    for (const [category, kws] of Object.entries(standardKeywords)) {
        if (kws.some(kw => text.includes(kw))) {
            return category;
        }
    }
    return '기타';
}

// 메타 태그 추출 함수
function extractMetaTags(pageDoc) {
    const metaTags = {};
    const metas = pageDoc.querySelectorAll('meta');
    metas.forEach(meta => {
        const name = meta.getAttribute('name') || meta.getAttribute('property');
        const content = meta.getAttribute('content');
        if (name && content) {
            metaTags[name.toLowerCase()] = content;
        }
    });
    return metaTags;
}

// 헤딩 추출 함수
function extractHeadings(pageDoc) {
    const headings = [];
    for (let i = 1; i <= 6; i++) {
        const hTags = pageDoc.querySelectorAll(`h${i}`);
        hTags.forEach(h => {
            headings.push(h.innerText.trim());
        });
    }
    return headings;
}

// 사용자 지정 키워드 분류 섹션 추가
// 이미 textarea에 설정됨

// URL 유효성 검사 함수
function isValidURL(url) {
    try {
        new URL(url);
        return true;
    } catch (_) {
        return false;
    }
}

// robots.txt 확인 함수
async function checkRobotsTxt(url) {
    try {
        const parsedUrl = new URL(url);
        const robotsUrl = `${parsedUrl.origin}/robots.txt`;
        const response = await GM_xmlhttpRequestPromise(robotsUrl);
        const robotsText = response.responseText;
        const parser = new RobotsParser(robotsUrl, robotsText);
        return parser.isAllowed(url, getCurrentUserAgent());
    } catch (error) {
        log(`robots.txt 확인 실패: ${error.message}`, 'warning');
        // robots.txt를 가져올 수 없으면 기본적으로 허용
        return true;
    }
}

// robots-parser 라이브러리 (간단한 구현)
class RobotsParser {
    constructor(url, text) {
        this.rules = [];
        this.parse(text);
    }

    parse(text) {
        const lines = text.split('\n');
        let userAgents = [];
        for (let line of lines) {
            line = line.trim();
            if (line.startsWith('#') || line === '') continue;
            const parts = line.split(':');
            if (parts.length < 2) continue;
            const key = parts[0].trim().toLowerCase();
            const value = parts.slice(1).join(':').trim();
            if (key === 'user-agent') {
                userAgents = [value.toLowerCase()];
            } else if (key === 'disallow') {
                this.rules.push({ userAgents: [...userAgents], disallow: value });
            } else if (key === 'allow') {
                this.rules.push({ userAgents: [...userAgents], allow: value });
            }
        }
    }

    isAllowed(url, ua) {
        const path = new URL(url).pathname;
        let allowed = true;
        for (let rule of this.rules) {
            if (rule.userAgents.includes('*') || rule.userAgents.includes(ua.toLowerCase())) {
                if (rule.disallow && path.startsWith(rule.disallow)) {
                    allowed = false;
                }
                if (rule.allow && path.startsWith(rule.allow)) {
                    allowed = true;
                }
            }
        }
        return allowed;
    }
}

// 큐에 있는 요청을 처리하는 딜레이 함수
async function processQueueWithDelay() {
    if (requestQueue.length > 0 && activeRequests < settings.maxConcurrentRequests) {
        const { url, depth } = requestQueue.shift();
        activeRequests++;
        try {
            await handleRequest(url, depth);
        } catch (error) {
            log(`요청 처리 중 오류 발생: ${url} - ${error.message}`, 'error');
        } finally {
            activeRequests--;
            processQueueWithDelay();
        }
    }
}

// 크롤링 시작: 기존 페이지에서 크롤링 재개
async function resumeCrawling() {
    if (isCrawling) {
        await crawlPage(currentDepth);
    }
}

// 크롤링 함수 (재작성)
async function crawlPage(currentDepthLevel) {
    try {
        while (isCrawling && currentPage <= totalPages && currentDepthLevel <= settings.maxDepth) {
            log(`페이지 ${currentPage} 크롤링 시작. (깊이: ${currentDepthLevel})`, 'info');

            // robots.txt 준수 체크
            if (settings.respectRobots) {
                const robotsAllowed = await checkRobotsTxt(window.location.href);
                if (!robotsAllowed) {
                    log(`robots.txt에 의해 크롤링이 금지된 페이지: ${window.location.href}`, 'warning');
                    break;
                }
            }

            const searchResults = extractSearchResults();
            if (!searchResults) {
                log('검색 결과를 찾을 수 없습니다.', 'error');
                break;
            }

            for (let result of searchResults) {
                if (!isCrawling) break;

                const name = result.name;
                const link = result.url;
                const description = result.description;

                // 중복 검사
                if (visitedUrls.includes(link)) {
                    log(`중복된 URL 발견: ${link}`, 'warning');
                    continue;
                }

                // 블랙리스트/화이트리스트 검사
                if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => link.includes(domain))) {
                    log(`화이트리스트에 없는 도메인: ${link}`, 'warning');
                    continue;
                }
                if (settings.blacklist.some(domain => link.includes(domain))) {
                    log(`블랙리스트에 있는 도메인: ${link}`, 'warning');
                    continue;
                }

                // 언어 필터링 검사
                if (settings.language.length > 0) {
                    const pageLanguage = await getPageLanguage(link);
                    if (!settings.language.includes(pageLanguage)) {
                        log(`선택된 언어에 맞지 않는 페이지: ${link} (${pageLanguage})`, 'warning');
                        continue;
                    }
                }

                // 키워드 포함/제외 검사
                if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                    log(`포함 키워드가 없는 URL: ${link}`, 'warning');
                    continue;
                }
                if (settings.excludeKeywords.some(kw => link.includes(kw) || description.includes(kw))) {
                    log(`제외 키워드가 포함된 URL: ${link}`, 'warning');
                    continue;
                }

                // 정규 표현식 필터링
                if (settings.regexInclude.length > 0) {
                    const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                    if (!matchesInclude) {
                        log(`정규 표현식 포함 조건에 맞지 않는 URL: ${link}`, 'warning');
                        continue;
                    }
                }
                if (settings.regexExclude.length > 0) {
                    const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(link) || new RegExp(pattern).test(description));
                    if (matchesExclude) {
                        log(`정규 표현식 제외 조건에 맞는 URL: ${link}`, 'warning');
                        continue;
                    }
                }

                // 큐에 추가
                requestQueue.push({ url: link, depth: currentDepthLevel });

                // 최대 동시 요청 도달 시 대기
                if (activeRequests >= settings.maxConcurrentRequests) {
                    break;
                }
            }

            // 큐 처리 시작
            processQueueWithDelay();

            // 진행률 업데이트
            progress = (currentPage / totalPages) * 100;
            GM_setValue('progress', progress);
            updateLogsUI();

            // 다음 페이지로 이동
            if (currentPage < totalPages && isCrawling) {
                const nextPageUrl = getNextPageUrl();
                if (nextPageUrl) {
                    currentPage++;
                    GM_setValue('currentPage', currentPage);
                    currentDepth++;
                    GM_setValue('currentDepth', currentDepth);
                    log(`다음 페이지로 이동: ${currentPage}`, 'info');
                    await dynamicDelay();
                    window.location.href = nextPageUrl;
                    return; // 페이지 이동 후 크롤링 이어서 하기 위해 함수 종료
                } else {
                    log('다음 페이지 링크를 찾을 수 없습니다.', 'error');
                    break;
                }
            } else {
                break;
            }
        }

        // 큐가 비어있고 모든 요청이 완료된 경우 크롤링 완료
        if (isCrawling && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {
            log('크롤링이 완료되었습니다.', 'success');
            isCrawling = false;
            GM_setValue('isCrawling', isCrawling);
            setButtonStates();
            GM_setValue('progress', 100);
            $('.progress-bar-fill').css('width', `100%`);
            $('.status-text').text(`진행률: 100% (${currentPage}/${totalPages} 페이지)`);
        }
    } catch (error) {
        log(`크롤링 중 오류 발생: ${error.message}`, 'error');
    } finally {
        // 크롤링이 아직 진행 중인 경우 state를 유지
        if (!isCrawling) {
            // 크롤링이 중지된 경우에만 상태를 초기화
            GM_setValue('isCrawling', isCrawling);
            setButtonStates();
            log('크롤링이 중지되었습니다.', 'info');
        }
        // 페이지 이동 시에는 상태를 유지하고, 다음 페이지에서 계속 크롤링
    }
}

// 동시 요청 제어 함수
async function handleRequest(url, depthLevel) {
    if (!isCrawling) return;

    // 중복 검사
    if (visitedUrls.includes(url)) {
        log(`중복된 URL 발견: ${url}`, 'warning');
        return;
    }

    // 블랙리스트/화이트리스트 검사
    if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => url.includes(domain))) {
        log(`화이트리스트에 없는 도메인: ${url}`, 'warning');
        return;
    }
    if (settings.blacklist.some(domain => url.includes(domain))) {
        log(`블랙리스트에 있는 도메인: ${url}`, 'warning');
        return;
    }

    // 언어 필터링 검사
    if (settings.language.length > 0) {
        const pageLanguage = await getPageLanguage(url);
        if (!settings.language.includes(pageLanguage)) {
            log(`선택된 언어에 맞지 않는 페이지: ${url} (${pageLanguage})`, 'warning');
            return;
        }
    }

    // 키워드 포함/제외 검사
    if (settings.includeKeywords.length > 0 && !settings.includeKeywords.some(kw => url.includes(kw))) {
        log(`포함 키워드가 없는 URL: ${url}`, 'warning');
        return;
    }
    if (settings.excludeKeywords.some(kw => url.includes(kw))) {
        log(`제외 키워드가 포함된 URL: ${url}`, 'warning');
        return;
    }

    // 정규 표현식 필터링
    if (settings.regexInclude.length > 0) {
        const matchesInclude = settings.regexInclude.some(pattern => new RegExp(pattern).test(url));
        if (!matchesInclude) {
            log(`정규 표현식 포함 조건에 맞지 않는 URL: ${url}`, 'warning');
            return;
        }
    }
    if (settings.regexExclude.length > 0) {
        const matchesExclude = settings.regexExclude.some(pattern => new RegExp(pattern).test(url));
        if (matchesExclude) {
            log(`정규 표현식 제외 조건에 맞는 URL: ${url}`, 'warning');
            return;
        }
    }

    // 데이터 수집
    try {
        const response = await fetchWithRetry(url, settings.retryAttempts, settings.retryDelay);
        const contentType = getContentType(response);
        if (!isContentTypeAllowed(contentType)) {
            log(`허용되지 않은 콘텐츠 유형(${contentType})의 URL: ${url}`, 'warning');
            return;
        }

        const pageHtml = response.responseText;
        const parser = new DOMParser();
        const pageDoc = parser.parseFromString(pageHtml, 'text/html');
        const textContent = pageDoc.body ? pageDoc.body.innerText : '';

        // CAPTCHA 감지 및 처리
        if (settings.enableCaptchas && detectCaptcha(pageDoc)) {
            await handleCaptcha(url);
            return;
        }

        // 단어 수 검사
        const wordCount = textContent.split(/\s+/).length;
        if (wordCount < settings.minWordCount) {
            log(`단어 수가 최소 기준 (${settings.minWordCount})에 미치지 못하는 URL: ${url} (${wordCount} 단어)`, 'warning');
            return;
        }
        if (wordCount > settings.maxWordCount) {
            log(`단어 수가 최대 기준 (${settings.maxWordCount})을 초과하는 URL: ${url} (${wordCount} 단어)`, 'warning');
            return;
        }

        // 콘텐츠 품질 평가
        const qualityScore = evaluateContentQuality(textContent);

        // 언어 추출
        const language = pageDoc.documentElement.lang || 'unknown';

        // 키워드 분류
        const category = classifyKeywords(textContent);

        // 메타 태그 및 헤딩 추출
        const metaTags = extractMetaTags(pageDoc);
        const headings = extractHeadings(pageDoc);

        // 조건에 맞는 경우에만 데이터 추가
        if (category !== '기타' || qualityScore > 100) { // 예시 조건
            const dataEntry = {};
            if (settings.dataFields.name) dataEntry.name = extractName(pageDoc) || 'No Title';
            if (settings.dataFields.url) dataEntry.url = url;
            if (settings.dataFields.description) dataEntry.description = extractDescription(pageDoc) || 'No Description';
            if (settings.dataFields.html) dataEntry.html = pageHtml;
            if (settings.dataFields.category) dataEntry.category = category;
            if (settings.dataFields.quality) dataEntry.quality = qualityScore;
            if (settings.dataFields.language) dataEntry.language = language;
            if (settings.dataFields.wordCount) dataEntry.wordCount = wordCount;
            if (settings.dataFields.metaTags) dataEntry.metaTags = metaTags;
            if (settings.dataFields.headings) dataEntry.headings = headings;

            data.push(dataEntry);
            GM_setValue('data', data);
            updateDataUI();
            log(`데이터 수집 완료: ${url}`, 'success');

            // 데이터 시각화 업데이트
            if (settings.dataVisualization) {
                renderChart();
            }
        } else {
            log(`조건에 맞지 않아 데이터에 추가되지 않은 URL: ${url}`, 'info');
        }

        // 링크 추적을 위한 추가 로직 (심층 크롤링)
        if (depthLevel < settings.maxDepth) {
            const additionalLinks = extractLinks(pageDoc);
            for (let additionalLink of additionalLinks) {
                if (!visitedUrls.includes(additionalLink) && isValidURL(additionalLink)) {
                    requestQueue.push({ url: additionalLink, depth: depthLevel + 1 });
                }
            }
        }

    } catch (error) {
        log(`데이터 수집 실패: ${url} - ${error.message}`, 'error');
        // 실패한 경우 data에 추가하지 않음
    }

    visitedUrls.push(url);
    GM_setValue('visitedUrls', visitedUrls);
}

// XML 이스케이프 함수
function escapeXML(str) {
    if (!str) return '';
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
}

// CSV 이스케이프 함수
function escapeCSV(str) {
    if (!str) return '';
    return str.replace(/"/g, '""');
}

// XML 이스케이프 함수
function escapeXML(str) {
    if (!str) return '';
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
}

// robots-parser 클래스 이미 정의됨

// 딜레이 함수
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 설정 초기화 함수 이미 구현됨

// 모달 창 기능 이미 구현됨

// 시작 및 중지 버튼 기능 이미 구현됨

// 기타 함수 이미 포함됨

function init() {
    createUI();
    if (isCrawling) {
        log('크롤링이 이전에 중단된 상태에서 다시 시작됩니다.', 'info');
        setButtonStates();
        crawlPage(currentDepth); // 페이지 로드 후 크롤링 재개
    } else {
        log('크롤러가 초기화되었습니다.', 'info');
    }
}

// 페이지 로드 시 초기화
window.addEventListener('load', init);

// 추가적인 기능: HTML 내용 수집 및 텍스트 추출, 데이터 테이블에 표시, 모달 창으로 HTML 보기
// 이미 기존 스크립트에서 구현됨
})();                                                                                                                          크롤링 하고 나서 딴 거를 크롤링할떄 초기화가 안됨... 그러니깐 크롤링 하고 나서 초기화 하는 기능 추가 전체 코드에 ㅇㅇ 일단 최대한 아무말없이 써봐. 그냥 코드만 써. ㅇㅇ 그리고 니가 써도 되는 용량이 최대가 될때 파트 1 으로 끝어 알겠지? 그리고 코드  최적화  좀 해줘