// ==UserScript==
// @name         종합 웹 크롤러 7.1
// @namespace    http://tampermonkey.net/
// @version      7.1
// @description  고급 기능을 갖춘 웹 크롤러로 검색 결과를 실시간으로 수집하고 표시
// @author       YourName
// @match        *://www.google.com/search*
// @match        *://www.bing.com/search*
// @match        *://search.daum.net/search*
// @match        *://search.naver.com/search*
// @match        *://search.yahoo.com/search*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require      https://cdn.jsdelivr.net/npm/chart.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css
// @require      https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// ==/UserScript==

(function() {
    'use strict';

    // CSS 스타일 추가 및 UI 개선
    GM_addStyle(`
        /* 기본 컨테이너 스타일 */
        #crawler-container {
            position: fixed;
            top: 10px;
            right: 10px;
            background-color: #ffffff;
            border: 2px solid #4CAF50;
            padding: 20px;
            z-index: 10000;
            width: 95%;
            max-width: 960px;
            max-height: 90vh;
            overflow: hidden;
            box-shadow: 0 4px 30px rgba(0,0,0,0.3);
            border-radius: 10px;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            font-size: 14px;
            resize: both;
            transition: all 0.3s ease;
        }
        /* 다크 모드 */
        #crawler-container.dark-mode {
            background-color: #2e2e2e;
            color: #ffffff;
            border-color: #555555;
        }
        #crawler-container.dark-mode input, 
        #crawler-container.dark-mode select, 
        #crawler-container.dark-mode textarea {
            background-color: #444444;
            color: #ffffff;
            border-color: #777777;
        }
        /* 헤더 스타일 */
        #crawler-container header {
            cursor: move;
            background-color: #4CAF50;
            color: white;
            padding: 10px;
            border-radius: 8px 8px 0 0;
            text-align: center;
            font-size: 20px;
            font-weight: bold;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        /* 닫기 버튼 스타일 */
        #crawler-container header .close-btn {
            cursor: pointer;
            font-size: 24px;
            color: #ff4d4d;
        }
        /* 테마 전환 버튼 */
        #crawler-container header .theme-toggle {
            cursor: pointer;
            font-size: 18px;
            color: #ffffff;
            margin-left: 10px;
        }
        /* 탭 스타일 */
        #crawler-container .tablinks {
            background-color: inherit;
            border: none;
            outline: none;
            cursor: pointer;
            padding: 10px 20px;
            transition: 0.3s;
            font-size: 16px;
        }
        #crawler-container .tablinks.active {
            background-color: #ddd;
            border-radius: 5px 5px 0 0;
        }
        /* 탭 컨텐츠 스타일 */
        #crawler-container .tabcontent {
            display: none;
            padding: 10px 0;
            overflow: auto;
        }
        #crawler-container .tabcontent.active {
            display: block;
        }
        /* 버튼 스타일 */
        #crawler-container button {
            padding: 8px 12px;
            margin: 5px 3px;
            border: none;
            border-radius: 4px;
            background-color: #4CAF50;
            color: white;
            cursor: pointer;
            transition: background-color 0.3s;
            font-size: 14px;
        }
        #crawler-container button:hover {
            background-color: #45a049;
        }
        /* 인풋 및 셀렉트 스타일 */
        #crawler-container input[type="text"], 
        #crawler-container textarea, 
        #crawler-container input[type="number"], 
        #crawler-container select {
            width: calc(100% - 16px);
            padding: 8px;
            margin: 5px 0 10px 0;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
            background-color: #f9f9f9;
            color: #000000;
        }
        #crawler-container.dark-mode input[type="text"],
        #crawler-container.dark-mode textarea,
        #crawler-container.dark-mode input[type="number"],
        #crawler-container.dark-mode select {
            background-color: #555555;
            color: #ffffff;
            border-color: #777777;
        }
        /* 테이블 스타일 */
        #crawler-container table {
            border-collapse: collapse;
            width: 100%;
            margin-top: 10px;
        }
        #crawler-container th, #crawler-container td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
            vertical-align: top;
        }
        #crawler-container th {
            background-color: #4CAF50;
            color: white;
        }
        #crawler-container.dark-mode th {
            background-color: #333333;
            color: #ffffff;
        }
        /* 로그 창 스타일 */
        #crawler-container .log {
            max-height: 250px;
            overflow-y: auto;
            background-color: #eef;
            padding: 10px;
            margin-top: 15px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 12px;
            white-space: pre-wrap;
        }
        #crawler-container.dark-mode .log {
            background-color: #444444;
            color: #ffffff;
            border-color: #666666;
        }
        /* 컨트롤 및 옵션 스타일 */
        #crawler-container .controls, 
        #crawler-container .options, 
        #crawler-container .advanced-options, 
        #crawler-container .notification-area {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 10px;
        }
        #crawler-container .options label, 
        #crawler-container .advanced-options label {
            margin-right: 15px;
            display: flex;
            align-items: center;
        }
        #crawler-container .options input[type="checkbox"], 
        #crawler-container .advanced-options input[type="checkbox"] {
            margin-right: 5px;
        }
        #crawler-container .filters, 
        #crawler-container .exclude-domains {
            margin-top: 10px;
            width: 100%;
        }
        #crawler-container .filters label, 
        #crawler-container .exclude-domains label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        #crawler-container progress {
            width: 80%;
            height: 20px;
            margin-right: 10px;
        }
        #crawler-container .status {
            margin-top: 10px;
            font-weight: bold;
        }
        /* 버튼 별 특수 스타일 */
        #crawler-container .export-btn {
            background-color: #2196F3;
        }
        #crawler-container .export-btn:hover {
            background-color: #0b7dda;
        }
        #crawler-container .pause-btn {
            background-color: #FF9800;
        }
        #crawler-container .pause-btn:hover {
            background-color: #e68a00;
        }
        /* 차트 컨테이너 스타일 */
        #crawler-container .chart-container {
            position: relative;
            height: 250px;
            width: 100%;
            margin-top: 20px;
        }
        /* 모달 창 스타일 */
        .crawler-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); 
        }
        .crawler-modal-content {
            background-color: #fefefe;
            margin: 5% auto; 
            padding: 20px;
            border: 1px solid #888;
            width: 80%; 
            max-width: 800px;
            border-radius: 10px;
            position: relative;
        }
        .crawler-modal-content pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            max-height: 70vh;
            overflow-y: auto;
        }
        .crawler-modal-close {
            color: #aaa;
            position: absolute;
            top: 10px;
            right: 20px;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
        }
        .crawler-modal-close:hover,
        .crawler-modal-close:focus {
            color: black;
            text-decoration: none;
        }
        /* 다크 모드 모달 창 */
        #crawler-container.dark-mode .crawler-modal-content {
            background-color: #333333;
            color: #ffffff;
            border-color: #555555;
        }
        /* HTML 내용 강조 */
        .hljs {
            background: none;
        }
    `);

    // UI 생성
    const container = document.createElement('div');
    container.id = 'crawler-container';
    container.innerHTML = `
        <header>
            <div style="display: flex; align-items: center;">
                <span class="title">🔥 웹 크롤러 7.1</span>
                <span class="theme-toggle" title="테마 전환">🌓</span>
            </div>
            <span class="close-btn">&times;</span>
        </header>
        <div class="tablinks">
            <button class="tablink active" data-tab="settings">설정</button>
            <button class="tablink" data-tab="results-section">결과</button>
            <button class="tablink" data-tab="logs">로그</button>
        </div>
        <div id="settings" class="tabcontent active">
            <div class="notification-area">
                <button id="startBtn">시작</button>
                <button id="pauseBtn" class="pause-btn">일시정지</button>
                <button id="stopBtn">중지</button>
                <button id="resetBtn">초기화</button>
                <button id="exportBtn" class="export-btn">결과 내보내기</button>
            </div>
            <div class="options">
                <label><input type="checkbox" id="toggleHTML" checked> HTML 가져오기</label>
                <label><input type="checkbox" id="toggleImages" checked> 이미지 가져오기</label>
                <label><input type="checkbox" id="toggleAds" checked> 광고 제외</label>
            </div>
            <div class="filters">
                <label for="filterInput">필터 단어 (정규식, 쉼표로 구분):</label>
                <input type="text" id="filterInput" placeholder="예: 기부, 광고, /promo/i">
            </div>
            <div class="exclude-domains">
                <label for="excludeDomainsInput">제외할 도메인 (쉼표로 구분):</label>
                <input type="text" id="excludeDomainsInput" placeholder="예: facebook.com, youtube.com">
            </div>
            <div class="advanced-options">
                <label for="duplicateThreshold">중복 허용 횟수:</label>
                <input type="number" id="duplicateThreshold" value="2" min="1" style="width: 60px;">
            </div>
            <div class="controls">
                <label for="retryAttempts">재시도 횟수:</label>
                <input type="number" id="retryAttempts" value="3" min="1" style="width: 60px;">
                <label for="retryInterval">재시도 간격 (ms):</label>
                <input type="number" id="retryInterval" value="500" min="100" style="width: 80px;">
            </div>
            <div style="margin-top:10px;">
                <label for="progressBar">진행률:</label>
                <progress id="progressBar" value="0" max="100"></progress>
                <span id="progressText">0%</span>
            </div>
            <div class="status">
                현재 페이지: <span id="currentPage">0</span> /
                총 페이지: <span id="totalPages">0</span>
                <br>
                방문된 사이트: <span id="visitedCount">0</span>
                <br>
                경과 시간: <span id="elapsedTime">00:00:00</span>
                <br>
                예상 남은 시간: <span id="remainingTime">00:00:00</span>
            </div>
            <div class="chart-container">
                <canvas id="crawlChart"></canvas>
            </div>
        </div>
        <div id="results-section" class="tabcontent">
            <div id="results" style="margin-top:10px; max-height: 400px; overflow: auto;">
                <table id="resultsTable">
                    <thead>
                        <tr>
                            <th>사이트 이름</th>
                            <th>주소</th>
                            <th>설명</th>
                            <th>이미지</th>
                            <th>HTML 링크</th>
                            <th>WHOIS 정보</th>
                            <th>페이지 로딩 시간 (ms)</th>
                            <th>HTML 보기</th>
                        </tr>
                    </thead>
                    <tbody id="resultsBody">
                    </tbody>
                </table>
            </div>
            <div class="keyword-analysis" style="margin-top:20px;">
                <h3>키워드 빈도 분석</h3>
                <canvas id="keywordChart"></canvas>
            </div>
        </div>
        <div id="logs" class="tabcontent">
            <div class="log" id="log">
                <strong>로그:</strong>
            </div>
        </div>

        <!-- HTML 보기 모달 창 추가 -->
        <div id="htmlModal" class="crawler-modal">
            <div class="crawler-modal-content">
                <span class="crawler-modal-close">&times;</span>
                <h2>사이트 HTML</h2>
                <pre><code id="htmlContent" class="html"></code></pre>
            </div>
        </div>
    `;
    document.body.appendChild(container);

    // 드래그 가능하게 설정
    (function makeDraggable(elmnt) {
        const header = elmnt.querySelector('header');
        const closeBtn = elmnt.querySelector('.close-btn');
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        header.onmousedown = dragMouseDown;

        closeBtn.onclick = () => {
            elmnt.style.display = 'none';
        };

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
            elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    })(container);

    // 탭 전환 기능 추가
    (function initializeTabs() {
        const tablinks = container.querySelectorAll('.tablink');
        const tabs = container.querySelectorAll('.tabcontent');
        const tabbuttons = container.querySelectorAll('.tablinks button');
        tabbuttons.forEach(tab => {
            tab.addEventListener('click', () => {
                const tabName = tab.getAttribute('data-tab');
                // 모든 탭 내용 숨기기
                tabs.forEach(tc => tc.classList.remove('active'));
                // 모든 탭 버튼 비활성화
                tabbuttons.forEach(t => t.classList.remove('active'));
                // 선택된 탭 내용 표시
                container.querySelector(`#${tabName}`).classList.add('active');
                // 선택된 탭 버튼 활성화
                tab.classList.add('active');
            });
        });
    })();

    // 다크 모드 토글 기능
    const themeToggleBtn = container.querySelector('.theme-toggle');
    themeToggleBtn.addEventListener('click', () => {
        container.classList.toggle('dark-mode');
        // 설정 저장
        GM_setValue('darkMode', container.classList.contains('dark-mode'));
    });

    // 저장된 테마 불러오기
    const savedTheme = GM_getValue('darkMode', false);
    if (savedTheme) {
        container.classList.add('dark-mode');
    }

    // 모달 창 요소 가져오기
    const modal = document.getElementById("htmlModal");
    const modalClose = container.querySelector(".crawler-modal-close");
    const htmlContent = document.getElementById("htmlContent");

    // 모달 닫기 이벤트
    modalClose.onclick = function() {
        modal.style.display = "none";
    };
    // 모달 외부 클릭 시 닫기
    window.onclick = function(event) {
        if (event.target == modal) {
            modal.style.display = "none";
        }
    };

    // 변수 초기화
    let isRunning = false;
    let isPaused = false;
    let currentPage = 1;
    let totalPages = 0;
    let results = [];
    let searchQuery = '';
    let searchEngine = '';
    let visitedLinks = new Map(); // 도메인별 방문 횟수 추적
    let logMessages = [];
    let filterPatterns = [];
    let excludeDomains = [];
    let duplicateThreshold = 2; // 중복 허용 횟수
    let retryAttempts = 3; // 재시도 횟수
    let retryInterval = 500; // 재시도 간격 (ms)
    let startTime = null;
    let elapsedInterval = null;
    let crawlChart = null;
    let keywordChart = null;
    let crawlData = {
        labels: [],  // 도메인 이름
        datasets: [{
            label: '방문 횟수',
            data: [],
            backgroundColor: 'rgba(75, 192, 192, 0.6)',
            borderColor: 'rgba(75, 192, 192, 1)',
            borderWidth: 1
        }]
    };
    let keywordData = {
        labels: [],
        datasets: [{
            label: '키워드 빈도',
            data: [],
            backgroundColor: 'rgba(153, 102, 255, 0.6)',
            borderColor: 'rgba(153, 102, 255, 1)',
            borderWidth: 1
        }]
    };
    let keywordFrequency = {}; // 키워드 빈도 저장

    // 버튼 및 요소 가져오기
    const startBtn = document.getElementById('startBtn');
    const pauseBtn = document.getElementById('pauseBtn');
    const stopBtn = document.getElementById('stopBtn');
    const resetBtn = document.getElementById('resetBtn');
    const exportBtn = document.getElementById('exportBtn');
    const progressBar = document.getElementById('progressBar');
    const progressText = document.getElementById('progressText');
    const resultsBody = document.getElementById('resultsBody');
    const logContainer = document.getElementById('log');
    const currentPageElem = document.getElementById('currentPage');
    const totalPagesElem = document.getElementById('totalPages');
    const toggleHTML = document.getElementById('toggleHTML');
    const toggleImages = document.getElementById('toggleImages');
    const toggleAds = document.getElementById('toggleAds');
    const filterInput = document.getElementById('filterInput');
    const excludeDomainsInput = document.getElementById('excludeDomainsInput');
    const duplicateThresholdInput = document.getElementById('duplicateThreshold');
    const retryAttemptsInput = document.getElementById('retryAttempts');
    const retryIntervalInput = document.getElementById('retryInterval');
    const visitedCountElem = document.getElementById('visitedCount');
    const elapsedTimeElem = document.getElementById('elapsedTime');
    const remainingTimeElem = document.getElementById('remainingTime');
    const crawlChartCtx = document.getElementById('crawlChart').getContext('2d');
    const keywordChartCtx = document.getElementById('keywordChart').getContext('2d');

    // 이벤트 리스너 등록
    startBtn.addEventListener('click', startCrawling);
    pauseBtn.addEventListener('click', togglePause);
    stopBtn.addEventListener('click', stopCrawling);
    resetBtn.addEventListener('click', resetCrawling);
    exportBtn.addEventListener('click', exportResults);
    filterInput.addEventListener('input', updateFilterPatterns);
    excludeDomainsInput.addEventListener('input', updateExcludeDomains);
    duplicateThresholdInput.addEventListener('change', updateDuplicateThreshold);
    retryAttemptsInput.addEventListener('change', updateRetryAttempts);
    retryIntervalInput.addEventListener('change', updateRetryInterval);

    // 페이지 새로 고침 시 크롤링 초기화
    window.addEventListener('beforeunload', () => {
        resetCrawling();
    });

    // 초기화 로드
    loadLogs();
    loadCrawlData();
    loadKeywordData();

    // 차트 초기화
    function initializeChart() {
        crawlChart = new Chart(crawlChartCtx, {
            type: 'bar',
            data: crawlData,
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: { 
                        beginAtZero: true,
                        title: {
                            display: true,
                            text: '방문 횟수'
                        }
                    },
                    x: {
                        title: {
                            display: true,
                            text: '도메인'
                        }
                    }
                },
                plugins: {
                    legend: {
                        display: false
                    },
                    tooltip: {
                        enabled: true
                    }
                }
            }
        });

        keywordChart = new Chart(keywordChartCtx, {
            type: 'bar',
            data: keywordData,
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: { 
                        beginAtZero: true,
                        title: {
                            display: true,
                            text: '빈도'
                        }
                    },
                    x: {
                        title: {
                            display: true,
                            text: '키워드'
                        }
                    }
                },
                plugins: {
                    legend: {
                        display: false
                    },
                    tooltip: {
                        enabled: true
                    }
                }
            }
        });
    }
    initializeChart();

    // 로그 함수 개선: 로그 타입별로 색상 표시
    function log(message, type = 'info') {
        const time = new Date().toLocaleTimeString();
        let coloredMessage = message;
        switch(type) {
            case 'warning':
                coloredMessage = `<span style="color: orange;">⚠️ ${message}</span>`;
                break;
            case 'error':
                coloredMessage = `<span style="color: red;">❌ ${message}</span>`;
                break;
            case 'success':
                coloredMessage = `<span style="color: green;">✅ ${message}</span>`;
                break;
            default:
                coloredMessage = `ℹ️ ${message}`;
        }
        const entry = `[${time}] ${coloredMessage}`;
        logMessages.push(entry);
        logContainer.innerHTML += `${entry}<br>`;
        logContainer.scrollTop = logContainer.scrollHeight;
        // 저장
        GM_setValue('crawlerLogs', JSON.stringify(logMessages));
    }

    function loadLogs() {
        const storedLogs = GM_getValue('crawlerLogs', []);
        if (storedLogs.length > 0) {
            logMessages = JSON.parse(storedLogs);
            logContainer.innerHTML = `<strong>로그:</strong><br>` + logMessages.join('<br>');
            logContainer.scrollTop = logContainer.scrollHeight;
        }
    }

    function updateFilterPatterns() {
        const input = filterInput.value.trim();
        filterPatterns = input ? input.split(',').map(word => {
            word = word.trim();
            if (word.startsWith('/') && word.endsWith('/')) {
                try {
                    return new RegExp(word.slice(1, -1), 'i');
                } catch (e) {
                    log(`⚠️ 유효하지 않은 정규식: ${word}`, 'warning');
                    return null;
                }
            } else {
                return new RegExp(word, 'i');
            }
        }).filter(regex => regex !== null) : [];
        log(`🛠 필터 패턴 업데이트: [${filterPatterns.map(r => r.toString()).join(', ')}]`);
    }

    function updateExcludeDomains() {
        const input = excludeDomainsInput.value.trim();
        excludeDomains = input ? input.split(',').map(domain => domain.trim().toLowerCase()) : [];
        log(`🚫 제외 도메인 업데이트: [${excludeDomains.join(', ')}]`);
    }

    function updateDuplicateThreshold() {
        const value = parseInt(duplicateThresholdInput.value);
        duplicateThreshold = isNaN(value) || value < 1 ? 2 : value;
        log(`🔄 중복 허용 횟수 설정: ${duplicateThreshold}`);
    }

    function updateRetryAttempts() {
        const value = parseInt(retryAttemptsInput.value);
        retryAttempts = isNaN(value) || value < 1 ? 3 : value;
        log(`🔄 재시도 횟수 설정: ${retryAttempts}`);
    }

    function updateRetryInterval() {
        const value = parseInt(retryIntervalInput.value);
        retryInterval = isNaN(value) || value < 100 ? 500 : value;
        log(`🔄 재시도 간격 설정: ${retryInterval}ms`);
    }

    function getSearchQueryAndEngine() {
        const url = window.location.href;
        if (url.includes('google.com/search')) {
            const params = new URLSearchParams(window.location.search);
            searchQuery = params.get('q') || '';
            searchEngine = 'google';
        } else if (url.includes('bing.com/search')) {
            const params = new URLSearchParams(window.location.search);
            searchQuery = params.get('q') || '';
            searchEngine = 'bing';
        } else if (url.includes('daum.net/search')) {
            const params = new URLSearchParams(window.location.search);
            searchQuery = params.get('q') || '';
            searchEngine = 'daum';
        } else if (url.includes('search.naver.com/search')) {
            const params = new URLSearchParams(window.location.search);
            searchQuery = params.get('query') || '';
            searchEngine = 'naver';
        } else if (url.includes('search.yahoo.com/search')) {
            const params = new URLSearchParams(window.location.search);
            searchQuery = params.get('p') || '';
            searchEngine = 'yahoo';
        } else {
            searchQuery = '';
            searchEngine = '';
        }
        log(`🔍 검색 엔진: ${searchEngine}, 검색어: "${searchQuery}"`);
    }

    async function startCrawling() {
        if (isRunning) {
            alert('이미 크롤링이 진행 중입니다.');
            return;
        }
        getSearchQueryAndEngine();
        if (!searchQuery) {
            alert('현재 페이지에서 검색어를 찾을 수 없습니다.');
            log('⚠️ 검색어 미입력 또는 인식 실패.', 'warning');
            return;
        }
        isRunning = true;
        isPaused = false;
        currentPage = 1;
        totalPages = 0;
        results = [];
        keywordFrequency = {};
        visitedLinks.clear();
        resultsBody.innerHTML = '';
        visitedCountElem.innerText = '0';
        elapsedTimeElem.innerText = '00:00:00';
        remainingTimeElem.innerText = '00:00:00';
        logMessages = [];
        GM_setValue('crawlerLogs', JSON.stringify(logMessages));
        log('✅ 크롤링을 시작합니다.', 'success');
        updateDuplicateThreshold();
        updateRetryAttempts();
        updateRetryInterval();
        await crawlPage();
        startTime = Date.now();
        elapsedInterval = setInterval(updateElapsedTime, 1000);
    }

    function togglePause() {
        if (!isRunning) {
            alert('일시정지/재개할 크롤링이 없습니다.');
            return;
        }
        isPaused = !isPaused;
        if (isPaused) {
            log('⏸️ 크롤링을 일시정지하였습니다.', 'warning');
            pauseBtn.innerText = '재개';
        } else {
            log('▶️ 크롤링을 재개하였습니다.', 'success');
            pauseBtn.innerText = '일시정지';
            crawlPage();
        }
    }

    function stopCrawling() {
        if (!isRunning) {
            alert('중지할 크롤링이 없습니다.');
            return;
        }
        isRunning = false;
        isPaused = false;
        pauseBtn.innerText = '일시정지';
        clearInterval(elapsedInterval);
        updateProgress();
        log('🚫 크롤링을 중지하였습니다.', 'error');
        GM_notification({
            text: '크롤링이 중지되었습니다.',
            title: '웹 크롤러 7.1',
            timeout: 3000
        });
    }

    async function crawlPage() {
        if (!isRunning || isPaused) {
            return;
        }

        // 검색 엔진에 따른 URL 구성
        let url = '';
        if (searchEngine === 'google') {
            url = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}&start=${(currentPage-1)*10}`;
        } else if (searchEngine === 'bing') {
            url = `https://www.bing.com/search?q=${encodeURIComponent(searchQuery)}&first=${(currentPage-1)*10 +1}`;
        } else if (searchEngine === 'daum') {
            url = `https://search.daum.net/search?q=${encodeURIComponent(searchQuery)}&page=${currentPage}`;
        } else if (searchEngine === 'naver') {
            url = `https://search.naver.com/search.naver?query=${encodeURIComponent(searchQuery)}&start=${(currentPage-1)*10 +1}`;
        } else if (searchEngine === 'yahoo') {
            url = `https://search.yahoo.com/search?p=${encodeURIComponent(searchQuery)}&b=${(currentPage-1)*10 +1}`;
        } else {
            alert('지원하지 않는 검색 엔진입니다.');
            log('❌ 지원하지 않는 검색 엔진.', 'error');
            isRunning = false;
            return;
        }

        log(`📄 페이지 ${currentPage} 크롤링 중: ${url}`);

        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            headers: {
                "User-Agent": getRandomUserAgent(),
                "Accept": "text/html"
            },
            onload: function(response) {
                if (response.status !== 200) {
                    log(`❌ 페이지 ${currentPage} 로드 실패: 상태 ${response.status}`, 'error');
                    handleCrawlError(currentPage, retryAttempts);
                    return;
                }

                const parser = new DOMParser();
                const doc = parser.parseFromString(response.responseText, 'text/html');
                let items = [];
                if (searchEngine === 'google') {
                    items = doc.querySelectorAll('div.g');
                    if (currentPage === 1) {
                        const pager = doc.querySelectorAll('a[href*="start="]');
                        totalPages = pager.length || 1;
                        totalPagesElem.innerText = totalPages;
                    }
                } else if (searchEngine === 'bing') {
                    items = doc.querySelectorAll('li.b_algo');
                    if (currentPage === 1) {
                        const pager = doc.querySelectorAll('a.sb_pagS');
                        totalPages = pager.length || 1;
                        totalPagesElem.innerText = totalPages;
                    }
                } else if (searchEngine === 'daum') {
                    items = doc.querySelectorAll('div.wrap_cont');
                    if (currentPage === 1) {
                        const pager = doc.querySelectorAll('a.pagination__link');
                        totalPages = pager.length || 1;
                        totalPagesElem.innerText = totalPages;
                    }
                } else if (searchEngine === 'naver') {
                    items = doc.querySelectorAll('div.basic1');
                    if (currentPage === 1) {
                        const pager = doc.querySelectorAll('a.nav');
                        totalPages = pager.length || 1;
                        totalPagesElem.innerText = totalPages;
                    }
                } else if (searchEngine === 'yahoo') {
                    items = doc.querySelectorAll('div.compTitle');
                    if (currentPage === 1) {
                        const pager = doc.querySelectorAll('a.fz-l.tco-pagination__anchor');
                        totalPages = pager.length || 1;
                        totalPagesElem.innerText = totalPages;
                    }
                }

                log(`📊 페이지 ${currentPage}에서 ${items.length}개 항목 발견.`);

                // 항목을 비동기적으로 처리하여 속도 향상
                const itemPromises = Array.from(items).map(async (item) => {
                    let title = 'N/A';
                    let link = 'N/A';
                    let description = 'N/A';
                    let image = 'N/A';
                    let htmlLink = 'N/A';
                    let whoisInfo = 'N/A';
                    let pageLoadTime = 'N/A';

                    if (searchEngine === 'google') {
                        const titleElem = item.querySelector('h3');
                        const linkElem = item.querySelector('a');
                        const descElem = item.querySelector('.IsZvec') || item.querySelector('.VwiC3b');
                        if (titleElem) title = titleElem.innerText;
                        if (linkElem) link = linkElem.href;
                        if (descElem) description = descElem.innerText;

                        // 이미지 가져오기 (가능한 경우)
                        const imgElem = item.querySelector('img');
                        if (imgElem && imgElem.src) {
                            image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 100px; max-height: 100px;">`;
                        }

                        // HTML 보기 버튼
                        if (toggleHTML.checked && link !== 'N/A') {
                            htmlLink = `<button class="view-html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                        }
                    } else if (searchEngine === 'bing') {
                        const titleElem = item.querySelector('h2 a');
                        const descElem = item.querySelector('p');
                        if (titleElem) {
                            title = titleElem.innerText;
                            link = titleElem.href;
                        }
                        if (descElem) description = descElem.innerText;

                        // 이미지 가져오기 (가능한 경우)
                        const imgElem = item.querySelector('img');
                        if (imgElem && imgElem.src) {
                            image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 100px; max-height: 100px;">`;
                        }

                        // HTML 보기 버튼
                        if (toggleHTML.checked && link !== 'N/A') {
                            htmlLink = `<button class="view-html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                        }
                    } else if (searchEngine === 'daum') {
                        const titleElem = item.querySelector('a.link_fax');
                        const descElem = item.querySelector('div.cont_inner');
                        if (titleElem) {
                            title = titleElem.innerText.trim();
                            link = titleElem.href;
                        }
                        if (descElem) description = descElem.innerText.trim();

                        // 이미지 가져오기 (가능한 경우)
                        const imgElem = item.querySelector('img');
                        if (imgElem && imgElem.src) {
                            image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 100px; max-height: 100px;">`;
                        }

                        // HTML 보기 버튼
                        if (toggleHTML.checked && link !== 'N/A') {
                            htmlLink = `<button class="view-html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                        }
                    } else if (searchEngine === 'naver') {
                        const titleElem = item.querySelector('a.tit');
                        const descElem = item.querySelector('div.desc');
                        if (titleElem) {
                            title = titleElem.innerText;
                            link = titleElem.href;
                        }
                        if (descElem) description = descElem.innerText;

                        // 이미지 가져오기 (가능한 경우)
                        const imgElem = item.querySelector('img.thumb');
                        if (imgElem && imgElem.src) {
                            image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 100px; max-height: 100px;">`;
                        }

                        // HTML 보기 버튼
                        if (toggleHTML.checked && link !== 'N/A') {
                            htmlLink = `<button class="view-html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                        }
                    } else if (searchEngine === 'yahoo') {
                        const titleElem = item.querySelector('h3');
                        const linkElem = item.querySelector('a');
                        const descElem = item.querySelector('div.compText');
                        if (titleElem) {
                            title = titleElem.innerText;
                        }
                        if (linkElem) {
                            link = linkElem.href;
                        }
                        if (descElem) {
                            description = descElem.innerText;
                        }

                        // 이미지 가져오기 (가능한 경우)
                        const imgElem = item.querySelector('img');
                        if (imgElem && imgElem.src) {
                            image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 100px; max-height: 100px;">`;
                        }

                        // HTML 보기 버튼
                        if (toggleHTML.checked && link !== 'N/A') {
                            htmlLink = `<button class="view-html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                        }
                    }

                    // 광고 제외 기능 적용
                    if (toggleAds.checked) {
                        const combinedText = `${title} ${description}`.toLowerCase();
                        const isAd = combinedText.includes('advertisement') || combinedText.includes('광고');
                        if (isAd) {
                            log(`🚫 광고 항목 제외: "${title}"`, 'warning');
                            return;
                        }
                    }

                    // 필터 단어 적용
                    if (filterPatterns.length > 0) {
                        const combinedText = `${title} ${description}`.toLowerCase();
                        const isFiltered = filterPatterns.some(regex => regex.test(combinedText));
                        if (isFiltered) {
                            log(`❌ 필터 단어 포함 항목 제외: "${title}"`, 'warning');
                            return;
                        }
                    }

                    // 도메인 추출
                    const domain = extractDomain(link);
                    if (!domain) return;

                    // 제외 도메인 검사
                    if (excludeDomains.includes(domain.toLowerCase())) {
                        log(`🚫 제외 도메인 항목 제외: "${link}"`, 'warning');
                        return;
                    }

                    // 중복 검사 (도메인별 방문 횟수)
                    let visitCount = visitedLinks.get(domain) || 0;
                    if (visitCount >= duplicateThreshold) {
                        log(`🔄 중복 방문 제한: "${domain}"`, 'warning');
                        return;
                    }

                    // 방문 횟수 업데이트
                    visitedLinks.set(domain, visitCount + 1);
                    visitedCountElem.innerText = visitedLinks.size.toString();

                    // 페이지 로딩 시간 측정 (추가 기능)
                    const startLoad = performance.now();
                    try {
                        // 페이지 로딩 시간은 CORS 정책 때문에 정확히 측정하기 어려움
                        await fetch(link, { mode: 'no-cors' });
                        const endLoad = performance.now();
                        pageLoadTime = Math.round(endLoad - startLoad);
                    } catch (err) {
                        pageLoadTime = '계산 불가';
                    }

                    // 키워드 빈도 분석
                    analyzeKeywords(title + ' ' + description);

                    // WHOIS 정보 가져오기 (추가 기능: 외부 API 필요)
                    whoisInfo = 'N/A'; // 기본값
                    if (toggleHTML.checked && link !== 'N/A') {
                        // 외부 API를 통해 WHOIS 정보 가져오기 (예: jsonwhois.com 사용)
                        // 여기서는 예시로 간단히 처리
                        // 실제로는 WHOIS API 키와 요청이 필요
                        whoisInfo = `<a href="https://jsonwhois.com/whois/${encodeURIComponent(domain)}" target="_blank">보기</a>`;
                    }

                    // 이미지 옵션
                    if (!toggleImages.checked) {
                        image = '비활성화';
                    }

                    results.push({ title, link, description, image, htmlLink, whoisInfo, pageLoadTime });

                    // 결과 테이블에 추가
                    const row = document.createElement('tr');
                    row.innerHTML = `
                        <td>${escapeHTML(title)}</td>
                        <td><a href="${escapeHTML(link)}" target="_blank">${escapeHTML(link)}</a></td>
                        <td>${escapeHTML(description)}</td>
                        <td>${image}</td>
                        <td>${htmlLink}</td>
                        <td>${whoisInfo}</td>
                        <td>${pageLoadTime}</td>
                        <td>${toggleHTML.checked ? htmlLink : 'N/A'}</td>
                    `;
                    resultsBody.appendChild(row);
                });

                // 모든 항목 처리 완료 후
                Promise.all(itemPromises).then(() => {
                    // 데이터 시각화 업데이트
                    updateChart();
                    updateKeywordChart();

                    // 진행률 업데이트
                    updateProgress();

                    // HTML 보기 버튼에 이벤트 리스너 추가
                    addHTMLViewEventListeners();

                    // 다음 페이지로 이동
                    currentPage++;
                    currentPageElem.innerText = currentPage;

                    // 크롤링 계속 진행
                    setTimeout(crawlPage, isPaused ? 0 : 0); // 속도 제한 없음
                }).catch(err => {
                    log(`❌ 항목 처리 중 오류 발생: ${err}`, 'error');
                });
            },
            onerror: function() {
                log(`❌ 페이지 ${currentPage} 요청 중 오류 발생.`, 'error');
                handleCrawlError(currentPage, retryAttempts);
            }
        });
    }

    function handleCrawlError(page, retriesLeft) {
        if (retriesLeft > 0 && isRunning) {
            log(`🔄 페이지 ${page} 재시도 (${retriesLeft}회 남음).`, 'warning');
            setTimeout(() => {
                const url = constructURL(page);
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    headers: {
                        "User-Agent": getRandomUserAgent(),
                        "Accept": "text/html"
                    },
                    onload: function(response) {
                        if (response.status === 200) {
                            log(`✅ 페이지 ${page} 재시도 성공.`, 'success');
                            crawlPage();
                        } else {
                            handleCrawlError(page, retriesLeft - 1);
                        }
                    },
                    onerror: function() {
                        handleCrawlError(page, retriesLeft - 1);
                    }
                });
            }, retryInterval);
        } else {
            log(`❌ 페이지 ${page} 로드 실패: 최대 재시도 횟수 초과.`, 'error');
            // 크롤링을 중지하지 않고 다음 페이지로 넘어감
            currentPage++;
            currentPageElem.innerText = currentPage;
            setTimeout(crawlPage, 0); // 속도 제한 없음
        }
    }

    function constructURL(page) {
        if (searchEngine === 'google') {
            return `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}&start=${(page-1)*10}`;
        } else if (searchEngine === 'bing') {
            return `https://www.bing.com/search?q=${encodeURIComponent(searchQuery)}&first=${(page-1)*10 +1}`;
        } else if (searchEngine === 'daum') {
            return `https://search.daum.net/search?q=${encodeURIComponent(searchQuery)}&page=${page}`;
        } else if (searchEngine === 'naver') {
            return `https://search.naver.com/search.naver?query=${encodeURIComponent(searchQuery)}&start=${(page-1)*10 +1}`;
        } else if (searchEngine === 'yahoo') {
            return `https://search.yahoo.com/search?p=${encodeURIComponent(searchQuery)}&b=${(page-1)*10 +1}`;
        } else {
            return '';
        }
    }

    function updateProgress() {
        if (totalPages === 0) {
            progressBar.value = 0;
            progressText.innerText = '0%';
        } else {
            const percent = Math.min(((currentPage - 1) / totalPages) * 100, 100);
            progressBar.value = percent;
            progressText.innerText = `${Math.round(percent)}%`;
            if (currentPage > totalPages) {
                isRunning = false;
                isPaused = false;
                pauseBtn.innerText = '일시정지';
                clearInterval(elapsedInterval);
                log('🎉 크롤링이 완료되었습니다.', 'success');
                updateProgress();
                GM_notification({
                    text: '크롤링이 완료되었습니다.',
                    title: '웹 크롤러 7.1',
                    timeout: 3000
                });
            }
        }
    }

    function resetCrawling() {
        isRunning = false;
        isPaused = false;
        currentPage = 1;
        totalPages = 0;
        results = [];
        keywordFrequency = {};
        visitedLinks.clear();
        resultsBody.innerHTML = '';
        progressBar.value = 0;
        progressText.innerText = '0%';
        currentPageElem.innerText = '0';
        totalPagesElem.innerText = '0';
        visitedCountElem.innerText = '0';
        elapsedTimeElem.innerText = '00:00:00';
        remainingTimeElem.innerText = '00:00:00';
        logMessages = [];
        logContainer.innerHTML = `<strong>로그:</strong><br>`;
        GM_setValue('crawlerLogs', JSON.stringify(logMessages));
        // 차트 초기화
        crawlData.labels = [];
        crawlData.datasets[0].data = [];
        if (crawlChart) {
            crawlChart.update();
        }
        // 키워드 차트 초기화
        keywordData.labels = [];
        keywordData.datasets[0].data = [];
        if (keywordChart) {
            keywordChart.update();
        }
        log('🔄 크롤링을 초기화하였습니다.', 'info');
        clearInterval(elapsedInterval);
    }

    function exportResults() {
        if (results.length === 0) {
            alert('내보낼 결과가 없습니다.');
            return;
        }
        const format = prompt('내보낼 형식을 선택하세요 (xlsx, csv, json, pdf):', 'xlsx');
        if (!format) return;
        if (format.toLowerCase() === 'xlsx') {
            const ws = XLSX.utils.json_to_sheet(results);
            const wb = XLSX.utils.book_new();
            XLSX.utils.book_append_sheet(wb, ws, "결과");
            XLSX.writeFile(wb, "크롤링_결과.xlsx");
            log('💾 결과를 "크롤링_결과.xlsx" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '결과를 "크롤링_결과.xlsx" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'csv') {
            const csv = [
                ["사이트 이름","주소","설명","이미지","HTML 링크","WHOIS 정보","페이지 로딩 시간 (ms)"],
                ...results.map(row => `"${escapeCSV(row.title)}","${escapeCSV(row.link)}","${escapeCSV(row.description)}","${escapeCSV(row.image)}","${escapeCSV(row.htmlLink)}","${escapeCSV(row.whoisInfo)}","${escapeCSV(row.pageLoadTime)}"`)
            ].join('\n');
            const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_결과.csv';
            a.click();
            log('💾 결과를 "크롤링_결과.csv" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '결과를 "크롤링_결과.csv" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'json') {
            const json = JSON.stringify(results, null, 2);
            const blob = new Blob([json], { type: 'application/json;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_결과.json';
            a.click();
            log('💾 결과를 "크롤링_결과.json" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '결과를 "크롤링_결과.json" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'pdf') {
            const { jsPDF } = window.jspdf;
            const doc = new jsPDF();
            let y = 10;
            results.forEach((row, index) => {
                doc.text(`${index + 1}. ${row.title}`, 10, y);
                y += 7;
                doc.text(`주소: ${row.link}`, 10, y);
                y += 7;
                doc.text(`설명: ${row.description}`, 10, y);
                y += 7;
                if (y > 270) {
                    doc.addPage();
                    y = 10;
                }
            });
            doc.save('크롤링_결과.pdf');
            log('💾 결과를 "크롤링_결과.pdf" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '결과를 "크롤링_결과.pdf" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else {
            alert('지원하지 않는 형식입니다.');
        }
    }

    function escapeHTML(str) {
        if (!str) return '';
        return str.replace(/&/g, "&amp;")
                  .replace(/</g, "&lt;")
                  .replace(/>/g, "&gt;")
                  .replace(/"/g, "&quot;")
                  .replace(/'/g, "&#039;");
    }

    function escapeCSV(str) {
        if (!str) return '';
        return str.replace(/"/g, '""');
    }

    function extractDomain(url) {
        try {
            const urlObj = new URL(url);
            return urlObj.hostname.replace('www.', '');
        } catch (e) {
            return null;
        }
    }

    function updateElapsedTime() {
        if (!startTime) return;
        const now = Date.now();
        const elapsed = Math.floor((now - startTime) / 1000);
        elapsedTimeElem.innerText = formatTime(elapsed);
        const estimatedTotal = totalPages * 0.1; // 예측: 각 페이지 100ms = 0.1초
        const remaining = Math.max(estimatedTotal - elapsed, 0);
        remainingTimeElem.innerText = formatTime(remaining);
    }

    function formatTime(seconds) {
        const hrs = String(Math.floor(seconds / 3600)).padStart(2, '0');
        const mins = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
        const secs = String(seconds % 60).padStart(2, '0');
        return `${hrs}:${mins}:${secs}`;
    }

    function updateChart() {
        crawlData.labels = Array.from(visitedLinks.keys());
        crawlData.datasets[0].data = Array.from(visitedLinks.values());

        if (crawlChart) {
            crawlChart.update();
        }
        // 저장
        GM_setValue('crawlerData', JSON.stringify(crawlData));
    }

    function updateKeywordChart() {
        keywordData.labels = Object.keys(keywordFrequency);
        keywordData.datasets[0].data = Object.values(keywordFrequency);

        if (keywordChart) {
            keywordChart.update();
        }
        // 저장
        GM_setValue('keywordData', JSON.stringify(keywordData));
    }

    function loadCrawlData() {
        const storedData = GM_getValue('crawlerData', null);
        if (storedData) {
            const parsedData = JSON.parse(storedData);
            crawlData = parsedData;
            if (crawlChart) {
                crawlChart.data = crawlData;
                crawlChart.update();
            }
        }
    }

    function loadKeywordData() {
        const storedKeywordData = GM_getValue('keywordData', null);
        if (storedKeywordData) {
            const parsedKeywordData = JSON.parse(storedKeywordData);
            keywordData = parsedKeywordData;
            if (keywordChart) {
                keywordChart.data = keywordData;
                keywordChart.update();
            }
        }
    }

    function saveCrawlData() {
        GM_setValue('crawlerData', JSON.stringify(crawlData));
        GM_setValue('keywordData', JSON.stringify(keywordData));
    }

    // 주기적으로 크롤링 데이터를 저장
    setInterval(saveCrawlData, 5000);

    // 각 HTML 보기 버튼에 이벤트 리스너 추가
    function addHTMLViewEventListeners() {
        const buttons = document.querySelectorAll('.view-html-btn');
        buttons.forEach(button => {
            button.addEventListener('click', async () => {
                const url = decodeURIComponent(button.getAttribute('data-url'));
                log(`📄 HTML을 불러오는 중: ${url}`);
                try {
                    const response = await fetch(url);
                    const htmlText = await response.text();
                    htmlContent.textContent = htmlText;
                    hljs.highlightElement(htmlContent); // 구문 강조
                    modal.style.display = "block";
                    log(`✅ HTML 불러오기 완료: ${url}`, 'success');
                } catch (err) {
                    log(`❌ HTML 불러오기 실패: ${url}`, 'error');
                    htmlContent.textContent = 'HTML을 불러오는 데 실패했습니다.';
                    modal.style.display = "block";
                }
            });
        });
    }

    function analyzeKeywords(text) {
        // 간단한 단어 빈도 분석 (영어 및 한국어 지원)
        const words = text.toLowerCase().match(/\b\w+\b/g);
        if (!words) return;
        words.forEach(word => {
            if (word.length < 2) return; // 너무 짧은 단어 제외
            keywordFrequency[word] = (keywordFrequency[word] || 0) + 1;
        });
    }

    // 저장된 상태 복원
    function restoreState() {
        const savedState = GM_getValue('crawlerState', null);
        if (savedState && savedState.isRunning) {
            isRunning = savedState.isRunning;
            isPaused = savedState.isPaused;
            currentPage = savedState.currentPage;
            totalPages = savedState.totalPages;
            results = savedState.results;
            keywordFrequency = savedState.keywordFrequency;
            visitedLinks = new Map(savedState.visitedLinks);
            logMessages = savedState.logMessages;
            filterPatterns = savedState.filterPatterns;
            excludeDomains = savedState.excludeDomains;
            duplicateThreshold = savedState.duplicateThreshold;
            retryAttempts = savedState.retryAttempts;
            retryInterval = savedState.retryInterval;
            startTime = savedState.startTime;
            // 결과 테이블 복원
            resultsBody.innerHTML = '';
            results.forEach(row => {
                const tr = document.createElement('tr');
                tr.innerHTML = `
                    <td>${escapeHTML(row.title)}</td>
                    <td><a href="${escapeHTML(row.link)}" target="_blank">${escapeHTML(row.link)}</a></td>
                    <td>${escapeHTML(row.description)}</td>
                    <td>${row.image}</td>
                    <td>${row.htmlLink}</td>
                    <td>${row.whoisInfo}</td>
                    <td>${row.pageLoadTime}</td>
                    <td>${toggleHTML.checked ? row.htmlLink : 'N/A'}</td>
                `;
                resultsBody.appendChild(tr);
            });
            // 차트 복원
            loadCrawlData();
            loadKeywordData();
            updateChart();
            updateKeywordChart();
            // 상태 업데이트
            visitedCountElem.innerText = visitedLinks.size.toString();
            currentPageElem.innerText = currentPage;
            totalPagesElem.innerText = totalPages;
            // 로그 복원
            loadLogs();
            // 크롤링 재개
            if (isRunning && !isPaused) {
                elapsedInterval = setInterval(updateElapsedTime, 1000);
                crawlPage();
            }
        }
    }

    restoreState();

    // 상태 저장
    window.addEventListener('unload', () => {
        const state = {
            isRunning,
            isPaused,
            currentPage,
            totalPages,
            results,
            keywordFrequency,
            visitedLinks: Array.from(visitedLinks.entries()),
            logMessages,
            filterPatterns,
            excludeDomains,
            duplicateThreshold,
            retryAttempts,
            retryInterval,
            startTime
        };
        GM_setValue('crawlerState', state);
    });

    // 사용자 에이전트 랜덤화
    function getRandomUserAgent() {
        const userAgents = [
            // 다양한 사용자 에이전트 목록
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
            'Mozilla/5.0 (X11; Linux x86_64)',
            'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)',
            'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)',
            'Mozilla/5.0 (Android 10; Mobile; rv:81.0) Gecko/81.0 Firefox/81.0',
            'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
        ];
        return userAgents[Math.floor(Math.random() * userAgents.length)];
    }

    // 로그 내보내기 기능 추가
    function exportLogs() {
        if (logMessages.length === 0) {
            alert('내보낼 로그가 없습니다.');
            return;
        }
        const format = prompt('내보낼 로그 형식을 선택하세요 (txt, csv, json):', 'txt');
        if (!format) return;
        if (format.toLowerCase() === 'txt') {
            const txt = logMessages.join('\n');
            const blob = new Blob([txt], { type: 'text/plain;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_로그.txt';
            a.click();
            log('💾 로그를 "크롤링_로그.txt" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '로그를 "크롤링_로그.txt" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'csv') {
            const csv = ['시간,메시지'].concat(logMessages.map(entry => entry.replace(/<[^>]+>/g, ''))).join('\n');
            const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_로그.csv';
            a.click();
            log('💾 로그를 "크롤링_로그.csv" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '로그를 "크롤링_로그.csv" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'json') {
            const json = JSON.stringify(logMessages, null, 2);
            const blob = new Blob([json], { type: 'application/json;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_로그.json';
            a.click();
            log('💾 로그를 "크롤링_로그.json" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '로그를 "크롤링_로그.json" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else {
            alert('지원하지 않는 형식입니다.');
        }
    }

    // HTML 내용 보기 및 구문 강조
    // @require로 추가한 highlight.js를 활용
    // 주요 기능:

    // - HTML 보기 버튼 클릭 시 해당 사이트의 HTML을 모달 창에 표시
    // - 구문 강조 적용

    // 이미 addHTMLViewEventListeners 함수에서 구현됨

    // 키워드 빈도 분석
    // 결과 탭의 "키워드 빈도 분석" 그래프를 업데이트
    function analyzeKeywords(text) {
        // 간단한 단어 빈도 분석 (영어 및 한국어 지원)
        const words = text.toLowerCase().match(/\b\w+\b/g);
        if (!words) return;
        words.forEach(word => {
            if (word.length < 2) return; // 너무 짧은 단어 제외
            keywordFrequency[word] = (keywordFrequency[word] || 0) + 1;
        });
    }

    // 이벤트 리스너 등록 및 HTML 보기 기능 구현
    function addHTMLViewEventListeners() {
        const buttons = document.querySelectorAll('.view-html-btn');
        buttons.forEach(button => {
            button.addEventListener('click', async () => {
                const url = decodeURIComponent(button.getAttribute('data-url'));
                log(`📄 HTML을 불러오는 중: ${url}`);
                try {
                    const response = await fetch(url);
                    const htmlText = await response.text();
                    htmlContent.textContent = htmlText;
                    hljs.highlightElement(htmlContent); // 구문 강조
                    modal.style.display = "block";
                    log(`✅ HTML 불러오기 완료: ${url}`, 'success');
                } catch (err) {
                    log(`❌ HTML 불러오기 실패: ${url}`, 'error');
                    htmlContent.textContent = 'HTML을 불러오는 데 실패했습니다.';
                    modal.style.display = "block";
                }
            });
        });
    }

    // 결과 내보내기 추가 (PDF 지원)
    // 이미 exportResults 함수에서 구현됨

    // 저장된 상태 복원
    function restoreState() {
        const savedState = GM_getValue('crawlerState', null);
        if (savedState && savedState.isRunning) {
            isRunning = savedState.isRunning;
            isPaused = savedState.isPaused;
            currentPage = savedState.currentPage;
            totalPages = savedState.totalPages;
            results = savedState.results;
            keywordFrequency = savedState.keywordFrequency;
            visitedLinks = new Map(savedState.visitedLinks);
            logMessages = savedState.logMessages;
            filterPatterns = savedState.filterPatterns;
            excludeDomains = savedState.excludeDomains;
            duplicateThreshold = savedState.duplicateThreshold;
            retryAttempts = savedState.retryAttempts;
            retryInterval = savedState.retryInterval;
            startTime = savedState.startTime;
            // 결과 테이블 복원
            resultsBody.innerHTML = '';
            results.forEach(row => {
                const tr = document.createElement('tr');
                tr.innerHTML = `
                    <td>${escapeHTML(row.title)}</td>
                    <td><a href="${escapeHTML(row.link)}" target="_blank">${escapeHTML(row.link)}</a></td>
                    <td>${escapeHTML(row.description)}</td>
                    <td>${row.image}</td>
                    <td>${row.htmlLink}</td>
                    <td>${row.whoisInfo}</td>
                    <td>${row.pageLoadTime}</td>
                    <td>${toggleHTML.checked ? row.htmlLink : 'N/A'}</td>
                `;
                resultsBody.appendChild(tr);
            });
            // 차트 복원
            loadCrawlData();
            loadKeywordData();
            updateChart();
            updateKeywordChart();
            // 상태 업데이트
            visitedCountElem.innerText = visitedLinks.size.toString();
            currentPageElem.innerText = currentPage;
            totalPagesElem.innerText = totalPages;
            // 로그 복원
            loadLogs();
            // 크롤링 재개
            if (isRunning && !isPaused) {
                elapsedInterval = setInterval(updateElapsedTime, 1000);
                crawlPage();
            }
        }
    }

    restoreState();

    // 상태 저장
    window.addEventListener('unload', () => {
        const state = {
            isRunning,
            isPaused,
            currentPage,
            totalPages,
            results,
            keywordFrequency,
            visitedLinks: Array.from(visitedLinks.entries()),
            logMessages,
            filterPatterns,
            excludeDomains,
            duplicateThreshold,
            retryAttempts,
            retryInterval,
            startTime
        };
        GM_setValue('crawlerState', state);
    });

    // 사용자 에이전트 랜덤화
    function getRandomUserAgent() {
        const userAgents = [
            // 다양한 사용자 에이전트 목록
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
            'Mozilla/5.0 (X11; Linux x86_64)',
            'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)',
            'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)',
            'Mozilla/5.0 (Android 10; Mobile; rv:81.0) Gecko/81.0 Firefox/81.0',
            'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
        ];
        return userAgents[Math.floor(Math.random() * userAgents.length)];
    }

    // 로그 내보내기 기능 추가
    // 새로운 로그 내보내기 버튼 추가 및 기능
    (function addLogExportButton() {
        const exportLogBtn = document.createElement('button');
        exportLogBtn.textContent = '로그 내보내기';
        exportLogBtn.style.backgroundColor = '#FF5722';
        exportLogBtn.style.marginLeft = '10px';
        container.querySelector('#settings .notification-area').appendChild(exportLogBtn);

        exportLogBtn.addEventListener('click', exportLogs);
    })();

    function exportLogs() {
        if (logMessages.length === 0) {
            alert('내보낼 로그가 없습니다.');
            return;
        }
        const format = prompt('내보낼 로그 형식을 선택하세요 (txt, csv, json):', 'txt');
        if (!format) return;
        if (format.toLowerCase() === 'txt') {
            const txt = logMessages.join('\n');
            const blob = new Blob([txt], { type: 'text/plain;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_로그.txt';
            a.click();
            log('💾 로그를 "크롤링_로그.txt" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '로그를 "크롤링_로그.txt" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'csv') {
            const csv = ['시간,메시지'].concat(logMessages.map(entry => entry.replace(/<[^>]+>/g, ''))).join('\n');
            const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_로그.csv';
            a.click();
            log('💾 로그를 "크롤링_로그.csv" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '로그를 "크롤링_로그.csv" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else if (format.toLowerCase() === 'json') {
            const json = JSON.stringify(logMessages, null, 2);
            const blob = new Blob([json], { type: 'application/json;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = '크롤링_로그.json';
            a.click();
            log('💾 로그를 "크롤링_로그.json" 파일로 내보냈습니다.', 'success');
            GM_notification({
                text: '로그를 "크롤링_로그.json" 파일로 내보냈습니다.',
                title: '웹 크롤러 7.1',
                timeout: 3000
            });
        } else {
            alert('지원하지 않는 형식입니다.');
        }
    }

    // 키워드 빈도 분석 및 그래프 업데이트
    function analyzeKeywords(text) {
        // 간단한 단어 빈도 분석 (영어 및 한국어 지원)
        const words = text.toLowerCase().match(/\b\w+\b/g);
        if (!words) return;
        words.forEach(word => {
            if (word.length < 2) return; // 너무 짧은 단어 제외
            keywordFrequency[word] = (keywordFrequency[word] || 0) + 1;
        });
    }

    // 완료된 크롤링 후 키워드 빈도 그래프 업데이트
    function updateKeywordChart() {
        keywordData.labels = Object.keys(keywordFrequency);
        keywordData.datasets[0].data = Object.values(keywordFrequency);

        if (keywordChart) {
            keywordChart.data = keywordData;
            keywordChart.update();
        }
        // 저장
        GM_setValue('keywordData', JSON.stringify(keywordData));
    }

})();            쓰잘대기 없는 걸 하지말고 (코드 최적화나 코드 삭제 크롤링 속도 제한)                        기능들만을 대폭 대폭 개선하셈