// ==UserScript==
// @name         Advanced Educational Web Scraper
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  구글, 얀덱스, 빙, 덕덕고 검색 결과를 자동으로 크롤링하고 다양한 기능을 제공하는 교육용 스크립트 (robots.txt 검사 제거)
// @author       Your Name
// @match        https://www.google.com/search*
// @match        https://www.yandex.com/search/*
// @match        https://www.yandex.ru/search/*
// @match        https://www.bing.com/search*
// @match        https://duckduckgo.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      *
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    /*** 1. 설정 및 상태 변수 초기화 ***/
    const DEFAULT_STATE = {
        isCrawling: false,
        currentPage: 1,
        maxPages: 5,
        delay: 3000, // ms
        collectedData: [],
        visitedURLs: [],
        filterKeywords: ['광고', 'promotion', 'sponsored', '광고주'],
        excludedDomains: ['facebook.com', 'twitter.com'],
        whitelistDomains: [],
        retryCount: 0,
        maxRetries: 3,
        targetSearchEngine: detectSearchEngine(),
        log: '',
    };

    const STORAGE_KEY = 'webScraperState';
    let state = GM_getValue(STORAGE_KEY, DEFAULT_STATE);

    /*** 2. 검색 엔진 감지 ***/
    function detectSearchEngine() {
        if (window.location.hostname.includes('google')) {
            return 'google';
        } else if (window.location.hostname.includes('yandex')) {
            return 'yandex';
        } else if (window.location.hostname.includes('bing')) {
            return 'bing';
        } else if (window.location.hostname.includes('duckduckgo')) {
            return 'duckduckgo';
        }
        return 'unknown';
    }

    /*** 3. 사용자 알림 함수 ***/
    function notifyUser(message) {
        if (Notification.permission === "granted") {
            new Notification(message);
        } else if (Notification.permission !== "denied") {
            Notification.requestPermission().then(permission => {
                if (permission === "granted") {
                    new Notification(message);
                }
            });
        }
    }

    /*** 4. UI 요소 생성 및 스타일링 ***/
    function createUI() {
        // 기존 UI 제거 (페이지 이동 시 초기화 방지)
        let existingUI = document.getElementById('advanced-web-scraper-ui');
        if (existingUI) existingUI.remove();

        // UI 컨테이너 생성
        const uiContainer = document.createElement('div');
        uiContainer.id = 'advanced-web-scraper-ui';
        uiContainer.style.position = 'fixed';
        uiContainer.style.top = '10px';
        uiContainer.style.right = '10px';
        uiContainer.style.width = '600px';
        uiContainer.style.height = '80vh'; // 반응형 높이
        uiContainer.style.backgroundColor = '#2c3e50';
        uiContainer.style.border = '1px solid #34495e';
        uiContainer.style.padding = '15px';
        uiContainer.style.overflowY = 'auto';
        uiContainer.style.zIndex = '10000';
        uiContainer.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
        uiContainer.style.borderRadius = '8px';
        uiContainer.style.fontFamily = 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif';
        uiContainer.style.color = '#ecf0f1';

        uiContainer.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
                <h2 style="margin: 0; font-size: 18px;">🕸️ 크롤러 대시보드</h2>
                <button id="closeUI" title="UI 닫기" style="background: transparent; border: none; color: #ecf0f1; font-size: 18px; cursor: pointer;">✖️</button>
            </div>

            <!-- 탭 메뉴 -->
            <div style="margin-bottom: 15px;">
                <div class="tab">
                    <button class="tablinks active" data-tab="logTab" title="로그 탭 (Ctrl+1)">📜 로그</button>
                    <button class="tablinks" data-tab="dataTab" title="데이터 탭 (Ctrl+2)">📊 데이터</button>
                    <button class="tablinks" data-tab="settingsTab" title="설정 탭 (Ctrl+3)">⚙️ 설정</button>
                </div>
            </div>

            <!-- 탭 컨텐츠 -->
            <div class="tabcontent" id="logTab" style="display: block;">
                <div style="margin-bottom: 15px;">
                    <div id="progress" style="background-color: #34495e; border-radius: 4px; overflow: hidden; height: 20px; margin-bottom: 5px;">
                        <div id="progressBar" style="height: 100%; width: 0%; background-color: #1abc9c; transition: width 0.3s;"></div>
                    </div>
                    <div style="font-size: 14px;">진행률: <span id="progressText">0%</span> (<span id="progressCount">0</span>/<span id="progressTotal">${state.maxPages * 10}</span>)</div>
                    <div style="font-size: 14px; margin-top: 5px;"><strong>현재 페이지:</strong> <span id="currentPage">${state.currentPage}</span></div>
                    <div style="font-size: 14px;"><strong>현재 상태:</strong> <span id="currentStatus">대기 중</span></div>
                    <div style="font-size: 14px;"><strong>예상 완료 시간:</strong> <span id="estimatedTime">계산 중...</span></div>
                    <div id="spinner" style="display: none; margin-top: 10px;">
                        <div class="loader"></div>
                    </div>
                </div>
                <div style="display: flex; gap: 10px; margin-bottom: 15px;">
                    <button id="startCrawl" title="크롤링 시작 (Ctrl+P)" style="flex: 1; padding: 10px; background-color: #27ae60; border: none; border-radius: 4px; color: #ecf0f1; cursor: pointer; font-size: 14px;">
                        ▶️ 크롤링 시작
                    </button>
                    <button id="stopCrawl" title="크롤링 중지 (Ctrl+Q)" style="flex: 1; padding: 10px; background-color: #c0392b; border: none; border-radius: 4px; color: #ecf0f1; cursor: pointer; font-size: 14px;">
                        ⏹️ 크롤링 중지
                    </button>
                </div>
                <div style="display: flex; gap: 10px; margin-bottom: 15px;">
                    <button id="downloadCSV" title="CSV 다운로드 (Ctrl+D)" style="flex: 1; padding: 10px; background-color: #2980b9; border: none; border-radius: 4px; color: #ecf0f1; cursor: pointer; font-size: 14px;">
                        📥 CSV 다운로드
                    </button>
                    <button id="downloadJSON" title="JSON 다운로드 (Ctrl+J)" style="flex: 1; padding: 10px; background-color: #8e44ad; border: none; border-radius: 4px; color: #ecf0f1; cursor: pointer; font-size: 14px;">
                        📥 JSON 다운로드
                    </button>
                </div>
                <h3 style="border-bottom: 1px solid #34495e; padding-bottom: 5px; margin-bottom: 15px;">📜 로그</h3>
                <div id="log" style="background-color: #34495e; padding: 10px; border-radius: 4px; height: 150px; overflow-y: auto; font-size: 12px;"></div>
            </div>

            <div class="tabcontent" id="dataTab" style="display: none;">
                <h3 style="margin-bottom: 15px;">📊 수집된 데이터</h3>
                <input type="text" id="tableFilter" placeholder="검색어 입력..." title="데이터 필터링" style="width: 100%; padding: 8px; border: none; border-radius: 4px; margin-bottom: 10px;">
                <div style="max-height: 300px; overflow-y: auto; margin-bottom: 15px;">
                    <table id="dataTable" style="width: 100%; border-collapse: collapse; background-color: #2c3e50;">
                        <thead>
                            <tr>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="사이트 이름으로 정렬">📝 사이트 이름</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="사이트 주소로 정렬">🔗 주소</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="설명으로 정렬">📄 설명</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="텍스트 내용으로 정렬">🗨️ 텍스트 내용</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="전체 HTML 내용으로 정렬">📄 HTML 내용</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="이미지 URL으로 정렬">🖼️ 이미지 URL</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="비디오 링크로 정렬">🎥 비디오 링크</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="콘텐츠 분류로 정렬">🗂️ 분류</th>
                                <th style="padding: 8px; border-bottom: 1px solid #34495e; cursor: pointer;" title="콘텐츠 품질 점수로 정렬">⭐ 품질 점수</th>
                            </tr>
                        </thead>
                        <tbody></tbody>
                    </table>
                </div>
            </div>

            <div class="tabcontent" id="settingsTab" style="display: none;">
                <h3 style="border-bottom: 1px solid #34495e; padding-bottom: 5px; margin-bottom: 15px;">⚙️ 설정</h3>
                <div style="margin-bottom: 10px;">
                    <label for="pageSelector" style="font-size: 14px;">🔢 크롤링할 페이지 수:</label>
                    <select id="pageSelector" title="크롤링할 최대 페이지 수 선택" style="width: 100%; padding: 8px; border: none; border-radius: 4px; margin-top: 5px;">
                        ${generatePageOptions(state.maxPages)}
                    </select>
                </div>
                <div style="margin-bottom: 10px;">
                    <label for="delay" style="font-size: 14px;">⏱️ 요청 간 지연 시간 (ms):</label>
                    <input type="number" id="delay" title="요청 간 지연 시간을 밀리초 단위로 설정" value="${state.delay}" min="500" max="10000" style="width: 100%; padding: 8px; border: none; border-radius: 4px; margin-top: 5px;">
                </div>
                <div style="margin-bottom: 10px;">
                    <label for="filterKeywords" style="font-size: 14px;">🔍 필터링 키워드 (쉼표로 구분):</label>
                    <input type="text" id="filterKeywords" title="특정 키워드로 광고 및 불필요한 콘텐츠 필터링" value="${state.filterKeywords.join(', ')}" style="width: 100%; padding: 8px; border: none; border-radius: 4px; margin-top: 5px;">
                </div>
                <div style="margin-bottom: 10px;">
                    <label for="excludedDomains" style="font-size: 14px;">🚫 제외할 도메인 (쉼표로 구분):</label>
                    <input type="text" id="excludedDomains" title="크롤링에서 제외할 도메인 목록" value="${state.excludedDomains.join(', ')}" style="width: 100%; padding: 8px; border: none; border-radius: 4px; margin-top: 5px;">
                </div>
                <div style="margin-bottom: 10px;">
                    <label for="whitelistDomains" style="font-size: 14px;">✅ 허용할 도메인 (쉼표로 구분):</label>
                    <input type="text" id="whitelistDomains" title="크롤링을 허용할 도메인 목록 (설정 시, 이 외의 도메인은 크롤링되지 않습니다)" value="${state.whitelistDomains.join(', ')}" style="width: 100%; padding: 8px; border: none; border-radius: 4px; margin-top: 5px;">
                </div>
                <button id="saveSettings" title="설정 저장 (Ctrl+S)" style="width: 100%; padding: 10px; background-color: #e67e22; border: none; border-radius: 4px; color: #ecf0f1; cursor: pointer; font-size: 14px;">
                    💾 설정 저장
                </button>
                <h3 style="border-bottom: 1px solid #34495e; padding-bottom: 5px; margin: 15px 0 15px 0;">📈 데이터 시각화</h3>
                <canvas id="dataChart" width="550" height="200"></canvas>
            </div>

            <!-- HTML Content Modal -->
            <div id="htmlModal" class="modal" style="display:none; position: fixed; z-index: 10001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5);">
                <div class="modal-content" style="background-color: #2c3e50; margin: 10% auto; padding: 20px; border: 1px solid #888; width: 80%; border-radius: 8px;">
                    <span id="closeModal" class="close" style="color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer;">&times;</span>
                    <h2 style="color: #ecf0f1;">📄 전체 HTML 내용</h2>
                    <pre id="modalHTMLContent" style="background-color: #34495e; padding: 10px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; color: #ecf0f1;"></pre>
                </div>
            </div>
        `;
        document.body.appendChild(uiContainer);

        // 버튼 스타일링을 CSS로 이동
        const style = document.createElement('style');
        style.innerHTML = `
            /* 로딩 스피너 스타일 */
            .loader {
                border: 4px solid #f3f3f3;
                border-top: 4px solid #1abc9c;
                border-radius: 50%;
                width: 24px;
                height: 24px;
                animation: spin 1s linear infinite;
                margin: auto;
            }

            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }

            /* 테이블 스트라이프 및 호버 효과 */
            #dataTable tbody tr:nth-child(odd) {
                background-color: #34495e;
            }

            #dataTable tbody tr:hover {
                background-color: #1abc9c;
                color: #2c3e50;
            }

            #dataTable th, #dataTable td {
                text-align: left;
            }

            /* 인풋 필드 포커스 효과 */
            input[type="text"], input[type="number"], select {
                background-color: #34495e;
                color: #ecf0f1;
            }

            input::placeholder {
                color: #95a5a6;
            }

            /* 탭 스타일 */
            .tab button {
                background-color: #2c3e50;
                border: none;
                outline: none;
                cursor: pointer;
                padding: 10px 16px;
                transition: background-color 0.3s;
                font-size: 16px;
                border-bottom: 2px solid transparent;
            }

            .tab button:hover {
                background-color: #34495e;
            }

            .tab button.active {
                background-color: #1abc9c;
                border-bottom: 2px solid #1abc9c;
            }

            .tabcontent {
                display: none;
            }

            /* 모달 스타일 */
            .modal-content {
                background-color: #2c3e50;
                color: #ecf0f1;
            }

            .close:hover,
            .close:focus {
                color: #ffffff;
                text-decoration: none;
                cursor: pointer;
            }
        `;
        document.head.appendChild(style);

        /*** 차트 초기화 및 업데이트 ***/
        if (!window.Chart) {
            const chartScript = document.createElement('script');
            chartScript.src = 'https://cdn.jsdelivr.net/npm/chart.js';
            chartScript.onload = () => {
                initializeChart();
                if (state.isCrawling) {
                    crawlPage();
                }
            };
            chartScript.onerror = () => {
                log('Chart.js 로드에 실패했습니다. 데이터 시각화를 사용할 수 없습니다.', 'error');
            };
            document.head.appendChild(chartScript);
        } else {
            initializeChart();
        }
    }

    /*** Helper 함수: 페이지 옵션 생성 ***/
    function generatePageOptions(selectedPage) {
        let options = '';
        for (let i = 1; i <= 20; i++) { // 최대 20페이지까지 선택 가능
            options += `<option value="${i}" ${i === selectedPage ? 'selected' : ''}>${i} 페이지</option>`;
        }
        return options;
    }

    /*** 5. 차트 초기화 및 업데이트 ***/
    let dataChart;
    function initializeChart() {
        const ctx = document.getElementById('dataChart').getContext('2d');
        dataChart = new Chart(ctx, {
            type: 'bar',
            data: {
                labels: ['수집된 사이트 수'],
                datasets: [{
                    label: '사이트 수',
                    data: [state.collectedData.length],
                    backgroundColor: 'rgba(46, 204, 113, 0.6)',
                    borderColor: 'rgba(46, 204, 113, 1)',
                    borderWidth: 1
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            precision:0
                        }
                    }
                },
                plugins: {
                    legend: {
                        display: false
                    }
                }
            }
        });
    }

    function updateChart() {
        if (!dataChart) {
            log('Chart.js가 초기화되지 않았습니다. 데이터를 시각화할 수 없습니다.', 'error');
            return;
        }
        dataChart.data.datasets[0].data = [state.collectedData.length];
        dataChart.update();
    }

    /*** 6. 로그 함수 ***/
    function log(message, type = 'info') {
        const time = new Date().toLocaleTimeString();
        let fullMessage = `[${time}] ${message}`;
        if (type === 'error') {
            fullMessage = `[${time}] <span style="color: #e74c3c;">${message}</span>`;
        } else if (type === 'warning') {
            fullMessage = `[${time}] <span style="color: #f1c40f;">${message}</span>`;
        } else if (type === 'info') {
            fullMessage = `[${time}] <span style="color: #ecf0f1;">${message}</span>`;
        }
        const logElement = document.getElementById('log');
        if (logElement) {
            logElement.innerHTML += fullMessage + '<br>';
            logElement.scrollTop = logElement.scrollHeight;
        }
        state.log += fullMessage + '<br>';
        GM_setValue(STORAGE_KEY, state);
    }

    /*** 7. 진행률 및 상태 업데이트 함수 ***/
    function updateProgress(current, total) {
        const percent = total === 0 ? 0 : Math.floor((current / total) * 100);
        const progressBar = document.getElementById('progressBar');
        const progressText = document.getElementById('progressText');
        const progressCount = document.getElementById('progressCount');
        const progressTotal = document.getElementById('progressTotal');

        if (progressBar) progressBar.style.width = `${percent}%`;
        if (progressText) progressText.innerText = `${percent}%`;
        if (progressCount) progressCount.innerText = current;
        if (progressTotal) progressTotal.innerText = total;
    }

    function updateStatus(statusText = null) {
        const currentPageElement = document.getElementById('currentPage');
        const estimatedTimeElement = document.getElementById('estimatedTime');
        if (currentPageElement) currentPageElement.innerText = `${state.currentPage}`;

        if (statusText) {
            setStatus(statusText);
        }

        // 예상 완료 시간 계산
        const remainingPages = state.maxPages - state.currentPage;
        const estimatedSeconds = Math.floor((remainingPages * state.delay) / 1000);
        if (estimatedTimeElement) estimatedTimeElement.innerText = `${estimatedSeconds}초`;
    }

    /*** 8. 데이터 추가 함수 (중복 제거 포함) ***/
    async function addData(name, url, description, textContent, htmlContent, imageUrls = [], videoLinks = [], classification = 'Uncategorized', qualityScore = 0) {
        // 중복 데이터 확인
        if (state.visitedURLs.includes(url)) {
            log(`이미 존재하는 데이터: ${url}`, 'warning');
            return;
        }

        // 광고 및 제외 도메인 필터링
        if (isAdSite(description, url)) {
            log(`광고 또는 제외 도메인 사이트 발견 및 필터링: ${url}`, 'warning');
            return;
        }

        // 링크 유효성 검사
        const isValid = await validateLink(url);
        if (!isValid) {
            log(`유효하지 않은 링크 발견 및 제외: ${url}`, 'warning');
            return;
        }

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

        // 콘텐츠 분류
        classification = classifyKeywords(description);

        // 데이터 수집
        state.visitedURLs.push(url);
        state.collectedData.push({ name, url, description, textContent, htmlContent, imageUrls, videoLinks, classification, qualityScore });
        GM_setValue(STORAGE_KEY, state);

        // 테이블에 데이터 추가
        const dataTableBody = document.querySelector('#dataTable tbody');
        if (dataTableBody) {
            const row = document.createElement('tr');
            row.innerHTML = `
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${name}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;"><a href="${url}" target="_blank" style="color: #1abc9c; text-decoration: none;">🔗 보기</a></td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${description}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${truncateText(textContent, 100)}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">
                    ${htmlContent.length > 0 ? `<button class="view-html" data-html="${encodeURIComponent(htmlContent)}" style="background: transparent; border: none; color: #1abc9c; cursor: pointer;">📄 보기</button>` : 'N/A'}
                </td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${imageUrls.length > 0 ? `<a href="${imageUrls[0]}" target="_blank" style="color: #e67e22; text-decoration: none;">🖼️ 이미지</a>` : 'N/A'}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${videoLinks.length > 0 ? `<a href="${videoLinks[0]}" target="_blank" style="color: #9b59b6; text-decoration: none;">🎥 비디오</a>` : 'N/A'}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${classification}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">⭐ ${qualityScore}</td>
            `;
            dataTableBody.appendChild(row);
        }

        // 이벤트 리스너 추가 for HTML View Button
        addHTMLViewListener();

        // 로그 및 진행률 업데이트
        log(`데이터 수집: ${name} (품질 점수: ${qualityScore})`);
        updateProgress(state.collectedData.length, state.maxPages * 10);
        updateChart();
        updateStatus();
    }

    /*** Helper 함수: 텍스트 트렁케이션 ***/
    function truncateText(text, maxLength) {
        if (text.length > maxLength) {
            return text.substring(0, maxLength) + '...';
        }
        return text;
    }

    /*** 9. 광고 및 제외 도메인 필터링 함수 ***/
    function isAdSite(description, url) {
        // 먼저, 화이트리스트가 설정된 경우, 화이트리스트에 없는 도메인은 제외
        if (state.whitelistDomains.length > 0) {
            const isWhitelisted = state.whitelistDomains.some(domain => url.includes(domain));
            if (!isWhitelisted) {
                return true; // 제외
            }
        }

        // 키워드 필터링
        const keywords = state.filterKeywords.map(k => k.toLowerCase());
        const isAd = keywords.some(keyword => description.toLowerCase().includes(keyword));

        // 도메인 제외 필터링
        const isExcluded = state.excludedDomains.some(domain => url.includes(domain));

        return isAd || isExcluded;
    }

    /*** 10. 검색 결과 추출 함수 (검색 엔진별 지원 확장) ***/
    function extractSearchResults() {
        let results = [];
        switch (state.targetSearchEngine) {
            case 'google':
                const googleResults = document.querySelectorAll('div.g');
                googleResults.forEach(block => {
                    const titleElement = block.querySelector('h3');
                    const linkElement = block.querySelector('a');
                    const descriptionElement = block.querySelector('.IsZvec, .VwiC3b');
                    const imageElements = block.querySelectorAll('img');
                    const videoElements = block.querySelectorAll('a[href*="video"]'); // Simplistic video link detection
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        const textContent = block.innerText;
                        const htmlContent = block.innerHTML;
                        const imageUrls = Array.from(imageElements).map(img => img.src).filter(src => src);
                        const videoLinks = Array.from(videoElements).map(a => a.href).filter(href => href);
                        results.push({ name, url, description, textContent, htmlContent, imageUrls, videoLinks });
                    }
                });
                break;
            case 'yandex':
                const yandexResults = document.querySelectorAll('.serp-item');
                yandexResults.forEach(block => {
                    const titleElement = block.querySelector('.organic__url-text');
                    const linkElement = block.querySelector('.organic__url');
                    const descriptionElement = block.querySelector('.organic__text');
                    const imageElements = block.querySelectorAll('img');
                    const videoElements = block.querySelectorAll('a[href*="video"]'); // Simplistic video link detection
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        const textContent = block.innerText;
                        const htmlContent = block.innerHTML;
                        const imageUrls = Array.from(imageElements).map(img => img.src).filter(src => src);
                        const videoLinks = Array.from(videoElements).map(a => a.href).filter(href => href);
                        results.push({ name, url, description, textContent, htmlContent, imageUrls, videoLinks });
                    }
                });
                break;
            case 'bing':
                const bingResults = document.querySelectorAll('.b_algo');
                bingResults.forEach(block => {
                    const titleElement = block.querySelector('h2');
                    const linkElement = block.querySelector('a');
                    const descriptionElement = block.querySelector('.b_caption p');
                    const imageElements = block.querySelectorAll('img');
                    const videoElements = block.querySelectorAll('a[href*="video"]'); // Simplistic video link detection
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        const textContent = block.innerText;
                        const htmlContent = block.innerHTML;
                        const imageUrls = Array.from(imageElements).map(img => img.src).filter(src => src);
                        const videoLinks = Array.from(videoElements).map(a => a.href).filter(href => href);
                        results.push({ name, url, description, textContent, htmlContent, imageUrls, videoLinks });
                    }
                });
                break;
            case 'duckduckgo':
                const ddgResults = document.querySelectorAll('.result');
                ddgResults.forEach(block => {
                    const titleElement = block.querySelector('.result__title a');
                    const linkElement = block.querySelector('.result__url');
                    const descriptionElement = block.querySelector('.result__snippet');
                    const imageElements = block.querySelectorAll('img');
                    const videoElements = block.querySelectorAll('a[href*="video"]'); // Simplistic video link detection
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        const textContent = block.innerText;
                        const htmlContent = block.innerHTML;
                        const imageUrls = Array.from(imageElements).map(img => img.src).filter(src => src);
                        const videoLinks = Array.from(videoElements).map(a => a.href).filter(href => href);
                        results.push({ name, url, description, textContent, htmlContent, imageUrls, videoLinks });
                    }
                });
                break;
            default:
                console.warn('지원되지 않는 검색 엔진입니다.');
        }
        return results;
    }

    /*** 11. 다음 페이지로 이동 함수 (검색 엔진별 지원 확장) ***/
    function goToNextPage() {
        switch (state.targetSearchEngine) {
            case 'google':
                const googleNext = document.querySelector('#pnnext');
                if (googleNext) {
                    googleNext.click();
                    return true;
                }
                break;
            case 'yandex':
                const yandexNext = document.querySelector('a.button_theme_clear.button_size_m.button_pagination_next');
                if (yandexNext) {
                    yandexNext.click();
                    return true;
                }
                break;
            case 'bing':
                const bingNext = document.querySelector('.sb_pagN');
                if (bingNext) {
                    bingNext.click();
                    return true;
                }
                break;
            case 'duckduckgo':
                const ddgNext = document.querySelector('.result--more__btn');
                if (ddgNext) {
                    ddgNext.click();
                    return true;
                }
                break;
            default:
                return false;
        }
        return false;
    }

    /*** 12. 설정 저장 함수 ***/
    function saveSettings() {
        const pageSelector = document.getElementById('pageSelector');
        const maxPages = parseInt(pageSelector.value);
        const delay = parseInt(document.getElementById('delay').value);
        const filterKeywords = document.getElementById('filterKeywords').value.split(',').map(k => k.trim());
        const excludedDomains = document.getElementById('excludedDomains').value.split(',').map(d => d.trim());
        const whitelistDomains = document.getElementById('whitelistDomains').value.split(',').map(d => d.trim());

        state.maxPages = maxPages;
        state.delay = delay;
        state.filterKeywords = filterKeywords;
        state.excludedDomains = excludedDomains;
        state.whitelistDomains = whitelistDomains.filter(d => d.length > 0);
        GM_setValue(STORAGE_KEY, state);

        log('설정이 저장되었습니다.', 'info');
        updateProgress(state.collectedData.length, state.maxPages * 10);
        updateStatus();
    }

    /*** 13. 링크 검증 함수 ***/
    function validateLink(url) {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: "HEAD",
                url: url,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 400) {
                        resolve(true);
                    } else {
                        resolve(false);
                    }
                },
                onerror: function() {
                    resolve(false);
                }
            });
        });
    }

    /*** 14. 크롤링 함수 (에러 처리 및 재시도 로직 포함) ***/
    async function crawlPage() {
        if (!state.isCrawling) return;

        try {
            setStatus(`페이지 ${state.currentPage} 크롤링 중...`);
            log(`페이지 ${state.currentPage} 크롤링 시작.`);
            updateProgress(state.collectedData.length, state.maxPages * 10);
            toggleSpinner(true);

            // 검색 결과 추출
            const searchResults = extractSearchResults();

            log(`검색 결과 추출: ${searchResults.length}개 항목 발견.`, 'info');

            // 데이터 처리
            for (let result of searchResults) {
                log(`데이터 처리 중: ${result.name}`, 'info');
                await addData(result.name, result.url, result.description, result.textContent, result.htmlContent, result.imageUrls, result.videoLinks);
            }

            toggleSpinner(false);

            // 최대 페이지 수 도달 여부 확인
            if (state.currentPage >= state.maxPages) {
                log('크롤링 완료!', 'info');
                notifyUser('크롤링이 완료되었습니다.');
                state.isCrawling = false;
                GM_setValue(STORAGE_KEY, state);
                updateStatus('크롤링 완료');
                return;
            }

            // 다음 페이지로 이동
            const navigated = goToNextPage();
            if (navigated) {
                state.currentPage++;
                log(`다음 페이지(${state.currentPage})로 이동.`, 'info');
                GM_setValue(STORAGE_KEY, state);
                updateStatus();

                // 단계별 진행 표시
                setStatus(`페이지 ${state.currentPage} 로드 중...`);

                // 페이지 로딩 대기 (DELAY)
                setTimeout(() => {
                    crawlPage();
                }, state.delay);
            } else {
                log('다음 페이지를 찾을 수 없습니다. 크롤링을 종료합니다.', 'warning');
                notifyUser('다음 페이지를 찾을 수 없어 크롤링을 종료합니다.');
                state.isCrawling = false;
                GM_setValue(STORAGE_KEY, state);
                updateStatus('다음 페이지 없음');
            }
        } catch (error) {
            log(`에러 발생: ${error.message}`, 'error');
            notifyUser(`에러 발생: ${error.message}`);
            toggleSpinner(false);
            // 재시도 로직
            if (state.retryCount < state.maxRetries) {
                state.retryCount++;
                log(`재시도 시도 (${state.retryCount}/${state.maxRetries})...`, 'warning');
                GM_setValue(STORAGE_KEY, state);
                setTimeout(crawlPage, state.delay);
            } else {
                log('재시도 한계를 초과했습니다. 크롤링을 중지합니다.', 'error');
                notifyUser('재시도 한계를 초과하여 크롤링을 중지했습니다.');
                state.isCrawling = false;
                GM_setValue(STORAGE_KEY, state);
                updateStatus('크롤링 중지');
            }
        }
    }

    /*** 15. 크롤링 제어 함수 ***/
    function startCrawling() {
        if (state.isCrawling) {
            log('이미 크롤링 중입니다.', 'warning');
            return;
        }

        state.isCrawling = true;
        state.currentPage = 1;
        state.collectedData = [];
        state.visitedURLs = [];
        state.retryCount = 0;
        GM_setValue(STORAGE_KEY, state);
        log('크롤링 시작.', 'info');
        setStatus('크롤링 시작');
        updateProgress(0, state.maxPages * 10);
        createUI();
        updateChart();
        crawlPage();
    }

    function stopCrawling() {
        if (!state.isCrawling) {
            log('크롤링이 진행되고 있지 않습니다.', 'warning');
            return;
        }
        state.isCrawling = false;
        GM_setValue(STORAGE_KEY, state);
        log('크롤링 중지.', 'info');
        notifyUser('크롤링이 중지되었습니다.');
        setStatus('크롤링 중지');
        updateStatus();
    }

    /*** 16. 상태 표시 함수 ***/
    function setStatus(status) {
        const statusElement = document.getElementById('currentStatus');
        if (statusElement) {
            statusElement.innerText = `${status}`;
        } else {
            const uiContainer = document.getElementById('advanced-web-scraper-ui');
            const newStatus = document.createElement('div');
            newStatus.id = 'currentStatus';
            newStatus.style.marginTop = '5px';
            newStatus.innerText = `${status}`;
            uiContainer.appendChild(newStatus);
        }
    }

    /*** 17. 데이터 정렬 및 필터링 ***/
    function sortTable(columnIndex) {
        const dataTable = document.getElementById('dataTable');
        const tbody = dataTable.querySelector('tbody');
        const rows = Array.from(tbody.querySelectorAll('tr'));

        rows.sort((a, b) => {
            const aText = a.children[columnIndex].innerText.toLowerCase();
            const bText = b.children[columnIndex].innerText.toLowerCase();
            if (aText < bText) return -1;
            if (aText > bText) return 1;
            return 0;
        });

        // 테이블 재정렬
        tbody.innerHTML = '';
        rows.forEach(row => tbody.appendChild(row));
        log(`테이블이 ${dataTable.querySelectorAll('th')[columnIndex].innerText} 기준으로 정렬되었습니다.`, 'info');
    }

    function filterTable() {
        const filter = document.getElementById('tableFilter').value.toLowerCase();
        const rows = document.querySelectorAll('#dataTable tbody tr');
        rows.forEach(row => {
            const cells = row.querySelectorAll('td');
            const match = Array.from(cells).some(cell => cell.innerText.toLowerCase().includes(filter));
            row.style.display = match ? '' : 'none';
        });
    }

    /*** 18. 데이터 내보내기 및 가져오기 ***/
    function downloadCSV() {
        if (state.collectedData.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }

        let csvContent = "data:text/csv;charset=utf-8,";
        csvContent += "사이트 이름,주소,설명,텍스트 내용,HTML 내용,이미지 URL,비디오 링크,분류,품질 점수\n";
        state.collectedData.forEach(row => {
            const rowData = `"${row.name.replace(/"/g, '""')}", "${row.url}", "${row.description.replace(/"/g, '""')}", "${row.textContent.replace(/"/g, '""')}", "${row.htmlContent.replace(/"/g, '""')}", "${row.imageUrls.length > 0 ? row.imageUrls[0] : 'N/A'}", "${row.videoLinks.length > 0 ? row.videoLinks[0] : 'N/A'}", "${row.classification}", "${row.qualityScore}"`;
            csvContent += rowData + "\n";
        });

        const encodedUri = encodeURI(csvContent);
        const link = document.createElement('a');
        link.setAttribute('href', encodedUri);
        link.setAttribute('download', 'search_results.csv');
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('CSV 파일이 다운로드되었습니다.', 'info');
    }

    function downloadJSON() {
        if (state.collectedData.length === 0) {
            log('다운로드할 데이터가 없습니다.', 'warning');
            return;
        }

        const jsonContent = JSON.stringify(state.collectedData, null, 2);
        const blob = new Blob([jsonContent], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = 'search_results.json';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        log('JSON 파일이 다운로드되었습니다.', 'info');
    }

    /*** 19. 테이블 정렬 이벤트 리스너 추가 ***/
    function addTableSortListeners() {
        const headers = document.querySelectorAll('#dataTable th');
        headers.forEach((header, index) => {
            header.style.cursor = 'pointer';
            header.addEventListener('click', () => sortTable(index));
        });
    }

    /*** 20. 테이블 필터 이벤트 리스너 추가 ***/
    function addTableFilterListener() {
        const filterInput = document.getElementById('tableFilter');
        filterInput.addEventListener('input', filterTable);
    }

    /*** 21. 반응형 디자인 (CSS 미디어 쿼리) ***/
    function addResponsiveStyles() {
        // 이미 스타일이 createUI에서 추가됨
    }

    /*** 22. 자동 저장 및 복구 ***/
    // 이미 GM_setValue와 GM_getValue로 구현됨. 또한 주기적으로 상태를 저장함.

    /*** 23. 초기화 및 상태 복원 함수 ***/
    function initializeUIComponents() {
        createUI();
        addResponsiveStyles();
        addTableSortListeners();
        addTableFilterListener();
        updateProgress(state.collectedData.length, state.maxPages * 10);
        updateStatus();

        // 기존 데이터 표시
        const dataTableBody = document.querySelector('#dataTable tbody');
        state.collectedData.forEach(item => {
            const row = document.createElement('tr');
            row.innerHTML = `
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${item.name}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;"><a href="${item.url}" target="_blank" style="color: #1abc9c; text-decoration: none;">🔗 보기</a></td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${item.description}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${truncateText(item.textContent, 100)}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">
                    ${item.htmlContent.length > 0 ? `<button class="view-html" data-html="${encodeURIComponent(item.htmlContent)}" style="background: transparent; border: none; color: #1abc9c; cursor: pointer;">📄 보기</button>` : 'N/A'}
                </td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${item.imageUrls.length > 0 ? `<a href="${item.imageUrls[0]}" target="_blank" style="color: #e67e22; text-decoration: none;">🖼️ 이미지</a>` : 'N/A'}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${item.videoLinks.length > 0 ? `<a href="${item.videoLinks[0]}" target="_blank" style="color: #9b59b6; text-decoration: none;">🎥 비디오</a>` : 'N/A'}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">${item.classification}</td>
                <td style="padding: 8px; border-bottom: 1px solid #34495e;">⭐ ${item.qualityScore}</td>
            `;
            dataTableBody.appendChild(row);
        });

        // 이벤트 리스너 추가 for HTML View Button
        addHTMLViewListener();

        // 로그 복원
        if (state.log) {
            const logElement = document.getElementById('log');
            logElement.innerHTML = state.log;
        }

        // 크롤링이 진행 중인 경우 계속 진행
        if (state.isCrawling) {
            crawlPage();
        }

        // UI 창 닫기 기능 추가
        const closeButton = document.getElementById('closeUI');
        closeButton.addEventListener('click', () => {
            const ui = document.getElementById('advanced-web-scraper-ui');
            ui.style.display = 'none';
        });

        // 모달 닫기 기능 추가
        const modal = document.getElementById('htmlModal');
        const closeModal = document.getElementById('closeModal');
        closeModal.addEventListener('click', () => {
            modal.style.display = 'none';
        });
        window.addEventListener('click', (event) => {
            if (event.target == modal) {
                modal.style.display = 'none';
            }
        });
    }

    /*** 24. 자동 저장 타이머 설정 ***/
    setInterval(() => {
        GM_setValue(STORAGE_KEY, state);
    }, 5000); // 5초마다 저장

    /*** 25. 이벤트 리스너 설정 ***/
    function setupEventListeners() {
        const startButton = document.getElementById('startCrawl');
        const stopButton = document.getElementById('stopCrawl');
        const saveSettingsButton = document.getElementById('saveSettings');
        const downloadCSVButton = document.getElementById('downloadCSV');
        const downloadJSONButton = document.getElementById('downloadJSON');

        if (startButton) startButton.addEventListener('click', startCrawling);
        if (stopButton) stopButton.addEventListener('click', stopCrawling);
        if (saveSettingsButton) saveSettingsButton.addEventListener('click', saveSettings);
        if (downloadCSVButton) downloadCSVButton.addEventListener('click', downloadCSV);
        if (downloadJSONButton) downloadJSONButton.addEventListener('click', downloadJSON);

        // 탭 전환 이벤트 리스너
        const tabButtons = document.querySelectorAll('.tab button');
        tabButtons.forEach(button => {
            button.addEventListener('click', () => {
                openTab(button.getAttribute('data-tab'));
            });
        });

        // 키보드 단축키 이벤트 리스너
        window.addEventListener('keydown', handleKeyboardShortcuts);
    }

    /*** 26. 스피너 토글 함수 ***/
    function toggleSpinner(show) {
        const spinner = document.getElementById('spinner');
        if (spinner) {
            spinner.style.display = show ? 'block' : 'none';
        }
    }

    /*** 27. 탭 전환 함수 ***/
    function openTab(tabName) {
        const tabContents = document.querySelectorAll('.tabcontent');
        tabContents.forEach(content => {
            content.style.display = 'none';
        });

        const tabButtons = document.querySelectorAll('.tab button');
        tabButtons.forEach(button => {
            button.classList.remove('active');
        });

        const activeTab = document.getElementById(tabName);
        if (activeTab) {
            activeTab.style.display = 'block';
        }

        const activeButton = document.querySelector(`.tab button[data-tab="${tabName}"]`);
        if (activeButton) {
            activeButton.classList.add('active');
        }
    }

    /*** 28. HTML 내용 Modal 표시 함수 ***/
    function addHTMLViewListener() {
        const htmlViewButtons = document.querySelectorAll('.view-html');
        htmlViewButtons.forEach(button => {
            button.addEventListener('click', () => {
                const encodedHTML = button.getAttribute('data-html');
                const decodedHTML = decodeURIComponent(encodedHTML);
                const modal = document.getElementById('htmlModal');
                const modalContent = document.getElementById('modalHTMLContent');
                modalContent.innerText = decodedHTML;
                modal.style.display = 'block';
            });
        });
    }

    /*** 29. 콘텐츠 품질 평가 함수 ***/
    function evaluateContentQuality(textContent) {
        const wordCount = textContent.split(/\s+/).length;
        if (wordCount < 100) return 1;
        if (wordCount < 500) return 2;
        if (wordCount < 1000) return 3;
        return 4;
    }

    /*** 30. 키워드 분류 함수 ***/
    function classifyKeywords(description) {
        const categories = {
            '기술': ['기술', 'software', 'hardware', 'AI', '인공지능'],
            '비즈니스': ['비즈니스', 'business', 'startup', 'entrepreneur'],
            '교육': ['교육', 'education', 'learning', '학교'],
            '건강': ['건강', 'health', '의료', '헬스'],
            // 추가 카테고리를 여기에 정의할 수 있습니다.
        };

        for (const [category, keywords] of Object.entries(categories)) {
            for (const keyword of keywords) {
                if (description.toLowerCase().includes(keyword.toLowerCase())) {
                    return category;
                }
            }
        }
        return 'Uncategorized';
    }

    /*** 31. 키보드 단축키 핸들러 ***/
    function handleKeyboardShortcuts(event) {
        if (event.ctrlKey) {
            switch (event.key.toLowerCase()) {
                case 's':
                    event.preventDefault();
                    saveSettings();
                    break;
                case 'p':
                    event.preventDefault();
                    startCrawling();
                    break;
                case 'q':
                    event.preventDefault();
                    stopCrawling();
                    break;
                case 'd':
                    event.preventDefault();
                    downloadCSV();
                    break;
                case 'j':
                    event.preventDefault();
                    downloadJSON();
                    break;
                case '1':
                    event.preventDefault();
                    openTab('logTab');
                    break;
                case '2':
                    event.preventDefault();
                    openTab('dataTab');
                    break;
                case '3':
                    event.preventDefault();
                    openTab('settingsTab');
                    break;
                default:
                    break;
            }
        }
    }

    /*** 32. 콘텐츠 정렬 및 필터링 함수 ***/
    // 이미 구현됨.

    /*** 33. 기타 Helper 함수 ***/
    // None at this time.

    /*** 34. 로드 시 초기화 ***/
    window.addEventListener('load', () => {
        initializeUIComponents();
        setupEventListeners();
    });

    /*** 35. 기타 Functionality ***/
    // None at this time.

    /*** 36. 추가된 HTML Modal 스타일링 ***/
    // 이미 CSS에 포함됨.

    /*** 37. 데이터 테이블 및 차트 동기화 ***/
    // Already handled in addData function.

    /*** 완료 ***/
})();

                      여기 코드에 어울릴 만 한 추가 기능 한 100가지 정도를 "알려줘" 한국어로!