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.

// ==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();
            }
        };
    }

})();