// ==UserScript==
// @name         Ultimate Google Search Crawler
// @namespace    http://tampermonkey.net/
// @version      6.1
// @description  Advanced crawler for Google Search with enhanced UI, real-time filtering, content quality assessment, dynamic ad filtering, and keyword-based exclusion.
// @author       Your Name
// @match        *://www.google.com/search*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function() {
    'use strict';

    /* ===========================
       ======== Style Section =====
       =========================== */
    GM_addStyle(`
        @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
        @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');

        body {
            font-family: 'Roboto', sans-serif;
        }

        /* 컨트롤 패널 스타일 */
        #ultimate-crawler-controls {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #ffffff;
            padding: 20px;
            border: 1px solid #ddd;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            z-index: 10000;
            border-radius: 10px;
            width: 400px;
            max-height: 95vh;
            overflow-y: auto;
            transition: all 0.3s ease;
        }

        #ultimate-crawler-controls h2 {
            font-size: 24px;
            margin-bottom: 20px;
            text-align: center;
            color: #333;
        }

        #ultimate-crawler-controls button {
            width: 100%;
            padding: 10px;
            margin: 5px 0;
            border: none;
            border-radius: 5px;
            background-color: #007BFF;
            color: white;
            font-size: 16px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background-color 0.3s;
        }

        #ultimate-crawler-controls button:hover {
            background-color: #0056b3;
        }

        #ultimate-crawler-controls button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }

        #ultimate-crawler-controls button i {
            margin-right: 8px;
        }

        /* 진행률 바 스타일 */
        #ultimate-crawler-progress-container {
            margin: 15px 0;
            display: flex;
            align-items: center;
        }

        #ultimate-crawler-progress {
            width: 80%;
            background-color: #f3f3f3;
            border-radius: 5px;
            height: 20px;
            overflow: hidden;
            position: relative;
        }

        #ultimate-crawler-progress-bar {
            height: 100%;
            width: 0%;
            background-color: #4caf50;
            transition: width 0.5s;
        }

        #ultimate-crawler-percentage {
            margin-left: 10px;
            font-size: 14px;
            color: #333;
            width: 15%;
            text-align: left;
        }

        /* 상세 진행 상황 표시 */
        #ultimate-crawler-details {
            font-size: 14px;
            color: #333;
            margin-bottom: 15px;
        }

        /* 검색 필드 스타일 */
        #ultimate-crawler-search {
            width: 100%;
            padding: 8px;
            margin-bottom: 15px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }

        /* 설정 섹션 스타일 */
        #ultimate-crawler-settings {
            margin-top: 15px;
        }

        #ultimate-crawler-settings label {
            display: block;
            margin: 10px 0 5px;
            font-size: 14px;
            color: #333;
        }

        #ultimate-crawler-settings input[type="text"] {
            width: calc(100% - 40px);
            padding: 5px;
            margin-right: 5px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }

        #ultimate-crawler-settings button.add-btn {
            width: 30px;
            padding: 5px;
            border: none;
            background-color: #28a745;
            color: white;
            border-radius: 3px;
            cursor: pointer;
            font-size: 14px;
        }

        #ultimate-crawler-settings button.add-btn:hover {
            background-color: #218838;
        }

        #ultimate-crawler-settings ul {
            list-style: none;
            padding: 0;
            margin: 5px 0;
        }

        #ultimate-crawler-settings ul li {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 3px 0;
            border-bottom: 1px solid #eee;
        }

        #ultimate-crawler-settings ul li button.remove-btn {
            background: none;
            border: none;
            color: red;
            cursor: pointer;
            font-size: 14px;
        }

        /* 데이터 테이블 스타일 */
        #ultimate-crawler-results {
            position: fixed;
            top: 20px;
            left: 20px;
            width: 95%;
            max-width: 1200px;
            max-height: 90vh;
            background: #ffffff;
            border: 1px solid #ddd;
            box-shadow: 0 4px 20px rgba(0,0,0,0.2);
            z-index: 10000;
            border-radius: 10px;
            display: none;
            flex-direction: column;
            resize: both;
            overflow: auto;
        }

        #ultimate-crawler-results-header {
            padding: 15px;
            background-color: #f5f5f5;
            border-bottom: 1px solid #ddd;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
        }

        #ultimate-crawler-results-header h3 {
            margin: 0;
            font-size: 22px;
            color: #333;
        }

        #ultimate-crawler-results-header button {
            background: none;
            border: none;
            font-size: 22px;
            cursor: pointer;
            color: #888;
            transition: color 0.3s;
        }

        #ultimate-crawler-results-header button:hover {
            color: #333;
        }

        #ultimate-crawler-table-search {
            width: 95%;
            padding: 8px;
            margin: 15px auto;
            border: 1px solid #ccc;
            border-radius: 5px;
            display: block;
        }

        #ultimate-crawler-results table {
            width: 100%;
            border-collapse: collapse;
            table-layout: fixed;
            word-wrap: break-word;
        }

        #ultimate-crawler-results th, #ultimate-crawler-results td {
            padding: 12px 15px;
            border-bottom: 1px solid #ddd;
            text-align: left;
        }

        #ultimate-crawler-results th {
            background-color: #007BFF;
            color: white;
            position: sticky;
            top: 0;
            z-index: 1;
        }

        #ultimate-crawler-results tbody tr:nth-child(even) {
            background-color: #f9f9f9;
        }

        /* 로그 섹션 스타일 */
        #ultimate-crawler-log {
            max-height: 200px;
            overflow-y: auto;
            padding: 10px;
            background-color: #fafafa;
            border-top: 1px solid #ddd;
            font-size: 12px;
            color: #555;
        }

        /* HTML 뷰어 스타일 */
        #ultimate-crawler-html-viewer {
            display: none;
            position: fixed;
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
            width: 90%;
            height: 90%;
            background: #fff;
            border: 2px solid #333;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
            z-index: 10001;
            border-radius: 10px;
            overflow: hidden;
            flex-direction: column;
        }

        #ultimate-crawler-html-viewer-header {
            padding: 10px;
            background-color: #333;
            color: #fff;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
        }

        #ultimate-crawler-html-viewer-header h4 {
            margin: 0;
            font-size: 18px;
        }

        #ultimate-crawler-html-viewer-header button {
            background: none;
            border: none;
            font-size: 20px;
            cursor: pointer;
            color: #fff;
            transition: color 0.3s;
        }

        #ultimate-crawler-html-viewer-header button:hover {
            color: #ddd;
        }

        #ultimate-crawler-html-viewer iframe {
            width: 100%;
            height: calc(100% - 40px);
            border: none;
        }

        /* 애니메이션 사이트 iframe 스타일 */
        #ultimate-crawler-animation {
            width: 100%;
            height: 250px;
            border: none;
            margin-top: 20px;
            border-radius: 8px;
        }

        /* 반응형 디자인 */
        @media (max-width: 1024px) {
            #ultimate-crawler-controls {
                width: 90%;
                left: 5%;
                right: 5%;
            }

            #ultimate-crawler-results {
                width: 95%;
                left: 2.5%;
                right: 2.5%;
            }
        }
    `);

    /* ===========================
       ======== UI Creation ========
       =========================== */
    // 컨트롤 패널 생성
    const controlPanel = document.createElement('div');
    controlPanel.id = 'ultimate-crawler-controls';
    controlPanel.innerHTML = `
        <h2><i class="fas fa-search"></i> 웹 크롤러</h2>
        <button id="crawler-start"><i class="fas fa-play"></i> 시작</button>
        <button id="crawler-stop" disabled><i class="fas fa-pause"></i> 중지</button>
        <button id="crawler-reset"><i class="fas fa-sync-alt"></i> 초기화</button>
        <button id="crawler-show"><i class="fas fa-table"></i> 결과 보기</button>
        <div id="ultimate-crawler-progress-container">
            <div id="ultimate-crawler-progress">
                <div id="ultimate-crawler-progress-bar"></div>
            </div>
            <div id="ultimate-crawler-percentage">0%</div>
        </div>
        <div id="ultimate-crawler-details">
            <p>현재 페이지: <span id="current-page">0</span></p>
            <p>처리된 사이트 수: <span id="processed-sites">0</span></p>
            <p>남은 예상 시간: <span id="remaining-time">0</span> 초</p>
        </div>
        <input type="text" id="ultimate-crawler-search" placeholder="데이터 검색...">
        <div id="ultimate-crawler-settings">
            <label><input type="checkbox" id="toggle-html-fetch" checked> 사이트 HTML 수집</label>
            <label><input type="checkbox" id="toggle-ads-filter" checked> 광고 제외</label>
            <label><input type="checkbox" id="toggle-logging" checked> 로그 활성화</label>
            <div>
                <label>🚫 블랙리스트 도메인 추가:
                    <input type="text" id="add-blacklist-domain" placeholder="example.com">
                    <button class="add-btn" id="add-blacklist-domain-btn"><i class="fas fa-plus"></i></button>
                </label>
                <ul id="blacklist-domains-list"></ul>
            </div>
            <div>
                <label>✅ 화이트리스트 도메인 추가:
                    <input type="text" id="add-whitelist-domain" placeholder="example.com">
                    <button class="add-btn" id="add-whitelist-domain-btn"><i class="fas fa-plus"></i></button>
                </label>
                <ul id="whitelist-domains-list"></ul>
            </div>
            <div>
                <label>광고 도메인 추가:
                    <input type="text" id="add-ad-domain" placeholder="ads.example.com">
                    <button class="add-btn" id="add-ad-domain-btn"><i class="fas fa-plus"></i></button>
                </label>
                <ul id="ad-domains-list"></ul>
            </div>
            <div>
                <label>광고 키워드 추가:
                    <input type="text" id="add-ad-keyword" placeholder="ad">
                    <button class="add-btn" id="add-ad-keyword-btn"><i class="fas fa-plus"></i></button>
                </label>
                <ul id="ad-keywords-list"></ul>
            </div>
            <div>
                <label>키워드 기반 제외 추가:
                    <input type="text" id="add-exclude-keyword" placeholder="keyword">
                    <button class="add-btn" id="add-exclude-keyword-btn"><i class="fas fa-plus"></i></button>
                </label>
                <ul id="exclude-keywords-list"></ul>
            </div>
        </div>
        <div id="ultimate-crawler-log"></div>
        <iframe id="ultimate-crawler-animation" src="https://linkkf.net/"></iframe>
    `;
    document.body.appendChild(controlPanel);

    // 결과 창 생성
    const resultsWindow = document.createElement('div');
    resultsWindow.id = 'ultimate-crawler-results';
    resultsWindow.innerHTML = `
        <div id="ultimate-crawler-results-header">
            <h3>크롤링 결과</h3>
            <button id="close-results"><i class="fas fa-times"></i></button>
        </div>
        <input type="text" id="ultimate-crawler-table-search" placeholder="테이블 검색...">
        <table>
            <thead>
                <tr>
                    <th>사이트 이름</th>
                    <th>주소</th>
                    <th>설명</th>
                    <th>🗨️ 점수</th>
                    <th>🗂️ 분류</th>
                    <th>HTML 보기</th>
                </tr>
            </thead>
            <tbody id="ultimate-crawler-data-body">
            </tbody>
        </table>
        <div id="ultimate-crawler-log-window"></div>
    `;
    document.body.appendChild(resultsWindow);

    // HTML 뷰어 생성
    const htmlViewer = document.createElement('div');
    htmlViewer.id = 'ultimate-crawler-html-viewer';
    htmlViewer.innerHTML = `
        <div id="ultimate-crawler-html-viewer-header">
            <h4>사이트 HTML 보기</h4>
            <button id="close-html-viewer"><i class="fas fa-times"></i></button>
        </div>
        <iframe id="ultimate-crawler-html-iframe" src=""></iframe>
    `;
    document.body.appendChild(htmlViewer);

    /* =============================
       ======== Variable Section ========
       ============================= */
    // 버튼 요소
    const startButton = document.getElementById('crawler-start');
    const stopButton = document.getElementById('crawler-stop');
    const resetButton = document.getElementById('crawler-reset');
    const showButton = document.getElementById('crawler-show');
    const closeResultsButton = document.getElementById('close-results');
    const closeHtmlViewerButton = document.getElementById('close-html-viewer');

    // 진행률 바 요소
    const progressBar = document.getElementById('ultimate-crawler-progress-bar');
    const percentageText = document.getElementById('ultimate-crawler-percentage');

    // 상세 진행 상황 요소
    const currentPageSpan = document.getElementById('current-page');
    const processedSitesSpan = document.getElementById('processed-sites');
    const remainingTimeSpan = document.getElementById('remaining-time');

    // 검색 필드 요소
    const searchInput = document.getElementById('ultimate-crawler-search');
    const tableSearchInput = document.getElementById('ultimate-crawler-table-search');

    // 설정 체크박스 요소
    const htmlFetchToggle = document.getElementById('toggle-html-fetch');
    const adsFilterToggle = document.getElementById('toggle-ads-filter');
    const loggingToggle = document.getElementById('toggle-logging');

    // 블랙리스트 도메인 추가 요소
    const addBlacklistDomainInput = document.getElementById('add-blacklist-domain');
    const addBlacklistDomainBtn = document.getElementById('add-blacklist-domain-btn');
    const blacklistDomainsList = document.getElementById('blacklist-domains-list');

    // 화이트리스트 도메인 추가 요소
    const addWhitelistDomainInput = document.getElementById('add-whitelist-domain');
    const addWhitelistDomainBtn = document.getElementById('add-whitelist-domain-btn');
    const whitelistDomainsList = document.getElementById('whitelist-domains-list');

    // 광고 도메인 추가 요소
    const addAdDomainInput = document.getElementById('add-ad-domain');
    const addAdDomainBtn = document.getElementById('add-ad-domain-btn');
    const adDomainsList = document.getElementById('ad-domains-list');

    // 광고 키워드 추가 요소
    const addAdKeywordInput = document.getElementById('add-ad-keyword');
    const addAdKeywordBtn = document.getElementById('add-ad-keyword-btn');
    const adKeywordsList = document.getElementById('ad-keywords-list');

    // 키워드 기반 제외 추가 요소
    const addExcludeKeywordInput = document.getElementById('add-exclude-keyword');
    const addExcludeKeywordBtn = document.getElementById('add-exclude-keyword-btn');
    const excludeKeywordsList = document.getElementById('exclude-keywords-list');

    // 로그 창 요소
    const logWindow = document.getElementById('ultimate-crawler-log');

    // HTML 뷰어 요소
    const htmlViewerWindow = document.getElementById('ultimate-crawler-html-viewer');
    const htmlIframe = document.getElementById('ultimate-crawler-html-iframe');

    // 애니메이션 사이트 iframe 요소
    const animationIframe = document.getElementById('ultimate-crawler-animation');

    // 데이터 테이블 요소
    const dataBody = document.getElementById('ultimate-crawler-data-body');

    // 크롤링 상태 변수
    let crawling = false;
    let results = [];
    let logs = [];
    let currentPage = 1;
    const maxPages = 20;
    let processedUrls = new Set();
    let crawlInterval = 1000; // 1초 간격
    let estimatedTotalTime = 0;

    // 광고 도메인, 키워드, 블랙리스트, 화이트리스트 초기 설정
    let adDomains = GM_getValue('adDomains', [
        'doubleclick.net', 
        'googlesyndication.com', 
        'adservice.google.com', 
        'ads.youtube.com',
        'googleadservices.com',
        'ads.twitter.com',
        'facebook.com',
        'amazon-adsystem.com'
    ]);

    let adKeywords = GM_getValue('adKeywords', [
        'ad', 
        'ads', 
        'advertisement', 
        'promotion', 
        'sponsored'
    ]);

    let blacklistDomains = GM_getValue('blacklistDomains', []);
    let whitelistDomains = GM_getValue('whitelistDomains', []);
    let excludeKeywords = GM_getValue('excludeKeywords', []);

    /* =============================
       ======== Helper Functions ========
       ============================= */
    function logMessage(message, type = 'info') {
        if (!loggingToggle.checked && type !== 'error') return;
        const timestamp = new Date().toLocaleTimeString();
        const formattedMessage = `[${timestamp}] [${type.toUpperCase()}] ${message}`;
        logs.push(formattedMessage);
        // 로그 창 업데이트
        const logEntry = document.createElement('div');
        logEntry.textContent = formattedMessage;
        logEntry.style.color = (type === 'error') ? 'red' : '#555';
        logWindow.appendChild(logEntry);
        logWindow.scrollTop = logWindow.scrollHeight;
        // 전체 로그 저장
        GM_setValue('logs', logs);
    }

    function updateProgressBar() {
        const progress = Math.min((currentPage / maxPages) * 100, 100);
        progressBar.style.width = `${progress}%`;
        percentageText.textContent = `${progress.toFixed(2)}%`;
    }

    function updateDetails() {
        currentPageSpan.textContent = currentPage;
        processedSitesSpan.textContent = results.length;
        const remainingPages = maxPages - currentPage;
        estimatedTotalTime = remainingPages * (crawlInterval / 1000);
        remainingTimeSpan.textContent = estimatedTotalTime > 0 ? estimatedTotalTime.toFixed(1) : 0;
    }

    function saveState() {
        GM_setValue('crawling', crawling);
        GM_setValue('results', results);
        GM_setValue('currentPage', currentPage);
        GM_setValue('logs', logs);
        GM_setValue('processedUrls', Array.from(processedUrls));
        GM_setValue('adDomains', adDomains);
        GM_setValue('adKeywords', adKeywords);
        GM_setValue('blacklistDomains', blacklistDomains);
        GM_setValue('whitelistDomains', whitelistDomains);
        GM_setValue('excludeKeywords', excludeKeywords);
    }

    function loadState() {
        crawling = GM_getValue('crawling', false);
        results = GM_getValue('results', []);
        currentPage = GM_getValue('currentPage', 1);
        logs = GM_getValue('logs', []);
        const processed = GM_getValue('processedUrls', []);
        processedUrls = new Set(processed);
        adDomains = GM_getValue('adDomains', adDomains);
        adKeywords = GM_getValue('adKeywords', adKeywords);
        blacklistDomains = GM_getValue('blacklistDomains', blacklistDomains);
        whitelistDomains = GM_getValue('whitelistDomains', whitelistDomains);
        excludeKeywords = GM_getValue('excludeKeywords', excludeKeywords);
    }

    function resetState() {
        crawling = false;
        results = [];
        logs = [];
        currentPage = 1;
        processedUrls = new Set();
        blacklistDomains = [];
        whitelistDomains = [];
        excludeKeywords = [];
        GM_setValue('crawling', crawling);
        GM_setValue('results', results);
        GM_setValue('currentPage', currentPage);
        GM_setValue('logs', logs);
        GM_setValue('processedUrls', Array.from(processedUrls));
        GM_setValue('blacklistDomains', blacklistDomains);
        GM_setValue('whitelistDomains', whitelistDomains);
        GM_setValue('excludeKeywords', excludeKeywords);
        logMessage('크롤링 상태가 초기화되었습니다.', 'info');
    }

    function displayResults() {
        dataBody.innerHTML = '';
        results.forEach((site, index) => {
            const row = document.createElement('tr');

            const titleCell = document.createElement('td');
            titleCell.textContent = site.title;
            row.appendChild(titleCell);

            const linkCell = document.createElement('td');
            const link = document.createElement('a');
            link.href = site.link;
            link.textContent = site.link;
            link.target = '_blank';
            linkCell.appendChild(link);
            row.appendChild(linkCell);

            const descCell = document.createElement('td');
            descCell.textContent = site.description;
            row.appendChild(descCell);

            const scoreCell = document.createElement('td');
            scoreCell.textContent = site.score ? `${site.score} 🗨️` : 'N/A';
            row.appendChild(scoreCell);

            const categoryCell = document.createElement('td');
            categoryCell.textContent = site.category ? `🗂️ ${site.category}` : 'Uncategorized';
            row.appendChild(categoryCell);

            const htmlViewerCell = document.createElement('td');
            if (site.html) {
                const viewButton = document.createElement('button');
                viewButton.textContent = '보기';
                viewButton.style.padding = '5px 10px';
                viewButton.style.cursor = 'pointer';
                viewButton.addEventListener('click', () => {
                    htmlIframe.srcdoc = site.html;
                    htmlViewerWindow.style.display = 'flex';
                });
                htmlViewerCell.appendChild(viewButton);
            } else {
                htmlViewerCell.textContent = 'N/A';
            }
            row.appendChild(htmlViewerCell);

            dataBody.appendChild(row);
        });
    }

    function filterAds(site) {
        // 화이트리스트가 설정된 경우, 화이트리스트에 포함된 도메인만 허용
        if (whitelistDomains.length > 0) {
            const isWhitelisted = whitelistDomains.some(domain => site.link.includes(domain));
            if (!isWhitelisted) return false;
        }
        // 블랙리스트 도메인 필터링
        for (let blDomain of blacklistDomains) {
            if (site.link.includes(blDomain)) {
                return false;
            }
        }
        // 광고 도메인 필터링
        for (let adDomain of adDomains) {
            if (site.link.includes(adDomain)) {
                return false;
            }
        }
        // 광고 키워드 필터링
        const lowerLink = site.link.toLowerCase();
        for (let keyword of adKeywords) {
            if (lowerLink.includes(keyword)) {
                return false;
            }
        }
        return true;
    }

    /* =============================
       ======== Content Quality ======
       ============================= */
    function evaluateContentQuality(site) {
        if (!site.description) {
            site.score = 0;
            return;
        }
        const wordCount = site.description.split(/\s+/).length;
        let score = 0;
        if (wordCount > 100) score += 2;
        if (wordCount > 200) score += 3;
        site.score = score;
        logMessage(`사이트 점수 평가: ${site.link} - ${score} 🗨️`, 'info');
    }

    /* =============================
       ======== Keyword Classification ======
       ============================= */
    function classifyKeywords(site) {
        if (!site.description) {
            site.category = 'Uncategorized';
            return;
        }
        const description = site.description.toLowerCase();
        const categories = {
            'News': ['news', 'breaking', 'update'],
            'Technology': ['technology', 'tech', 'software', 'hardware'],
            'Entertainment': ['entertainment', 'movie', 'music', 'game'],
            'Health': ['health', 'medicine', 'wellness'],
            'Finance': ['finance', 'stock', 'investment']
        };
        for (let category in categories) {
            for (let keyword of categories[category]) {
                if (description.includes(keyword)) {
                    site.category = category;
                    logMessage(`사이트 분류: ${site.link} - 🗂️ ${category}`, 'info');
                    return;
                }
            }
        }
        site.category = 'Uncategorized';
    }

    /* =============================
       ======== Table Filtering ======
       ============================= */
    function filterTable() {
        const filter = searchInput.value.toLowerCase();
        const rows = dataBody.getElementsByTagName('tr');
        for (let row of rows) {
            const cells = row.getElementsByTagName('td');
            let match = false;
            for (let cell of cells) {
                if (cell.textContent.toLowerCase().includes(filter)) {
                    match = true;
                    break;
                }
            }
            row.style.display = match ? '' : 'none';
        }
    }

    function filterTableByKeyword() {
        const filter = tableSearchInput.value.toLowerCase();
        const rows = dataBody.getElementsByTagName('tr');
        for (let row of rows) {
            const cells = row.getElementsByTagName('td');
            let match = false;
            for (let cell of cells) {
                if (cell.textContent.toLowerCase().includes(filter)) {
                    match = true;
                    break;
                }
            }
            row.style.display = match ? '' : 'none';
        }
    }

    /* =============================
       ======== Exclude Keywords ======
       ============================= */
    function shouldExclude(site) {
        for (let keyword of excludeKeywords) {
            if (site.title.toLowerCase().includes(keyword) ||
                site.description.toLowerCase().includes(keyword) ||
                site.link.toLowerCase().includes(keyword)) {
                logMessage(`사이트 제외: ${site.link} 키워드 "${keyword}" 포함`, 'info');
                return true;
            }
        }
        return false;
    }

    /* =============================
       ======== Event Handlers ========
       ============================= */
    startButton.addEventListener('click', () => {
        if (crawling) return;
        crawling = true;
        logMessage('크롤링을 시작합니다.', 'info');
        updateUI();
        crawlPage();
    });

    stopButton.addEventListener('click', () => {
        if (!crawling) return;
        crawling = false;
        logMessage('크롤링을 중지합니다.', 'info');
        updateUI();
    });

    resetButton.addEventListener('click', () => {
        crawling = false;
        resetState();
        displayResults();
        updateProgressBar();
        updateDetails();
    });

    showButton.addEventListener('click', () => {
        resultsWindow.style.display = 'block';
    });

    closeResultsButton.addEventListener('click', () => {
        resultsWindow.style.display = 'none';
    });

    closeHtmlViewerButton.addEventListener('click', () => {
        htmlViewerWindow.style.display = 'none';
        htmlIframe.srcdoc = '';
    });

    // 블랙리스트 도메인 추가 버튼 이벤트
    addBlacklistDomainBtn.addEventListener('click', () => {
        const domain = addBlacklistDomainInput.value.trim();
        if (domain && !blacklistDomains.includes(domain)) {
            blacklistDomains.push(domain);
            GM_setValue('blacklistDomains', blacklistDomains);
            const listItem = document.createElement('li');
            listItem.textContent = domain;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                blacklistDomains = blacklistDomains.filter(d => d !== domain);
                GM_setValue('blacklistDomains', blacklistDomains);
                blacklistDomainsList.removeChild(listItem);
                logMessage(`블랙리스트 도메인 제거: ${domain}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            blacklistDomainsList.appendChild(listItem);
            logMessage(`블랙리스트 도메인 추가: ${domain}`, 'info');
            addBlacklistDomainInput.value = '';
            saveState();
        }
    });

    // 화이트리스트 도메인 추가 버튼 이벤트
    addWhitelistDomainBtn.addEventListener('click', () => {
        const domain = addWhitelistDomainInput.value.trim();
        if (domain && !whitelistDomains.includes(domain)) {
            whitelistDomains.push(domain);
            GM_setValue('whitelistDomains', whitelistDomains);
            const listItem = document.createElement('li');
            listItem.textContent = domain;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                whitelistDomains = whitelistDomains.filter(d => d !== domain);
                GM_setValue('whitelistDomains', whitelistDomains);
                whitelistDomainsList.removeChild(listItem);
                logMessage(`화이트리스트 도메인 제거: ${domain}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            whitelistDomainsList.appendChild(listItem);
            logMessage(`화이트리스트 도메인 추가: ${domain}`, 'info');
            addWhitelistDomainInput.value = '';
            saveState();
        }
    });

    // 광고 도메인 추가 버튼 이벤트
    addAdDomainBtn.addEventListener('click', () => {
        const domain = addAdDomainInput.value.trim();
        if (domain && !adDomains.includes(domain)) {
            adDomains.push(domain);
            GM_setValue('adDomains', adDomains);
            const listItem = document.createElement('li');
            listItem.textContent = domain;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                adDomains = adDomains.filter(d => d !== domain);
                GM_setValue('adDomains', adDomains);
                adDomainsList.removeChild(listItem);
                logMessage(`광고 도메인 제거: ${domain}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            adDomainsList.appendChild(listItem);
            logMessage(`광고 도메인 추가: ${domain}`, 'info');
            addAdDomainInput.value = '';
            saveState();
        }
    });

    // 광고 키워드 추가 버튼 이벤트
    addAdKeywordBtn.addEventListener('click', () => {
        const keyword = addAdKeywordInput.value.trim().toLowerCase();
        if (keyword && !adKeywords.includes(keyword)) {
            adKeywords.push(keyword);
            GM_setValue('adKeywords', adKeywords);
            const listItem = document.createElement('li');
            listItem.textContent = keyword;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                adKeywords = adKeywords.filter(k => k !== keyword);
                GM_setValue('adKeywords', adKeywords);
                adKeywordsList.removeChild(listItem);
                logMessage(`광고 키워드 제거: ${keyword}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            adKeywordsList.appendChild(listItem);
            logMessage(`광고 키워드 추가: ${keyword}`, 'info');
            addAdKeywordInput.value = '';
            saveState();
        }
    });

    // 키워드 기반 제외 추가 버튼 이벤트
    addExcludeKeywordBtn.addEventListener('click', () => {
        const keyword = addExcludeKeywordInput.value.trim().toLowerCase();
        if (keyword && !excludeKeywords.includes(keyword)) {
            excludeKeywords.push(keyword);
            GM_setValue('excludeKeywords', excludeKeywords);
            const listItem = document.createElement('li');
            listItem.textContent = keyword;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                excludeKeywords = excludeKeywords.filter(k => k !== keyword);
                GM_setValue('excludeKeywords', excludeKeywords);
                excludeKeywordsList.removeChild(listItem);
                logMessage(`제외 키워드 제거: ${keyword}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            excludeKeywordsList.appendChild(listItem);
            logMessage(`제외 키워드 추가: ${keyword}`, 'info');
            addExcludeKeywordInput.value = '';
            saveState();
        }
    });

    // 검색 필드 이벤트
    searchInput.addEventListener('input', filterTable);
    tableSearchInput.addEventListener('input', filterTableByKeyword);

    /* =============================
       ======== Core Functions ========
       ============================= */
    function updateUI() {
        // 버튼 상태 업데이트
        if (crawling) {
            startButton.disabled = true;
            stopButton.disabled = false;
            resetButton.disabled = true;
        } else {
            startButton.disabled = false;
            stopButton.disabled = true;
            resetButton.disabled = false;
        }
        // 진행률 바 업데이트
        updateProgressBar();
        // 상세 진행 상황 업데이트
        updateDetails();
        // 상태 저장
        saveState();
    }

    async function crawlPage() {
        if (!crawling) {
            logMessage('크롤링이 중지되었습니다.', 'info');
            updateUI();
            return;
        }
        if (currentPage > maxPages) {
            crawling = false;
            logMessage('최대 페이지를 모두 크롤링했습니다.', 'info');
            updateUI();
            displayResults();
            return;
        }

        logMessage(`페이지 ${currentPage} 크롤링 시작`, 'info');
        updateProgressBar();
        updateDetails();

        // 사이트 정보 추출
        extractSites();

        // 다음 페이지로 이동
        currentPage++;
        saveState();
        navigateToNextPage();
    }

    function extractSites() {
        // 구글 검색 결과 선택자
        const siteElements = document.querySelectorAll('div.g');

        siteElements.forEach(el => {
            const titleElement = el.querySelector('h3');
            const linkElement = el.querySelector('a');
            const descriptionElement = el.querySelector('.IsZvec');

            const title = titleElement ? titleElement.innerText : '제목 없음';
            const link = linkElement ? linkElement.href : '주소 없음';
            const description = descriptionElement ? descriptionElement.innerText : '설명 없음';

            const site = { title, link, description };

            // 키워드를 기반으로 한 제외 검사
            if (shouldExclude(site)) {
                return;
            }

            // 광고 제외 필터
            if (adsFilterToggle.checked && !filterAds(site)) {
                logMessage(`광고 사이트 제외: ${link}`, 'info');
                return;
            }

            // 콘텐츠 품질 평가
            evaluateContentQuality(site);

            // 키워드 분류
            classifyKeywords(site);

            // HTML 수집 옵션
            if (htmlFetchToggle.checked) {
                fetchSiteHTML(site);
            }

            // 중복 검사
            if (processedUrls.has(link)) {
                logMessage(`중복된 사이트 발견: ${link} (무시됨)`, 'info');
                return;
            }

            results.push(site);
            processedUrls.add(link);
        });

        displayResults();
    }

    function fetchSiteHTML(site) {
        GM_xmlhttpRequest({
            method: "GET",
            url: site.link,
            onload: function(response) {
                if (response.status === 200) {
                    site.html = response.responseText;
                    logMessage(`사이트 HTML 수집 성공: ${site.link}`, 'info');
                } else {
                    site.html = null;
                    logMessage(`사이트 HTML 수집 실패 (${response.status}): ${site.link}`, 'error');
                }
                displayResults();
            },
            onerror: function(err) {
                site.html = null;
                logMessage(`사이트 HTML 수집 오류: ${site.link}`, 'error');
                displayResults();
            }
        });
    }

    function navigateToNextPage() {
        const nextButton = document.querySelector('#pnnext');
        if (nextButton) {
            nextButton.click();
            logMessage('다음 페이지로 이동 중...', 'info');
            // 페이지가 로드될 때까지 대기
            const observer = new MutationObserver(() => {
                if (document.querySelector('#search')) {
                    logMessage(`페이지 ${currentPage} 로드 완료`, 'info');
                    observer.disconnect();
                    crawlPage();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        } else {
            crawling = false;
            logMessage('다음 페이지 버튼을 찾을 수 없습니다. 크롤링 종료.', 'info');
            updateUI();
            displayResults();
        }
    }

    /* =============================
       ======== Content Quality ======
       ============================= */
    function evaluateContentQuality(site) {
        if (!site.description) {
            site.score = 0;
            return;
        }
        const wordCount = site.description.split(/\s+/).length;
        let score = 0;
        if (wordCount > 100) score += 2;
        if (wordCount > 200) score += 3;
        site.score = score;
        logMessage(`사이트 점수 평가: ${site.link} - ${score} 🗨️`, 'info');
    }

    /* =============================
       ======== Keyword Classification ======
       ============================= */
    function classifyKeywords(site) {
        if (!site.description) {
            site.category = 'Uncategorized';
            return;
        }
        const description = site.description.toLowerCase();
        const categories = {
            'News': ['news', 'breaking', 'update'],
            'Technology': ['technology', 'tech', 'software', 'hardware'],
            'Entertainment': ['entertainment', 'movie', 'music', 'game'],
            'Health': ['health', 'medicine', 'wellness'],
            'Finance': ['finance', 'stock', 'investment']
        };
        for (let category in categories) {
            for (let keyword of categories[category]) {
                if (description.includes(keyword)) {
                    site.category = category;
                    logMessage(`사이트 분류: ${site.link} - 🗂️ ${category}`, 'info');
                    return;
                }
            }
        }
        site.category = 'Uncategorized';
    }

    /* =============================
       ======== Exclude Keywords ======
       ============================= */
    function shouldExclude(site) {
        for (let keyword of excludeKeywords) {
            if (site.title.toLowerCase().includes(keyword) ||
                site.description.toLowerCase().includes(keyword) ||
                site.link.toLowerCase().includes(keyword)) {
                logMessage(`사이트 제외: ${site.link} 키워드 "${keyword}" 포함`, 'info');
                return true;
            }
        }
        return false;
    }

    /* =============================
       ======== Table Filtering ======
       ============================= */
    function filterTable() {
        const filter = searchInput.value.toLowerCase();
        const rows = dataBody.getElementsByTagName('tr');
        for (let row of rows) {
            const cells = row.getElementsByTagName('td');
            let match = false;
            for (let cell of cells) {
                if (cell.textContent.toLowerCase().includes(filter)) {
                    match = true;
                    break;
                }
            }
            row.style.display = match ? '' : 'none';
        }
    }

    function filterTableByKeyword() {
        const filter = tableSearchInput.value.toLowerCase();
        const rows = dataBody.getElementsByTagName('tr');
        for (let row of rows) {
            const cells = row.getElementsByTagName('td');
            let match = false;
            for (let cell of cells) {
                if (cell.textContent.toLowerCase().includes(filter)) {
                    match = true;
                    break;
                }
            }
            row.style.display = match ? '' : 'none';
        }
    }

    /* =============================
       ======== Initialization =======
       ============================= */
    loadState();
    updateUI();
    displayResults();

    // 블랙리스트, 화이트리스트, 광고 도메인 및 키워드 리스트 초기화
    function initializeLists() {
        // 블랙리스트 도메인
        blacklistDomainsList.innerHTML = '';
        blacklistDomains.forEach(domain => {
            const listItem = document.createElement('li');
            listItem.textContent = domain;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                blacklistDomains = blacklistDomains.filter(d => d !== domain);
                GM_setValue('blacklistDomains', blacklistDomains);
                blacklistDomainsList.removeChild(listItem);
                logMessage(`블랙리스트 도메인 제거: ${domain}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            blacklistDomainsList.appendChild(listItem);
        });

        // 화이트리스트 도메인
        whitelistDomainsList.innerHTML = '';
        whitelistDomains.forEach(domain => {
            const listItem = document.createElement('li');
            listItem.textContent = domain;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                whitelistDomains = whitelistDomains.filter(d => d !== domain);
                GM_setValue('whitelistDomains', whitelistDomains);
                whitelistDomainsList.removeChild(listItem);
                logMessage(`화이트리스트 도메인 제거: ${domain}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            whitelistDomainsList.appendChild(listItem);
        });

        // 광고 도메인
        adDomainsList.innerHTML = '';
        adDomains.forEach(domain => {
            const listItem = document.createElement('li');
            listItem.textContent = domain;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                adDomains = adDomains.filter(d => d !== domain);
                GM_setValue('adDomains', adDomains);
                adDomainsList.removeChild(listItem);
                logMessage(`광고 도메인 제거: ${domain}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            adDomainsList.appendChild(listItem);
        });

        // 광고 키워드
        adKeywordsList.innerHTML = '';
        adKeywords.forEach(keyword => {
            const listItem = document.createElement('li');
            listItem.textContent = keyword;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                adKeywords = adKeywords.filter(k => k !== keyword);
                GM_setValue('adKeywords', adKeywords);
                adKeywordsList.removeChild(listItem);
                logMessage(`광고 키워드 제거: ${keyword}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            adKeywordsList.appendChild(listItem);
        });

        // 제외 키워드
        excludeKeywordsList.innerHTML = '';
        excludeKeywords.forEach(keyword => {
            const listItem = document.createElement('li');
            listItem.textContent = keyword;
            const removeBtn = document.createElement('button');
            removeBtn.textContent = '✖';
            removeBtn.classList.add('remove-btn');
            removeBtn.addEventListener('click', () => {
                excludeKeywords = excludeKeywords.filter(k => k !== keyword);
                GM_setValue('excludeKeywords', excludeKeywords);
                excludeKeywordsList.removeChild(listItem);
                logMessage(`제외 키워드 제거: ${keyword}`, 'info');
                saveState();
            });
            listItem.appendChild(removeBtn);
            excludeKeywordsList.appendChild(listItem);
        });
    }

    initializeLists();

    // 초기 로그 로드
    logs.forEach(entry => {
        const logEntry = document.createElement('div');
        logEntry.textContent = entry;
        logEntry.style.color = entry.includes('[ERROR]') ? 'red' : '#555';
        logWindow.appendChild(logEntry);
    });
    logWindow.scrollTop = logWindow.scrollHeight;

    // 페이지 로드 시 이어서 크롤링
    window.addEventListener('load', () => {
        if (crawling) {
            logMessage('페이지 로드 시 크롤링을 이어갑니다.', 'info');
            crawlPage();
        }
    });

    /* =============================
       ======== Error Handling =======
       ============================= */
    // 에러 발생 시 재시도 로직
    function handleError(retries, delay, func) {
        if (retries <= 0) {
            logMessage('재시도 횟수를 초과했습니다.', 'error');
            return;
        }
        setTimeout(() => {
            try {
                func();
            } catch (error) {
                logMessage(`에러 발생: ${error.message}. 재시도 ${retries - 1}회 남음.`, 'error');
                handleError(retries - 1, delay, func);
            }
        }, delay);
    }

    /* =============================
       ======== Updates and Enhancements ========
       ============================= */
    // 실시간 데이터 업데이트 및 고도화
    // 이 기능은 이미 displayResults 함수에서 구현되었습니다.

})();                                                                            탭 메뉴: 📜 로그, 📊 데이터, ⚙️ 설정의 세 가지 탭으로 구성되어 있습니다.

탭 이벤트: 탭을 클릭하거나 Ctrl+1, Ctrl+2, Ctrl+3 단축키를 사용하여 각 탭으로 전환할 수 있습니다.

스타일: 활성화된 탭은 색상이 다르며, 마우스 호버 시 색상이 변경됩니다.
HTML 내용 수집 및 텍스트 추출
각 검색 결과의 전체 HTML 내용과 주요 텍스트 내용을 비동기로 수집합니다.
수집된 내용을 데이터 테이블의 🗨️ 텍스트 내용과 📄 HTML 내용 열에 표시합니다.
📄 HTML 내용 셀을 클릭하면 모달 창으로 전체 HTML 내용을 확인할 수 있습니다.
툴팁 추가
버튼과 입력 필드에 title 속성을 추가하여 툴팁을 제공합니다. 이를 통해 사용자가 각 요소의 기능을 직관적으로 이해할 수 있습니다.
키보드 단축키 지원
기본 단축키:
Ctrl+S: 설정 저장
Ctrl+P: 크롤링 시작
Ctrl+Q: 크롤링 중지
Ctrl+D: CSV 다운로드
Ctrl+J: JSON 다운로드
Ctrl+1: 로그 탭 활성화
Ctrl+2: 데이터 탭 활성화
Ctrl+3: 설정 탭 활성화


초기화 기능을 더욱 더 더욱 더 고도화해!