// ==UserScript==
// @name         Advanced Educational Web Scraper
// @namespace    http://tampermonkey.net/
// @version      3.1
// @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'],
        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 notify(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 = '350px';
        uiContainer.style.height = '600px';
        uiContainer.style.backgroundColor = 'white';
        uiContainer.style.border = '1px solid #ccc';
        uiContainer.style.padding = '10px';
        uiContainer.style.overflowY = 'scroll';
        uiContainer.style.zIndex = '10000';
        uiContainer.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        uiContainer.style.fontFamily = 'Arial, sans-serif';
        uiContainer.style.color = '#333';
        uiContainer.innerHTML = `
            <h3>크롤러 상태</h3>
            <div id="progress">진행률: 0% (0/${state.maxPages * 10})</div>
            <div id="currentPage">현재 페이지: ${state.currentPage}</div>
            <div id="estimatedTime">예상 완료 시간: 계산 중...</div>
            <button id="startCrawl" aria-label="크롤링 시작">크롤링 시작</button>
            <button id="stopCrawl" aria-label="크롤링 중지">크롤링 중지</button>
            <button id="downloadCSV" aria-label="CSV 데이터 다운로드" style="margin-top: 10px;">데이터 다운로드 (CSV)</button>
            <button id="downloadJSON" aria-label="JSON 데이터 다운로드" style="margin-top: 10px;">데이터 다운로드 (JSON)</button>
            <h4>로그</h4>
            <div id="log" style="height: 150px; overflow-y: scroll; border: 1px solid #ddd; padding: 5px; background: #f9f9f9;"></div>
            <h4>수집된 데이터</h4>
            <input type="text" id="tableFilter" placeholder="검색어 입력..." style="width: 100%; padding: 5px; margin-bottom: 5px;">
            <table id="dataTable" border="1" style="width: 100%; border-collapse: collapse;">
                <thead>
                    <tr>
                        <th>사이트 이름</th>
                        <th>주소</th>
                        <th>설명</th>
                    </tr>
                </thead>
                <tbody></tbody>
            </table>
            <h4>설정</h4>
            <label for="pageSelector">크롤링할 페이지 수:</label>
            <select id="pageSelector" style="width: 100%; padding: 5px;">
                ${generatePageOptions(state.maxPages)}
            </select><br>
            <label for="delay">요청 간 지연 시간 (ms):</label>
            <input type="number" id="delay" value="${state.delay}" min="500" max="10000" style="width: 100%; padding: 5px;"><br>
            <label for="filterKeywords">필터링 키워드 (쉼표로 구분):</label>
            <input type="text" id="filterKeywords" value="${state.filterKeywords.join(', ')}" style="width: 100%; padding: 5px;"><br>
            <label for="excludedDomains">제외할 도메인 (쉼표로 구분):</label>
            <input type="text" id="excludedDomains" value="${state.excludedDomains.join(', ')}" style="width: 100%; padding: 5px;"><br>
            <button id="saveSettings" style="margin-top: 5px;">설정 저장</button>
            <h4>데이터 시각화</h4>
            <canvas id="dataChart" width="300" height="200"></canvas>
        `;
        document.body.appendChild(uiContainer);

        // 버튼 스타일링
        const buttons = uiContainer.querySelectorAll('button');
        buttons.forEach(button => {
            button.style.padding = '5px 10px';
            button.style.margin = '5px 0';
            button.style.border = 'none';
            button.style.borderRadius = '3px';
            button.style.backgroundColor = '#4CAF50';
            button.style.color = 'white';
            button.style.cursor = 'pointer';
            button.style.width = '100%';
        });

        // Chart.js 추가
        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 로드에 실패했습니다. 데이터 시각화를 사용할 수 없습니다.');
            };
            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: [],
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    borderColor: 'rgba(75, 192, 192, 1)',
                    borderWidth: 1
                }]
            },
            options: {
                scales: {
                    y: { beginAtZero: true }
                }
            }
        });
        // 초기 데이터 추가
        updateChart();
    }

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

    /*** 6. 로그 함수 ***/
    function log(message) {
        const time = new Date().toLocaleTimeString();
        const fullMessage = `[${time}] ${message}<br>`;
        const logElement = document.getElementById('log');
        logElement.innerHTML += fullMessage;
        logElement.scrollTop = logElement.scrollHeight;
        state.log += fullMessage;
        GM_setValue(STORAGE_KEY, state);
    }

    /*** 7. 진행률 및 상태 업데이트 함수 ***/
    function updateProgress(current, total) {
        const percent = total === 0 ? 0 : Math.floor((current / total) * 100);
        document.getElementById('progress').innerText = `진행률: ${percent}% (${current}/${total})`;
    }

    function updateStatus() {
        const currentPageElement = document.getElementById('currentPage');
        const estimatedTimeElement = document.getElementById('estimatedTime');
        currentPageElement.innerText = `현재 페이지: ${state.currentPage}`;

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

    /*** 8. 데이터 추가 함수 (중복 제거 포함) ***/
    function addData(name, url, description) {
        // 중복 데이터 확인
        if (state.visitedURLs.includes(url)) {
            log(`이미 존재하는 데이터: ${url}`);
            return;
        }

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

        // 데이터 수집
        state.visitedURLs.push(url);
        state.collectedData.push({ name, url, description });
        GM_setValue(STORAGE_KEY, state);

        // 테이블에 데이터 추가
        const dataTableBody = document.querySelector('#dataTable tbody');
        const row = document.createElement('tr');
        row.innerHTML = `
            <td>${name}</td>
            <td><a href="${url}" target="_blank">${url}</a></td>
            <td>${description}</td>
        `;
        dataTableBody.appendChild(row);

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

    /*** 9. 광고 및 제외 도메인 필터링 함수 ***/
    function isAdSite(description, url) {
        // 키워드 필터링
        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');
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        results.push({ name, url, description });
                    }
                });
                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');
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        results.push({ name, url, description });
                    }
                });
                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');
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        results.push({ name, url, description });
                    }
                });
                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');
                    if (titleElement && linkElement && descriptionElement) {
                        const name = titleElement.innerText;
                        const url = linkElement.href;
                        const description = descriptionElement.innerText;
                        results.push({ name, url, description });
                    }
                });
                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());

        state.maxPages = maxPages;
        state.delay = delay;
        state.filterKeywords = filterKeywords;
        state.excludedDomains = excludedDomains;
        GM_setValue(STORAGE_KEY, state);

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

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

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

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

            // 데이터 처리
            searchResults.forEach(result => {
                addData(result.name, result.url, result.description);
            });

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

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

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

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

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

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

    /*** 15. 탐색 단계 시각화 ***/
    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);
        }
    }

    /*** 16. 데이터 정렬 및 필터링 ***/
    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} 기준으로 정렬되었습니다.`);
    }

    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';
        });
    }

    /*** 17. 프록시 및 사용자 에이전트 변경 (제한적 구현) ***/
    // Tampermonkey 스크립트에서는 브라우저 전체의 User-Agent를 변경할 수 없지만, HTTP 요청 시 헤더를 설정할 수 있습니다.
    // 그러나 DOM 기반 크롤링 방식에서는 User-Agent 변경이 어렵습니다.
    // 필요 시, GM_xmlhttpRequest를 활용한 별도 요청 방식을 고려할 수 있습니다.

    /*** 18. robots.txt 확인 함수 (제거됨) ***/
    // robots.txt 검사를 제거하여 크롤링이 항상 가능하도록 했습니다.
    // **주의**: 이로 인해 웹사이트의 크롤링 정책을 무시하게 되므로, 법적 문제가 발생할 수 있습니다. 반드시 **교육 목적** 및 **허용된 웹사이트**에서만 사용하세요.

    /*** 19. 반응형 디자인 (CSS 미디어 쿼리) ***/
    function addResponsiveStyles() {
        const style = document.createElement('style');
        style.innerHTML = `
            @media (max-width: 600px) {
                #advanced-web-scraper-ui {
                    width: 90% !important;
                    right: 5% !important;
                    height: auto !important;
                    max-height: 80% !important;
                }
                table, th, td {
                    font-size: 12px !important;
                }
                canvas {
                    width: 100% !important;
                    height: auto !important;
                }
            }
        `;
        document.head.appendChild(style);
    }

    /*** 20. 알림 기능 추가 ***/
    // 이미 notify 함수로 구현됨. 크롤링 완료, 오류 발생 시 알림을 제공.

    /*** 21. CSV 및 JSON 다운로드 기능 ***/
    function downloadCSV() {
        if (state.collectedData.length === 0) {
            log('다운로드할 데이터가 없습니다.');
            return;
        }

        let csvContent = "data:text/csv;charset=utf-8,";
        csvContent += "사이트 이름,주소,설명\n";
        state.collectedData.forEach(row => {
            const rowData = `"${row.name.replace(/"/g, '""')}", "${row.url}", "${row.description.replace(/"/g, '""')}"`;
            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 파일이 다운로드되었습니다.');
    }

    function downloadJSON() {
        if (state.collectedData.length === 0) {
            log('다운로드할 데이터가 없습니다.');
            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 파일이 다운로드되었습니다.');
    }

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

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

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

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

        // 기존 데이터 표시
        state.collectedData.forEach(item => {
            const dataTableBody = document.querySelector('#dataTable tbody');
            const row = document.createElement('tr');
            row.innerHTML = `
                <td>${item.name}</td>
                <td><a href="${item.url}" target="_blank">${item.url}</a></td>
                <td>${item.description}</td>
            `;
            dataTableBody.appendChild(row);
        });

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

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

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

    /*** 27. 이벤트 리스너 설정 ***/
    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');

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

    /*** 28. 초기 실행 ***/
    window.addEventListener('load', () => {
        initializeUIComponents();
        setupEventListeners();
    });

})();
                 여기 코드를 분석하고 여기에 알맞은 확장 기능 60가지를 추천해줘