YouTube Last Watched Video Tracker

Track and find your last watched video on YouTube subscriptions page. Drag the green box to mark videos, with automatic search and history backup.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube Last Watched Video Tracker
// @namespace    https://greasyfork.org/users/1513610
// @version      1.0.0
// @description  Track and find your last watched video on YouTube subscriptions page. Drag the green box to mark videos, with automatic search and history backup.
// @author       NAABO
// @match        https://www.youtube.com/feed/subscriptions*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// @license      MIT
// ==/UserScript==

/*
 * YouTube Last Watched Video Tracker
 *
 * HOW TO USE:
 * 1. Go to YouTube subscriptions page
 * 2. You'll see a green "DRAG TO MARK VIDEO" box
 * 3. Drag it onto any video to mark as your last watched
 * 4. When you return, the script will automatically find and highlight that video
 * 5. Check browser console (F12) to see your video history
 *
 * FEATURES:
 * - Automatic video search with smart scrolling
 * - Video history backup (last 10 videos)
 * - Drag and drop interface
 * - Cancellable operations
 * - Accessibility support
 *
 * PRIVACY:
 * - All data stored locally in your browser
 * - No external requests or tracking
 * - No personal data collection
 */

(function() {
    'use strict';

    // ==================== CONFIGURATION ====================
    const CONFIG = {
        SCROLL_DELAY_BASE: 800,
        SCROLL_DELAY_MIN: 400,
        SCROLL_DELAY_MAX: 1200,
        SCROLL_STEP: 500,
        VISIBILITY_CHECK_INTERVAL: 500,
        MAX_SEARCH_ATTEMPTS: 200,
        NOTIFICATION_DURATION: 3000,
        DEBOUNCE_DELAY: 300,
        ANIMATION_DURATION: 300,
        DROP_DETECTION_DELAY: 50,
        MAX_HISTORY_SIZE: 10,
        CONTENT_LOAD_TIMEOUT: 30000, // 30 seconds timeout for content loading
        RETRY_ATTEMPTS: 3
    };

    const SELECTORS = {
        VIDEO_CONTAINER: 'ytd-rich-item-renderer',
        VIDEO_LINK: 'a[href*="/watch?v="]',
        SUBSCRIPTIONS_PATH: '/feed/subscriptions'
    };

    const STYLES = {
        SUCCESS: { bg: 'linear-gradient(135deg, #00ff41, #00cc33)', color: '#000', shadow: '0 4px 20px rgba(0, 255, 65, 0.4)' },
        ERROR: { bg: 'linear-gradient(135deg, #ff4757, #ff3742)', color: '#fff', shadow: '0 4px 20px rgba(255, 71, 87, 0.4)' },
        INFO: { bg: 'linear-gradient(135deg, #3742fa, #2f3542)', color: '#fff', shadow: '0 4px 20px rgba(55, 66, 250, 0.4)' }
    };

    const VERSION = '1.0.0';

    // ==================== UTILITY CLASSES ====================
    class Logger {
        static log(msg) {
            console.log(`[YT Tracker v${VERSION}] ${new Date().toLocaleTimeString()} - ${msg}`);
        }

        static warn(msg) {
            console.warn(`[YT Tracker v${VERSION}] ${new Date().toLocaleTimeString()} - ${msg}`);
        }

        static error(msg) {
            console.error(`[YT Tracker v${VERSION}] ${new Date().toLocaleTimeString()} - ${msg}`);
        }

        static welcome() {
            console.group(`🎥 YouTube Last Watched Video Tracker v${VERSION}`);
            console.log('✅ Script loaded successfully!');
            console.log('📖 How to use:');
            console.log('   1. Drag the green box onto any video to mark as last watched');
            console.log('   2. Script will automatically find that video when you return');
            console.log('   3. Your last 3 videos are shown here as backup');
            console.log('🔒 Privacy: All data stored locally, no tracking');
            console.groupEnd();
        }

        static history(videos) {
            if (videos.length === 0) {
                console.log('📺 No video history yet. Drag the green box onto a video to start tracking!');
                return;
            }

            console.group('📺 Last 3 Watched Videos History');
            videos.forEach((video, index) => {
                const timeAgo = this._getTimeAgo(video.timestamp);
                console.log(`${index + 1}. Video ID: ${video.id} | ${timeAgo} | https://youtube.com/watch?v=${video.id}`);
            });
            console.groupEnd();
        }

        static _getTimeAgo(timestamp) {
            const now = Date.now();
            const diff = now - timestamp;
            const minutes = Math.floor(diff / 60000);
            const hours = Math.floor(diff / 3600000);
            const days = Math.floor(diff / 86400000);

            if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
            if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
            if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
            return 'Just now';
        }
    }

    class Utils {
        static debounce(func, delay) {
            let timeoutId;
            return (...args) => {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(() => func.apply(this, args), delay);
            };
        }

        static throttle(func, delay) {
            let lastExecution = 0;
            return (...args) => {
                const now = Date.now();
                if (now - lastExecution >= delay) {
                    lastExecution = now;
                    return func.apply(this, args);
                }
            };
        }

        static getVideoId(url) {
            if (!url || typeof url !== 'string') return null;
            const match = url.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
            return match ? match[1] : null;
        }

        static isSubscriptionsPage() {
            return location.href.includes(SELECTORS.SUBSCRIPTIONS_PATH);
        }

        static async sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        static safeJsonParse(json, fallback = null) {
            try {
                return JSON.parse(json);
            } catch (error) {
                Logger.warn(`JSON parse failed: ${error.message}`);
                return fallback;
            }
        }

        static safeJsonStringify(obj, fallback = '[]') {
            try {
                return JSON.stringify(obj);
            } catch (error) {
                Logger.warn(`JSON stringify failed: ${error.message}`);
                return fallback;
            }
        }
    }

    // ==================== ENHANCED STORAGE MANAGER ====================
    class StorageManager {
        static save(key, value) {
            try {
                GM_setValue(key, value);
                Logger.log(`Saved ${key}: ${value}`);
                return true;
            } catch (error) {
                Logger.error(`Save failed for ${key}: ${error.message}`);
                return false;
            }
        }

        static get(key, defaultValue = null) {
            try {
                const value = GM_getValue(key, defaultValue);
                return value;
            } catch (error) {
                Logger.error(`Get failed for ${key}: ${error.message}`);
                return defaultValue;
            }
        }

        static saveVideo(videoId) {
            if (!videoId || typeof videoId !== 'string') {
                Logger.error('Invalid video ID provided');
                return false;
            }

            try {
                // Save current video (backward compatibility)
                this.save('lastVideo', videoId);

                // Get existing history
                let history = this.getVideoHistory();

                // Remove if already exists (to avoid duplicates)
                history = history.filter(video => video.id !== videoId);

                // Add new video at the beginning
                history.unshift({
                    id: videoId,
                    timestamp: Date.now(),
                    url: `https://youtube.com/watch?v=${videoId}`
                });

                // Limit history size
                if (history.length > CONFIG.MAX_HISTORY_SIZE) {
                    history = history.slice(0, CONFIG.MAX_HISTORY_SIZE);
                }

                // Save updated history
                const historyJson = Utils.safeJsonStringify(history);
                this.save('videoHistory', historyJson);

                // Display recent history in console
                this.displayRecentHistory();

                return true;
            } catch (error) {
                Logger.error(`Failed to save video with history: ${error.message}`);
                return false;
            }
        }

        static getSavedVideo() {
            return this.get('lastVideo');
        }

        static getVideoHistory() {
            const historyJson = this.get('videoHistory', '[]');
            const history = Utils.safeJsonParse(historyJson, []);

            // Validate history entries
            return history.filter(video =>
                video &&
                typeof video === 'object' &&
                video.id &&
                typeof video.id === 'string' &&
                video.timestamp &&
                typeof video.timestamp === 'number'
            );
        }

        static displayRecentHistory() {
            const history = this.getVideoHistory();
            const recent = history.slice(0, 3);
            Logger.history(recent);
        }

        static getVideoFromHistory(index) {
            const history = this.getVideoHistory();
            return history[index] || null;
        }

        // Migration for existing users
        static migrateToHistoryFormat() {
            try {
                const existingVideo = this.getSavedVideo();
                const existingHistory = this.getVideoHistory();

                if (existingVideo && existingHistory.length === 0) {
                    const historyEntry = {
                        id: existingVideo,
                        timestamp: Date.now() - (24 * 60 * 60 * 1000), // Set as 1 day ago
                        url: `https://youtube.com/watch?v=${existingVideo}`
                    };

                    const historyJson = Utils.safeJsonStringify([historyEntry]);
                    this.save('videoHistory', historyJson);
                    Logger.log(`Migrated existing video ${existingVideo} to history format`);
                    return true;
                }
                return false;
            } catch (error) {
                Logger.error(`Migration failed: ${error.message}`);
                return false;
            }
        }

        // Clear all data (for troubleshooting)
        static clearAllData() {
            try {
                this.save('lastVideo', '');
                this.save('videoHistory', '[]');
                Logger.log('All data cleared');
                return true;
            } catch (error) {
                Logger.error(`Clear data failed: ${error.message}`);
                return false;
            }
        }
    }

    // ==================== NOTIFICATION SYSTEM ====================
    class NotificationManager {
        constructor() {
            this.notifications = new WeakMap();
            this.searchNotification = null;
        }

        show(text, type = 'success') {
            if (!text || typeof text !== 'string') return;

            this._removeExistingNotification();

            const notification = this._createNotification(text, type);
            if (!notification) return;

            document.body.appendChild(notification);

            requestAnimationFrame(() => {
                notification.style.opacity = '1';
                notification.style.transform = 'translateX(-50%) translateY(0)';
            });

            setTimeout(() => this._hideNotification(notification), CONFIG.NOTIFICATION_DURATION);
        }

        showSearch(text, progress = 0) {
            if (!text || typeof text !== 'string') return;

            if (this.searchNotification) {
                this._updateSearchNotification(text, progress);
                return;
            }

            this.searchNotification = this._createSearchNotification(text, progress);
            if (this.searchNotification) {
                document.body.appendChild(this.searchNotification);
            }
        }

        hideSearch() {
            if (this.searchNotification) {
                this._hideNotification(this.searchNotification);
                this.searchNotification = null;
            }
        }

        _removeExistingNotification() {
            const existing = document.querySelector('.yt-tracker-notif');
            if (existing) existing.remove();
        }

        _createNotification(text, type) {
            try {
                const style = STYLES[type.toUpperCase()] || STYLES.SUCCESS;
                const notification = document.createElement('div');

                notification.className = 'yt-tracker-notif';
                notification.textContent = text;
                notification.setAttribute('role', 'alert');
                notification.setAttribute('aria-live', 'polite');

                notification.style.cssText = `
                    position: fixed !important;
                    top: 20px !important;
                    left: 50% !important;
                    transform: translateX(-50%) translateY(-20px) !important;
                    background: ${style.bg} !important;
                    color: ${style.color} !important;
                    padding: 12px 24px !important;
                    border-radius: 25px !important;
                    z-index: 999999 !important;
                    font-weight: bold !important;
                    font-size: 14px !important;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif !important;
                    box-shadow: ${style.shadow} !important;
                    border: 1px solid rgba(255, 255, 255, 0.1) !important;
                    backdrop-filter: blur(10px) !important;
                    transition: all ${CONFIG.ANIMATION_DURATION}ms ease !important;
                    opacity: 0 !important;
                    max-width: 400px !important;
                    word-wrap: break-word !important;
                `;

                return notification;
            } catch (error) {
                Logger.error(`Failed to create notification: ${error.message}`);
                return null;
            }
        }

        _createSearchNotification(text, progress) {
            try {
                const notification = document.createElement('div');
                notification.className = 'yt-tracker-search-notif';
                notification.setAttribute('role', 'dialog');
                notification.setAttribute('aria-label', 'Search Progress');

                notification.style.cssText = `
                    position: fixed !important;
                    top: 20px !important;
                    left: 50% !important;
                    transform: translateX(-50%) !important;
                    background: linear-gradient(135deg, #1e3799, #0c2461) !important;
                    color: #ffffff !important;
                    padding: 18px 28px 22px 28px !important;
                    border-radius: 25px !important;
                    z-index: 999999 !important;
                    font-weight: bold !important;
                    font-size: 14px !important;
                    box-shadow: 0 8px 32px rgba(30, 55, 153, 0.4) !important;
                    display: flex !important;
                    align-items: center !important;
                    gap: 15px !important;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif !important;
                    border: 1px solid rgba(255, 255, 255, 0.1) !important;
                    backdrop-filter: blur(10px) !important;
                    min-width: 320px !important;
                    max-width: 500px !important;
                    flex-direction: column !important;
                `;

                notification.innerHTML = this._getSearchNotificationHTML(text, progress);
                this._attachCancelHandler(notification);

                return notification;
            } catch (error) {
                Logger.error(`Failed to create search notification: ${error.message}`);
                return null;
            }
        }

        _getSearchNotificationHTML(text, progress) {
            const clampedProgress = Math.min(Math.max(progress, 0), 100);
            return `
                <div style="display: flex !important; align-items: center !important; gap: 15px !important; width: 100% !important;">
                    <span style="flex: 1 !important;">${text}</span>
                    <button class="cancel-search-btn" style="
                        background: rgba(255, 87, 87, 0.2) !important;
                        border: 2px solid rgba(255, 87, 87, 0.6) !important;
                        color: #ff5757 !important;
                        font-weight: bold !important;
                        font-size: 16px !important;
                        width: 28px !important;
                        height: 28px !important;
                        border-radius: 50% !important;
                        cursor: pointer !important;
                        display: flex !important;
                        align-items: center !important;
                        justify-content: center !important;
                        padding: 0 !important;
                        line-height: 1 !important;
                        transition: all 0.2s ease !important;
                        flex-shrink: 0 !important;
                    " title="Cancel search" aria-label="Cancel search">×</button>
                </div>
                <div style="
                    width: 100% !important;
                    height: 4px !important;
                    background: rgba(255, 255, 255, 0.1) !important;
                    border-radius: 2px !important;
                    overflow: hidden !important;
                    margin-top: 8px !important;
                ">
                    <div class="progress-bar" style="
                        height: 100% !important;
                        background: linear-gradient(90deg, #00ff41, #00cc33) !important;
                        border-radius: 2px !important;
                        transition: width 0.3s ease !important;
                        width: ${clampedProgress}% !important;
                        box-shadow: 0 0 8px rgba(0, 255, 65, 0.6) !important;
                    "></div>
                </div>
            `;
        }

        _attachCancelHandler(notification) {
            if (!notification) return;

            const cancelBtn = notification.querySelector('.cancel-search-btn');
            if (cancelBtn) {
                cancelBtn.addEventListener('click', () => {
                    this.hideSearch();
                    window.dispatchEvent(new CustomEvent('ytTrackerSearchCancel'));
                });

                cancelBtn.addEventListener('mouseenter', () => {
                    cancelBtn.style.background = 'rgba(255, 87, 87, 0.4) !important';
                });

                cancelBtn.addEventListener('mouseleave', () => {
                    cancelBtn.style.background = 'rgba(255, 87, 87, 0.2) !important';
                });
            }
        }

        _updateSearchNotification(text, progress) {
            if (!this.searchNotification) return;

            const textSpan = this.searchNotification.querySelector('span');
            const progressBar = this.searchNotification.querySelector('.progress-bar');

            if (textSpan) textSpan.textContent = text;
            if (progressBar) {
                const clampedProgress = Math.min(Math.max(progress, 0), 100);
                progressBar.style.width = `${clampedProgress}%`;
            }
        }

        _hideNotification(notification) {
            if (!notification || !notification.parentNode) return;

            notification.style.opacity = '0';
            notification.style.transform = notification.classList.contains('yt-tracker-search-notif')
                ? 'translateX(-50%) translateY(-20px)'
                : 'translateX(-50%) translateY(-40px)';

            setTimeout(() => {
                if (notification.parentNode) {
                    notification.remove();
                }
            }, CONFIG.ANIMATION_DURATION);
        }
    }

    // ==================== DRAG BOX COMPONENT ====================
    class DragBox {
        constructor() {
            this.element = null;
            this.isDragging = false;
            this.visibilityTimer = null;
            this.boundHandlers = new Map();
            this.isPositioned = false;
            this.retryCount = 0;

            this._createStyleSheet();
        }

        create() {
            try {
                this._removeExisting();

                this.element = document.createElement('div');
                this.element.id = 'yt-tracker-box';
                this.element.setAttribute('role', 'button');
                this.element.setAttribute('aria-label', 'Drag to mark video as last watched');
                this.element.setAttribute('tabindex', '0');

                this._applyInitialStyles();
                this._setContent('DRAG TO<br>MARK VIDEO');

                document.body.appendChild(this.element);
                this._setupEventListeners();
                this._startVisibilityGuard();

                this.isPositioned = false;
                this.retryCount = 0;
                Logger.log('Drag box created successfully');
                return true;
            } catch (error) {
                Logger.error(`Failed to create drag box: ${error.message}`);
                this._retryCreate();
                return false;
            }
        }

        _retryCreate() {
            if (this.retryCount < CONFIG.RETRY_ATTEMPTS) {
                this.retryCount++;
                Logger.log(`Retrying drag box creation (attempt ${this.retryCount}/${CONFIG.RETRY_ATTEMPTS})`);
                setTimeout(() => this.create(), 1000 * this.retryCount);
            } else {
                Logger.error('Failed to create drag box after all retry attempts');
            }
        }

        destroy() {
            try {
                this._stopVisibilityGuard();
                this._removeEventListeners();

                if (this.element && this.element.parentNode) {
                    this.element.remove();
                }

                this.element = null;
                this.isPositioned = false;
                Logger.log('Drag box destroyed');
            } catch (error) {
                Logger.error(`Failed to destroy drag box: ${error.message}`);
            }
        }

        resetToFloating() {
            if (!this.element) return;

            try {
                Logger.log('Resetting drag box to floating state');

                this.isPositioned = false;
                this.element.classList.remove('yt-tracker-pulsing');

                this._applyInitialStyles();
                this._setContent('DRAG TO<br>MARK VIDEO');

                this.element.style.transition = 'all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)';

                setTimeout(() => {
                    if (this.element) {
                        this.element.style.transition = 'all 0.3s ease';
                    }
                }, 500);
            } catch (error) {
                Logger.error(`Failed to reset drag box: ${error.message}`);
            }
        }

        positionOnVideo(container) {
            if (!this.element || !container) return;

            try {
                const rect = container.getBoundingClientRect();
                const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
                const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;

                this.element.style.position = 'absolute';
                this.element.style.left = (rect.left + scrollLeft + 10) + 'px';
                this.element.style.top = (rect.top + scrollTop + 10) + 'px';
                this.element.style.right = 'auto';

                this._updateForPositioned();
                this._setContent('✓ LAST<br>WATCHED');

                this.isPositioned = true;
                Logger.log('Box positioned on video successfully');
            } catch (error) {
                Logger.error(`Failed to position drag box: ${error.message}`);
            }
        }

        _createStyleSheet() {
            if (document.querySelector('#yt-tracker-styles')) return;

            try {
                const style = document.createElement('style');
                style.id = 'yt-tracker-styles';
                style.textContent = `
                    @keyframes yt-tracker-pulse {
                        0% {
                            box-shadow: 0 0 15px #00ff00, 0 0 30px rgba(0, 255, 0, 0.4);
                            transform: scale(1);
                        }
                        50% {
                            box-shadow: 0 0 25px #00ff00, 0 0 50px rgba(0, 255, 0, 0.6);
                            transform: scale(1.02);
                        }
                        100% {
                            box-shadow: 0 0 15px #00ff00, 0 0 30px rgba(0, 255, 0, 0.4);
                            transform: scale(1);
                        }
                    }

                    .yt-tracker-pulsing {
                        animation: yt-tracker-pulse 2s ease-in-out infinite !important;
                    }

                    @keyframes yt-tracker-glow {
                        0%, 100% { text-shadow: 0 0 5px #00ff00; }
                        50% { text-shadow: 0 0 15px #00ff00, 0 0 25px rgba(0, 255, 0, 0.8); }
                    }

                    .yt-tracker-glow-text {
                        animation: yt-tracker-glow 1.5s ease-in-out infinite !important;
                    }

                    #yt-tracker-box:focus {
                        outline: 2px solid #00ff41 !important;
                        outline-offset: 2px !important;
                    }

                    #yt-tracker-box:hover {
                        opacity: 0.9 !important;
                        transform: scale(1.05) !important;
                    }
                `;
                document.head.appendChild(style);
            } catch (error) {
                Logger.error(`Failed to create stylesheet: ${error.message}`);
            }
        }

        _removeExisting() {
            const existing = document.querySelector('#yt-tracker-box');
            if (existing) existing.remove();
        }

        _applyInitialStyles() {
            if (!this.element) return;

            this.element.style.cssText = `
                position: fixed !important;
                width: 130px !important;
                height: 85px !important;
                background: linear-gradient(135deg, rgba(0, 255, 0, 0.25), rgba(0, 200, 0, 0.15)) !important;
                border: 3px solid #00ff41 !important;
                border-radius: 12px !important;
                cursor: move !important;
                z-index: 2147483647 !important;
                display: block !important;
                visibility: visible !important;
                opacity: 1 !important;
                pointer-events: auto !important;
                user-select: none !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif !important;
                box-shadow: 0 0 20px rgba(0, 255, 65, 0.4), inset 0 0 20px rgba(0, 255, 0, 0.1) !important;
                top: 100px !important;
                right: 20px !important;
                backdrop-filter: blur(5px) !important;
                transition: all 0.3s ease !important;
            `;
        }

        _setContent(text) {
            if (!this.element) return;

            this.element.innerHTML = `
                <div class="yt-tracker-glow-text" style="
                    text-align: center !important;
                    color: #00ff41 !important;
                    font-weight: bold !important;
                    font-size: 12px !important;
                    margin-top: 22px !important;
                    text-shadow: 0 0 8px #00ff41 !important;
                    line-height: 1.3 !important;
                    pointer-events: none !important;
                ">
                    ${text}
                </div>
            `;
        }

        _updateForPositioned() {
            if (!this.element) return;

            this.element.style.background = 'linear-gradient(135deg, rgba(0, 255, 65, 0.3), rgba(0, 200, 50, 0.2))';
            this.element.style.width = '140px';
            this.element.style.height = '90px';
            this.element.classList.add('yt-tracker-pulsing');
        }

        _setupEventListeners() {
            if (!this.element) return;

            try {
                // Mouse events
                const mouseDownHandler = this._createMouseDownHandler();
                this.element.addEventListener('mousedown', mouseDownHandler);
                this.boundHandlers.set('mousedown', mouseDownHandler);

                // Keyboard events for accessibility
                const keyDownHandler = (e) => {
                    if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault();
                        const rect = this.element.getBoundingClientRect();
                        const centerX = rect.left + rect.width / 2;
                        const centerY = rect.top + rect.height / 2;
                        window.dispatchEvent(new CustomEvent('ytTrackerDrop', {
                            detail: { x: centerX, y: centerY }
                        }));
                    }
                };
                this.element.addEventListener('keydown', keyDownHandler);
                this.boundHandlers.set('keydown', keyDownHandler);
            } catch (error) {
                Logger.error(`Failed to setup event listeners: ${error.message}`);
            }
        }

        _createMouseDownHandler() {
            return (e) => {
                try {
                    if (e.button !== 0) return;

                    this.isDragging = true;

                    const rect = this.element.getBoundingClientRect();
                    const startX = rect.left;
                    const startY = rect.top;
                    const mouseX = e.clientX;
                    const mouseY = e.clientY;

                    this.element.style.opacity = '0.8';
                    this.element.style.transform = 'scale(1.05)';

                    const mouseMoveHandler = (e) => {
                        if (!this.isDragging) return;

                        const newX = startX + (e.clientX - mouseX);
                        const newY = startY + (e.clientY - mouseY);

                        this.element.style.position = 'fixed';
                        this.element.style.left = newX + 'px';
                        this.element.style.top = newY + 'px';
                        this.element.style.right = 'auto';
                    };

                    const mouseUpHandler = (e) => {
                        if (!this.isDragging) return;

                        this.isDragging = false;
                        this.element.style.opacity = '1';
                        this.element.style.transform = 'scale(1)';

                        document.removeEventListener('mousemove', mouseMoveHandler);
                        document.removeEventListener('mouseup', mouseUpHandler);

                        setTimeout(() => {
                            window.dispatchEvent(new CustomEvent('ytTrackerDrop', {
                                detail: { x: e.clientX, y: e.clientY }
                            }));
                        }, CONFIG.DROP_DETECTION_DELAY);
                    };

                    document.addEventListener('mousemove', mouseMoveHandler);
                    document.addEventListener('mouseup', mouseUpHandler);

                    e.preventDefault();
                } catch (error) {
                    Logger.error(`Mouse handler error: ${error.message}`);
                }
            };
        }

        _removeEventListeners() {
            for (const [event, handler] of this.boundHandlers) {
                if (this.element) {
                    this.element.removeEventListener(event, handler);
                }
            }
            this.boundHandlers.clear();
        }

        _startVisibilityGuard() {
            this._stopVisibilityGuard();

            this.visibilityTimer = setInterval(() => {
                if (this.element && this.element.parentNode) {
                    this.element.style.display = 'block';
                    this.element.style.visibility = 'visible';
                    this.element.style.opacity = this.element.style.opacity || '1';

                    if (!document.body.contains(this.element)) {
                        document.body.appendChild(this.element);
                    }
                }
            }, CONFIG.VISIBILITY_CHECK_INTERVAL);
        }

        _stopVisibilityGuard() {
            if (this.visibilityTimer) {
                clearInterval(this.visibilityTimer);
                this.visibilityTimer = null;
            }
        }
    }

    // ==================== VIDEO SEARCH ENGINE ====================
    class VideoSearchEngine {
        constructor(notificationManager) {
            this.notificationManager = notificationManager;
            this.isSearching = false;
            this.searchCancelled = false;
            this.scrollDelay = CONFIG.SCROLL_DELAY_BASE;

            window.addEventListener('ytTrackerSearchCancel', () => {
                this.cancelSearch();
            });
        }

        async searchForVideo(videoId) {
            if (!videoId || this.isSearching) return false;

            try {
                this.isSearching = true;
                this.searchCancelled = false;
                this.scrollDelay = CONFIG.SCROLL_DELAY_BASE;

                Logger.log(`Starting search for video: ${videoId}`);
                this.notificationManager.showSearch('🔍 Searching for your last watched video...', 0);

                if (this._findVideoInCurrentView(videoId)) {
                    this.isSearching = false;
                    return true;
                }

                const result = await this._performSmartSearch(videoId);
                this.isSearching = false;

                if (this.searchCancelled) {
                    this.notificationManager.show('🚫 Search cancelled', 'error');
                    return false;
                }

                return result;
            } catch (error) {
                Logger.error(`Search error: ${error.message}`);
                this.isSearching = false;
                this.notificationManager.hideSearch();
                this.notificationManager.show('❌ Search failed', 'error');
                return false;
            }
        }

        cancelSearch() {
            this.searchCancelled = true;
            this.isSearching = false;
            this.notificationManager.hideSearch();
            Logger.log('Search cancelled by user');
        }

        _findVideoInCurrentView(videoId) {
            try {
                const containers = document.querySelectorAll(SELECTORS.VIDEO_CONTAINER);

                for (const container of containers) {
                    const links = container.querySelectorAll(SELECTORS.VIDEO_LINK);
                    for (const link of links) {
                        if (Utils.getVideoId(link.href) === videoId) {
                            Logger.log(`Found video ${videoId} in current view`);
                            this._highlightFoundVideo(container);
                            return true;
                        }
                    }
                }
                return false;
            } catch (error) {
                Logger.error(`Error finding video in current view: ${error.message}`);
                return false;
            }
        }

        async _performSmartSearch(videoId) {
            let attempts = 0;
            let lastScrollPosition = 0;
            let stuckCount = 0;
            let lastVideoCount = this._countCurrentVideos();

            while (this.isSearching && !this.searchCancelled && attempts < CONFIG.MAX_SEARCH_ATTEMPTS) {
                attempts++;
                const currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop;

                if (currentScrollPosition === lastScrollPosition) {
                    stuckCount++;
                    if (stuckCount >= 3) {
                        this.notificationManager.hideSearch();
                        this.notificationManager.show('❌ Reached end - video not found', 'error');
                        Logger.log('Reached end of page, video not found');
                        return false;
                    }
                } else {
                    stuckCount = 0;
                }
                lastScrollPosition = currentScrollPosition;

                const currentVideoCount = this._countCurrentVideos();
                const newVideosLoaded = currentVideoCount - lastVideoCount;
                this._adjustScrollDelay(newVideosLoaded);
                lastVideoCount = currentVideoCount;

                window.scrollBy(0, CONFIG.SCROLL_STEP);

                if (attempts % 3 === 0) {
                    const progressEstimate = Math.min((attempts * 2), 90);
                    this.notificationManager.showSearch(
                        `🔍 Searching... (${attempts} scrolls, ${currentVideoCount} videos)`,
                        progressEstimate
                    );
                }

                await Utils.sleep(this.scrollDelay);

                if (this._findVideoInCurrentView(videoId)) {
                    return true;
                }

                if (attempts % 15 === 0) {
                    Logger.log(`Search progress: attempt ${attempts}, delay: ${this.scrollDelay}ms, videos: ${currentVideoCount}`);
                }
            }

            if (!this.searchCancelled) {
                this.notificationManager.hideSearch();
                this.notificationManager.show('❌ Video not found after extensive search', 'error');
            }

            return false;
        }

        _highlightFoundVideo(container) {
            try {
                this.notificationManager.hideSearch();

                container.scrollIntoView({
                    behavior: 'smooth',
                    block: 'center'
                });

                setTimeout(() => {
                    window.dispatchEvent(new CustomEvent('ytTrackerVideoFound', {
                        detail: { container }
                    }));
                    this.notificationManager.show('🎯 Found your video!');
                }, 1500);
            } catch (error) {
                Logger.error(`Error highlighting video: ${error.message}`);
            }
        }

        _countCurrentVideos() {
            try {
                return document.querySelectorAll(SELECTORS.VIDEO_CONTAINER).length;
            } catch (error) {
                return 0;
            }
        }

        _adjustScrollDelay(newVideosLoaded) {
            if (newVideosLoaded === 0) {
                this.scrollDelay = Math.max(CONFIG.SCROLL_DELAY_MIN, this.scrollDelay - 100);
            } else if (newVideosLoaded > 10) {
                this.scrollDelay = Math.min(CONFIG.SCROLL_DELAY_MAX, this.scrollDelay + 200);
            }
        }
    }

    // ==================== MAIN TRACKER CLASS ====================
    class YouTubeVideoTracker {
        constructor() {
            this.dragBox = null;
            this.notificationManager = new NotificationManager();
            this.searchEngine = new VideoSearchEngine(this.notificationManager);
            this.currentVideoId = null;
            this.isInitialized = false;
            this.urlChangeObserver = null;
            this.lastUrl = location.href;

            this._setupEventListeners();
        }

        async init() {
            try {
                if (!Utils.isSubscriptionsPage()) {
                    Logger.log('Not on subscriptions page, skipping initialization');
                    return;
                }

                if (this.isInitialized) {
                    Logger.log('Already initialized, skipping');
                    return;
                }

                Logger.log('Initializing YouTube video tracker...');
                Logger.welcome();

                // Migrate existing data
                const migrated = StorageManager.migrateToHistoryFormat();
                if (migrated) {
                    Logger.log('Successfully migrated existing data');
                }

                if (!(await this._waitForContent())) {
                    Logger.error('Failed to load YouTube content within timeout');
                    this.notificationManager.show('⚠️ YouTube content loading slowly. Tracker may not work properly.', 'error');
                    return;
                }

                const dragBoxCreated = this._createDragBox();
                if (dragBoxCreated) {
                    this.notificationManager.show('💚 YouTube Tracker ready! Drag the green box to mark videos.', 'success');
                } else {
                    this.notificationManager.show('❌ Failed to create tracker interface', 'error');
                    return;
                }

                // Look for saved video
                const savedVideoId = StorageManager.getSavedVideo();
                if (savedVideoId) {
                    this.currentVideoId = savedVideoId;
                    Logger.log(`Looking for saved video: ${savedVideoId}`);

                    setTimeout(() => {
                        StorageManager.displayRecentHistory();
                    }, 1000);

                    setTimeout(() => {
                        this.searchEngine.searchForVideo(savedVideoId);
                    }, 2000);
                } else {
                    // First-time user
                    setTimeout(() => {
                        Logger.log('Welcome! This is your first time using the tracker.');
                        StorageManager.displayRecentHistory();
                    }, 1000);
                }

                this.isInitialized = true;
                Logger.log('Tracker initialization complete');
            } catch (error) {
                Logger.error(`Initialization failed: ${error.message}`);
                this.notificationManager.show('❌ Tracker initialization failed', 'error');
            }
        }

        destroy() {
            try {
                Logger.log('Destroying tracker...');

                if (this.dragBox) {
                    this.dragBox.destroy();
                    this.dragBox = null;
                }

                if (this.urlChangeObserver) {
                    this.urlChangeObserver.disconnect();
                    this.urlChangeObserver = null;
                }

                this.searchEngine.cancelSearch();
                this.isInitialized = false;

                Logger.log('Tracker destroyed');
            } catch (error) {
                Logger.error(`Destroy failed: ${error.message}`);
            }
        }

        _createDragBox() {
            try {
                if (this.dragBox) {
                    this.dragBox.destroy();
                }

                this.dragBox = new DragBox();
                return this.dragBox.create();
            } catch (error) {
                Logger.error(`Failed to create drag box: ${error.message}`);
                return false;
            }
        }

        _setupEventListeners() {
            try {
                window.addEventListener('ytTrackerDrop', (e) => {
                    this._handleDrop(e.detail.x, e.detail.y);
                });

                window.addEventListener('ytTrackerVideoFound', (e) => {
                    if (this.dragBox && e.detail.container) {
                        this.dragBox.positionOnVideo(e.detail.container);
                    }
                });

                window.addEventListener('ytTrackerSearchCancel', () => {
                    if (this.dragBox && this.dragBox.isPositioned) {
                        Logger.log('Resetting drag box due to search cancellation');
                        this.dragBox.resetToFloating();
                    }
                });

                this._setupUrlChangeDetection();
            } catch (error) {
                Logger.error(`Failed to setup event listeners: ${error.message}`);
            }
        }

        _setupUrlChangeDetection() {
            try {
                const checkUrlChange = Utils.debounce(() => {
                    if (location.href !== this.lastUrl) {
                        this.lastUrl = location.href;
                        this._handleUrlChange();
                    }
                }, CONFIG.DEBOUNCE_DELAY);

                this.urlChangeObserver = new MutationObserver(checkUrlChange);
                this.urlChangeObserver.observe(document, {
                    subtree: true,
                    childList: true
                });
            } catch (error) {
                Logger.error(`Failed to setup URL change detection: ${error.message}`);
            }
        }

        _handleUrlChange() {
            Logger.log(`URL changed to: ${location.href}`);

            this.searchEngine.cancelSearch();

            if (Utils.isSubscriptionsPage()) {
                setTimeout(() => this.init(), 1000);
            } else {
                this.destroy();
            }
        }

        async _handleDrop(x, y) {
            try {
                Logger.log(`Handling drop at coordinates: ${x}, ${y}`);

                if (!this.dragBox || !this.dragBox.element) {
                    Logger.error('Drag box not available');
                    return;
                }

                const originalPointerEvents = this.dragBox.element.style.pointerEvents;
                this.dragBox.element.style.pointerEvents = 'none';

                await new Promise(resolve => requestAnimationFrame(resolve));

                const element = document.elementFromPoint(x, y);

                this.dragBox.element.style.pointerEvents = originalPointerEvents;

                if (!element) {
                    Logger.warn('Drop target not found at coordinates');
                    this.notificationManager.show('❌ Drop target not found!', 'error');
                    return;
                }

                const videoContainer = element.closest(SELECTORS.VIDEO_CONTAINER);
                if (!videoContainer) {
                    Logger.warn('Drop target is not a video container');
                    this.notificationManager.show('❌ Please drop on a video!', 'error');
                    return;
                }

                const videoLink = videoContainer.querySelector(SELECTORS.VIDEO_LINK);
                if (!videoLink) {
                    Logger.warn('Video link not found in container');
                    this.notificationManager.show('❌ Video link not found!', 'error');
                    return;
                }

                const videoId = Utils.getVideoId(videoLink.href);
                if (!videoId) {
                    Logger.warn('Could not extract video ID from link');
                    this.notificationManager.show('❌ Could not get video ID!', 'error');
                    return;
                }

                Logger.log(`Attempting to save video ID: ${videoId}`);

                if (StorageManager.saveVideo(videoId)) {
                    this.currentVideoId = videoId;
                    this.dragBox.positionOnVideo(videoContainer);
                    this.notificationManager.show('✅ Video marked as last watched!', 'success');
                    Logger.log(`Successfully marked video ${videoId} as last watched`);
                } else {
                    Logger.error('Failed to save video to storage');
                    this.notificationManager.show('❌ Failed to save video!', 'error');
                }
            } catch (error) {
                Logger.error(`Drop handling failed: ${error.message}`);
                this.notificationManager.show('❌ Error processing drop', 'error');
            }
        }

        async _waitForContent() {
            const startTime = Date.now();

            for (let i = 0; i < 30; i++) {
                await Utils.sleep(1000);

                if (Date.now() - startTime > CONFIG.CONTENT_LOAD_TIMEOUT) {
                    Logger.warn('Content loading timeout reached');
                    break;
                }

                const videos = document.querySelectorAll(SELECTORS.VIDEO_CONTAINER);
                if (videos.length > 0) {
                    Logger.log(`Content ready - ${videos.length} videos found`);
                    return true;
                }
            }
            return false;
        }
    }

    // ==================== INITIALIZATION ====================
    let tracker = null;
    let initializationAttempts = 0;

    const startTracker = () => {
        try {
            Logger.log(`Starting YouTube Last Watched Video Tracker v${VERSION}`);

            if (tracker) {
                tracker.destroy();
            }

            tracker = new YouTubeVideoTracker();

            if (Utils.isSubscriptionsPage()) {
                setTimeout(() => tracker.init(), 1000);
            }
        } catch (error) {
            Logger.error(`Failed to start tracker: ${error.message}`);

            // Retry initialization
            initializationAttempts++;
            if (initializationAttempts < CONFIG.RETRY_ATTEMPTS) {
                Logger.log(`Retrying initialization (attempt ${initializationAttempts}/${CONFIG.RETRY_ATTEMPTS})`);
                setTimeout(startTracker, 2000 * initializationAttempts);
            } else {
                Logger.error('Failed to initialize after all attempts');
            }
        }
    };

    // Cleanup on page unload
    window.addEventListener('beforeunload', () => {
        if (tracker) {
            tracker.destroy();
        }
    });

    // Error handling for uncaught errors
    window.addEventListener('error', (e) => {
        if (e.message && e.message.includes('YT Tracker')) {
            Logger.error(`Uncaught error: ${e.message}`);
        }
    });

    // Start when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startTracker);
    } else {
        startTracker();
    }

    // Expose some functions for debugging (only in console)
    if (typeof window !== 'undefined') {
        window.ytTracker = {
            version: VERSION,
            clearData: () => StorageManager.clearAllData(),
            showHistory: () => StorageManager.displayRecentHistory(),
            restart: () => {
                if (tracker) tracker.destroy();
                startTracker();
            }
        };
    }

})();