YouTube +

Вкладки для информации, комментариев, видео, плейлиста и скачивание видео и другие функции ↴

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            YouTube +
// @name:en         YouTube +
// @namespace       by
// @version         2.1
// @author          diorhc
// @description     Вкладки для информации, комментариев, видео, плейлиста и скачивание видео и другие функции ↴
// @description:en  Tabview YouTube and Download and others features ↴
// @match           https://*.youtube.com/*
// @match           https://music.youtube.com/*
// @match           *://myactivity.google.com/*
// @include         *://www.youtube.com/feed/history/*
// @include         https://www.youtube.com
// @include         *://*.youtube.com/**
// @exclude         *://accounts.youtube.com/*
// @exclude         *://www.youtube.com/live_chat_replay*
// @exclude         *://www.youtube.com/persist_identity*
// @exclude         /^https?://\w+\.youtube\.com\/live_chat.*$/
// @exclude         /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @icon            https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @license         MIT
// @grant           GM_xmlhttpRequest
// @grant           unsafeWindow
// @connect         api.livecounts.io
// @connect         livecounts.io
// @connect         ytplaylist.robert.wesner.io
// @connect         youtube.com
// @connect         googlevideo.com
// @connect         self
// @run-at          document-start
// @noframes
// @homepageURL     https://github.com/diorhc/YTP
// @supportURL      https://github.com/diorhc/YTP/issues
// ==/UserScript==

// Shared utilities for YouTube+ modules
(function () {
  'use strict';

  /**
   * Logs an error message with module context
   * @param {string} module - The module name where the error occurred
   * @param {string} message - Description of the error
   * @param {Error|*} error - The error object or value
   */
  const logError = (module, message, error) => {
    try {
      const errorDetails = {
        module,
        message,
        error:
          error instanceof Error
            ? {
                name: error.name,
                message: error.message,
                stack: error.stack,
              }
            : error,
        timestamp: new Date().toISOString(),
        userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
        url: typeof window !== 'undefined' ? window.location.href : 'unknown',
      };

      console.error(`[YouTube+][${module}] ${message}:`, error);
      console.debug('[YouTube+] Error details:', errorDetails);
    } catch (loggingError) {
      // Fallback if logging itself fails
      console.error('[YouTube+] Error logging failed:', loggingError);
    }
  };

  /**
   * Creates a debounced function that delays invoking func until after wait milliseconds
   * @template {Function} T
   * @param {T} fn - The function to debounce
   * @param {number} ms - The number of milliseconds to delay
   * @param {{leading?: boolean}} [options={}] - Options object
   * @returns {T & {cancel: () => void}} The debounced function with a cancel method
   */
  const debounce = (fn, ms, options = {}) => {
    let timeout = null;
    let lastArgs = null;
    let lastThis = null;
    /** @this {any} */
    const debounced = function (...args) {
      lastArgs = args;
      lastThis = this;
      clearTimeout(timeout);
      if (options.leading && !timeout) {
        /** @type {Function} */ (fn).apply(this, args);
      }
      timeout = setTimeout(() => {
        if (!options.leading) /** @type {Function} */ (fn).apply(lastThis, lastArgs);
        timeout = null;
        lastArgs = null;
        lastThis = null;
      }, ms);
    };
    debounced.cancel = () => {
      clearTimeout(timeout);
      timeout = null;
      lastArgs = null;
      lastThis = null;
    };
    return /** @type {any} */ (debounced);
  };

  /**
   * Creates a throttled function that only invokes func at most once per limit milliseconds
   * @template {Function} T
   * @param {T} fn - The function to throttle
   * @param {number} limit - The number of milliseconds to throttle invocations to
   * @returns {T} The throttled function
   */
  const throttle = (fn, limit) => {
    let inThrottle = false;
    let lastResult;
    /** @this {any} */
    const throttled = function (...args) {
      if (!inThrottle) {
        lastResult = /** @type {Function} */ (fn).apply(this, args);
        inThrottle = true;
        setTimeout(() => (inThrottle = false), limit);
      }
      return lastResult;
    };
    return /** @type {any} */ (throttled);
  };

  const StyleManager = (function () {
    const styles = new Map();
    return {
      add(id, css) {
        try {
          let el = document.getElementById(id);
          if (!el) {
            el = document.createElement('style');
            el.id = id;
            document.head.appendChild(el);
          }
          styles.set(id, css);
          el.textContent = Array.from(styles.values()).join('\n\n');
        } catch (e) {
          logError('StyleManager', 'add failed', e);
        }
      },
      remove(id) {
        try {
          styles.delete(id);
          const el = document.getElementById(id);
          if (el) el.remove();
        } catch (e) {
          logError('StyleManager', 'remove failed', e);
        }
      },
      clear() {
        for (const id of Array.from(styles.keys())) this.remove(id);
      },
    };
  })();

  const cleanupManager = (function () {
    const observers = new Set();
    const listeners = new Map();
    const intervals = new Set();
    const timeouts = new Set();
    const animationFrames = new Set();

    return {
      registerObserver(o) {
        try {
          observers.add(o);
        } catch {}
        return o;
      },
      registerListener(target, ev, fn, opts) {
        try {
          target.addEventListener(ev, fn, opts);
          const key = Symbol();
          listeners.set(key, { target, ev, fn, opts });
          return key;
        } catch (e) {
          logError('cleanupManager', 'registerListener failed', e);
          return null;
        }
      },
      registerInterval(id) {
        intervals.add(id);
        return id;
      },
      registerTimeout(id) {
        timeouts.add(id);
        return id;
      },
      registerAnimationFrame(id) {
        animationFrames.add(id);
        return id;
      },
      cleanup() {
        try {
          for (const o of observers) {
            try {
              o.disconnect();
            } catch {}
          }
          observers.clear();
          for (const keyEntry of listeners.values()) {
            try {
              keyEntry.target.removeEventListener(keyEntry.ev, keyEntry.fn, keyEntry.opts);
            } catch {}
          }
          listeners.clear();
          for (const id of intervals) clearInterval(id);
          intervals.clear();
          for (const id of timeouts) clearTimeout(id);
          timeouts.clear();
          for (const id of animationFrames) cancelAnimationFrame(id);
          animationFrames.clear();
        } catch (e) {
          logError('cleanupManager', 'cleanup failed', e);
        }
      },
      // expose for debug
      observers,
      listeners,
      intervals,
      timeouts,
      animationFrames,
    };
  })();

  const createElement = (tag, props = {}, children = []) => {
    try {
      const element = document.createElement(tag);
      Object.entries(props).forEach(([k, v]) => {
        if (k === 'className') element.className = v;
        else if (k === 'style' && typeof v === 'object') Object.assign(element.style, v);
        else if (k === 'dataset' && typeof v === 'object') Object.assign(element.dataset, v);
        else if (k.startsWith('on') && typeof v === 'function') {
          element.addEventListener(k.slice(2), v);
        } else element.setAttribute(k, v);
      });
      children.forEach(c => {
        if (typeof c === 'string') element.appendChild(document.createTextNode(c));
        else if (c instanceof Node) element.appendChild(c);
      });
      return element;
    } catch (e) {
      logError('createElement', 'failed', e);
      return document.createElement('div');
    }
  };

  const waitForElement = (selector, timeout = 5000, parent = document.body) =>
    new Promise((resolve, reject) => {
      if (!selector || typeof selector !== 'string') return reject(new Error('Invalid selector'));
      try {
        const el = parent.querySelector(selector);
        if (el) return resolve(el);
      } catch (e) {
        return reject(e);
      }
      const obs = new MutationObserver(() => {
        const el = parent.querySelector(selector);
        if (el) {
          try {
            obs.disconnect();
          } catch {}
          resolve(el);
        }
      });
      obs.observe(parent, { childList: true, subtree: true });
      const id = setTimeout(() => {
        try {
          obs.disconnect();
        } catch {}
        reject(new Error('timeout'));
      }, timeout);
      cleanupManager.registerTimeout(id);
    });

  /**
   * Sanitize HTML string to prevent XSS attacks
   * @param {string} html - HTML string to sanitize
   * @returns {string} Sanitized HTML
   */
  const sanitizeHTML = html => {
    if (typeof html !== 'string') return '';

    // Check for extremely long strings (potential DoS)
    if (html.length > 1000000) {
      console.warn('[YouTube+] HTML content too large, truncating');
      html = html.substring(0, 1000000);
    }

    /** @type {Record<string, string>} */
    const map = {
      '<': '&lt;',
      '>': '&gt;',
      '&': '&amp;',
      '"': '&quot;',
      "'": '&#39;',
      '/': '&#x2F;',
      '`': '&#x60;',
      '=': '&#x3D;',
    };

    return html.replace(/[<>&"'\/`=]/g, char => map[char] || char);
  };

  /**
   * Validate URL to prevent injection attacks
   * @param {string} url - URL to validate
   * @returns {boolean} Whether URL is safe
   */
  const isValidURL = url => {
    if (typeof url !== 'string') return false;
    if (url.length > 2048) return false; // RFC 2616
    if (/^\s|\s$/.test(url)) return false; // No leading/trailing whitespace

    try {
      const parsed = new URL(url);
      // Only allow http/https protocols
      if (!['http:', 'https:'].includes(parsed.protocol)) return false;
      return true;
    } catch {
      return false;
    }
  };

  /**
   * Retry an async operation with exponential backoff
   * @template T
   * @param {() => Promise<T>} fn - Async function to retry
   * @param {number} maxRetries - Maximum number of retries
   * @param {number} baseDelay - Base delay in milliseconds
   * @returns {Promise<T>} Result of the async operation
   */
  const retryWithBackoff = async (fn, maxRetries = 3, baseDelay = 1000) => {
    let lastError;
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error;
        if (i < maxRetries - 1) {
          const delay = baseDelay * Math.pow(2, i);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }
    throw lastError;
  };

  // Enhanced storage wrapper with better validation
  const storage = {
    /**
     * Get value from localStorage with validation
     * @param {string} key - Storage key
     * @param {*} def - Default value
     * @returns {*} Stored value or default
     */
    get(key, def = null) {
      // Validate key format
      if (typeof key !== 'string' || !/^[a-zA-Z0-9_\-\.]+$/.test(key)) {
        logError('storage', 'Invalid key format', new Error(`Invalid key: ${key}`));
        return def;
      }

      try {
        const v = localStorage.getItem(key);
        if (v === null) return def;

        // Check size before parsing
        if (v.length > 5 * 1024 * 1024) {
          // 5MB limit
          logError('storage', 'Stored value too large', new Error(`Key: ${key}`));
          return def;
        }

        return JSON.parse(v);
      } catch (e) {
        logError('storage', 'Failed to parse stored value', e);
        return def;
      }
    },

    /**
     * Set value in localStorage with validation
     * @param {string} key - Storage key
     * @param {*} val - Value to store
     * @returns {boolean} Whether operation succeeded
     */
    set(key, val) {
      // Validate key format
      if (typeof key !== 'string' || !/^[a-zA-Z0-9_\-\.]+$/.test(key)) {
        logError('storage', 'Invalid key format', new Error(`Invalid key: ${key}`));
        return false;
      }

      try {
        const serialized = JSON.stringify(val);

        // Check size limit (5MB)
        if (serialized.length > 5 * 1024 * 1024) {
          logError('storage', 'Value too large to store', new Error(`Key: ${key}`));
          return false;
        }

        localStorage.setItem(key, serialized);
        return true;
      } catch (e) {
        logError('storage', 'Failed to store value', e);
        return false;
      }
    },

    /**
     * Remove value from localStorage
     * @param {string} key - Storage key
     */
    remove(key) {
      try {
        localStorage.removeItem(key);
      } catch (e) {
        logError('storage', 'Failed to remove value', e);
      }
    },

    /**
     * Clear all localStorage
     */
    clear() {
      try {
        localStorage.clear();
      } catch (e) {
        logError('storage', 'Failed to clear storage', e);
      }
    },

    /**
     * Check if key exists
     * @param {string} key - Storage key
     * @returns {boolean} Whether key exists
     */
    has(key) {
      try {
        return localStorage.getItem(key) !== null;
      } catch {
        return false;
      }
    },
  };

  // Expose a global YouTubeUtils if not present (non-destructive)
  if (typeof window !== 'undefined') {
    /** @type {any} */ (window).YouTubeUtils = /** @type {any} */ (window).YouTubeUtils || {};
    const U = /** @type {any} */ (window).YouTubeUtils;
    U.logError = U.logError || logError;
    U.debounce = U.debounce || debounce;
    U.throttle = U.throttle || throttle;
    U.StyleManager = U.StyleManager || StyleManager;
    U.cleanupManager = U.cleanupManager || cleanupManager;
    U.createElement = U.createElement || createElement;
    U.waitForElement = U.waitForElement || waitForElement;
    U.storage = U.storage || storage;
    U.sanitizeHTML = U.sanitizeHTML || sanitizeHTML;
    U.isValidURL = U.isValidURL || isValidURL;
    U.retryWithBackoff = U.retryWithBackoff || retryWithBackoff;
  }
})();

// --- MODULE: error-boundary.js ---

// Global error boundary for YouTube+ userscript
(function () {
  'use strict';

  /**
   * Error boundary configuration object
   * @typedef {Object} ErrorBoundaryConfig
   * @property {number} maxErrors - Maximum number of errors allowed within the error window
   * @property {number} errorWindow - Time window in milliseconds for tracking errors (default: 60000ms = 1 minute)
   * @property {boolean} enableLogging - Whether to log errors to console
   * @property {boolean} enableRecovery - Whether to attempt automatic recovery from errors
   * @property {string} storageKey - LocalStorage key for persisting error data
   */
  const ErrorBoundaryConfig = {
    maxErrors: 10,
    errorWindow: 60000, // 1 minute
    enableLogging: true,
    enableRecovery: true,
    storageKey: 'youtube_plus_errors',
  };

  /**
   * Error tracking state (used inline for internal tracking)
   */
  const errorState = {
    errors: [],
    errorCount: 0,
    lastErrorTime: 0,
    isRecovering: false,
  };

  /**
   * Error severity levels enumeration
   * @enum {string}
   */
  const ErrorSeverity = {
    LOW: 'low',
    MEDIUM: 'medium',
    HIGH: 'high',
    CRITICAL: 'critical',
  };

  /**
   * Categorize error severity based on error message patterns
   * @param {Error} error - The error object to categorize
   * @returns {string} Severity level from ErrorSeverity enum
   */
  const categorizeSeverity = error => {
    const message = error.message?.toLowerCase() || '';

    if (
      message.includes('cannot read') ||
      message.includes('undefined') ||
      message.includes('null')
    ) {
      return ErrorSeverity.MEDIUM;
    }

    if (message.includes('network') || message.includes('fetch') || message.includes('timeout')) {
      return ErrorSeverity.LOW;
    }

    if (message.includes('syntax') || message.includes('reference') || message.includes('type')) {
      return ErrorSeverity.HIGH;
    }

    if (message.includes('security') || message.includes('csp')) {
      return ErrorSeverity.CRITICAL;
    }

    return ErrorSeverity.MEDIUM;
  };

  /**
   * Log error with context
   * @param {Error} error - The error object
   * @param {Object} context - Additional context information
   */
  const logError = (error, context = {}) => {
    if (!ErrorBoundaryConfig.enableLogging) return;

    const fallbackMessage = error.message?.trim() || '(no message)';

    const errorInfo = {
      timestamp: new Date().toISOString(),
      message: fallbackMessage,
      stack: error.stack,
      severity: categorizeSeverity(error),
      context: {
        url: window.location.href,
        userAgent: navigator.userAgent,
        ...context,
      },
    };

    console.error(`[YouTube+ Error Boundary] ${errorInfo.message}`, errorInfo);

    // Store error for analysis
    errorState.errors.push(errorInfo);
    if (errorState.errors.length > 50) {
      errorState.errors.shift(); // Keep only last 50 errors
    }

    // Persist to localStorage for debugging
    try {
      const stored = JSON.parse(localStorage.getItem(ErrorBoundaryConfig.storageKey) || '[]');
      stored.push(errorInfo);
      if (stored.length > 20) stored.shift();
      localStorage.setItem(ErrorBoundaryConfig.storageKey, JSON.stringify(stored));
    } catch {}
  };

  /**
   * Check if error rate is too high
   * @returns {boolean} True if error rate exceeded
   */
  const isErrorRateExceeded = () => {
    const now = Date.now();
    const windowStart = now - ErrorBoundaryConfig.errorWindow;

    // Count errors in the time window
    const recentErrors = errorState.errors.filter(
      e => new Date(e.timestamp).getTime() > windowStart
    );

    return recentErrors.length >= ErrorBoundaryConfig.maxErrors;
  };

  /**
   * Get error rate per minute
   * @returns {number} Errors per minute
   */
  const getErrorRate = () => {
    const now = Date.now();
    const oneMinuteAgo = now - 60000;
    const recentErrors = errorState.errors.filter(
      e => new Date(e.timestamp).getTime() > oneMinuteAgo
    );
    return recentErrors.length;
  };

  /**
   * Check if should suppress error notification (rate limiting)
   * @param {Error} error - The error object
   * @returns {boolean} True if should suppress
   */
  const shouldSuppressNotification = error => {
    const rate = getErrorRate();

    // Suppress if more than 5 errors in the last minute
    if (rate > 5) {
      return true;
    }

    // Suppress duplicate errors within 10 seconds
    const tenSecondsAgo = Date.now() - 10000;
    const recentSimilar = errorState.errors.filter(
      e =>
        new Date(e.timestamp).getTime() > tenSecondsAgo &&
        e.message === error.message &&
        e.severity === categorizeSeverity(error)
    );

    return recentSimilar.length > 0;
  };

  /**
   * Show user-friendly error notification
   * @param {Error} error - The error object
   * @param {Object} _context - Error context (unused but kept for API consistency)
   */
  const showErrorNotification = (error, _context) => {
    try {
      const Y = window.YouTubeUtils;
      if (!Y || !Y.NotificationManager || typeof Y.NotificationManager.show !== 'function') {
        return; // Notification manager not available
      }

      const severity = categorizeSeverity(error);
      let message = 'An error occurred';
      let duration = 3000;

      switch (severity) {
        case ErrorSeverity.LOW:
          message = 'A minor issue occurred. Functionality should continue normally.';
          duration = 2000;
          break;
        case ErrorSeverity.MEDIUM:
          message = 'An error occurred. Some features may not work correctly.';
          duration = 3000;
          break;
        case ErrorSeverity.HIGH:
          message = 'A serious error occurred. Please refresh the page if issues persist.';
          duration = 5000;
          break;
        case ErrorSeverity.CRITICAL:
          message =
            'A critical error occurred. YouTube+ may not function properly. Please report this issue.';
          duration = 7000;
          break;
      }

      Y.NotificationManager.show(message, { duration, type: 'error' });
    } catch (notificationError) {
      console.error('[YouTube+] Failed to show error notification:', notificationError);
    }
  };

  /**
   * Attempt to recover from error
   * @param {Error} error - The error that occurred
   * @param {Object} context - Error context
   */
  const attemptRecovery = (error, context) => {
    if (!ErrorBoundaryConfig.enableRecovery || errorState.isRecovering) return;

    const severity = categorizeSeverity(error);

    if (severity === ErrorSeverity.CRITICAL) {
      console.error('[YouTube+] Critical error detected. Script may not function properly.');
      showErrorNotification(error, context);
      return;
    }

    errorState.isRecovering = true;

    try {
      // Show notification to user (except for low severity errors and rate-limited)
      if (severity !== ErrorSeverity.LOW && !shouldSuppressNotification(error)) {
        showErrorNotification(error, context);
      }

      // Attempt module-specific recovery
      if (context.module) {
        console.log(`[YouTube+] Attempting recovery for module: ${context.module}`);

        // Try to reinitialize the module if possible
        const Y = window.YouTubeUtils;
        if (Y && Y.cleanupManager) {
          // Could cleanup and reinitialize module-specific resources
          switch (context.module) {
            case 'StyleManager':
              // Clear and re-add styles if needed
              break;
            case 'NotificationManager':
              // Reset notification queue
              break;
            default:
              // Generic cleanup
              break;
          }
        }

        // Check if it's a DOM-related error and the element is missing
        if (
          error.message &&
          (error.message.includes('null') || error.message.includes('undefined')) &&
          context.element
        ) {
          console.log('[YouTube+] Attempting to re-query DOM element');
          // Could trigger element re-query here
        }
      }

      setTimeout(() => {
        errorState.isRecovering = false;
      }, 5000);
    } catch (recoveryError) {
      console.error('[YouTube+] Recovery attempt failed:', recoveryError);
      errorState.isRecovering = false;
    }
  };

  /**
   * Global error handler
   * @param {ErrorEvent} event - The error event
   */
  const handleError = event => {
    const error = event.error || new Error(event.message);

    const message = (error.message || event.message || '').trim();
    const source = event.filename || '';
    const isCrossOriginSource =
      source && !source.startsWith(window.location.origin) && !/YouTube\+/.test(source);

    if (!message && isCrossOriginSource) {
      // Ignore opaque cross-origin errors we can't introspect
      return false;
    }

    // Track error
    errorState.errorCount++;
    errorState.lastErrorTime = Date.now();

    // Log error
    logError(error, {
      type: 'uncaught',
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
    });

    // Check error rate
    if (isErrorRateExceeded()) {
      console.error(
        '[YouTube+] Error rate exceeded! Too many errors in short period. Some features may be disabled.'
      );
      return false;
    }

    // Attempt recovery
    attemptRecovery(error, { type: 'uncaught' });

    // Don't prevent default error handling
    return false;
  };

  /**
   * Unhandled promise rejection handler
   * @param {PromiseRejectionEvent} event - The rejection event
   */
  const handleUnhandledRejection = event => {
    const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));

    logError(error, {
      type: 'unhandledRejection',
      promise: event.promise,
    });

    // Check error rate
    if (isErrorRateExceeded()) {
      console.error('[YouTube+] Promise rejection rate exceeded!');
      return;
    }

    // Attempt recovery
    attemptRecovery(error, { type: 'unhandledRejection' });
  };

  /**
   * Safe function wrapper with error boundary
   * @param {Function} fn - Function to wrap
   * @param {string} context - Context identifier
   * @returns {Function} Wrapped function
   */
  const withErrorBoundary = (fn, context = 'unknown') => {
    /** @this {any} */
    return function (...args) {
      try {
        const fnAny = /** @type {any} */ (fn);
        return /** @this {any} */ fnAny.apply(this, args);
      } catch (error) {
        logError(error, { module: context, args });
        attemptRecovery(error, { module: context });
        return null;
      }
    };
  };

  /**
   * Safe async function wrapper with error boundary
   * @param {Function} fn - Async function to wrap
   * @param {string} context - Context identifier
   * @returns {Function} Wrapped async function
   */
  const withAsyncErrorBoundary = (fn, context = 'unknown') => {
    /** @this {any} */
    return async function (...args) {
      try {
        const fnAny = /** @type {any} */ (fn);
        return /** @this {any} */ await fnAny.apply(this, args);
      } catch (error) {
        logError(error, { module: context, args });
        attemptRecovery(error, { module: context });
        return null;
      }
    };
  };

  /**
   * Get error statistics
   * @returns {Object} Error statistics
   */
  const getErrorStats = () => {
    return {
      totalErrors: errorState.errorCount,
      recentErrors: errorState.errors.length,
      lastErrorTime: errorState.lastErrorTime,
      isRecovering: errorState.isRecovering,
      errorsByType: errorState.errors.reduce((acc, e) => {
        acc[e.severity] = (acc[e.severity] || 0) + 1;
        return acc;
      }, {}),
    };
  };

  /**
   * Clear stored errors
   */
  const clearErrors = () => {
    errorState.errors = [];
    try {
      localStorage.removeItem(ErrorBoundaryConfig.storageKey);
    } catch {}
  };

  // Install global error handlers
  if (typeof window !== 'undefined') {
    window.addEventListener('error', handleError, true);
    window.addEventListener('unhandledrejection', handleUnhandledRejection, true);

    // Expose error boundary utilities
    window.YouTubeErrorBoundary = {
      withErrorBoundary,
      withAsyncErrorBoundary,
      getErrorStats,
      clearErrors,
      logError,
      getErrorRate,
      config: ErrorBoundaryConfig,
    };

    console.log('[YouTube+] Error boundary initialized');
  }
})();

// Performance monitoring for YouTube+ userscript
(function () {
  'use strict';

  /* global Blob, URL */

  /**
   * Performance monitoring configuration
   */
  const PerformanceConfig = {
    enabled: true,
    sampleRate: 1.0, // 100% sampling
    storageKey: 'youtube_plus_performance',
    metricsRetention: 100, // Keep last 100 metrics
    enableConsoleOutput: false,
  };

  /**
   * Performance metrics storage
   */
  const metrics = {
    timings: new Map(),
    marks: new Map(),
    measures: [],
    resources: [],
  };

  /**
   * Create a performance mark
   * @param {string} name - Mark name
   */
  const mark = name => {
    if (!PerformanceConfig.enabled) return;

    try {
      if (typeof performance !== 'undefined' && performance.mark) {
        performance.mark(name);
      }
      metrics.marks.set(name, Date.now());
    } catch (e) {
      console.warn('[YouTube+ Perf] Failed to create mark:', e);
    }
  };

  /**
   * Measure time between two marks
   * @param {string} name - Measure name
   * @param {string} startMark - Start mark name
   * @param {string} endMark - End mark name (optional, defaults to now)
   * @returns {number} Duration in milliseconds
   */
  const measure = (name, startMark, endMark) => {
    if (!PerformanceConfig.enabled) return 0;

    try {
      const startTime = metrics.marks.get(startMark);
      if (!startTime) {
        console.warn(`[YouTube+ Perf] Start mark "${startMark}" not found`);
        return 0;
      }

      const endTime = endMark ? metrics.marks.get(endMark) : Date.now();
      const duration = endTime - startTime;

      const measureData = {
        name,
        startMark,
        endMark: endMark || 'now',
        duration,
        timestamp: Date.now(),
      };

      metrics.measures.push(measureData);

      // Keep only recent measures
      if (metrics.measures.length > PerformanceConfig.metricsRetention) {
        metrics.measures.shift();
      }

      if (PerformanceConfig.enableConsoleOutput) {
        console.log(`[YouTube+ Perf] ${name}: ${duration.toFixed(2)}ms`);
      }

      // Try native performance API
      if (typeof performance !== 'undefined' && performance.measure) {
        try {
          performance.measure(name, startMark, endMark);
        } catch {}
      }

      return duration;
    } catch (e) {
      console.warn('[YouTube+ Perf] Failed to measure:', e);
      return 0;
    }
  };

  /**
   * Time a function execution
   * @param {string} name - Timer name
   * @param {Function} fn - Function to time
   * @returns {Function} Wrapped function
   */
  const timeFunction = (name, fn) => {
    if (!PerformanceConfig.enabled) return fn;

    return /** @this {any} */ function (...args) {
      const startMark = `${name}-start-${Date.now()}`;
      mark(startMark);

      try {
        const fnAny = /** @type {any} */ (fn);
        const result = fnAny.apply(this, args);

        // Handle promises
        if (result && typeof result.then === 'function') {
          return result.finally(() => {
            measure(name, startMark, undefined);
          });
        }

        measure(name, startMark, undefined);
        return result;
      } catch (error) {
        measure(name, startMark, undefined);
        throw error;
      }
    };
  };

  /**
   * Time an async function execution
   * @param {string} name - Timer name
   * @param {Function} fn - Async function to time
   * @returns {Function} Wrapped async function
   */
  const timeAsyncFunction = (name, fn) => {
    if (!PerformanceConfig.enabled) return fn;

    return /** @this {any} */ async function (...args) {
      const startMark = `${name}-start-${Date.now()}`;
      mark(startMark);

      try {
        const fnAny = /** @type {any} */ (fn);
        const result = await fnAny.apply(this, args);
        measure(name, startMark, undefined);
        return result;
      } catch (error) {
        measure(name, startMark, undefined);
        throw error;
      }
    };
  };

  /**
   * Record custom metric
   * @param {string} name - Metric name
   * @param {number} value - Metric value
   * @param {Object} metadata - Additional metadata
   */
  const recordMetric = (name, value, metadata = {}) => {
    if (!PerformanceConfig.enabled) return;

    const metric = {
      name,
      value,
      timestamp: Date.now(),
      ...metadata,
    };

    metrics.timings.set(name, metric);

    if (PerformanceConfig.enableConsoleOutput) {
      console.log(`[YouTube+ Perf] ${name}: ${value}`, metadata);
    }
  };

  /**
   * Get performance statistics
   * @param {string} metricName - Optional metric name filter
   * @returns {Object} Performance statistics
   */
  const getStats = metricName => {
    if (metricName) {
      const filtered = metrics.measures.filter(m => m.name === metricName);
      if (filtered.length === 0) return null;

      const durations = filtered.map(m => m.duration);
      return {
        name: metricName,
        count: durations.length,
        min: Math.min(...durations),
        max: Math.max(...durations),
        avg: durations.reduce((a, b) => a + b, 0) / durations.length,
        latest: durations[durations.length - 1],
      };
    }

    // Get all stats
    const allMetrics = {};
    const metricNames = [...new Set(metrics.measures.map(m => m.name))];

    metricNames.forEach(name => {
      allMetrics[name] = getStats(name);
    });

    return {
      metrics: allMetrics,
      totalMeasures: metrics.measures.length,
      totalMarks: metrics.marks.size,
      customMetrics: Object.fromEntries(metrics.timings),
    };
  };

  /**
   * Get memory usage information
   * @returns {Object|null} Memory usage data
   */
  const getMemoryUsage = () => {
    if (typeof performance === 'undefined' || !performance.memory) {
      return null;
    }

    try {
      const memory = performance.memory;
      return {
        usedJSHeapSize: memory.usedJSHeapSize,
        totalJSHeapSize: memory.totalJSHeapSize,
        jsHeapSizeLimit: memory.jsHeapSizeLimit,
        usedPercent: ((memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100).toFixed(2),
      };
    } catch {
      return null;
    }
  };

  /**
   * Track memory usage as a metric
   */
  const trackMemory = () => {
    const memory = getMemoryUsage();
    if (memory) {
      recordMetric('memory-usage', memory.usedJSHeapSize, {
        totalJSHeapSize: memory.totalJSHeapSize,
        usedPercent: memory.usedPercent,
      });
    }
  };

  /**
   * Check if metrics exceed thresholds
   * @param {Object} thresholds - Threshold configuration
   * @returns {Array} Array of threshold violations
   */
  const checkThresholds = thresholds => {
    const violations = [];
    const allStats = getStats(undefined);

    if (!allStats || !allStats.metrics) return violations;

    Object.entries(thresholds).forEach(([metricName, threshold]) => {
      const stat = allStats.metrics[metricName];
      if (stat && stat.avg > threshold) {
        violations.push({
          metric: metricName,
          threshold,
          actual: stat.avg,
          exceeded: stat.avg - threshold,
        });
      }
    });

    return violations;
  };

  /**
   * Export metrics to JSON
   * @returns {string} JSON string of metrics
   */
  const exportMetrics = () => {
    const data = {
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href,
      memory: getMemoryUsage(),
      stats: getStats(undefined),
      measures: metrics.measures,
      customMetrics: Object.fromEntries(metrics.timings),
    };

    return JSON.stringify(data, null, 2);
  };

  /**
   * Export metrics to downloadable file
   * @param {string} filename - Filename for export
   * @returns {boolean} Success status
   */
  const exportToFile = (filename = 'youtube-plus-performance.json') => {
    try {
      const data = exportMetrics();
      if (typeof Blob === 'undefined') {
        console.warn('[YouTube+ Perf] Blob API not available');
        return false;
      }
      const blob = new Blob([data], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      return true;
    } catch (e) {
      console.error('[YouTube+ Perf] Failed to export to file:', e);
      return false;
    }
  };

  /**
   * Aggregate metrics by time period
   * @param {number} periodMs - Time period in milliseconds
   * @returns {Array} Aggregated metrics
   */
  const aggregateByPeriod = (periodMs = 60000) => {
    const periods = new Map();

    metrics.measures.forEach(measure => {
      const periodStart = Math.floor(measure.timestamp / periodMs) * periodMs;
      if (!periods.has(periodStart)) {
        periods.set(periodStart, []);
      }
      periods.get(periodStart).push(measure);
    });

    const aggregated = [];
    periods.forEach((measures, periodStart) => {
      const durations = measures.map(m => m.duration);
      aggregated.push({
        period: new Date(periodStart).toISOString(),
        count: durations.length,
        min: Math.min(...durations),
        max: Math.max(...durations),
        avg: durations.reduce((a, b) => a + b, 0) / durations.length,
      });
    });

    return aggregated;
  };

  /**
   * Clear all performance metrics
   */
  const clearMetrics = () => {
    metrics.timings.clear();
    metrics.marks.clear();
    metrics.measures = [];
    metrics.resources = [];

    try {
      localStorage.removeItem(PerformanceConfig.storageKey);
    } catch {}

    if (typeof performance !== 'undefined' && performance.clearMarks) {
      try {
        performance.clearMarks();
        performance.clearMeasures();
      } catch {}
    }
  };

  /**
   * Monitor DOM mutations performance
   * @param {Element} element - Element to monitor
   * @param {string} name - Monitor name
   * @returns {MutationObserver} The observer instance
   */
  const monitorMutations = (element, name) => {
    if (!PerformanceConfig.enabled) return null;

    let mutationCount = 0;
    const startTime = Date.now();

    const observer = new MutationObserver(mutations => {
      mutationCount += mutations.length;
      recordMetric(`${name}-mutations`, mutationCount, {
        elapsed: Date.now() - startTime,
      });
    });

    observer.observe(element, {
      childList: true,
      subtree: true,
      attributes: true,
    });

    return observer;
  };

  /**
   * Get browser performance entries
   * @param {string} type - Entry type filter
   * @returns {Array} Performance entries
   */
  const getPerformanceEntries = type => {
    if (typeof performance === 'undefined' || !performance.getEntriesByType) {
      return [];
    }

    try {
      return performance.getEntriesByType(type);
    } catch {
      return [];
    }
  };

  /**
   * Log page load performance
   */
  const logPageLoadMetrics = () => {
    if (!PerformanceConfig.enabled) return;

    try {
      const navigation = getPerformanceEntries('navigation')[0];
      if (navigation) {
        recordMetric('page-load-time', navigation.loadEventEnd - navigation.fetchStart);
        recordMetric('dom-content-loaded', navigation.domContentLoadedEventEnd);
        recordMetric('dom-interactive', navigation.domInteractive);
      }
    } catch (e) {
      console.warn('[YouTube+ Perf] Failed to log page metrics:', e);
    }
  };

  // Auto-log page load metrics
  if (typeof window !== 'undefined') {
    if (document.readyState === 'complete') {
      logPageLoadMetrics();
    } else {
      window.addEventListener('load', logPageLoadMetrics, { once: true });
    }

    // Expose performance monitoring API
    window.YouTubePerformance = {
      mark,
      measure,
      timeFunction,
      timeAsyncFunction,
      recordMetric,
      getStats,
      exportMetrics,
      exportToFile,
      clearMetrics,
      monitorMutations,
      getPerformanceEntries,
      getMemoryUsage,
      trackMemory,
      checkThresholds,
      aggregateByPeriod,
      config: PerformanceConfig,
    };

    console.log('[YouTube+] Performance monitoring initialized');
  }
})();

// --- MODULE: main.js ---

/**
 * Identity function that returns the input value unchanged
 * @param {*} value - The value to return
 * @returns {*} The same value
 */
// @ts-nocheck

const identityFn = value => value; /**
 * Ensure TrustedTypes policy exists for secure HTML handling
 * @returns {{createHTML: Function, error: Error|null}} Policy object with createHTML function and error status
 */
function ensureTrustedTypesPolicy() {
  if (typeof trustedTypes === 'undefined') {
    return { createHTML: identityFn, error: null };
  }

  try {
    if (trustedTypes.defaultPolicy === null) {
      trustedTypes.createPolicy('default', {
        createHTML: identityFn,
        createScriptURL: identityFn,
        createScript: identityFn,
      });
    }

    const policy = trustedTypes.defaultPolicy;
    const createHTML =
      policy && typeof policy.createHTML === 'function'
        ? policy.createHTML.bind(policy)
        : identityFn;

    // Validate policy works
    const testDiv = document.createElement('div');
    testDiv.innerHTML = createHTML('1');
    return { createHTML, error: null };
  } catch (error) {
    console.error('TrustedTypes policy creation failed:', error);
    return { createHTML: identityFn, error };
  }
}

/**
 * Create browser tick scheduler for microtask execution
 * @param {Function} existing - Existing scheduler to reuse if version compatible
 * @returns {Function} Scheduler function with version property
 */
function createNextBrowserTick(existing) {
  if (existing && typeof existing === 'function' && existing.version >= 2) {
    return existing;
  }

  const SafePromise = (async () => {})().constructor;
  const queue =
    typeof queueMicrotask === 'function'
      ? callback => queueMicrotask(callback)
      : callback => SafePromise.resolve().then(callback);

  const scheduler = callback => {
    if (typeof callback === 'function') {
      queue(callback);
      return;
    }
    return SafePromise.resolve();
  };

  scheduler.version = 2;
  return scheduler;
}

const { createHTML, error: trustHTMLErr } = ensureTrustedTypesPolicy();

if (trustHTMLErr) {
  console.error(
    '[YouTube+] TrustedHTML Error: Script cannot run due to Content Security Policy restrictions',
    trustHTMLErr
  );
  throw new Error('CSP restriction - cannot initialize TrustedTypes');
}

// Export createHTML for use in modules if needed
if (typeof window !== 'undefined') {
  window._ytplusCreateHTML = createHTML;
}

const nextBrowserTick = createNextBrowserTick(
  (typeof window !== 'undefined' && window.nextBrowserTick) || undefined
);

if (
  typeof window !== 'undefined' &&
  (!window.nextBrowserTick || window.nextBrowserTick.version < 2)
) {
  window.nextBrowserTick = nextBrowserTick;
}

// -----------------------------------------------------------------------------------------------------------------------------

/**
 * Main execution script for YouTube tab view
 * @param {string} _communicationKey - Unique key for cross-context communication (reserved for future use)
 */
const executionScript = _communicationKey => {
  /** @const {boolean} Debug flag for attachment/detachment events */
  const DEBUG_5084 = false;

  /** @const {boolean} Debug flag for tab operations */
  const DEBUG_5085 = false;

  /** @const {boolean} Auto-switch to comments tab when available */
  const DEBUG_handleNavigateFactory = false;
  const TAB_AUTO_SWITCH_TO_COMMENTS = false;

  // Configuration validation
  /** @const {number} Maximum value for attributes before overflow reset */
  const MAX_ATTRIBUTE_VALUE = 1e9;

  /** @const {number} Reset value when attribute exceeds max */
  const ATTRIBUTE_RESET_VALUE = 9;

  // Validate configuration
  if (
    MAX_ATTRIBUTE_VALUE <= 0 ||
    ATTRIBUTE_RESET_VALUE < 0 ||
    ATTRIBUTE_RESET_VALUE >= MAX_ATTRIBUTE_VALUE
  ) {
    console.error(
      '[YouTube+] Invalid configuration: MAX_ATTRIBUTE_VALUE and ATTRIBUTE_RESET_VALUE must be valid positive numbers'
    );
  } // Reuse utility functions from parent scope
  const identityFn = value => value;
  const ensureTrustedTypesPolicyLocal = () => {
    if (typeof trustedTypes === 'undefined') {
      return { createHTML: identityFn, error: null };
    }

    try {
      if (trustedTypes.defaultPolicy === null) {
        trustedTypes.createPolicy('default', {
          createHTML: identityFn,
          createScriptURL: identityFn,
          createScript: identityFn,
        });
      }

      const policy = trustedTypes.defaultPolicy;
      const createHTML = policy?.createHTML?.bind?.(policy) ?? identityFn;

      // Validate policy works
      const testDiv = document.createElement('div');
      testDiv.innerHTML = createHTML('1');
      return { createHTML, error: null };
    } catch (error) {
      console.error('[YouTube+] TrustedTypes local policy failed:', error);
      return { createHTML: identityFn, error };
    }
  };

  /**
   * Create browser tick scheduler for microtask execution
   * @param {Function} existing - Existing scheduler to reuse if version compatible
   * @returns {Function} Scheduler function
   */
  const createNextBrowserTickLocal = existing => {
    if (existing?.version >= 2) {
      return existing;
    }

    const SafePromise = (async () => {})().constructor;
    const queue =
      typeof queueMicrotask === 'function'
        ? callback => queueMicrotask(callback)
        : callback => SafePromise.resolve().then(callback);

    const scheduler = callback => {
      if (typeof callback === 'function') {
        queue(callback);
        return;
      }
      return SafePromise.resolve();
    };

    scheduler.version = 2;
    return scheduler;
  };

  const { createHTML, error: trustHTMLErr } = ensureTrustedTypesPolicyLocal();

  if (trustHTMLErr) {
    console.error(
      '[YouTube+] TrustedHTML Error: Script cannot run due to CSP restrictions',
      trustHTMLErr
    );
    return; // Exit execution script gracefully
  }

  const nextBrowserTick = createNextBrowserTickLocal(
    (typeof window !== 'undefined' && window.nextBrowserTick) || undefined
  );

  if (
    typeof window !== 'undefined' &&
    (!window.nextBrowserTick || window.nextBrowserTick.version < 2)
  ) {
    window.nextBrowserTick = nextBrowserTick;
  }

  try {
    let executionFinished = 0;

    if (typeof CustomElementRegistry === 'undefined') return;
    if (CustomElementRegistry.prototype.define000) return;
    if (typeof CustomElementRegistry.prototype.define !== 'function') return;

    /** @type {HTMLElement} HTMLElement constructor reference */
    const HTMLElement_ = HTMLElement.prototype.constructor;

    /**
     * Simple cache for frequently used querySelector results
     * Helps reduce DOM traversal overhead
     */
    const selectorCache = new Map();

    const _CACHE_MAX_SIZE = 50; // Reserved for future cache implementation
    const CACHE_TTL = 5000; // 5 seconds

    /**
     * Clear expired cache entries
     */
    const clearExpiredCache = () => {
      const now = Date.now();
      for (const [key, value] of selectorCache.entries()) {
        if (now - value.timestamp > CACHE_TTL) {
          selectorCache.delete(key);
        }
      }
    };

    // Periodically clear expired cache
    setInterval(clearExpiredCache, CACHE_TTL);

    /**
     * Query single element from a specific parent
     * @param {Element} elm - Parent element to query from
     * @param {string} selector - CSS selector string
     * @returns {Element | null} Found element or null
     */
    const qsOne = (elm, selector) => {
      return HTMLElement_.prototype.querySelector.call(elm, selector);
    };

    /**
     * Query all matching elements from a specific parent
     * @param {Element} elm - Parent element to query from
     * @param {string} selector - CSS selector string
     * @returns {NodeListOf<Element>} NodeList of found elements
     */

    const _qsAll = (elm, selector) => {
      return HTMLElement_.prototype.querySelectorAll.call(elm, selector);
    };

    const pdsBaseDF = Object.getOwnPropertyDescriptors(DocumentFragment.prototype);

    Object.defineProperties(DocumentFragment.prototype, {
      replaceChildren000: pdsBaseDF.replaceChildren,
    });

    const pdsBaseNode = Object.getOwnPropertyDescriptors(Node.prototype);

    Object.defineProperties(Node.prototype, {
      appendChild000: pdsBaseNode.appendChild,
      insertBefore000: pdsBaseNode.insertBefore,
    });

    const pdsBaseElement = Object.getOwnPropertyDescriptors(Element.prototype);

    Object.defineProperties(Element.prototype, {
      setAttribute000: pdsBaseElement.setAttribute,
      getAttribute000: pdsBaseElement.getAttribute,
      hasAttribute000: pdsBaseElement.hasAttribute,
      removeAttribute000: pdsBaseElement.removeAttribute,
      querySelector000: pdsBaseElement.querySelector,
      replaceChildren000: pdsBaseElement.replaceChildren,
    });

    /**
     * Set attribute only if value has changed (optimization to reduce DOM operations)
     * @param {string} p - Attribute name
     * @param {*} v - Attribute value
     */
    Element.prototype.setAttribute111 = function (p, v) {
      if (!p || typeof p !== 'string') {
        console.warn('[YouTube+] setAttribute111: invalid attribute name', p);
        return;
      }
      try {
        v = `${v}`;
        if (this.getAttribute000(p) === v) return;
        this.setAttribute000(p, v);
      } catch (error) {
        console.warn('[YouTube+] setAttribute111 failed:', error, p, v);
      }
    };

    /**
     * Increment attribute value (with overflow protection)
     * @param {string} p - Attribute name
     * @returns {number} New attribute value
     */
    Element.prototype.incAttribute111 = function (p) {
      if (!p || typeof p !== 'string') {
        console.warn('[YouTube+] incAttribute111: invalid attribute name', p);
        return 0;
      }
      try {
        let v = +this.getAttribute000(p) || 0;
        v = v > MAX_ATTRIBUTE_VALUE ? ATTRIBUTE_RESET_VALUE : v + 1;
        this.setAttribute000(p, `${v}`);
        return v;
      } catch (error) {
        console.warn('[YouTube+] incAttribute111 failed:', error, p);
        return 0;
      }
    };

    /**
     * Assign children elements in specific order while managing DOM efficiently
     * @param {Array<Node>|null} previousSiblings - Nodes to place before target node
     * @param {Node} node - Target node (required)
     * @param {Array<Node>|null} nextSiblings - Nodes to place after target node
     */
    Element.prototype.assignChildren111 = function (previousSiblings, node, nextSiblings) {
      if (!node) {
        console.warn('[YouTube+] assignChildren111: node is required');
        return;
      }

      try {
        // Collect all child nodes except the target node
        let nodeList = [];
        for (let t = this.firstChild; t instanceof Node; t = t.nextSibling) {
          if (t === node) continue;
          nodeList.push(t);
        }

        inPageRearrange = true;

        if (node.parentNode === this) {
          // Node is already a child, rearrange efficiently
          let fm = new DocumentFragment();

          if (nodeList.length > 0) {
            fm.replaceChildren000(...nodeList);
          }

          if (previousSiblings?.length > 0) {
            fm.replaceChildren000(...previousSiblings);
            this.insertBefore000(fm, node);
          }

          if (nextSiblings?.length > 0) {
            fm.replaceChildren000(...nextSiblings);
            this.appendChild000(fm);
          }

          fm.replaceChildren000();
          fm = null;
        } else {
          // Node is not a child yet, replace all children
          this.replaceChildren000(...(previousSiblings || []), node, ...(nextSiblings || []));
        }

        inPageRearrange = false;

        // Cleanup disconnected nodes
        if (nodeList.length > 0) {
          for (const t of nodeList) {
            if (t instanceof Element && t.isConnected === false) {
              t.remove(); // Trigger removal events
            }
          }
        }

        nodeList.length = 0;
        nodeList = null;
      } catch (error) {
        inPageRearrange = false;
        console.error('[YouTube+] assignChildren111 failed:', error);
      }
    };

    // ==============================================================================================================================================================================================================================================================================

    const DISABLE_FLAGS_SHADYDOM_FREE = true;

    /**
     *
     * Minified Code from https://greasyfork.org/en/scripts/475632-ytconfighacks/code (ytConfigHacks)
     * Date: 2024.04.17
     * Minifier: https://www.toptal.com/developers/javascript-minifier
     *
     */
    /* eslint-disable no-unused-expressions */
    (() => {
      const e =
        'undefined' != typeof unsafeWindow ? unsafeWindow : this instanceof Window ? this : window;
      if (!e._ytConfigHacks) {
        let t = 4;
        class n extends Set {
          add(e) {
            if (t <= 0) return console.warn('yt.config_ is already applied on the page.');
            'function' == typeof e && super.add(e);
          }
        }
        const a = (async () => {})().constructor,
          i = (e._ytConfigHacks = new n());
        let l = () => {
          const t = e.ytcsi.originalYtcsi;
          t && ((e.ytcsi = t), (l = null));
        };
        let c = null;
        const o = () => {
          if (t >= 1) {
            const n = (e.yt || 0).config_ || (e.ytcfg || 0).data_ || 0;
            if ('string' == typeof n.INNERTUBE_API_KEY && 'object' == typeof n.EXPERIMENT_FLAGS) {
              for (const a of (--t <= 0 && l && l(), (c = !0), i)) a(n);
            }
          }
        };
        let f = 1;
        const d = t => {
          if ((t = t || e.ytcsi)) {
            return (
              (e.ytcsi = new Proxy(t, {
                get: (e, t) => ('originalYtcsi' === t ? e : (o(), c && --f <= 0 && l && l(), e[t])),
              })),
              !0
            );
          }
        };
        d() ||
          Object.defineProperty(e, 'ytcsi', {
            get() {},
            set: t => (t && (delete e.ytcsi, d(t)), !0),
            enumerable: !1,
            configurable: !0,
          });
        const { addEventListener: s, removeEventListener: y } = Document.prototype;
        function r(t) {
          (o(), t && e.removeEventListener('DOMContentLoaded', r, !1));
        }
        (new a(e => {
          if ('undefined' != typeof AbortSignal) {
            (s.call(document, 'yt-page-data-fetched', e, { once: !0 }),
              s.call(document, 'yt-navigate-finish', e, { once: !0 }),
              s.call(document, 'spfdone', e, { once: !0 }));
          } else {
            const t = () => {
              (e(),
                y.call(document, 'yt-page-data-fetched', t, !1),
                y.call(document, 'yt-navigate-finish', t, !1),
                y.call(document, 'spfdone', t, !1));
            };
            (s.call(document, 'yt-page-data-fetched', t, !1),
              s.call(document, 'yt-navigate-finish', t, !1),
              s.call(document, 'spfdone', t, !1));
          }
        }).then(o),
          new a(e => {
            if ('undefined' != typeof AbortSignal) {
              s.call(document, 'yt-action', e, { once: !0, capture: !0 });
            } else {
              const t = () => {
                (e(), y.call(document, 'yt-action', t, !0));
              };
              s.call(document, 'yt-action', t, !0);
            }
          }).then(o),
          a.resolve().then(() => {
            'loading' !== document.readyState ? r() : e.addEventListener('DOMContentLoaded', r, !1);
          }));
      }
    })();
    /* eslint-enable no-unused-expressions */

    let configOnce = false;
    window._ytConfigHacks.add(config_ => {
      if (configOnce) return;
      configOnce = true;

      const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS || 0;
      const EXPERIMENTS_FORCED_FLAGS = config_.EXPERIMENTS_FORCED_FLAGS || 0;
      for (const flags of [EXPERIMENT_FLAGS, EXPERIMENTS_FORCED_FLAGS]) {
        if (flags) {
          // flags.kevlar_watch_metadata_refresh_no_old_secondary_data = false;
          // flags.live_chat_overflow_hide_chat = false;
          flags.web_watch_chat_hide_button_killswitch = false;
          flags.web_watch_theater_chat = false; // for re-openable chat (ytd-watch-flexy's liveChatCollapsed is always undefined)
          flags.suppress_error_204_logging = true;
          flags.kevlar_watch_grid = false; // A/B testing for watch grid

          if (DISABLE_FLAGS_SHADYDOM_FREE) {
            flags.enable_shadydom_free_scoped_node_methods = false;
            flags.enable_shadydom_free_scoped_query_methods = false;
            flags.enable_shadydom_free_scoped_readonly_properties_batch_one = false;
            flags.enable_shadydom_free_parent_node = false;
            flags.enable_shadydom_free_children = false;
            flags.enable_shadydom_free_last_child = false;
          }
        }
      }
    });

    // ===================================================================================================================================================================================================================================
    /* globals WeakRef:false */

    /** @type {(o: Object | null) => WeakRef | null} */
    const mWeakRef =
      typeof WeakRef === 'function' ? o => (o ? new WeakRef(o) : null) : o => o || null; // typeof InvalidVar == 'undefined'

    /** @type {(wr: Object | null) => Object | null} */
    const kRef = wr => (wr && wr.deref ? wr.deref() : wr);

    /** @type {globalThis.PromiseConstructor} */
    /** @type {PromiseConstructor} Safe Promise constructor (YouTube hacks Promise in WaterFox Classic) */
    const Promise = (async () => {})().constructor;

    /**
     * Create a promise that resolves after a delay
     * @param {number} delay - Delay in milliseconds
     * @returns {Promise<void>} Promise that resolves after the delay
     */
    const delayPn = delay => new Promise(fn => setTimeout(fn, delay));

    /**
     * Get polymer controller or instance from element
     * @param {*} o - Element or object to inspect
     * @returns {*} Polymer controller, instance, or the object itself
     */
    const insp = o => (o ? o.polymerController || o.inst || o || 0 : o || 0);

    /** @type {Function} Bound setTimeout to ensure correct context */
    const setTimeout_ = setTimeout.bind(window);

    /**
     * Error handler for promises - logs errors with context
     * @param {Error} error - The error that occurred
     * @param {string} context - Context information about where the error occurred
     */
    const handlePromiseError = (error, context = 'Unknown') => {
      if (error) {
        console.error(`[YouTube+] Promise error in ${context}:`, error);
      }
    };

    /**
     * Promise class with external resolve/reject methods
     * Useful for creating deferred promises that can be resolved externally
     */
    const PromiseExternal = ((resolve_, reject_) => {
      const h = (resolve, reject) => {
        resolve_ = resolve;
        reject_ = reject;
      };
      return class PromiseExternal extends Promise {
        constructor(cb = h) {
          super(cb);
          if (cb === h) {
            /** @type {(value: any) => void} */
            this.resolve = resolve_;
            /** @type {(reason?: any) => void} */
            this.reject = reject_;
          }
        }
      };
    })();

    // ------------------------------------------------------------------------ Event Listener Options ------------------------------------------------------------------------

    /** @const {boolean} Check if passive event listeners are supported */
    const isPassiveArgSupport = typeof IntersectionObserver === 'function';

    /** @const {Object|boolean} Event listener options for bubble phase with passive */

    const _bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;

    /** @const {Object|boolean} Event listener options for capture phase with passive */
    const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;

    /**
     * Helper class to manage binary flags as string attributes
     */
    class Attributer {
      /**
       * @param {string} list - String where each character represents a flag
       */
      constructor(list) {
        this.list = list;
        this.flag = 0;
      }

      /**
       * Convert active flags to string representation
       * @returns {string} String with characters for active flags
       */
      makeString() {
        let k = 1;
        let s = '';
        let i = 0;
        while (this.flag >= k) {
          if (this.flag & k) {
            s += this.list[i];
          }
          i++;
          k <<= 1;
        }
        return s;
      }
    }

    /** @type {Attributer} Module loaded state tracker */
    const mLoaded = new Attributer('icp');

    /** @type {WeakMap} WeakMap for self-referencing objects */
    const wrSelfMap = new WeakMap();

    /**
     * Elements cache using Proxy with WeakRef for memory efficiency
     * Automatically manages element references and prevents memory leaks
     * @type {Object.<string, Element | null>}
     */
    const elements = new Proxy(
      {
        related: null,
        comments: null,
        infoExpander: null,
      },
      {
        get(target, prop) {
          return kRef(target[prop]);
        },
        set(target, prop, value) {
          if (value) {
            let wr = wrSelfMap.get(value);
            if (!wr) {
              wr = mWeakRef(value);
              wrSelfMap.set(value, wr);
            }
            target[prop] = wr;
          } else {
            target[prop] = null;
          }
          return true;
        },
      }
    );

    /**
     * Get the main info element from the infoExpander
     * @returns {Element|null} The main info element or null
     */
    const getMainInfo = () => {
      const infoExpander = elements.infoExpander;
      if (!infoExpander) return null;
      const mainInfo = infoExpander.matches('[tyt-main-info]')
        ? infoExpander
        : infoExpander.querySelector000('[tyt-main-info]');
      return mainInfo || null;
    };
    /**
     * Wrap async function to execute in next microtask
     * @param {Function} asyncFn - Async function to wrap
     * @returns {Function} Wrapped function
     */

    const _asyncWrap = asyncFn => {
      return () => {
        Promise.resolve().then(asyncFn);
      };
    };

    let pageType = null;

    let pageLang = 'en';
    /**
     * Localized strings for different languages
     * @type {Object.<string, Object.<string, string>>}
     */
    const langWords = {
      en: {
        info: 'Info',
        videos: 'Videos',
        playlist: 'Playlist',
      },
      jp: {
        info: '情報',
        videos: '動画',
        playlist: '再生リスト',
      },
      tw: {
        info: '資訊',
        videos: '影片',
        playlist: '播放清單',
      },
      cn: {
        info: '资讯',
        videos: '视频',
        playlist: '播放列表',
      },
      du: {
        info: 'Info',
        videos: 'Videos',
        playlist: 'Playlist',
      },
      fr: {
        info: 'Info',
        videos: 'Vidéos',
        playlist: 'Playlist',
      },
      kr: {
        info: '정보',
        videos: '동영상',
        playlist: '재생목록',
      },
      ru: {
        info: 'Описание',
        videos: 'Видео',
        playlist: 'Плейлист',
      },
    };

    const svgComments =
      `<path d="M80 27H12A12 12 90 0 0 0 39v42a12 12 90 0 0 12 12h12v20a2 2 90 0 0 3.4 2L47 93h33a12 
  12 90 0 0 12-12V39a12 12 90 0 0-12-12zM20 47h26a2 2 90 1 1 0 4H20a2 2 90 1 1 0-4zm52 28H20a2 2 90 1 1 0-4h52a2 2 90 
  1 1 0 4zm0-12H20a2 2 90 1 1 0-4h52a2 2 90 1 1 0 4zm36-58H40a12 12 90 0 0-12 12v6h52c9 0 16 7 16 16v42h0v4l7 7a2 2 90 
  0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>`.trim();

    const svgVideos =
      `<path d="M89 10c0-4-3-7-7-7H7c-4 0-7 3-7 7v70c0 4 3 7 7 7h75c4 0 7-3 7-7V10zm-62 2h13v10H27V12zm-9 
  66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25 
  25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>`.trim();

    const svgInfo =
      `<path d="M30 0C13.3 0 0 13.3 0 30s13.3 30 30 30 30-13.3 30-30S46.7 0 30 0zm6.2 46.6c-1.5.5-2.6 
  1-3.6 1.3a10.9 10.9 0 0 1-3.3.5c-1.7 0-3.3-.5-4.3-1.4a4.68 4.68 0 0 1-1.6-3.6c0-.4.2-1 .2-1.5a20.9 20.9 90 0 1 
  .3-2l2-6.8c.1-.7.3-1.3.4-1.9a8.2 8.2 90 0 0 .3-1.6c0-.8-.3-1.4-.7-1.8s-1-.5-2-.5a4.53 4.53 0 0 0-1.6.3c-.5.2-1 
  .2-1.3.4l.6-2.1c1.2-.5 2.4-1 3.5-1.3s2.3-.6 3.3-.6c1.9 0 3.3.6 4.3 1.3s1.5 2.1 1.5 3.5c0 .3 0 .9-.1 1.6a10.4 10.4 
  90 0 1-.4 2.2l-1.9 6.7c-.2.5-.2 1.1-.4 1.8s-.2 1.3-.2 1.6c0 .9.2 1.6.6 1.9s1.1.5 2.1.5a6.1 6.1 90 0 0 1.5-.3 9 9 90 
  0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>`.trim();

    const svgPlayList =
      `<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>`.trim();

    // eslint-disable-next-line no-unused-vars
    const svgDiag1 = `<svg stroke="currentColor" fill="none"><path d="M8 2h2v2M7 5l3-3m-6 8H2V8m0 2l3-3"/></svg>`;
    // eslint-disable-next-line no-unused-vars
    const svgDiag2 = `<svg stroke="currentColor" fill="none"><path d="M7 3v2h2M7 5l3-3M5 9V7H3m-1 3l3-3"/></svg>`;

    /**
     * Get GMT offset for the current timezone
     * @returns {string} GMT offset string (e.g., "+9" or "-5")
     */
    // eslint-disable-next-line no-unused-vars
    const getGMT = () => {
      const m = new Date('2023-01-01T00:00:00Z');
      return m.getDate() === 1 ? `+${m.getHours()}` : `-${24 - m.getHours()}`;
    };

    /**
     * Get localized word based on current page language
     * @param {string} tag - Word identifier
     * @returns {string} Localized word or empty string
     */
    function getWord(tag) {
      return langWords[pageLang]?.[tag] || langWords['en']?.[tag] || '';
    }

    /**
     * Create SVG element string
     * @param {number} w - Width
     * @param {number} h - Height
     * @param {number} vw - ViewBox width
     * @param {number} vh - ViewBox height
     * @param {string} p - Path data
     * @param {string} m - Optional class name
     * @returns {string} SVG element string
     */
    const svgElm = (w, h, vw, vh, p, m) =>
      `<svg${m ? ` class=${m}` : ''} width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`;

    const hiddenTabsByUserCSS = 0;

    /**
     * Generate HTML for tab buttons
     * @returns {string} HTML string for tabs
     */

    function getTabsHTML() {
      const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord('videos')}</span>`;
      const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord('info')}</span>`;
      const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord('playlist')}</span>`;

      const str1 = `
        <paper-ripple class="style-scope yt-icon-button">
            <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
            <div id="waves" class="style-scope paper-ripple"></div>
        </paper-ripple>
        `;

      const str_fbtns = `
    <div class="font-size-right">
    <div class="font-size-btn font-size-plus" tyt-di="8rdLQ">
    <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet" 
    stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
      <path d="M12 25H38M25 12V38"/>
    </svg>
    </div><div class="font-size-btn font-size-minus" tyt-di="8rdLQ">
    <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
    stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
      <path d="M12 25h26"/>
    </svg>
    </div>
    </div>
    `.replace(/[\r\n]+/g, '');

      const str_tabs = [
        `<a id="tab-btn1" tyt-di="q9Kjc" tyt-tab-content="#tab-info" class="tab-btn${(hiddenTabsByUserCSS & 1) === 1 ? ' tab-btn-hidden' : ''}">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
        `<a id="tab-btn3" tyt-di="q9Kjc" tyt-tab-content="#tab-comments" class="tab-btn${(hiddenTabsByUserCSS & 2) === 2 ? ' tab-btn-hidden' : ''}">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`,
        `<a id="tab-btn4" tyt-di="q9Kjc" tyt-tab-content="#tab-videos" class="tab-btn${(hiddenTabsByUserCSS & 4) === 4 ? ' tab-btn-hidden' : ''}">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
        `<a id="tab-btn5" tyt-di="q9Kjc" tyt-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`,
      ].join('');

      const addHTML = `
        <div id="right-tabs">
            <tabview-view-pos-thead></tabview-view-pos-thead>
            <header>
                <div id="material-tabs">
                    ${str_tabs}
                </div>
            </header>
            <div class="tab-content">
                <div id="tab-info" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
                <div id="tab-comments" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
                <div id="tab-videos" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
                <div id="tab-list" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
            </div>
        </div>
        `;
      return addHTML;
    }

    function getLang() {
      const htmlLang = ((document || 0).documentElement || 0).lang || '';

      // Language mapping with optimized lookup
      const langMap = {
        en: 'en',
        'en-GB': 'en',
        de: 'du',
        'de-DE': 'du',
        fr: 'fr',
        'fr-CA': 'fr',
        'fr-FR': 'fr',
        'zh-Hant': 'tw',
        'zh-Hant-HK': 'tw',
        'zh-Hant-TW': 'tw',
        'zh-Hans': 'cn',
        'zh-Hans-CN': 'cn',
        ja: 'jp',
        'ja-JP': 'jp',
        ko: 'kr',
        'ko-KR': 'kr',
        ru: 'ru',
        'ru-RU': 'ru',
      };

      return langMap[htmlLang] || 'en';
    }

    function getLangForPage() {
      const lang = getLang();
      pageLang = langWords[lang] ? lang : 'en';
    }

    /** @type {Object.<string, number>} */
    const _locks = {};

    const lockGet = new Proxy(_locks, {
      get(target, prop) {
        return target[prop] || 0;
      },
      set(_target, _prop, _val) {
        return true;
      },
    });

    const lockSet = new Proxy(_locks, {
      get(target, prop) {
        if (target[prop] > MAX_ATTRIBUTE_VALUE) target[prop] = ATTRIBUTE_RESET_VALUE;
        return (target[prop] = (target[prop] || 0) + 1);
      },
      set(_target, _prop, _val) {
        return true;
      },
    });

    // note: xxxxxxxxxAsyncLock is not expected for calling multiple time in a short period.
    //       it is just to split the process into microTasks.

    const videosElementProvidedPromise = new PromiseExternal();
    const navigateFinishedPromise = new PromiseExternal();

    let isRightTabsInserted = false;
    const rightTabsProvidedPromise = new PromiseExternal();

    const infoExpanderElementProvidedPromise = new PromiseExternal();

    const pluginsDetected = {};
    const pluginDetectObserver = new MutationObserver(mutations => {
      let changeOnRoot = false;
      const newPlugins = [];
      const attributeChangedSet = new Set();
      for (const mutation of mutations) {
        if (mutation.target === document) changeOnRoot = true;
        let detected = '';
        switch (mutation.attributeName) {
          case 'data-ytlstm-new-layout':
          case 'data-ytlstm-overlay-text-shadow':
          case 'data-ytlstm-theater-mode':
            detected = 'external.ytlstm'; // YouTube Livestreams Theater Mode
            attributeChangedSet.add(detected);
            break;
        }
        if (detected && !pluginsDetected[detected]) {
          pluginsDetected[detected] = true;
          newPlugins.push(detected);
        }
      }
      if (elements.flexy && attributeChangedSet.has('external.ytlstm')) {
        elements.flexy.setAttribute(
          'tyt-external-ytlstm',
          document.querySelector('[data-ytlstm-theater-mode]') ? '1' : '0'
        );
      }
      if (changeOnRoot) {
        // prevent change of document.body
        pluginDetectObserver.observe(document.body, { attributes: true });
      }
      for (const detected of newPlugins) {
        const pluginItem = plugin[`${detected}`];
        if (pluginItem) {
          pluginItem.activate();
        } else {
          console.warn(`No Plugin Activator for ${detected}`);
        }
      }
    });

    pluginDetectObserver.observe(document.documentElement, { attributes: true });
    if (document.body) pluginDetectObserver.observe(document.body, { attributes: true });
    navigateFinishedPromise.then(() => {
      pluginDetectObserver.observe(document.documentElement, { attributes: true });
      if (document.body) pluginDetectObserver.observe(document.body, { attributes: true });
    });

    /**
     * Function to calculate if element can collapse
     * @param {*} _s - Parameter (unused but kept for compatibility)
     */
    const funcCanCollapse = function (_s) {
      const playlistElm = elements.playlist;
      const ytdFlexyElm = elements.flexy;
      // console.log(1882, chatElm, ytdFlexyElm)
      let doAttributeChange = 0;
      if (playlistElm && ytdFlexyElm) {
        if (playlistElm.closest('[hidden]')) {
          doAttributeChange = 2;
        } else if (playlistElm.hasAttribute000('collapsed')) {
          doAttributeChange = 2;
        } else {
          doAttributeChange = 1;
        }
      } else if (ytdFlexyElm) {
        doAttributeChange = 2;
      }
      if (doAttributeChange === 1) {
        if (ytdFlexyElm.getAttribute000('tyt-playlist-expanded') !== '') {
          ytdFlexyElm.setAttribute111('tyt-playlist-expanded', '');
        }
      } else if (doAttributeChange === 2) {
        if (ytdFlexyElm.hasAttribute000('tyt-playlist-expanded')) {
          ytdFlexyElm.removeAttribute000('tyt-playlist-expanded');
        }
      }
    };

    const aoChatAttrChangeFn = async lockId => {
      if (lockGet['aoChatAttrAsyncLock'] !== lockId) return;

      const chatElm = elements.chat;
      const ytdFlexyElm = elements.flexy;
      if (chatElm && ytdFlexyElm) {
        const isChatCollapsed = chatElm.hasAttribute000('collapsed');
        if (isChatCollapsed) {
          ytdFlexyElm.setAttribute111('tyt-chat-collapsed', '');
        } else {
          ytdFlexyElm.removeAttribute000('tyt-chat-collapsed');
        }

        ytdFlexyElm.setAttribute111('tyt-chat', isChatCollapsed ? '-' : '+');
      }
    };

    const aoPlayListAttrChangeFn = async lockId => {
      if (lockGet['aoPlayListAttrAsyncLock'] !== lockId) return;

      const playlistElm = elements.playlist;
      const ytdFlexyElm = elements.flexy;
      if (playlistElm && ytdFlexyElm) {
        if (playlistElm.hasAttribute000('collapsed')) {
          ytdFlexyElm.removeAttribute000('tyt-playlist-expanded');
        } else {
          ytdFlexyElm.setAttribute111('tyt-playlist-expanded', '');
        }
      } else if (ytdFlexyElm) {
        ytdFlexyElm.removeAttribute000('tyt-playlist-expanded');
      }
    };

    const aoChat = new MutationObserver(() => {
      Promise.resolve(lockSet['aoChatAttrAsyncLock'])
        .then(aoChatAttrChangeFn)
        .catch(err => handlePromiseError(err, 'aoChatAttrChange'));
    });

    const aoPlayList = new MutationObserver(() => {
      Promise.resolve(lockSet['aoPlayListAttrAsyncLock'])
        .then(aoPlayListAttrChangeFn)
        .catch(err => handlePromiseError(err, 'aoPlayListAttrChange'));
    });

    const aoComment = new MutationObserver(async mutations => {
      const commentsArea = elements.comments;
      const ytdFlexyElm = elements.flexy;

      //tyt-comments-video-id //tyt-comments-data-status // hidden
      if (!commentsArea) return;
      let bfHidden = false;
      let bfCommentsVideoId = false;
      let bfCommentDisabled = false;
      for (const mutation of mutations) {
        if (mutation.attributeName === 'hidden' && mutation.target === commentsArea) {
          bfHidden = true;
        } else if (
          mutation.attributeName === 'tyt-comments-video-id' &&
          mutation.target === commentsArea
        ) {
          bfCommentsVideoId = true;
        } else if (
          mutation.attributeName === 'tyt-comments-data-status' &&
          mutation.target === commentsArea
        ) {
          bfCommentDisabled = true;
        }
      }

      if (bfHidden) {
        if (!commentsArea.hasAttribute000('hidden')) {
          Promise.resolve(commentsArea)
            .then(eventMap['settingCommentsVideoId'])
            .catch(err => handlePromiseError(err, 'settingCommentsVideoId'));
        }

        Promise.resolve(lockSet['removeKeepCommentsScrollerLock'])
          .then(removeKeepCommentsScroller)
          .catch(err => handlePromiseError(err, 'removeKeepCommentsScroller'));
      }

      if ((bfHidden || bfCommentsVideoId || bfCommentDisabled) && ytdFlexyElm) {
        const commentsDataStatus = +commentsArea.getAttribute000('tyt-comments-data-status');
        if (commentsDataStatus === 2) {
          ytdFlexyElm.setAttribute111('tyt-comment-disabled', '');
        } else if (commentsDataStatus === 1) {
          ytdFlexyElm.removeAttribute000('tyt-comment-disabled');
        }

        Promise.resolve(lockSet['checkCommentsShouldBeHiddenLock'])
          .then(eventMap['checkCommentsShouldBeHidden'])
          .catch(err => handlePromiseError(err, 'checkCommentsShouldBeHidden'));

        const lockId = lockSet['rightTabReadyLock01'];
        await rightTabsProvidedPromise.then();
        if (lockGet['rightTabReadyLock01'] !== lockId) return;

        if (elements.comments !== commentsArea) return;
        if (commentsArea.isConnected === false) return;
        // console.log(7932, 'comments');

        if (commentsArea.closest('#tab-comments')) {
          const shouldTabVisible = !commentsArea.closest('[hidden]');
          document
            .querySelector('[tyt-tab-content="#tab-comments"]')
            .classList.toggle('tab-btn-hidden', !shouldTabVisible);
        }
      }
    });

    const ioComment = new IntersectionObserver(
      entries => {
        for (const entry of entries) {
          const target = entry.target;
          const cnt = insp(target);
          if (
            entry.isIntersecting &&
            target instanceof HTMLElement_ &&
            typeof cnt.calculateCanCollapse === 'function'
          ) {
            // lockSet['removeKeepCommentsScrollerLock']; // Commented out - no-op expression
            cnt.calculateCanCollapse(true);
            target.setAttribute111('io-intersected', '');
            const ytdFlexyElm = elements.flexy;
            if (ytdFlexyElm && !ytdFlexyElm.hasAttribute000('keep-comments-scroller')) {
              ytdFlexyElm.setAttribute111('keep-comments-scroller', '');
            }
          } else if (target.hasAttribute000('io-intersected')) {
            target.removeAttribute000('io-intersected');
          }
        }
      },
      {
        threshold: [0],
        rootMargin: '32px', // enlarging viewport for getting intersection earlier
      }
    );

    let bFixForResizedTabLater = false;
    let lastRoRightTabsWidth = 0;

    const roRightTabs = new ResizeObserver(entries => {
      const entry = entries[entries.length - 1];
      const width = Math.round(entry.borderBoxSize.inlineSize);

      if (lastRoRightTabsWidth !== width) {
        lastRoRightTabsWidth = width;
        if ((tabAStatus & 2) === 2) {
          bFixForResizedTabLater = false;
          Promise.resolve(1).then(eventMap['fixForTabDisplay']);
        } else {
          bFixForResizedTabLater = true;
        }
      }
    });

    /**
     * Switch to specified tab
     * @param {string|Element} activeLink - Tab link selector or element
     */
    const switchToTab = activeLink => {
      if (typeof activeLink === 'string') {
        activeLink = document.querySelector(`a[tyt-tab-content="${activeLink}"]`) || null;
      }

      const ytdFlexyElm = elements.flexy;
      const links = document.querySelectorAll('#material-tabs a[tyt-tab-content]');

      for (const link of links) {
        const content = document.querySelector(link.getAttribute000('tyt-tab-content'));
        if (!link || !content) continue;

        const isActive = link === activeLink;

        link.classList.toggle('active', isActive);
        content.classList.toggle('tab-content-hidden', !isActive);

        if (isActive) {
          content.removeAttribute000('tyt-hidden');
        } else if (!content.hasAttribute000('tyt-hidden')) {
          content.setAttribute111('tyt-hidden', '');
        }
      }

      const switchingTo = activeLink ? activeLink.getAttribute000('tyt-tab-content') : '';
      if (switchingTo) {
        lastTab = lastPanel = switchingTo;
      }

      if (ytdFlexyElm?.getAttribute000('tyt-chat') === '') {
        ytdFlexyElm.removeAttribute000('tyt-chat');
      }
      ytdFlexyElm?.setAttribute111('tyt-tab', switchingTo);

      if (switchingTo) {
        bFixForResizedTabLater = false;
        Promise.resolve(0).then(eventMap['fixForTabDisplay']);
      }
    };

    let tabAStatus = 0;

    /**
     * Calculate status flags based on element attributes
     * @param {number} r - Initial result value
     * @param {number} flag - Flags to check (bitwise)
     * @returns {number} Calculated status flags
     */
    const calculationFn = (r = 0, flag) => {
      const ytdFlexyElm = elements.flexy;
      if (!ytdFlexyElm) return r;
      if (flag & 1) {
        r |= 1;
        if (!ytdFlexyElm.hasAttribute000('theater')) r -= 1;
      }
      if (flag & 2) {
        r |= 2;
        if (!ytdFlexyElm.getAttribute000('tyt-tab')) r -= 2;
      }
      if (flag & 4) {
        r |= 4;
        if (ytdFlexyElm.getAttribute000('tyt-chat') !== '-') r -= 4;
      }
      if (flag & 8) {
        r |= 8;
        if (ytdFlexyElm.getAttribute000('tyt-chat') !== '+') r -= 8;
      }
      if (flag & 16) {
        r |= 16;
        if (!ytdFlexyElm.hasAttribute000('is-two-columns_')) r -= 16;
      }
      if (flag & 32) {
        r |= 32;
        if (!ytdFlexyElm.hasAttribute000('tyt-egm-panel_')) r -= 32;
      }
      if (flag & 64) {
        r |= 64;
        if (!document.fullscreenElement) r -= 64;
      }

      if (flag & 128) {
        r |= 128;
        if (!ytdFlexyElm.hasAttribute000('tyt-playlist-expanded')) r -= 128;
      }
      if (flag & 4096) {
        r |= 4096;
        if (ytdFlexyElm.getAttribute('tyt-external-ytlstm') !== '1') r -= 4096;
      }
      return r;
    };

    /**
     * Check if theater mode is active
     * @returns {boolean} True if theater mode is active
     */
    function isTheater() {
      return Boolean(elements.flexy?.hasAttribute000('theater'));
    }

    /**
     * Get theater mode toggle button
     * @returns {HTMLButtonElement|null} Theater button or null
     */
    function getTheaterButton() {
      return document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button');
    }

    /**
     * Enable theater mode
     * @internal Reserved for future use
     */
    // eslint-disable-next-line no-unused-vars
    function ytBtnSetTheater() {
      if (!isTheater()) {
        getTheaterButton()?.click();
      }
    }

    /**
     * Disable theater mode
     */
    function ytBtnCancelTheater() {
      if (isTheater()) {
        getTheaterButton()?.click();
      }
    }

    /**
     * Get element with most nested children (best match)
     * @param {string} selector - CSS selector
     * @returns {Element|null} Element with most children or null
     */
    function getSuitableElement(selector) {
      const elements = document.querySelectorAll(selector);
      let bestIndex = -1;
      let maxDepth = -1;

      for (let i = 0; i < elements.length; i++) {
        const depth = elements[i].getElementsByTagName('*').length;
        if (depth > maxDepth) {
          maxDepth = depth;
          bestIndex = i;
        }
      }

      return bestIndex >= 0 ? elements[bestIndex] : null;
    }

    /**
     * Expand YouTube live chat
     */
    function ytBtnExpandChat() {
      const dom = getSuitableElement('ytd-live-chat-frame#chat');
      const cnt = insp(dom);

      if (cnt && typeof cnt.collapsed === 'boolean') {
        if (typeof cnt.setCollapsedState === 'function') {
          cnt.setCollapsedState({
            setLiveChatCollapsedStateAction: {
              collapsed: false,
            },
          });
          if (cnt.collapsed === false) return;
        }
        cnt.collapsed = false;
        if (cnt.collapsed === false) return;
        if (cnt.isHiddenByUser === true && cnt.collapsed === true) {
          cnt.isHiddenByUser = false;
          cnt.collapsed = false;
        }
      }

      let button = document.querySelector(
        'ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button'
      );
      if (button) {
        button =
          button.querySelector000('div.yt-spec-touch-feedback-shape') ||
          button.querySelector000('ytd-toggle-button-renderer');
        button?.click();
      }
    }

    /**
     * Collapse YouTube live chat
     */
    /**
     * Collapse YouTube live chat panel
     */
    function ytBtnCollapseChat() {
      const dom = getSuitableElement('ytd-live-chat-frame#chat');
      const cnt = insp(dom);

      if (cnt && typeof cnt.collapsed === 'boolean') {
        if (typeof cnt.setCollapsedState === 'function') {
          cnt.setCollapsedState({
            setLiveChatCollapsedStateAction: {
              collapsed: true,
            },
          });
          if (cnt.collapsed === true) return;
        }
        cnt.collapsed = true;
        if (cnt.collapsed === true) return;
        if (cnt.isHiddenByUser === false && cnt.collapsed === false) {
          cnt.isHiddenByUser = true;
          cnt.collapsed = true;
        }
      }

      let button = document.querySelector(
        'ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button'
      );
      if (button) {
        button =
          button.querySelector000('div.yt-spec-touch-feedback-shape') ||
          button.querySelector000('ytd-toggle-button-renderer');
        button?.click();
      }
    }

    /**
     * Control YouTube engagement panels (show/hide)
     * @param {Array|Object} arr - Array of panel actions or single action object
     */
    function ytBtnEgmPanelCore(arr) {
      if (!arr) return;
      if (!('length' in arr)) arr = [arr];

      const ytdFlexyElm = elements.flexy;
      if (!ytdFlexyElm) return;

      const actions = [];

      for (const entry of arr) {
        if (!entry) continue;

        const { panelId, toHide, toShow } = entry;

        if (toHide === true && !toShow) {
          actions.push({
            changeEngagementPanelVisibilityAction: {
              targetId: panelId,
              visibility: 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN',
            },
          });
        } else if (toShow === true && !toHide) {
          actions.push({
            showEngagementPanelEndpoint: {
              panelIdentifier: panelId,
            },
          });
        }
      }

      if (actions.length > 0) {
        const cnt = insp(ytdFlexyElm);
        cnt.resolveCommand(
          {
            signalServiceEndpoint: {
              signal: 'CLIENT_SIGNAL',
              actions: actions,
            },
          },
          {},
          false
        );
      }
    }

    /*
            function ytBtnCloseEngagementPanel( s) {
              //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
           
              let panelId = s.getAttribute('target-id')
              scriptletDeferred.debounce(() => {
                document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
                  detail: {
                    panelId,
                    toHide: true
                  }
                }))
              })
           
            }
        
            /**
             * Close all expanded YouTube engagement panels
             */
    function ytBtnCloseEngagementPanels() {
      const actions = [];
      for (const panelElm of document.querySelectorAll(
        `ytd-watch-flexy[tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
      )) {
        if (
          panelElm.getAttribute('visibility') === 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED' &&
          !panelElm.closest('[hidden]')
        ) {
          actions.push({
            panelId: panelElm.getAttribute000('target-id'),
            toHide: true,
          });
        }
      }
      ytBtnEgmPanelCore(actions);
    }

    /**
     * Open YouTube playlist panel
     */
    function ytBtnOpenPlaylist() {
      const cnt = insp(elements.playlist);
      if (cnt && typeof cnt.collapsed === 'boolean') {
        cnt.collapsed = false;
      }
    }

    /**
     * Close YouTube playlist panel
     */
    function ytBtnClosePlaylist() {
      const cnt = insp(elements.playlist);
      if (cnt && typeof cnt.collapsed === 'boolean') {
        cnt.collapsed = true;
      }
    }

    const updateChatLocation498 = function () {
      if (this.is !== 'ytd-watch-grid') {
        this.updatePageMediaQueries();
        this.schedulePlayerSizeUpdate_();
      }
    };

    const mirrorNodeWS = new WeakMap();

    const dummyNode = document.createElement('noscript');

    const __j4836__ = Symbol();
    const __j5744__ = Symbol(); // original element
    const __j5733__ = Symbol(); // __lastChanged__

    const monitorDataChangedByDOMMutation = async function (_mutations) {
      const nodeWR = this;
      const node = kRef(nodeWR);
      if (!node) return;

      const cnt = insp(node);
      const __lastChanged__ = cnt[__j5733__];

      const val = cnt.data ? cnt.data[__j4836__] || 1 : 0;

      if (__lastChanged__ !== val) {
        cnt[__j5733__] = val > 0 ? (cnt.data[__j4836__] = Date.now()) : 0;

        await Promise.resolve(); // required for making sufficient delay for data rendering
        attributeInc(node, 'tyt-data-change-counter'); // next macro task
      }
    };

    const moChangeReflection = function (mutations) {
      const nodeWR = this;
      const node = kRef(nodeWR);
      if (!node) return;
      const originElement = kRef(node[__j5744__] || null) || null;
      if (!originElement) return;

      const cnt = insp(node);
      const oriCnt = insp(originElement);

      if (mutations) {
        let bfDataChangeCounter = false;
        for (const mutation of mutations) {
          if (
            mutation.attributeName === 'tyt-clone-refresh-count' &&
            mutation.target === originElement
          ) {
            bfDataChangeCounter = true;
          } else if (
            mutation.attributeName === 'tyt-data-change-counter' &&
            mutation.target === originElement
          ) {
            bfDataChangeCounter = true;
          }
        }
        if (bfDataChangeCounter && oriCnt.data) {
          node.replaceWith(dummyNode);
          cnt.data = Object.assign({}, oriCnt.data);
          dummyNode.replaceWith(node);
        }
      }
    };

    /**
     * Increment attribute value with overflow protection
     * @param {Element} elm - Element to modify
     * @param {string} prop - Attribute name
     * @returns {number} New attribute value
     */
    const attributeInc = (elm, prop) => {
      let v = (+elm.getAttribute000(prop) || 0) + 1;
      if (v > MAX_ATTRIBUTE_VALUE) v = ATTRIBUTE_RESET_VALUE;
      elm.setAttribute000(prop, v);
      return v;
    };

    /**
     * Validates if a string is a valid YouTube channel ID
     * Format: UC[-_a-zA-Z0-9+=.]{22}
     * @see https://support.google.com/youtube/answer/6070344?hl=en
     * @param {string} x - The string to validate
     * @returns {boolean} True if valid channel ID
     */
    const isChannelId = x => {
      return typeof x === 'string' && x.length === 24 && /^UC[-_a-zA-Z0-9+=.]{22}$/.test(x);
    };

    /**
     * Fix and organize info panel layout
     * @param {number|null} lockId - Lock identifier for concurrent execution control
     */
    const infoFix = lockId => {
      if (lockId !== null && lockGet['infoFixLock'] !== lockId) return;
      const infoExpander = elements.infoExpander;
      const infoContainer =
        (infoExpander ? infoExpander.parentNode : null) || document.querySelector('#tab-info');
      const ytdFlexyElm = elements.flexy;
      if (!infoContainer || !ytdFlexyElm) return;
      if (infoExpander) {
        const match =
          infoExpander.matches('#tab-info > [class]') ||
          infoExpander.matches('#tab-info > [tyt-main-info]');
        if (!match) return;
      }

      const requireElements = [
        ...document.querySelectorAll(
          'ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"] > *, ytd-watch-metadata.ytd-watch-flexy #extra-content > *'
        ),
      ]
        .filter(elm => {
          return typeof elm.is == 'string';
        })
        .map(elm => {
          const is = elm.is;
          while (elm instanceof HTMLElement_) {
            const q = [...elm.querySelectorAll(is)].filter(e => insp(e).data);
            if (q.length >= 1) return q[0];
            elm = elm.parentNode;
          }
        })
        .filter(elm => !!elm && typeof elm.is === 'string');

      const source = requireElements.map(entry => {
        const inst = insp(entry);
        return {
          data: inst.data,
          tag: inst.is,
          elm: entry,
        };
      });

      let noscript_ = document.querySelector('noscript#aythl');
      if (!noscript_) {
        noscript_ = document.createElement('noscript');
        noscript_.id = 'aythl';

        inPageRearrange = true;
        ytdFlexyElm.insertBefore000(noscript_, ytdFlexyElm.firstChild);
        inPageRearrange = false;
      }
      const noscript = noscript_;

      let requiredUpdate = false;
      const mirrorElmSet = new Set();
      const targetParent = infoContainer;
      for (const { data, tag: tag, elm: s } of source) {
        let mirrorNode = mirrorNodeWS.get(s);
        mirrorNode = mirrorNode ? kRef(mirrorNode) : mirrorNode;
        if (!mirrorNode) {
          const cnt = insp(s);
          const cProto = cnt.constructor.prototype;

          const element = document.createElement(tag);
          noscript.appendChild(element);
          mirrorNode = element;
          mirrorNode[__j5744__] = mWeakRef(s);

          const nodeWR = mWeakRef(mirrorNode);

          new MutationObserver(moChangeReflection.bind(nodeWR)).observe(s, {
            attributes: true,
            attributeFilter: ['tyt-clone-refresh-count', 'tyt-data-change-counter'],
          });

          s.jy8432 = 1;
          if (
            !(cProto instanceof Node) &&
            !cProto._dataChanged496 &&
            typeof cProto._createPropertyObserver === 'function'
          ) {
            cProto._dataChanged496 = function () {
              const cnt = this;
              const node = cnt.hostElement || cnt;
              if (node.jy8432) {
                attributeInc(node, 'tyt-data-change-counter');
              }
            };
            cProto._createPropertyObserver('data', '_dataChanged496', undefined);
          } else if (
            !(cProto instanceof Node) &&
            !cProto._dataChanged496 &&
            cProto.useSignals === true &&
            insp(s).signalProxy
          ) {
            const dataSignal = cnt?.signalProxy?.signalCache?.data;
            if (
              dataSignal &&
              typeof dataSignal.setWithPath === 'function' &&
              !dataSignal.setWithPath573 &&
              !dataSignal.controller573
            ) {
              dataSignal.controller573 = mWeakRef(cnt);
              dataSignal.setWithPath573 = dataSignal.setWithPath;
              dataSignal.setWithPath = function () {
                const cnt = kRef(this.controller573 || null) || null;
                cnt &&
                  typeof cnt._dataChanged496k === 'function' &&
                  Promise.resolve(cnt)
                    .then(cnt._dataChanged496k)
                    .catch(err => handlePromiseError(err, 'setWithPath_dataChanged496k'));
                return this.setWithPath573(...arguments);
              };
              cProto._dataChanged496 = function () {
                const cnt = this;
                const node = cnt.hostElement || cnt;
                if (node.jy8432) {
                  attributeInc(node, 'tyt-data-change-counter');
                }
              };
              cProto._dataChanged496k = cnt => cnt._dataChanged496();
            }
          }

          if (!cProto._dataChanged496) {
            new MutationObserver(
              monitorDataChangedByDOMMutation.bind(mirrorNode[__j5744__])
            ).observe(s, { attributes: true, childList: true, subtree: true });
          }

          mirrorNodeWS.set(s, nodeWR);
          requiredUpdate = true;
        } else {
          if (mirrorNode.parentNode !== targetParent) {
            requiredUpdate = true;
          }
        }
        if (!requiredUpdate) {
          const cloneNodeCnt = insp(mirrorNode);
          if (cloneNodeCnt.data !== data) {
            // if(mirrorNode.parentNode !== noscript){
            //   noscript.appendChild(mirrorNode);
            // }
            // mirrorNode.replaceWith(dummyNode);
            // cloneNodeCnt.data = data;
            // dummyNode.replaceWith(mirrorNode);
            requiredUpdate = true;
          }
        }

        mirrorElmSet.add(mirrorNode);
        source.mirrored = mirrorNode;
      }

      const mirroElmArr = [...mirrorElmSet];
      mirrorElmSet.clear();

      if (!requiredUpdate) {
        let e = infoExpander ? -1 : 0;
        // DOM Tree Check
        for (let n = targetParent.firstChild; n instanceof Node; n = n.nextSibling) {
          const target = e < 0 ? infoExpander : mirroElmArr[e];
          e++;
          if (n !== target) {
            // target can be undefined if index overflow
            requiredUpdate = true;
            break;
          }
        }
        if (!requiredUpdate && e !== mirroElmArr.length + 1) requiredUpdate = true;
      }

      if (requiredUpdate) {
        if (infoExpander) {
          targetParent.assignChildren111(null, infoExpander, mirroElmArr);
        } else {
          targetParent.replaceChildren000(...mirroElmArr);
        }
        for (const mirrorElm of mirroElmArr) {
          // trigger data assignment and record refresh count by manual update
          const j = attributeInc(mirrorElm, 'tyt-clone-refresh-count');
          const oriElm = kRef(mirrorElm[__j5744__] || null) || null;
          if (oriElm) {
            oriElm.setAttribute111('tyt-clone-refresh-count', j);
          }
        }
      }

      mirroElmArr.length = 0;
      source.length = 0;
    };

    /**
     * Fix and optimize secondary layout structure
     * @param {number} lockId - Lock identifier for concurrent execution control
     */
    const layoutFix = lockId => {
      if (lockGet['layoutFixLock'] !== lockId) return;
      // console.log('((layoutFix))')

      const secondaryWrapper = document.querySelector(
        '#secondary-inner.style-scope.ytd-watch-flexy > secondary-wrapper'
      );
      // console.log(3838, !!chatContainer, !!(secondaryWrapper && secondaryInner), secondaryInner?.firstChild, secondaryInner?.lastChild , secondaryWrapper?.parentNode === secondaryInner)
      if (secondaryWrapper) {
        const secondaryInner = secondaryWrapper.parentNode;

        const chatContainer = document.querySelector(
          '#columns.style-scope.ytd-watch-flexy [tyt-chat-container]'
        );
        if (
          secondaryInner.firstChild !== secondaryInner.lastChild ||
          (chatContainer && !chatContainer.closest('secondary-wrapper'))
        ) {
          // console.log(38381)
          const w = [];
          const w2 = [];
          for (
            let node = secondaryInner.firstChild;
            node instanceof Node;
            node = node.nextSibling
          ) {
            if (node === chatContainer && chatContainer) {
            } else if (node === secondaryWrapper) {
              for (
                let node2 = secondaryWrapper.firstChild;
                node2 instanceof Node;
                node2 = node2.nextSibling
              ) {
                if (node2 === chatContainer && chatContainer) {
                } else {
                  if (node2.id === 'right-tabs' && chatContainer) {
                    w2.push(chatContainer);
                  }
                  w2.push(node2);
                }
              }
            } else {
              w.push(node);
            }
          }
          // console.log('qww', w, w2)

          inPageRearrange = true;
          secondaryWrapper.replaceChildren000(...w, ...w2);
          inPageRearrange = false;
          const chatElm = elements.chat;
          const chatCnt = insp(chatElm);
          if (
            chatCnt &&
            typeof chatCnt.urlChanged === 'function' &&
            secondaryWrapper.contains(chatElm)
          ) {
            // setTimeout(() => chatCnt.urlChanged, 136);
            if (typeof chatCnt.urlChangedAsync12 === 'function') {
              DEBUG_5085 && console.log('elements.chat urlChangedAsync12', 61);
              chatCnt.urlChanged();
            } else {
              DEBUG_5085 && console.log('elements.chat urlChangedAsync12', 62);
              setTimeout(() => chatCnt.urlChanged(), 136);
            }
          }
        }
      }
    };

    let lastPanel = '';
    let lastTab = '';
    // let fixInitialTabState = 0;

    const aoEgmPanels = new MutationObserver(() => {
      // console.log(5094,3);
      Promise.resolve(lockSet['updateEgmPanelsLock'])
        .then(updateEgmPanels)
        .catch(err => handlePromiseError(err, 'aoEgmPanels_updateEgmPanels'));
    });

    const removeKeepCommentsScroller = async lockId => {
      if (lockGet['removeKeepCommentsScrollerLock'] !== lockId) return;
      await Promise.resolve();
      if (lockGet['removeKeepCommentsScrollerLock'] !== lockId) return;
      const ytdFlexyFlm = elements.flexy;
      if (ytdFlexyFlm) {
        ytdFlexyFlm.removeAttribute000('keep-comments-scroller');
      }
    };

    const updateEgmPanels = async lockId => {
      if (lockId !== lockGet['updateEgmPanelsLock']) return;
      await navigateFinishedPromise.then().catch(console.warn);
      if (lockId !== lockGet['updateEgmPanelsLock']) return;
      // console.log('updateEgmPanels::called');
      const ytdFlexyElm = elements.flexy;
      if (!ytdFlexyElm) return;
      let newVisiblePanels = [];
      let newHiddenPanels = [];
      let allVisiblePanels = [];
      for (const panelElm of document.querySelectorAll('[tyt-egm-panel][target-id][visibility]')) {
        const visibility = panelElm.getAttribute000('visibility');

        if (visibility === 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN' || panelElm.closest('[hidden]')) {
          if (panelElm.hasAttribute000('tyt-visible-at')) {
            panelElm.removeAttribute000('tyt-visible-at');
            newHiddenPanels.push(panelElm);
          }
        } else if (
          visibility === 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED' &&
          !panelElm.closest('[hidden]')
        ) {
          const visibleAt = panelElm.getAttribute000('tyt-visible-at');
          if (!visibleAt) {
            panelElm.setAttribute111('tyt-visible-at', Date.now());
            newVisiblePanels.push(panelElm);
          }
          allVisiblePanels.push(panelElm);
        }
      }
      if (newVisiblePanels.length >= 1 && allVisiblePanels.length >= 2) {
        const targetVisible = newVisiblePanels[newVisiblePanels.length - 1];

        const actions = [];
        for (const panelElm of allVisiblePanels) {
          if (panelElm === targetVisible) continue;
          actions.push({
            panelId: panelElm.getAttribute000('target-id'),
            toHide: true,
          });
        }

        if (actions.length >= 1) {
          ytBtnEgmPanelCore(actions);
        }
      }
      if (allVisiblePanels.length >= 1) {
        ytdFlexyElm.setAttribute111('tyt-egm-panel_', '');
      } else {
        ytdFlexyElm.removeAttribute000('tyt-egm-panel_');
      }
      newVisiblePanels.length = 0;
      newVisiblePanels = null;
      newHiddenPanels.length = 0;
      newHiddenPanels = null;
      allVisiblePanels.length = 0;
      allVisiblePanels = null;
    };

    const checkElementExist = (css, exclude) => {
      for (const p of document.querySelectorAll(css)) {
        if (!p.closest(exclude)) return p;
      }
      return null;
    };

    let fixInitialTabStateK = 0;

    const { handleNavigateFactory } = (() => {
      let isLoadStartListened = false;

      function findLcComment(lc) {
        if (arguments.length === 1) {
          const element = document.querySelector(
            `#tab-comments ytd-comments ytd-comment-renderer #header-author a[href*="lc=${lc}"]`
          );
          if (element) {
            const commentRendererElm = closestFromAnchor.call(element, 'ytd-comment-renderer');
            if (commentRendererElm && lc) {
              return {
                lc,
                commentRendererElm,
              };
            }
          }
        } else if (arguments.length === 0) {
          const element = document.querySelector(
            `#tab-comments ytd-comments ytd-comment-renderer > #linked-comment-badge span:not(:empty)`
          );
          if (element) {
            const commentRendererElm = closestFromAnchor.call(element, 'ytd-comment-renderer');
            if (commentRendererElm) {
              const header = _querySelector.call(commentRendererElm, '#header-author');
              if (header) {
                const anchor = _querySelector.call(header, 'a[href*="lc="]');
                if (anchor) {
                  const href = anchor.getAttribute('href') || '';
                  const m = /[&?]lc=([\w_.-]+)/.exec(href); // dot = sub-comment
                  if (m) {
                    lc = m[1];
                  }
                }
              }
            }
            if (commentRendererElm && lc) {
              return {
                lc,
                commentRendererElm,
              };
            }
          }
        }

        return null;
      }

      function lcSwapFuncA(targetLcId, currentLcId) {
        let done = 0;
        try {
          // console.log(currentLcId, targetLcId)

          const r1 = findLcComment(currentLcId).commentRendererElm;
          const r2 = findLcComment(targetLcId).commentRendererElm;

          if (
            typeof insp(r1).data.linkedCommentBadge === 'object' &&
            typeof insp(r2).data.linkedCommentBadge === 'undefined'
          ) {
            const p = Object.assign({}, insp(r1).data.linkedCommentBadge);

            if (((p || 0).metadataBadgeRenderer || 0).trackingParams) {
              delete p.metadataBadgeRenderer.trackingParams;
            }

            const v1 = findContentsRenderer(r1);
            const v2 = findContentsRenderer(r2);

            if (
              v1.parent === v2.parent &&
              (v2.parent.nodeName === 'YTD-COMMENTS' ||
                v2.parent.nodeName === 'YTD-ITEM-SECTION-RENDERER')
            ) {
            } else {
              // currently not supported
              return false;
            }

            if (v2.index >= 0) {
              if (v2.parent.nodeName === 'YTD-COMMENT-REPLIES-RENDERER') {
                if (lcSwapFuncB(targetLcId, currentLcId, p)) {
                  done = 1;
                }

                done = 1;
              } else {
                const v2pCnt = insp(v2.parent);
                const v2Conents = (v2pCnt.data || 0).contents || 0;
                if (!v2Conents) console.warn('v2Conents is not found');

                v2pCnt.data = Object.assign({}, v2pCnt.data, {
                  contents: [].concat(
                    [v2Conents[v2.index]],
                    v2Conents.slice(0, v2.index),
                    v2Conents.slice(v2.index + 1)
                  ),
                });

                if (lcSwapFuncB(targetLcId, currentLcId, p)) {
                  done = 1;
                }
              }
            }
          }
        } catch (e) {
          console.warn(e);
        }
        return done === 1;
      }

      function lcSwapFuncB(targetLcId, currentLcId, _p) {
        let done = 0;
        try {
          const r1 = findLcComment(currentLcId).commentRendererElm;
          const r1cnt = insp(r1);
          const r2 = findLcComment(targetLcId).commentRendererElm;
          const r2cnt = insp(r2);

          const r1d = r1cnt.data;
          const p = Object.assign({}, _p);
          r1d.linkedCommentBadge = null;
          delete r1d.linkedCommentBadge;

          const q = Object.assign({}, r1d);
          q.linkedCommentBadge = null;
          delete q.linkedCommentBadge;

          r1cnt.data = Object.assign({}, q);
          r2cnt.data = Object.assign({}, r2cnt.data, { linkedCommentBadge: p });

          done = 1;
        } catch (e) {
          console.warn(e);
        }
        return done === 1;
      }

      const loadStartFx = async evt => {
        const media = (evt || 0).target || 0;
        if (media.nodeName === 'VIDEO' || media.nodeName === 'AUDIO') {
        } else return;

        const newMedia = media;

        const media1 = common.getMediaElement(0); // document.querySelector('#movie_player video[src]');
        const media2 = common.getMediaElements(2); // document.querySelectorAll('ytd-browse[role="main"] video[src]');

        if (media1 !== null && media2.length > 0) {
          if (newMedia !== media1 && media1.paused === false) {
            if (isVideoPlaying(media1)) {
              Promise.resolve(newMedia)
                .then(video => video.paused === false && video.pause())
                .catch(console.warn);
            }
          } else if (newMedia === media1) {
            for (const s of media2) {
              if (s.paused === false) {
                Promise.resolve(s)
                  .then(s => s.paused === false && s.pause())
                  .catch(console.warn);
                break;
              }
            }
          } else {
            Promise.resolve(media1)
              .then(video1 => video1.paused === false && video1.pause())
              .catch(console.warn);
          }
        }
      };

      const getBrowsableEndPoint = req => {
        let valid = false;
        let endpoint = req ? req.command : null;
        if (
          endpoint &&
          (endpoint.commandMetadata || 0).webCommandMetadata &&
          endpoint.watchEndpoint
        ) {
          const videoId = endpoint.watchEndpoint.videoId;
          const url = endpoint.commandMetadata.webCommandMetadata.url;

          if (typeof videoId === 'string' && typeof url === 'string' && url.indexOf('lc=') > 0) {
            const m = /^\/watch\?v=([\w_-]+)&lc=([\w_.-]+)$/.exec(url); // dot = sub-comment
            if (m && m[1] === videoId) {
              /*
                                          {
                                            "style": "BADGE_STYLE_TYPE_SIMPLE",
                                            "label": "注目のコメント",
                                            "trackingParams": "XXXXXX"
                                        }
                                          */

              const targetLc = findLcComment(m[2]);
              const currentLc = targetLc ? findLcComment() : null;

              if (targetLc && currentLc) {
                const done =
                  targetLc.lc === currentLc.lc ? 1 : lcSwapFuncA(targetLc.lc, currentLc.lc) ? 1 : 0;

                if (done === 1) {
                  common.xReplaceState(history.state, url);
                  return;
                }
              }
            }
          }
        }

        /*
                            
                            {
                              "type": 0,
                              "command": endpoint,
                              "form": {
                                "tempData": {},
                                "reload": false
                              }
                            }
                  
                        */

        if (
          endpoint &&
          (endpoint.commandMetadata || 0).webCommandMetadata &&
          endpoint.browseEndpoint &&
          isChannelId(endpoint.browseEndpoint.browseId)
        ) {
          valid = true;
        } else if (
          endpoint &&
          (endpoint.browseEndpoint || endpoint.searchEndpoint) &&
          !endpoint.urlEndpoint &&
          !endpoint.watchEndpoint
        ) {
          if (endpoint.browseEndpoint && endpoint.browseEndpoint.browseId === 'FEwhat_to_watch') {
            // valid = false;
            const playerMedia = common.getMediaElement(1);
            if (playerMedia && playerMedia.paused === false) valid = true; // home page
          } else if (endpoint.commandMetadata && endpoint.commandMetadata.webCommandMetadata) {
            const meta = endpoint.commandMetadata.webCommandMetadata;
            if (meta && /*meta.apiUrl &&*/ meta.url && meta.webPageType) {
              valid = true;
            }
          }
        }

        if (!valid) endpoint = null;

        return endpoint;
      };

      const shouldUseMiniPlayer = () => {
        const isSubTypeExist = document.querySelector(
          'ytd-page-manager#page-manager > ytd-browse[page-subtype]'
        );

        if (isSubTypeExist) return true;

        const movie_player = [...document.querySelectorAll('#movie_player')].filter(
          e => !e.closest('[hidden]')
        )[0];
        if (movie_player) {
          const media = qsOne(movie_player, 'video[class], audio[class]');
          if (
            media &&
            media.currentTime > 3 &&
            media.duration - media.currentTime > 3 &&
            media.paused === false
          ) {
            return true;
          }
        }
        return false;
        // return true;
        // return !!document.querySelector('ytd-page-manager#page-manager > ytd-browse[page-subtype]');
      };

      const conditionFulfillment = req => {
        const command = req ? req.command : null;
        DEBUG_handleNavigateFactory && console.log('handleNavigateFactory - 0801', command);
        if (!command) return;

        if (command && (command.commandMetadata || 0).webCommandMetadata && command.watchEndpoint) {
        } else if (
          command &&
          (command.commandMetadata || 0).webCommandMetadata &&
          command.browseEndpoint &&
          isChannelId(command.browseEndpoint.browseId)
        ) {
        } else if (
          command &&
          (command.browseEndpoint || command.searchEndpoint) &&
          !command.urlEndpoint &&
          !command.watchEndpoint
        ) {
        } else {
          return false;
        }

        if (!shouldUseMiniPlayer()) return false;

        /*
                          // user would like to switch page immediately without playing the video;
                          // attribute appear after playing video for more than 2s
                          if (!document.head.dataset.viTime) return false;
                          else {
                            let currentVideo = common.getMediaElement(0);
                            if (currentVideo && currentVideo.readyState > currentVideo.HAVE_CURRENT_DATA && currentVideo.currentTime > 2.2 && currentVideo.duration - 2.2 < currentVideo.currentTime) {
                              // disable miniview browsing if the media is near to the end
                              return false;
                            }
                          }
                        */

        if (pageType !== 'watch') return false;

        // 2025.10.16 - ignore ytp-miniplayer-button existance
        // if (!checkElementExist('ytd-watch-flexy #player button.ytp-miniplayer-button.ytp-button', '[hidden]')) {
        //   return false;
        // }

        // DEBUG_handleNavigateFactory && console.log("handleNavigateFactory - 0805");

        return true;
      };

      let u38 = 0;
      const fixChannelAboutPopup = async t38 => {
        let promise = new PromiseExternal();
        const f = () => {
          promise && promise.resolve();
          promise = null;
        };
        document.addEventListener('yt-navigate-finish', f, false);
        await promise.then();
        promise = null;
        document.removeEventListener('yt-navigate-finish', f, false);
        if (t38 !== u38) return;
        setTimeout(() => {
          const currentAbout = [...document.querySelectorAll('ytd-about-channel-renderer')].filter(
            e => !e.closest('[hidden]')
          )[0];
          let okay = false;
          if (!currentAbout) okay = true;
          else {
            const popupContainer = currentAbout.closest('ytd-popup-container');
            if (popupContainer) {
              const cnt = insp(popupContainer);
              let arr = null;
              try {
                arr = cnt.handleGetOpenedPopupsAction_();
              } catch {}
              if (arr && arr.length === 0) okay = true;
            } else {
              okay = false;
            }
          }
          if (okay) {
            const descriptionModel = [
              ...document.querySelectorAll('yt-description-preview-view-model'),
            ].filter(e => !e.closest('[hidden]'))[0];
            if (descriptionModel) {
              const button = [...descriptionModel.querySelectorAll('button')].filter(
                e => !e.closest('[hidden]') && `${e.textContent}`.trim().length > 0
              )[0];
              if (button) {
                button.click();
              }
            }
          }
        }, 80);
      };
      const handleNavigateFactory = handleNavigate => {
        return function (req) {
          if (u38 > MAX_ATTRIBUTE_VALUE) u38 = ATTRIBUTE_RESET_VALUE;
          const t38 = ++u38;

          const $this = this;
          const $arguments = arguments;

          let endpoint = null;

          if (conditionFulfillment(req)) {
            endpoint = getBrowsableEndPoint(req);
          }

          if (!endpoint || !shouldUseMiniPlayer()) return handleNavigate.apply($this, $arguments);

          // console.log('tabview-script-handleNavigate')

          const ytdAppElm = document.querySelector('ytd-app');
          const ytdAppCnt = insp(ytdAppElm);

          let object = null;
          try {
            object = ytdAppCnt.data.response.currentVideoEndpoint.watchEndpoint || null;
          } catch {
            object = null;
          }

          if (typeof object !== 'object') object = null;

          const once = { once: true }; // browsers supporting async function can also use once option.

          if (object !== null && !('playlistId' in object)) {
            let wObject = mWeakRef(object);

            const N = 3;

            let count = 0;

            /*
                                      
                                      rcb(b) => a = playlistId = undefinded
                                      
                                      var scb = function(a, b, c, d) {
                                              a.isInitialized() && (B("kevlar_miniplayer_navigate_to_shorts_killswitch") ? c || d ? ("watch" !== Xu(b) && "shorts" !== Xu(b) && os(a.miniplayerEl, "yt-cache-miniplayer-page-action", [b]),
                                              qs(a.miniplayerEl, "yt-deactivate-miniplayer-action")) : "watch" === Xu(b) && rcb(b) && (qt.getInstance().playlistWatchPageActivation = !0,
                                              a.activateMiniplayer(b)) : c ? ("watch" !== Xu(b) && os(a.miniplayerEl, "yt-cache-miniplayer-page-action", [b]),
                                              qs(a.miniplayerEl, "yt-deactivate-miniplayer-action")) : d ? qs(a.miniplayerEl, "yt-pause-miniplayer-action") : "watch" === Xu(b) && rcb(b) && (qt.getInstance().playlistWatchPageActivation = !0,
                                              a.activateMiniplayer(b)))
                                          };
                            
                                    */

            Object.defineProperty(kRef(wObject) || {}, 'playlistId', {
              get() {
                count++;
                if (count === N) {
                  delete this.playlistId;
                }
                return '*';
              },
              set(value) {
                delete this.playlistId; // remove property definition
                this.playlistId = value; // assign as normal property
              },
              enumerable: false,
              configurable: true,
            });

            let playlistClearout = null;

            let timeoutid = 0;
            Promise.race([
              new Promise(r => {
                timeoutid = setTimeout(r, 4000);
              }),
              new Promise(r => {
                playlistClearout = () => {
                  if (timeoutid > 0) {
                    clearTimeout(timeoutid);
                    timeoutid = 0;
                  }
                  r();
                };
                document.addEventListener('yt-page-type-changed', playlistClearout, once);
              }),
            ])
              .then(() => {
                if (timeoutid !== 0) {
                  playlistClearout &&
                    document.removeEventListener('yt-page-type-changed', playlistClearout, once);
                  timeoutid = 0;
                }
                playlistClearout = null;
                count = N - 1;
                const object = kRef(wObject);
                wObject = null;
                return object ? object.playlistId : null;
              })
              .catch(console.warn);
          }

          if (!isLoadStartListened) {
            isLoadStartListened = true;
            document.addEventListener('loadstart', loadStartFx, true);
          }

          const endpointURL = `${endpoint?.commandMetadata?.webCommandMetadata?.url || ''}`;

          if (
            endpointURL &&
            endpointURL.endsWith('/about') &&
            /\/channel\/UC[-_a-zA-Z0-9+=.]{22}\/about/.test(endpointURL)
          ) {
            fixChannelAboutPopup(t38);
          }

          handleNavigate.apply($this, $arguments);
        };
      };

      return { handleNavigateFactory };
    })();

    const common = (() => {
      let mediaModeLock = 0;
      const _getMediaElement = i => {
        if (mediaModeLock === 0) {
          const e =
            document.querySelector('.video-stream.html5-main-video') ||
            document.querySelector('#movie_player video, #movie_player audio') ||
            document.querySelector('body video[src], body audio[src]');
          if (e) {
            if (e.nodeName === 'VIDEO') mediaModeLock = 1;
            else if (e.nodeName === 'AUDIO') mediaModeLock = 2;
          }
        }
        if (!mediaModeLock) return null;
        if (mediaModeLock === 1) {
          switch (i) {
            case 1:
              return 'ytd-player#ytd-player video[src]';
            case 2:
              return 'ytd-browse[role="main"] video[src]';
            case 0:
            default:
              return '#movie_player video[src]';
          }
        } else if (mediaModeLock === 2) {
          switch (i) {
            case 1:
              return 'ytd-player#ytd-player audio.video-stream.html5-main-video[src]';
            case 2:
              return 'ytd-browse[role="main"] audio.video-stream.html5-main-video[src]';
            case 0:
            default:
              return '#movie_player audio.video-stream.html5-main-video[src]';
          }
        }
        return null;
      };

      return {
        xReplaceState(s, u) {
          try {
            history.replaceState(s, '', u);
          } catch {
            // in case error occurs if replaceState is replaced by any external script / extension
          }
          if (s.endpoint) {
            try {
              const ytdAppElm = document.querySelector('ytd-app');
              const ytdAppCnt = insp(ytdAppElm);
              ytdAppCnt.replaceState(s.endpoint, '', u);
            } catch {}
          }
        },
        getMediaElement(i) {
          const s = _getMediaElement(i) || '';
          if (s) return document.querySelector(s);
          return null;
        },
        getMediaElements(i) {
          const s = _getMediaElement(i) || '';
          if (s) return document.querySelectorAll(s);
          return [];
        },
      };
    })();

    let inPageRearrange = false;
    let tmpLastVideoId = '';
    // const nsMap = new Map();

    const getCurrentVideoId = () => {
      const ytdFlexyElm = elements.flexy;
      const ytdFlexyCnt = insp(ytdFlexyElm);
      if (ytdFlexyCnt && typeof ytdFlexyCnt.videoId === 'string') return ytdFlexyCnt.videoId;
      if (ytdFlexyElm && typeof ytdFlexyElm.videoId === 'string') return ytdFlexyElm.videoId;
      console.log('video id not found');
      return '';
    };

    // eslint-disable-next-line no-unused-vars
    const holdInlineExpanderAlwaysExpanded = inlineExpanderCnt => {
      console.log('holdInlineExpanderAlwaysExpanded');
      if (inlineExpanderCnt.alwaysShowExpandButton === true) {
        inlineExpanderCnt.alwaysShowExpandButton = false;
      }
      if (typeof (inlineExpanderCnt.collapseLabel || 0) === 'string') {
        inlineExpanderCnt.collapseLabel = '';
      }
      if (typeof (inlineExpanderCnt.expandLabel || 0) === 'string') {
        inlineExpanderCnt.expandLabel = '';
      }
      if (inlineExpanderCnt.showCollapseButton === true) {
        inlineExpanderCnt.showCollapseButton = false;
      }
      if (inlineExpanderCnt.showExpandButton === true) inlineExpanderCnt.showExpandButton = false;
      if (inlineExpanderCnt.expandButton instanceof HTMLElement_) {
        inlineExpanderCnt.expandButton = null;
        inlineExpanderCnt.expandButton.remove();
      }
    };

    const fixInlineExpanderDisplay = inlineExpanderCnt => {
      try {
        inlineExpanderCnt.updateIsAttributedExpanded();
      } catch (e) {
        console.warn('[YouTube+] updateIsAttributedExpanded failed:', e);
      }
      try {
        inlineExpanderCnt.updateIsFormattedExpanded();
      } catch (e) {
        console.warn('[YouTube+] updateIsFormattedExpanded failed:', e);
      }
      try {
        inlineExpanderCnt.updateTextOnSnippetTypeChange();
      } catch (e) {
        console.warn('[YouTube+] updateTextOnSnippetTypeChange failed:', e);
      }
      try {
        inlineExpanderCnt.updateStyles();
      } catch (e) {
        console.warn('[YouTube+] updateStyles failed:', e);
      }
    };

    const fixInlineExpanderMethods = inlineExpanderCnt => {
      if (inlineExpanderCnt && !inlineExpanderCnt.__$$idncjk8487$$__) {
        inlineExpanderCnt.__$$idncjk8487$$__ = true;
        inlineExpanderCnt.updateTextOnSnippetTypeChange = function () {
          true || (this.isResetMutation && this.mutationCallback());
        };
        // inlineExpanderCnt.hasAttributedStringText = true;
        inlineExpanderCnt.isResetMutation = true;
        fixInlineExpanderDisplay(inlineExpanderCnt); // do the initial fix
      }
    };

    const fixInlineExpanderContent = () => {
      // console.log(21886,1)
      const mainInfo = getMainInfo();
      if (!mainInfo) return;
      // console.log(21886,2)
      const inlineExpanderElm = mainInfo.querySelector('ytd-text-inline-expander');
      const inlineExpanderCnt = insp(inlineExpanderElm);
      fixInlineExpanderMethods(inlineExpanderCnt);

      // console.log(21886, 3)
      // if (inlineExpanderCnt && inlineExpanderCnt.isExpanded === true && plugin.autoExpandInfoDesc.activated) {
      //   // inlineExpanderCnt.isExpandedChanged();
      //   // holdInlineExpanderAlwaysExpanded(inlineExpanderCnt);
      // }
      // if(inlineExpanderCnt){
      //   // console.log(21886,4, inlineExpanderCnt.isExpanded, inlineExpanderCnt.isTruncated)
      //   if (inlineExpanderCnt.isExpanded === false && inlineExpanderCnt.isTruncated === true) {
      //     // console.log(21881)
      //     inlineExpanderCnt.isTruncated = false;
      //   }
      // }
    };

    const plugin = {
      minibrowser: {
        activated: false,
        toUse: true, // depends on shouldUseMiniPlayer()
        activate() {
          if (this.activated) return;

          // Use global isPassiveArgSupport constant
          // https://caniuse.com/?search=observer
          // https://caniuse.com/?search=addEventListener%20passive

          if (!isPassiveArgSupport) return;

          this.activated = true;

          const ytdAppElm = document.querySelector('ytd-app');
          const ytdAppCnt = insp(ytdAppElm);

          if (!ytdAppCnt) return;

          const cProto = ytdAppCnt.constructor.prototype;

          if (!cProto.handleNavigate) return;

          if (cProto.handleNavigate.__ma355__) return;

          cProto.handleNavigate = handleNavigateFactory(cProto.handleNavigate);

          cProto.handleNavigate.__ma355__ = 1;
        },
      },
      autoExpandInfoDesc: {
        activated: false,
        toUse: false, // false by default; once the expand is clicked, maintain the feature until the browser is closed.
        /** @type { MutationObserver | null } */
        mo: null,
        promiseReady: new PromiseExternal(),
        moFn(lockId) {
          if (lockGet['autoExpandInfoDescAttrAsyncLock'] !== lockId) return;

          const mainInfo = getMainInfo();

          if (!mainInfo) return;
          switch (((mainInfo || 0).nodeName || '').toLowerCase()) {
            case 'ytd-expander':
              if (mainInfo.hasAttribute000('collapsed')) {
                let success = false;
                try {
                  insp(mainInfo).handleMoreTap(new Event('tap'));
                  success = true;
                } catch {}
                if (success) mainInfo.setAttribute111('tyt-no-less-btn', '');
              }
              break;
            case 'ytd-expandable-video-description-body-renderer':
              const inlineExpanderElm = mainInfo.querySelector('ytd-text-inline-expander');
              const inlineExpanderCnt = insp(inlineExpanderElm);
              if (inlineExpanderCnt && inlineExpanderCnt.isExpanded === false) {
                inlineExpanderCnt.isExpanded = true;
                inlineExpanderCnt.isExpandedChanged();
                // holdInlineExpanderAlwaysExpanded(inlineExpanderCnt);
              }
              break;
          }
        },
        activate() {
          if (this.activated) return;

          this.moFn = this.moFn.bind(this);
          this.mo = new MutationObserver(() => {
            Promise.resolve(lockSet['autoExpandInfoDescAttrAsyncLock'])
              .then(this.moFn)
              .catch(console.warn);
          });
          this.activated = true;
          this.promiseReady.resolve();
        },
        async onMainInfoSet(mainInfo) {
          await this.promiseReady.then();
          if (mainInfo.nodeName.toLowerCase() === 'ytd-expander') {
            this.mo.observe(mainInfo, {
              attributes: true,
              attributeFilter: ['collapsed', 'attr-8ifv7'],
            });
          } else {
            this.mo.observe(mainInfo, { attributes: true, attributeFilter: ['attr-8ifv7'] });
          }
          mainInfo.incAttribute111('attr-8ifv7');
        },
      },
      fullChannelNameOnHover: {
        activated: false,
        toUse: true,
        /** @type { MutationObserver | null } */
        mo: null,
        /** @type { ResizeObserver | null} */
        ro: null,
        promiseReady: new PromiseExternal(),
        checkResize: 0,
        mouseEnterFn(evt) {
          const target = evt ? evt.target : null;
          if (!(target instanceof HTMLElement_)) return;
          const metaDataElm = target.closest('ytd-watch-metadata');
          metaDataElm.classList.remove('tyt-metadata-hover-resized');
          this.checkResize = Date.now() + 300;
          metaDataElm.classList.add('tyt-metadata-hover');
          // console.log('mouseEnter')
        },
        mouseLeaveFn(evt) {
          const target = evt ? evt.target : null;
          if (!(target instanceof HTMLElement_)) return;
          const metaDataElm = target.closest('ytd-watch-metadata');
          metaDataElm.classList.remove('tyt-metadata-hover-resized');
          metaDataElm.classList.remove('tyt-metadata-hover');
          // console.log('mouseLeaveFn')
        },
        moFn(lockId) {
          if (lockGet['fullChannelNameOnHoverAttrAsyncLock'] !== lockId) return;

          const uploadInfo = document.querySelector(
            '#primary.ytd-watch-flexy ytd-watch-metadata #upload-info'
          );
          if (!uploadInfo) return;

          const evtOpt = { passive: true, capture: false };
          uploadInfo.removeEventListener('pointerenter', this.mouseEnterFn, evtOpt);
          uploadInfo.removeEventListener('pointerleave', this.mouseLeaveFn, evtOpt);

          uploadInfo.addEventListener('pointerenter', this.mouseEnterFn, evtOpt);
          uploadInfo.addEventListener('pointerleave', this.mouseLeaveFn, evtOpt);
        },
        async onNavigateFinish() {
          await this.promiseReady.then();
          const uploadInfo = document.querySelector(
            '#primary.ytd-watch-flexy ytd-watch-metadata #upload-info'
          );
          if (!uploadInfo) return;
          this.mo.observe(uploadInfo, {
            attributes: true,
            attributeFilter: ['hidden', 'attr-3wb0k'],
          });
          uploadInfo.incAttribute111('attr-3wb0k');
          this.ro.observe(uploadInfo);
        },
        activate() {
          if (this.activated) return;

          // Use global isPassiveArgSupport constant
          // https://caniuse.com/?search=observer
          // https://caniuse.com/?search=addEventListener%20passive

          if (!isPassiveArgSupport) return;

          this.activated = true;

          this.mouseEnterFn = this.mouseEnterFn.bind(this);
          this.mouseLeaveFn = this.mouseLeaveFn.bind(this);

          this.moFn = this.moFn.bind(this);
          this.mo = new MutationObserver(() => {
            Promise.resolve(lockSet['fullChannelNameOnHoverAttrAsyncLock'])
              .then(this.moFn)
              .catch(console.warn);
          });
          this.ro = new ResizeObserver(mutations => {
            if (Date.now() > this.checkResize) return;
            for (const mutation of mutations) {
              const uploadInfo = mutation.target;
              if (uploadInfo && mutation.contentRect.width > 0 && mutation.contentRect.height > 0) {
                const metaDataElm = uploadInfo.closest('ytd-watch-metadata');
                if (metaDataElm.classList.contains('tyt-metadata-hover')) {
                  metaDataElm.classList.add('tyt-metadata-hover-resized');
                }

                break;
              }
            }
          });
          this.promiseReady.resolve();
        },
      },
      'external.ytlstm': {
        activated: false,
        toUse: true, // depends on shouldUseMiniPlayer()
        activate() {
          if (this.activated) return;

          this.activated = true;
          document.documentElement.classList.add('external-ytlstm');
        },
      },
    };

    if (sessionStorage.__$$tmp_UseAutoExpandInfoDesc$$__) plugin.autoExpandInfoDesc.toUse = true;

    // let shouldFixInfo = false;
    const __attachedSymbol__ = Symbol();

    const makeInitAttached = tag => {
      const inPageRearrange_ = inPageRearrange;
      inPageRearrange = false;
      for (const elm of document.querySelectorAll(`${tag}`)) {
        const cnt = insp(elm) || 0;
        if (typeof cnt.attached498 === 'function' && !elm[__attachedSymbol__]) {
          Promise.resolve(elm).then(eventMap[`${tag}::attached`]).catch(console.warn);
        }
      }
      inPageRearrange = inPageRearrange_;
    };

    const getGeneralChatElement = async () => {
      for (let i = 2; i-- > 0; ) {
        const t = document.querySelector(
          '#columns.style-scope.ytd-watch-flexy ytd-live-chat-frame#chat'
        );
        if (t instanceof Element) return t;
        if (i > 0) {
          // try later
          console.log('ytd-live-chat-frame::attached - delayPn(200)');
          await delayPn(200);
        }
      }
      return null;
    };

    const nsTemplateObtain = () => {
      let nsTemplate = document.querySelector('ytd-watch-flexy noscript[ns-template]');
      if (!nsTemplate) {
        nsTemplate = document.createElement('noscript');
        nsTemplate.setAttribute('ns-template', '');
        document.querySelector('ytd-watch-flexy').appendChild(nsTemplate);
      }
      return nsTemplate;
    };

    const isPageDOM = (elm, selector) => {
      if (!elm || !(elm instanceof Element) || !elm.nodeName) return false;
      if (!elm.closest(selector)) return false;
      if (elm.isConnected !== true) return false;
      return true;
    };

    const invalidFlexyParent = hostElement => {
      if (hostElement instanceof HTMLElement) {
        const hasFlexyParent = HTMLElement.prototype.closest.call(hostElement, 'ytd-watch-flexy'); // eg short
        if (!hasFlexyParent) return true;
        const currentFlexy = elements.flexy;
        if (currentFlexy && currentFlexy !== hasFlexyParent) return true;
      }
      return false;
    };

    // const mutationComment = document.createComment('1');
    // let mutationPromise = new PromiseExternal();
    // const mutationPromiseObs = new MutationObserver(()=>{
    //   mutationPromise.resolve();
    //   mutationPromise = new PromiseExternal();
    // });
    // mutationPromiseObs.observe(mutationComment, {characterData: true});

    let headerMutationObserver = null;
    let headerMutationTmpNode = null;

    const eventMap = {
      ceHack: () => {
        mLoaded.flag |= 2;
        document.documentElement.setAttribute111('tabview-loaded', mLoaded.makeString());

        retrieveCE('ytd-watch-flexy')
          .then(eventMap['ytd-watch-flexy::defined'])
          .catch(console.warn);
        retrieveCE('ytd-expander').then(eventMap['ytd-expander::defined']).catch(console.warn);
        retrieveCE('ytd-watch-next-secondary-results-renderer')
          .then(eventMap['ytd-watch-next-secondary-results-renderer::defined'])
          .catch(err =>
            console.warn(
              '[YouTube+] retrieveCE ytd-watch-next-secondary-results-renderer failed:',
              err
            )
          );
        retrieveCE('ytd-comments-header-renderer')
          .then(eventMap['ytd-comments-header-renderer::defined'])
          .catch(err =>
            console.warn('[YouTube+] retrieveCE ytd-comments-header-renderer failed:', err)
          );
        retrieveCE('ytd-live-chat-frame')
          .then(eventMap['ytd-live-chat-frame::defined'])
          .catch(err => console.warn('[YouTube+] retrieveCE ytd-live-chat-frame failed:', err));
        retrieveCE('ytd-comments')
          .then(eventMap['ytd-comments::defined'])
          .catch(err => console.warn('[YouTube+] retrieveCE ytd-comments failed:', err));
        retrieveCE('ytd-engagement-panel-section-list-renderer')
          .then(eventMap['ytd-engagement-panel-section-list-renderer::defined'])
          .catch(err =>
            console.warn(
              '[YouTube+] retrieveCE ytd-engagement-panel-section-list-renderer failed:',
              err
            )
          );
        retrieveCE('ytd-watch-metadata')
          .then(eventMap['ytd-watch-metadata::defined'])
          .catch(err => console.warn('[YouTube+] retrieveCE ytd-watch-metadata failed:', err));
        retrieveCE('ytd-playlist-panel-renderer')
          .then(eventMap['ytd-playlist-panel-renderer::defined'])
          .catch(err =>
            console.warn('[YouTube+] retrieveCE ytd-playlist-panel-renderer failed:', err)
          );
        retrieveCE('ytd-expandable-video-description-body-renderer')
          .then(eventMap['ytd-expandable-video-description-body-renderer::defined'])
          .catch(err =>
            console.warn(
              '[YouTube+] retrieveCE ytd-expandable-video-description-body-renderer failed:',
              err
            )
          );
      },

      fixForTabDisplay: isResize => {
        // isResize is true if the layout is resized (not due to tab switching)
        // youtube components shall handle the resize issue. can skip some checkings.

        bFixForResizedTabLater = false;
        for (const element of document.querySelectorAll('[io-intersected]')) {
          const cnt = insp(element);
          if (element instanceof HTMLElement_ && typeof cnt.calculateCanCollapse === 'function') {
            try {
              cnt.calculateCanCollapse(true);
            } catch (e) {
              console.warn('[YouTube+] calculateCanCollapse failed:', e);
            }
          }
        }

        if (!isResize && lastTab === '#tab-info') {
          // #tab-info is now shown.
          // to fix the sizing issue (description info cards in tab info)
          for (const element of document.querySelectorAll(
            '#tab-info ytd-video-description-infocards-section-renderer, #tab-info yt-chip-cloud-renderer, #tab-info ytd-horizontal-card-list-renderer, #tab-info yt-horizontal-list-renderer'
          )) {
            const cnt = insp(element);
            if (element instanceof HTMLElement_ && typeof cnt.notifyResize === 'function') {
              try {
                cnt.notifyResize();
              } catch (e) {
                console.warn('[YouTube+] notifyResize failed for tab-info:', e);
              }
            }
          }
          // to fix expand/collapse sizing issue (inline-expander in tab info)
          // for example, expand button is required but not shown as it was rendered in the hidden state
          for (const element of document.querySelectorAll('#tab-info ytd-text-inline-expander')) {
            const cnt = insp(element);
            if (element instanceof HTMLElement_ && typeof cnt.resize === 'function') {
              cnt.resize(false); // reflow due to offsetWidth calling
            }
            fixInlineExpanderDisplay(cnt); // just in case
          }
        }

        if (!isResize && typeof lastTab === 'string' && lastTab.startsWith('#tab-')) {
          const tabContent = document.querySelector('.tab-content-cld:not(.tab-content-hidden)');
          if (tabContent) {
            const renderers = tabContent.querySelectorAll('yt-chip-cloud-renderer');
            for (const renderer of renderers) {
              const cnt = insp(renderer);
              if (typeof cnt.notifyResize === 'function') {
                try {
                  cnt.notifyResize();
                } catch (e) {
                  console.warn('[YouTube+] notifyResize failed for renderer:', e);
                }
              }
            }
          }
        }
      },

      'ytd-watch-flexy::defined': cProto => {
        if (
          !cProto.updateChatLocation498 &&
          typeof cProto.updateChatLocation === 'function' &&
          cProto.updateChatLocation.length === 0
        ) {
          cProto.updateChatLocation498 = cProto.updateChatLocation;
          cProto.updateChatLocation = updateChatLocation498;
        }
      },

      'ytd-watch-next-secondary-results-renderer::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-watch-next-secondary-results-renderer::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-watch-next-secondary-results-renderer::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }

        makeInitAttached('ytd-watch-next-secondary-results-renderer');
      },

      'ytd-watch-next-secondary-results-renderer::attached': hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-watch-next-secondary-results-renderer::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;
        if (
          hostElement instanceof HTMLElement_ &&
          hostElement.matches('#columns #related ytd-watch-next-secondary-results-renderer') &&
          !hostElement.matches(
            '#right-tabs ytd-watch-next-secondary-results-renderer, [hidden] ytd-watch-next-secondary-results-renderer'
          )
        ) {
          elements.related = hostElement.closest('#related');
          hostElement.setAttribute111('tyt-videos-list', '');
        }
        // console.log('ytd-watch-next-secondary-results-renderer::attached', hostElement);
      },

      'ytd-watch-next-secondary-results-renderer::detached': hostElement => {
        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-watch-next-secondary-results-renderer::detached');
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
        if (hostElement.hasAttribute000('tyt-videos-list')) {
          elements.related = null;
          hostElement.removeAttribute000('tyt-videos-list');
        }
        console.log('ytd-watch-next-secondary-results-renderer::detached', hostElement);
      },

      settingCommentsVideoId: hostElement => {
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        const cnt = insp(hostElement);
        const commentsArea = elements.comments;
        if (
          commentsArea !== hostElement ||
          hostElement.isConnected !== true ||
          cnt.isAttached !== true ||
          !cnt.data ||
          cnt.hidden !== false
        ) {
          return;
        }
        const ytdFlexyElm = elements.flexy;
        const ytdFlexyCnt = ytdFlexyElm ? insp(ytdFlexyElm) : null;
        if (ytdFlexyCnt && ytdFlexyCnt.videoId) {
          hostElement.setAttribute111('tyt-comments-video-id', ytdFlexyCnt.videoId);
        } else {
          hostElement.removeAttribute000('tyt-comments-video-id');
        }
      },
      checkCommentsShouldBeHidden: lockId => {
        if (lockGet['checkCommentsShouldBeHiddenLock'] !== lockId) return;

        // commentsArea's attribute: tyt-comments-video-id
        // ytdFlexyElm's attribute: video-id

        const commentsArea = elements.comments;
        const ytdFlexyElm = elements.flexy;
        if (commentsArea && ytdFlexyElm && !commentsArea.hasAttribute000('hidden')) {
          const ytdFlexyCnt = insp(ytdFlexyElm);
          if (typeof ytdFlexyCnt.videoId === 'string') {
            const commentsVideoId = commentsArea.getAttribute('tyt-comments-video-id');
            if (commentsVideoId && commentsVideoId !== ytdFlexyCnt.videoId) {
              commentsArea.setAttribute111('hidden', '');
              // removeKeepCommentsScroller();
            }
          }
        }
      },
      'ytd-comments::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-comments::attached'])
                .catch(console.warn);
            }
            // Promise.resolve(this.hostElement).then(eventMap['ytd-comments::dataChanged_']).catch(console.warn);
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-comments::detached'])
                .catch(console.warn);
            }
            // Promise.resolve(this.hostElement).then(eventMap['ytd-comments::dataChanged_']).catch(console.warn);
            return this.detached498();
          };
        }

        cProto._createPropertyObserver('data', '_dataChanged498', undefined);
        cProto._dataChanged498 = function () {
          // console.log('_dataChanged498', this.hostElement)
          Promise.resolve(this.hostElement)
            .then(eventMap['ytd-comments::_dataChanged498'])
            .catch(console.warn);
        };

        // if (!cProto.dataChanged498_ && typeof cProto.dataChanged_ === 'function') {
        //   cProto.dataChanged498_ = cProto.dataChanged_;
        //   cProto.dataChanged_ = function () {
        //     Promise.resolve(this.hostElement).then(eventMap['ytd-comments::dataChanged_']).catch(console.warn);
        //     return this.dataChanged498_();
        //   }
        // }

        makeInitAttached('ytd-comments');
      },

      'ytd-comments::_dataChanged498': hostElement => {
        // console.log(18984, hostElement.hasAttribute('tyt-comments-area'))
        if (!hostElement.hasAttribute000('tyt-comments-area')) return;
        let commentsDataStatus = 0;
        const cnt = insp(hostElement);
        const data = cnt ? cnt.data : null;
        const contents = data ? data.contents : null;
        if (data) {
          if (contents && contents.length === 1 && contents[0].messageRenderer) {
            commentsDataStatus = 2;
          }
          if (contents && contents.length > 1 && contents[0].commentThreadRenderer) {
            commentsDataStatus = 1;
          }
        }
        if (commentsDataStatus) {
          hostElement.setAttribute111('tyt-comments-data-status', commentsDataStatus);
          // ytdFlexyElm.setAttribute111('tyt-comment-disabled', '')
        } else {
          // ytdFlexyElm.removeAttribute000('tyt-comment-disabled')
          hostElement.removeAttribute000('tyt-comments-data-status');
        }
        Promise.resolve(hostElement).then(eventMap['settingCommentsVideoId']).catch(console.warn);
      },

      'ytd-comments::attached': async hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-comments::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;
        if (!hostElement || hostElement.id !== 'comments') return;
        // if (!hostElement || hostElement.closest('[hidden]')) return;
        elements.comments = hostElement;
        console.log('ytd-comments::attached');
        Promise.resolve(hostElement).then(eventMap['settingCommentsVideoId']).catch(console.warn);

        aoComment.observe(hostElement, { attributes: true });
        hostElement.setAttribute111('tyt-comments-area', '');

        const lockId = lockSet['rightTabReadyLock02'];
        await rightTabsProvidedPromise.then();
        if (lockGet['rightTabReadyLock02'] !== lockId) return;

        if (elements.comments !== hostElement) return;
        if (hostElement.isConnected === false) return;
        DEBUG_5085 && console.log(7932, 'comments');

        // if(!elements.comments || elements.comments.isConnected === false) return;
        if (hostElement && !hostElement.closest('#right-tabs')) {
          document.querySelector('#tab-comments').assignChildren111(null, hostElement, null);
        } else {
          const shouldTabVisible =
            elements.comments &&
            elements.comments.closest('#tab-comments') &&
            !elements.comments.closest('[hidden]');

          document
            .querySelector('[tyt-tab-content="#tab-comments"]')
            .classList.toggle('tab-btn-hidden', !shouldTabVisible);

          //   document.querySelector('#tab-comments').classList.remove('tab-content-hidden')
          //   document.querySelector('[tyt-tab-content="#tab-comments"]').classList.remove('tab-btn-hidden')

          Promise.resolve(lockSet['removeKeepCommentsScrollerLock'])
            .then(removeKeepCommentsScroller)
            .catch(console.warn);
        }

        TAB_AUTO_SWITCH_TO_COMMENTS && switchToTab('#tab-comments');
      },
      'ytd-comments::detached': hostElement => {
        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-comments::detached');
        // console.log(858, hostElement)
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;

        if (hostElement.hasAttribute000('tyt-comments-area')) {
          // foComments.disconnect();
          // foComments.takeRecords();
          hostElement.removeAttribute000('tyt-comments-area');
          // document.querySelector('#tab-comments').classList.add('tab-content-hidden')
          // document.querySelector('[tyt-tab-content="#tab-comments"]').classList.add('tab-btn-hidden')

          aoComment.disconnect();
          aoComment.takeRecords();
          elements.comments = null;

          document
            .querySelector('[tyt-tab-content="#tab-comments"]')
            .classList.add('tab-btn-hidden');

          Promise.resolve(lockSet['removeKeepCommentsScrollerLock'])
            .then(removeKeepCommentsScroller)
            .catch(console.warn);
        }
      },

      'ytd-comments-header-renderer::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-comments-header-renderer::attached'])
                .catch(console.warn);
            }
            Promise.resolve(this.hostElement)
              .then(eventMap['ytd-comments-header-renderer::dataChanged'])
              .catch(console.warn); // force dataChanged on attached
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-comments-header-renderer::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }

        if (!cProto.dataChanged498 && typeof cProto.dataChanged === 'function') {
          cProto.dataChanged498 = cProto.dataChanged;
          cProto.dataChanged = function () {
            Promise.resolve(this.hostElement)
              .then(eventMap['ytd-comments-header-renderer::dataChanged'])
              .catch(console.warn);
            return this.dataChanged498();
          };
        }

        makeInitAttached('ytd-comments-header-renderer');
      },

      'ytd-comments-header-renderer::attached': hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-comments-header-renderer::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;
        if (!hostElement || !hostElement.classList.contains('ytd-item-section-renderer')) return;
        // console.log(12991, 'ytd-comments-header-renderer::attached')
        const targetElement = document.querySelector(
          '[tyt-comments-area] ytd-comments-header-renderer'
        );
        if (hostElement === targetElement) {
          hostElement.setAttribute111('tyt-comments-header-field', '');
        } else {
          const parentNode = hostElement.parentNode;
          if (
            parentNode instanceof HTMLElement_ &&
            parentNode.querySelector('[tyt-comments-header-field]')
          ) {
            hostElement.setAttribute111('tyt-comments-header-field', '');
          }
        }
      },

      'ytd-comments-header-renderer::detached': hostElement => {
        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-comments-header-renderer::detached');

        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
        // console.log(12992, 'ytd-comments-header-renderer::detached')
        if (hostElement.hasAttribute000('field-of-cm-count')) {
          hostElement.removeAttribute000('field-of-cm-count');

          const cmCount = document.querySelector('#tyt-cm-count');
          if (
            cmCount &&
            !document.querySelector('#tab-comments ytd-comments-header-renderer[field-of-cm-count]')
          ) {
            cmCount.textContent = '';
          }
        }
        if (hostElement.hasAttribute000('tyt-comments-header-field')) {
          hostElement.removeAttribute000('tyt-comments-header-field');
        }
      },

      'ytd-comments-header-renderer::dataChanged': hostElement => {
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }

        const ytdFlexyElm = elements.flexy;

        let b = false;
        const cnt = insp(hostElement);
        if (
          cnt &&
          hostElement.closest('#tab-comments') &&
          document.querySelector('#tab-comments ytd-comments-header-renderer') === hostElement
        ) {
          b = true;
        } else if (
          hostElement instanceof HTMLElement_ &&
          hostElement.parentNode instanceof HTMLElement_ &&
          hostElement.parentNode.querySelector('[tyt-comments-header-field]')
        ) {
          b = true;
        }
        if (b) {
          hostElement.setAttribute111('tyt-comments-header-field', '');
          ytdFlexyElm && ytdFlexyElm.removeAttribute000('tyt-comment-disabled');
        }

        if (
          hostElement.hasAttribute000('tyt-comments-header-field') &&
          hostElement.isConnected === true
        ) {
          if (!headerMutationObserver) {
            headerMutationObserver = new MutationObserver(
              eventMap['ytd-comments-header-renderer::deferredCounterUpdate']
            );
          }
          headerMutationObserver.observe(hostElement.parentNode, {
            subtree: false,
            childList: true,
          });
          if (!headerMutationTmpNode) {
            headerMutationTmpNode = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
          }
          const tmpNode = headerMutationTmpNode;
          hostElement.insertAdjacentElement('afterend', tmpNode);
          tmpNode.remove();
        }
      },

      'ytd-comments-header-renderer::deferredCounterUpdate': () => {
        const nodes = document.querySelectorAll(
          '#tab-comments ytd-comments-header-renderer[class]'
        );
        if (nodes.length === 1) {
          const hostElement = nodes[0];
          const cnt = insp(hostElement);
          const data = cnt.data;
          if (!data) return;
          let ez = '';
          if (
            data.commentsCount &&
            data.commentsCount.runs &&
            data.commentsCount.runs.length >= 1
          ) {
            let max = -1;
            const z = data.commentsCount.runs
              .map(e => {
                const c = e.text.replace(/\D+/g, '').length;
                if (c > max) max = c;
                return [e.text, c];
              })
              .filter(a => a[1] === max);
            if (z.length >= 1) {
              ez = z[0][0];
            }
          } else if (data.countText && data.countText.runs && data.countText.runs.length >= 1) {
            let max = -1;
            const z = data.countText.runs
              .map(e => {
                const c = e.text.replace(/\D+/g, '').length;
                if (c > max) max = c;
                return [e.text, c];
              })
              .filter(a => a[1] === max);
            if (z.length >= 1) {
              ez = z[0][0];
            }
          }
          const cmCount = document.querySelector('#tyt-cm-count');
          if (ez) {
            hostElement.setAttribute111('field-of-cm-count', '');
            cmCount && (cmCount.textContent = ez.trim());
          } else {
            hostElement.removeAttribute000('field-of-cm-count');
            cmCount && (cmCount.textContent = '');
            console.warn('no text for #tyt-cm-count');
          }
        }
      },

      'ytd-expander::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-expander::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-expander::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }
        if (!cProto.calculateCanCollapse498 && typeof cProto.calculateCanCollapse === 'function') {
          cProto.calculateCanCollapse498 = cProto.calculateCanCollapse;
          cProto.calculateCanCollapse = funcCanCollapse;
        }

        if (!cProto.childrenChanged498 && typeof cProto.childrenChanged === 'function') {
          cProto.childrenChanged498 = cProto.childrenChanged;
          cProto.childrenChanged = function () {
            Promise.resolve(this.hostElement)
              .then(eventMap['ytd-expander::childrenChanged'])
              .catch(console.warn);
            return this.childrenChanged498();
          };
        }

        /*
                 
                        console.log('ytd-expander::defined 01');
                        
                        CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.connectedCallback = connectedCallbackY(CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.connectedCallback)
                        CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.disconnectedCallback = disconnectedCallbackY(CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.disconnectedCallback)
                        
                        console.log('ytd-expander::defined 02');
                 
                        */

        makeInitAttached('ytd-expander');
      },

      'ytd-expander::childrenChanged': hostElement => {
        if (
          hostElement instanceof Node &&
          hostElement.hasAttribute000('hidden') &&
          hostElement.hasAttribute000('tyt-main-info') &&
          hostElement.firstElementChild
        ) {
          hostElement.removeAttribute('hidden');
        }
      },

      'ytd-expandable-video-description-body-renderer::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-expandable-video-description-body-renderer::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-expandable-video-description-body-renderer::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }

        makeInitAttached('ytd-expandable-video-description-body-renderer');
      },

      'ytd-expandable-video-description-body-renderer::attached': async hostElement => {
        if (
          hostElement instanceof HTMLElement_ &&
          isPageDOM(hostElement, '[tyt-info-renderer]') &&
          !hostElement.matches('[tyt-main-info]')
        ) {
          elements.infoExpander = hostElement;
          console.log(128384, elements.infoExpander);

          // console.log(1299, hostElement.parentNode, isRightTabsInserted)

          infoExpanderElementProvidedPromise.resolve();
          hostElement.setAttribute111('tyt-main-info', '');
          if (plugin.autoExpandInfoDesc.toUse) {
            plugin.autoExpandInfoDesc.onMainInfoSet(hostElement);
          }

          const lockId = lockSet['rightTabReadyLock03'];
          await rightTabsProvidedPromise.then();
          if (lockGet['rightTabReadyLock03'] !== lockId) return;

          if (elements.infoExpander !== hostElement) return;
          if (hostElement.isConnected === false) return;
          console.log(7932, 'infoExpander');

          elements.infoExpander.classList.add('tyt-main-info'); // add a classname for it

          const infoExpander = elements.infoExpander;
          // const infoExpanderBack = elements.infoExpanderBack;

          // console.log(5438,infoExpander, qt);

          // const dummy = document.createElement('noscript');
          // dummy.setAttribute000('id', 'info-expander-vid');
          // dummy.setAttribute000('video-id', getCurrentVideoId());
          // infoExpander.insertBefore000(dummy, infoExpander.firstChild);

          // aoInfo.observe(infoExpander, { attributes: true, attributeFilter: ['tyt-display-for', 'tyt-video-id'] });
          // zoInfo.observe(infoExpanderBack, { attributes: true, attributeFilter: ['hidden', 'attr-w20ts'], childList: true, subtree: true});
          // new MutationObserver(()=>{
          //   console.log(591499)
          // }).observe(infoExpanderBack, {childList: true, subtree: true})

          const inlineExpanderElm = infoExpander.querySelector('ytd-text-inline-expander');
          if (inlineExpanderElm) {
            const mo = new MutationObserver(() => {
              const p = document.querySelector('#tab-info ytd-text-inline-expander');
              sessionStorage.__$$tmp_UseAutoExpandInfoDesc$$__ =
                p && p.hasAttribute('is-expanded') ? '1' : '';
              if (p) fixInlineExpanderContent();
            });
            mo.observe(inlineExpanderElm, {
              attributes: ['is-expanded', 'attr-6v8qu', 'hidden'],
              subtree: true,
            }); // hidden + subtree to trigger the fn by delayedUpdate
            inlineExpanderElm.incAttribute111('attr-6v8qu');
            const cnt = insp(inlineExpanderElm);

            if (cnt) fixInlineExpanderDisplay(cnt);
          }

          if (infoExpander && !infoExpander.closest('#right-tabs')) {
            document.querySelector('#tab-info').assignChildren111(null, infoExpander, null);
          } else {
            if (document.querySelector('[tyt-tab-content="#tab-info"]')) {
              const shouldTabVisible =
                elements.infoExpander && elements.infoExpander.closest('#tab-info');
              document
                .querySelector('[tyt-tab-content="#tab-info"]')
                .classList.toggle('tab-btn-hidden', !shouldTabVisible);
            }
          }

          Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn); // required when the page is switched from channel to watch

          // if (infoExpander && infoExpander.closest('#right-tabs')) Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);

          // infoExpanderBack.incAttribute111('attr-w20ts');

          // return;
        }

        DEBUG_5084 && console.log(5084, 'ytd-expandable-video-description-body-renderer::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;

        if (isPageDOM(hostElement, '#tab-info [tyt-main-info]')) {
          // const cnt = insp(hostElement);
          // if(cnt.data){
          //   cnt.data = Object.assign({}, cnt.data);
          // }
        } else if (!hostElement.closest('#tab-info')) {
          const bodyRenderer = hostElement;
          let bodyRendererNew = document.querySelector(
            'ytd-expandable-video-description-body-renderer[tyt-info-renderer]'
          );
          if (!bodyRendererNew) {
            bodyRendererNew = document.createElement(
              'ytd-expandable-video-description-body-renderer'
            );
            bodyRendererNew.setAttribute('tyt-info-renderer', '');
            nsTemplateObtain().appendChild(bodyRendererNew);
          }
          // document.querySelector('#tab-info').assignChildren111(null, bodyRendererNew, null);

          const cnt = insp(bodyRendererNew);
          cnt.data = Object.assign({}, insp(bodyRenderer).data);

          const inlineExpanderElm = bodyRendererNew.querySelector('ytd-text-inline-expander');
          const inlineExpanderCnt = insp(inlineExpanderElm);
          fixInlineExpanderMethods(inlineExpanderCnt);

          // insp(bodyRendererNew).data = insp(bodyRenderer).data;

          // if((bodyRendererNew.hasAttribute('hidden')?1:0)^(bodyRenderer.hasAttribute('hidden')?1:0)){
          //   if(bodyRenderer.hasAttribute('hidden')) bodyRendererNew.setAttribute('hidden', '');
          //   else bodyRendererNew.removeAttribute('hidden');
          // }

          elements.infoExpanderRendererBack = bodyRenderer;
          elements.infoExpanderRendererFront = bodyRendererNew;
          bodyRenderer.setAttribute('tyt-info-renderer-back', '');
          bodyRendererNew.setAttribute('tyt-info-renderer-front', '');

          // elements.infoExpanderBack = {{ytd-expander}};
        }
      },

      'ytd-expandable-video-description-body-renderer::detached': async hostElement => {
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
        // console.log(5992, hostElement)
        if (hostElement.hasAttribute000('tyt-main-info')) {
          DEBUG_5084 &&
            console.log(5084, 'ytd-expandable-video-description-body-renderer::detached');
          elements.infoExpander = null;
          hostElement.removeAttribute000('tyt-main-info');
        }
      },

      'ytd-expander::attached': async hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;
        // console.log(4959, hostElement)

        if (
          hostElement instanceof HTMLElement_ &&
          hostElement.matches('[tyt-comments-area] #contents ytd-expander#expander') &&
          !hostElement.matches('[hidden] ytd-expander#expander')
        ) {
          hostElement.setAttribute111('tyt-content-comment-entry', '');
          ioComment.observe(hostElement);
        }

        // --------------

        // else if (hostElement instanceof HTMLElement_ && hostElement.matches('ytd-expander#expander.style-scope.ytd-expandable-video-description-body-renderer')) {
        //   //  && !hostElement.matches('#right-tabs ytd-expander#expander, [hidden] ytd-expander#expander')

        //   console.log(5084, 'ytd-expander::attached');
        //   const bodyRenderer = hostElement.closest('ytd-expandable-video-description-body-renderer');
        //   let bodyRendererNew = document.querySelector('ytd-expandable-video-description-body-renderer[tyt-info-renderer]');
        //   if (!bodyRendererNew) {
        //     bodyRendererNew = document.createElement('ytd-expandable-video-description-body-renderer');
        //     bodyRendererNew.setAttribute('tyt-info-renderer', '');
        //     nsTemplateObtain().appendChild(bodyRendererNew);
        //   }
        //   // document.querySelector('#tab-info').assignChildren111(null, bodyRendererNew, null);

        //   insp(bodyRendererNew).data = insp(bodyRenderer).data;
        //   // if((bodyRendererNew.hasAttribute('hidden')?1:0)^(bodyRenderer.hasAttribute('hidden')?1:0)){
        //   //   if(bodyRenderer.hasAttribute('hidden')) bodyRendererNew.setAttribute('hidden', '');
        //   //   else bodyRendererNew.removeAttribute('hidden');
        //   // }

        //   elements.infoExpanderRendererBack = bodyRenderer;
        //   elements.infoExpanderRendererFront = bodyRendererNew;
        //   bodyRenderer.setAttribute('tyt-info-renderer-back','')
        //   bodyRendererNew.setAttribute('tyt-info-renderer-front','')

        //   elements.infoExpanderBack = hostElement;

        // }

        // --------------

        // console.log('ytd-expander::attached', hostElement);
      },

      'ytd-expander::detached': hostElement => {
        // if (inPageRearrange) return;
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
        // console.log(5992, hostElement)
        if (hostElement.hasAttribute000('tyt-content-comment-entry')) {
          ioComment.unobserve(hostElement);
          hostElement.removeAttribute000('tyt-content-comment-entry');
        } else if (hostElement.hasAttribute000('tyt-main-info')) {
          DEBUG_5084 && console.log(5084, 'ytd-expander::detached');
          elements.infoExpander = null;
          hostElement.removeAttribute000('tyt-main-info');
        }
        // console.log('ytd-expander::detached', hostElement);
      },

      'ytd-live-chat-frame::defined': cProto => {
        // eslint-disable-next-line no-unused-vars
        let lastDomAction = 0;

        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            lastDomAction = Date.now();
            // console.log('chat868-attached', Date.now());
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-live-chat-frame::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            lastDomAction = Date.now();
            // console.log('chat868-detached', Date.now());
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-live-chat-frame::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }

        if (
          typeof cProto.urlChanged === 'function' &&
          !cProto.urlChanged66 &&
          !cProto.urlChangedAsync12 &&
          cProto.urlChanged.length === 0
        ) {
          cProto.urlChanged66 = cProto.urlChanged;
          let ath = 0;
          cProto.urlChangedAsync12 = async function () {
            await this.__urlChangedAsyncT689__;
            const t = (ath = (ath & 1073741823) + 1);
            const chatframe = this.chatframe || (this.$ || 0).chatframe || 0;
            if (chatframe instanceof HTMLIFrameElement) {
              if (chatframe.contentDocument === null) {
                await Promise.resolve('#').catch(console.warn);
                if (t !== ath) return;
              }
              await new Promise(resolve => setTimeout_(resolve, 1)).catch(console.warn); // neccessary for Brave
              if (t !== ath) return;
              const isBlankPage = !this.data || this.collapsed;
              const p1 = new Promise(resolve => setTimeout_(resolve, 706)).catch(console.warn);
              const p2 = new Promise(resolve => {
                new IntersectionObserver((entries, observer) => {
                  for (const entry of entries) {
                    const rect = entry.boundingClientRect || 0;
                    if (isBlankPage || (rect.width > 0 && rect.height > 0)) {
                      observer.disconnect();
                      resolve('#');
                      break;
                    }
                  }
                }).observe(chatframe);
              }).catch(console.warn);
              await Promise.race([p1, p2]);
              if (t !== ath) return;
            }
            this.urlChanged66();
          };
          cProto.urlChanged = function () {
            const t = (this.__urlChangedAsyncT688__ =
              (this.__urlChangedAsyncT688__ & 1073741823) + 1);
            nextBrowserTick(() => {
              if (t !== this.__urlChangedAsyncT688__) return;
              this.urlChangedAsync12();
            });
          };
        }

        makeInitAttached('ytd-live-chat-frame');
      },

      'ytd-live-chat-frame::attached': async hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-live-chat-frame::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;
        if (!hostElement || hostElement.id !== 'chat') return;
        console.log('ytd-live-chat-frame::attached');

        const lockId = lockSet['ytdLiveAttachedLock'];
        const chatElem = await getGeneralChatElement();
        if (lockGet['ytdLiveAttachedLock'] !== lockId) return;

        if (chatElem === hostElement) {
          elements.chat = chatElem;
          aoChat.observe(chatElem, { attributes: true });
          const isFlexyReady = elements.flexy instanceof Element;
          chatElem.setAttribute111('tyt-active-chat-frame', isFlexyReady ? 'CF' : 'C');

          const chatContainer = chatElem ? chatElem.closest('#chat-container') || chatElem : null;
          if (chatContainer && !chatContainer.hasAttribute000('tyt-chat-container')) {
            for (const p of document.querySelectorAll('[tyt-chat-container]')) {
              p.removeAttribute000('[tyt-chat-container]');
            }
            chatContainer.setAttribute111('tyt-chat-container', '');
          }
          const cnt = insp(hostElement);
          const q = cnt.__urlChangedAsyncT688__;
          const p = (cnt.__urlChangedAsyncT689__ = new PromiseExternal());
          setTimeout_(() => {
            if (p !== cnt.__urlChangedAsyncT689__) return;
            if (cnt.isAttached === true && hostElement.isConnected === true) {
              p.resolve();
              if (q === cnt.__urlChangedAsyncT688__) {
                cnt.urlChanged();
              }
            }
          }, 320);
          Promise.resolve(lockSet['layoutFixLock']).then(layoutFix);
        } else {
          console.log('Issue found in ytd-live-chat-frame::attached', chatElem, hostElement);
        }
      },

      'ytd-live-chat-frame::detached': hostElement => {
        // if (inPageRearrange) return;
        DEBUG_5084 && console.log(5084, 'ytd-live-chat-frame::detached');

        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
        console.log('ytd-live-chat-frame::detached');
        if (hostElement.hasAttribute000('tyt-active-chat-frame')) {
          aoChat.disconnect();
          aoChat.takeRecords();
          hostElement.removeAttribute000('tyt-active-chat-frame');
          elements.chat = null;

          const ytdFlexyElm = elements.flexy;
          if (ytdFlexyElm) {
            ytdFlexyElm.removeAttribute000('tyt-chat-collapsed');
            ytdFlexyElm.setAttribute111('tyt-chat', '');
          }
        }
      },

      'ytd-engagement-panel-section-list-renderer::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-engagement-panel-section-list-renderer::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-engagement-panel-section-list-renderer::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }
        makeInitAttached('ytd-engagement-panel-section-list-renderer');
      },

      'ytd-engagement-panel-section-list-renderer::bindTarget': hostElement => {
        if (
          hostElement.matches(
            '#panels.ytd-watch-flexy > ytd-engagement-panel-section-list-renderer[target-id][visibility]'
          )
        ) {
          hostElement.setAttribute111('tyt-egm-panel', '');
          Promise.resolve(lockSet['updateEgmPanelsLock']).then(updateEgmPanels).catch(console.warn);
          aoEgmPanels.observe(hostElement, {
            attributes: true,
            attributeFilter: ['visibility', 'hidden'],
          });

          // console.log(5094, 2, 'ytd-engagement-panel-section-list-renderer::attached', hostElement);
        }
      },

      'ytd-engagement-panel-section-list-renderer::attached': hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;

        DEBUG_5084 && console.log(5084, 'ytd-engagement-panel-section-list-renderer::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;
        // console.log('ytd-engagement-panel-section-list-renderer::attached', hostElement)
        // console.log(5094, 1, 'ytd-engagement-panel-section-list-renderer::attached', hostElement);

        if (
          !hostElement.matches(
            '#panels.ytd-watch-flexy > ytd-engagement-panel-section-list-renderer'
          )
        ) {
          return;
        }

        if (hostElement.hasAttribute000('target-id') && hostElement.hasAttribute000('visibility')) {
          Promise.resolve(hostElement)
            .then(eventMap['ytd-engagement-panel-section-list-renderer::bindTarget'])
            .catch(console.warn);
        } else {
          hostElement.setAttribute000('tyt-egm-panel-jclmd', '');
          moEgmPanelReady.observe(hostElement, {
            attributes: true,
            attributeFilter: ['visibility', 'target-id'],
          });
        }
      },

      'ytd-engagement-panel-section-list-renderer::detached': hostElement => {
        // if (inPageRearrange) return;

        DEBUG_5084 && console.log(5084, 'ytd-engagement-panel-section-list-renderer::detached');
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
        if (hostElement.hasAttribute000('tyt-egm-panel')) {
          hostElement.removeAttribute000('tyt-egm-panel');
          Promise.resolve(lockSet['updateEgmPanelsLock']).then(updateEgmPanels).catch(console.warn);
        } else if (hostElement.hasAttribute000('tyt-egm-panel-jclmd')) {
          hostElement.removeAttribute000('tyt-egm-panel-jclmd');
          moEgmPanelReadyClearFn();
        }
      },

      'ytd-watch-metadata::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-watch-metadata::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-watch-metadata::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }

        makeInitAttached('ytd-watch-metadata');
      },

      'ytd-watch-metadata::attached': hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;

        DEBUG_5084 && console.log(5084, 'ytd-watch-metadata::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;

        if (plugin.fullChannelNameOnHover.activated) {
          plugin.fullChannelNameOnHover.onNavigateFinish();
        }
      },

      'ytd-watch-metadata::detached': hostElement => {
        // if (inPageRearrange) return;

        DEBUG_5084 && console.log(5084, 'ytd-watch-metadata::detached');
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
      },

      'ytd-playlist-panel-renderer::defined': cProto => {
        if (!cProto.attached498 && typeof cProto.attached === 'function') {
          cProto.attached498 = cProto.attached;
          cProto.attached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-playlist-panel-renderer::attached'])
                .catch(console.warn);
            }
            return this.attached498();
          };
        }
        if (!cProto.detached498 && typeof cProto.detached === 'function') {
          cProto.detached498 = cProto.detached;
          cProto.detached = function () {
            if (!inPageRearrange) {
              Promise.resolve(this.hostElement)
                .then(eventMap['ytd-playlist-panel-renderer::detached'])
                .catch(console.warn);
            }
            return this.detached498();
          };
        }

        makeInitAttached('ytd-playlist-panel-renderer');
      },

      'ytd-playlist-panel-renderer::attached': hostElement => {
        if (invalidFlexyParent(hostElement)) return;

        // if (inPageRearrange) return;

        DEBUG_5084 && console.log(5084, 'ytd-playlist-panel-renderer::attached');
        if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
        if (
          !(hostElement instanceof HTMLElement_) ||
          !(hostElement.classList.length > 0) ||
          hostElement.closest('noscript')
        ) {
          return;
        }
        if (hostElement.isConnected !== true) return;
        // if (hostElement.__connectedFlg__ !== 4) return;
        // hostElement.__connectedFlg__ = 5;

        elements.playlist = hostElement;

        aoPlayList.observe(hostElement, {
          attributes: true,
          attributeFilter: ['hidden', 'collapsed', 'attr-1y6nu'],
        });
        hostElement.incAttribute111('attr-1y6nu');
      },

      'ytd-playlist-panel-renderer::detached': hostElement => {
        // if (inPageRearrange) return;

        DEBUG_5084 && console.log(5084, 'ytd-playlist-panel-renderer::detached');
        if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
        if (hostElement.isConnected !== false) return;
        // if (hostElement.__connectedFlg__ !== 8) return;
        // hostElement.__connectedFlg__ = 9;
      },

      _yt_playerProvided: () => {
        mLoaded.flag |= 4;
        document.documentElement.setAttribute111('tabview-loaded', mLoaded.makeString());
      },
      relatedElementProvided: target => {
        if (target.closest('[hidden]')) return;
        elements.related = target;
        console.log('relatedElementProvided');
        videosElementProvidedPromise.resolve();
      },
      onceInfoExpanderElementProvidedPromised: () => {
        console.log('hide-default-text-inline-expander');
        const ytdFlexyElm = elements.flexy;
        if (ytdFlexyElm) {
          ytdFlexyElm.setAttribute111('hide-default-text-inline-expander', '');
        }
      },

      refreshSecondaryInner: lockId => {
        if (lockGet['refreshSecondaryInnerLock'] !== lockId) return;
        /*
                   
                        ytd-watch-flexy:not([panels-beside-player]):not([fixed-panels]) #panels-full-bleed-container.ytd-watch-flexy{
                            display: none;}
                   
                  #player-full-bleed-container.ytd-watch-flexy{
                      position: relative;
                      flex: 1;}
                   
                        */

        const ytdFlexyElm = elements.flexy;
        // if(ytdFlexyElm && ytdFlexyElm.matches('ytd-watch-flexy[fixed-panels][theater]')){
        //   // ytdFlexyElm.fixedPanels = true;
        //   ytdFlexyElm.removeAttribute000('fixed-panels');
        // }

        if (
          ytdFlexyElm &&
          ytdFlexyElm.matches(
            'ytd-watch-flexy[theater][full-bleed-player]:not([full-bleed-no-max-width-columns])'
          )
        ) {
          // ytdFlexyElm.fullBleedNoMaxWidthColumns = true;
          ytdFlexyElm.setAttribute111('full-bleed-no-max-width-columns', '');
        }

        const related = elements.related;
        if (related && related.isConnected && !related.closest('#right-tabs #tab-videos')) {
          document.querySelector('#tab-videos').assignChildren111(null, related, null);
        }
        const infoExpander = elements.infoExpander;
        if (
          infoExpander &&
          infoExpander.isConnected &&
          !infoExpander.closest('#right-tabs #tab-info')
        ) {
          document.querySelector('#tab-info').assignChildren111(null, infoExpander, null);
        } else {
          // if (infoExpander && ytdFlexyElm && shouldFixInfo) {
          //   shouldFixInfo = false;
          //   Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
          // }
        }

        const commentsArea = elements.comments;
        if (commentsArea) {
          const isConnected = commentsArea.isConnected;
          if (isConnected && !commentsArea.closest('#right-tabs #tab-comments')) {
            const tab = document.querySelector('#tab-comments');
            tab.assignChildren111(null, commentsArea, null);
          } else {
            // if (!isConnected || tab.classList.contains('tab-content-hidden')) removeKeepCommentsScroller();
          }
        }
      },

      'yt-navigate-finish': _evt => {
        const ytdAppElm = document.querySelector(
          'ytd-page-manager#page-manager.style-scope.ytd-app'
        );
        const ytdAppCnt = insp(ytdAppElm);
        pageType = ytdAppCnt ? (ytdAppCnt.data || 0).page : null;

        if (!document.querySelector('ytd-watch-flexy #player')) return;
        // shouldFixInfo = true;
        // console.log('yt-navigate-finish')
        const flexyArr = [...document.querySelectorAll('ytd-watch-flexy')].filter(
          e => !e.closest('[hidden]') && e.querySelector('#player')
        );
        if (flexyArr.length === 1) {
          // const lockId = lockSet['yt-navigate-finish-videos'];
          elements.flexy = flexyArr[0];
          if (isRightTabsInserted) {
            Promise.resolve(lockSet['refreshSecondaryInnerLock'])
              .then(eventMap['refreshSecondaryInner'])
              .catch(console.warn);
            Promise.resolve(lockSet['removeKeepCommentsScrollerLock'])
              .then(removeKeepCommentsScroller)
              .catch(console.warn);
          } else {
            navigateFinishedPromise.resolve();
            if (plugin.minibrowser.toUse) plugin.minibrowser.activate();
            if (plugin.autoExpandInfoDesc.toUse) plugin.autoExpandInfoDesc.activate();
            if (plugin.fullChannelNameOnHover.toUse) plugin.fullChannelNameOnHover.activate();
          }
          const chat = elements.chat;
          if (chat instanceof Element) {
            chat.setAttribute111('tyt-active-chat-frame', 'CF'); // chat and flexy ready
          }
          const infoExpander = elements.infoExpander;
          if (infoExpander && infoExpander.closest('#right-tabs')) {
            Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
          }
          Promise.resolve(lockSet['layoutFixLock']).then(layoutFix);
          if (plugin.fullChannelNameOnHover.activated) {
            plugin.fullChannelNameOnHover.onNavigateFinish();
          }
        }
      },

      onceInsertRightTabs: () => {
        // if(lockId !== lockGet['yt-navigate-finish-videos']) return;
        const related = elements.related;
        let rightTabs = document.querySelector('#right-tabs');
        if (!document.querySelector('#right-tabs') && related) {
          getLangForPage();
          const docTmp = document.createElement('template');
          docTmp.innerHTML = createHTML(getTabsHTML());
          const newElm = docTmp.content.firstElementChild;
          if (newElm !== null) {
            inPageRearrange = true;
            related.parentNode.insertBefore000(newElm, related);
            inPageRearrange = false;
          }
          rightTabs = newElm;
          rightTabs
            .querySelector('[tyt-tab-content="#tab-comments"]')
            .classList.add('tab-btn-hidden');

          const secondaryWrapper = document.createElement('secondary-wrapper');
          secondaryWrapper.classList.add('tabview-secondary-wrapper');
          const secondaryInner = document.querySelector(
            '#secondary-inner.style-scope.ytd-watch-flexy'
          );

          inPageRearrange = true;
          secondaryWrapper.replaceChildren000(...secondaryInner.childNodes);
          secondaryInner.insertBefore000(secondaryWrapper, secondaryInner.firstChild);
          inPageRearrange = false;

          rightTabs
            .querySelector('#material-tabs')
            .addEventListener('click', eventMap['tabs-btn-click'], true);

          inPageRearrange = true;
          if (!rightTabs.closest('secondary-wrapper')) secondaryWrapper.appendChild000(rightTabs);
          inPageRearrange = false;
        }
        if (rightTabs) {
          isRightTabsInserted = true;
          const ioTabBtns = new IntersectionObserver(
            entries => {
              for (const entry of entries) {
                const rect = entry.boundingClientRect;
                entry.target.classList.toggle('tab-btn-visible', rect.width && rect.height);
              }
            },
            { rootMargin: '0px' }
          );
          for (const btn of document.querySelectorAll('.tab-btn[tyt-tab-content]')) {
            ioTabBtns.observe(btn);
          }
          if (!related.closest('#right-tabs')) {
            document.querySelector('#tab-videos').assignChildren111(null, related, null);
          }
          const infoExpander = elements.infoExpander;
          if (infoExpander && !infoExpander.closest('#right-tabs')) {
            document.querySelector('#tab-info').assignChildren111(null, infoExpander, null);
          }
          const commentsArea = elements.comments;
          if (commentsArea && !commentsArea.closest('#right-tabs')) {
            document.querySelector('#tab-comments').assignChildren111(null, commentsArea, null);
          }
          rightTabsProvidedPromise.resolve();
          roRightTabs.disconnect();
          roRightTabs.observe(rightTabs);
          const ytdFlexyElm = elements.flexy;
          const aoFlexy = new MutationObserver(eventMap['aoFlexyFn']);
          aoFlexy.observe(ytdFlexyElm, { attributes: true });
          // Promise.resolve(lockSet['tabsStatusCorrectionLock']).then(eventMap['tabsStatusCorrection']).catch(console.warn);

          Promise.resolve(lockSet['fixInitialTabStateLock'])
            .then(eventMap['fixInitialTabStateFn'])
            .catch(console.warn);

          ytdFlexyElm.incAttribute111('attr-7qlsy'); // tabsStatusCorrectionLock and video-id
        }
      },

      aoFlexyFn: () => {
        Promise.resolve(lockSet['checkCommentsShouldBeHiddenLock'])
          .then(eventMap['checkCommentsShouldBeHidden'])
          .catch(console.warn);

        Promise.resolve(lockSet['refreshSecondaryInnerLock'])
          .then(eventMap['refreshSecondaryInner'])
          .catch(console.warn);

        Promise.resolve(lockSet['tabsStatusCorrectionLock'])
          .then(eventMap['tabsStatusCorrection'])
          .catch(console.warn);

        const videoId = getCurrentVideoId();
        if (videoId !== tmpLastVideoId) {
          tmpLastVideoId = videoId;
          Promise.resolve(lockSet['updateOnVideoIdChangedLock'])
            .then(eventMap['updateOnVideoIdChanged'])
            .catch(console.warn);
        }
      },

      twoColumnChanged10: lockId => {
        if (lockId !== lockGet['twoColumnChanged10Lock']) return;
        for (const continuation of document.querySelectorAll(
          '#tab-videos ytd-watch-next-secondary-results-renderer ytd-continuation-item-renderer'
        )) {
          if (continuation.closest('[hidden]')) continue;
          const cnt = insp(continuation);
          if (typeof cnt.showButton === 'boolean') {
            if (cnt.showButton === false) continue;
            cnt.showButton = false;
            const behavior = cnt.ytRendererBehavior || cnt;
            if (typeof behavior.invalidate === 'function') {
              behavior.invalidate(!1);
            }
          }
        }
      },

      tabsStatusCorrection: lockId => {
        if (lockId !== lockGet['tabsStatusCorrectionLock']) return;
        const ytdFlexyElm = elements.flexy;
        if (!ytdFlexyElm) return;
        const p = tabAStatus;
        const q = calculationFn(p, 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 4096);

        let resetForPanelDisappeared = false;
        let special = 0;
        let actioned = false;
        if (plugin['external.ytlstm'].activated) {
          if (q & 64) {
            // ignore fullscreen
          } else if (
            (p & (1 | 2 | 4 | 8 | 16 | 4096)) === (1 | 0 | 0 | 8 | 16 | 4096) &&
            (q & (1 | 2 | 4 | 8 | 16 | 4096)) === (1 | 0 | 4 | 0 | 16 | 4096)
          ) {
            special = 3;
          } else if (
            (q & (1 | 16)) === (1 | 16) &&
            document.querySelector('[data-ytlstm-theater-mode]')
          ) {
            special = 1;
          } else if (
            (q & (1 | 8 | 16)) === (1 | 8 | 16) &&
            document.querySelector('[is-two-columns_][theater][tyt-chat="+"]')
          ) {
            special = 2;
          }
        }
        if (special) {
          // special
        } else if ((p & 128) === 0 && (q & 128) === 128) {
          lastPanel = 'playlist';
        } else if ((p & 8) === 0 && (q & 8) === 8) {
          lastPanel = 'chat';
        } else if (
          (((p & 4) === 4 && (q & (4 | 8)) === (0 | 0)) ||
            ((p & 8) === 8 && (q & (4 | 8)) === (0 | 0))) &&
          lastPanel === 'chat'
        ) {
          // 24 -> 16 = -8; 'd'
          lastPanel = lastTab || '';
          resetForPanelDisappeared = true;
        } else if ((p & (4 | 8)) === 8 && (q & (4 | 8)) === 4 && lastPanel === 'chat') {
          // click close
          lastPanel = lastTab || '';
          resetForPanelDisappeared = true;
        } else if ((p & 128) === 128 && (q & 128) === 0 && lastPanel === 'playlist') {
          lastPanel = lastTab || '';
          resetForPanelDisappeared = true;
        }
        tabAStatus = q;

        if (special) {
          if (special === 1) {
            if (ytdFlexyElm.getAttribute('tyt-chat') !== '+') {
              ytBtnExpandChat();
            }
            if (ytdFlexyElm.getAttribute('tyt-tab')) {
              switchToTab(null);
            }
          } else if (special === 2) {
            ytBtnCollapseChat();
          } else if (special === 3) {
            ytBtnCancelTheater();
            if (lastTab) {
              switchToTab(lastTab);
            }
          }
          return;
        }

        let bFixForResizedTab = false;

        if ((q ^ 2) === 2 && bFixForResizedTabLater) {
          bFixForResizedTab = true;
        }

        if (((p & 16) === 16) & ((q & 16) === 0)) {
          Promise.resolve(lockSet['twoColumnChanged10Lock'])
            .then(eventMap['twoColumnChanged10'])
            .catch(console.warn);
        }

        if (((p & 2) === 2) ^ ((q & 2) === 2) && (q & 2) === 2) {
          bFixForResizedTab = true;
        }

        // p->q +2
        if ((p & 2) === 0 && (q & 2) === 2 && (p & 128) === 128 && (q & 128) === 128) {
          lastPanel = lastTab || '';
          ytBtnClosePlaylist();
          actioned = true;
        }

        // p->q +8
        if (
          (p & (8 | 128)) === (0 | 128) &&
          (q & (8 | 128)) === (8 | 128) &&
          lastPanel === 'chat'
        ) {
          lastPanel = lastTab || '';
          ytBtnClosePlaylist();
          actioned = true;
        }

        if (
          (p & (1 | 2 | 4 | 8 | 16 | 32 | 64 | 128)) === (1 | 2 | 0 | 8 | 16) &&
          (q & (1 | 2 | 4 | 8 | 16 | 32 | 64 | 128)) === (0 | 2 | 0 | 8 | 16)
        ) {
          // external.ytlstm case
          lastPanel = lastTab || '';
          ytBtnCollapseChat();
          actioned = true;
        }

        // p->q +128
        if (
          (p & (2 | 128)) === (2 | 0) &&
          (q & (2 | 128)) === (2 | 128) &&
          lastPanel === 'playlist'
        ) {
          switchToTab(null);
          actioned = true;
        }

        // p->q +128
        if (
          (p & (8 | 128)) === (8 | 0) &&
          (q & (8 | 128)) === (8 | 128) &&
          lastPanel === 'playlist'
        ) {
          lastPanel = lastTab || '';
          ytBtnCollapseChat();
          actioned = true;
        }

        // p->q +128
        if ((p & (1 | 16 | 128)) === (1 | 16) && (q & (1 | 16 | 128)) === (1 | 16 | 128)) {
          ytBtnCancelTheater();
          actioned = true;
        }

        // p->q +1
        if ((p & (1 | 16 | 128)) === (16 | 128) && (q & (1 | 16 | 128)) === (1 | 16 | 128)) {
          lastPanel = lastTab || '';
          ytBtnClosePlaylist();
          actioned = true;
        }

        if ((q & 64) === 64) {
          actioned = false;
        } else if ((p & 64) === 64 && (q & 64) === 0) {
          // p->q -64

          if ((q & 32) === 32) {
            ytBtnCloseEngagementPanels();
          }

          if ((q & (2 | 8)) === (2 | 8)) {
            if (lastPanel === 'chat') {
              switchToTab(null);
              actioned = true;
            } else if (lastPanel) {
              ytBtnCollapseChat();
              actioned = true;
            }
          }
        } else if (
          (p & (1 | 2 | 8 | 16 | 32)) === (1 | 0 | 0 | 16 | 0) &&
          (q & (1 | 2 | 8 | 16 | 32)) === (1 | 0 | 8 | 16 | 0)
        ) {
          // p->q +8
          ytBtnCancelTheater();
          actioned = true;
        } else if (
          (p & (1 | 16 | 32)) === (0 | 16 | 0) &&
          (q & (1 | 16 | 32)) === (0 | 16 | 32) &&
          (q & (2 | 8)) > 0
        ) {
          // p->q +32
          if (q & 2) {
            switchToTab(null);
            actioned = true;
          }
          if (q & 8) {
            ytBtnCollapseChat();
            actioned = true;
          }
        } else if (
          (p & (1 | 16 | 8 | 2)) === (16 | 8) &&
          (q & (1 | 16 | 8 | 2)) === 16 &&
          (q & 128) === 0
        ) {
          // p->q -8
          if (lastTab) {
            switchToTab(lastTab);
            actioned = true;
          }
        } else if ((p & 1) === 0 && (q & 1) === 1) {
          // p->q +1
          if ((q & 32) === 32) {
            ytBtnCloseEngagementPanels();
          }
          if ((p & 9) === 8 && (q & 9) === 9) {
            ytBtnCollapseChat();
          }
          switchToTab(null);
          actioned = true;
        } else if ((p & 3) === 1 && (q & 3) === 3) {
          // p->q +2
          ytBtnCancelTheater();
          actioned = true;
        } else if ((p & 10) === 2 && (q & 10) === 10) {
          // p->q +8
          switchToTab(null);
          actioned = true;
        } else if ((p & (8 | 32)) === (0 | 32) && (q & (8 | 32)) === (8 | 32)) {
          // p->q +8
          ytBtnCloseEngagementPanels();
          actioned = true;
        } else if ((p & (2 | 32)) === (0 | 32) && (q & (2 | 32)) === (2 | 32)) {
          // p->q +2
          ytBtnCloseEngagementPanels();
          actioned = true;
        } else if ((p & (2 | 8)) === (0 | 8) && (q & (2 | 8)) === (2 | 8)) {
          // p->q +2
          ytBtnCollapseChat();
          actioned = true;
          // if( lastPanel && (p & (1|16) === 16)  && (q & (1 | 16 | 8 | 2)) === (16) ){
          //   switchToTab(lastTab)
          //   actioned = true;
          // }
        } else if ((p & 1) === 1 && (q & (1 | 32)) === (0 | 0)) {
          // p->q -1
          if (lastPanel === 'chat') {
            ytBtnExpandChat();
            actioned = true;
          } else if (lastPanel === lastTab && lastTab) {
            switchToTab(lastTab);
            actioned = true;
          }
        }

        // 24 20
        // 8 16   4 16

        if (!actioned && (q & 128) === 128) {
          lastPanel = 'playlist';
          if ((q & 2) === 2) {
            switchToTab(null);
            actioned = true;
          }
        }

        let shouldDoAutoFix = false;

        if ((p & 2) === 2 && (q & (2 | 128)) === (0 | 128)) {
          // p->q -2
        } else if ((p & 8) === 8 && (q & (8 | 128)) === (0 | 128)) {
          // p->q -8
        } else if (
          !actioned &&
          (p & (1 | 16)) === 16 &&
          (q & (1 | 16 | 8 | 2 | 32 | 64)) === (16 | 0 | 0)
        ) {
          shouldDoAutoFix = true;
        } else if ((q & (1 | 2 | 4 | 8 | 16 | 32 | 64 | 128)) === (4 | 16)) {
          shouldDoAutoFix = true;
        }

        if (shouldDoAutoFix) {
          console.log(388, 'd');
          if (lastPanel === 'chat') {
            console.log(388, 'd1c');
            ytBtnExpandChat();
            actioned = true;
          } else if (lastPanel === 'playlist') {
            console.log(388, 'd1p');
            ytBtnOpenPlaylist();
            actioned = true;
          } else if (lastTab) {
            console.log(388, 'd2t');
            switchToTab(lastTab);
            actioned = true;
          } else if (resetForPanelDisappeared) {
            // if lastTab is undefined
            console.log(388, 'd2d');
            Promise.resolve(lockSet['fixInitialTabStateLock'])
              .then(eventMap['fixInitialTabStateFn'])
              .catch(console.warn);
            actioned = true;
          }
        }

        if (bFixForResizedTab) {
          bFixForResizedTabLater = false;
          Promise.resolve(0).then(eventMap['fixForTabDisplay']).catch(console.warn);
        }

        if (((p & 16) === 16) ^ ((q & 16) === 16)) {
          Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
          Promise.resolve(lockSet['removeKeepCommentsScrollerLock'])
            .then(removeKeepCommentsScroller)
            .catch(console.warn);
          Promise.resolve(lockSet['layoutFixLock']).then(layoutFix).catch(console.warn);
        }
      },

      updateOnVideoIdChanged: lockId => {
        if (lockId !== lockGet['updateOnVideoIdChangedLock']) return;
        const videoId = tmpLastVideoId;
        if (!videoId) return;

        const bodyRenderer = elements.infoExpanderRendererBack;
        const bodyRendererNew = elements.infoExpanderRendererFront;

        if (bodyRendererNew && bodyRenderer) {
          insp(bodyRendererNew).data = insp(bodyRenderer).data;
          // if ((bodyRendererNew.hasAttribute('hidden') ? 1 : 0) ^ (bodyRenderer.hasAttribute('hidden') ? 1 : 0)) {
          //   if (bodyRenderer.hasAttribute('hidden')) bodyRendererNew.setAttribute('hidden', '');
          //   else bodyRendererNew.removeAttribute('hidden');
          // }
        }

        Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
      },

      fixInitialTabStateFn: async lockId => {
        // console.log('fixInitialTabStateFn 0a');
        if (lockGet['fixInitialTabStateLock'] !== lockId) return;
        // console.log('fixInitialTabStateFn 0b');
        const delayTime = fixInitialTabStateK > 0 ? 200 : 1;
        await delayPn(delayTime);
        if (lockGet['fixInitialTabStateLock'] !== lockId) return;
        // console.log('fixInitialTabStateFn 0c');
        const kTab = document.querySelector('[tyt-tab]');
        const qTab =
          !kTab || kTab.getAttribute('tyt-tab') === ''
            ? checkElementExist('ytd-watch-flexy[is-two-columns_]', '[hidden]')
            : null;
        if (checkElementExist('ytd-playlist-panel-renderer#playlist', '[hidden], [collapsed]')) {
          DEBUG_5085 && console.log('fixInitialTabStateFn 1p');
          switchToTab(null);
        } else if (checkElementExist('ytd-live-chat-frame#chat', '[hidden], [collapsed]')) {
          DEBUG_5085 && console.log('fixInitialTabStateFn 1a');
          switchToTab(null);
          if (checkElementExist('ytd-watch-flexy[theater]', '[hidden]')) {
            ytBtnCollapseChat();
          }
        } else if (qTab) {
          const hasTheater = qTab.hasAttribute('theater');
          if (!hasTheater) {
            DEBUG_5085 && console.log('fixInitialTabStateFn 1b');
            const btn0 = document.querySelector('.tab-btn-visible'); // or default button
            if (btn0) {
              switchToTab(btn0);
            } else {
              switchToTab(null);
            }
          } else {
            DEBUG_5085 && console.log('fixInitialTabStateFn 1c');
            switchToTab(null);
          }
        } else {
          DEBUG_5085 && console.log('fixInitialTabStateFn 1z');
        }
        // console.log('fixInitialTabStateFn 0d');
        fixInitialTabStateK++;
      },

      'tabs-btn-click': evt => {
        const target = evt.target;
        if (
          target instanceof HTMLElement_ &&
          target.classList.contains('tab-btn') &&
          target.hasAttribute000('tyt-tab-content')
        ) {
          evt.preventDefault();
          evt.stopPropagation();
          evt.stopImmediatePropagation();

          const activeLink = target;

          switchToTab(activeLink);
        }
      },
    };

    Promise.all([videosElementProvidedPromise, navigateFinishedPromise])
      .then(eventMap['onceInsertRightTabs'])
      .catch(console.warn);
    Promise.all([navigateFinishedPromise, infoExpanderElementProvidedPromise])
      .then(eventMap['onceInfoExpanderElementProvidedPromised'])
      .catch(console.warn);

    const isCustomElementsProvided =
      typeof customElements !== 'undefined' &&
      typeof (customElements || 0).whenDefined === 'function';

    const promiseForCustomYtElementsReady = isCustomElementsProvided
      ? Promise.resolve(0)
      : new Promise(callback => {
          const EVENT_KEY_ON_REGISTRY_READY = 'ytI-ce-registry-created';
          if (typeof customElements === 'undefined') {
            if (!('__CE_registry' in document)) {
              // https://github.com/webcomponents/polyfills/
              Object.defineProperty(document, '__CE_registry', {
                get() {
                  // return undefined
                },
                set(nv) {
                  if (typeof nv == 'object') {
                    delete this.__CE_registry;
                    this.__CE_registry = nv;
                    this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY));
                  }
                  return true;
                },
                enumerable: false,
                configurable: true,
              });
            }
            let eventHandler = _evt => {
              document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
              const f = callback;
              callback = null;
              eventHandler = null;
              f();
            };
            document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
          } else {
            callback();
          }
        });

    const _retrieveCE = async nodeName => {
      try {
        isCustomElementsProvided || (await promiseForCustomYtElementsReady);
        await customElements.whenDefined(nodeName);
      } catch (e) {
        console.warn(e);
      }
    };

    const retrieveCE = async nodeName => {
      try {
        isCustomElementsProvided || (await promiseForCustomYtElementsReady);
        await customElements.whenDefined(nodeName);
        const dummy = document.querySelector(nodeName) || document.createElement(nodeName);
        const cProto = insp(dummy).constructor.prototype;
        return cProto;
      } catch (e) {
        console.warn(e);
      }
    };

    const moOverallRes = {
      _yt_playerProvided: () => (window || 0)._yt_player || 0 || 0,
    };

    let promiseWaitNext = null;
    const moOverall = new MutationObserver(() => {
      if (promiseWaitNext) {
        promiseWaitNext.resolve();
        promiseWaitNext = null;
      }

      if (typeof moOverallRes._yt_playerProvided === 'function') {
        const r = moOverallRes._yt_playerProvided();
        if (r) {
          moOverallRes._yt_playerProvided = r;
          eventMap._yt_playerProvided();
        }
      }
    });

    moOverall.observe(document, { subtree: true, childList: true });

    const moEgmPanelReady = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        const target = mutation.target;
        if (!target.hasAttribute000('tyt-egm-panel-jclmd')) continue;
        if (target.hasAttribute000('target-id') && target.hasAttribute000('visibility')) {
          target.removeAttribute000('tyt-egm-panel-jclmd');
          moEgmPanelReadyClearFn();
          Promise.resolve(target)
            .then(eventMap['ytd-engagement-panel-section-list-renderer::bindTarget'])
            .catch(console.warn);
        }
      }
    });

    const moEgmPanelReadyClearFn = () => {
      if (document.querySelector('[tyt-egm-panel-jclmd]') === null) {
        moEgmPanelReady.takeRecords();
        moEgmPanelReady.disconnect();
      }
    };

    document.addEventListener('yt-navigate-finish', eventMap['yt-navigate-finish'], false);

    document.addEventListener(
      'animationstart',
      evt => {
        const f = eventMap[evt.animationName];
        if (typeof f === 'function') f(evt.target);
      },
      capturePassive
    );

    // console.log('hi122')

    mLoaded.flag |= 1;
    document.documentElement.setAttribute111('tabview-loaded', mLoaded.makeString());

    promiseForCustomYtElementsReady.then(eventMap['ceHack']).catch(console.warn);

    // eslint-disable-next-line no-unused-vars
    executionFinished = 1;
  } catch (e) {
    console.log('error 0xF491');
    console.error(e);
  }
};
const styles = {
  main: `
@keyframes relatedElementProvided{0%{background-position-x:3px;}100%{background-position-x:4px;}}
html[tabview-loaded="icp"] #related.ytd-watch-flexy{animation:relatedElementProvided 1ms linear 0s 1 normal forwards;}
html[tabview-loaded="icp"] #right-tabs #related.ytd-watch-flexy,html[tabview-loaded="icp"] [hidden] #related.ytd-watch-flexy,html[tabview-loaded="icp"] #right-tabs ytd-expander#expander,html[tabview-loaded="icp"] [hidden] ytd-expander#expander,html[tabview-loaded="icp"] ytd-comments ytd-expander#expander{animation:initial;}
#secondary.ytd-watch-flexy{position:relative;}
#secondary-inner.style-scope.ytd-watch-flexy{height:100%;}
#secondary-inner secondary-wrapper{display:flex;flex-direction:column;flex-wrap:nowrap;box-sizing:border-box;padding:0;margin:0;border:0;height:100%;max-height:calc(100vh - var(--ytd-toolbar-height,56px));position:absolute;top:0;right:0;left:0;contain:strict;padding:var(--ytd-margin-6x) var(--ytd-margin-6x) var(--ytd-margin-6x) 0;}
#right-tabs{position:relative;display:flex;padding:0;margin:0;flex-grow:1;flex-direction:column;}
[tyt-tab=""] #right-tabs{flex-grow:0;}
[tyt-tab=""] #right-tabs .tab-content{border:0;}
#right-tabs .tab-content{flex-grow:1;}
ytd-watch-flexy[hide-default-text-inline-expander] #primary.style-scope.ytd-watch-flexy ytd-text-inline-expander{display:none;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden{--comment-pre-load-sizing:90px;visibility:collapse;z-index:-1;position:fixed!important;left:2px;top:2px;width:var(--comment-pre-load-sizing)!important;height:var(--comment-pre-load-sizing)!important;display:block!important;pointer-events:none!important;overflow:hidden;contain:strict;border:0;margin:0;padding:0;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments>ytd-item-section-renderer#sections{display:block!important;overflow:hidden;height:var(--comment-pre-load-sizing);width:var(--comment-pre-load-sizing);contain:strict;border:0;margin:0;padding:0;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments>ytd-item-section-renderer#sections>#contents{display:flex!important;flex-direction:row;gap:60px;overflow:hidden;height:var(--comment-pre-load-sizing);width:var(--comment-pre-load-sizing);contain:strict;border:0;margin:0;padding:0;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents{--comment-pre-load-display:none;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*:only-of-type,ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*:last-child{--comment-pre-load-display:block;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*{display:var(--comment-pre-load-display)!important;}
#right-tabs #material-tabs{position:relative;display:flex;padding:0;border:1px solid var(--ytd-searchbox-legacy-border-color);overflow:hidden;}
[tyt-tab] #right-tabs #material-tabs{border-radius:12px;}
[tyt-tab^="#"] #right-tabs #material-tabs{border-radius:12px 12px 0 0;}
ytd-watch-flexy:not([is-two-columns_]) #right-tabs #material-tabs{outline:0;}
#right-tabs #material-tabs a.tab-btn[tyt-tab-content]>*{pointer-events:none;}
#right-tabs #material-tabs a.tab-btn[tyt-tab-content]>.font-size-right{pointer-events:initial;display:none;}
ytd-watch-flexy #right-tabs .tab-content{padding:0;box-sizing:border-box;display:block;border:1px solid var(--ytd-searchbox-legacy-border-color);border-top:0;position:relative;top:0;display:flex;flex-direction:row;overflow:hidden;border-radius:0 0 12px 12px;}
ytd-watch-flexy:not([is-two-columns_]) #right-tabs .tab-content{height:100%;}
ytd-watch-flexy #right-tabs .tab-content-cld{box-sizing:border-box;position:relative;display:block;width:100%;overflow:auto;--tab-content-padding:var(--ytd-margin-4x);padding:var(--tab-content-padding);contain:layout paint;}
.tab-content-cld,#right-tabs,.tab-content{transition:none;animation:none;}
#right-tabs #emojis.ytd-commentbox{inset:auto 0 auto 0;width:auto;}
ytd-watch-flexy[is-two-columns_] #right-tabs .tab-content-cld{height:100%;width:100%;contain:size layout paint style;position:absolute;}
ytd-watch-flexy #right-tabs .tab-content-cld.tab-content-hidden{display:none;width:100%;contain:size layout paint style;}
@supports (color:var(--tabview-tab-btn-define)){
ytd-watch-flexy #right-tabs .tab-btn{background:var(--yt-spec-general-background-a);}
html{--tyt-tab-btn-flex-grow:1;--tyt-tab-btn-flex-basis:0%;--tyt-tab-bar-color-1-def:#ff4533;--tyt-tab-bar-color-2-def:var(--yt-brand-light-red);--tyt-tab-bar-color-1:var(--main-color,var(--tyt-tab-bar-color-1-def));--tyt-tab-bar-color-2:var(--main-color,var(--tyt-tab-bar-color-2-def));}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]{flex:var(--tyt-tab-btn-flex-grow) 1 var(--tyt-tab-btn-flex-basis);position:relative;display:inline-block;text-decoration:none;text-transform:uppercase;--tyt-tab-btn-color:var(--yt-spec-text-secondary);color:var(--tyt-tab-btn-color);text-align:center;padding:14px 8px 10px;border:0;border-bottom:4px solid transparent;font-weight:500;font-size:12px;line-height:18px;cursor:pointer;transition:border 200ms linear 100ms;background-color:var(--ytd-searchbox-legacy-button-color);text-transform:var(--yt-button-text-transform,inherit);user-select:none!important;overflow:hidden;white-space:nowrap;text-overflow:clip;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]>svg{height:18px;padding-right:0;vertical-align:bottom;opacity:.5;margin-right:0;color:var(--yt-button-color,inherit);fill:var(--iron-icon-fill-color,currentcolor);stroke:var(--iron-icon-stroke-color,none);pointer-events:none;}
ytd-watch-flexy #right-tabs .tab-btn{--tabview-btn-txt-ml:8px;}
ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"]{--tabview-btn-txt-ml:0;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]>svg+span{margin-left:var(--tabview-btn-txt-ml);}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].active{font-weight:500;outline:0;--tyt-tab-btn-color:var(--yt-spec-text-primary);background-color:var(--ytd-searchbox-legacy-button-focus-color);border-bottom:2px var(--tyt-tab-bar-color-2) solid;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].active svg{opacity:.9;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]:not(.active):hover{background-color:var(--ytd-searchbox-legacy-button-hover-color);--tyt-tab-btn-color:var(--yt-spec-text-primary);}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]:not(.active):hover svg{opacity:.9;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].tab-btn-hidden{display:none;}
ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"],ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"]:hover{--tyt-tab-btn-color:var(--yt-spec-icon-disabled);}
ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"] span#tyt-cm-count:empty{display:none;}
ytd-watch-flexy #right-tabs .tab-btn span#tyt-cm-count:empty::after{display:inline-block;width:4em;text-align:left;font-size:inherit;color:currentColor;transform:scaleX(.8);}
}
@supports (color:var(--tyt-cm-count-define)){
ytd-watch-flexy{--tyt-x-loading-content-letter-spacing:2px;}
html{--tabview-text-loading:"Loading";--tabview-text-fetching:"Fetching";--tabview-panel-loading:var(--tabview-text-loading);}
html:lang(ja){--tabview-text-loading:"読み込み中";--tabview-text-fetching:"フェッチ..";}
html:lang(ko){--tabview-text-loading:"로딩..";--tabview-text-fetching:"가져오기..";}
html:lang(zh-Hant){--tabview-text-loading:"載入中";--tabview-text-fetching:"擷取中";}
html:lang(zh-Hans){--tabview-text-loading:"加载中";--tabview-text-fetching:"抓取中";}
html:lang(ru){--tabview-text-loading:"Загрузка";--tabview-text-fetching:"Получение";}
ytd-watch-flexy #right-tabs .tab-btn span#tyt-cm-count:empty::after{content:var(--tabview-text-loading);letter-spacing:var(--tyt-x-loading-content-letter-spacing);}
}
@supports (color:var(--tabview-font-size-btn-define)){
.font-size-right{display:inline-flex;flex-direction:column;position:absolute;right:0;top:0;bottom:0;width:16px;padding:4px 0;justify-content:space-evenly;align-content:space-evenly;pointer-events:none;}
html body ytd-watch-flexy.style-scope .font-size-btn{user-select:none!important;}
.font-size-btn{--tyt-font-size-btn-display:none;display:var(--tyt-font-size-btn-display,none);width:12px;height:12px;color:var(--yt-spec-text-secondary);background-color:var(--yt-spec-badge-chip-background);box-sizing:border-box;cursor:pointer;transform-origin:left top;margin:0;padding:0;position:relative;font-family:'Menlo','Lucida Console','Monaco','Consolas',monospace;line-height:100%;font-weight:900;transition:background-color 90ms linear,color 90ms linear;pointer-events:all;}
.font-size-btn:hover{background-color:var(--yt-spec-text-primary);color:var(--yt-spec-general-background-a);}
@supports (zoom:.5){
.tab-btn .font-size-btn{--tyt-font-size-btn-display:none;}
.tab-btn.active:hover .font-size-btn{--tyt-font-size-btn-display:inline-block;}
body ytd-watch-flexy:not([is-two-columns_]) #columns.ytd-watch-flexy{flex-direction:column;}
body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy{display:block;width:100%;box-sizing:border-box;}
body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy secondary-wrapper{padding-left:var(--ytd-margin-6x);contain:content;height:initial;}
body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy secondary-wrapper #right-tabs{overflow:auto;}
[tyt-chat="+"] { --tyt-chat-grow: 1;}
[tyt-chat="+"] secondary-wrapper>[tyt-chat-container]{flex-grow:var(--tyt-chat-grow);flex-shrink:0;display:flex;flex-direction:column;}
[tyt-chat="+"] secondary-wrapper>[tyt-chat-container]>#chat{flex-grow:var(--tyt-chat-grow);}
ytd-watch-flexy[is-two-columns_]:not([theater]) #columns.style-scope.ytd-watch-flexy{min-height:calc(100vh - var(--ytd-toolbar-height,56px));}
ytd-watch-flexy[is-two-columns_] ytd-live-chat-frame#chat{min-height:initial!important;height:initial!important;}
ytd-watch-flexy[tyt-tab^="#"]:not([is-two-columns_]):not([tyt-chat="+"]) #right-tabs{min-height:var(--ytd-watch-flexy-chat-max-height);}
body ytd-watch-flexy:not([is-two-columns_]) #chat.ytd-watch-flexy{margin-top:0;}
body ytd-watch-flexy:not([is-two-columns_]) ytd-watch-metadata.ytd-watch-flexy{margin-bottom:0;}
ytd-watch-metadata.ytd-watch-flexy ytd-metadata-row-container-renderer{display:none;}
#tab-info [show-expand-button] #expand-sizer.ytd-text-inline-expander{visibility:initial;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#left-arrow-container.ytd-video-description-infocards-section-renderer>#left-arrow,#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#right-arrow-container.ytd-video-description-infocards-section-renderer>#right-arrow{border:6px solid transparent;opacity:.65;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#left-arrow-container.ytd-video-description-infocards-section-renderer>#left-arrow:hover,#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#right-arrow-container.ytd-video-description-infocards-section-renderer>#right-arrow:hover{opacity:1;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>div#left-arrow-container::before{content:'';background:transparent;width:40px;display:block;height:40px;position:absolute;left:-20px;top:0;z-index:-1;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>div#right-arrow-container::before{content:'';background:transparent;width:40px;display:block;height:40px;position:absolute;right:-20px;top:0;z-index:-1;}
body ytd-watch-flexy[is-two-columns_][tyt-egm-panel_] #columns.style-scope.ytd-watch-flexy #panels.style-scope.ytd-watch-flexy{flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}
body ytd-watch-flexy[is-two-columns_][tyt-egm-panel_] #columns.style-scope.ytd-watch-flexy #panels.style-scope.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]{height:initial;max-height:initial;min-height:initial;flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}
secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] ytd-transcript-renderer:not(:empty),secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] #body.ytd-transcript-renderer:not(:empty),secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] #content.ytd-transcript-renderer:not(:empty){flex-grow:1;height:initial;max-height:initial;min-height:initial;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer{position:relative;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer>[panel-target-id]:only-child{contain:style size;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-list-renderer.ytd-transcript-search-panel-renderer{flex-grow:1;contain:strict;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-renderer.style-scope.ytd-transcript-segment-list-renderer{contain:layout paint style;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-renderer.style-scope.ytd-transcript-segment-list-renderer>.segment{contain:layout paint style;}
body ytd-watch-flexy[theater] #secondary.ytd-watch-flexy{margin-top:var(--ytd-margin-3x);padding-top:0;}
body ytd-watch-flexy[theater] secondary-wrapper{margin-top:0;padding-top:0;}
body ytd-watch-flexy[theater] #chat.ytd-watch-flexy{margin-bottom:var(--ytd-margin-2x);}
ytd-watch-flexy[theater] #right-tabs .tab-btn[tyt-tab-content]{padding:8px 4px 6px;border-bottom:0 solid transparent;}
ytd-watch-flexy[theater] #playlist.ytd-watch-flexy{margin-bottom:var(--ytd-margin-2x);}
ytd-watch-flexy[theater] ytd-playlist-panel-renderer[collapsible][collapsed] .header.ytd-playlist-panel-renderer{padding:6px 8px;}
#tab-comments ytd-comments#comments [field-of-cm-count]{margin-top:0;}
#tab-info>ytd-expandable-video-description-body-renderer{margin-bottom:var(--ytd-margin-3x);}
#tab-info [class]:last-child{margin-bottom:0;padding-bottom:0;}
#tab-info ytd-rich-metadata-row-renderer ytd-rich-metadata-renderer{max-width:initial;}
ytd-watch-flexy[is-two-columns_] secondary-wrapper #chat.ytd-watch-flexy{margin-bottom:var(--ytd-margin-3x);}
ytd-watch-flexy[tyt-tab] tp-yt-paper-tooltip{white-space:nowrap;contain:content;}
ytd-watch-info-text tp-yt-paper-tooltip.style-scope.ytd-watch-info-text{margin-bottom:-300px;margin-top:-96px;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata{font-size:1.2rem;line-height:1.8rem;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata yt-animated-rolling-number{font-size:inherit;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata #info-container.style-scope.ytd-watch-info-text{align-items:center;}
ytd-watch-flexy[hide-default-text-inline-expander]{--tyt-bottom-watch-metadata-margin:6px;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata>#description-inner.ytd-watch-metadata{margin:6px 12px;}
[hide-default-text-inline-expander] ytd-watch-metadata[title-headline-xs] h1.ytd-watch-metadata{font-size:1.8rem;}
ytd-watch-flexy[is-two-columns_][hide-default-text-inline-expander] #below.style-scope.ytd-watch-flexy ytd-merch-shelf-renderer{padding:0;border:0;margin:0;}
ytd-watch-flexy[is-two-columns_][hide-default-text-inline-expander] #below.style-scope.ytd-watch-flexy ytd-watch-metadata.ytd-watch-flexy{margin-bottom:6px;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--horizontal .yt-video-attribute-view-model__link-container .yt-video-attribute-view-model__hero-section{flex-shrink:0;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model__overflow-menu{background:var(--yt-emoji-picker-category-background-color);border-radius:99px;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--image-square.yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section{max-height:128px;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section{max-width:128px;}
#tab-info ytd-reel-shelf-renderer #items.yt-horizontal-list-renderer ytd-reel-item-renderer.yt-horizontal-list-renderer{max-width:142px;}
ytd-watch-info-text#ytd-watch-info-text.style-scope.ytd-watch-metadata #view-count.style-scope.ytd-watch-info-text,ytd-watch-info-text#ytd-watch-info-text.style-scope.ytd-watch-metadata #date-text.style-scope.ytd-watch-info-text{align-items:center;}
ytd-watch-info-text:not([detailed]) #info.ytd-watch-info-text a.yt-simple-endpoint.yt-formatted-string{pointer-events:none;}
body ytd-app>ytd-popup-container>tp-yt-iron-dropdown>#contentWrapper>[slot="dropdown-content"]{backdrop-filter:none;}
#tab-info [tyt-clone-refresh-count]{overflow:visible!important;}
#tab-info #items.ytd-horizontal-card-list-renderer yt-video-attribute-view-model.ytd-horizontal-card-list-renderer{contain:layout;}
#tab-info #thumbnail-container.ytd-structured-description-channel-lockup-renderer,#tab-info ytd-media-lockup-renderer[is-compact] #thumbnail-container.ytd-media-lockup-renderer{flex-shrink:0;}
secondary-wrapper ytd-donation-unavailable-renderer{--ytd-margin-6x:var(--ytd-margin-2x);--ytd-margin-5x:var(--ytd-margin-2x);--ytd-margin-4x:var(--ytd-margin-2x);--ytd-margin-3x:var(--ytd-margin-2x);}
[tyt-no-less-btn] #less{display:none;}
.tyt-metadata-hover-resized #purchase-button,.tyt-metadata-hover-resized #sponsor-button,.tyt-metadata-hover-resized #analytics-button,.tyt-metadata-hover-resized #subscribe-button{display:none!important;}
.tyt-metadata-hover #upload-info{max-width:max-content;min-width:max-content;flex-basis:100vw;flex-shrink:0;}
.tyt-info-invisible{display:none;}
[tyt-playlist-expanded] secondary-wrapper>ytd-playlist-panel-renderer#playlist{overflow:auto;flex-shrink:1;flex-grow:1;max-height:unset!important;}
[tyt-playlist-expanded] secondary-wrapper>ytd-playlist-panel-renderer#playlist>#container{max-height:unset!important;}
secondary-wrapper ytd-playlist-panel-renderer{--ytd-margin-6x:var(--ytd-margin-3x);}
#tab-info ytd-structured-description-playlist-lockup-renderer[collections] #playlist-thumbnail.style-scope.ytd-structured-description-playlist-lockup-renderer{max-width:100%;}
#tab-info ytd-structured-description-playlist-lockup-renderer[collections] #lockup-container.ytd-structured-description-playlist-lockup-renderer{padding:1px;}
#tab-info ytd-structured-description-playlist-lockup-renderer[collections] #thumbnail.ytd-structured-description-playlist-lockup-renderer{outline:1px solid rgba(127,127,127,.5);}
ytd-live-chat-frame#chat[collapsed] ytd-message-renderer~#show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame{padding:0;}
ytd-watch-flexy{--tyt-bottom-watch-metadata-margin:12px;}
ytd-watch-flexy[rounded-info-panel],ytd-watch-flexy[rounded-player-large]{--tyt-rounded-a1:12px;}
#bottom-row.style-scope.ytd-watch-metadata .item.ytd-watch-metadata{margin-right:var(--tyt-bottom-watch-metadata-margin,12px);margin-top:var(--tyt-bottom-watch-metadata-margin,12px);}
#cinematics{contain:layout style size;}
ytd-watch-flexy[is-two-columns_]{contain:layout style;}
.yt-spec-touch-feedback-shape--touch-response .yt-spec-touch-feedback-shape__fill{background-color:transparent;}
/* plugin: external.ytlstm */
  body[data-ytlstm-theater-mode] #secondary-inner[class] > secondary-wrapper[class]:not(#chat-container):not(#chat) {display: flex !important;}  
  body[data-ytlstm-theater-mode] secondary-wrapper {all: unset;height: 100vh;}
  body[data-ytlstm-theater-mode] #right-tabs {display: none;}
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] [tyt-chat="+"] {--tyt-chat-grow: unset;}
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #columns.style-scope.ytd-watch-flexy,
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #secondary.style-scope.ytd-watch-flexy,
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #secondary-inner.style-scope.ytd-watch-flexy,
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] secondary-wrapper,
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #chat-container.style-scope,
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] [tyt-chat-container].style-scope {pointer-events: none;}
  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #chat[class] {pointer-events: auto;}
  .playlist-items.ytd-playlist-panel-renderer {background-color: transparent !important;}
  `,
};
(async () => {
  const communicationKey = `ck-${Date.now()}-${Math.floor(Math.random() * 314159265359 + 314159265359).toString(36)}`;

  /** @type {globalThis.PromiseConstructor} */
  const Promise = (async () => {})().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.

  if (!document.documentElement) {
    await Promise.resolve(0);
    while (!document.documentElement) {
      await new Promise(resolve => nextBrowserTick(resolve)).then().catch(console.warn);
    }
  }
  const sourceURL = 'debug://tabview-youtube/tabview.execution.js';
  const textContent = `(${executionScript})("${communicationKey}");${'\n\n'}//# sourceURL=${sourceURL}${'\n'}`;

  // Inject script using a script element with the page's nonce (if available) to comply with CSP
  let script = document.createElement('script');

  // Try to get the nonce from an existing script on the page
  const existingScript = document.querySelector('script[nonce]');
  if (existingScript && existingScript.nonce) {
    script.nonce = existingScript.nonce;
  }

  // Use TrustedTypes if available
  if (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy) {
    script.textContent = trustedTypes.defaultPolicy.createScript(textContent);
  } else {
    script.textContent = textContent;
  }

  (document.head || document.documentElement).appendChild(script);
  script.remove();
  script = null;

  const style = document.createElement('style');
  const sourceURLMainCSS = 'debug://tabview-youtube/tabview.main.css';
  style.textContent = `${styles['main'].trim()}${'\n\n'}/*# sourceURL=${sourceURLMainCSS} */${'\n'}`;
  document.documentElement.appendChild(style);
})();

// --- MODULE: basic.js ---

const YouTubeUtils = (() => {
  'use strict';

  // Internationalization (i18n) system
  const i18n = {
    en: {
      // Settings modal
      settingsTitle: 'Settings',
      basicTab: 'Basic',
      advancedTab: 'Advanced',
      experimentalTab: 'Experimental',
      reportTab: 'Report',
      aboutTab: 'About',
      closeButton: 'Close',
      saveChanges: 'Save Changes',
      settingsSaved: 'Settings saved',

      // Basic settings
      speedControl: 'Speed Control',
      speedControlDesc: 'Add speed control buttons to video player',
      screenshotButton: 'Screenshot Button',
      screenshotButtonDesc: 'Add screenshot capture button to video player',
      downloadButton: 'Download Button',
      downloadButtonDesc: 'Add download button with multiple site options to video player',

      // Download customization
      customDownloader: 'Use custom downloader',
      alwaysEnabled: 'Always enabled - GitHub repository',
      siteName: 'Site name',
      urlTemplate: 'URL template (use {videoId} or {videoUrl})',
      saveButton: 'Save',
      resetButton: 'Reset',

      // Notifications
      y2mateSettingsSaved: 'Y2Mate settings saved',
      y2mateReset: 'Y2Mate reset to defaults',
      xbuddySettingsSaved: '9xBuddy settings saved',
      xbuddyReset: '9xBuddy reset to defaults',

      // Tooltips
      youtubeSettings: 'YouTube + Settings',
      takeScreenshot: 'Take screenshot',
      downloadOptions: 'Download options',

      // Download sites
      byYTDL: 'by YTDL',
      copiedToClipboard: 'Video URL copied to clipboard!',
      // End screen remover
      endscreenHideLabel: 'Hide End Screens & Cards',
      endscreenHideDesc: 'Remove end screen suggestions and info cards',
      // Generic small suffixes
      removedSuffix: ' ({n} removed)',
    },
    ru: {
      // Settings modal
      settingsTitle: 'Настройки',
      basicTab: 'Основные',
      advancedTab: 'Расширенные',
      experimentalTab: 'Экспериментальные',
      reportTab: 'Отчет',
      aboutTab: 'О программе',
      closeButton: 'Закрыть',
      saveChanges: 'Сохранить изменения',
      settingsSaved: 'Настройки сохранены',

      // Basic settings
      speedControl: 'Управление скоростью',
      speedControlDesc: 'Добавить кнопки управления скоростью в видеоплеер',
      screenshotButton: 'Кнопка скриншота',
      screenshotButtonDesc: 'Добавить кнопку создания скриншота в видеоплеер',
      downloadButton: 'Кнопка загрузки',
      downloadButtonDesc: 'Добавить кнопку загрузки с несколькими вариантами сайтов в видеоплеер',

      // Download customization
      customDownloader: 'Использовать свой загрузчик',
      alwaysEnabled: 'Всегда включено - репозиторий GitHub',
      siteName: 'Название сайта',
      urlTemplate: 'Шаблон URL (используйте {videoId} или {videoUrl})',
      saveButton: 'Сохранить',
      resetButton: 'Сбросить',

      // Notifications
      y2mateSettingsSaved: 'Настройки Y2Mate сохранены',
      y2mateReset: 'Y2Mate сброшен к значениям по умолчанию',
      xbuddySettingsSaved: 'Настройки 9xBuddy сохранены',
      xbuddyReset: '9xBuddy сброшен к значениям по умолчанию',

      // Tooltips
      youtubeSettings: 'Настройки YouTube +',
      takeScreenshot: 'Сделать скриншот',
      downloadOptions: 'Варианты загрузки',

      // Download sites
      byYTDL: 'by YTDL',
      copiedToClipboard: 'URL видео скопирован в буфер обмена!',
      // End screen remover
      endscreenHideLabel: 'Скрыть конечные экраны и карточки',
      endscreenHideDesc: 'Удалять рекомендации в конце видео и информационные карточки',
      // Generic small suffixes
      removedSuffix: ' ({n} удалено)',
    },
  };

  // Get browser language
  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  /**
   * Translation function
   * @param {string} key - Translation key
   * @returns {string} Translated text
   */
  function t(key) {
    const lang = getLanguage();
    return i18n[lang]?.[key] || i18n.en[key] || key;
  }

  /**
   * Error logging with module context
   * @param {string} module - Module name
   * @param {string} message - Error message
   * @param {Error} error - Error object
   */
  const logError = (module, message, error) => {
    console.error(`[YouTube+][${module}] ${message}:`, error);
  };

  /**
   * Safe function wrapper with error handling
   * @param {Function} fn - Function to wrap
   * @param {string} context - Context for error logging
   * @returns {Function} Wrapped function
   */
  const safeExecute = (fn, context = 'Unknown') => {
    /** @this {any} */
    return function (...args /** @type {any[]} */) {
      try {
        return fn.apply(this, args);
      } catch (error) {
        logError(context, 'Execution failed', error);
        return null;
      }
    };
  };

  /**
   * Safe async function wrapper with error handling
   * @param {Function} fn - Async function to wrap
   * @param {string} context - Context for error logging
   * @returns {Function} Wrapped async function
   */
  const safeExecuteAsync = (fn, context = 'Unknown') => {
    /** @this {any} */
    return async function (...args /** @type {any[]} */) {
      try {
        return await fn.apply(this, args);
      } catch (error) {
        logError(context, 'Async execution failed', error);
        return null;
      }
    };
  };

  /**
   * Sanitize HTML string to prevent XSS
   * Enhanced with additional security checks
   * @param {string} html - HTML string to sanitize
   * @returns {string} Sanitized HTML
   */
  const sanitizeHTML = html => {
    if (typeof html !== 'string') return '';

    // Check for extremely long strings (potential DoS)
    if (html.length > 1000000) {
      console.warn('[YouTube+] HTML content too large, truncating');
      html = html.substring(0, 1000000);
    }

    /** @type {Record<string, string>} */
    const map = {
      '<': '&lt;',
      '>': '&gt;',
      '&': '&amp;',
      '"': '&quot;',
      "'": '&#39;',
      '/': '&#x2F;',
      '`': '&#x60;',
      '=': '&#x3D;',
    };

    return html.replace(/[<>&"'\/`=]/g, char => map[char] || char);
  };

  /**
   * Validate URL to prevent injection attacks
   * Enhanced with additional protocol and domain checks
   * @param {string} url - URL to validate
   * @returns {boolean} Whether URL is safe
   */
  const isValidURL = url => {
    if (typeof url !== 'string') return false;
    if (url.length > 2048) return false; // RFC 2616 recommends 2048 chars max
    if (url.trim() !== url) return false; // No leading/trailing whitespace

    try {
      const parsed = new URL(url);
      // Only allow http and https protocols
      return ['http:', 'https:'].includes(parsed.protocol);
    } catch {
      return false;
    }
  };

  /**
   * Safe localStorage wrapper with enhanced validation
   */
  const storage = {
    /**
     * Get item from localStorage with JSON parsing
     * @param {string} key - Storage key
     * @param {*} defaultValue - Default value if key doesn't exist
     * @returns {*} Parsed value or default
     */
    get: (key, defaultValue = null) => {
      try {
        if (typeof key !== 'string' || !key) {
          logError('Storage', 'Invalid storage key', new Error('Key must be a non-empty string'));
          return defaultValue;
        }
        // Validate key format (alphanumeric, -, _, .)
        if (!/^[a-zA-Z0-9_.-]+$/.test(key)) {
          logError(
            'Storage',
            'Invalid key format',
            new Error(`Key contains invalid characters: ${key}`)
          );
          return defaultValue;
        }
        const value = localStorage.getItem(key);
        if (value === null) return defaultValue;

        // Validate JSON size before parsing
        if (value.length > 5242880) {
          // 5MB limit
          logError(
            'Storage',
            'Stored value too large',
            new Error(`Value exceeds 5MB limit for key: ${key}`)
          );
          return defaultValue;
        }

        return JSON.parse(value);
      } catch (e) {
        logError('Storage', `Failed to get item: ${key}`, e);
        return defaultValue;
      }
    },

    /**
     * Set item to localStorage with JSON serialization and validation
     * @param {string} key - Storage key
     * @param {*} value - Value to store
     * @returns {boolean} Success status
     */
    set: (key, value) => {
      try {
        if (typeof key !== 'string' || !key) {
          logError('Storage', 'Invalid storage key', new Error('Key must be a non-empty string'));
          return false;
        }
        // Validate key format
        if (!/^[a-zA-Z0-9_.-]+$/.test(key)) {
          logError(
            'Storage',
            'Invalid key format',
            new Error(`Key contains invalid characters: ${key}`)
          );
          return false;
        }

        // Serialize and validate size
        const serialized = JSON.stringify(value);
        if (serialized.length > 5242880) {
          // 5MB limit
          logError(
            'Storage',
            'Value too large',
            new Error(`Serialized value exceeds 5MB limit for key: ${key}`)
          );
          return false;
        }

        localStorage.setItem(key, serialized);
        return true;
      } catch (e) {
        logError('Storage', `Failed to set item: ${key}`, e);
        return false;
      }
    },

    /**
     * Remove item from localStorage
     * @param {string} key - Storage key
     */
    remove: key => {
      try {
        if (typeof key !== 'string' || !key) {
          logError('Storage', 'Invalid storage key', new Error('Key must be a non-empty string'));
          return;
        }
        localStorage.removeItem(key);
      } catch (e) {
        logError('Storage', `Failed to remove item: ${key}`, e);
      }
    },
  };

  // Use shared debounce and throttle from YouTubeUtils (defined in utils.js)
  const debounce =
    /** @type {any} */ (window).YouTubeUtils?.debounce ||
    /**
     * @param {Function} func - Function to debounce
     * @param {number} wait - Wait time in milliseconds
     * @param {{leading?: boolean}} [options] - Options object
     */
    ((func, wait, options = {}) => {
      /** @type {ReturnType<typeof setTimeout> | null} */
      let timeout = null;
      /** @type {any[] | null} */
      let lastArgs = null;
      /** @type {any} */
      let lastThis = null;

      /** @this {any} */
      const debounced = function (...args /** @type {any[]} */) {
        lastArgs = args;
        lastThis = this;
        if (timeout !== null) clearTimeout(timeout);

        if (options.leading && timeout === null) {
          /** @type {Function} */ (func).apply(this, args);
        }

        timeout = setTimeout(() => {
          if (!options.leading) {
            /** @type {Function} */ (func).apply(lastThis, lastArgs);
          }
          timeout = null;
          lastArgs = null;
          lastThis = null;
        }, wait);
      };

      debounced.cancel = () => {
        if (timeout !== null) clearTimeout(timeout);
        timeout = null;
        lastArgs = null;
        lastThis = null;
      };

      return debounced;
    });

  const throttle =
    /** @type {any} */ (window).YouTubeUtils?.throttle ||
    /**
     * @param {Function} func - Function to throttle
     * @param {number} limit - Time limit in milliseconds
     */
    ((func, limit) => {
      /** @type {boolean} */
      let inThrottle = false;
      /** @type {any} */
      let lastResult = undefined;

      /** @this {any} */
      return function (...args /** @type {any[]} */) {
        if (!inThrottle) {
          lastResult = /** @type {Function} */ (func).apply(this, args);
          inThrottle = true;
          setTimeout(() => (inThrottle = false), limit);
        }
        return lastResult;
      };
    });

  /**
   * Safe DOM element creation with props and children
   * @param {string} tag - HTML tag name
   * @param {Object} props - Element properties
   * @param {Array<string | Node>} children - Child elements or text
   * @returns {HTMLElement} Created element
   */
  const createElement = (tag, props = {}, children = []) => {
    // Validate tag name to prevent XSS
    const validTags = /^[a-z][a-z0-9-]*$/i;
    if (!validTags.test(tag)) {
      logError('createElement', 'Invalid tag name', new Error(`Tag "${tag}" is not allowed`));
      return document.createElement('div');
    }

    const element = document.createElement(tag);

    Object.entries(props).forEach(([key, value]) => {
      if (key === 'className') {
        element.className = value;
      } else if (key === 'style' && typeof value === 'object') {
        Object.assign(element.style, value);
      } else if (key.startsWith('on') && typeof value === 'function') {
        element.addEventListener(key.substring(2).toLowerCase(), value);
      } else if (key === 'dataset' && typeof value === 'object') {
        Object.assign(element.dataset, value);
      } else if (key === 'innerHTML' || key === 'outerHTML') {
        // Prevent direct HTML injection
        logError(
          'createElement',
          'Direct HTML injection prevented',
          new Error('Use children array instead')
        );
      } else {
        try {
          element.setAttribute(key, value);
        } catch (e) {
          logError('createElement', `Failed to set attribute ${key}`, e);
        }
      }
    });

    children.forEach(child => {
      if (typeof child === 'string') {
        element.appendChild(document.createTextNode(child));
      } else if (child instanceof Node) {
        element.appendChild(child);
      }
    });

    return element;
  };

  /**
   * DOM Selector Cache with automatic cleanup
   */
  const selectorCache = new Map();
  const CACHE_MAX_SIZE = 50;
  const CACHE_MAX_AGE = 5000; // 5 seconds

  /**
   * Cached querySelector with LRU-like eviction
   * @param {string} selector - CSS selector
   * @param {boolean} nocache - Skip cache
   * @returns {HTMLElement|null} Found element
   */
  const querySelector = (selector, nocache = false) => {
    if (nocache) return document.querySelector(selector);

    const now = Date.now();
    const cached = selectorCache.get(selector);

    // Check if cached element is still valid
    if (cached?.element?.isConnected && now - cached.timestamp < CACHE_MAX_AGE) {
      return cached.element;
    }

    // Remove stale entry
    if (cached) {
      selectorCache.delete(selector);
    }

    const element = document.querySelector(selector);

    if (element) {
      // LRU eviction: remove oldest entries if cache is full
      if (selectorCache.size >= CACHE_MAX_SIZE) {
        const firstKey = selectorCache.keys().next().value;
        selectorCache.delete(firstKey);
      }

      selectorCache.set(selector, { element, timestamp: now });
    }

    return element;
  };

  /**
   * Wait for element with timeout and AbortController
   * @param {string} selector - CSS selector
   * @param {number} timeout - Timeout in ms
   * @param {HTMLElement} parent - Parent element to search in
   * @returns {Promise<HTMLElement>} Promise resolving to element
   */
  const waitForElement = (selector, timeout = 5000, parent = document.body) => {
    return new Promise((resolve, reject) => {
      // Validate inputs
      if (!selector || typeof selector !== 'string') {
        reject(new Error('Selector must be a non-empty string'));
        return;
      }

      if (!parent || !(parent instanceof Element)) {
        reject(new Error('Parent must be a valid DOM element'));
        return;
      }

      // Check if element already exists
      try {
        const element = parent.querySelector(selector);
        if (element) {
          resolve(/** @type {HTMLElement} */ (/** @type {unknown} */ (element)));
          return;
        }
      } catch {
        reject(new Error(`Invalid selector: ${selector}`));
        return;
      }

      const controller = new AbortController();
      /** @type {MutationObserver | null} */
      let observer = null;

      const timeoutId = setTimeout(() => {
        controller.abort();
        if (observer) {
          try {
            observer.disconnect();
          } catch (e) {
            logError('waitForElement', 'Observer disconnect failed', e);
          }
        }
        reject(new Error(`Element ${selector} not found within ${timeout}ms`));
      }, timeout);

      observer = new MutationObserver(() => {
        try {
          const element = parent.querySelector(selector);
          if (element) {
            clearTimeout(timeoutId);
            if (observer) observer.disconnect();
            resolve(/** @type {HTMLElement} */ (/** @type {unknown} */ (element)));
          }
        } catch (e) {
          logError('waitForElement', 'Observer callback error', e);
        }
      });

      try {
        // Ensure parent supports observe/querySelector
        if (!(parent instanceof Element) && parent !== document) {
          throw new Error('Parent does not support observation');
        }
        observer.observe(parent, { childList: true, subtree: true });
      } catch {
        // Fallback for browsers without signal support
        try {
          observer.observe(parent, { childList: true, subtree: true });
        } catch {
          clearTimeout(timeoutId);
          reject(new Error('Failed to observe DOM'));
        }
      }
    });
  };

  /**
   * Resource Cleanup Manager
   * Manages observers, listeners, and intervals
   */
  const cleanupManager = {
    observers: new Set(),
    listeners: new Map(),
    intervals: new Set(),
    timeouts: new Set(),
    animationFrames: new Set(),
    cleanupFunctions: new Set(),

    /**
     * Register a generic cleanup function
     * @param {Function} fn - Cleanup function to call during cleanup
     * @returns {Function} The registered function
     */
    register: fn => {
      if (typeof fn === 'function') {
        cleanupManager.cleanupFunctions.add(fn);
      }
      return fn;
    },

    /**
     * Unregister a specific cleanup function
     * @param {Function} fn - Function to unregister
     */
    unregister: fn => {
      cleanupManager.cleanupFunctions.delete(fn);
    },

    /**
     * Register MutationObserver for cleanup
     * @param {MutationObserver} observer - Observer to register
     * @returns {MutationObserver} Registered observer
     */
    registerObserver: observer => {
      cleanupManager.observers.add(observer);
      return observer;
    },

    /**
     * Unregister and disconnect specific observer
     * @param {MutationObserver} observer - Observer to unregister
     */
    unregisterObserver: observer => {
      if (observer) {
        try {
          observer.disconnect();
        } catch (e) {
          logError('Cleanup', 'Observer disconnect failed', e);
        }
        cleanupManager.observers.delete(observer);
      }
    },

    /**
     * Register event listener for cleanup
     * @param {EventTarget|Document|Window} element - Target element
     * @param {string} event - Event name
     * @param {EventListener|EventListenerObject} handler - Event handler
     * @param {Object} options - Event listener options
     * @returns {Symbol} Listener key for later removal
     */
    registerListener: (element, event, handler, options) => {
      const key = Symbol('listener');
      cleanupManager.listeners.set(key, { element, event, handler, options });
      try {
        element.addEventListener(event, /** @type {EventListener} */ (handler), options);
      } catch {
        // best-effort: if addEventListener fails, still register the listener record
      }
      return key;
    },

    /**
     * Unregister specific listener
     * @param {Symbol} key - Listener key
     */
    unregisterListener: key => {
      const listener = cleanupManager.listeners.get(key);
      if (listener) {
        const { element, event, handler, options } = listener;
        try {
          element.removeEventListener(event, handler, options);
        } catch (e) {
          logError('Cleanup', 'Listener removal failed', e);
        }
        cleanupManager.listeners.delete(key);
      }
    },

    /**
     * Register interval for cleanup
     * @param {TimerId} id - Interval ID
     * @returns {TimerId} Interval ID
     */
    registerInterval: id => {
      cleanupManager.intervals.add(id);
      return id;
    },

    /**
     * Unregister specific interval
     * @param {number} id - Interval ID
     */
    unregisterInterval: id => {
      clearInterval(id);
      cleanupManager.intervals.delete(id);
    },

    /**
     * Register timeout for cleanup
     * @param {TimerId} id - Timeout ID
     * @returns {TimerId} Timeout ID
     */
    registerTimeout: id => {
      cleanupManager.timeouts.add(id);
      return id;
    },

    /**
     * Unregister specific timeout
     * @param {number} id - Timeout ID
     */
    unregisterTimeout: id => {
      clearTimeout(id);
      cleanupManager.timeouts.delete(id);
    },

    /**
     * Register animation frame for cleanup
     * @param {number} id - Animation frame ID
     * @returns {number} Animation frame ID
     */
    registerAnimationFrame: id => {
      cleanupManager.animationFrames.add(id);
      return id;
    },

    /**
     * Unregister specific animation frame
     * @param {number} id - Animation frame ID
     */
    unregisterAnimationFrame: id => {
      cancelAnimationFrame(id);
      cleanupManager.animationFrames.delete(id);
    },

    /**
     * Cleanup all registered resources
     */
    cleanup: () => {
      // Call all registered cleanup functions
      cleanupManager.cleanupFunctions.forEach(fn => {
        try {
          fn();
        } catch (e) {
          logError('Cleanup', 'Cleanup function failed', e);
        }
      });
      cleanupManager.cleanupFunctions.clear();

      // Disconnect all observers
      cleanupManager.observers.forEach(obs => {
        try {
          obs.disconnect();
        } catch (e) {
          logError('Cleanup', 'Observer disconnect failed', e);
        }
      });
      cleanupManager.observers.clear();

      // Remove all listeners
      cleanupManager.listeners.forEach(({ element, event, handler, options }) => {
        try {
          element.removeEventListener(event, handler, options);
        } catch (e) {
          logError('Cleanup', 'Listener removal failed', e);
        }
      });
      cleanupManager.listeners.clear();

      // Clear all intervals
      cleanupManager.intervals.forEach(id => clearInterval(id));
      cleanupManager.intervals.clear();

      // Clear all timeouts
      cleanupManager.timeouts.forEach(id => clearTimeout(id));
      cleanupManager.timeouts.clear();

      // Cancel all animation frames
      cleanupManager.animationFrames.forEach(id => cancelAnimationFrame(id));
      cleanupManager.animationFrames.clear();
    },
  };

  /**
   * Settings Manager
   * Centralized settings storage and retrieval
   */
  const SettingsManager = {
    storageKey: 'youtube_plus_all_settings_v2',

    defaults: {
      speedControl: { enabled: true, currentSpeed: 1 },
      screenshot: { enabled: true },
      download: { enabled: true },
      updateChecker: { enabled: true },
      adBlocker: { enabled: true },
      pip: { enabled: true },
      timecodes: { enabled: true },
      // Add other modules...
    },

    /**
     * Load all settings
     * @returns {Object} Settings object
     */
    load() {
      const saved = storage.get(this.storageKey);
      return saved ? { ...this.defaults, ...saved } : { ...this.defaults };
    },

    /**
     * Save all settings
     * @param {Object} settings - Settings to save
     */
    save(settings) {
      storage.set(this.storageKey, settings);
      // Dispatch event for modules to react
      window.dispatchEvent(
        new CustomEvent('youtube-plus-settings-changed', {
          detail: settings,
        })
      );
    },

    /**
     * Get setting by path
     * @param {string} path - Dot-separated path (e.g., 'speedControl.enabled')
     * @returns {*} Setting value
     */
    get(path) {
      const settings = this.load();
      return path.split('.').reduce((obj, key) => /** @type {any} */ (obj)?.[key], settings);
    },

    /**
     * Set setting by path
     * @param {string} path - Dot-separated path
     * @param {*} value - Value to set
     */
    set(path, value) {
      const settings = this.load();
      const keys = path.split('.');
      const last = keys.pop();
      const target = keys.reduce((obj, key) => {
        /** @type {any} */ (obj)[key] = /** @type {any} */ (obj)[key] || {};
        return /** @type {any} */ (obj)[key];
      }, settings);
      /** @type {any} */ (target)[/** @type {string} */ (last)] = value;
      this.save(settings);
    },
  };

  /**
   * Style Manager
   * Centralized CSS injection and management
   */
  const StyleManager = {
    styles: new Map(),
    /** @type {HTMLStyleElement | null} */
    element: null,

    /**
     * Add CSS rules
     * @param {string} id - Unique identifier
     * @param {string} css - CSS rules
     */
    add(id, css) {
      if (typeof id !== 'string' || !id) {
        logError('StyleManager', 'Invalid style ID', new Error('ID must be a non-empty string'));
        return;
      }
      if (typeof css !== 'string') {
        logError('StyleManager', 'Invalid CSS', new Error('CSS must be a string'));
        return;
      }
      this.styles.set(id, css);
      this.update();
    },

    /**
     * Remove CSS rules
     * @param {string} id - Identifier
     */
    remove(id) {
      this.styles.delete(id);
      this.update();
    },

    /**
     * Update style element
     */
    update() {
      try {
        if (!this.element) {
          this.element = document.createElement('style');
          this.element.id = 'youtube-plus-styles';
          this.element.type = 'text/css';
          (document.head || document.documentElement).appendChild(this.element);
        }
        this.element.textContent = Array.from(this.styles.values()).join('\n');
      } catch (error) {
        logError('StyleManager', 'Failed to update styles', error);
      }
    },

    /**
     * Clear all styles
     */
    clear() {
      this.styles.clear();
      if (this.element) {
        try {
          this.element.remove();
        } catch (e) {
          logError('StyleManager', 'Failed to remove style element', e);
        }
        this.element = null;
      }
    },
  };

  /**
   * Centralized Notification System
   * Manages all notifications with queue and deduplication
   */
  const NotificationManager = {
    /** @type {any[]} */
    queue: [],
    activeNotifications: new Set(),
    maxVisible: 3,
    defaultDuration: 3000,

    /**
     * Show notification
     * @param {string} message - Notification message
     * @param {{duration?: number, position?: string | null, action?: {text: string, callback: Function} | null, type?: string}} [options] - Notification options
     * @returns {HTMLElement | null} Notification element
     */
    show(message, options = {}) {
      // Validate message
      if (!message || typeof message !== 'string') {
        logError(
          'NotificationManager',
          'Invalid message',
          new Error('Message must be a non-empty string')
        );
        return null;
      }

      const {
        duration = this.defaultDuration,
        position = null,
        action = null, // { text: string, callback: function }
      } = options;

      // Remove duplicate messages
      this.activeNotifications.forEach(notif => {
        if (notif.dataset.message === message) {
          this.remove(notif);
        }
      });

      const positions = {
        'top-right': { top: '20px', right: '20px' },
        'top-left': { top: '20px', left: '20px' },
        'bottom-right': { bottom: '20px', right: '20px' },
        'bottom-left': { bottom: '20px', left: '20px' },
      };

      try {
        // Use shared enhancer notification class for consistent appearance
        const notification = createElement('div', {
          className: 'youtube-enhancer-notification',
          dataset: { message }, // Store message for deduplication
          // Keep minimal inline styles; main visuals come from the shared CSS class
          style: Object.assign(
            {
              zIndex: '10001',
              width: 'auto',
              display: 'flex',
              alignItems: 'center',
              gap: '10px',
            },
            position && /** @type {any} */ (positions)[position]
              ? /** @type {any} */ (positions)[position]
              : {}
          ),
        });

        // Add message (with accessibility attributes)
        notification.setAttribute('role', 'status');
        notification.setAttribute('aria-live', 'polite');
        notification.setAttribute('aria-atomic', 'true');

        const messageSpan = createElement(
          'span',
          {
            style: { flex: '1' },
          },
          [message]
        );
        notification.appendChild(messageSpan);

        // Add action button if provided
        if (action && action.text && typeof action.callback === 'function') {
          const actionBtn = createElement(
            'button',
            {
              style: {
                background: 'rgba(255,255,255,0.2)',
                border: '1px solid rgba(255,255,255,0.3)',
                color: 'white',
                padding: '4px 12px',
                borderRadius: '4px',
                cursor: 'pointer',
                fontSize: '12px',
                fontWeight: '600',
                transition: 'background 0.2s',
              },
              onClick: () => {
                action.callback();
                this.remove(notification);
              },
            },
            [action.text]
          );
          notification.appendChild(actionBtn);
        }

        // Ensure a centralized bottom-center container exists and add notification there
        const _notifContainerId = 'youtube-enhancer-notification-container';
        let _notifContainer = document.getElementById(_notifContainerId);
        if (!_notifContainer) {
          _notifContainer = createElement('div', {
            id: _notifContainerId,
            className: 'youtube-enhancer-notification-container',
          });
          try {
            document.body.appendChild(_notifContainer);
          } catch {
            // fallback to body append if container append fails
            document.body.appendChild(notification);
            this.activeNotifications.add(notification);
          }
        }

        try {
          // Prepend so newest notifications appear on top
          _notifContainer.insertBefore(notification, _notifContainer.firstChild);
        } catch {
          // fallback
          document.body.appendChild(notification);
        }
        // ensure notification accepts pointer events (container is pointer-events:none)
        try {
          notification.style.pointerEvents = 'auto';
        } catch {}
        this.activeNotifications.add(notification);

        // Apply entry animation from bottom
        try {
          notification.style.animation = 'slideInFromBottom 0.38s ease-out forwards';
        } catch {}

        // Auto-dismiss
        if (duration > 0) {
          const timeoutId = setTimeout(() => this.remove(notification), duration);
          cleanupManager.registerTimeout(timeoutId);
        }

        // Limit visible notifications
        if (this.activeNotifications.size > this.maxVisible) {
          const oldest = Array.from(this.activeNotifications)[0];
          this.remove(oldest);
        }

        return notification;
      } catch (error) {
        logError('NotificationManager', 'Failed to show notification', error);
        return null;
      }
    },

    /**
     * Remove notification
     * @param {HTMLElement} notification - Notification element
     */
    remove(notification) {
      if (!notification || !notification.isConnected) return;

      try {
        try {
          notification.style.animation = 'slideOutToBottom 0.32s ease-in forwards';
          const timeoutId = setTimeout(() => {
            try {
              notification.remove();
              this.activeNotifications.delete(notification);
            } catch (e) {
              logError('NotificationManager', 'Failed to remove notification', e);
            }
          }, 340);
          cleanupManager.registerTimeout(timeoutId);
        } catch {
          // Fallback: immediate removal
          try {
            notification.remove();
            this.activeNotifications.delete(notification);
          } catch (e) {
            logError('NotificationManager', 'Failed to remove notification (fallback)', e);
          }
        }
      } catch (error) {
        logError('NotificationManager', 'Failed to animate notification removal', error);
        // Force remove
        notification.remove();
        this.activeNotifications.delete(notification);
      }
    },

    /**
     * Clear all notifications
     */
    clearAll() {
      this.activeNotifications.forEach(notif => {
        try {
          notif.remove();
        } catch (e) {
          logError('NotificationManager', 'Failed to clear notification', e);
        }
      });
      this.activeNotifications.clear();
    },
  };

  // Add notification animation styles
  StyleManager.add(
    'notification-animations',
    `
    @keyframes slideInFromBottom {
      from { transform: translateY(100%); opacity: 0; }
      to { transform: translateY(0); opacity: 1; }
    }

    @keyframes slideOutToBottom {
      from { transform: translateY(0); opacity: 1; }
      to { transform: translateY(100%); opacity: 0; }
    }
  `
  );

  // Global cleanup on page unload
  window.addEventListener('beforeunload', () => {
    cleanupManager.cleanup();
    selectorCache.clear();
    StyleManager.clear();
    NotificationManager.clearAll();
  });

  // Periodic cache cleanup to prevent memory leaks
  const cacheCleanupInterval = setInterval(() => {
    const now = Date.now();
    for (const [key, value] of selectorCache.entries()) {
      if (!value.element?.isConnected || now - value.timestamp > CACHE_MAX_AGE) {
        selectorCache.delete(key);
      }
    }
  }, 30000); // Clean every 30 seconds

  cleanupManager.registerInterval(cacheCleanupInterval);

  // Global error handler for uncaught promise rejections
  window.addEventListener('unhandledrejection', event => {
    logError('Global', 'Unhandled promise rejection', event.reason);
    event.preventDefault(); // Prevent console spam
  });

  // Global error handler for uncaught errors
  window.addEventListener('error', event => {
    // Only log errors from our script
    if (event.filename && event.filename.includes('youtube')) {
      logError(
        'Global',
        'Uncaught error',
        new Error(`${event.message} at ${event.filename}:${event.lineno}:${event.colno}`)
      );
    }
  });

  /**
   * Performance monitoring wrapper
   * @param {string} label - Operation label
   * @param {Function} fn - Function to monitor
   * @returns {Function} Wrapped function
   */
  const measurePerformance = (label, fn) => {
    /** @this {any} */
    return function (...args) {
      const start = performance.now();
      try {
        const result = fn.apply(this, args);
        const duration = performance.now() - start;
        if (duration > 100) {
          console.warn(`[YouTube+][Performance] ${label} took ${duration.toFixed(2)}ms`);
        }
        return result;
      } catch (error) {
        logError('Performance', `${label} failed`, error);
        throw error;
      }
    };
  };

  /**
   * Async performance monitoring wrapper
   * @param {string} label - Operation label
   * @param {Function} fn - Async function to monitor
   * @returns {Function} Wrapped async function
   */
  const measurePerformanceAsync = (label, fn) => {
    /** @this {any} */
    return async function (...args) {
      const start = performance.now();
      try {
        const result = await fn.apply(this, args);
        const duration = performance.now() - start;
        if (duration > 100) {
          console.warn(`[YouTube+][Performance] ${label} took ${duration.toFixed(2)}ms`);
        }
        return result;
      } catch (error) {
        logError('Performance', `${label} failed`, error);
        throw error;
      }
    };
  };

  /**
   * Mobile device detection
   * @returns {boolean} True if mobile device
   */
  const isMobile = () => {
    return (
      /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
      window.innerWidth <= 768
    );
  };

  /**
   * Get viewport dimensions
   * @returns {Object} Width and height
   */
  const getViewport = () => ({
    width: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0),
    height: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0),
  });

  /**
   * Safe async retry wrapper
   * @param {Function} fn - Async function to retry
   * @param {number} retries - Number of retries
   * @param {number} delay - Delay between retries
   * @returns {Promise} Result or error
   */
  const retryAsync = async (fn, retries = 3, delay = 1000) => {
    for (let i = 0; i < retries; i++) {
      try {
        return await fn();
      } catch (error) {
        if (i === retries - 1) throw error;
        await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
      }
    }
  };

  // Export public API
  return {
    logError,
    safeExecute,
    safeExecuteAsync,
    sanitizeHTML,
    isValidURL,
    storage,
    debounce,
    throttle,
    createElement,
    querySelector,
    waitForElement,
    cleanupManager,
    SettingsManager,
    StyleManager,
    NotificationManager,
    clearCache: () => selectorCache.clear(),
    isMobile,
    getViewport,
    retryAsync,
    measurePerformance,
    measurePerformanceAsync,
    t, // Translation function
  };
})();

// Make available globally
if (typeof window !== 'undefined') {
  // Merge utilities into existing global YouTubeUtils without overwriting
  /** @type {any} */ (window).YouTubeUtils = /** @type {any} */ (window).YouTubeUtils || {};
  const existing = /** @type {any} */ (window).YouTubeUtils;
  try {
    for (const k of Object.keys(YouTubeUtils)) {
      if (existing[k] === undefined) existing[k] = YouTubeUtils[k];
    }
  } catch {}

  // Add initialization health check (non-intrusive)
  console.log('[YouTube+ v2.1] Core utilities merged');

  // Expose debug info
  /** @type {any} */ (window).YouTubePlusDebug = {
    version: '2.1',
    cacheSize: () =>
      YouTubeUtils.cleanupManager.observers.size +
      YouTubeUtils.cleanupManager.listeners.size +
      YouTubeUtils.cleanupManager.intervals.size,
    clearAll: () => {
      YouTubeUtils.cleanupManager.cleanup();
      YouTubeUtils.clearCache();
      YouTubeUtils.StyleManager.clear();
      YouTubeUtils.NotificationManager.clearAll();
      console.log('[YouTube+] All resources cleared');
    },
    stats: () => ({
      observers: YouTubeUtils.cleanupManager.observers.size,
      listeners: YouTubeUtils.cleanupManager.listeners.size,
      intervals: YouTubeUtils.cleanupManager.intervals.size,
      timeouts: YouTubeUtils.cleanupManager.timeouts.size,
      animationFrames: YouTubeUtils.cleanupManager.animationFrames.size,
      styles: YouTubeUtils.StyleManager.styles.size,
      notifications: YouTubeUtils.NotificationManager.activeNotifications.size,
    }),
  };

  // Show subtle startup notification (only once per session)
  if (!sessionStorage.getItem('youtube_plus_started')) {
    sessionStorage.setItem('youtube_plus_started', 'true');
    setTimeout(() => {
      if (YouTubeUtils.NotificationManager) {
        YouTubeUtils.NotificationManager.show('YouTube+ v2.1 loaded', {
          type: 'success',
          duration: 2000,
          position: 'bottom-right',
        });
      }
    }, 1000);
  }
} //-----------------------------------------------------------------------------
// YouTube enhancements module
(function () {
  'use strict';

  // Local reference to translation function
  const t = YouTubeUtils.t;

  const YouTubeEnhancer = {
    // Speed control variables
    speedControl: {
      currentSpeed: 1,
      activeAnimationId: null,
      storageKey: 'youtube_playback_speed',
    },

    _initialized: false,

    // Settings
    settings: {
      enableSpeedControl: true,
      enableScreenshot: true,
      enableDownload: true,
      // Состояние сайтов внутри сабменю кнопки Download (ytdl всегда включён)
      downloadSites: {
        y2mate: true,
        xbbuddy: true,
      },
      // Настройки кастомизации download сайтов
      downloadSiteCustomization: {
        y2mate: {
          name: 'Y2Mate',
          url: 'https://www.y2mate.com/youtube/{videoId}',
        },
        xbbuddy: {
          name: '9xbuddy',
          url: 'https://9xbuddy.org/process?url={videoUrl}',
        },
      },
      storageKey: 'youtube_plus_settings',
      // runtime setting: hide left side guide/footer when true
      hideSideGuide: false,
    },

    // Cache DOM queries
    _cache: new Map(),

    // Cached element getter
    getElement(selector, useCache = true) {
      if (useCache && this._cache.has(selector)) {
        const element = this._cache.get(selector);
        if (element?.isConnected) return element;
        this._cache.delete(selector);
      }

      const element = document.querySelector(selector);
      if (element && useCache) this._cache.set(selector, element);
      return element;
    },

    loadSettings() {
      try {
        const saved = localStorage.getItem(this.settings.storageKey);
        if (saved) Object.assign(this.settings, JSON.parse(saved));
      } catch (e) {
        console.error('Error loading settings:', e);
      }
    },

    init() {
      if (this._initialized) {
        return;
      }

      this._initialized = true;

      try {
        this.loadSettings();
      } catch (error) {
        console.warn('[YouTube Enhancer] Failed to load settings during init:', error);
      }

      this.insertStyles();
      this.addSettingsButtonToHeader();
      this.setupNavigationObserver();

      if (location.href.includes('watch?v=')) {
        this.setupCurrentPage();
      }

      document.addEventListener('visibilitychange', () => {
        if (!document.hidden && location.href.includes('watch?v=')) {
          this.setupCurrentPage();
        }
      });
    },

    saveSettings() {
      localStorage.setItem(this.settings.storageKey, JSON.stringify(this.settings));
      this.updatePageBasedOnSettings();
      this.refreshDownloadButton();
    },

    updatePageBasedOnSettings() {
      const settingsMap = {
        'ytp-screenshot-button': 'enableScreenshot',
        'ytp-download-button': 'enableDownload',
        'speed-control-btn': 'enableSpeedControl',
      };

      Object.entries(settingsMap).forEach(([className, setting]) => {
        const button = this.getElement(`.${className}`, false);
        if (button) button.style.display = this.settings[setting] ? '' : 'none';
      });

      // Also handle speed options dropdown (attached to body)
      const speedOptions = document.querySelector('.speed-options');
      if (speedOptions) {
        speedOptions.style.display = this.settings.enableSpeedControl ? '' : 'none';
      }
    },

    refreshDownloadButton() {
      const selector = '.ytp-download-button';

      // Очистить кеш, чтобы избежать возврата удалённых элементов
      if (this._cache.has(selector)) {
        this._cache.delete(selector);
      }

      const existingButton = document.querySelector(selector);
      if (existingButton?.parentElement) {
        existingButton.remove();
      }

      if (!this.settings.enableDownload) {
        return;
      }

      const controls = this.getElement('.ytp-right-controls', false);
      if (!controls) {
        return;
      }

      this.addDownloadButton(controls);
    },

    setupCurrentPage() {
      this.waitForElement('#player-container-outer .html5-video-player, .ytp-right-controls', 5000)
        .then(() => {
          this.addCustomButtons();
          this.setupVideoObserver();
          this.applyCurrentSpeed();
          this.updatePageBasedOnSettings();
          this.refreshDownloadButton();
        })
        .catch(() => {});
    },

    insertStyles() {
      // Glassmorphism styles for modal and controls
      const styles = `:root{--yt-accent:#ff0000;--yt-accent-hover:#cc0000;--yt-radius-sm:6px;--yt-radius-md:10px;--yt-radius-lg:16px;--yt-transition:all .2s ease;--yt-space-xs:4px;--yt-space-sm:8px;--yt-space-md:16px;--yt-space-lg:24px;--yt-glass-blur:blur(18px) saturate(180%);--yt-glass-blur-light:blur(12px) saturate(160%);--yt-glass-blur-heavy:blur(24px) saturate(200%);}
        html[dark],html:not([dark]):not([light]){--yt-bg-primary:rgba(15,15,15,.85);--yt-bg-secondary:rgba(28,28,28,.85);--yt-bg-tertiary:rgba(34,34,34,.85);--yt-text-primary:#fff;--yt-text-secondary:#aaa;--yt-border-color:rgba(255,255,255,.2);--yt-hover-bg:rgba(255,255,255,.1);--yt-shadow:0 4px 12px rgba(0,0,0,.25);--yt-glass-bg:rgba(255,255,255,.1);--yt-glass-border:rgba(255,255,255,.2);--yt-glass-shadow:0 8px 32px rgba(0,0,0,.2);--yt-modal-bg:rgba(0,0,0,.75);--yt-notification-bg:rgba(28,28,28,.9);--yt-panel-bg:rgba(34,34,34,.3);--yt-header-bg:rgba(20,20,20,.6);--yt-input-bg:rgba(255,255,255,.1);--yt-button-bg:rgba(255,255,255,.2);--yt-text-stroke:white;}
        html[light]{--yt-bg-primary:rgba(255,255,255,.85);--yt-bg-secondary:rgba(248,248,248,.85);--yt-bg-tertiary:rgba(240,240,240,.85);--yt-text-primary:#030303;--yt-text-secondary:#606060;--yt-border-color:rgba(0,0,0,.2);--yt-hover-bg:rgba(0,0,0,.05);--yt-shadow:0 4px 12px rgba(0,0,0,.15);--yt-glass-bg:rgba(255,255,255,.7);--yt-glass-border:rgba(0,0,0,.1);--yt-glass-shadow:0 8px 32px rgba(0,0,0,.1);--yt-modal-bg:rgba(0,0,0,.5);--yt-notification-bg:rgba(255,255,255,.95);--yt-panel-bg:rgba(255,255,255,.7);--yt-header-bg:rgba(248,248,248,.8);--yt-input-bg:rgba(0,0,0,.05);--yt-button-bg:rgba(0,0,0,.1);--yt-text-stroke:#030303;}
        .ytp-screenshot-button,.ytp-cobalt-button,.ytp-pip-button{position:relative;width:44px;height:100%;display:inline-flex;align-items:center;justify-content:center;vertical-align:top;transition:opacity .15s,transform .15s;}
        .ytp-screenshot-button:hover,.ytp-cobalt-button:hover,.ytp-pip-button:hover{transform:scale(1.1);}
        .speed-control-btn{width:4em!important;position:relative!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;height:100%!important;vertical-align:top!important;text-align:center!important;border-radius:var(--yt-radius-sm);font-size:13px;color:var(--yt-text-primary);cursor:pointer;user-select:none;font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:color .2s;}
        .speed-control-btn:hover{color:var(--yt-accent);font-weight:bold;}
        .speed-options{position:fixed!important;background:var(--yt-glass-bg)!important;color:var(--yt-text-primary)!important;border-radius:var(--yt-radius-md)!important;display:flex!important;flex-direction:column!important;align-items:stretch!important;gap:0!important;transform:translate(-50%,12px)!important;width:92px!important;z-index:2147483647!important;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);overflow:hidden;backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);opacity:0;pointer-events:none!important;transition:opacity .18s ease,transform .18s ease;box-sizing:border-box;}
        .speed-options.visible{opacity:1;pointer-events:auto!important;transform:translate(-50%,0)!important;}
        .speed-option-item{cursor:pointer!important;height:28px!important;line-height:28px!important;font-size:12px!important;text-align:center!important;transition:background-color .15s,color .15s;}
        .speed-option-active,.speed-option-item:hover{color:var(--yt-accent)!important;font-weight:bold!important;background:var(--yt-hover-bg)!important;}
        #speed-indicator{position:absolute!important;margin:auto!important;top:0!important;right:0!important;bottom:0!important;left:0!important;border-radius:24px!important;font-size:30px!important;background:var(--yt-glass-bg)!important;color:var(--yt-text-primary)!important;z-index:99999!important;width:80px!important;height:80px!important;line-height:80px!important;text-align:center!important;display:none;box-shadow:var(--yt-glass-shadow);backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);border:1px solid var(--yt-glass-border);}
        .youtube-enhancer-notification-container{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:10px;z-index:2147483647;pointer-events:none;max-width:calc(100% - 32px);width:100%;box-sizing:border-box;padding:0 16px;}
        .youtube-enhancer-notification{position:relative;max-width:700px;width:auto;background:var(--yt-glass-bg);color:var(--yt-text-primary);padding:8px 14px;font-size:13px;border-radius:var(--yt-radius-md);z-index:inherit;transition:opacity .35s,transform .32s;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);backdrop-filter:var(--yt-glass-blur); -webkit-backdrop-filter:var(--yt-glass-blur);font-weight:500;box-sizing:border-box;display:flex;align-items:center;gap:10px;pointer-events:auto;}
        .ytp-plus-settings-button{background:transparent;border:none;color:var(--yt-text-secondary);cursor:pointer;padding:var(--yt-space-sm);margin-right:var(--yt-space-sm);border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background-color .2s,transform .2s;}
        .ytp-plus-settings-button svg{width:24px;height:24px;}
        .ytp-plus-settings-button:hover{background:var(--yt-hover-bg);transform:rotate(30deg);color:var(--yt-text-secondary);}
        .ytp-plus-settings-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.45);display:flex;align-items:center;justify-content:center;z-index:100000;backdrop-filter:blur(8px) saturate(140%);-webkit-backdrop-filter:blur(8px) saturate(140%);animation:ytEnhanceFadeIn .25s ease-out;}
        .ytp-plus-settings-panel{background:var(--yt-glass-bg);color:var(--yt-text-primary);border-radius:20px;width:760px;max-width:94%;max-height:60vh;overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,0.45);animation:ytEnhanceScaleIn .28s cubic-bezier(.4,0,.2,1);backdrop-filter:blur(14px) saturate(140%);-webkit-backdrop-filter:blur(14px) saturate(140%);border:1.5px solid var(--yt-glass-border);will-change:transform,opacity;display:flex;flex-direction:row}
        .ytp-plus-settings-sidebar{width:240px;background:var(--yt-header-bg);border-right:1px solid var(--yt-glass-border);display:flex;flex-direction:column;backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);}
        .ytp-plus-settings-sidebar-header{padding:var(--yt-space-md) var(--yt-space-lg);border-bottom:1px solid var(--yt-glass-border);display:flex;justify-content:space-between;align-items:center;}
        .ytp-plus-settings-title{font-size:18px;font-weight:500;margin:0;color:var(--yt-text-primary);}
        .ytp-plus-settings-sidebar-close{padding:var(--yt-space-md) var(--yt-space-lg);display:flex;justify-content:flex-end;background:transparent;}
        .ytp-plus-settings-close{background:none;border:none;cursor:pointer;padding:var(--yt-space-sm);margin:-8px;color:var(--yt-text-primary);transition:color .2s,transform .2s;}
        .ytp-plus-settings-close:hover{color:var(--yt-accent);transform:scale(1.25) rotate(90deg);}
        .ytp-plus-settings-nav{flex:1;padding:var(--yt-space-md) 0;}
        .ytp-plus-settings-nav-item{display:flex;align-items:center;padding:12px var(--yt-space-lg);cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);font-size:14px;border-left:3px solid transparent;color:var(--yt-text-primary);}
        .ytp-plus-settings-nav-item:hover{background:var(--yt-hover-bg);}
        .ytp-plus-settings-nav-item.active{background:rgba(255,0,0,.1);border-left-color:var(--yt-accent);color:var(--yt-accent);font-weight:500;}
        .ytp-plus-settings-nav-item svg{width:18px;height:18px;margin-right:12px;opacity:.8;transition:opacity .2s,transform .2s;}
        .ytp-plus-settings-nav-item.active svg{opacity:1;transform:scale(1.1);}
        .ytp-plus-settings-nav-item:hover svg{transform:scale(1.05);}
        .ytp-plus-settings-main{flex:1;display:flex;flex-direction:column;overflow-y:auto;}
        .ytp-plus-settings-header{padding:var(--yt-space-md) var(--yt-space-lg);border-bottom:1px solid var(--yt-glass-border);background:var(--yt-header-bg);backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);}
        .ytp-plus-settings-content{flex:1;padding:var(--yt-space-md) var(--yt-space-lg);overflow-y:auto;}
        .ytp-plus-settings-section{margin-bottom:var(--yt-space-lg);}
        .ytp-plus-settings-section-title{font-size:16px;font-weight:500;margin-bottom:var(--yt-space-md);color:var(--yt-text-primary);}
        .ytp-plus-settings-section.hidden{display:none;}
        .ytp-plus-settings-item{display:flex;align-items:center;margin-bottom:var(--yt-space-md);padding:14px 18px;background:transparent;transition:all .25s cubic-bezier(.4,0,.2,1);border-radius:var(--yt-radius-md);}
        .ytp-plus-settings-item:hover{background:var(--yt-hover-bg);transform:translateX(6px);box-shadow:0 2px 8px rgba(0,0,0,.1);}
        .ytp-plus-settings-item-label{flex:1;font-size:14px;color:var(--yt-text-primary);}
        .ytp-plus-settings-item-description{font-size:12px;color:var(--yt-text-secondary);margin-top:4px;}
        .ytp-plus-settings-checkbox{appearance:none;-webkit-appearance:none;-moz-appearance:none;width:15px;height:15px;margin-left:auto;border:1px solid var(--yt-glass-border);border-radius:50%;background:transparent;display:inline-flex;align-items:center;justify-content:center;transition:all 250ms cubic-bezier(.4,0,.23,1);cursor:pointer;position:relative;flex-shrink:0;color:#fff;}
        html:not([dark]) .ytp-plus-settings-checkbox{border-color:rgba(0,0,0,.25);color:#222;}
        .ytp-plus-settings-checkbox:focus-visible{outline:2px solid var(--yt-accent);outline-offset:2px;}
        .ytp-plus-settings-checkbox:hover{background:var(--yt-hover-bg);transform:scale(1.1);}
        .ytp-plus-settings-checkbox::before{content:"";width:4px;height:2px;background:var(--yt-text-primary);position:absolute;transform:rotate(45deg);top:4px;left:3px;transition:width 100ms ease 50ms,opacity 50ms;transform-origin:0% 0%;opacity:0;}
        .ytp-plus-settings-checkbox::after{content:"";width:0;height:2px;background:var(--yt-text-primary);position:absolute;transform:rotate(305deg);top:9px;left:6px;transition:width 100ms ease,opacity 50ms;transform-origin:0% 0%;opacity:0;}
        .ytp-plus-settings-checkbox:checked{transform:rotate(0deg) scale(1.2);}
        .ytp-plus-settings-checkbox:checked::before{width:8px;opacity:1;background:#fff;transition:width 150ms ease 100ms,opacity 150ms ease 100ms;}
        .ytp-plus-settings-checkbox:checked::after{width:15px;opacity:1;background:#fff;transition:width 150ms ease 250ms,opacity 150ms ease 250ms;}
        .ytp-plus-footer{padding:var(--yt-space-md) var(--yt-space-lg);border-top:1px solid var(--yt-glass-border);display:flex;justify-content:flex-end;background:transparent;}
        .ytp-plus-button{padding:var(--yt-space-sm) var(--yt-space-md);border-radius:18px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);}
        .ytp-plus-button-primary{background:transparent;border:1px solid var(--yt-glass-border);color:var(--yt-text-primary);}
        .ytp-plus-button-primary:hover{background:var(--yt-accent);color:#fff;box-shadow:0 6px 16px rgba(255,0,0,.35);transform:translateY(-2px);}
        .app-icon{fill:var(--yt-text-primary);stroke:var(--yt-text-primary);transition:all .3s;}
        @keyframes ytEnhanceFadeIn{from{opacity:0;}to{opacity:1;}}
        @keyframes ytEnhanceScaleIn{from{opacity:0;transform:scale(.92) translateY(10px);}to{opacity:1;transform:scale(1) translateY(0);}}
        @media(max-width:768px){.ytp-plus-settings-panel{width:95%;max-height:80vh;flex-direction:column;}
        .ytp-plus-settings-sidebar{width:100%;max-height:120px;flex-direction:row;overflow-x:auto;}
        .ytp-plus-settings-nav{display:flex;flex-direction:row;padding:0;}
        .ytp-plus-settings-nav-item{white-space:nowrap;border-left:none;border-bottom:3px solid transparent;}
        .ytp-plus-settings-nav-item.active{border-left:none;border-bottom-color:var(--yt-accent);}
        .ytp-plus-settings-item{padding:10px 12px;}}
        .ytp-plus-settings-section h1{margin:-95px 90px 8px;font-family:'Montserrat',sans-serif;font-size:52px;font-weight:600;color:transparent;-webkit-text-stroke-width:1px;-webkit-text-stroke-color:var(--yt-text-stroke);cursor:pointer;transition:color .2s;}
        .ytp-plus-settings-section h1:hover{color:var(--yt-accent);-webkit-text-stroke-width:1px;-webkit-text-stroke-color:transparent;}
        .download-options{position:fixed;background:var(--yt-glass-bg);color:var(--yt-text-primary);border-radius:var(--yt-radius-md);width:150px;z-index:2147483647;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);overflow:hidden;backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease;transform:translateY(8px);box-sizing:border-box;}
        .download-options.visible{opacity:1;pointer-events:auto;transform:translateY(0);}
        .download-options-list{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;}
        .download-option-item{cursor:pointer;padding:12px;text-align:center;transition:background .2s,color .2s;width:100%;}
        .download-option-item:hover{background:var(--yt-hover-bg);color:var(--yt-accent);}
        .ytp-download-button{position:relative!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;height:100%!important;vertical-align:top!important;padding:0 10px!important;cursor:pointer!important;}
        .glass-panel{background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);box-shadow:var(--yt-glass-shadow);}
        .glass-card{background:var(--yt-panel-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);padding:var(--yt-space-md);backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);box-shadow:var(--yt-shadow);}
        .glass-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--yt-modal-bg);display:flex;align-items:center;justify-content:center;z-index:99999;backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);}
        .glass-button{background:var(--yt-button-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);padding:var(--yt-space-sm) var(--yt-space-md);color:var(--yt-text-primary);cursor:pointer;transition:all .2s ease;backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);}
        .glass-button:hover{background:var(--yt-hover-bg);transform:translateY(-1px);box-shadow:var(--yt-shadow);}
        .download-site-option{display:flex;flex-direction:column;align-items:stretch;gap:8px;}
        .download-site-header{display:flex;flex-direction:row;align-items:center;justify-content:space-between;width:100%;gap:8px;}
        .download-site-controls{width:100%;margin-top:6px;}
        .download-site-cta{display:flex;flex-direction:row;gap:8px;margin-top:6px;}
        .download-site-cta .glass-button{width:100%;}
        .download-site-option .ytp-plus-settings-checkbox{margin:0;}
        .download-site-name{font-weight:600;color:var(--yt-text-primary);}
        .download-site-desc{font-size:12px;color:var(--yt-text-secondary);margin-top:2px;}
          /* Ensure custom YouTube searchbox input backgrounds are transparent to match theme */
        .ytSearchboxComponentInputBox { background: transparent !important; }
          /* Fix native select/option contrast inside settings modal */
          .ytp-plus-settings-panel select,
          .ytp-plus-settings-panel select option {
            background: var(--yt-panel-bg) !important;
            color: var(--yt-text-primary) !important;
          }
          /* Improve select appearance and ensure options are legible */
          .ytp-plus-settings-panel select {
            -webkit-appearance: menulist !important;
            appearance: menulist !important;
            padding: 6px 8px !important;
            border-radius: 6px !important;
            border: 1px solid var(--yt-glass-border) !important;
          }
        `;

      // ✅ Use StyleManager instead of createElement('style')
      if (!document.getElementById('yt-enhancer-styles')) {
        YouTubeUtils.StyleManager.add('yt-enhancer-main', styles);
      }
    },

    addSettingsButtonToHeader() {
      this.waitForElement('ytd-masthead #end', 5000)
        .then(headerEnd => {
          if (!this.getElement('.ytp-plus-settings-button')) {
            const settingsButton = document.createElement('div');
            settingsButton.className = 'ytp-plus-settings-button';
            settingsButton.setAttribute('title', t('youtubeSettings'));
            settingsButton.innerHTML = `
                <svg width="24" height="24" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <path d="M39.23,26a16.52,16.52,0,0,0,.14-2,16.52,16.52,0,0,0-.14-2l4.33-3.39a1,1,0,0,0,.25-1.31l-4.1-7.11a1,1,0,0,0-1.25-.44l-5.11,2.06a15.68,15.68,0,0,0-3.46-2l-.77-5.43a1,1,0,0,0-1-.86H19.9a1,1,0,0,0-1,.86l-.77,5.43a15.36,15.36,0,0,0-3.46,2L9.54,9.75a1,1,0,0,0-1.25.44L4.19,17.3a1,1,0,0,0,.25,1.31L8.76,22a16.66,16.66,0,0,0-.14,2,16.52,16.52,0,0,0,.14,2L4.44,29.39a1,1,0,0,0-.25,1.31l4.1,7.11a1,1,0,0,0,1.25.44l5.11-2.06a15.68,15.68,0,0,0,3.46,2l.77,5.43a1,1,0,0,0,1,.86h8.2a1,1,0,0,0,1-.86l.77-5.43a15.36,15.36,0,0,0,3.46-2l5.11,2.06a1,1,0,0,0,1.25-.44l4.1-7.11a1,1,0,0,0-.25-1.31ZM24,31.18A7.18,7.18,0,1,1,31.17,24,7.17,7.17,0,0,1,24,31.18Z"/>
                </svg>
              `;

            settingsButton.addEventListener('click', this.openSettingsModal.bind(this));

            const avatarButton = headerEnd.querySelector('ytd-topbar-menu-button-renderer');
            if (avatarButton) {
              headerEnd.insertBefore(settingsButton, avatarButton);
            } else {
              headerEnd.appendChild(settingsButton);
            }
          }
        })
        .catch(() => {});
    },

    createSettingsModal() {
      const modal = document.createElement('div');
      modal.className = 'ytp-plus-settings-modal';

      modal.innerHTML = `
          <div class="ytp-plus-settings-panel">
            <div class="ytp-plus-settings-sidebar">
              <div class="ytp-plus-settings-sidebar-header">
                <h2 class="ytp-plus-settings-title">${t('settingsTitle')}</h2>                
              </div>
              <div class="ytp-plus-settings-nav">
                <div class="ytp-plus-settings-nav-item active" data-section="basic">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
                    <circle cx="9" cy="9" r="2"/>
                    <path d="m21 15-3.086-3.086a2 2 0 0 0-1.414-.586H13l-2-2v3h6l3 3"/>
                  </svg>
                  ${t('basicTab')}
                </div>
                <div class="ytp-plus-settings-nav-item" data-section="advanced">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="3"/>
                    <path d="m12 1 0 6m0 6 0 6"/>
                    <path d="m17.5 6.5-4.5 4.5m0 0-4.5 4.5m9-9L12 12l5.5 5.5"/>
                  </svg>
                  ${t('advancedTab')}
                </div>
                <div class="ytp-plus-settings-nav-item" data-section="experimental">
                  <svg width="64px" height="64px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path fill-rule="evenodd" clip-rule="evenodd" d="M18.019 4V15.0386L6.27437 39.3014C5.48686 40.9283 6.16731 42.8855 7.79421 43.673C8.23876 43.8882 8.72624 44 9.22013 44H38.7874C40.5949 44 42.0602 42.5347 42.0602 40.7273C42.0602 40.2348 41.949 39.7488 41.7351 39.3052L30.0282 15.0386V4H18.019Z" stroke="currentColor" stroke-width="4" stroke-linejoin="round"></path> <path d="M10.9604 29.9998C13.1241 31.3401 15.2893 32.0103 17.4559 32.0103C19.6226 32.0103 21.7908 31.3401 23.9605 29.9998C26.1088 28.6735 28.2664 28.0103 30.433 28.0103C32.5997 28.0103 34.7755 28.6735 36.9604 29.9998" stroke="currentColor" stroke-width="4" stroke-linecap="round"></path>
                  </svg>
                  ${t('experimentalTab')}
                </div>
                <div class="ytp-plus-settings-nav-item" data-section="report">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                    <polyline points="14 2 14 8 20 8"></polyline>
                    <line x1="12" y1="18" x2="12" y2="12"></line>
                    <line x1="12" y1="9" x2="12.01" y2="9"></line>
                  </svg>
                  ${t('reportTab')}
                </div>
                <div class="ytp-plus-settings-nav-item" data-section="about">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="10"/>
                    <path d="m9 12 2 2 4-4"/>
                  </svg>
                  ${t('aboutTab')}
                </div>
              </div>
            </div>
            <div class="ytp-plus-settings-main">
              <div class="ytp-plus-settings-sidebar-close">
                <button class="ytp-plus-settings-close" aria-label="${t('closeButton')}">
                  <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
                  </svg>
                </button>
              </div>              
              <div class="ytp-plus-settings-content">                
                <div class="ytp-plus-settings-section" data-section="basic">
                  <div class="ytp-plus-settings-item">
                    <div>
                      <label class="ytp-plus-settings-item-label">${t('speedControl')}</label>
                      <div class="ytp-plus-settings-item-description">${t('speedControlDesc')}</div>
                    </div>
                    <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableSpeedControl" ${this.settings.enableSpeedControl ? 'checked' : ''}>
                  </div>
                  <div class="ytp-plus-settings-item">
                    <div>
                      <label class="ytp-plus-settings-item-label">${t('screenshotButton')}</label>
                      <div class="ytp-plus-settings-item-description">${t('screenshotButtonDesc')}</div>
                    </div>
                    <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableScreenshot" ${this.settings.enableScreenshot ? 'checked' : ''}>
                  </div>
                  <div class="ytp-plus-settings-item">
                    <div>
                      <label class="ytp-plus-settings-item-label">${t('downloadButton')}</label>
                      <div class="ytp-plus-settings-item-description">${t('downloadButtonDesc')}</div>
                    </div>
                    <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableDownload" ${this.settings.enableDownload ? 'checked' : ''}>
                  </div>
                  <div class="download-submenu" style="display:${this.settings.enableDownload ? 'block' : 'none'};margin-left:12px;margin-bottom:12px;">
                    <div class="glass-card" style="display:flex;flex-direction:column;gap:8px;">
                      <div class="download-site-option">
                        <div class="download-site-header">
                          <div>
                            <div class="download-site-name">${this.settings.downloadSiteCustomization?.y2mate?.name || 'Y2Mate'}</div>
                            <div class="download-site-desc">${t('customDownloader')}</div>
                          </div>
                          <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="downloadSite_y2mate" ${this.settings.downloadSites?.y2mate ? 'checked' : ''}>
                        </div>
                        <div class="download-site-controls" style="display:${this.settings.downloadSites?.y2mate ? 'block' : 'none'};">
                          <input type="text" placeholder="${t('siteName')}" value="${this.settings.downloadSiteCustomization?.y2mate?.name || 'Y2Mate'}" 
                              data-site="y2mate" data-field="name" class="download-site-input" 
                              style="width:100%;margin-top:6px;padding:6px;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);border-radius:4px;color:white;font-size:12px;">
                          <input type="text" placeholder="${t('urlTemplate')}" value="${this.settings.downloadSiteCustomization?.y2mate?.url || 'https://www.y2mate.com/youtube/{videoId}'}" 
                            data-site="y2mate" data-field="url" class="download-site-input" 
                            style="width:100%;margin-top:4px;padding:6px;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);border-radius:4px;color:white;font-size:11px;">
                          <div class="download-site-cta">
                            <button class="glass-button" id="download-y2mate-save" style="padding:6px 10px;font-size:12px;">${t('saveButton')}</button>
                            <button class="glass-button" id="download-y2mate-reset" style="padding:6px 10px;font-size:12px;background:rgba(255,0,0,0.12);">${t('resetButton')}</button>
                          </div>
                        </div>
                      </div>

                      <div class="download-site-option">
                        <div class="download-site-header">
                          <div>
                            <div class="download-site-name">${this.settings.downloadSiteCustomization?.xbbuddy?.name || '9xbuddy'}</div>
                            <div class="download-site-desc">${t('customDownloader')}</div>
                          </div>
                          <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="downloadSite_xbbuddy" ${this.settings.downloadSites?.xbbuddy ? 'checked' : ''}>
                        </div>
                        <div class="download-site-controls" style="display:${this.settings.downloadSites?.xbbuddy ? 'block' : 'none'};">
                          <input type="text" placeholder="${t('siteName')}" value="${this.settings.downloadSiteCustomization?.xbbuddy?.name || '9xbuddy'}" 
                            data-site="xbbuddy" data-field="name" class="download-site-input" 
                            style="width:100%;margin-top:6px;padding:6px;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);border-radius:4px;color:white;font-size:12px;">
                          <input type="text" placeholder="${t('urlTemplate')}" value="${this.settings.downloadSiteCustomization?.xbbuddy?.url || 'https://9xbuddy.org/process?url={videoUrl}'}" 
                            data-site="xbbuddy" data-field="url" class="download-site-input" 
                            style="width:100%;margin-top:4px;padding:6px;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);border-radius:4px;color:white;font-size:11px;">
                          <div class="download-site-cta">
                            <button class="glass-button" id="download-xbbuddy-save" style="padding:6px 10px;font-size:12px;">${t('saveButton')}</button>
                            <button class="glass-button" id="download-xbbuddy-reset" style="padding:6px 10px;font-size:12px;background:rgba(255,0,0,0.12);">${t('resetButton')}</button>
                          </div>
                        </div>
                      </div>

                      <div class="download-site-option" style="padding:4px 0;">
                        <div>
                          <div class="download-site-name">${t('byYTDL')}</div>
                          <div class="download-site-desc">${t('alwaysEnabled')}</div>
                        </div>
                        <button class="glass-button" id="open-ytdl-github" style="margin:0;padding:10px 14px;font-size:13px;">
                          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
                            <polyline points="15,3 21,3 21,9"/>
                            <line x1="10" y1="14" x2="21" y2="3"/>
                          </svg>
                        </button>
                      </div>
                    </div>
                  </div>
                </div>
                
                <div class="ytp-plus-settings-section hidden" data-section="advanced">
                </div>

                <div class="ytp-plus-settings-section hidden" data-section="experimental">
                </div>

                <div class="ytp-plus-settings-section hidden" data-section="report">
                </div>
                
                <div class="ytp-plus-settings-section hidden" data-section="about">
                  <svg class="app-icon" width="90" height="90" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
                    <path d="m23.24,4.62c-0.85,0.45 -2.19,2.12 -4.12,5.13c-1.54,2.41 -2.71,4.49 -3.81,6.8c-0.55,1.14 -1.05,2.2 -1.13,2.35c-0.08,0.16 -0.78,0.7 -1.66,1.28c-1.38,0.91 -1.8,1.29 -1.4,1.28c0.08,0 0.67,-0.35 1.31,-0.77c0.64,-0.42 1.19,-0.76 1.2,-0.74c0.02,0.02 -0.1,0.31 -0.25,0.66c-1.03,2.25 -1.84,5.05 -1.84,6.37c0.01,1.89 0.84,2.67 2.86,2.67c1.08,0 1.94,-0.31 3.66,-1.29c1.84,-1.06 3.03,-1.93 4.18,-3.09c1.69,-1.7 2.91,-3.4 3.28,-4.59c0.59,-1.9 -0.1,-3.08 -2.02,-3.44c-0.87,-0.16 -2.85,-0.14 -3.75,0.06c-1.78,0.38 -2.74,0.76 -2.5,1c0.03,0.03 0.5,-0.1 1.05,-0.28c1.49,-0.48 2.34,-0.59 3.88,-0.53c1.64,0.07 2.09,0.19 2.69,0.75l0.46,0.43l0,0.87c0,0.74 -0.05,0.98 -0.35,1.6c-0.69,1.45 -2.69,3.81 -4.37,5.14c-0.93,0.74 -2.88,1.94 -4.07,2.5c-1.64,0.77 -3.56,0.72 -4.21,-0.11c-0.39,-0.5 -0.5,-1.02 -0.44,-2.11c0.05,-0.85 0.16,-1.32 0.67,-2.86c0.34,-1.01 0.86,-2.38 1.15,-3.04c0.52,-1.18 0.55,-1.22 1.6,-2.14c4.19,-3.65 8.42,-9.4 9.02,-12.26c0.2,-0.94 0.13,-1.46 -0.21,-1.7c-0.31,-0.22 -0.38,-0.21 -0.89,0.06m0.19,0.26c-0.92,0.41 -3.15,3.44 -5.59,7.6c-1.05,1.79 -3.12,5.85 -3.02,5.95c0.07,0.07 1.63,-1.33 2.58,-2.34c1.57,-1.65 3.73,-4.39 4.88,-6.17c1.31,-2.03 2.06,-4.11 1.77,-4.89c-0.13,-0.34 -0.16,-0.35 -0.62,-0.15m11.69,13.32c-0.3,0.6 -1.19,2.54 -1.98,4.32c-1.6,3.62 -1.67,3.71 -2.99,4.34c-1.13,0.54 -2.31,0.85 -3.54,0.92c-0.99,0.06 -1.08,0.04 -1.38,-0.19c-0.28,-0.22 -0.31,-0.31 -0.26,-0.7c0.03,-0.25 0.64,-1.63 1.35,-3.08c1.16,-2.36 2.52,-5.61 2.52,-6.01c0,-0.49 -0.36,0.19 -1.17,2.22c-0.51,1.26 -1.37,3.16 -1.93,4.24c-0.55,1.08 -1.04,2.17 -1.09,2.43c-0.1,0.59 0.07,1.03 0.49,1.28c0.78,0.46 3.3,0.06 5.13,-0.81l0.93,-0.45l-0.66,1.25c-0.7,1.33 -3.36,6.07 -4.31,7.67c-2.02,3.41 -3.96,5.32 -6.33,6.21c-2.57,0.96 -4.92,0.74 -6.14,-0.58c-0.81,-0.88 -0.82,-1.71 -0.04,-3.22c1.22,-2.36 6.52,-6.15 10.48,-7.49c0.52,-0.18 0.95,-0.39 0.95,-0.46c0,-0.21 -0.19,-0.18 -1.24,0.2c-1.19,0.43 -3.12,1.37 -4.34,2.11c-2.61,1.59 -5.44,4.09 -6.13,5.43c-1.15,2.2 -0.73,3.61 1.4,4.6c0.59,0.28 0.75,0.3 2.04,0.3c1.67,0 2.42,-0.18 3.88,-0.89c1.87,-0.92 3.17,-2.13 4.72,-4.41c0.98,-1.44 4.66,-7.88 5.91,-10.33c0.25,-0.49 0.68,-1.19 0.96,-1.56c0.28,-0.37 0.76,-1.15 1.06,-1.73c0.82,-1.59 2.58,-6.1 2.58,-6.6c0,-0.06 -0.07,-0.1 -0.17,-0.1c-0.1,0 -0.39,0.44 -0.71,1.09m-1.34,3.7c-0.93,2.08 -1.09,2.48 -0.87,2.2c0.19,-0.24 1.66,-3.65 1.6,-3.71c-0.02,-0.02 -0.35,0.66 -0.73,1.51" fill="none" fill-rule="evenodd" stroke="currentColor" />
                  </svg>
                    <h1>YouTube +</h1><br><br>
                </div>
              </div>
              <div class="ytp-plus-footer">
                <button class="ytp-plus-button ytp-plus-button-primary" id="ytp-plus-save-settings">${t('saveChanges')}</button>
              </div>
            </div>
          </div>
        `;

      // Track unsaved changes: hide save icon until user modifies something
      let dirty = false;
      const saveIconBtn = modal.querySelector('#ytp-plus-save-settings-icon');
      if (saveIconBtn) saveIconBtn.style.display = 'none';
      const markDirty = () => {
        if (!dirty) {
          dirty = true;
          if (saveIconBtn) saveIconBtn.style.display = '';
        }
      };

      // Event delegation for better performance
      modal.addEventListener('click', e => {
        const target = /** @type {HTMLElement} */ (e.target);
        if (target === modal) modal.remove();
        // Close when clicking the moved close button (or legacy class)
        if (
          target.id === 'ytp-plus-close-settings' ||
          target.id === 'ytp-plus-close-settings-icon' ||
          target.classList.contains('ytp-plus-settings-close') ||
          target.closest('.ytp-plus-settings-close') ||
          target.closest('#ytp-plus-close-settings') ||
          target.closest('#ytp-plus-close-settings-icon')
        ) {
          modal.remove();
        }

        // Обработка кнопки GitHub для YTDL
        if (target.id === 'open-ytdl-github' || target.closest('#open-ytdl-github')) {
          window.open('https://github.com/diorhc/YouTube-Downloader', '_blank');
          return;
        }

        // Handle sidebar navigation (support clicks on children by using closest)
        {
          const navItem = /** @type {HTMLElement | null} */ (
            target.classList && target.classList.contains('ytp-plus-settings-nav-item')
              ? target
              : target.closest && target.closest('.ytp-plus-settings-nav-item')
          );
          if (navItem) {
            const section = /** @type {HTMLElement} */ (navItem).dataset.section;
            modal
              .querySelectorAll('.ytp-plus-settings-nav-item')
              .forEach(item => item.classList.remove('active'));
            modal
              .querySelectorAll('.ytp-plus-settings-section')
              .forEach(s => s.classList.add('hidden'));
            navItem.classList.add('active');
            const targetSection = modal.querySelector(
              `.ytp-plus-settings-section[data-section="${section}"]`
            );
            if (targetSection) targetSection.classList.remove('hidden');
          }
        }

        if (target.classList.contains('ytp-plus-settings-checkbox')) {
          const setting = /** @type {HTMLElement} */ (target).dataset.setting;
          if (!setting) return;

          // Сохранение простых настроек (enableSpeedControl, enableScreenshot, enableDownload)
          if (!setting.startsWith('downloadSite_')) {
            this.settings[setting] = /** @type {HTMLInputElement} */ (target).checked;

            // Mark modal as dirty so save icon becomes visible
            try {
              markDirty();
            } catch {}

            // Показывать/скрывать сабменю при переключении Download
            if (setting === 'enableDownload') {
              const submenu = modal.querySelector('.download-submenu');
              if (submenu) {
                submenu.style.display = /** @type {HTMLInputElement} */ (target).checked
                  ? 'block'
                  : 'none';
              }
            }
          } else {
            // Обработка чекбоксов в сабменю: data-setting = downloadSite_<key>
            const key = setting.replace('downloadSite_', '');
            if (!this.settings.downloadSites) {
              this.settings.downloadSites = { y2mate: true, xbbuddy: true };
            }
            const checkbox = /** @type {HTMLElement} */ (target);
            this.settings.downloadSites[key] = /** @type {HTMLInputElement} */ (checkbox).checked;

            // Mark modal as dirty when toggling download site
            try {
              markDirty();
            } catch {}
            // Toggle visibility of controls for this site (if present in DOM)
            try {
              const container = checkbox.closest('.download-site-option');
              if (container) {
                const controls = container.querySelector('.download-site-controls');
                if (controls) {
                  controls.style.display = /** @type {HTMLInputElement} */ (checkbox).checked
                    ? 'block'
                    : 'none';
                }
              }
            } catch (err) {
              console.warn('[YouTube+] toggle download-site-controls failed:', err);
            }
            // Rebuild dropdown if present
            try {
              if (
                typeof window !== 'undefined' &&
                /** @type {any} */ (window).youtubePlus &&
                typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                  'function'
              ) {
                /** @type {any} */ (window).youtubePlus.settings =
                  /** @type {any} */ (window).youtubePlus.settings || this.settings;
                /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
              }
            } catch (err) {
              console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
            }
          }
        }

        // Обработка кастомизации download сайтов
        if (target.classList.contains('download-site-input')) {
          const site = /** @type {HTMLElement} */ (target).dataset.site;
          const field = /** @type {HTMLElement} */ (target).dataset.field;
          if (!site || !field) return;

          if (!this.settings.downloadSiteCustomization) {
            this.settings.downloadSiteCustomization = {
              y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
              xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
            };
          }
          if (!this.settings.downloadSiteCustomization[site]) {
            this.settings.downloadSiteCustomization[site] = { name: '', url: '' };
          }

          this.settings.downloadSiteCustomization[site][field] = /** @type {HTMLInputElement} */ (
            target
          ).value;

          // Mark modal as dirty when customizing download site fields
          try {
            markDirty();
          } catch {}

          // Обновить имя в UI в реальном времени
          if (field === 'name') {
            const nameDisplay = target
              .closest('.download-site-option')
              ?.querySelector('.download-site-name');
            if (nameDisplay) {
              nameDisplay.textContent =
                /** @type {HTMLInputElement} */ (target).value ||
                (site === 'y2mate' ? 'Y2Mate' : '9xbuddy');
            }
          }
          // Rebuild dropdown if present so changes reflect immediately
          try {
            if (
              typeof window !== 'undefined' &&
              /** @type {any} */ (window).youtubePlus &&
              typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                'function'
            ) {
              /** @type {any} */ (window).youtubePlus.settings =
                /** @type {any} */ (window).youtubePlus.settings || this.settings;
              /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
            }
          } catch (err) {
            console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
          }
        }

        if (target.id === 'ytp-plus-save-settings' || target.id === 'ytp-plus-save-settings-icon') {
          this.saveSettings();
          modal.remove();
          this.showNotification(t('settingsSaved'));
        }
        // Save specific Y2Mate customization
        if (target.id === 'download-y2mate-save') {
          // Ensure settings structure
          if (!this.settings.downloadSiteCustomization) {
            this.settings.downloadSiteCustomization = {
              y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
              xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
            };
          }
          if (!this.settings.downloadSiteCustomization.y2mate) {
            this.settings.downloadSiteCustomization.y2mate = { name: '', url: '' };
          }
          // Read current inputs inside this download-site-option
          const container = /** @type {HTMLElement|null} */ (
            /** @type {unknown} */ (target.closest('.download-site-option'))
          );
          if (container) {
            const nameInput = container.querySelector(
              'input.download-site-input[data-site="y2mate"][data-field="name"]'
            );
            const urlInput = container.querySelector(
              'input.download-site-input[data-site="y2mate"][data-field="url"]'
            );
            if (nameInput) this.settings.downloadSiteCustomization.y2mate.name = nameInput.value;
            if (urlInput) this.settings.downloadSiteCustomization.y2mate.url = urlInput.value;
          }
          this.saveSettings();
          try {
            if (
              typeof window !== 'undefined' &&
              /** @type {any} */ (window).youtubePlus &&
              typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                'function'
            ) {
              /** @type {any} */ (window).youtubePlus.settings =
                /** @type {any} */ (window).youtubePlus.settings || this.settings;
              /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
            }
          } catch (err) {
            console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
          }
          this.showNotification(t('y2mateSettingsSaved'));
        }

        // Reset Y2Mate to defaults
        if (target.id === 'download-y2mate-reset') {
          if (!this.settings.downloadSiteCustomization) {
            // Initialize with expected structure to satisfy type checks
            this.settings.downloadSiteCustomization = {
              y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
              xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
            };
          }
          this.settings.downloadSiteCustomization.y2mate = {
            name: 'Y2Mate',
            url: 'https://www.y2mate.com/youtube/{videoId}',
          };
          // Update inputs in modal if present
          const container = /** @type {HTMLElement|null} */ (
            /** @type {unknown} */ (modal.querySelector('.download-site-option'))
          );
          if (container) {
            const nameInput = container.querySelector(
              'input.download-site-input[data-site="y2mate"][data-field="name"]'
            );
            const urlInput = container.querySelector(
              'input.download-site-input[data-site="y2mate"][data-field="url"]'
            );
            const nameDisplay = container.querySelector('.download-site-name');
            if (nameInput) nameInput.value = this.settings.downloadSiteCustomization.y2mate.name;
            if (urlInput) urlInput.value = this.settings.downloadSiteCustomization.y2mate.url;
            if (nameDisplay) {
              nameDisplay.textContent = this.settings.downloadSiteCustomization.y2mate.name;
            }
          }
          this.saveSettings();
          try {
            if (
              typeof window !== 'undefined' &&
              /** @type {any} */ (window).youtubePlus &&
              typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                'function'
            ) {
              /** @type {any} */ (window).youtubePlus.settings =
                /** @type {any} */ (window).youtubePlus.settings || this.settings;
              /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
            }
          } catch (err) {
            console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
          }
          this.showNotification(t('y2mateReset'));
        }

        // Save specific 9xBuddy customization
        if (target.id === 'download-xbbuddy-save') {
          if (!this.settings.downloadSiteCustomization) {
            // Initialize expected structure
            this.settings.downloadSiteCustomization = {
              y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
              xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
            };
          }
          if (!this.settings.downloadSiteCustomization.xbbuddy) {
            this.settings.downloadSiteCustomization.xbbuddy = { name: '', url: '' };
          }
          const container = /** @type {HTMLElement|null} */ (
            /** @type {unknown} */ (target.closest('.download-site-option'))
          );
          if (container) {
            const nameInput = container.querySelector(
              'input.download-site-input[data-site="xbbuddy"][data-field="name"]'
            );
            const urlInput = container.querySelector(
              'input.download-site-input[data-site="xbbuddy"][data-field="url"]'
            );
            if (nameInput) this.settings.downloadSiteCustomization.xbbuddy.name = nameInput.value;
            if (urlInput) this.settings.downloadSiteCustomization.xbbuddy.url = urlInput.value;
          }
          this.saveSettings();
          try {
            if (
              typeof window !== 'undefined' &&
              /** @type {any} */ (window).youtubePlus &&
              typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                'function'
            ) {
              /** @type {any} */ (window).youtubePlus.settings =
                /** @type {any} */ (window).youtubePlus.settings || this.settings;
              /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
            }
          } catch (err) {
            console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
          }
          this.showNotification(t('xbuddySettingsSaved'));
        }

        // Reset 9xBuddy to defaults
        if (target.id === 'download-xbbuddy-reset') {
          if (!this.settings.downloadSiteCustomization) {
            this.settings.downloadSiteCustomization = {
              y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
              xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
            };
          }
          this.settings.downloadSiteCustomization.xbbuddy = {
            name: '9xbuddy',
            url: 'https://9xbuddy.org/process?url={videoUrl}',
          };
          // Update inputs in modal if present
          const container = /** @type {HTMLElement|null} */ (
            /** @type {unknown} */ (modal.querySelectorAll('.download-site-option')[1])
          );
          if (container) {
            const nameInput = container.querySelector(
              'input.download-site-input[data-site="xbbuddy"][data-field="name"]'
            );
            const urlInput = container.querySelector(
              'input.download-site-input[data-site="xbbuddy"][data-field="url"]'
            );
            const nameDisplay = container.querySelector('.download-site-name');
            if (nameInput) nameInput.value = this.settings.downloadSiteCustomization.xbbuddy.name;
            if (urlInput) urlInput.value = this.settings.downloadSiteCustomization.xbbuddy.url;
            if (nameDisplay) {
              nameDisplay.textContent = this.settings.downloadSiteCustomization.xbbuddy.name;
            }
          }
          this.saveSettings();
          try {
            if (
              typeof window !== 'undefined' &&
              /** @type {any} */ (window).youtubePlus &&
              typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                'function'
            ) {
              /** @type {any} */ (window).youtubePlus.settings =
                /** @type {any} */ (window).youtubePlus.settings || this.settings;
              /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
            }
          } catch (err) {
            console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
          }
          this.showNotification(t('xbuddyReset'));
        }
      });

      // Обработка изменений input полей для кастомизации
      modal.addEventListener('input', e => {
        const target = /** @type {EventTarget & HTMLElement} */ (e.target);
        if (target.classList.contains('download-site-input')) {
          const site = /** @type {HTMLElement} */ (target).dataset.site;
          const field = /** @type {HTMLElement} */ (target).dataset.field;
          if (!site || !field) return;

          if (!this.settings.downloadSiteCustomization) {
            this.settings.downloadSiteCustomization = {
              y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
              xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
            };
          }
          if (!this.settings.downloadSiteCustomization[site]) {
            this.settings.downloadSiteCustomization[site] = { name: '', url: '' };
          }

          this.settings.downloadSiteCustomization[site][field] = /** @type {HTMLInputElement} */ (
            target
          ).value;

          // Обновить имя в UI в реальном времени
          if (field === 'name') {
            const nameDisplay = /** @type {HTMLElement|null} */ (
              /** @type {unknown} */ (target.closest('.download-site-option'))
            )?.querySelector('.download-site-name');
            if (nameDisplay) {
              nameDisplay.textContent =
                /** @type {HTMLInputElement} */ (target).value ||
                (site === 'y2mate' ? 'Y2Mate' : '9xbuddy');
            }
          }
          // Rebuild dropdown if present so changes reflect immediately
          try {
            if (
              typeof window !== 'undefined' &&
              /** @type {any} */ (window).youtubePlus &&
              typeof (/** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown) ===
                'function'
            ) {
              /** @type {any} */ (window).youtubePlus.settings =
                /** @type {any} */ (window).youtubePlus.settings || this.settings;
              /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown();
            }
          } catch (err) {
            console.warn('[YouTube+] rebuildDownloadDropdown call failed:', err);
          }
        }
      });

      // Allow report module to populate the report settings section if available
      try {
        if (
          typeof window !== 'undefined' &&
          /** @type {any} */ (window).youtubePlusReport &&
          typeof (/** @type {any} */ (window).youtubePlusReport.render) === 'function'
        ) {
          try {
            /** @type {any} */ (window).youtubePlusReport.render(modal);
          } catch (e) {
            YouTubeUtils.logError('Report', 'report.render failed', e);
          }
        }
      } catch (e) {
        YouTubeUtils.logError('Report', 'Failed to initialize report section', e);
      }

      return modal;
    },

    openSettingsModal() {
      const existingModal = this.getElement('.ytp-plus-settings-modal', false);
      if (existingModal) existingModal.remove();
      document.body.appendChild(this.createSettingsModal());
    },

    waitForElement(selector, timeout = 5000) {
      // ✅ Use centralized utility
      return YouTubeUtils.waitForElement(selector, timeout);
    },

    addCustomButtons() {
      const controls = this.getElement('.ytp-right-controls');
      if (!controls) return;

      if (!this.getElement('.ytp-screenshot-button')) this.addScreenshotButton(controls);
      if (!this.getElement('.ytp-download-button')) this.addDownloadButton(controls);
      if (!this.getElement('.speed-control-btn')) this.addSpeedControlButton(controls);

      if (!document.getElementById('speed-indicator')) {
        const indicator = document.createElement('div');
        indicator.id = 'speed-indicator';
        const player = document.getElementById('movie_player');
        if (player) player.appendChild(indicator);
      }

      this.handleFullscreenChange();
    },

    addScreenshotButton(controls) {
      const button = document.createElement('button');
      button.className = 'ytp-button ytp-screenshot-button';
      button.setAttribute('title', t('takeScreenshot'));
      button.innerHTML = `
          <svg width="24" height="24" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M19.83,8.77l-2.77,2.84H6.29A1.79,1.79,0,0,0,4.5,13.4V36.62a1.8,1.8,0,0,0,1.79,1.8H41.71a1.8,1.8,0,0,0,1.79-1.8V13.4a1.79,1.79,0,0,0-1.79-1.79H30.94L28.17,8.77Zm18.93,5.74a1.84,1.84,0,1,1,0,3.68A1.84,1.84,0,0,1,38.76,14.51ZM24,17.71a8.51,8.51,0,1,1-8.51,8.51A8.51,8.51,0,0,1,24,17.71Z"/>
          </svg>
        `;
      button.addEventListener('click', this.captureFrame.bind(this));
      controls.insertBefore(button, controls.firstChild);
    },

    addDownloadButton(controls) {
      if (!this.settings.enableDownload) return;
      const button = document.createElement('div');
      button.className = 'ytp-button ytp-download-button';
      button.setAttribute('title', t('downloadOptions'));
      button.setAttribute('tabindex', '0');
      button.setAttribute('role', 'button');
      button.setAttribute('aria-haspopup', 'true');
      button.setAttribute('aria-expanded', 'false');
      button.innerHTML = `
          <svg fill="currentColor" width="24" height="24" viewBox="0 0 256 256" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;margin:auto;vertical-align:middle;">
        <path d="M83.17188,112.83984a4.00026,4.00026,0,0,1,5.65624-5.6582L124,142.34473V40a4,4,0,0,1,8,0V142.34473l35.17188-35.16309a4.00026,4.00026,0,0,1,5.65624,5.6582l-42,41.98926a4.00088,4.00088,0,0,1-5.65624,0ZM216,148a4.0002,4.0002,0,0,0-4,4v56a4.00427,4.00427,0,0,1-4,4H48a4.00427,4.00427,0,0,1-4-4V152a4,4,0,0,0-8,0v56a12.01343,12.01343,0,0,0,12,12H208a12.01343,12.01343,0,0,0,12-12V152A4.0002,4.0002,0,0,0,216,148Z"/>
          </svg>
        `;

      // Dropdown options
      const options = document.createElement('div');
      options.className = 'download-options';
      options.setAttribute('role', 'menu');

      // Position dropdown below button (centered)
      function positionDropdown() {
        const rect = button.getBoundingClientRect();
        const left = Math.max(8, rect.left + rect.width / 2 - 75);
        const bottom = Math.max(8, window.innerHeight - rect.top + 12);
        options.style.left = `${left}px`;
        options.style.bottom = `${bottom}px`;
      }

      // Helper to open download site
      function openDownloadSite(url, isYTDL = false) {
        if (isYTDL) {
          // For YTDL: copy video URL to clipboard and open localhost
          const videoId = new URLSearchParams(location.search).get('v');
          const videoUrl = videoId ? `https://www.youtube.com/watch?v=${videoId}` : location.href;

          // Copy to clipboard
          navigator.clipboard
            .writeText(videoUrl)
            .then(() => {
              // Show notification
              YouTubeUtils.NotificationManager.show(t('copiedToClipboard'), {
                duration: 2000,
                type: 'success',
              });
            })
            .catch(() => {
              // Fallback for older browsers
              const input = document.createElement('input');
              input.value = videoUrl;
              document.body.appendChild(input);
              input.select();
              document.execCommand('copy');
              document.body.removeChild(input);
              YouTubeUtils.NotificationManager.show(t('copiedToClipboard'), {
                duration: 2000,
                type: 'success',
              });
            });

          // Open YTDL in new tab
          window.open(url, '_blank');
        } else {
          window.open(url, '_blank');
        }
        options.classList.remove('visible');
        button.setAttribute('aria-expanded', 'false');
      }

      // Helper to rebuild the dropdown if settings changed while dropdown exists
      // Exposed on button element via dataset so external handlers can trigger a rebuild
      function rebuildDropdown() {
        try {
          // Remove existing list if present
          const existingList = options.querySelector('.download-options-list');
          if (existingList) existingList.remove();

          // Rebuild downloadSites from current settings
          const customizationNow =
            typeof window !== 'undefined' &&
            /** @type {any} */ (window).youtubePlus &&
            /** @type {any} */ (window).youtubePlus.settings &&
            /** @type {any} */ (window).youtubePlus.settings.downloadSiteCustomization
              ? /** @type {any} */ (window).youtubePlus.settings.downloadSiteCustomization
              : customization;
          const videoIdNow = new URLSearchParams(location.search).get('v');
          const videoUrlNow = videoIdNow
            ? `https://www.youtube.com/watch?v=${videoIdNow}`
            : location.href;
          const buildUrlNow = template =>
            (template || '')
              .replace('{videoId}', videoIdNow || '')
              .replace('{videoUrl}', encodeURIComponent(videoUrlNow));

          const baseSitesNow = [
            {
              key: 'y2mate',
              name: customizationNow?.y2mate?.name || 'Y2Mate',
              url: buildUrlNow(
                customizationNow?.y2mate?.url || `https://www.y2mate.com/youtube/{videoId}`
              ),
              isYTDL: false,
            },
            {
              key: 'xbbuddy',
              name: customizationNow?.xbbuddy?.name || '9xbuddy',
              url: buildUrlNow(
                customizationNow?.xbbuddy?.url || `https://9xbuddy.org/process?url={videoUrl}`
              ),
              isYTDL: false,
            },
            { key: 'ytdl', name: 'by YTDL', url: `http://localhost:5005`, isYTDL: true },
          ];

          const enabledSitesNow =
            typeof window !== 'undefined' &&
            /** @type {any} */ (window).youtubePlus &&
            /** @type {any} */ (window).youtubePlus.settings &&
            /** @type {any} */ (window).youtubePlus.settings.downloadSites
              ? /** @type {any} */ (window).youtubePlus.settings.downloadSites
              : enabledSites;

          const downloadSitesNow = baseSitesNow.filter(s => {
            if (s.key === 'ytdl') return true;
            return enabledSitesNow[s.key] !== false;
          });

          // If only one site remains replace click handler
          if (downloadSitesNow.length === 1) {
            const single = downloadSitesNow[0];
            // Remove any existing clickable handlers on button
            button.replaceWith(button.cloneNode(true));
            const newButton = controls.querySelector('.ytp-download-button');
            if (newButton) {
              newButton.addEventListener('click', () =>
                openDownloadSite(single.url, single.isYTDL)
              );
            }
            return;
          }

          // Build new list
          const newList = document.createElement('div');
          newList.className = 'download-options-list';
          downloadSitesNow.forEach(site => {
            const opt = document.createElement('div');
            opt.className = 'download-option-item';
            opt.textContent = site.name;
            opt.setAttribute('role', 'menuitem');
            opt.setAttribute('tabindex', '0');
            opt.addEventListener('click', () => openDownloadSite(site.url, site.isYTDL));
            opt.addEventListener('keydown', e => {
              if (e.key === 'Enter' || e.key === ' ') openDownloadSite(site.url, site.isYTDL);
            });
            newList.appendChild(opt);
          });
          options.appendChild(newList);
        } catch (err) {
          console.warn('[YouTube+] rebuildDropdown failed:', err);
        }
      }

      // Get current video URL
      const videoId = new URLSearchParams(location.search).get('v');
      const videoUrl = videoId ? `https://www.youtube.com/watch?v=${videoId}` : location.href;

      // Получить кастомные настройки или использовать defaults
      const customization = this.settings.downloadSiteCustomization || {
        y2mate: { name: 'Y2Mate', url: 'https://www.y2mate.com/youtube/{videoId}' },
        xbbuddy: { name: '9xbuddy', url: 'https://9xbuddy.org/process?url={videoUrl}' },
      };

      // Функция для замены плейсхолдеров в URL
      const buildUrl = template => {
        return template
          .replace('{videoId}', videoId || '')
          .replace('{videoUrl}', encodeURIComponent(videoUrl));
      };

      // List of download sites (ytdl всегда включён, filter by user settings.downloadSites для остальных)
      const baseSites = [
        {
          key: 'y2mate',
          name: customization.y2mate?.name || 'Y2Mate',
          url: buildUrl(customization.y2mate?.url || `https://www.y2mate.com/youtube/{videoId}`),
          isYTDL: false,
        },
        {
          key: 'xbbuddy',
          name: customization.xbbuddy?.name || '9xbuddy',
          url: buildUrl(customization.xbbuddy?.url || `https://9xbuddy.org/process?url={videoUrl}`),
          isYTDL: false,
        },
        { key: 'ytdl', name: 'by YTDL', url: `http://localhost:5005`, isYTDL: true },
      ];

      const enabledSites =
        this.settings && this.settings.downloadSites
          ? this.settings.downloadSites
          : { y2mate: true, xbbuddy: true };

      // YTDL всегда включён, фильтруем остальные по настройкам
      const downloadSites = baseSites.filter(s => {
        if (s.key === 'ytdl') return true; // ytdl всегда включён
        return enabledSites[s.key] !== false;
      });

      // Если активен только один сайт — прямой переход без dropdown
      if (downloadSites.length === 1) {
        const singleSite = downloadSites[0];
        button.style.cursor = 'pointer';
        button.addEventListener('click', () => openDownloadSite(singleSite.url, singleSite.isYTDL));
        controls.insertBefore(button, controls.firstChild);
        return; // Не создаём dropdown
      }

      // Centered list
      const list = document.createElement('div');
      list.className = 'download-options-list';

      downloadSites.forEach(site => {
        const opt = document.createElement('div');
        opt.className = 'download-option-item';
        opt.textContent = site.name;
        opt.setAttribute('role', 'menuitem');
        opt.setAttribute('tabindex', '0');
        opt.addEventListener('click', () => openDownloadSite(site.url, site.isYTDL));
        opt.addEventListener('keydown', e => {
          if (e.key === 'Enter' || e.key === ' ') {
            openDownloadSite(site.url, site.isYTDL);
          }
        });
        list.appendChild(opt);
      });

      options.appendChild(list);

      // Ensure only one dropdown exists to avoid duplicates (helpful on refresh)
      const existingDownload = document.querySelector('.download-options');
      if (existingDownload) existingDownload.remove();

      // Append dropdown to body to avoid Firefox issues with positioned children
      try {
        document.body.appendChild(options);
      } catch {
        // fallback: append to controls
        button.appendChild(options);
      }

      // Expose rebuild function globally (safe guard) so settings handlers can call it
      try {
        if (typeof window !== 'undefined') {
          /** @type {any} */ (window).youtubePlus = /** @type {any} */ (window).youtubePlus || {};
          /** @type {any} */ (window).youtubePlus.rebuildDownloadDropdown = rebuildDropdown;
          // also store settings ref for rebuildDropdown to read
          /** @type {any} */ (window).youtubePlus.settings =
            /** @type {any} */ (window).youtubePlus.settings || this.settings;
        }
      } catch (e) {
        console.warn('[YouTube+] expose rebuildDownloadDropdown failed:', e);
      }

      let dropdownTimeout;
      function showDropdown() {
        clearTimeout(dropdownTimeout);
        positionDropdown();
        options.classList.add('visible');
        button.setAttribute('aria-expanded', 'true');
      }
      function hideDropdown() {
        dropdownTimeout = setTimeout(() => {
          options.classList.remove('visible');
          button.setAttribute('aria-expanded', 'false');
        }, 150);
      }
      // Hover behaviour for download dropdown (show on hover, hide on leave)
      let downloadHideTimer;
      button.addEventListener('mouseenter', () => {
        clearTimeout(downloadHideTimer);
        showDropdown();
      });
      button.addEventListener('mouseleave', () => {
        clearTimeout(downloadHideTimer);
        downloadHideTimer = setTimeout(hideDropdown, 180);
      });
      options.addEventListener('mouseenter', () => {
        clearTimeout(downloadHideTimer);
        showDropdown();
      });
      options.addEventListener('mouseleave', () => {
        clearTimeout(downloadHideTimer);
        downloadHideTimer = setTimeout(hideDropdown, 180);
      });
      button.addEventListener('keydown', e => {
        if (e.key === 'Enter' || e.key === ' ') {
          if (options.classList.contains('visible')) {
            hideDropdown();
          } else {
            showDropdown();
          }
        }
      });

      controls.insertBefore(button, controls.firstChild);
    },

    addSpeedControlButton(controls) {
      // Check if speed control is enabled in settings
      if (!this.settings.enableSpeedControl) return;

      const speedBtn = document.createElement('button');
      speedBtn.type = 'button';
      speedBtn.className = 'ytp-button speed-control-btn';
      speedBtn.setAttribute('aria-label', t('speedControl'));
      speedBtn.setAttribute('aria-haspopup', 'true');
      speedBtn.setAttribute('aria-expanded', 'false');
      speedBtn.innerHTML = `<span>${this.speedControl.currentSpeed}×</span>`;

      const speedOptions = document.createElement('div');
      speedOptions.className = 'speed-options';
      speedOptions.setAttribute('role', 'menu');

      const selectSpeed = speed => {
        this.changeSpeed(speed);
        hideDropdown();
      };

      [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0].forEach(speed => {
        const option = document.createElement('div');
        option.className = `speed-option-item${Number(speed) === this.speedControl.currentSpeed ? ' speed-option-active' : ''}`;
        option.textContent = `${speed}x`;
        option.dataset.speed = String(speed);
        option.setAttribute('role', 'menuitem');
        option.tabIndex = 0;
        option.addEventListener('click', () => selectSpeed(speed));
        option.addEventListener('keydown', event => {
          if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault();
            selectSpeed(speed);
          }
        });
        speedOptions.appendChild(option);
      });

      speedBtn.appendChild(speedOptions);

      // Ensure only one speed dropdown exists
      const existingSpeed = document.querySelector('.speed-options');
      if (existingSpeed) existingSpeed.remove();

      // Append speedOptions to body to avoid Firefox positioning/hover issues
      try {
        document.body.appendChild(speedOptions);
      } catch {
        // fallback keep as child
      }

      const positionDropdown = () => {
        const rect = speedBtn.getBoundingClientRect();
        speedOptions.style.left = `${rect.left + rect.width / 2}px`;
        speedOptions.style.bottom = `${window.innerHeight - rect.top + 8}px`;
      };

      const hideDropdown = () => {
        speedOptions.classList.remove('visible');
        speedBtn.setAttribute('aria-expanded', 'false');
      };

      const showDropdown = () => {
        positionDropdown();
        speedOptions.classList.add('visible');
        speedBtn.setAttribute('aria-expanded', 'true');
      };

      const toggleDropdown = () => {
        if (speedOptions.classList.contains('visible')) {
          hideDropdown();
        } else {
          showDropdown();
        }
      };

      let documentClickKey;

      const documentClickHandler = event => {
        if (!speedBtn.isConnected) {
          if (documentClickKey) {
            YouTubeUtils.cleanupManager.unregisterListener(documentClickKey);
            documentClickKey = undefined;
          }
          return;
        }
        if (!speedOptions.classList.contains('visible')) return;
        if (
          speedBtn.contains(/** @type {Node} */ (event.target)) ||
          speedOptions.contains(/** @type {Node} */ (event.target))
        ) {
          return;
        }
        hideDropdown();
      };

      const documentKeydownHandler = event => {
        if (event.key === 'Escape' && speedOptions.classList.contains('visible')) {
          hideDropdown();
          speedBtn.focus();
        }
      };

      documentClickKey = YouTubeUtils.cleanupManager.registerListener(
        document,
        'click',
        documentClickHandler,
        true
      );
      YouTubeUtils.cleanupManager.registerListener(
        document,
        'keydown',
        documentKeydownHandler,
        true
      );

      YouTubeUtils.cleanupManager.registerListener(window, 'resize', () => {
        if (speedOptions.classList.contains('visible')) {
          positionDropdown();
        }
      });

      YouTubeUtils.cleanupManager.registerListener(
        window,
        'scroll',
        () => {
          if (speedOptions.classList.contains('visible')) {
            positionDropdown();
          }
        },
        true
      );

      // Hover behaviour: show on mouseenter, hide on mouseleave (with small delay)
      let speedHideTimer;
      speedBtn.addEventListener('mouseenter', () => {
        clearTimeout(speedHideTimer);
        showDropdown();
      });
      speedBtn.addEventListener('mouseleave', () => {
        clearTimeout(speedHideTimer);
        speedHideTimer = setTimeout(hideDropdown, 200);
      });
      speedOptions.addEventListener('mouseenter', () => {
        clearTimeout(speedHideTimer);
        showDropdown();
      });
      speedOptions.addEventListener('mouseleave', () => {
        clearTimeout(speedHideTimer);
        speedHideTimer = setTimeout(hideDropdown, 200);
      });

      // Keep keyboard support (Enter toggles dropdown)
      speedBtn.addEventListener('keydown', event => {
        if (event.key === 'Enter' || event.key === ' ') {
          event.preventDefault();
          toggleDropdown();
        } else if (event.key === 'Escape') {
          hideDropdown();
        }
      });

      controls.insertBefore(speedBtn, controls.firstChild);
    },

    // ------------------ Side Guide Toggle ------------------
    applyGuideVisibility() {
      try {
        const enabled = Boolean(YouTubeUtils.storage.get('ytplus.hideGuide', false));
        document.documentElement.classList.toggle('ytp-hide-guide', enabled);
        // update floating button appearance if present
        const btn = document.getElementById('ytplus-guide-toggle-btn');
        if (btn) {
          btn.setAttribute('aria-pressed', String(enabled));
          btn.title = enabled ? 'Show side guide' : 'Hide side guide';
        }
      } catch (e) {
        console.warn('[YouTube+] applyGuideVisibility failed:', e);
      }
    },

    toggleSideGuide() {
      try {
        const current = Boolean(YouTubeUtils.storage.get('ytplus.hideGuide', false));
        const next = !current;
        YouTubeUtils.storage.set('ytplus.hideGuide', next);
        this.applyGuideVisibility();
      } catch (e) {
        console.warn('[YouTube+] toggleSideGuide failed:', e);
      }
    },

    createGuideToggleButton() {
      try {
        if (document.getElementById('ytplus-guide-toggle-btn')) return;
        const btn = document.createElement('button');
        btn.id = 'ytplus-guide-toggle-btn';
        btn.type = 'button';
        btn.style.cssText =
          'position:fixed;right:12px;bottom:12px;z-index:100000;background:var(--yt-spec-call-to-action);color:#fff;border:none;border-radius:8px;padding:8px 10px;box-shadow:0 6px 18px rgba(0,0,0,0.3);cursor:pointer;opacity:0.95;font-size:13px;';
        btn.setAttribute('aria-pressed', 'false');
        btn.title = 'Hide side guide';
        btn.textContent = 'Toggle Guide';
        btn.addEventListener('click', e => {
          e.preventDefault();
          e.stopPropagation();
          this.toggleSideGuide();
        });

        // keyboard support
        btn.addEventListener('keydown', e => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            this.toggleSideGuide();
          }
        });

        document.body.appendChild(btn);
        // Apply current stored value
        this.applyGuideVisibility();
      } catch (e) {
        console.warn('[YouTube+] createGuideToggleButton failed:', e);
      }
    },

    captureFrame() {
      const video = this.getElement('video', false);
      if (!video) return;

      const canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

      const videoTitle = document.title.replace(/\s-\sYouTube$/, '').trim();
      const link = document.createElement('a');
      link.href = canvas.toDataURL('image/png');
      link.download = `${videoTitle}.png`;
      link.click();
    },

    showNotification(message, duration = 2000) {
      YouTubeUtils.NotificationManager.show(message, { duration, type: 'info' });
    },

    handleFullscreenChange() {
      const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement;
      document.querySelectorAll('.ytp-screenshot-button, .ytp-cobalt-button').forEach(button => {
        button.style.bottom = isFullscreen ? '0px' : '0px';
      });
    },

    changeSpeed(speed) {
      speed = Number(speed);
      this.speedControl.currentSpeed = speed;
      localStorage.setItem(this.speedControl.storageKey, String(speed));

      const speedBtn = this.getElement('.speed-control-btn span', false);
      if (speedBtn) speedBtn.textContent = `${speed}×`;

      document.querySelectorAll('.speed-option-item').forEach(option => {
        option.classList.toggle('speed-option-active', parseFloat(option.dataset.speed) === speed);
      });

      this.applyCurrentSpeed();
      this.showSpeedIndicator(speed);
    },

    applyCurrentSpeed() {
      document.querySelectorAll('video').forEach(video => {
        if (video && video.playbackRate !== this.speedControl.currentSpeed) {
          video.playbackRate = this.speedControl.currentSpeed;
        }
      });
    },

    setupVideoObserver() {
      if (this._speedInterval) clearInterval(this._speedInterval);
      this._speedInterval = setInterval(() => this.applyCurrentSpeed(), 1000);

      // ✅ Register interval in cleanupManager
      YouTubeUtils.cleanupManager.registerInterval(this._speedInterval);
    },

    setupNavigationObserver() {
      let lastUrl = location.href;

      document.addEventListener('fullscreenchange', this.handleFullscreenChange.bind(this));

      document.addEventListener('yt-navigate-finish', () => {
        if (location.href.includes('watch?v=')) this.setupCurrentPage();
        this.addSettingsButtonToHeader();
      });

      // ✅ Register observer in cleanupManager
      const observer = new MutationObserver(() => {
        if (lastUrl !== location.href) {
          lastUrl = location.href;
          if (location.href.includes('watch?v=')) {
            setTimeout(() => this.setupCurrentPage(), 500);
          }
          this.addSettingsButtonToHeader();
        }
      });

      YouTubeUtils.cleanupManager.registerObserver(observer);

      // ✅ Safe observe with document.body check
      if (document.body) {
        observer.observe(document.body, { childList: true, subtree: true });
      } else {
        document.addEventListener('DOMContentLoaded', () => {
          observer.observe(document.body, { childList: true, subtree: true });
        });
      }
    },

    showSpeedIndicator(speed) {
      const indicator = document.getElementById('speed-indicator');
      if (!indicator) return;

      if (this.speedControl.activeAnimationId) {
        cancelAnimationFrame(this.speedControl.activeAnimationId);
        YouTubeUtils.cleanupManager.unregisterAnimationFrame(this.speedControl.activeAnimationId);
        this.speedControl.activeAnimationId = null;
      }

      indicator.textContent = `${speed}×`;
      indicator.style.display = 'block';
      indicator.style.opacity = '0.8';

      const startTime = performance.now();
      const fadeOut = timestamp => {
        const elapsed = timestamp - startTime;
        const progress = Math.min(elapsed / 1500, 1);

        indicator.style.opacity = String(0.8 * (1 - progress));

        if (progress < 1) {
          this.speedControl.activeAnimationId = YouTubeUtils.cleanupManager.registerAnimationFrame(
            requestAnimationFrame(fadeOut)
          );
        } else {
          indicator.style.display = 'none';
          this.speedControl.activeAnimationId = null;
        }
      };

      this.speedControl.activeAnimationId = YouTubeUtils.cleanupManager.registerAnimationFrame(
        requestAnimationFrame(fadeOut)
      );
    },
  };

  // Save reference to init function BEFORE IIFE closes (critical for DOMContentLoaded)
  const initFunction = YouTubeEnhancer.init.bind(YouTubeEnhancer);

  // Initialize immediately or on DOMContentLoaded
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initFunction);
  } else {
    initFunction();
  }
})();

// --- MODULE: enhanced.js ---

// Enhanced Tabviews
(function () {
  'use strict';

  // Localization
  const i18n = {
    en: {
      scrollToTop: 'Scroll to top',
    },
    ru: {
      scrollToTop: 'Прокрутить вверх',
    },
  };

  const getLanguage = () => {
    const htmlLang = document.documentElement.lang || 'en';
    if (htmlLang.startsWith('ru')) return 'ru';
    return 'en';
  };

  const lang = getLanguage();
  const t = key => i18n[lang][key] || i18n.en[key] || key;

  /**
   * Configuration object for scroll-to-top button
   * @type {Object}
   * @property {boolean} enabled - Whether the feature is enabled
   * @property {string} storageKey - LocalStorage key for settings
   */
  const config = {
    enabled: true,
    storageKey: 'youtube_top_button_settings',
  };

  /**
   * Adds CSS styles for scroll-to-top button and scrollbars
   * @returns {void}
   */
  const addStyles = () => {
    if (document.getElementById('custom-styles')) return;

    const style = document.createElement('style');
    style.id = 'custom-styles';
    style.textContent = `
      :root{--scrollbar-width:8px;--scrollbar-track:transparent;--scrollbar-thumb:rgba(144,144,144,.5);--scrollbar-thumb-hover:rgba(170,170,170,.7);--scrollbar-thumb-active:rgba(190,190,190,.9);}
      ::-webkit-scrollbar{width:var(--scrollbar-width)!important;height:var(--scrollbar-width)!important;}
      ::-webkit-scrollbar-track{background:var(--scrollbar-track)!important;border-radius:4px!important;}
      ::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb)!important;border-radius:4px!important;transition:background .2s!important;}
      ::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)!important;}
      ::-webkit-scrollbar-thumb:active{background:var(--scrollbar-thumb-active)!important;}
      ::-webkit-scrollbar-corner{background:transparent!important;}
      *{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) var(--scrollbar-track);}
      html[dark]{--scrollbar-thumb:rgba(144,144,144,.4);--scrollbar-thumb-hover:rgba(170,170,170,.6);--scrollbar-thumb-active:rgba(190,190,190,.8);}
      .top-button{position:absolute;bottom:16px;right:16px;width:40px;height:40px;background:var(--yt-top-btn-bg,rgba(0,0,0,.7));color:var(--yt-top-btn-color,#fff);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:1000;opacity:0;visibility:hidden;transition:all .3s;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);border:1px solid var(--yt-top-btn-border,rgba(255,255,255,.1));background:rgba(255,255,255,.12);box-shadow:0 8px 32px 0 rgba(31,38,135,.18);}
      .top-button:hover{background:var(--yt-top-btn-hover,rgba(0,0,0,.15));transform:translateY(-2px) scale(1.07);box-shadow:0 8px 32px rgba(0,0,0,.25);}
      .top-button.visible{opacity:1;visibility:visible;}
      .top-button svg{transition:transform .2s;}
      .top-button:hover svg{transform:translateY(-1px) scale(1.1);}
      html[dark]{--yt-top-btn-bg:rgba(255,255,255,.10);--yt-top-btn-color:#fff;--yt-top-btn-border:rgba(255,255,255,.18);--yt-top-btn-hover:rgba(255,255,255,.18);}
      html:not([dark]){--yt-top-btn-bg:rgba(255,255,255,.12);--yt-top-btn-color:#222;--yt-top-btn-border:rgba(0,0,0,.08);--yt-top-btn-hover:rgba(255,255,255,.18);}
      ytd-watch-flexy:not([tyt-tab^="#"]) .top-button{display:none;}
      ytd-watch-flexy[flexy] #movie_player,
      ytd-watch-flexy[flexy] #movie_player .html5-video-container,
      ytd-watch-flexy[flexy] .html5-main-video{
        width:100%!important;
        max-width:100%!important;
      }
      ytd-watch-flexy[flexy] .html5-main-video{
        height:auto!important;
        max-height:100%!important;
        object-fit:contain!important;
        transform:none!important;
      }
      ytd-watch-flexy[flexy] #player-container-outer,
      ytd-watch-flexy[flexy] #movie_player{
        display:flex!important;
        align-items:center!important;
        justify-content:center!important;
      }
        `;
    (document.head || document.documentElement).appendChild(style);
  };

  /**
   * Updates button visibility based on scroll position
   * @param {HTMLElement} scrollContainer - The container being scrolled
   * @returns {void}
   */
  const handleScroll = scrollContainer => {
    try {
      const button = document.getElementById('right-tabs-top-button');
      if (!button || !scrollContainer) return;
      button.classList.toggle('visible', scrollContainer.scrollTop > 100);
    } catch (error) {
      console.error('[YouTube+][Enhanced] Error in handleScroll:', error);
    }
  };

  /**
   * Sets up scroll event listener on active tab with debouncing for performance
   * @returns {void}
   */
  const setupScrollListener = () => {
    try {
      document.querySelectorAll('.tab-content-cld').forEach(tab => {
        tab.removeEventListener('scroll', tab._topButtonScrollHandler);
      });

      const activeTab = document.querySelector(
        '#right-tabs .tab-content-cld:not(.tab-content-hidden)'
      );
      if (activeTab) {
        // Use debounce for better performance (check if YouTubeUtils.debounce is available)
        const debounceFunc =
          typeof YouTubeUtils !== 'undefined' && YouTubeUtils.debounce
            ? YouTubeUtils.debounce
            : (fn, delay) => {
                let timeoutId;
                return (...args) => {
                  clearTimeout(timeoutId);
                  timeoutId = setTimeout(() => fn(...args), delay);
                };
              };
        const scrollHandler = debounceFunc(() => handleScroll(activeTab), 100);
        activeTab._topButtonScrollHandler = scrollHandler;
        activeTab.addEventListener('scroll', scrollHandler, { passive: true });
        handleScroll(activeTab);
      }
    } catch (error) {
      console.error('[YouTube+][Enhanced] Error in setupScrollListener:', error);
    }
  };

  /**
   * Creates and appends scroll-to-top button with error handling
   * @returns {void}
   */
  const createButton = () => {
    try {
      const rightTabs = document.querySelector('#right-tabs');
      if (!rightTabs || document.getElementById('right-tabs-top-button')) return;
      if (!config.enabled) return;

      const button = document.createElement('button');
      button.id = 'right-tabs-top-button';
      button.className = 'top-button';
      button.title = t('scrollToTop');
      button.setAttribute('aria-label', t('scrollToTop'));
      button.innerHTML =
        '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>';

      button.addEventListener('click', () => {
        try {
          const activeTab = document.querySelector(
            '#right-tabs .tab-content-cld:not(.tab-content-hidden)'
          );
          if (activeTab) activeTab.scrollTo({ top: 0, behavior: 'smooth' });
        } catch (error) {
          console.error('[YouTube+][Enhanced] Error scrolling to top:', error);
        }
      });

      rightTabs.style.position = 'relative';
      rightTabs.appendChild(button);
      setupScrollListener();
    } catch (error) {
      console.error('[YouTube+][Enhanced] Error creating button:', error);
    }
  };

  /**
   * Observes DOM changes to detect tab switches
   * @returns {MutationObserver|null} The created observer or null on error
   */
  const observeTabChanges = () => {
    try {
      const observer = new MutationObserver(mutations => {
        try {
          if (
            mutations.some(
              m =>
                m.type === 'attributes' &&
                m.attributeName === 'class' &&
                m.target instanceof Element &&
                m.target.classList.contains('tab-content-cld')
            )
          ) {
            setTimeout(setupScrollListener, 100);
          }
        } catch (error) {
          console.error('[YouTube+][Enhanced] Error in mutation observer:', error);
        }
      });

      const rightTabs = document.querySelector('#right-tabs');
      if (rightTabs) {
        observer.observe(rightTabs, {
          attributes: true,
          subtree: true,
          attributeFilter: ['class'],
        });
        return observer;
      }
      return null;
    } catch (error) {
      console.error('[YouTube+][Enhanced] Error in observeTabChanges:', error);
      return null;
    }
  };

  /**
   * Sets up event listeners for tab button clicks
   * @returns {void}
   */
  const setupEvents = () => {
    try {
      document.addEventListener(
        'click',
        e => {
          try {
            const target = /** @type {EventTarget & HTMLElement} */ (e.target);
            if (target.closest && target.closest('.tab-btn[tyt-tab-content]')) {
              setTimeout(setupScrollListener, 100);
            }
          } catch (error) {
            console.error('[YouTube+][Enhanced] Error in click handler:', error);
          }
        },
        true
      );
    } catch (error) {
      console.error('[YouTube+][Enhanced] Error in setupEvents:', error);
    }
  };

  /**
   * Initialize scroll-to-top button module
   * @returns {void}
   */
  const init = () => {
    try {
      addStyles();
      setupEvents();

      const checkForTabs = () => {
        try {
          if (document.querySelector('#right-tabs')) {
            createButton();
            observeTabChanges();
          } else {
            setTimeout(checkForTabs, 500);
          }
        } catch (error) {
          console.error('[YouTube+][Enhanced] Error checking for tabs:', error);
        }
      };

      checkForTabs();
    } catch (error) {
      console.error('[YouTube+][Enhanced] Error in initialization:', error);
    }
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();

// YouTube End Screen Remover
(function () {
  'use strict';

  // Optimized configuration
  const CONFIG = {
    enabled: true,
    storageKey: 'youtube_endscreen_settings',
    // Added .teaser-carousel to cover variants named 'teaser-carousel'
    selectors:
      '.ytp-ce-element-show,.ytp-ce-element,.ytp-endscreen-element,.ytp-ce-covering-overlay,.ytp-cards-teaser,.teaser-carousel,.ytp-cards-button,.iv-drawer,.video-annotations',
    debounceMs: 32,
    batchSize: 20,
  };

  // Minimal state with better tracking
  const state = {
    observer: null,
    styleEl: null,
    isActive: false,
    removeCount: 0,
    lastCheck: 0,
    ytNavigateListenerKey: null,
    settingsNavListenerKey: null,
  };

  // High-performance utilities: use shared debounce when available
  const debounce = (fn, ms) => {
    try {
      return (
        (window.YouTubeUtils && window.YouTubeUtils.debounce) ||
        ((f, t) => {
          let id;
          return (...args) => {
            clearTimeout(id);
            id = setTimeout(() => f(...args), t);
          };
        })(fn, ms)
      );
    } catch {
      let id;
      return (...args) => {
        clearTimeout(id);
        id = setTimeout(() => fn(...args), ms);
      };
    }
  };

  const fastRemove = elements => {
    const len = Math.min(elements.length, CONFIG.batchSize);
    for (let i = 0; i < len; i++) {
      const el = elements[i];
      if (el?.isConnected) {
        el.style.cssText = 'display:none!important;visibility:hidden!important';
        try {
          el.remove();
          state.removeCount++;
        } catch {}
      }
    }
  };

  // Settings with caching
  const settings = {
    load: () => {
      try {
        const data = localStorage.getItem(CONFIG.storageKey);
        CONFIG.enabled = data ? (JSON.parse(data).enabled ?? true) : true;
      } catch {
        CONFIG.enabled = true;
      }
    },

    save: () => {
      try {
        localStorage.setItem(CONFIG.storageKey, JSON.stringify({ enabled: CONFIG.enabled }));
      } catch {}
      settings.apply();
    },

    apply: () => (CONFIG.enabled ? init() : cleanup()),
  };

  // Optimized core functions
  const injectCSS = () => {
    if (state.styleEl || !CONFIG.enabled) return;

    // ✅ Use StyleManager instead of createElement('style')
    const styles = `${CONFIG.selectors}{display:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important;transform:scale(0)!important}`;
    YouTubeUtils.StyleManager.add('end-screen-remover', styles);
    state.styleEl = true; // Mark as added
  };

  const removeEndScreens = () => {
    if (!CONFIG.enabled) return;
    const now = performance.now();
    if (now - state.lastCheck < CONFIG.debounceMs) return;
    state.lastCheck = now;

    const elements = document.querySelectorAll(CONFIG.selectors);
    if (elements.length) fastRemove(elements);
  };

  const setupWatcher = () => {
    if (state.observer || !CONFIG.enabled) return;

    const throttledRemove = debounce(removeEndScreens, CONFIG.debounceMs);

    state.observer = new MutationObserver(mutations => {
      let hasRelevantChanges = false;
      for (const { addedNodes } of mutations) {
        for (const node of addedNodes) {
          if (node instanceof Element) {
            // Handle both HTML and SVG elements - className can be string or SVGAnimatedString
            const classNameValue =
              typeof node.className === 'string'
                ? node.className
                : node.className &&
                    typeof node.className === 'object' &&
                    'baseVal' in node.className
                  ? /** @type {any} */ (node.className).baseVal
                  : '';
            if (classNameValue.includes('ytp-') || node.querySelector?.('.ytp-ce-element')) {
              hasRelevantChanges = true;
              break;
            }
          }
        }
        if (hasRelevantChanges) break;
      }
      if (hasRelevantChanges) throttledRemove();
    });

    // ✅ Register observer in cleanupManager
    YouTubeUtils.cleanupManager.registerObserver(state.observer);

    const target = document.querySelector('#movie_player') || document.body;
    state.observer.observe(target, {
      childList: true,
      subtree: true,
      attributeFilter: ['class', 'style'],
    });
  };

  const cleanup = () => {
    state.observer?.disconnect();
    state.observer = null;
    state.styleEl?.remove();
    state.styleEl = null;
    state.isActive = false;
  };

  const init = () => {
    if (state.isActive || !CONFIG.enabled) return;
    state.isActive = true;
    injectCSS();
    removeEndScreens();
    setupWatcher();
  };

  // Streamlined settings UI
  const addSettingsUI = () => {
    const section = document.querySelector('.ytp-plus-settings-section[data-section="advanced"]');
    if (!section || section.querySelector('.endscreen-settings')) return;

    const container = document.createElement('div');
    container.className = 'ytp-plus-settings-item endscreen-settings';
    container.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${YouTubeUtils.t('endscreenHideLabel')}</label>
          <div class="ytp-plus-settings-item-description">${YouTubeUtils.t('endscreenHideDesc')}${state.removeCount ? ` (${state.removeCount} ${YouTubeUtils.t('removedSuffix').replace('{n}', '')?.trim() || 'removed'})` : ''}</div>
        </div>
        <input type="checkbox" class="ytp-plus-settings-checkbox" ${CONFIG.enabled ? 'checked' : ''}>
      `;

    section.appendChild(container);

    container.querySelector('input').addEventListener(
      'change',
      e => {
        const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
        CONFIG.enabled = target.checked;
        settings.save();
      },
      { passive: true }
    );
  };

  // Optimized navigation handler
  const handlePageChange = debounce(() => {
    if (location.pathname === '/watch') {
      cleanup();
      requestIdleCallback ? requestIdleCallback(init) : setTimeout(init, 1);
    }
  }, 50);

  // Initialize
  settings.load();

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init, { once: true });
  } else {
    init();
  }

  const handleSettingsNavClick = e => {
    const target = /** @type {EventTarget & HTMLElement} */ (e.target);
    if (target.dataset?.section === 'advanced') {
      setTimeout(addSettingsUI, 10);
    }
  };

  if (!state.ytNavigateListenerKey) {
    state.ytNavigateListenerKey = YouTubeUtils.cleanupManager.registerListener(
      document,
      'yt-navigate-finish',
      /** @type {EventListener} */ (handlePageChange),
      { passive: true }
    );
  }

  // Settings modal integration
  const settingsObserver = new MutationObserver(mutations => {
    for (const { addedNodes } of mutations) {
      for (const node of addedNodes) {
        if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
          setTimeout(addSettingsUI, 25);
          return;
        }
      }
    }
  });

  // ✅ Register observer in cleanupManager
  YouTubeUtils.cleanupManager.registerObserver(settingsObserver);

  // ✅ Safe observe with document.body check
  if (document.body) {
    settingsObserver.observe(document.body, { childList: true });
  } else {
    document.addEventListener('DOMContentLoaded', () => {
      settingsObserver.observe(document.body, { childList: true });
    });
  }

  if (!state.settingsNavListenerKey) {
    state.settingsNavListenerKey = YouTubeUtils.cleanupManager.registerListener(
      document,
      'click',
      handleSettingsNavClick,
      { passive: true, capture: true }
    );
  }
})();

// Time to Read (Resume Playback)
(function () {
  'use strict';

  const RESUME_STORAGE_KEY = 'youtube_resume_times_v1';
  const OVERLAY_ID = 'yt-resume-overlay';
  const AUTO_HIDE_MS = 20000; // hide overlay after 20s

  // Localization
  const i18n = {
    en: {
      resumePlayback: 'Resume playback?',
      resume: 'Resume',
      startOver: 'Start over',
    },
    ru: {
      resumePlayback: 'Продолжить воспроизведение?',
      resume: 'Продолжить',
      startOver: 'Начать сначала',
    },
  };

  // Detect language
  const getLanguage = () => {
    const htmlLang = document.documentElement.lang || 'en';
    if (htmlLang.startsWith('ru')) return 'ru';
    return 'en';
  };

  const lang = getLanguage();
  const t = key => i18n[lang][key] || i18n.en[key] || key;

  const readStorage = () => {
    try {
      return JSON.parse(localStorage.getItem(RESUME_STORAGE_KEY) || '{}');
    } catch {
      return {};
    }
  };

  const writeStorage = obj => {
    try {
      localStorage.setItem(RESUME_STORAGE_KEY, JSON.stringify(obj));
    } catch {}
  };

  // Get current video id from the page (works on standard watch pages)
  const getVideoId = () => {
    try {
      // First try URL parameters (most reliable)
      const urlParams = new URLSearchParams(window.location.search);
      const videoIdFromUrl = urlParams.get('v');
      if (videoIdFromUrl) return videoIdFromUrl;

      // Try canonical link
      const meta = document.querySelector('link[rel="canonical"]');
      if (meta && meta.href) {
        const u = new URL(meta.href);
        const vParam = u.searchParams.get('v');
        if (vParam) return vParam;

        // Try extracting from pathname (for /watch/ or /shorts/ URLs)
        const pathMatch = u.pathname.match(/\/(watch|shorts)\/([^\/\?]+)/);
        if (pathMatch && pathMatch[2]) return pathMatch[2];
      }

      // Fallback to ytInitialPlayerResponse
      if (
        window.ytInitialPlayerResponse &&
        window.ytInitialPlayerResponse.videoDetails &&
        window.ytInitialPlayerResponse.videoDetails.videoId
      ) {
        return window.ytInitialPlayerResponse.videoDetails.videoId;
      }

      // Last resort: try to extract from current URL pathname
      const pathMatch = window.location.pathname.match(/\/(watch|shorts)\/([^\/\?]+)/);
      if (pathMatch && pathMatch[2]) return pathMatch[2];

      return null;
    } catch {
      return null;
    }
  };

  const createOverlay = (seconds, onResume, onRestart) => {
    if (document.getElementById(OVERLAY_ID)) return null;
    const wrap = document.createElement('div');
    wrap.id = OVERLAY_ID;

    // Try to insert overlay inside the player so it appears above the progress bar
    const player = document.querySelector('#movie_player');
    const inPlayer = !!player;

    // Ensure glassmorphism styles are available for the overlay
    const resumeOverlayStyles = `
      .ytp-resume-overlay{min-width:180px;max-width:36vw;background:rgba(24, 24, 24, 0.3);color:var(--yt-spec-text-primary,#fff);padding:12px 14px;border-radius:12px;backdrop-filter:blur(8px) saturate(150%);-webkit-backdrop-filter:blur(8px) saturate(150%);box-shadow:0 14px 40px rgba(0,0,0,0.48);border:1.25px solid rgba(255,255,255,0.06);font-family:Arial,Helvetica,sans-serif;display:flex;flex-direction:column;align-items:center;text-align:center}
      .ytp-resume-overlay .ytp-resume-title{font-weight:600;margin-bottom:8px}
      .ytp-resume-overlay .ytp-resume-actions{display:flex;gap:8px;justify-content:center;margin-top:6px}
      .ytp-resume-overlay .ytp-resume-btn{padding:6px 12px;border-radius:8px;border:none;cursor:pointer}
      .ytp-resume-overlay .ytp-resume-btn.primary{background:#1e88e5;color:#fff}
      .ytp-resume-overlay .ytp-resume-btn.ghost{background:rgba(255,255,255,0.06);color:#fff}
    `;
    try {
      if (window.YouTubeUtils && YouTubeUtils.StyleManager) {
        YouTubeUtils.StyleManager.add('ytp-resume-overlay-styles', resumeOverlayStyles);
      } else if (!document.getElementById('ytp-resume-overlay-styles')) {
        const s = document.createElement('style');
        s.id = 'ytp-resume-overlay-styles';
        s.textContent = resumeOverlayStyles;
        (document.head || document.documentElement).appendChild(s);
      }
    } catch {}

    if (inPlayer) {
      try {
        // Ensure player can be a positioning context
        const playerStyle = window.getComputedStyle(
          /** @type {Element} */ (/** @type {unknown} */ (player))
        );
        if (playerStyle.position === 'static') player.style.position = 'relative';
      } catch {}

      // Position centered inside the player
      wrap.className = 'ytp-resume-overlay';
      // absolute center (use transform to center by both axes)
      wrap.style.cssText =
        'position:absolute;left:50%;bottom:5%;transform:translate(-50%,-50%);z-index:9999;pointer-events:auto;';
      player.appendChild(wrap);
    } else {
      // Fallback: fixed centered on the page
      wrap.className = 'ytp-resume-overlay';
      wrap.style.cssText =
        'position:fixed;left:50%;bottom:5%;transform:translate(-50%,-50%);z-index:1200;pointer-events:auto;';
      document.body.appendChild(wrap);
    }

    const title = document.createElement('div');
    title.className = 'ytp-resume-title';
    title.textContent = `${t('resumePlayback')} (${formatTime(seconds)})`;

    const btnResume = document.createElement('button');
    btnResume.className = 'ytp-resume-btn primary';
    btnResume.textContent = t('resume');
    const btnRestart = document.createElement('button');
    btnRestart.className = 'ytp-resume-btn ghost';
    btnRestart.textContent = t('startOver');

    btnResume.addEventListener('click', () => {
      try {
        onResume();
      } catch {}
      try {
        wrap.remove();
      } catch {}
    });
    btnRestart.addEventListener('click', () => {
      try {
        onRestart();
      } catch {}
      try {
        wrap.remove();
      } catch {}
    });

    // group actions and center them
    const actions = document.createElement('div');
    actions.className = 'ytp-resume-actions';
    actions.appendChild(btnResume);
    actions.appendChild(btnRestart);

    wrap.appendChild(title);
    wrap.appendChild(actions);

    const to = setTimeout(() => {
      try {
        wrap.remove();
      } catch {}
    }, AUTO_HIDE_MS);

    // Return function to cancel timeout
    const cancel = () => clearTimeout(to);

    // Register cleanup: cancel timeout and remove overlay when cleanup runs
    if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
      YouTubeUtils.cleanupManager.register(() => {
        try {
          cancel();
        } catch {}
        try {
          wrap.remove();
        } catch {}
      });
    }

    return cancel;
  };

  const formatTime = secs => {
    const s = Math.floor(secs % 60)
      .toString()
      .padStart(2, '0');
    const m = Math.floor((secs / 60) % 60).toString();
    const h = Math.floor(secs / 3600);
    return h ? `${h}:${m.padStart(2, '0')}:${s}` : `${m}:${s}`;
  };

  const attachResumeHandlers = videoEl => {
    if (!videoEl) return;

    // Get current video ID dynamically each time
    const getCurrentVideoId = () => getVideoId();
    const vid = getCurrentVideoId();
    if (!vid) return;

    const storage = readStorage();
    const saved = storage[vid];

    // Save current time using `timeupdate` event (throttled) instead of interval
    let timeUpdateHandler = null;
    let lastSavedAt = 0;
    const SAVE_THROTTLE_MS = 800; // minimum ms between writes

    const startSaving = () => {
      if (timeUpdateHandler) return;
      timeUpdateHandler = () => {
        try {
          // Get current video ID each time we save
          const currentVid = getCurrentVideoId();
          if (!currentVid) return;

          const t = Math.floor(videoEl.currentTime || 0);
          const now = Date.now();
          if (t && (!lastSavedAt || now - lastSavedAt > SAVE_THROTTLE_MS)) {
            const s = readStorage();
            s[currentVid] = t;
            writeStorage(s);
            lastSavedAt = now;
          }
        } catch {}
      };
      videoEl.addEventListener('timeupdate', timeUpdateHandler, { passive: true });

      // register cleanup to remove listener
      if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
        YouTubeUtils.cleanupManager.register(() => {
          try {
            videoEl.removeEventListener('timeupdate', timeUpdateHandler);
          } catch {}
        });
      }
    };

    const stopSaving = () => {
      if (!timeUpdateHandler) return;
      try {
        videoEl.removeEventListener('timeupdate', timeUpdateHandler);
      } catch {}
      timeUpdateHandler = null;
      lastSavedAt = 0;
    };

    // If saved time exists and is > 5s, show overlay
    if (saved && saved > 5 && !document.getElementById(OVERLAY_ID)) {
      const cancelTimeout = createOverlay(
        saved,
        () => {
          try {
            videoEl.currentTime = saved;
            videoEl.play();
          } catch {}
        },
        () => {
          try {
            videoEl.currentTime = 0;
            videoEl.play();
          } catch {}
        }
      );

      // register cleanup for overlay timeout
      if (window.YouTubeUtils && YouTubeUtils.cleanupManager && cancelTimeout) {
        YouTubeUtils.cleanupManager.register(cancelTimeout);
      }
    }

    // Start saving when playing
    const onPlay = () => startSaving();
    const onPause = () => stopSaving();
    videoEl.addEventListener('play', onPlay, { passive: true });
    videoEl.addEventListener('pause', onPause, { passive: true });

    // Cleanup listeners when needed
    if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
      YouTubeUtils.cleanupManager.register(() => {
        try {
          videoEl.removeEventListener('play', onPlay);
          videoEl.removeEventListener('pause', onPause);
        } catch {}
      });
    }
  };

  // Try to find the primary HTML5 video element on the YouTube watch page
  const findVideoElement = () => {
    // Try multiple selectors for better compatibility
    const selectors = [
      'video.html5-main-video',
      'video.video-stream',
      '#movie_player video',
      'video',
    ];

    for (const selector of selectors) {
      const video = document.querySelector(selector);
      if (video && video.tagName === 'VIDEO') {
        return /** @type {HTMLVideoElement} */ (video);
      }
    }

    return null;
  };

  const initResume = () => {
    // Only run on watch pages
    if (window.location.pathname !== '/watch') {
      // Remove overlay if we navigate away from watch page
      const existingOverlay = document.getElementById(OVERLAY_ID);
      if (existingOverlay) {
        existingOverlay.remove();
      }
      return;
    }

    // Remove any existing overlay from previous video
    const existingOverlay = document.getElementById(OVERLAY_ID);
    if (existingOverlay) {
      existingOverlay.remove();
    }

    const videoEl = findVideoElement();
    if (videoEl) {
      attachResumeHandlers(videoEl);
    } else {
      // Retry after a short delay if video not found yet
      setTimeout(initResume, 500);
    }
  };

  // Listen for navigation events used by YouTube SPA
  const onNavigate = () => setTimeout(initResume, 150);

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initResume, { once: true });
  } else {
    initResume();
  }

  // YouTube internal navigation event
  if (window && window.document) {
    // Prefer custom event registered in other modules
    if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
      YouTubeUtils.cleanupManager.registerListener(document, 'yt-navigate-finish', onNavigate, {
        passive: true,
      });
    } else {
      document.addEventListener('yt-navigate-finish', onNavigate, { passive: true });
    }
  }
})();

// Play All
(async function () {
  'use strict';

  /** @type {any} */
  const globalContext =
    typeof unsafeWindow !== 'undefined'
      ? /** @type {any} */ (unsafeWindow)
      : /** @type {any} */ (window);

  const gmApi = globalContext?.GM ?? null;
  const gmInfo = globalContext?.GM_info ?? null;

  const scriptVersion = gmInfo?.script?.version ?? null;
  if (scriptVersion && /-(alpha|beta|dev|test)$/.test(scriptVersion)) {
    console.log(
      '%cytp - YouTube Play All\n',
      'color: #bf4bcc; font-size: 32px; font-weight: bold',
      'You are currently running a test version:',
      scriptVersion
    );
  }

  if (
    Object.prototype.hasOwnProperty.call(window, 'trustedTypes') &&
    !window.trustedTypes.defaultPolicy
  ) {
    window.trustedTypes.createPolicy('default', { createHTML: string => string });
  }

  const insertStylesSafely = html => {
    try {
      const target = document.head || document.documentElement;
      if (target && typeof target.insertAdjacentHTML === 'function') {
        target.insertAdjacentHTML('beforeend', html);
        return;
      }

      // If head isn't available yet, wait for DOMContentLoaded and insert then.
      const onReady = () => {
        try {
          const t = document.head || document.documentElement;
          if (t && typeof t.insertAdjacentHTML === 'function') {
            t.insertAdjacentHTML('beforeend', html);
          }
        } catch {}
      };
      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', onReady, { once: true });
      } else {
        onReady();
      }
    } catch {}
  };

  insertStylesSafely(`<style>
        .ytp-btn {
            border-radius: 8px;
            font-family: 'Roboto', 'Arial', sans-serif;
            font-size: 1.4rem;
            line-height: 2rem;
            font-weight: 500;
            padding: 0.5em;
            margin-left: 0.6em;
            user-select: none;
        }
        
        .ytp-btn, .ytp-btn > * {
            text-decoration: none;
            cursor: pointer;
        }
        
        .ytp-btn-sections {
            padding: 0;
        }
        
        .ytp-btn-sections > .ytp-btn-section {
            padding: 0.5em;
            display: inline-block;
        }
 
        .ytp-btn-sections > .ytp-btn-section:first-child {
            border-top-left-radius: 8px;
            border-bottom-left-radius: 8px;
        }
 
        .ytp-btn-sections > .ytp-btn-section:nth-last-child(1 of .ytp-btn-section) {
            border-top-right-radius: 8px;
            border-bottom-right-radius: 8px;
        }
        
        .ytp-badge {
            border-radius: 8px;
            padding: 0.2em;
            font-size: 0.8em;
            vertical-align: top;
        }
 
        .ytp-play-all-btn {
            background-color: #bf4bcc;
            color: white;
        }
 
        .ytp-play-all-btn:hover {
            background-color: #d264de;
        }
        
        .ytp-random-btn > .ytp-btn-section, .ytp-random-badge, .ytp-random-notice, .ytp-random-popover > * {
            background-color: #2b66da;
            color: white;
        }
 
        .ytp-random-btn > .ytp-btn-section:hover, .ytp-random-popover > *:hover {
            background-color: #6192ee;
        }
        
        .ytp-play-all-btn.ytp-unsupported {
            background-color: #828282;
            color: white;
        }
        
        .ytp-random-popover {
            position: absolute;
            border-radius: 8px;
            font-size: 1.6rem;
            transform: translate(-100%, 0.4em);
        }
        
        .ytp-random-popover > * {
            display: block;
            text-decoration: none;
            padding: 0.4em;
        }
        
        .ytp-random-popover > :first-child {
            border-top-left-radius: 8px;
            border-top-right-radius: 8px;
        }
        
        .ytp-random-popover > :last-child {
            border-bottom-left-radius: 8px;
            border-bottom-right-radius: 8px;
        }
    
        .ytp-random-popover > *:not(:last-child) {
            border-bottom: 1px solid #6e8dbb;
        }
    
        .ytp-button-container {
            display: flex;
            width: 100%;
            margin-top: 1em;
            margin-bottom: -1em;
        }
 
        ytd-rich-grid-renderer .ytp-button-container > :first-child {
            margin-left: 0;
        }
        
        /* fetch() API introduces a race condition. This hides the occasional duplicate buttons */
        .ytp-play-all-btn ~ .ytp-play-all-btn,
        .ytp-random-btn ~ .ytp-random-btn {
            display: none;
        }
        
        /* Fix for mobile view */
        ytm-feed-filter-chip-bar-renderer .ytp-btn {
            margin-right: 12px;
            padding: 0.4em;
        }
        
        body:has(#secondary ytd-playlist-panel-renderer[ytp-random]) .ytp-prev-button.ytp-button,
        body:has(#secondary ytd-playlist-panel-renderer[ytp-random]) .ytp-next-button.ytp-button:not([ytp-random="applied"]) {
            display: none !important;
        }
        
        #secondary ytd-playlist-panel-renderer[ytp-random] ytd-menu-renderer.ytd-playlist-panel-renderer {
            height: 1em;
            visibility: hidden;
        }
        
        #secondary ytd-playlist-panel-renderer[ytp-random]:not(:hover) ytd-playlist-panel-video-renderer {
            filter: blur(2em);
        }
 
        .ytp-random-notice {
            padding: 1em;
            z-index: 1000;
        }
        
        .ytp-playlist-emulator {
            margin-bottom: 1.6rem;
            border-radius: 1rem;
        }
        
        .ytp-playlist-emulator > .title {
            border-top-left-radius: 1rem;
            border-top-right-radius: 1rem;
            font-size: 2rem;
            background-color: #323232;
            color: white;
            padding: 0.8rem;
        }
        
        .ytp-playlist-emulator > .information {
            font-size: 1rem;
            background-color: #2b2a2a;
            color: white;
            padding: 0.8rem;
        }
        
        .ytp-playlist-emulator > .footer {
            border-bottom-left-radius: 1rem;
            border-bottom-right-radius: 1rem;
            background-color: #323232;
            padding: 0.8rem;
        }
        
        .ytp-playlist-emulator > .items {
            max-height: 500px;
            overflow-y: auto;
            overflow-x: hidden;
        }
        
        .ytp-playlist-emulator:not([data-failed]) > .items:empty::before {
            content: 'Loading playlist...';
            background-color: #626262;
            padding: 0.8rem;
            color: white;
            font-size: 2rem;
            display: block;
        }
        
        .ytp-playlist-emulator[data-failed="rejected"] > .items:empty::before {
            content: "Make sure to allow the external API call to ytplaylist.robert.wesner.io to keep viewing playlists that YouTube doesn't natively support!";
            background-color: #491818;
            padding: 0.8rem;
            color: #ff7c7c;
            font-size: 1rem;
            display: block;
        }
        
        .ytp-playlist-emulator > .items > .item {
            background-color: #2c2c2c;
            padding: 0.8rem;
            border: 1px solid #1b1b1b;
            font-size: 1.6rem;
            color: white;
            min-height: 5rem;
            cursor: pointer;
        }
        
        .ytp-playlist-emulator > .items > .item:hover {
            background-color: #505050;
        }
        
        .ytp-playlist-emulator > .items > .item:not(:last-of-type) {
            border-bottom: 0;
        }
        
        .ytp-playlist-emulator > .items > .item[data-current] {
            background-color: #767676;
        }
        
        body:has(.ytp-playlist-emulator) .ytp-prev-button.ytp-button,
        body:has(.ytp-playlist-emulator) .ytp-next-button.ytp-button:not([ytp-emulation="applied"]) {
            display: none !important;
        }
        
        /* hide when sorting by oldest */
        ytm-feed-filter-chip-bar-renderer > div :nth-child(3).selected ~ .ytp-btn:not(.ytp-unsupported), ytd-feed-filter-chip-bar-renderer iron-selector#chips :nth-child(3).iron-selected ~ .ytp-btn:not(.ytp-unsupported) {
            display: none;
        }
    </style>`);

  const getVideoId = url => {
    try {
      return new URLSearchParams(new URL(url).search).get('v');
    } catch {
      return null;
    }
  };

  const queryHTMLElement = selector => {
    const el = document.querySelector(selector);
    return el instanceof HTMLElement ? el : null;
  };

  /**
   * @typedef {HTMLDivElement & {
   *   getProgressState: () => { current: number, duration: number, number: number },
   *   pauseVideo: () => void,
   *   seekTo: (seconds: number, allowSeekAhead?: boolean) => void,
   *   isLifaAdPlaying: () => boolean
   * }} PlayerElement
   */

  /**
   * @return {{ getProgressState: () => { current: number, duration, number }, pauseVideo: () => void, seekTo: (number) => void, isLifaAdPlaying: () => boolean }} player
   */
  const getPlayer = () =>
    /** @type {PlayerElement | null} */ (document.querySelector('#movie_player'));

  const isAdPlaying = () => !!document.querySelector('.ad-interrupting');

  const redirect = (v, list, ytpRandom = null) => {
    if (location.host === 'm.youtube.com') {
      // Mobile: use direct navigation
      const url = `/watch?v=${v}&list=${list}${ytpRandom !== null ? `&ytp-random=${ytpRandom}` : ''}`;
      window.location.href = url;
    } else {
      // Desktop: try YouTube's client-side routing first, with fallback
      try {
        const playlistPanel = document.querySelector('ytd-playlist-panel-renderer #items');
        if (playlistPanel) {
          const redirector = document.createElement('a');
          redirector.className = 'yt-simple-endpoint style-scope ytd-playlist-panel-video-renderer';
          redirector.setAttribute('hidden', '');
          redirector.data = {
            commandMetadata: {
              webCommandMetadata: {
                url: `/watch?v=${v}&list=${list}${ytpRandom !== null ? `&ytp-random=${ytpRandom}` : ''}`,
                webPageType: 'WEB_PAGE_TYPE_WATCH',
                rootVe: 3832, // ??? required though
              },
            },
            watchEndpoint: {
              videoId: v,
              playlistId: list,
            },
          };
          playlistPanel.append(redirector);
          redirector.click();
        } else {
          // Fallback: use direct navigation if playlist panel not found
          const url = `/watch?v=${v}&list=${list}${ytpRandom !== null ? `&ytp-random=${ytpRandom}` : ''}`;
          window.location.href = url;
        }
      } catch {
        // Fallback: use direct navigation on error
        const url = `/watch?v=${v}&list=${list}${ytpRandom !== null ? `&ytp-random=${ytpRandom}` : ''}`;
        window.location.href = url;
      }
    }
  };

  let id = '';
  const apply = () => {
    if (id === '') {
      // do not apply prematurely, caused by mutation observer
      console.warn('[Play All] Channel ID not yet determined');
      return;
    }

    let parent =
      location.host === 'm.youtube.com'
        ? // mobile view
          queryHTMLElement(
            'ytm-feed-filter-chip-bar-renderer .chip-bar-contents, ytm-feed-filter-chip-bar-renderer > div'
          )
        : // desktop view
          queryHTMLElement('ytd-feed-filter-chip-bar-renderer iron-selector#chips');

    // #5: add a custom container for buttons if Latest/Popular/Oldest is missing
    if (parent === null) {
      const grid = queryHTMLElement('ytd-rich-grid-renderer, ytm-rich-grid-renderer');
      if (!grid) {
        console.warn('[Play All] Could not find grid container');
        return;
      }

      // Check if container already exists
      let existingContainer = grid.querySelector('.ytp-button-container');
      if (!existingContainer) {
        grid.insertAdjacentHTML('afterbegin', '<div class="ytp-button-container"></div>');
        existingContainer = grid.querySelector('.ytp-button-container');
      }
      parent = existingContainer instanceof HTMLElement ? existingContainer : null;
    }

    if (!parent) {
      console.warn('[Play All] Could not find parent container');
      return;
    }

    // Prevent duplicate buttons
    if (parent.querySelector('.ytp-play-all-btn, .ytp-random-btn')) {
      console.log('[Play All] Buttons already exist, skipping');
      return;
    }

    // See: available-lists.md
    const [allPlaylist, popularPlaylist] = window.location.pathname.endsWith('/videos')
      ? // Normal videos
        // list=UULP has the all videos sorted by popular
        // list=UU<ID> adds shorts into the playlist, list=UULF<ID> has videos without shorts
        ['UULF', 'UULP']
      : // Shorts
        window.location.pathname.endsWith('/shorts')
        ? ['UUSH', 'UUPS']
        : // Live streams
          ['UULV', 'UUPV'];

    const playlistSuffix = id.startsWith('UC') ? id.substring(2) : id;

    // Check if popular videos are displayed
    if (parent.querySelector(':nth-child(2).selected, :nth-child(2).iron-selected')) {
      parent.insertAdjacentHTML(
        'beforeend',
        `<a class="ytp-btn ytp-play-all-btn" href="/playlist?list=${popularPlaylist}${playlistSuffix}&playnext=1">Play Popular</a>`
      );
    } else if (parent.querySelector(':nth-child(1).selected, :nth-child(1).iron-selected')) {
      parent.insertAdjacentHTML(
        'beforeend',
        `<a class="ytp-btn ytp-play-all-btn" href="/playlist?list=${allPlaylist}${playlistSuffix}&playnext=1">Play All</a>`
      );
    } else {
      parent.insertAdjacentHTML(
        'beforeend',
        `<a class="ytp-btn ytp-play-all-btn ytp-unsupported" href="https://github.com/RobertWesner/YouTube-Play-All/issues/39" target="_blank">No Playlist Found</a>`
      );
    }

    const navigate = href => {
      window.location.assign(href);
    };

    if (location.host === 'm.youtube.com') {
      // YouTube returns an "invalid response" when using client side routing for playnext=1 on mobile
      parent.querySelectorAll('.ytp-btn').forEach(btn => {
        btn.addEventListener('click', event => {
          event.preventDefault();

          navigate(btn.href);
        });
      });
    } else {
      const attachNavigationHandler = elements => {
        elements.forEach(btn => {
          btn.addEventListener('click', event => {
            event.preventDefault();
            event.stopPropagation();

            navigate(btn.href);
          });
        });
      };

      attachNavigationHandler(parent.querySelectorAll('.ytp-play-all-btn:not(.ytp-unsupported)'));

      // Only allow random play in desktop version for now
      parent.insertAdjacentHTML(
        'beforeend',
        `
                <span class="ytp-btn ytp-random-btn ytp-btn-sections">
                    <a class="ytp-btn-section" href="/playlist?list=${allPlaylist}${playlistSuffix}&playnext=1&ytp-random=random&ytp-random-initial=1">
                        Play Random
                    </a><!--
                    --><span class="ytp-btn-section ytp-random-more-options-btn ytp-hover-popover">
                        &#x25BE
                    </span>
                </span>
            `
      );

      // Remove existing popovers to prevent duplicates when navigating between tabs
      document.querySelectorAll('.ytp-random-popover').forEach(popover => popover.remove());

      document.body.insertAdjacentHTML(
        'beforeend',
        `
                <div class="ytp-random-popover" hidden="">
                    <a href="/playlist?list=${allPlaylist}${playlistSuffix}&playnext=1&ytp-random=prefer-newest">
                        Prefer newest
                    </a>
                    <a href="/playlist?list=${allPlaylist}${playlistSuffix}&playnext=1&ytp-random=prefer-oldest&ytp-random-initial=1">
                        Prefer oldest
                    </a>
                </div>
            `
      );

      attachNavigationHandler(parent.querySelectorAll('.ytp-random-btn a'));

      const randomPopover = document.querySelector('.ytp-random-popover');
      if (randomPopover) {
        attachNavigationHandler(randomPopover.querySelectorAll('a'));
      }

      const randomMoreOptionsBtn = document.querySelector('.ytp-random-more-options-btn');
      if (randomMoreOptionsBtn && randomPopover) {
        randomMoreOptionsBtn.addEventListener('click', () => {
          const rect = randomMoreOptionsBtn.getBoundingClientRect();
          randomPopover.style.top = `${rect.bottom}px`;
          randomPopover.style.left = `${rect.right}px`;
          randomPopover.removeAttribute('hidden');
        });
        randomPopover.addEventListener('mouseleave', () => {
          randomPopover.setAttribute('hidden', '');
        });
      }
    }
  };

  const observer = new MutationObserver(() => {
    // [20250929-0] removeButton first and then apply, not addButton, since we don't need the pathname validation, and we want mobile to also use it
    removeButton();
    apply();
  });

  const addButton = async () => {
    observer.disconnect();

    if (
      !(
        window.location.pathname.endsWith('/videos') ||
        window.location.pathname.endsWith('/shorts') ||
        window.location.pathname.endsWith('/streams')
      )
    ) {
      return;
    }

    // Regenerate button if switched between Latest and Popular
    const element = document.querySelector(
      'ytd-rich-grid-renderer, ytm-feed-filter-chip-bar-renderer .iron-selected, ytm-feed-filter-chip-bar-renderer .chip-bar-contents .selected'
    );
    if (element) {
      observer.observe(element, {
        attributes: true,
        childList: false,
        subtree: false,
      });
    }

    // This check is necessary for the mobile Interval
    if (document.querySelector('.ytp-play-all-btn')) {
      return;
    }

    // Try to extract channel ID from canonical link first
    try {
      const canonical = document.querySelector('link[rel="canonical"]');
      if (canonical && canonical.href) {
        const match = canonical.href.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/);
        if (match && match[1]) {
          id = match[1];
          apply();
          return;
        }

        // Also try @handle format
        const handleMatch = canonical.href.match(/\/@([^\/]+)/);
        if (handleMatch) {
          // Try to get channel ID from page data
          const pageData = document.querySelector('ytd-browse[page-subtype="channels"]');
          if (pageData) {
            const channelId = pageData.getAttribute('channel-id');
            if (channelId && channelId.startsWith('UC')) {
              id = channelId;
              apply();
              return;
            }
          }
        }
      }
    } catch (e) {
      console.warn('[Play All] Error extracting channel ID from canonical:', e);
    }

    // Fallback: fetch HTML and parse
    try {
      const html = await (await fetch(location.href)).text();
      const canonicalMatch = html.match(
        /<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/(UC[a-zA-Z0-9_-]{22})"/
      );

      if (canonicalMatch && canonicalMatch[1]) {
        id = canonicalMatch[1];
      } else {
        // Try alternative extraction methods
        const channelIdMatch = html.match(/"channelId":"(UC[a-zA-Z0-9_-]{22})"/);
        if (channelIdMatch && channelIdMatch[1]) {
          id = channelIdMatch[1];
        }
      }

      if (id) {
        apply();
      } else {
        console.warn('[Play All] Could not extract channel ID');
      }
    } catch (e) {
      console.error('[Play All] Error fetching channel data:', e);
    }
  };

  // Removing the button prevents it from still existing when switching between "Videos", "Shorts", and "Live"
  // This is necessary due to the mobile Interval requiring a check for an already existing button
  const removeButton = () =>
    document.querySelectorAll('.ytp-btn').forEach(element => element.remove());

  if (location.host === 'm.youtube.com') {
    // The "yt-navigate-finish" event does not fire on mobile
    // Unfortunately pushState is triggered before the navigation occurs, so a Proxy is useless
    setInterval(addButton, 1000);
  } else {
    window.addEventListener('yt-navigate-start', removeButton);
    window.addEventListener('yt-navigate-finish', addButton);
    // Also attempt to add buttons on initial script run in case the SPA navigation event
    // already happened before this script was loaded (some browsers/firefox timing).
    try {
      setTimeout(addButton, 300);
    } catch {}
  }

  // Fallback playlist emulation
  (() => {
    const getItems = playlist => {
      return new Promise(resolve => {
        const payload = {
          uri: `https://www.youtube.com/playlist?list=${playlist}`,
          requestType: `ytp ${gmInfo?.script?.version ?? 'unknown'}`,
        };

        const markFailure = () => {
          const emulator = document.querySelector('.ytp-playlist-emulator');
          if (emulator instanceof HTMLElement) {
            emulator.setAttribute('data-failed', 'rejected');
          }
        };

        const handleSuccess = data => {
          resolve(data);
        };

        const handleError = () => {
          markFailure();
          resolve({ status: 'error', items: [] });
        };

        if (gmApi && typeof gmApi.xmlHttpRequest === 'function') {
          gmApi.xmlHttpRequest({
            method: 'POST',
            url: 'https://ytplaylist.robert.wesner.io/api/list',
            data: JSON.stringify(payload),
            headers: {
              'Content-Type': 'application/json',
            },
            onload: response => {
              try {
                handleSuccess(JSON.parse(response.responseText));
              } catch (parseError) {
                console.error('[Play All] Failed to parse playlist response:', parseError);
                handleError();
              }
            },
            onerror: _error => {
              handleError();
            },
          });
          return;
        }

        // Fallback to fetch when GM.xmlHttpRequest is unavailable (e.g., during tests)
        fetch('https://ytplaylist.robert.wesner.io/api/list', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(payload),
        })
          .then(resp => resp.json())
          .then(handleSuccess)
          .catch(err => {
            console.error('[Play All] Playlist fetch failed:', err);
            handleError();
          });
      });
    };

    const processItems = items => {
      const itemsContainer = document.querySelector('.ytp-playlist-emulator .items');
      const params = new URLSearchParams(window.location.search);
      const list = params.get('list');

      if (!(itemsContainer instanceof HTMLElement)) {
        return;
      }

      items.forEach(
        /**
         * @param {{
         *  position: number,
         *  title: string,
         *  videoId: string,
         * }} item
         */
        item => {
          const element = document.createElement('div');
          element.className = 'item';
          element.textContent = item.title;
          element.setAttribute('data-id', item.videoId);
          element.addEventListener('click', () => redirect(item.videoId, list));

          itemsContainer.append(element);
        }
      );

      markCurrentItem(params.get('v'));
    };

    const playNextEmulationItem = () => {
      document.querySelector(`.ytp-playlist-emulator .items .item[data-current] + .item`)?.click();
    };

    const markCurrentItem = videoId => {
      const existing = document.querySelector(`.ytp-playlist-emulator .items .item[data-current]`);
      if (existing) {
        existing.removeAttribute('data-current');
      }

      const current = document.querySelector(
        `.ytp-playlist-emulator .items .item[data-id="${videoId}"]`
      );
      if (current instanceof HTMLElement) {
        current.setAttribute('data-current', '');
        const parentElement = current.parentElement;
        if (parentElement instanceof HTMLElement) {
          const docElement = /** @type {any} */ (document.documentElement);
          const fontSize = parseFloat(getComputedStyle(docElement).fontSize || '16');
          parentElement.scrollTop = current.offsetTop - 12 * fontSize;
        }
      }
    };

    const emulatePlaylist = () => {
      if (!window.location.pathname.endsWith('/watch')) {
        return;
      }

      const params = new URLSearchParams(window.location.search);
      const list = params.get('list');
      if (!list) {
        return;
      }
      if (params.has('ytp-random')) {
        return;
      }

      // prevent playlist emulation on queue
      // its impossible to fetch that playlist externally anyway
      // https://github.com/RobertWesner/YouTube-Play-All/issues/33
      if (list.startsWith('TLPQ')) {
        return;
      }

      // No user ID in the list, cannot be fetched externally -> no emulation
      if (list.length <= 4) {
        return;
      }

      const existingEmulator = document.querySelector('.ytp-playlist-emulator');
      if (existingEmulator) {
        if (list === existingEmulator.getAttribute('data-list')) {
          markCurrentItem(params.get('v'));

          return;
        } else {
          // necessary to lose all the client side manipulations like SHIFT + N and the play next button
          window.location.reload();
        }
      }

      if (!new URLSearchParams(window.location.search).has('list')) {
        return;
      }

      if (
        !document.querySelector(
          '#secondary-inner > ytd-playlist-panel-renderer#playlist #items:empty'
        )
      ) {
        return;
      }

      const playlistEmulator = document.createElement('div');
      playlistEmulator.className = 'ytp-playlist-emulator';
      playlistEmulator.innerHTML = `
                <div class="title">
                    Playlist emulator
                </div>
                <div class="information">
                    It looks like YouTube is unable to handle this large playlist.
                    Playlist emulation is a <b>limited</b> fallback feature of ytp to enable you to watch even more content. <br>
                </div>
                <div class="items"></div>
                <div class="footer"></div>
            `;
      playlistEmulator.setAttribute('data-list', list);
      const playlistHost = document.querySelector(
        '#secondary-inner > ytd-playlist-panel-renderer#playlist'
      );
      if (playlistHost instanceof HTMLElement) {
        playlistHost.insertAdjacentElement('afterend', /** @type {any} */ (playlistEmulator));
      }

      getItems(list).then(response => {
        if (response?.status === 'running') {
          setTimeout(
            () =>
              getItems(list).then(nextResponse => {
                if (nextResponse && Array.isArray(nextResponse.items)) {
                  processItems(nextResponse.items);
                }
              }),
            5000
          );

          return;
        }

        if (response && Array.isArray(response.items)) {
          processItems(response.items);
        }
      });

      const nextButtonInterval = setInterval(() => {
        const nextButton = document.querySelector(
          '#ytd-player .ytp-next-button.ytp-button:not([ytp-emulation="applied"])'
        );
        if (nextButton) {
          clearInterval(nextButtonInterval);

          // Replace with span to prevent anchor click events
          const newButton = document.createElement('span');
          newButton.className = nextButton.className;
          newButton.innerHTML = nextButton.innerHTML;
          nextButton.replaceWith(newButton);

          newButton.setAttribute('ytp-emulation', 'applied');
          newButton.addEventListener('click', () => playNextEmulationItem());
        }
      }, 1000);

      document.addEventListener(
        'keydown',
        event => {
          // SHIFT + N
          if (event.shiftKey && event.key.toLowerCase() === 'n') {
            event.stopImmediatePropagation();
            event.preventDefault();

            playNextEmulationItem();
          }
        },
        true
      );

      setInterval(() => {
        const player = getPlayer();
        if (!player || typeof player.getProgressState !== 'function') {
          return;
        }

        const progressState = player.getProgressState();
        if (!progressState) {
          return;
        }

        // Do not listen for watch progress when watching advertisements
        if (!isAdPlaying()) {
          // Autoplay random video
          if (
            typeof progressState.current === 'number' &&
            typeof progressState.duration === 'number' &&
            progressState.current >= progressState.duration - 2
          ) {
            // make sure vanilla autoplay doesnt take over
            if (typeof player.pauseVideo === 'function') player.pauseVideo();
            if (typeof player.seekTo === 'function') player.seekTo(0);
            playNextEmulationItem();
          }
        }
      }, 500);
    };

    if (location.host === 'm.youtube.com') {
      // Note: Mobile playlist emulation is currently not supported due to different DOM structure
      // and API limitations on mobile YouTube. Future implementation would require:
      // - Mobile-specific DOM selectors
      // - Touch event handling
      // - Responsive UI adjustments
      console.log('[Play All] Mobile playlist emulation not yet supported');
    } else {
      window.addEventListener('yt-navigate-finish', () => setTimeout(emulatePlaylist, 1000));
    }
  })();

  // Random play feature
  (() => {
    // Random play is not supported for mobile devices
    if (location.host === 'm.youtube.com') {
      return;
    }

    const urlParams = new URLSearchParams(window.location.search);

    if (!urlParams.has('ytp-random') || urlParams.get('ytp-random') === '0') {
      return;
    }

    const ytpRandomParam = urlParams.get('ytp-random');
    /** @type {'random'|'prefer-newest'|'prefer-oldest'} */
    const ytpRandom =
      ytpRandomParam === 'prefer-newest' || ytpRandomParam === 'prefer-oldest'
        ? ytpRandomParam
        : 'random';

    const getStorageKey = () => `ytp-random-${urlParams.get('list')}`;
    const getStorage = () => JSON.parse(localStorage.getItem(getStorageKey()) || '{}');

    const isWatched = videoId => getStorage()[videoId] || false;
    const markWatched = videoId => {
      localStorage.setItem(getStorageKey(), JSON.stringify({ ...getStorage(), [videoId]: true }));
      document
        .querySelectorAll('#wc-endpoint[href*=zsA3X40nz9w]')
        .forEach(element => element.parentElement.setAttribute('hidden', ''));
    };

    // Storage needs to now be { [videoId]: bool }
    try {
      if (Array.isArray(getStorage())) {
        localStorage.removeItem(getStorageKey());
      }
    } catch {
      localStorage.removeItem(getStorageKey());
    }

    const playNextRandom = (reload = false) => {
      const playerInstance = getPlayer();
      if (playerInstance && typeof playerInstance.pauseVideo === 'function') {
        playerInstance.pauseVideo();
      }

      const videos = Object.entries(getStorage()).filter(([_, watched]) => !watched);
      const params = new URLSearchParams(window.location.search);

      if (videos.length === 0) {
        return;
      }

      // Either one fifth or at most the 20 newest.
      const preferredCount = Math.max(1, Math.min(Math.floor(videos.length * 0.2), 20));

      let videoIndex;
      switch (ytpRandom) {
        case 'prefer-newest':
          // Select between latest 20 videos
          videoIndex = Math.floor(Math.random() * preferredCount);

          break;
        case 'prefer-oldest':
          // Select between oldest `preferredCount` videos (the last N entries).
          // videos is an array where order follows the playlist DOM order; to pick
          // from the oldest items we need to start at `videos.length - preferredCount`.
          videoIndex = videos.length - preferredCount + Math.floor(Math.random() * preferredCount);

          break;
        default:
          videoIndex = Math.floor(Math.random() * videos.length);
      }

      // Safety clamp in case of unexpected edge cases
      if (videoIndex < 0) videoIndex = 0;
      if (videoIndex >= videos.length) videoIndex = videos.length - 1;

      if (reload) {
        params.set('v', videos[videoIndex][0]);
        params.set('ytp-random', ytpRandom);
        params.delete('t');
        params.delete('index');
        params.delete('ytp-random-initial');
        window.location.href = `${window.location.pathname}?${params.toString()}`;
      } else {
        // Use the redirect() function for consistent navigation
        try {
          redirect(videos[videoIndex][0], params.get('list'), ytpRandom);
        } catch (error) {
          console.error(
            '[Play All] Error using redirect(), falling back to manual redirect:',
            error
          );
          // Fallback to manual redirect if the redirect() function fails
          const redirector = document.createElement('a');
          redirector.className = 'yt-simple-endpoint style-scope ytd-playlist-panel-video-renderer';
          redirector.setAttribute('hidden', '');
          redirector.data = {
            commandMetadata: {
              webCommandMetadata: {
                url: `/watch?v=${videos[videoIndex][0]}&list=${params.get('list')}&ytp-random=${ytpRandom}`,
                webPageType: 'WEB_PAGE_TYPE_WATCH',
                rootVe: 3832,
              },
            },
            watchEndpoint: {
              videoId: videos[videoIndex][0],
              playlistId: params.get('list'),
            },
          };
          const listContainer = document.querySelector('ytd-playlist-panel-renderer #items');
          if (listContainer instanceof HTMLElement) {
            listContainer.append(redirector);
          } else {
            document.body.appendChild(redirector);
          }
          redirector.click();
        }
      }
    };

    let isIntervalSet = false;

    const applyRandomPlay = () => {
      if (!window.location.pathname.endsWith('/watch')) {
        return;
      }

      const playlistContainer = document.querySelector('#secondary ytd-playlist-panel-renderer');
      if (playlistContainer === null) {
        return;
      }
      if (playlistContainer.hasAttribute('ytp-random')) {
        return;
      }

      playlistContainer.setAttribute('ytp-random', 'applied');
      const headerContainer = playlistContainer.querySelector('.header');
      if (headerContainer) {
        headerContainer.insertAdjacentHTML(
          'afterend',
          `
                <div class="ytp-random-notice">
                    This playlist is using random play.<br>
                    The videos will <strong>not be played in the order</strong> listed here.
                </div>
            `
        );
      }

      const storage = getStorage();

      // Robustly collect playlist anchors - different YT layouts use different selectors
      const anchorSelectors = [
        '#wc-endpoint',
        'ytd-playlist-panel-video-renderer a#wc-endpoint',
        'ytd-playlist-panel-video-renderer a',
        'a#video-title',
        '#secondary ytd-playlist-panel-renderer a[href*="/watch?"]',
      ];

      const anchors = [];
      anchorSelectors.forEach(sel => {
        playlistContainer.querySelectorAll(sel).forEach(a => {
          if (a instanceof Element && a.tagName === 'A') anchors.push(/** @type {any} */ (a));
        });
      });

      // Deduplicate by href
      const uniq = [];
      const seen = new Set();
      anchors.forEach(a => {
        const href = a.href || a.getAttribute('href') || '';
        if (!seen.has(href)) {
          seen.add(href);
          uniq.push(a);
        }
      });

      const navigate = href => (window.location.href = href);

      uniq.forEach(element => {
        let videoId = null;
        try {
          videoId = new URL(element.href, window.location.origin).searchParams.get('v');
        } catch {
          videoId = new URLSearchParams(element.search || '').get('v');
        }

        if (!videoId) return;

        if (!isWatched(videoId)) {
          storage[videoId] = false;
        }

        // Ensure ytp-random param present
        try {
          const u = new URL(element.href, window.location.origin);
          u.searchParams.set('ytp-random', ytpRandom);
          element.href = u.toString();
        } catch {}

        // This bypasses the client side routing
        element.addEventListener('click', event => {
          event.preventDefault();
          navigate(element.href);
        });

        const entryKey = getVideoId(element.href);
        if (isWatched(entryKey)) {
          element.parentElement?.setAttribute('hidden', '');
        }
      });
      localStorage.setItem(getStorageKey(), JSON.stringify(storage));

      if (urlParams.get('ytp-random-initial') === '1' || isWatched(getVideoId(location.href))) {
        playNextRandom();

        return;
      }

      const header = playlistContainer.querySelector('h3 a');
      if (header && header.tagName === 'A') {
        const anchorHeader = /** @type {HTMLAnchorElement} */ (/** @type {unknown} */ (header));
        anchorHeader.innerHTML += ` <span class="ytp-badge ytp-random-badge">${ytpRandom} <span style="font-size: 2rem; vertical-align: top">&times;</span></span>`;
        anchorHeader.href = 'javascript:void(0)';
        const badge = anchorHeader.querySelector('.ytp-random-badge');
        if (badge) {
          badge.addEventListener('click', event => {
            event.preventDefault();

            localStorage.removeItem(getStorageKey());

            const params = new URLSearchParams(location.search);
            params.delete('ytp-random');
            window.location.href = `${window.location.pathname}?${params.toString()}`;
          });
        }
      }

      document.addEventListener(
        'keydown',
        event => {
          // SHIFT + N
          if (event.shiftKey && event.key.toLowerCase() === 'n') {
            event.stopImmediatePropagation();
            event.preventDefault();

            const videoId = getVideoId(location.href);
            markWatched(videoId);
            // Unfortunately there is no workaround to YouTube redirecting to the next in line without a reload
            playNextRandom(true);
          }
        },
        true
      );

      if (isIntervalSet) {
        return;
      }
      isIntervalSet = true;

      setInterval(() => {
        const videoId = getVideoId(location.href);

        const params = new URLSearchParams(location.search);
        params.set('ytp-random', ytpRandom);
        window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);

        const player = getPlayer();
        if (!player || typeof player.getProgressState !== 'function') {
          return;
        }

        const progressState = player.getProgressState();
        if (
          !progressState ||
          typeof progressState.current !== 'number' ||
          typeof progressState.duration !== 'number'
        ) {
          return;
        }

        // Do not listen for watch progress when watching advertisements
        if (!isAdPlaying()) {
          if (progressState.current / progressState.duration >= 0.9) {
            if (videoId) markWatched(videoId);
          }

          // Autoplay random video
          if (progressState.current >= progressState.duration - 2) {
            // make sure vanilla autoplay doesnt take over
            if (typeof player.pauseVideo === 'function') player.pauseVideo();
            if (typeof player.seekTo === 'function') player.seekTo(0);
            playNextRandom();
          }
        }

        const nextButton = document.querySelector(
          '#ytd-player .ytp-next-button.ytp-button:not([ytp-random="applied"])'
        );
        if (nextButton instanceof HTMLElement) {
          // Replace with span to prevent anchor click events
          const newButton = document.createElement('span');
          newButton.className = nextButton.className;
          newButton.innerHTML = nextButton.innerHTML;
          nextButton.replaceWith(newButton);

          newButton.setAttribute('ytp-random', 'applied');
          newButton.addEventListener('click', () => {
            if (videoId) markWatched(videoId);
            playNextRandom();
          });
        }
      }, 1000);
    };

    setInterval(applyRandomPlay, 1000);
  })();
})().catch(error =>
  console.error(
    '%cytp - YouTube Play All\n',
    'color: #bf4bcc; font-size: 32px; font-weight: bold',
    error
  )
);

// --- MODULE: adblocker.js ---

// Ad Blocker
(function () {
  'use strict';

  // Internationalization
  const i18n = {
    en: {
      adBlocker: 'Ad Blocker',
      adBlockerDescription: 'Skip ads and remove ad elements automatically',
    },
    ru: {
      adBlocker: 'Блокировщик рекламы',
      adBlockerDescription: 'Автоматически пропускать рекламу и удалять рекламные элементы',
    },
  };

  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  /**
   * Ad blocking functionality for YouTube
   * @namespace AdBlocker
   */
  const AdBlocker = {
    /**
     * Configuration settings
     * @type {Object}
     */
    config: {
      skipInterval: 500,
      removeInterval: 1500,
      enableLogging: false,
      maxRetries: 2,
      enabled: true,
      storageKey: 'youtube_adblocker_settings',
    },

    /**
     * Current state tracking
     * @type {Object}
     */
    state: {
      isYouTubeShorts: false,
      isYouTubeMusic: location.hostname === 'music.youtube.com',
      lastSkipAttempt: 0,
      retryCount: 0,
      initialized: false,
    },

    /**
     * Cached DOM queries for performance
     * @type {Object}
     */
    cache: {
      moviePlayer: null,
      ytdPlayer: null,
      lastCacheTime: 0,
      cacheTimeout: 5000,
    },

    /**
     * Optimized CSS selectors for ad elements
     * @type {Object}
     */
    selectors: {
      ads: '#player-ads,.ytp-ad-module,.ad-showing,.ytp-ad-timed-pie-countdown-container,.ytp-ad-survey-questions',
      elements:
        '#masthead-ad,ytd-merch-shelf-renderer,.yt-mealbar-promo-renderer,ytmusic-mealbar-promo-renderer,ytmusic-statement-banner-renderer,.ytp-featured-product',
      video: 'video.html5-main-video',
      removal: 'ytd-reel-video-renderer .ytd-ad-slot-renderer',
    },

    /**
     * Settings management with localStorage persistence
     * @type {Object}
     */
    settings: {
      /**
       * Load settings from localStorage with validation
       * @returns {void}
       */
      load() {
        try {
          const saved = localStorage.getItem(AdBlocker.config.storageKey);
          if (!saved) return;

          const parsed = JSON.parse(saved);
          if (typeof parsed !== 'object' || parsed === null) {
            console.warn('[AdBlocker] Invalid settings format');
            return;
          }

          // Validate and apply settings
          if (typeof parsed.enabled === 'boolean') {
            AdBlocker.config.enabled = parsed.enabled;
          } else {
            AdBlocker.config.enabled = true; // Default to enabled
          }

          if (typeof parsed.enableLogging === 'boolean') {
            AdBlocker.config.enableLogging = parsed.enableLogging;
          } else {
            AdBlocker.config.enableLogging = false; // Default to disabled
          }
        } catch (error) {
          console.error('[AdBlocker] Error loading settings:', error);
          // Set safe defaults on error
          AdBlocker.config.enabled = true;
          AdBlocker.config.enableLogging = false;
        }
      },

      /**
       * Save settings to localStorage with error handling
       * @returns {void}
       */
      save() {
        try {
          const settingsToSave = {
            enabled: AdBlocker.config.enabled,
            enableLogging: AdBlocker.config.enableLogging,
          };
          localStorage.setItem(AdBlocker.config.storageKey, JSON.stringify(settingsToSave));
        } catch (error) {
          console.error('[AdBlocker] Error saving settings:', error);
        }
      },
    },

    /**
     * Get cached player elements
     * @returns {Object} Object containing player element and controller
     */
    getPlayer() {
      const now = Date.now();
      if (now - AdBlocker.cache.lastCacheTime > AdBlocker.cache.cacheTimeout) {
        AdBlocker.cache.moviePlayer = document.querySelector('#movie_player');
        AdBlocker.cache.ytdPlayer = document.querySelector('#ytd-player');
        AdBlocker.cache.lastCacheTime = now;
      }

      const playerEl = AdBlocker.cache.ytdPlayer;
      return {
        element: AdBlocker.cache.moviePlayer,
        player: playerEl?.getPlayer?.() || playerEl,
      };
    },

    /**
     * Skip current ad by seeking to end
     * @returns {void}
     */
    skipAd() {
      if (!AdBlocker.config.enabled) return;

      const now = Date.now();
      if (now - AdBlocker.state.lastSkipAttempt < 300) return;
      AdBlocker.state.lastSkipAttempt = now;

      if (location.pathname.startsWith('/shorts/')) return;

      // Fast ad detection
      const adElement = document.querySelector(
        '.ad-showing, .ytp-ad-timed-pie-countdown-container'
      );
      if (!adElement) {
        AdBlocker.state.retryCount = 0;
        return;
      }

      try {
        const { player } = AdBlocker.getPlayer();
        if (!player) return;

        const video = document.querySelector(AdBlocker.selectors.video);

        // Mute ad immediately
        if (video) video.muted = true;

        // Skip logic based on platform
        if (AdBlocker.state.isYouTubeMusic && video) {
          /** @type {HTMLVideoElement} */ (video).currentTime = video.duration || 999;
        } else if (typeof player.getVideoData === 'function') {
          const videoData = player.getVideoData();
          if (videoData?.video_id) {
            const currentTime = Math.floor(player.getCurrentTime?.() || 0);

            // Use most efficient skip method
            if (typeof player.loadVideoById === 'function') {
              player.loadVideoById(videoData.video_id, currentTime);
            }
          }
        }

        AdBlocker.state.retryCount = 0;
      } catch {
        if (AdBlocker.state.retryCount < AdBlocker.config.maxRetries) {
          AdBlocker.state.retryCount++;
          setTimeout(AdBlocker.skipAd, 800);
        }
      }
    },

    // Minimal CSS injection
    addCss() {
      if (document.querySelector('#yt-ab-styles') || !AdBlocker.config.enabled) return;

      // ✅ Use StyleManager instead of createElement('style')
      const styles = `${AdBlocker.selectors.ads},${AdBlocker.selectors.elements}{display:none!important;}`;
      YouTubeUtils.StyleManager.add('yt-ab-styles', styles);
    },

    removeCss() {
      YouTubeUtils.StyleManager.remove('yt-ab-styles');
    },

    // Batched element removal
    removeElements() {
      if (!AdBlocker.config.enabled || AdBlocker.state.isYouTubeMusic) return;

      // Use requestIdleCallback for non-blocking removal
      const remove = () => {
        const elements = document.querySelectorAll(AdBlocker.selectors.removal);
        elements.forEach(el => el.closest('ytd-reel-video-renderer')?.remove());
      };

      if (window.requestIdleCallback) {
        requestIdleCallback(remove, { timeout: 100 });
      } else {
        setTimeout(remove, 0);
      }
    },

    // Optimized settings UI
    addSettingsUI() {
      const section = document.querySelector('.ytp-plus-settings-section[data-section="basic"]');
      if (!section || section.querySelector('.ab-settings')) return;

      try {
        const item = document.createElement('div');
        item.className = 'ytp-plus-settings-item ab-settings';
        item.innerHTML = `
          <div>
            <label class="ytp-plus-settings-item-label">${t('adBlocker')}</label>
            <div class="ytp-plus-settings-item-description">${t('adBlockerDescription')}</div>
          </div>
          <input type="checkbox" class="ytp-plus-settings-checkbox" ${AdBlocker.config.enabled ? 'checked' : ''}>
        `;

        section.appendChild(item);

        item.querySelector('input').addEventListener('change', e => {
          const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
          AdBlocker.config.enabled = target.checked;
          AdBlocker.settings.save();
          AdBlocker.config.enabled ? AdBlocker.addCss() : AdBlocker.removeCss();
        });
      } catch (error) {
        YouTubeUtils.logError('AdBlocker', 'Failed to add settings UI', error);
      }
    },

    // Streamlined initialization
    init() {
      if (AdBlocker.state.initialized) return;
      AdBlocker.state.initialized = true;

      AdBlocker.settings.load();

      if (AdBlocker.config.enabled) {
        AdBlocker.addCss();
        AdBlocker.removeElements();
      }

      // Start optimized intervals with cleanup registration
      const skipInterval = setInterval(AdBlocker.skipAd, AdBlocker.config.skipInterval);
      const removeInterval = setInterval(AdBlocker.removeElements, AdBlocker.config.removeInterval);

      // ✅ Register intervals in cleanupManager
      YouTubeUtils.cleanupManager.registerInterval(skipInterval);
      YouTubeUtils.cleanupManager.registerInterval(removeInterval);

      // Navigation handling
      const handleNavigation = () => {
        AdBlocker.state.isYouTubeShorts = location.pathname.startsWith('/shorts/');
        AdBlocker.cache.lastCacheTime = 0; // Reset cache
      };

      // Override pushState for SPA navigation
      const originalPushState = history.pushState;
      history.pushState = function () {
        const result = originalPushState.apply(this, arguments);
        setTimeout(handleNavigation, 50);
        return result;
      };

      // Settings modal integration
      const settingsObserver = new MutationObserver(_mutations => {
        for (const { addedNodes } of _mutations) {
          for (const node of addedNodes) {
            if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
              setTimeout(AdBlocker.addSettingsUI, 50);
              return;
            }
          }
        }
      });

      // ✅ Register observer in cleanupManager
      YouTubeUtils.cleanupManager.registerObserver(settingsObserver);

      // ✅ Safe observe with document.body check
      if (document.body) {
        settingsObserver.observe(document.body, { childList: true });
      } else {
        document.addEventListener('DOMContentLoaded', () => {
          settingsObserver.observe(document.body, { childList: true });
        });
      }

      // ✅ Register global click listener in cleanupManager
      const clickHandler = e => {
        const target = /** @type {EventTarget & HTMLElement} */ (e.target);
        if (target.dataset?.section === 'basic') {
          setTimeout(AdBlocker.addSettingsUI, 25);
        }
      };
      YouTubeUtils.cleanupManager.registerListener(document, 'click', clickHandler, {
        passive: true,
        capture: true,
      });

      // Initial skip attempt
      if (AdBlocker.config.enabled) {
        setTimeout(AdBlocker.skipAd, 200);
      }
    },
  };

  // Initialize
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', AdBlocker.init, { once: true });
  } else {
    AdBlocker.init();
  }
})();

// --- MODULE: pip.js ---

// YouTube Picture-in-Picture settings
(function () {
  'use strict';

  // Internationalization
  const i18n = {
    en: {
      pipTitle: 'Picture-in-Picture',
      pipDescription: 'Add Picture-in-Picture functionality with keyboard shortcut',
      pipShortcutTitle: 'PiP Keyboard Shortcut',
      pipShortcutDescription: 'Customize keyboard combination to toggle PiP mode',
      none: 'None',
      ctrl: 'Ctrl',
      alt: 'Alt',
      shift: 'Shift',
    },
    ru: {
      pipTitle: 'Картинка в картинке',
      pipDescription: 'Добавляет функцию «Картинка в картинке» с клавишной комбинацией',
      pipShortcutTitle: 'Клавишная комбинация PiP',
      pipShortcutDescription: 'Настройка клавиатурной комбинации для переключения режима PiP',
      none: 'Нет',
      ctrl: 'Ctrl',
      alt: 'Alt',
      shift: 'Shift',
    },
  };

  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  /**
   * PiP settings configuration
   * @type {Object}
   * @property {boolean} enabled - Whether PiP is enabled
   * @property {Object} shortcut - Keyboard shortcut configuration
   * @property {string} storageKey - LocalStorage key for persistence
   */
  const pipSettings = {
    enabled: true,
    shortcut: { key: 'P', shiftKey: true, altKey: false, ctrlKey: false },
    storageKey: 'youtube_pip_settings',
  };

  const PIP_SESSION_KEY = 'youtube_plus_pip_session';

  /**
   * Get video element with validation
   * @returns {HTMLVideoElement|null} Video element or null if not found
   */
  const getVideoElement = () => {
    try {
      const candidate =
        (typeof YouTubeUtils?.querySelector === 'function' &&
          YouTubeUtils.querySelector('video')) ||
        document.querySelector('video');

      if (candidate && candidate.tagName && candidate.tagName.toLowerCase() === 'video') {
        return /** @type {HTMLVideoElement} */ (candidate);
      }

      return null;
    } catch (error) {
      console.error('[PiP] Error getting video element:', error);
      return null;
    }
  };

  /**
   * Wait for video metadata to load with timeout
   * @param {HTMLVideoElement} video - Video element
   * @returns {Promise<void>} Resolves when metadata is loaded
   */
  const waitForMetadata = video => {
    if (!video) {
      return Promise.reject(new Error('[PiP] Invalid video element'));
    }

    if (video.readyState >= 1 && !video.seeking) {
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      let settled = false;

      const cleanup = () => {
        video.removeEventListener('loadedmetadata', onLoaded);
        video.removeEventListener('error', onError);
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
      };

      const onLoaded = () => {
        if (settled) return;
        settled = true;
        cleanup();
        resolve();
      };

      const onError = () => {
        if (settled) return;
        settled = true;
        cleanup();
        reject(new Error('[PiP] Video metadata failed to load'));
      };

      let timeoutId = setTimeout(() => {
        if (settled) return;
        settled = true;
        cleanup();
        reject(new Error('[PiP] Timed out waiting for video metadata'));
      }, 3000);

      const registeredTimeout = YouTubeUtils?.cleanupManager?.registerTimeout?.(timeoutId);
      if (registeredTimeout) {
        timeoutId = registeredTimeout;
      }

      video.addEventListener('loadedmetadata', onLoaded, { once: true });
      video.addEventListener('error', onError, { once: true });
    });
  };

  const setSessionActive = isActive => {
    try {
      if (isActive) {
        sessionStorage.setItem(PIP_SESSION_KEY, 'true');
      } else {
        sessionStorage.removeItem(PIP_SESSION_KEY);
      }
    } catch {}
  };

  const wasSessionActive = () => {
    try {
      return sessionStorage.getItem(PIP_SESSION_KEY) === 'true';
    } catch {
      return false;
    }
  };

  /**
   * Load settings from localStorage with validation
   * @returns {void}
   */
  const loadSettings = () => {
    try {
      const saved = localStorage.getItem(pipSettings.storageKey);
      if (!saved) return;

      const parsed = JSON.parse(saved);
      if (typeof parsed !== 'object' || parsed === null) {
        console.warn('[PiP] Invalid settings format');
        return;
      }

      // Validate and merge settings
      if (typeof parsed.enabled === 'boolean') {
        pipSettings.enabled = parsed.enabled;
      }

      // Validate shortcut object
      if (parsed.shortcut && typeof parsed.shortcut === 'object') {
        if (typeof parsed.shortcut.key === 'string' && parsed.shortcut.key.length > 0) {
          pipSettings.shortcut.key = parsed.shortcut.key;
        }
        if (typeof parsed.shortcut.shiftKey === 'boolean') {
          pipSettings.shortcut.shiftKey = parsed.shortcut.shiftKey;
        }
        if (typeof parsed.shortcut.altKey === 'boolean') {
          pipSettings.shortcut.altKey = parsed.shortcut.altKey;
        }
        if (typeof parsed.shortcut.ctrlKey === 'boolean') {
          pipSettings.shortcut.ctrlKey = parsed.shortcut.ctrlKey;
        }
      }
    } catch (e) {
      console.error('[PiP] Error loading settings:', e);
    }
  };

  /**
   * Save settings to localStorage with error handling
   * @returns {void}
   */
  const saveSettings = () => {
    try {
      const settingsToSave = {
        enabled: pipSettings.enabled,
        shortcut: pipSettings.shortcut,
      };
      localStorage.setItem(pipSettings.storageKey, JSON.stringify(settingsToSave));
    } catch (e) {
      console.error('[PiP] Error saving settings:', e);
    }
  };

  /**
   * Get current PiP element as HTMLVideoElement when available
   * @returns {HTMLVideoElement|null}
   */
  const getCurrentPiPElement = () => {
    const current = document.pictureInPictureElement;
    if (current && typeof current === 'object' && 'tagName' in current) {
      const tag = /** @type {{ tagName?: string }} */ (current).tagName;
      if (typeof tag === 'string' && tag.toLowerCase() === 'video') {
        return /** @type {HTMLVideoElement} */ (/** @type {unknown} */ (current));
      }
    }
    return null;
  };

  /**
   * Toggle Picture-in-Picture mode
   * @param {HTMLVideoElement} video - The video element
   * @returns {Promise<void>}
   */
  const togglePictureInPicture = async video => {
    if (!pipSettings.enabled || !video) return;

    try {
      const currentPiP = getCurrentPiPElement();

      if (currentPiP && currentPiP !== video) {
        await document.exitPictureInPicture();
        setSessionActive(false);
      }

      if (getCurrentPiPElement() === video) {
        await document.exitPictureInPicture();
        setSessionActive(false);
        return;
      }

      if (video.disablePictureInPicture) {
        throw new Error('Picture-in-Picture is disabled by the video element');
      }

      await waitForMetadata(video);

      await video.requestPictureInPicture();
      setSessionActive(true);
    } catch (error) {
      console.error('[YouTube+][PiP] Failed to toggle Picture-in-Picture:', error);
    }
  };

  /**
   * Add PiP settings UI to advanced settings modal
   * @returns {void}
   */
  const addPipSettingsToModal = () => {
    // ✅ Use cached querySelector
    const advancedSection = YouTubeUtils.querySelector(
      '.ytp-plus-settings-section[data-section="advanced"]'
    );
    if (!advancedSection || YouTubeUtils.querySelector('.pip-settings-item')) return;

    // Add styles if they don't exist
    // ✅ Use StyleManager instead of createElement('style')
    if (!document.getElementById('pip-styles')) {
      const styles = `
          .pip-shortcut-editor { display: flex; align-items: center; gap: 8px; }
          .pip-shortcut-editor select, #pip-key {background: rgba(34, 34, 34, var(--yt-header-bg-opacity)); color: var(--yt-spec-text-primary); border: 1px solid var(--yt-spec-10-percent-layer); border-radius: var(--yt-radius-sm); padding: 4px;}
        `;
      YouTubeUtils.StyleManager.add('pip-styles', styles);
    }

    // Enable/disable toggle
    const enableItem = document.createElement('div');
    enableItem.className = 'ytp-plus-settings-item pip-settings-item';
    enableItem.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('pipTitle')}</label>
          <div class="ytp-plus-settings-item-description">${t('pipDescription')}</div>
        </div>
        <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enablePiP" id="pip-enable-checkbox" ${pipSettings.enabled ? 'checked' : ''}>
      `;
    advancedSection.appendChild(enableItem);

    // Shortcut settings
    const shortcutItem = document.createElement('div');
    shortcutItem.className = 'ytp-plus-settings-item pip-shortcut-item';
    shortcutItem.style.display = pipSettings.enabled ? 'flex' : 'none';

    const { ctrlKey, altKey, shiftKey } = pipSettings.shortcut;
    const modifierValue =
      ctrlKey && altKey && shiftKey
        ? 'ctrl+alt+shift'
        : ctrlKey && altKey
          ? 'ctrl+alt'
          : ctrlKey && shiftKey
            ? 'ctrl+shift'
            : altKey && shiftKey
              ? 'alt+shift'
              : ctrlKey
                ? 'ctrl'
                : altKey
                  ? 'alt'
                  : shiftKey
                    ? 'shift'
                    : 'none';

    shortcutItem.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('pipShortcutTitle')}</label>
          <div class="ytp-plus-settings-item-description">${t('pipShortcutDescription')}</div>
        </div>
        <div class="pip-shortcut-editor">
          <select id="pip-modifier-combo">
            ${[
              'none',
              'ctrl',
              'alt',
              'shift',
              'ctrl+alt',
              'ctrl+shift',
              'alt+shift',
              'ctrl+alt+shift',
            ]
              .map(
                v =>
                  `<option value="${v}" ${v === modifierValue ? 'selected' : ''}>${
                    v === 'none'
                      ? t('none')
                      : v
                          .replace(/\+/g, '+')
                          .split('+')
                          .map(k => t(k.toLowerCase()))
                          .join('+')
                          .split('+')
                          .map(k => k.charAt(0).toUpperCase() + k.slice(1))
                          .join('+')
                  }</option>`
              )
              .join('')}
          </select>
          <span>+</span>
          <input type="text" id="pip-key" value="${pipSettings.shortcut.key}" maxlength="1" style="width: 30px; text-align: center;">
        </div>
      `;
    advancedSection.appendChild(shortcutItem);

    // Event listeners
    document.getElementById('pip-enable-checkbox').addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      pipSettings.enabled = target.checked;
      shortcutItem.style.display = pipSettings.enabled ? 'flex' : 'none';
      saveSettings();
    });

    document.getElementById('pip-modifier-combo').addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLSelectElement} */ (e.target);
      const value = target.value;
      pipSettings.shortcut.ctrlKey = value.includes('ctrl');
      pipSettings.shortcut.altKey = value.includes('alt');
      pipSettings.shortcut.shiftKey = value.includes('shift');
      saveSettings();
    });

    document.getElementById('pip-key').addEventListener('input', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      if (target.value) {
        pipSettings.shortcut.key = target.value.toUpperCase();
        saveSettings();
      }
    });

    document.getElementById('pip-key').addEventListener('keydown', e => e.stopPropagation());
  };

  // Initialize
  loadSettings();

  // Event listeners
  document.addEventListener('keydown', e => {
    if (!pipSettings.enabled) return;
    const { shiftKey, altKey, ctrlKey, key } = pipSettings.shortcut;
    if (
      e.shiftKey === shiftKey &&
      e.altKey === altKey &&
      e.ctrlKey === ctrlKey &&
      e.key.toUpperCase() === key
    ) {
      // ✅ Use cached querySelector and guard by tagName to avoid referencing DOM lib types in TS
      const video = getVideoElement();
      if (video) {
        void togglePictureInPicture(video);
      }
      e.preventDefault();
    }
  });

  window.addEventListener('storage', e => {
    if (e.key === pipSettings.storageKey) {
      loadSettings();
    }
  });

  window.addEventListener('load', () => {
    if (!pipSettings.enabled || !wasSessionActive() || document.pictureInPictureElement) {
      return;
    }

    const resumePiP = () => {
      const video = getVideoElement();
      if (!video) return;

      togglePictureInPicture(video).catch(() => {
        // If resume fails we reset the session flag to avoid loops
        setSessionActive(false);
      });
    };

    const ensureCleanup = handler => {
      if (!handler) return;
      try {
        document.removeEventListener('pointerdown', handler, true);
      } catch {}
    };

    const cleanupListeners = () => {
      ensureCleanup(pointerListener);
      ensureCleanup(keyListener);
    };

    const pointerListener = () => {
      cleanupListeners();
      resumePiP();
    };

    const keyListener = () => {
      cleanupListeners();
      resumePiP();
    };

    document.addEventListener('pointerdown', pointerListener, { once: true, capture: true });
    document.addEventListener('keydown', keyListener, { once: true, capture: true });
  });

  // DOM observers for the settings modal
  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
          setTimeout(addPipSettingsToModal, 100);
        }
      }
    }

    document.addEventListener('leavepictureinpicture', () => {
      setSessionActive(false);
    });
    // Check for section changes - ✅ Use cached querySelector
    if (YouTubeUtils.querySelector('.ytp-plus-settings-nav-item[data-section="advanced"].active')) {
      // If advanced section is active and our settings aren't there yet, add them
      if (!YouTubeUtils.querySelector('.pip-settings-item')) {
        setTimeout(addPipSettingsToModal, 50);
      }
    }
  });

  // ✅ Register observer in cleanupManager
  YouTubeUtils.cleanupManager.registerObserver(observer);

  // ✅ Safe observe with document.body check
  if (document.body) {
    observer.observe(document.body, { childList: true, subtree: true });
  } else {
    document.addEventListener('DOMContentLoaded', () => {
      observer.observe(document.body, { childList: true, subtree: true });
    });
  }

  // ✅ Register global click listener in cleanupManager
  const clickHandler = e => {
    const target = /** @type {EventTarget & HTMLElement} */ (e.target);
    if (target.classList && target.classList.contains('ytp-plus-settings-nav-item')) {
      if (target.dataset?.section === 'advanced') {
        setTimeout(addPipSettingsToModal, 50);
      }
    }
  };
  YouTubeUtils.cleanupManager.registerListener(document, 'click', clickHandler, true);
})();

// --- MODULE: timecode.js ---

// YouTube Timecode Panel
(function () {
  'use strict';

  // Early exit for embeds to prevent duplicate panels - ✅ Use cached querySelector
  if (window.location.hostname !== 'www.youtube.com' || window.frameElement) {
    return;
  }

  // Prevent multiple initializations
  if (window._timecodeModuleInitialized) return;
  window._timecodeModuleInitialized = true;

  // Localization
  const i18n = {
    en: {
      timecodes: 'Timecodes',
      noTimecodesFound: 'No timecodes found',
      clickToAdd: 'Click + to add current time',
      reload: 'Reload timecodes',
      close: 'Close',
      add: '+ Add',
      export: 'Export',
      tracking: 'Tracking',
      track: 'Track',
      cancel: 'Cancel',
      save: 'Save',
      timePlaceholder: 'Time (e.g., 1:30)',
      labelPlaceholder: 'Label (optional)',
      enableTimecode: 'Enable Timecode Panel',
      keyboardShortcut: 'Keyboard Shortcut',
      enableDescription: 'Enable video timecode/chapter panel with quick navigation',
      shortcutDescription: 'Customize keyboard combination to toggle Timecode Panel',
      foundTimecodes: 'Found timecodes: {count}',
      cannotDeleteChapter: 'Cannot delete YouTube chapters',
      invalidTimeFormat: 'Invalid time format',
      timecodeDeleted: 'Timecode deleted',
      timecodeUpdated: 'Timecode updated',
      timecodeAdded: 'Timecode added',
      noTimecodesToExport: 'No timecodes to export',
      timecodesCopied: 'Timecodes copied to clipboard',
      edit: 'Edit',
      delete: 'Delete',
      copied: 'Copied!',
      confirmDelete: 'Delete timecode "{label}"?',
      reloadError: 'Error reloading timecodes',
      cannotEditChapter: 'Cannot edit YouTube chapters',
    },
    ru: {
      timecodes: 'Таймкоды',
      noTimecodesFound: 'Таймкоды не найдены',
      clickToAdd: 'Нажмите + чтобы добавить текущее время',
      reload: 'Обновить таймкоды',
      close: 'Закрыть',
      add: '+ Добавить',
      export: 'Экспорт',
      tracking: 'Отслеживание',
      track: 'Отслеживать',
      cancel: 'Отмена',
      save: 'Сохранить',
      timePlaceholder: 'Время (например, 1:30)',
      labelPlaceholder: 'Метка (необязательно)',
      enableTimecode: 'Включить панель таймкодов',
      keyboardShortcut: 'Горячая клавиша',
      enableDescription: 'Включить панель таймкодов/глав с быстрым переходом',
      shortcutDescription: 'Настройте комбинацию клавиш для переключения панели таймкодов',
      foundTimecodes: 'Найдено таймкодов: {count}',
      cannotDeleteChapter: 'Нельзя удалить главы YouTube',
      invalidTimeFormat: 'Неверный формат времени',
      timecodeDeleted: 'Таймкод удалён',
      timecodeUpdated: 'Таймкод обновлён',
      timecodeAdded: 'Таймкод добавлен',
      noTimecodesToExport: 'Нет таймкодов для экспорта',
      timecodesCopied: 'Таймкоды скопированы в буфер обмена',
      edit: 'Редактировать',
      delete: 'Удалить',
      copied: 'Скопировано!',
      confirmDelete: 'Удалить таймкод "{label}"?',
      reloadError: 'Ошибка при обновлении таймкодов',
      cannotEditChapter: 'Нельзя редактировать главы YouTube',
    },
  };

  // Detect language
  const getLanguage = () => {
    const htmlLang = document.documentElement.lang || 'en';
    if (htmlLang.startsWith('ru')) return 'ru';
    return 'en';
  };

  const lang = getLanguage();
  const t = key => i18n[lang][key] || i18n.en[key] || key;

  // Configuration
  const config = {
    enabled: true,
    autoDetect: true,
    shortcut: { key: 'T', shiftKey: true, altKey: false, ctrlKey: false },
    storageKey: 'youtube_timecode_settings',
    autoSave: true,
    autoTrackPlayback: true,
    panelPosition: null,
    export: true,
  };

  // State management
  const state = {
    timecodes: new Map(),
    dom: {},
    isReloading: false,
    activeIndex: null,
    trackingId: 0,
    dragging: false,
    editingIndex: null,
    resizeListenerKey: null,
  };

  let initStarted = false;

  const scheduleInitRetry = () => {
    const timeoutId = setTimeout(init, 250);
    YouTubeUtils.cleanupManager?.registerTimeout?.(timeoutId);
  };

  // Utilities
  /**
   * Load settings from localStorage with error handling
   * @returns {void}
   */
  const loadSettings = () => {
    try {
      const saved = localStorage.getItem(config.storageKey);
      if (!saved) return;

      const parsed = JSON.parse(saved);
      if (typeof parsed !== 'object' || parsed === null) {
        console.warn('[Timecode] Invalid settings format');
        return;
      }

      // Validate and merge settings
      if (typeof parsed.enabled === 'boolean') {
        config.enabled = parsed.enabled;
      }
      if (typeof parsed.autoDetect === 'boolean') {
        config.autoDetect = parsed.autoDetect;
      }
      if (typeof parsed.autoSave === 'boolean') {
        config.autoSave = parsed.autoSave;
      }
      if (typeof parsed.autoTrackPlayback === 'boolean') {
        config.autoTrackPlayback = parsed.autoTrackPlayback;
      }
      if (typeof parsed.export === 'boolean') {
        config.export = parsed.export;
      }

      // Validate shortcut object
      if (parsed.shortcut && typeof parsed.shortcut === 'object') {
        if (typeof parsed.shortcut.key === 'string') {
          config.shortcut.key = parsed.shortcut.key;
        }
        if (typeof parsed.shortcut.shiftKey === 'boolean') {
          config.shortcut.shiftKey = parsed.shortcut.shiftKey;
        }
        if (typeof parsed.shortcut.altKey === 'boolean') {
          config.shortcut.altKey = parsed.shortcut.altKey;
        }
        if (typeof parsed.shortcut.ctrlKey === 'boolean') {
          config.shortcut.ctrlKey = parsed.shortcut.ctrlKey;
        }
      }

      // Validate panel position
      if (parsed.panelPosition && typeof parsed.panelPosition === 'object') {
        const { left, top } = parsed.panelPosition;
        if (
          typeof left === 'number' &&
          typeof top === 'number' &&
          !isNaN(left) &&
          !isNaN(top) &&
          left >= 0 &&
          top >= 0
        ) {
          config.panelPosition = { left, top };
        }
      }
    } catch (error) {
      console.error('[Timecode] Error loading settings:', error);
    }
  };

  /**
   * Save settings to localStorage with error handling
   * @returns {void}
   */
  const saveSettings = () => {
    try {
      const settingsToSave = {
        enabled: config.enabled,
        autoDetect: config.autoDetect,
        shortcut: config.shortcut,
        autoSave: config.autoSave,
        autoTrackPlayback: config.autoTrackPlayback,
        panelPosition: config.panelPosition,
        export: config.export,
      };
      localStorage.setItem(config.storageKey, JSON.stringify(settingsToSave));
    } catch (error) {
      console.error('[Timecode] Error saving settings:', error);
    }
  };

  /**
   * Clamp panel position within viewport bounds
   * @param {HTMLElement} panel - Panel element
   * @param {number} left - Desired left position
   * @param {number} top - Desired top position
   * @returns {{left: number, top: number}} Clamped position
   */
  const clampPanelPosition = (panel, left, top) => {
    try {
      if (!panel || !(panel instanceof HTMLElement)) {
        console.warn('[Timecode] Invalid panel element');
        return { left: 0, top: 0 };
      }

      // Validate input coordinates
      if (typeof left !== 'number' || typeof top !== 'number' || isNaN(left) || isNaN(top)) {
        console.warn('[Timecode] Invalid position coordinates');
        return { left: 0, top: 0 };
      }

      const rect = panel.getBoundingClientRect();
      const width = rect.width || panel.offsetWidth || 0;
      const height = rect.height || panel.offsetHeight || 0;

      const maxLeft = Math.max(0, window.innerWidth - width);
      const maxTop = Math.max(0, window.innerHeight - height);

      return {
        left: Math.min(Math.max(0, left), maxLeft),
        top: Math.min(Math.max(0, top), maxTop),
      };
    } catch (error) {
      console.error('[Timecode] Error clamping panel position:', error);
      return { left: 0, top: 0 };
    }
  };

  /**
   * Save panel position to settings
   * @param {number} left - Left position
   * @param {number} top - Top position
   * @returns {void}
   */
  const savePanelPosition = (left, top) => {
    try {
      if (typeof left !== 'number' || typeof top !== 'number' || isNaN(left) || isNaN(top)) {
        console.warn('[Timecode] Invalid position coordinates for saving');
        return;
      }
      config.panelPosition = { left, top };
      saveSettings();
    } catch (error) {
      console.error('[Timecode] Error saving panel position:', error);
    }
  };

  const applySavedPanelPosition = panel => {
    if (!panel || !config.panelPosition) return;

    requestAnimationFrame(() => {
      const { left, top } = clampPanelPosition(
        panel,
        config.panelPosition.left,
        config.panelPosition.top
      );
      panel.style.left = `${left}px`;
      panel.style.top = `${top}px`;
      panel.style.right = 'auto';
    });
  };

  const showNotification = (message, duration = 2000, type = 'info') => {
    YouTubeUtils.NotificationManager.show(message, { duration, type });
  };

  // Time utilities
  const formatTime = seconds => {
    if (isNaN(seconds)) return '00:00';
    seconds = Math.round(seconds);
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    const s = seconds % 60;
    return h > 0
      ? `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
      : `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
  };

  /**
   * Parse time string to seconds with validation
   * @param {string} timeStr - Time string (MM:SS or HH:MM:SS)
   * @returns {number|null} Seconds or null if invalid
   */
  const parseTime = timeStr => {
    try {
      if (!timeStr || typeof timeStr !== 'string') return null;

      const str = timeStr.trim();
      if (str.length === 0 || str.length > 12) return null; // Sanity check

      // Handle HH:MM:SS format
      let match = str.match(/^(\d+):(\d{1,2}):(\d{2})$/);
      if (match) {
        const [, h, m, s] = match.map(Number);
        // Validate ranges
        if (isNaN(h) || isNaN(m) || isNaN(s)) return null;
        if (m >= 60 || s >= 60 || h < 0 || m < 0 || s < 0) return null;
        const total = h * 3600 + m * 60 + s;
        // Sanity check: max 24 hours
        return total <= 86400 ? total : null;
      }

      // Handle MM:SS format
      match = str.match(/^(\d{1,2}):(\d{2})$/);
      if (match) {
        const [, m, s] = match.map(Number);
        // Validate ranges
        if (isNaN(m) || isNaN(s)) return null;
        if (m >= 60 || s >= 60 || m < 0 || s < 0) return null;
        return m * 60 + s;
      }

      return null;
    } catch (error) {
      console.error('[Timecode] Error parsing time:', error);
      return null;
    }
  };

  /**
   * Extract timecodes from text with validation
   * @param {string} text - Text containing timecodes
   * @returns {Array<{time: number, label: string, originalText: string}>} Extracted timecodes
   */
  const extractTimecodes = text => {
    try {
      if (!text || typeof text !== 'string') return [];

      // Security: limit text length to prevent DoS
      if (text.length > 50000) {
        console.warn('[Timecode] Text too long, truncating');
        text = text.substring(0, 50000);
      }

      const timecodes = [];
      const seen = new Set();
      const patterns = [
        /(\d{1,2}:\d{2}(?::\d{2})?)\s*[-–—]\s*(.+?)$/gm,
        /^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+?)$/gm,
        /(\d{1,2}:\d{2}(?::\d{2})?)\s*[-–—:]\s*([^\n\r]{1,100}?)(?=\s*\d{1,2}:\d{2}|\s*$)/g,
        /(\d{1,2}:\d{2}(?::\d{2})?)\s*[–—-]\s*([^\n]+)/gm,
        /^(\d{1,2}:\d{2}(?::\d{2})?)\s*(.+)$/gm,
      ];

      for (const pattern of patterns) {
        let match;
        let iterations = 0;
        const maxIterations = 1000; // Prevent infinite loops

        while ((match = pattern.exec(text)) !== null && iterations++ < maxIterations) {
          const time = parseTime(match[1]);
          if (time !== null && !seen.has(time)) {
            seen.add(time);
            // Sanitize label text
            let label = (match[2] || formatTime(time))
              .trim()
              .replace(/^\d+[\.\)]\s*/, '')
              .substring(0, 100); // Limit label length

            // Remove potentially dangerous characters
            label = label.replace(/[<>\"']/g, '');

            if (label) {
              timecodes.push({ time, label, originalText: match[1] });
            }
          }
        }

        if (iterations >= maxIterations) {
          console.warn('[Timecode] Maximum iterations reached during extraction');
        }
      }

      return timecodes.sort((a, b) => a.time - b.time);
    } catch (error) {
      console.error('[Timecode] Error extracting timecodes:', error);
      return [];
    }
  };

  const DESCRIPTION_SELECTORS = [
    '#description-inline-expander yt-attributed-string',
    '#description-inline-expander yt-formatted-string',
    '#description-inline-expander ytd-text-inline-expander',
    '#description-inline-expander .yt-core-attributed-string',
    '#description ytd-text-inline-expander',
    '#description ytd-expandable-video-description-body-renderer',
    '#description.ytd-watch-metadata yt-formatted-string',
    '#description.ytd-watch-metadata #description-inline-expander',
    '#tab-info ytd-expandable-video-description-body-renderer yt-formatted-string',
    '#tab-info ytd-expandable-video-description-body-renderer yt-attributed-string',
    '#structured-description ytd-text-inline-expander',
    '#structured-description yt-formatted-string',
    'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] yt-formatted-string',
    'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] yt-attributed-string',
    'ytd-watch-metadata #description',
    'ytd-watch-metadata #description-inline-expander',
    '#description',
  ];

  const DESCRIPTION_SELECTOR_COMBINED = DESCRIPTION_SELECTORS.join(',');

  const DESCRIPTION_EXPANDERS = [
    '#description-inline-expander yt-button-shape button',
    '#description-inline-expander tp-yt-paper-button#expand',
    '#description-inline-expander tp-yt-paper-button[aria-label]',
    'ytd-watch-metadata #description-inline-expander yt-button-shape button',
    'ytd-text-inline-expander[collapsed] yt-button-shape button',
    'ytd-text-inline-expander[collapsed] tp-yt-paper-button#expand',
    'ytd-expandable-video-description-body-renderer #expand',
    'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] #expand',
  ];

  const sleep = (ms = 250) => new Promise(resolve => setTimeout(resolve, ms));

  const collectDescriptionText = () => {
    const snippets = [];
    DESCRIPTION_SELECTORS.forEach(selector => {
      document.querySelectorAll(selector).forEach(node => {
        const text = node?.textContent?.trim();
        if (text) {
          snippets.push(text);
        }
      });
    });
    return snippets.join('\n');
  };

  const expandDescriptionIfNeeded = async () => {
    for (const selector of DESCRIPTION_EXPANDERS) {
      const button = document.querySelector(selector);
      if (!button) continue;

      const ariaExpanded = button.getAttribute('aria-expanded');
      if (ariaExpanded === 'true') return false;

      const ariaLabel = button.getAttribute('aria-label')?.toLowerCase();
      if (ariaLabel && ariaLabel.includes('less')) return false;

      if (button.offsetParent !== null) {
        try {
          /** @type {HTMLElement} */ (button).click();
          await sleep(400);
          return true;
        } catch (error) {
          console.warn('[Timecode] Failed to click expand button:', error);
        }
      }
    }

    const inlineExpander = document.querySelector('ytd-text-inline-expander[collapsed]');
    if (inlineExpander) {
      try {
        inlineExpander.removeAttribute('collapsed');
      } catch (error) {
        YouTubeUtils.logError('TimecodePanel', 'Failed to expand description', error);
      }
      await sleep(300);
      return true;
    }

    return false;
  };

  const ensureDescriptionReady = async () => {
    const initialText = collectDescriptionText();
    if (initialText) return;

    const maxAttempts = 3;
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      try {
        await YouTubeUtils.waitForElement(DESCRIPTION_SELECTOR_COMBINED, 1500);
      } catch {
        // Continue trying
      }

      await sleep(200);
      const expanded = await expandDescriptionIfNeeded();

      await sleep(expanded ? 500 : 200);
      const text = collectDescriptionText();

      if (text && text.length > initialText.length) {
        return;
      }
    }
  };
  const getCurrentVideoId = () => new URLSearchParams(window.location.search).get('v');

  // Detection
  const detectTimecodes = async (options = {}) => {
    const { force = false } = options;

    if (!config.enabled) return [];
    if (!force && !config.autoDetect) return [];

    const videoId = getCurrentVideoId();
    if (!videoId) return [];

    const cacheKey = `detect_${videoId}`;
    if (!force && state.timecodes.has(cacheKey)) {
      const cached = state.timecodes.get(cacheKey);
      if (Array.isArray(cached) && cached.length) {
        return cached;
      }
      state.timecodes.delete(cacheKey);
    }

    await ensureDescriptionReady();

    const uniqueMap = new Map();
    const descriptionText = collectDescriptionText();

    if (descriptionText) {
      const extracted = extractTimecodes(descriptionText);
      extracted.forEach(tc => {
        if (tc.time >= 0 && tc.label?.trim()) {
          uniqueMap.set(tc.time.toString(), tc);
        }
      });
    }

    // Get native chapters
    const chapters = getYouTubeChapters();

    chapters.forEach(chapter => {
      if (chapter.time >= 0 && chapter.label?.trim()) {
        uniqueMap.set(chapter.time.toString(), chapter);
      }
    });

    const result = Array.from(uniqueMap.values()).sort((a, b) => a.time - b.time);
    const hadExistingItems = state.dom.list?.childElementCount > 0;

    if (result.length > 0) {
      updateTimecodePanel(result);
      state.timecodes.set(cacheKey, result);
      if (config.autoSave) saveTimecodesToStorage(result);
    } else {
      if (force || !hadExistingItems) {
        updateTimecodePanel([]);
      }
      if (force) {
        state.timecodes.delete(cacheKey);
      }
    }

    return result;
  };

  const reloadTimecodes = async (buttonOverride = null) => {
    const button =
      buttonOverride || state.dom.reloadButton || document.getElementById('timecode-reload');

    if (state.isReloading || !config.enabled) return;

    state.isReloading = true;
    if (button) {
      button.disabled = true;
      button.classList.add('loading');
    }

    try {
      const result = await detectTimecodes({ force: true });

      if (Array.isArray(result) && result.length) {
        showNotification(t('foundTimecodes').replace('{count}', result.length));
      } else {
        updateTimecodePanel([]);
        showNotification(t('noTimecodesFound'));
      }
    } catch (error) {
      YouTubeUtils.logError('TimecodePanel', 'Reload failed', error);
      showNotification(t('reloadError'));
    } finally {
      if (button) {
        button.disabled = false;
        button.classList.remove('loading');
      }
      state.isReloading = false;
    }
  };

  const getYouTubeChapters = () => {
    // Расширенный поиск глав/эпизодов
    const selectors = [
      'ytd-macro-markers-list-item-renderer',
      'ytd-chapter-renderer',
      'ytd-engagement-panel-section-list-renderer[target-id*="description-chapters"] ytd-macro-markers-list-item-renderer',
      'ytd-engagement-panel-section-list-renderer[target-id*="description-chapters"] #details',
      '#structured-description ytd-horizontal-card-list-renderer ytd-macro-markers-list-item-renderer',
    ];

    const items = document.querySelectorAll(selectors.join(', '));
    const chapters = new Map();

    items.forEach(item => {
      // Попробуем разные способы извлечения времени и заголовка
      const timeSelectors = ['.time-info', '.timestamp', '#time', 'span[id*="time"]'];
      const titleSelectors = ['.marker-title', '.chapter-title', '#details', 'h4', '.title'];

      let timeText = null;
      for (const sel of timeSelectors) {
        const el = item.querySelector(sel);
        if (el?.textContent) {
          timeText = el.textContent;
          break;
        }
      }

      let titleText = null;
      for (const sel of titleSelectors) {
        const el = item.querySelector(sel);
        if (el?.textContent) {
          titleText = el.textContent;
          break;
        }
      }

      if (timeText) {
        const time = parseTime(timeText.trim());
        if (time !== null) {
          // Очищаем заголовок от лишних пробелов и переносов строк
          const cleanTitle = titleText?.trim().replace(/\s+/g, ' ') || formatTime(time);
          chapters.set(time.toString(), {
            time,
            label: cleanTitle,
            isChapter: true,
          });
        }
      }
    });
    const result = Array.from(chapters.values()).sort((a, b) => a.time - b.time);
    return result;
  };

  // Settings panel
  const addTimecodePanelSettings = () => {
    // ✅ Use cached querySelector
    const advancedSection = YouTubeUtils.querySelector(
      '.ytp-plus-settings-section[data-section="advanced"]'
    );
    if (!advancedSection || YouTubeUtils.querySelector('.timecode-settings-item')) return;

    const { ctrlKey, altKey, shiftKey } = config.shortcut;
    const modifierValue =
      [
        ctrlKey && altKey && shiftKey && 'ctrl+alt+shift',
        ctrlKey && altKey && 'ctrl+alt',
        ctrlKey && shiftKey && 'ctrl+shift',
        altKey && shiftKey && 'alt+shift',
        ctrlKey && 'ctrl',
        altKey && 'alt',
        shiftKey && 'shift',
      ].find(Boolean) || 'none';

    const enableDiv = document.createElement('div');
    enableDiv.className = 'ytp-plus-settings-item timecode-settings-item';
    enableDiv.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('enableTimecode')}</label>
          <div class="ytp-plus-settings-item-description">${t('enableDescription')}</div>
        </div>
        <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enabled" ${config.enabled ? 'checked' : ''}>
      `;

    const shortcutDiv = document.createElement('div');
    shortcutDiv.className = 'ytp-plus-settings-item timecode-settings-item timecode-shortcut-item';
    shortcutDiv.style.display = config.enabled ? 'flex' : 'none';
    shortcutDiv.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('keyboardShortcut')}</label>
          <div class="ytp-plus-settings-item-description">${t('shortcutDescription')}</div>
        </div>
        <div style="display: flex; align-items: center; gap: 8px;">
          <select id="timecode-modifier-combo" style="background: rgba(34, 34, 34, 0.6); color: white; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 4px;">
            ${[
              'none',
              'ctrl',
              'alt',
              'shift',
              'ctrl+alt',
              'ctrl+shift',
              'alt+shift',
              'ctrl+alt+shift',
            ]
              .map(
                v =>
                  `<option value="${v}" ${v === modifierValue ? 'selected' : ''}>${
                    v === 'none'
                      ? 'None'
                      : v
                          .split('+')
                          .map(k => k.charAt(0).toUpperCase() + k.slice(1))
                          .join('+')
                  }</option>`
              )
              .join('')}
          </select>
          <span>+</span>
          <input type="text" id="timecode-key" value="${config.shortcut.key}" maxlength="1" style="width: 30px; text-align: center; background: rgba(34, 34, 34, 0.6); color: white; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 4px;">
        </div>
      `;

    advancedSection.append(enableDiv, shortcutDiv);

    // Event listeners
    advancedSection.addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLElement} */ (e.target);
      if (target.matches && target.matches('.ytp-plus-settings-checkbox[data-setting="enabled"]')) {
        config.enabled = /** @type {HTMLInputElement} */ (target).checked;
        shortcutDiv.style.display = config.enabled ? 'flex' : 'none';
        toggleTimecodePanel(config.enabled);
        saveSettings();
      }
    });

    document.getElementById('timecode-modifier-combo')?.addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLSelectElement} */ (e.target);
      const value = target.value;
      config.shortcut.ctrlKey = value.includes('ctrl');
      config.shortcut.altKey = value.includes('alt');
      config.shortcut.shiftKey = value.includes('shift');
      saveSettings();
    });

    document.getElementById('timecode-key')?.addEventListener('input', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      if (target.value) {
        config.shortcut.key = target.value.toUpperCase();
        saveSettings();
      }
    });
  };

  // CSS
  const insertTimecodeStyles = () => {
    if (document.getElementById('timecode-panel-styles')) return;

    // ✅ Use StyleManager instead of createElement('style')
    const styles = `
        :root{--tc-panel-bg:rgba(255,255,255,0.06);--tc-panel-border:rgba(255,255,255,0.12);--tc-panel-color:#fff}
        html[dark],body[dark]{--tc-panel-bg:rgba(34,34,34,0.75);--tc-panel-border:rgba(255,255,255,0.12);--tc-panel-color:#fff}
        html:not([dark]){--tc-panel-bg:rgba(255,255,255,0.95);--tc-panel-border:rgba(0,0,0,0.08);--tc-panel-color:#222}
        #timecode-panel{position:fixed;right:20px;top:80px;background:var(--tc-panel-bg);border-radius:16px;box-shadow:0 12px 40px rgba(0,0,0,0.45);width:320px;max-height:70vh;z-index:10000;color:var(--tc-panel-color);backdrop-filter:blur(14px) saturate(140%);-webkit-backdrop-filter:blur(14px) saturate(140%);border:1.5px solid var(--tc-panel-border);transition:transform .28s cubic-bezier(.4,0,.2,1),opacity .28s;overflow:hidden;display:flex;flex-direction:column}
        #timecode-panel.hidden{transform:translateX(300px);opacity:0;pointer-events:none}
        #timecode-panel.auto-tracking{box-shadow:0 12px 48px rgba(255,0,0,0.12);border-color:rgba(255,0,0,0.25)}
        #timecode-header{display:flex;justify-content:space-between;align-items:center;padding:14px;border-bottom:1px solid rgba(255,255,255,0.04);background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);cursor:move}
                #timecode-title{font-weight:600;margin:0;font-size:15px;user-select:none;display:flex;align-items:center;gap:8px}
                #timecode-tracking-indicator{width:8px;height:8px;background:red;border-radius:50%;opacity:0;transition:opacity .3s}
                #timecode-panel.auto-tracking #timecode-tracking-indicator{opacity:1}
                #timecode-current-time{font-family:monospace;font-size:12px;padding:2px 6px;background:rgba(255,0,0,.3);border-radius:3px;margin-left:auto}
                #timecode-header-controls{display:flex;align-items:center;gap:6px}
                #timecode-reload,#timecode-close{background:transparent;border:none;color:inherit;cursor:pointer;width:28px;height:28px;padding:0;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:background .18s,color .18s}
                #timecode-reload:hover,#timecode-close:hover{background:rgba(255,255,255,0.04)}
                #timecode-reload.loading{animation:timecode-spin .8s linear infinite}
                #timecode-list{overflow-y:auto;padding:8px 0;max-height:calc(70vh - 80px);scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}
                #timecode-list::-webkit-scrollbar{width:6px}
                #timecode-list::-webkit-scrollbar-thumb{background:rgba(255,255,255,.3);border-radius:3px}
                .timecode-item{padding:10px 14px;display:flex;align-items:center;cursor:pointer;transition:background-color .16s,transform .12s;border-left:3px solid transparent;position:relative;border-radius:8px;margin:6px 10px}
                .timecode-item:hover{background:rgba(255,255,255,0.04);transform:translateY(-2px)}
                .timecode-item:hover .timecode-actions{opacity:1}
                .timecode-item.active{background:linear-gradient(90deg, rgba(255,68,68,0.12), rgba(255,68,68,0.04));border-left-color:#ff6666;box-shadow:inset 0 0 0 1px rgba(255,68,68,0.03)}
                .timecode-item.active.pulse{animation:pulse .8s ease-out}
                .timecode-item.editing{background:linear-gradient(90deg, rgba(255,170,0,0.08), rgba(255,170,0,0.03));border-left-color:#ffaa00}
                .timecode-item.editing .timecode-actions{opacity:1}
                @keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.02)}100%{transform:scale(1)}}
                @keyframes timecode-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
                .timecode-time{font-family:monospace;margin-right:10px;color:rgba(255,255,255,.8);font-size:13px;min-width:45px}
                .timecode-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:13px;flex:1}
                .timecode-item.has-chapter .timecode-time{color:#ff4444}
                .timecode-progress{width:0;height:2px;background:#ff4444;position:absolute;bottom:0;left:0;transition:width .3s;opacity:.8}
                .timecode-actions{position:absolute;right:8px;top:50%;transform:translateY(-50%);display:flex;gap:4px;opacity:0;transition:opacity .2s;background:rgba(0,0,0,.8);border-radius:4px;padding:2px}
                .timecode-action{background:none;border:none;color:rgba(255,255,255,.8);cursor:pointer;padding:4px;font-size:12px;border-radius:2px;transition:color .2s,background-color .2s}
                .timecode-action:hover{color:#fff;background:rgba(255,255,255,.2)}
                .timecode-action.edit:hover{color:#ffaa00}
                .timecode-action.delete:hover{color:#ff4444}
                #timecode-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center;color:rgba(255,255,255,.7);font-size:13px}
                #timecode-form{padding:12px;border-top:1px solid rgba(255,255,255,.04);display:none}
                #timecode-form.visible{display:block}
                #timecode-form input{width:100%;margin-bottom:8px;padding:8px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.2);border-radius:4px;color:#fff;font-size:13px}
                #timecode-form input::placeholder{color:rgba(255,255,255,.6)}
                #timecode-form-buttons{display:flex;gap:8px;justify-content:flex-end}
                #timecode-form-buttons button{padding:6px 12px;border:none;border-radius:4px;cursor:pointer;font-size:12px;transition:background-color .2s}
                #timecode-form-cancel{background:rgba(255,255,255,.2);color:#fff}
                #timecode-form-cancel:hover{background:rgba(255,255,255,.3)}
                #timecode-form-save{background:#ff4444;color:#fff}
                #timecode-form-save:hover{background:#ff6666}
        #timecode-actions{padding:10px;border-top:1px solid rgba(255,255,255,.04);display:flex;gap:8px;background:linear-gradient(180deg,transparent,rgba(0,0,0,0.03))}
        #timecode-actions button{padding:8px 12px;border:none;border-radius:8px;cursor:pointer;font-size:13px;transition:background .18s;color:inherit;background:rgba(255,255,255,0.02)}
        #timecode-actions button:hover{background:rgba(255,255,255,0.04)}
        #timecode-track-toggle.active{background:linear-gradient(90deg,#ff6b6b,#ff4444);color:#fff}
            `;
    YouTubeUtils.StyleManager.add('timecode-panel-styles', styles);
  };

  // Panel creation
  const createTimecodePanel = () => {
    if (state.dom.panel) return state.dom.panel;

    // Remove any existing panels (for redundancy)
    document.querySelectorAll('#timecode-panel').forEach(p => p.remove());

    const panel = document.createElement('div');
    panel.id = 'timecode-panel';
    panel.className = config.enabled ? '' : 'hidden';
    if (config.autoTrackPlayback) panel.classList.add('auto-tracking');

    panel.innerHTML = `
        <div id="timecode-header">
          <h3 id="timecode-title">
            <div id="timecode-tracking-indicator"></div>
            ${t('timecodes')}
            <span id="timecode-current-time"></span>
          </h3>
          <div id="timecode-header-controls">
            <button id="timecode-reload" title="${t('reload')}" aria-label="${t('reload')}">⟳</button>
            <button id="timecode-close" title="${t('close')}" aria-label="${t('close')}">
              <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
              </svg>
            </button>
          </div>
        </div>
        <div id="timecode-list"></div>
        <div id="timecode-empty">
          <div>${t('noTimecodesFound')}</div>
          <div style="margin-top:5px;font-size:12px">${t('clickToAdd')}</div>
        </div>
        <div id="timecode-form">
          <input type="text" id="timecode-form-time" placeholder="${t('timePlaceholder')}">
          <input type="text" id="timecode-form-label" placeholder="${t('labelPlaceholder')}">
          <div id="timecode-form-buttons">
            <button type="button" id="timecode-form-cancel">${t('cancel')}</button>
            <button type="button" id="timecode-form-save" class="save">${t('save')}</button>
          </div>
        </div>
        <div id="timecode-actions">
          <button id="timecode-add-btn">${t('add')}</button>
          <button id="timecode-export-btn" ${config.export ? '' : 'style="display:none"'}>${t('export')}</button>
          <button id="timecode-track-toggle" class="${config.autoTrackPlayback ? 'active' : ''}">${config.autoTrackPlayback ? t('tracking') : t('track')}</button>
        </div>
      `;

    // Cache DOM elements
    state.dom = {
      panel,
      list: panel.querySelector('#timecode-list'),
      empty: panel.querySelector('#timecode-empty'),
      form: panel.querySelector('#timecode-form'),
      timeInput: panel.querySelector('#timecode-form-time'),
      labelInput: panel.querySelector('#timecode-form-label'),
      currentTime: panel.querySelector('#timecode-current-time'),
      trackToggle: panel.querySelector('#timecode-track-toggle'),
      reloadButton: panel.querySelector('#timecode-reload'),
    };

    // Event delegation
    panel.addEventListener('click', handlePanelClick);
    makeDraggable(panel);

    document.body.appendChild(panel);
    applySavedPanelPosition(panel);
    return panel;
  };

  // Event handling
  const handlePanelClick = e => {
    const { target } = e;
    const item = target.closest('.timecode-item');

    // Use closest so clicks on child SVG/path elements are detected
    const reloadButton = target.closest
      ? target.closest('#timecode-reload')
      : target.id === 'timecode-reload'
        ? target
        : null;
    if (reloadButton) {
      e.preventDefault();
      reloadTimecodes(reloadButton);
      return;
    }

    const closeButton = target.closest
      ? target.closest('#timecode-close')
      : target.id === 'timecode-close'
        ? target
        : null;
    if (closeButton) {
      toggleTimecodePanel(false);
    } else if (target.id === 'timecode-add-btn') {
      // ✅ Use cached querySelector
      const video = YouTubeUtils.querySelector('video');
      if (video) showTimecodeForm(video.currentTime);
    } else if (target.id === 'timecode-track-toggle') {
      config.autoTrackPlayback = !config.autoTrackPlayback;
      target.textContent = config.autoTrackPlayback ? t('tracking') : t('track');
      target.classList.toggle('active', config.autoTrackPlayback);
      state.dom.panel.classList.toggle('auto-tracking', config.autoTrackPlayback);
      saveSettings();
      if (config.autoTrackPlayback) startTracking();
    } else if (target.id === 'timecode-export-btn') {
      exportTimecodes();
    } else if (target.id === 'timecode-form-cancel') {
      hideTimecodeForm();
    } else if (target.id === 'timecode-form-save') {
      saveTimecodeForm();
    } else if (target.classList.contains('timecode-action')) {
      e.stopPropagation();
      const action = target.dataset.action;
      const index = parseInt(target.closest('.timecode-item').dataset.index);

      if (action === 'edit') {
        editTimecode(index);
      } else if (action === 'delete') {
        deleteTimecode(index);
      }
    } else if (item && !target.closest('.timecode-actions')) {
      const time = parseFloat(item.dataset.time);
      const video = document.querySelector('video');
      if (video && !isNaN(time)) {
        /** @type {HTMLVideoElement} */ (video).currentTime = time;
        if (video.paused) video.play();
        updateActiveItem(item);
      }
    }
  };

  // Edit timecode
  const editTimecode = index => {
    const timecodes = getCurrentTimecodes();
    if (index < 0 || index >= timecodes.length) return;

    const timecode = timecodes[index];
    state.editingIndex = index;

    // Update item appearance
    const item = state.dom.list.querySelector(`.timecode-item[data-index="${index}"]`);
    if (item) {
      item.classList.add('editing');
      // Hide other editing items
      state.dom.list.querySelectorAll('.timecode-item.editing').forEach(el => {
        if (el !== item) el.classList.remove('editing');
      });
    }

    showTimecodeForm(timecode.time, timecode.label);
  };

  // Delete timecode
  const deleteTimecode = index => {
    const timecodes = getCurrentTimecodes();
    if (index < 0 || index >= timecodes.length) return;

    const timecode = timecodes[index];

    // Don't allow deletion of native YouTube chapters
    if (timecode.isChapter && !timecode.isUserAdded) {
      showNotification(t('cannotDeleteChapter'));
      return;
    }

    // Confirm deletion
    if (!confirm(t('confirmDelete').replace('{label}', timecode.label))) return;

    timecodes.splice(index, 1);
    updateTimecodePanel(timecodes);
    saveTimecodesToStorage(timecodes);
    showNotification(t('timecodeDeleted'));
  };

  // Form handling
  const showTimecodeForm = (currentTime, existingLabel = '') => {
    const { form, timeInput, labelInput } = state.dom;
    form.classList.add('visible');
    timeInput.value = formatTime(currentTime);
    labelInput.value = existingLabel;
    requestAnimationFrame(() => labelInput.focus());
  };

  const hideTimecodeForm = () => {
    state.dom.form.classList.remove('visible');
    state.editingIndex = null;
    // Remove editing class from all items
    state.dom.list?.querySelectorAll('.timecode-item.editing').forEach(el => {
      el.classList.remove('editing');
    });
  };

  const saveTimecodeForm = () => {
    const { timeInput, labelInput } = state.dom;
    const timeValue = timeInput.value.trim();
    const labelValue = labelInput.value.trim();

    const time = parseTime(timeValue);
    if (time === null) {
      showNotification(t('invalidTimeFormat'));
      return;
    }

    const timecodes = getCurrentTimecodes();
    const newTimecode = {
      time,
      label: labelValue || formatTime(time),
      isUserAdded: true,
      isChapter: false,
    };

    if (state.editingIndex !== null) {
      // Editing existing timecode
      const oldTimecode = timecodes[state.editingIndex];
      if (oldTimecode.isChapter && !oldTimecode.isUserAdded) {
        showNotification(t('cannotEditChapter'));
        hideTimecodeForm();
        return;
      }

      timecodes[state.editingIndex] = { ...oldTimecode, ...newTimecode };
      showNotification(t('timecodeUpdated'));
    } else {
      // Adding new timecode
      timecodes.push(newTimecode);
      showNotification(t('timecodeAdded'));
    }

    const sorted = timecodes.sort((a, b) => a.time - b.time);
    updateTimecodePanel(sorted);
    saveTimecodesToStorage(sorted);
    hideTimecodeForm();
  };

  // Export
  const exportTimecodes = () => {
    const timecodes = getCurrentTimecodes();
    if (!timecodes.length) {
      showNotification(t('noTimecodesToExport'));
      return;
    }

    const exportBtn = state.dom.panel?.querySelector('#timecode-export-btn');
    if (exportBtn) {
      exportBtn.textContent = t('copied');
      exportBtn.style.backgroundColor = 'rgba(0,220,0,0.8)';
      setTimeout(() => {
        exportBtn.textContent = t('export');
        exportBtn.style.backgroundColor = '';
      }, 2000);
    }

    const videoTitle = document.title.replace(/\s-\sYouTube$/, '');
    let content = `${videoTitle}\n\nTimecodes:\n`;
    timecodes.forEach(tc => (content += `${formatTime(tc.time)} - ${tc.label}\n`));

    if (navigator.clipboard?.writeText) {
      navigator.clipboard.writeText(content).then(() => {
        showNotification(t('timecodesCopied'));
      });
    }
  };

  // Panel updates
  const updateTimecodePanel = timecodes => {
    const { list, empty } = state.dom;
    if (!list || !empty) return;

    const isEmpty = !timecodes.length;
    empty.style.display = isEmpty ? 'flex' : 'none';
    list.style.display = isEmpty ? 'none' : 'block';

    if (isEmpty) {
      list.innerHTML = '';
      return;
    }

    list.innerHTML = timecodes
      .map((tc, i) => {
        const timeStr = formatTime(tc.time);
        const label = (tc.label?.trim() || timeStr).replace(
          /[<>&"']/g,
          c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&#39;' })[c]
        );
        const isEditable = !tc.isChapter || tc.isUserAdded;

        return `
          <div class="timecode-item ${tc.isChapter ? 'has-chapter' : ''}" data-time="${tc.time}" data-index="${i}">
            <div class="timecode-time">${timeStr}</div>
            <div class="timecode-label" title="${label}">${label}</div>
            <div class="timecode-progress"></div>
            ${
              isEditable
                ? `
              <div class="timecode-actions">
                <button class="timecode-action edit" data-action="edit" title="${t('edit')}">✎</button>
                <button class="timecode-action delete" data-action="delete" title="${t('delete')}">✕</button>
              </div>
            `
                : ''
            }
          </div>
        `;
      })
      .join('');
  };

  const updateActiveItem = activeItem => {
    const items = state.dom.list?.querySelectorAll('.timecode-item');
    if (!items) return;

    items.forEach(item => item.classList.remove('active', 'pulse'));
    if (activeItem) {
      activeItem.classList.add('active', 'pulse');
      setTimeout(() => activeItem.classList.remove('pulse'), 800);
    }
  };

  // Tracking
  const startTracking = () => {
    if (state.trackingId) return;

    const track = () => {
      try {
        const video = document.querySelector('video');
        const { panel, currentTime, list } = state.dom;

        // Stop tracking if essential elements are missing or panel is hidden
        if (!video || !panel || panel.classList.contains('hidden') || !config.autoTrackPlayback) {
          if (state.trackingId) {
            cancelAnimationFrame(state.trackingId);
            state.trackingId = 0;
          }
          return;
        }

        // Update current time display
        if (currentTime && !isNaN(video.currentTime)) {
          currentTime.textContent = formatTime(video.currentTime);
        }

        // Update active item
        const items = list?.querySelectorAll('.timecode-item');
        if (items?.length) {
          let activeIndex = -1;
          let nextIndex = -1;

          for (let i = 0; i < items.length; i++) {
            const timeData = items[i].dataset.time;
            if (!timeData) continue;

            const time = parseFloat(timeData);
            if (isNaN(time)) continue;

            if (video.currentTime >= time) {
              activeIndex = i;
            } else if (nextIndex === -1) {
              nextIndex = i;
            }
          }

          // Update active state
          if (state.activeIndex !== activeIndex) {
            // Remove previous active state
            if (state.activeIndex !== null && state.activeIndex >= 0 && items[state.activeIndex]) {
              items[state.activeIndex].classList.remove('active');
            }

            // Set new active state
            if (activeIndex >= 0 && items[activeIndex]) {
              items[activeIndex].classList.add('active');
              try {
                items[activeIndex].scrollIntoView({ behavior: 'smooth', block: 'center' });
              } catch {
                // Fallback for browsers that don't support smooth scrolling
                items[activeIndex].scrollIntoView(false);
              }
            }

            state.activeIndex = activeIndex;
          }

          // Update progress bar
          if (activeIndex >= 0 && nextIndex >= 0 && items[activeIndex]) {
            const currentTimeData = items[activeIndex].dataset.time;
            const nextTimeData = items[nextIndex].dataset.time;

            if (currentTimeData && nextTimeData) {
              const current = parseFloat(currentTimeData);
              const next = parseFloat(nextTimeData);

              if (!isNaN(current) && !isNaN(next) && next > current) {
                const progress = ((video.currentTime - current) / (next - current)) * 100;
                const progressEl = items[activeIndex].querySelector('.timecode-progress');
                if (progressEl) {
                  const clampedProgress = Math.min(100, Math.max(0, progress));
                  progressEl.style.width = `${clampedProgress}%`;
                }
              }
            }
          }
        }

        // Continue tracking if enabled
        if (config.autoTrackPlayback) {
          state.trackingId = requestAnimationFrame(track);
        }
      } catch (error) {
        console.warn('Timecode tracking error:', error);
        // Stop tracking on error to prevent infinite error loops
        if (state.trackingId) {
          cancelAnimationFrame(state.trackingId);
          state.trackingId = 0;
        }
      }
    };

    state.trackingId = requestAnimationFrame(track);
  };

  // Stop tracking function
  const stopTracking = () => {
    if (state.trackingId) {
      cancelAnimationFrame(state.trackingId);
      state.trackingId = 0;
    }
  };

  // Drag functionality
  const makeDraggable = panel => {
    const header = panel.querySelector('#timecode-header');
    if (!header) return;

    let startX, startY, startLeft, startTop;

    const mouseDownHandler = e => {
      if (e.button !== 0) return;

      state.dragging = true;
      startX = e.clientX;
      startY = e.clientY;

      const rect = panel.getBoundingClientRect();

      if (!panel.style.left) {
        panel.style.left = `${rect.left}px`;
      }
      if (!panel.style.top) {
        panel.style.top = `${rect.top}px`;
      }

      panel.style.right = 'auto';

      startLeft = parseFloat(panel.style.left) || rect.left;
      startTop = parseFloat(panel.style.top) || rect.top;

      const handleMove = event => {
        if (!state.dragging) return;

        const deltaX = event.clientX - startX;
        const deltaY = event.clientY - startY;
        const { left, top } = clampPanelPosition(panel, startLeft + deltaX, startTop + deltaY);

        panel.style.left = `${left}px`;
        panel.style.top = `${top}px`;
        panel.style.right = 'auto';
      };

      const handleUp = () => {
        if (!state.dragging) return;

        state.dragging = false;
        document.removeEventListener('mousemove', handleMove);
        document.removeEventListener('mouseup', handleUp);

        const rectAfter = panel.getBoundingClientRect();
        const { left, top } = clampPanelPosition(panel, rectAfter.left, rectAfter.top);

        panel.style.left = `${left}px`;
        panel.style.top = `${top}px`;
        panel.style.right = 'auto';

        savePanelPosition(left, top);
      };

      document.addEventListener('mousemove', handleMove);
      document.addEventListener('mouseup', handleUp);
    };

    // ✅ Register the mousedown listener for cleanup
    YouTubeUtils.cleanupManager.registerListener(header, 'mousedown', mouseDownHandler);
  };

  // Storage
  const saveTimecodesToStorage = timecodes => {
    const videoId = new URLSearchParams(window.location.search).get('v');
    if (!videoId) return;

    try {
      const minimal = timecodes.map(tc => ({
        t: tc.time,
        l: tc.label?.trim() || formatTime(tc.time),
        c: tc.isChapter || false,
        u: tc.isUserAdded || false,
      }));
      localStorage.setItem(`yt_tc_${videoId}`, JSON.stringify(minimal));
    } catch {}
  };

  const loadTimecodesFromStorage = () => {
    const videoId = new URLSearchParams(window.location.search).get('v');
    if (!videoId) return null;

    try {
      const data = localStorage.getItem(`yt_tc_${videoId}`);
      return data
        ? JSON.parse(data)
            .map(tc => ({
              time: tc.t,
              label: tc.l,
              isChapter: tc.c,
              isUserAdded: tc.u || false,
            }))
            .sort((a, b) => a.time - b.time)
        : null;
    } catch {
      return null;
    }
  };

  const getCurrentTimecodes = () => {
    const items = state.dom.list?.querySelectorAll('.timecode-item');
    if (!items) return [];

    return Array.from(items)
      .map(item => ({
        time: parseFloat(item.dataset.time),
        label:
          item.querySelector('.timecode-label')?.textContent ||
          formatTime(parseFloat(item.dataset.time)),
        isChapter: item.classList.contains('has-chapter'),
        isUserAdded: !item.classList.contains('has-chapter') || false,
      }))
      .sort((a, b) => a.time - b.time);
  };

  // Toggle panel
  const toggleTimecodePanel = show => {
    // Close any existing panels first (cleanup)
    document.querySelectorAll('#timecode-panel').forEach(panel => {
      if (panel !== state.dom.panel) panel.remove();
    });

    const panel = state.dom.panel || createTimecodePanel();
    if (show === undefined) show = panel.classList.contains('hidden');

    panel.classList.toggle('hidden', !show);

    if (show) {
      applySavedPanelPosition(panel);

      const saved = loadTimecodesFromStorage();
      if (saved?.length) {
        updateTimecodePanel(saved);
      } else if (config.autoDetect) {
        detectTimecodes().catch(err => console.error('[Timecode] Detection failed:', err));
      }

      if (config.autoTrackPlayback) startTracking();
    } else if (state.trackingId) {
      cancelAnimationFrame(state.trackingId);
      state.trackingId = 0;
    }
  };

  // Navigation handling
  const setupNavigation = () => {
    let currentVideoId = new URLSearchParams(window.location.search).get('v');

    const handleNavigationChange = () => {
      const newVideoId = new URLSearchParams(window.location.search).get('v');
      if (newVideoId === currentVideoId || window.location.pathname !== '/watch') return;

      currentVideoId = newVideoId;
      state.activeIndex = null;
      state.editingIndex = null;
      state.timecodes.clear();

      // ✅ Обновляем панель только если она уже открыта
      if (config.enabled && state.dom.panel && !state.dom.panel.classList.contains('hidden')) {
        const saved = loadTimecodesFromStorage();
        if (saved?.length) {
          updateTimecodePanel(saved);
        } else if (config.autoDetect) {
          setTimeout(
            () =>
              detectTimecodes().catch(err => console.error('[Timecode] Detection failed:', err)),
            500
          );
        }
        if (config.autoTrackPlayback) startTracking();
      }
    };

    document.addEventListener('yt-navigate-finish', handleNavigationChange);

    // Also watch for URL changes using MutationObserver as a fallback
    const observer = new MutationObserver(() => {
      const newVideoId = new URLSearchParams(window.location.search).get('v');
      if (newVideoId !== currentVideoId) {
        handleNavigationChange();
      }
    });

    // ✅ Register observer in cleanupManager
    YouTubeUtils.cleanupManager.registerObserver(observer);

    // ✅ Safe observe with document.body check
    if (document.body) {
      observer.observe(document.body, { subtree: true, childList: true });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, { subtree: true, childList: true });
      });
    }
  };

  // Keyboard shortcuts
  const setupKeyboard = () => {
    document.addEventListener('keydown', e => {
      // ✅ Проверяем, включена ли функция в настройках
      if (!config.enabled) return;

      const target = /** @type {EventTarget & HTMLElement} */ (e.target);
      if (target.matches && target.matches('input, textarea, [contenteditable]')) return;

      const { key, shiftKey, altKey, ctrlKey } = config.shortcut;
      if (
        e.key.toUpperCase() === key &&
        e.shiftKey === shiftKey &&
        e.altKey === altKey &&
        e.ctrlKey === ctrlKey
      ) {
        e.preventDefault();
        toggleTimecodePanel();
      }
    });
  };

  // Cleanup on unload
  const cleanup = () => {
    stopTracking();
    if (state.dom.panel) {
      state.dom.panel.remove();
      state.dom.panel = null;
    }
  };

  // Initialize
  const init = () => {
    if (initStarted) return;

    const appRoot =
      (typeof YouTubeUtils?.querySelector === 'function' &&
        YouTubeUtils.querySelector('ytd-app')) ||
      document.querySelector('ytd-app');

    if (!appRoot) {
      scheduleInitRetry();
      return;
    }

    initStarted = true;

    loadSettings();
    insertTimecodeStyles();
    setupKeyboard();
    setupNavigation();

    // Settings modal observer
    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
            setTimeout(addTimecodePanelSettings, 100);
            return;
          }
        }
      }

      if (
        document.querySelector(
          '.ytp-plus-settings-section[data-section="advanced"]:not(.hidden)'
        ) &&
        !document.querySelector('.timecode-settings-item')
      ) {
        setTimeout(addTimecodePanelSettings, 50);
      }
    });

    // ✅ Register observer in cleanupManager
    YouTubeUtils.cleanupManager.registerObserver(observer);

    // ✅ Safe observe with document.body check
    if (document.body) {
      observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['class'],
      });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, {
          childList: true,
          subtree: true,
          attributes: true,
          attributeFilter: ['class'],
        });
      });
    }

    // ✅ Register global click listener in cleanupManager
    const clickHandler = e => {
      if (
        /** @type {HTMLElement} */ (e.target).classList?.contains('ytp-plus-settings-nav-item') &&
        /** @type {HTMLElement} */ (e.target).dataset.section === 'advanced'
      ) {
        setTimeout(addTimecodePanelSettings, 50);
      }
    };
    YouTubeUtils.cleanupManager.registerListener(document, 'click', clickHandler, true);

    // ✅ Больше не создаём панель автоматически - только по шорткату
    if (config.enabled && !state.resizeListenerKey) {
      const onResize = YouTubeUtils.throttle(() => {
        if (!state.dom.panel) return;

        const rect = state.dom.panel.getBoundingClientRect();
        const { left, top } = clampPanelPosition(state.dom.panel, rect.left, rect.top);

        state.dom.panel.style.left = `${left}px`;
        state.dom.panel.style.top = `${top}px`;
        state.dom.panel.style.right = 'auto';

        savePanelPosition(left, top);
      }, 200);

      state.resizeListenerKey = YouTubeUtils.cleanupManager.registerListener(
        window,
        'resize',
        onResize
      );
    }
  };

  // Start on document ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init, { once: true });
  } else {
    init();
  }

  // Cleanup on beforeunload
  window.addEventListener('beforeunload', cleanup);
})();

// --- MODULE: playlist-search.js ---

// Playlist Search
(function () {
  'use strict';

  // Prevent multiple initializations
  if (window._playlistSearchInitialized) return;
  window._playlistSearchInitialized = true;

  // Localization
  const i18n = {
    en: {
      searchPlaceholder: 'Search in {playlist}...',
    },
    ru: {
      searchPlaceholder: 'Поиск в плейлисте "{playlist}"...',
    },
  };

  // Detect language
  const getLanguage = () => {
    const htmlLang = document.documentElement.lang || 'en';
    if (htmlLang.startsWith('ru')) return 'ru';
    return 'en';
  };

  const lang = getLanguage();
  const t = key => i18n[lang][key] || i18n.en[key] || key;

  // Utility functions for performance optimization
  const debounce = (func, wait) => {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  };

  const throttle = (func, limit) => {
    let inThrottle;
    return function executedFunction(...args) {
      if (!inThrottle) {
        func(...args);
        inThrottle = true;
        setTimeout(() => (inThrottle = false), limit);
      }
    };
  };

  // Previously limited to specific lists (LL/WL). Now support any playlist id.

  const config = {
    enabled: true,
    storageKey: 'youtube_playlist_search_settings',
    searchDebounceMs: 200, // Debounce search input
    observerThrottleMs: 500, // Throttle mutation observer
    maxPlaylistItems: 5000, // Maximum items to process
    maxQueryLength: 200, // Maximum search query length
  };

  const state = {
    searchInput: null,
    searchResults: null,
    originalItems: [],
    currentPlaylistId: null,
    mutationObserver: null,
    rafId: null,
    itemsCache: new Map(), // Cache for faster lookups
  };

  // Load settings from localStorage
  const loadSettings = () => {
    try {
      const saved = localStorage.getItem(config.storageKey);
      if (saved) Object.assign(config, JSON.parse(saved));
    } catch (error) {
      console.warn('[Playlist Search] Failed to load settings:', error);
    }
  };

  // (saveSettings removed - settings are static for this module)

  /**
   * Get current playlist id with validation
   * @returns {string|null} Valid playlist ID or null
   */
  const getCurrentPlaylistId = () => {
    try {
      const urlParams = new URLSearchParams(window.location.search);
      const listId = urlParams.get('list');

      // Validate playlist ID format (alphanumeric, dashes, underscores)
      if (listId && /^[a-zA-Z0-9_-]+$/.test(listId)) {
        return listId;
      }

      return null;
    } catch (error) {
      console.warn('[Playlist Search] Failed to get playlist ID:', error);
      return null;
    }
  };

  /**
   * Try to obtain a display name for the current playlist from DOM
   * @param {Element|HTMLElement} playlistPanel - Playlist panel element
   * @param {string} listId - Playlist ID
   * @returns {string} Playlist display name
   */
  const getPlaylistDisplayName = (playlistPanel, listId) => {
    try {
      // Common places for title: .title, h3 a, #header-title, #title
      const sel = ['.title', 'h3 a', '#header-title', '#title', '.playlist-title', 'h1.title'];
      for (const s of sel) {
        const el = playlistPanel.querySelector(s) || document.querySelector(s);
        if (el && el.textContent && el.textContent.trim()) {
          // Sanitize and limit length
          const title = el.textContent.trim();
          return title.length > 100 ? title.substring(0, 100) + '...' : title;
        }
      }

      // Fallback to meta or channel-specific metadata
      const meta =
        document.querySelector('meta[name="title"]') ||
        document.querySelector('meta[property="og:title"]');
      if (meta && meta.content) {
        const title = meta.content.trim();
        return title.length > 100 ? title.substring(0, 100) + '...' : title;
      }
    } catch (error) {
      console.warn('[Playlist Search] Failed to get display name:', error);
    }

    // Default to sanitized id if nothing else
    if (listId && typeof listId === 'string') {
      return listId.substring(0, 50); // Limit length
    }

    return 'playlist';
  };

  // Add search UI to playlist panel
  const addSearchUI = () => {
    if (!config.enabled) return;

    const playlistId = getCurrentPlaylistId();
    if (!playlistId) return;

    // Find playlist panel (works both on /watch and on playlist pages)
    const playlistPanel = document.querySelector('ytd-playlist-panel-renderer');
    if (!playlistPanel) {
      // Use MutationObserver instead of setTimeout for better performance
      const observer = new MutationObserver((_mutations, obs) => {
        const panel = document.querySelector('ytd-playlist-panel-renderer');
        if (panel) {
          obs.disconnect();
          addSearchUI();
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });

      // Timeout fallback to prevent infinite observation
      setTimeout(() => observer.disconnect(), 5000);
      return;
    }

    // Don't add search UI twice
    if (playlistPanel.querySelector('.ytplus-playlist-search')) return;

    state.currentPlaylistId = playlistId;

    // Create search container
    const searchContainer = document.createElement('div');
    searchContainer.className = 'ytplus-playlist-search';
    searchContainer.style.cssText = `
      padding: 8px 16px;
      background: transparent;
      border-bottom: 1px solid var(--yt-spec-10-percent-layer);
      position: sticky;
      top: 0;
      z-index: 1;
    `;

    const searchInput = document.createElement('input');
    searchInput.type = 'text';
    const playlistName = getPlaylistDisplayName(playlistPanel, playlistId);
    searchInput.placeholder = t('searchPlaceholder').replace('{playlist}', playlistName);
    searchInput.className = 'ytplus-playlist-search-input';
    searchInput.style.cssText = `
      width: 93%;
      padding: 8px 16px;
      border: 1px solid var(--yt-spec-10-percent-layer);
      border-radius: 20px;
      background: var(--yt-spec-badge-chip-background);
      color: var(--yt-spec-text-primary);
      font-size: 14px;
      font-family: 'Roboto', Arial, sans-serif;
      outline: none;
      transition: border-color 0.2s;
    `;

    searchInput.addEventListener('focus', () => {
      searchInput.style.borderColor = 'var(--yt-spec-call-to-action)';
    });

    searchInput.addEventListener('blur', () => {
      searchInput.style.borderColor = 'var(--yt-spec-10-percent-layer)';
    });

    // Use debounced filtering for better performance
    const debouncedFilter = debounce(value => {
      filterPlaylistItems(value);
    }, config.searchDebounceMs);

    searchInput.addEventListener('input', e => {
      const target = /** @type {HTMLInputElement} */ (e.target);
      debouncedFilter(target.value);
    });

    searchContainer.appendChild(searchInput);
    state.searchInput = searchInput;

    // Try to insert the search UI into the playlist items container so it appears
    // inline with the list of videos. Prefer inserting before the first
    // ytd-playlist-panel-video-renderer if present.
    /** @type {Element|null} */
    const rawItemsContainer =
      playlistPanel.querySelector('.playlist-items.style-scope.ytd-playlist-panel-renderer') ||
      playlistPanel.querySelector('.playlist-items') ||
      playlistPanel.querySelector('#items');

    if (rawItemsContainer) {
      /** @type {HTMLElement} */
      const itemsContainer = /** @type {HTMLElement} */ (
        /** @type {unknown} */ (rawItemsContainer)
      );
      /** @type {Element|null} */
      const firstVideo = itemsContainer.querySelector('ytd-playlist-panel-video-renderer');
      if (firstVideo && firstVideo.parentElement === itemsContainer) {
        itemsContainer.insertBefore(searchContainer, /** @type {Node} */ (firstVideo));
      } else {
        // Append to items container if no video element found
        itemsContainer.appendChild(searchContainer);
      }
    } else {
      // Fallback: prepend to the panel root to ensure visibility
      if (playlistPanel.firstChild) {
        playlistPanel.insertBefore(searchContainer, playlistPanel.firstChild);
      } else {
        playlistPanel.appendChild(searchContainer);
      }
    }

    // Store original items
    collectOriginalItems();

    // Setup MutationObserver to watch for new playlist items
    setupPlaylistObserver();
  };

  // Setup MutationObserver for dynamic playlist updates
  const setupPlaylistObserver = () => {
    // Disconnect existing observer if any
    if (state.mutationObserver) {
      state.mutationObserver.disconnect();
    }

    const playlistPanel = document.querySelector('ytd-playlist-panel-renderer');
    if (!playlistPanel) return;

    // Throttled handler for mutations
    const handleMutations = throttle(() => {
      const currentCount = state.originalItems.length;
      const newItems = document.querySelectorAll(
        'ytd-playlist-panel-renderer ytd-playlist-panel-video-renderer'
      );

      // Only recollect if item count changed significantly
      if (Math.abs(newItems.length - currentCount) > 0) {
        collectOriginalItems();

        // Re-apply current search filter if any
        if (state.searchInput && state.searchInput.value) {
          filterPlaylistItems(state.searchInput.value);
        }
      }
    }, config.observerThrottleMs);

    state.mutationObserver = new MutationObserver(handleMutations);

    // Observe the playlist container for changes
    state.mutationObserver.observe(playlistPanel, {
      childList: true,
      subtree: true,
    });
  };

  /**
   * Collect all playlist items for filtering with limit
   */
  const collectOriginalItems = () => {
    const items = document.querySelectorAll(
      'ytd-playlist-panel-renderer ytd-playlist-panel-video-renderer'
    );

    // Limit number of items to prevent performance issues
    if (items.length > config.maxPlaylistItems) {
      console.warn(
        `[Playlist Search] Playlist has ${items.length} items, limiting to ${config.maxPlaylistItems}`
      );
    }

    // Clear cache when collecting new items
    state.itemsCache.clear();

    const itemsArray = Array.from(items).slice(0, config.maxPlaylistItems);

    state.originalItems = itemsArray.map((item, index) => {
      const videoId = item.getAttribute('video-id') || `item-${index}`;

      // Check if this item is already cached
      if (state.itemsCache.has(videoId)) {
        return state.itemsCache.get(videoId);
      }

      const titleEl = item.querySelector('#video-title');
      const bylineEl = item.querySelector('#byline');

      const itemData = {
        element: item,
        videoId,
        title: titleEl?.textContent?.trim()?.toLowerCase() || '',
        channel: bylineEl?.textContent?.trim()?.toLowerCase() || '',
      };

      // Cache the item data
      state.itemsCache.set(videoId, itemData);

      return itemData;
    });
  };

  /**
   * Filter playlist items based on search query with validation
   * @param {string} query - Search query
   */
  const filterPlaylistItems = query => {
    // Cancel any pending RAF
    if (state.rafId) {
      cancelAnimationFrame(state.rafId);
    }

    // Validate and sanitize query
    if (query && typeof query !== 'string') {
      console.warn('[Playlist Search] Invalid query type');
      return;
    }

    // Limit query length to prevent performance issues
    if (query && query.length > config.maxQueryLength) {
      query = query.substring(0, config.maxQueryLength);
    }

    if (!query || query.trim() === '') {
      // Show all items using RAF for smooth update
      state.rafId = requestAnimationFrame(() => {
        state.originalItems.forEach(item => {
          item.element.style.display = '';
        });
        state.rafId = null;
      });
      return;
    }

    const searchTerm = query.toLowerCase().trim();
    let visibleCount = 0;

    // Batch DOM updates using RAF
    state.rafId = requestAnimationFrame(() => {
      // Use document fragment approach - collect changes first
      const updates = [];

      state.originalItems.forEach(item => {
        const matches = item.title.includes(searchTerm) || item.channel.includes(searchTerm);

        if (matches) {
          if (item.element.style.display === 'none') {
            updates.push({ element: item.element, display: '' });
          }
          visibleCount++;
        } else {
          if (item.element.style.display !== 'none') {
            updates.push({ element: item.element, display: 'none' });
          }
        }
      });

      // Apply all updates in one batch to minimize reflows
      updates.forEach(update => {
        update.element.style.display = update.display;
      });

      // Update results count indicator if needed
      updateResultsCount(visibleCount, state.originalItems.length);

      state.rafId = null;
    });
  };

  // Update results count (optional visual feedback)
  const updateResultsCount = (visible, total) => {
    // Could add a results counter here if desired
    console.log(`[Playlist Search] Showing ${visible} of ${total} videos`);
  };

  // Clean up search UI
  const cleanup = () => {
    const searchUI = document.querySelector('.ytplus-playlist-search');
    if (searchUI) {
      searchUI.remove();
    }

    // Disconnect mutation observer
    if (state.mutationObserver) {
      state.mutationObserver.disconnect();
      state.mutationObserver = null;
    }

    // Cancel any pending RAF
    if (state.rafId) {
      cancelAnimationFrame(state.rafId);
      state.rafId = null;
    }

    // Clear cache
    state.itemsCache.clear();

    state.searchInput = null;
    state.originalItems = [];
    state.currentPlaylistId = null;
  };

  // Handle navigation changes
  const handleNavigation = () => {
    cleanup();
    setTimeout(addSearchUI, 300);
  };

  // Initialize
  const init = () => {
    loadSettings();

    // Try to add search UI
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', addSearchUI, { once: true });
    } else {
      addSearchUI();
    }

    // Listen for YouTube navigation events
    document.addEventListener('yt-navigate-finish', handleNavigation);

    // Cleanup on page unload
    window.addEventListener('beforeunload', cleanup);
  };

  // Start
  init();
})();

// --- MODULE: thumbnail.js ---

(function () {
  'use strict';

  // Internationalization
  const i18n = {
    en: {
      close: 'Close',
      thumbnailPreview: 'Thumbnail Preview',
      clickToOpen: 'Click to open in new tab',
      download: 'Download',
    },
    ru: {
      close: 'Закрыть',
      thumbnailPreview: 'Предпросмотр миниатюры',
      clickToOpen: 'Нажмите, чтобы открыть в новой вкладке',
      download: 'Скачать',
    },
  };

  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  /**
   * Extract video ID from thumbnail source with validation
   * @param {string} thumbnailSrc - Thumbnail source URL
   * @returns {string|null} Video ID or null if invalid
   */
  function extractVideoId(thumbnailSrc) {
    try {
      if (!thumbnailSrc || typeof thumbnailSrc !== 'string') return null;
      const match = thumbnailSrc.match(/\/vi\/([^\/]+)\//);
      const videoId = match ? match[1] : null;
      // Validate video ID format (11 characters, alphanumeric + - and _)
      if (videoId && !/^[a-zA-Z0-9_-]{11}$/.test(videoId)) {
        console.warn('[Thumbnail] Invalid video ID format:', videoId);
        return null;
      }
      return videoId;
    } catch (error) {
      console.error('[Thumbnail] Error extracting video ID:', error);
      return null;
    }
  }

  /**
   * Extract shorts ID from URL with validation
   * @param {string} href - URL to extract shorts ID from
   * @returns {string|null} Shorts ID or null if invalid
   */
  function extractShortsId(href) {
    try {
      if (!href || typeof href !== 'string') return null;
      const match = href.match(/\/shorts\/([^\/\?]+)/);
      const shortsId = match ? match[1] : null;
      // Validate shorts ID format (11 characters, alphanumeric + - and _)
      if (shortsId && !/^[a-zA-Z0-9_-]{11}$/.test(shortsId)) {
        console.warn('[Thumbnail] Invalid shorts ID format:', shortsId);
        return null;
      }
      return shortsId;
    } catch (error) {
      console.error('[Thumbnail] Error extracting shorts ID:', error);
      return null;
    }
  }

  /**
   * Check if image exists with timeout and error handling
   * @param {string} url - Image URL to check
   * @returns {Promise<boolean>} True if image exists and is accessible
   */
  async function checkImageExists(url) {
    try {
      // Validate URL
      if (!url || typeof url !== 'string') {
        console.warn('[Thumbnail] Invalid URL provided');
        return false;
      }

      // Validate URL format and protocol
      let parsedUrl;
      try {
        parsedUrl = new URL(url);
        // Only allow https protocol for security
        if (parsedUrl.protocol !== 'https:') {
          console.warn('[Thumbnail] Only HTTPS URLs are allowed');
          return false;
        }
        // Validate domain (only allow YouTube image domains)
        if (
          !parsedUrl.hostname.endsWith('ytimg.com') &&
          !parsedUrl.hostname.endsWith('youtube.com')
        ) {
          console.warn('[Thumbnail] Only YouTube image domains are allowed');
          return false;
        }
      } catch (error) {
        console.error('[Thumbnail] Invalid URL:', error);
        return false;
      }

      // Try HEAD request first with timeout
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout

      try {
        const corsTest = await fetch(url, {
          method: 'HEAD',
          signal: controller.signal,
        }).catch(() => null);

        clearTimeout(timeoutId);

        if (corsTest) {
          return corsTest.ok;
        } else {
          return true; // Fallback if HEAD request fails
        }
      } catch {
        clearTimeout(timeoutId);

        // Fallback to image load test
        return new Promise(resolve => {
          const img = document.createElement('img');
          img.style.display = 'none';

          const timeout = setTimeout(() => {
            if (img.parentNode) {
              document.body.removeChild(img);
            }
            resolve(false);
          }, 3000); // 3 second timeout for image load

          img.onload = () => {
            clearTimeout(timeout);
            if (img.parentNode) {
              document.body.removeChild(img);
            }
            resolve(true);
          };

          img.onerror = () => {
            clearTimeout(timeout);
            if (img.parentNode) {
              document.body.removeChild(img);
            }
            resolve(false);
          };

          document.body.appendChild(img);
          img.src = url;
        });
      }
    } catch (error) {
      console.error('[Thumbnail] Error checking image:', error);
      return false;
    }
  }

  function createSpinner() {
    const spinner = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    spinner.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    spinner.setAttribute('width', '16');
    spinner.setAttribute('height', '16');
    spinner.setAttribute('viewBox', '0 0 24 24');
    spinner.setAttribute('fill', 'none');
    spinner.setAttribute('stroke', 'white');
    spinner.setAttribute('stroke-width', '2');
    spinner.setAttribute('stroke-linecap', 'round');
    spinner.setAttribute('stroke-linejoin', 'round');

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', 'M21 12a9 9 0 1 1-6.219-8.56');
    spinner.appendChild(path);

    spinner.style.animation = 'spin 1s linear infinite';

    if (!document.querySelector('#spinner-keyframes')) {
      const style = document.createElement('style');
      style.id = 'spinner-keyframes';
      style.textContent = `
                @keyframes spin {
                    from { transform: rotate(0deg); }
                    to { transform: rotate(360deg); }
                }
            `;
      (document.head || document.documentElement).appendChild(style);
    }

    return spinner;
  }

  /**
   * Open thumbnail in modal with error handling
   * @param {string} videoId - YouTube video ID
   * @param {boolean} isShorts - Whether this is a Shorts video
   * @param {HTMLElement} overlayElement - Overlay element containing the button
   * @returns {Promise<void>}
   */
  async function openThumbnail(videoId, isShorts, overlayElement) {
    try {
      // Validate inputs
      if (!videoId || typeof videoId !== 'string' || !/^[a-zA-Z0-9_-]{11}$/.test(videoId)) {
        console.error('[Thumbnail] Invalid video ID:', videoId);
        return;
      }

      if (!overlayElement || !(overlayElement instanceof HTMLElement)) {
        console.error('[Thumbnail] Invalid overlay element');
        return;
      }

      const originalSvg = overlayElement.querySelector('svg');
      if (!originalSvg) {
        console.warn('[Thumbnail] No SVG found in overlay element');
        return;
      }

      const spinner = createSpinner();
      overlayElement.replaceChild(spinner, originalSvg);

      try {
        if (isShorts) {
          const oardefaultUrl = `https://i.ytimg.com/vi/${videoId}/oardefault.jpg`;
          const isOarDefaultAvailable = await checkImageExists(oardefaultUrl);

          if (isOarDefaultAvailable) {
            showImageModal(oardefaultUrl);
          } else {
            showImageModal(`https://i.ytimg.com/vi/${videoId}/oar2.jpg`);
          }
        } else {
          const maxresdefaultUrl = `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`;
          const isMaxResAvailable = await checkImageExists(maxresdefaultUrl);

          if (isMaxResAvailable) {
            showImageModal(maxresdefaultUrl);
          } else {
            showImageModal(`https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`);
          }
        }
      } finally {
        // Restore original svg
        try {
          if (spinner && spinner.parentNode) {
            overlayElement.replaceChild(originalSvg, spinner);
          }
        } catch (restoreError) {
          console.error('[Thumbnail] Error restoring original SVG:', restoreError);
          // Fallback: remove spinner if original not found
          if (spinner && spinner.parentNode) {
            spinner.parentNode.removeChild(spinner);
          }
        }
      }
    } catch (error) {
      console.error('[Thumbnail] Error opening thumbnail:', error);
    }
  }

  // Inject CSS styles via StyleManager (if available) to match base theme
  (function addThumbnailStyles() {
    try {
      const css = `
    :root { --thumbnail-btn-bg-light: rgba(255, 255, 255, 0.85); --thumbnail-btn-bg-dark: rgba(0, 0, 0, 0.7); --thumbnail-btn-hover-bg-light: rgba(255, 255, 255, 1); --thumbnail-btn-hover-bg-dark: rgba(0, 0, 0, 0.9); --thumbnail-btn-color-light: #222; --thumbnail-btn-color-dark: #fff; --thumbnail-modal-bg-light: rgba(255, 255, 255, 0.95); --thumbnail-modal-bg-dark: rgba(34, 34, 34, 0.85); --thumbnail-modal-title-light: #222; --thumbnail-modal-title-dark: #fff; --thumbnail-modal-btn-bg-light: rgba(0, 0, 0, 0.08); --thumbnail-modal-btn-bg-dark: rgba(255, 255, 255, 0.08); --thumbnail-modal-btn-hover-bg-light: rgba(0, 0, 0, 0.18); --thumbnail-modal-btn-hover-bg-dark: rgba(255, 255, 255, 0.18); --thumbnail-modal-btn-color-light: #222; --thumbnail-modal-btn-color-dark: #fff; --thumbnail-modal-btn-hover-color-light: #ff4444; --thumbnail-modal-btn-hover-color-dark: #ff4444; --thumbnail-glass-blur: blur(18px) saturate(180%); --thumbnail-glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); --thumbnail-glass-border: rgba(255, 255, 255, 0.2); }
    html[dark], body[dark] { --thumbnail-btn-bg: var(--thumbnail-btn-bg-dark); --thumbnail-btn-hover-bg: var(--thumbnail-btn-hover-bg-dark); --thumbnail-btn-color: var(--thumbnail-btn-color-dark); --thumbnail-modal-bg: var(--thumbnail-modal-bg-dark); --thumbnail-modal-title: var(--thumbnail-modal-title-dark); --thumbnail-modal-btn-bg: var(--thumbnail-modal-btn-bg-dark); --thumbnail-modal-btn-hover-bg: var(--thumbnail-modal-btn-hover-bg-dark); --thumbnail-modal-btn-color: var(--thumbnail-modal-btn-color-dark); --thumbnail-modal-btn-hover-color: var(--thumbnail-modal-btn-hover-color-dark); }
    html:not([dark]) { --thumbnail-btn-bg: var(--thumbnail-btn-bg-light); --thumbnail-btn-bg: var(--thumbnail-btn-bg-light); --thumbnail-btn-hover-bg: var(--thumbnail-btn-hover-bg-light); --thumbnail-btn-color: var(--thumbnail-btn-color-light); --thumbnail-modal-bg: var(--thumbnail-modal-bg-light); --thumbnail-modal-title: var(--thumbnail-modal-title-light); --thumbnail-modal-btn-bg: var(--thumbnail-modal-btn-bg-light); --thumbnail-modal-btn-hover-bg: var(--thumbnail-modal-btn-hover-bg-light); --thumbnail-modal-btn-color: var(--thumbnail-modal-btn-color-light); --thumbnail-modal-btn-hover-color: var(--thumbnail-modal-btn-hover-color-light); }
    .thumbnail-overlay-container { position: absolute; bottom: 8px; left: 8px; z-index: 9999; opacity: 0; transition: opacity 0.2s ease; }
    .thumbnail-overlay-button { width: 28px; height: 28px; background: var(--thumbnail-btn-bg); border: none; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--thumbnail-btn-color); position: relative; box-shadow: var(--thumbnail-glass-shadow); backdrop-filter: var(--thumbnail-glass-blur); -webkit-backdrop-filter: var(--thumbnail-glass-blur); border: 1px solid var(--thumbnail-glass-border); }
    .thumbnail-overlay-button:hover { background: var(--thumbnail-btn-hover-bg); }
    .thumbnail-dropdown { position: absolute; bottom: 100%; left: 0; background: var(--thumbnail-btn-hover-bg); border-radius: 8px; padding: 4px; margin-bottom: 4px; display: none; flex-direction: column; min-width: 140px; box-shadow: var(--thumbnail-glass-shadow); z-index: 10000; backdrop-filter: var(--thumbnail-glass-blur); -webkit-backdrop-filter: var(--thumbnail-glass-blur); border: 1px solid var(--thumbnail-glass-border); }
    .thumbnail-dropdown.show { display: flex !important; }
    .thumbnail-dropdown-item { background: none; border: none; color: var(--thumbnail-btn-color); padding: 8px 12px; cursor: pointer; border-radius: 4px; font-size: 12px; text-align: left; white-space: nowrap; transition: background-color 0.2s ease; }
    .thumbnail-dropdown-item:hover { background: rgba(255,255,255,0.06); }
    .thumbnailPreview-button { position: absolute; bottom: 10px; left: 5px; background-color: var(--thumbnail-btn-bg); color: var(--thumbnail-btn-color); border: none; border-radius: 6px; padding: 3px; font-size: 18px; cursor: pointer; z-index: 2000; opacity: 0; transition: opacity 0.3s; display: flex; align-items: center; justify-content: center; box-shadow: var(--thumbnail-glass-shadow); backdrop-filter: var(--thumbnail-glass-blur); -webkit-backdrop-filter: var(--thumbnail-glass-blur); border: 1px solid var(--thumbnail-glass-border); }
    .thumbnailPreview-container { position: relative; }
    .thumbnailPreview-container:hover .thumbnailPreview-button { opacity: 1; }
    .thumbnail-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.55); z-index: 100000; display: flex; align-items: center; justify-content: center; animation: fadeInModal 0.22s cubic-bezier(.4,0,.2,1); backdrop-filter: blur(8px) saturate(140%); -webkit-backdrop-filter: blur(8px) saturate(140%); }
  .thumbnail-modal-content { background: var(--thumbnail-modal-bg); border-radius: 20px; box-shadow: 0 12px 40px rgba(0,0,0,0.45); max-width: 78vw; max-height: 90vh; overflow: auto; position: relative; display: flex; flex-direction: column; align-items: center; animation: scaleInModal 0.22s cubic-bezier(.4,0,.2,1); border: 1.5px solid var(--thumbnail-glass-border); backdrop-filter: blur(14px) saturate(150%); -webkit-backdrop-filter: blur(14px) saturate(150%);}
    /* Wrapper to place content and action buttons side-by-side */
    .thumbnail-modal-wrapper { display: flex; align-items: flex-start; gap: 12px; }
    .thumbnail-modal-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
  .thumbnail-modal-action-btn { width: 40px; height: 40px; border-radius: 50%; background: var(--thumbnail-modal-btn-bg); border: 1px solid rgba(0,0,0,0.08); display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 14px rgba(0,0,0,0.2); transition: transform 0.12s ease, background 0.12s ease; color: var(--thumbnail-modal-btn-color); }
    .thumbnail-modal-action-btn:hover { transform: translateY(-2px); }
    .thumbnail-modal-close { }
    .thumbnail-modal-open { }
    .thumbnail-modal-img { max-width: 72vw; max-height: 70vh; box-shadow: var(--thumbnail-glass-shadow); background: #222; border: 1px solid var(--thumbnail-glass-border); }
    .thumbnail-modal-options { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
    .thumbnail-modal-option-btn { background: var(--thumbnail-modal-btn-bg); color: var(--thumbnail-modal-btn-color); border: none; border-radius: 8px; padding: 8px 18px; font-size: 14px; cursor: pointer; transition: background 0.2s; margin-bottom: 6px; box-shadow: var(--thumbnail-glass-shadow); backdrop-filter: var(--thumbnail-glass-blur); -webkit-backdrop-filter: var(--thumbnail-glass-blur); border: 1px solid var(--thumbnail-glass-border); }
    .thumbnail-modal-option-btn:hover { background: var(--thumbnail-modal-btn-hover-bg); color: var(--thumbnail-modal-btn-hover-color); }
    .thumbnail-modal-title { font-size: 18px; font-weight: 600; color: var(--thumbnail-modal-title); margin-bottom: 10px; text-align: center; text-shadow: 0 2px 8px rgba(0,0,0,0.15); }
    @keyframes fadeInModal { from { opacity: 0; } to { opacity: 1; } }
    @keyframes scaleInModal { from { transform: scale(0.95); } to { transform: scale(1); } }
            `;

      if (
        window.YouTubeUtils &&
        YouTubeUtils.StyleManager &&
        typeof YouTubeUtils.StyleManager.add === 'function'
      ) {
        YouTubeUtils.StyleManager.add('thumbnail-viewer-styles', css);
      } else {
        const s = document.createElement('style');
        s.id = 'ytplus-thumbnail-styles';
        s.textContent = css;
        (document.head || document.documentElement).appendChild(s);
      }
    } catch {
      // fallback: inject minimal styles
      if (!document.getElementById('ytplus-thumbnail-styles')) {
        const s = document.createElement('style');
        s.id = 'ytplus-thumbnail-styles';
        s.textContent = '.thumbnail-modal-img{max-width:72vw;max-height:70vh;}';
        (document.head || document.documentElement).appendChild(s);
      }
    }
  })();

  /**
   * Show image in modal with error handling and security
   * @param {string} url - Image URL to display
   * @returns {void}
   */
  function showImageModal(url) {
    try {
      // Validate URL
      if (!url || typeof url !== 'string') {
        console.error('[Thumbnail] Invalid URL provided to modal');
        return;
      }

      // Validate URL format and security
      try {
        const parsedUrl = new URL(url);
        // Only allow HTTPS protocol
        if (parsedUrl.protocol !== 'https:') {
          console.error('[Thumbnail] Only HTTPS URLs are allowed');
          return;
        }
        // Validate domain (allow common YouTube image domains)
        // Avatars and some images may be hosted on ggpht.com or googleusercontent.com
        const allowedDomains = ['ytimg.com', 'youtube.com', 'ggpht.com', 'googleusercontent.com'];
        if (!allowedDomains.some(d => parsedUrl.hostname.endsWith(d))) {
          console.error('[Thumbnail] Image domain not allowed:', parsedUrl.hostname);
          return;
        }
      } catch (urlError) {
        console.error('[Thumbnail] Invalid URL format:', urlError);
        return;
      }

      // Remove existing modals
      document.querySelectorAll('.thumbnail-modal-overlay').forEach(m => m.remove());

      const overlay = document.createElement('div');
      overlay.className = 'thumbnail-modal-overlay';

      const content = document.createElement('div');
      content.className = 'thumbnail-modal-content';

      // create image element
      const img = document.createElement('img');
      img.className = 'thumbnail-modal-img';
      img.src = url;
      img.alt = t('thumbnailPreview');
      // remove tooltip/title text per request
      img.title = '';
      img.style.cursor = 'pointer';
      img.addEventListener('click', () => window.open(img.src, '_blank'));

      const optionsDiv = document.createElement('div');
      optionsDiv.className = 'thumbnail-modal-options';

      // close button placed on the overlay (outside modal content)
      const closeBtn = document.createElement('button');
      closeBtn.className = 'thumbnail-modal-close';
      closeBtn.innerHTML = `\n            <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>\n            </svg>\n            `;
      closeBtn.title = t('close');
      closeBtn.setAttribute('aria-label', t('close'));
      closeBtn.addEventListener('click', e => {
        e.preventDefault();
        e.stopPropagation();
        overlay.remove();
      });

      // open-in-new-tab button (uses extension-like puzzle icon)
      const newTabBtn = document.createElement('button');
      newTabBtn.className = 'thumbnail-modal-open';
      newTabBtn.innerHTML = `\n            <svg fill="currentColor" viewBox="0 0 24 24" width="18" height="18" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">\n        <g id="SVGRepo_bgCarrier" stroke-width="0"></g>\n        <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>\n        <g id="SVGRepo_iconCarrier"><path d="M14.293,9.707a1,1,0,0,1,0-1.414L18.586,4H16a1,1,0,0,1,0-2h5a1,1,0,0,1,1,1V8a1,1,0,0,1-2,0V5.414L15.707,9.707a1,1,0,0,1-1.414,0ZM3,22H8a1,1,0,0,0,0-2H5.414l4.293-4.293a1,1,0,0,0-1.414-1.414L4,18.586V16a1,1,0,0,0-2,0v5A1,1,0,0,0,3,22Z"></path></g>\n      </svg>\n        `;
      newTabBtn.title = t('clickToOpen');
      newTabBtn.setAttribute('aria-label', t('clickToOpen'));
      newTabBtn.addEventListener('click', e => {
        e.preventDefault();
        e.stopPropagation();
        window.open(img.src, '_blank');
      });

      // download button
      const downloadBtn = document.createElement('button');
      downloadBtn.className = 'thumbnail-modal-download';
      downloadBtn.innerHTML = `\n            <svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>\n                <polyline points="7 10 12 15 17 10"/>\n                <line x1="12" y1="15" x2="12" y2="3"/>\n            </svg>\n        `;
      downloadBtn.title = t('download');
      downloadBtn.setAttribute('aria-label', t('download'));
      downloadBtn.addEventListener('click', async e => {
        e.preventDefault();
        e.stopPropagation();
        try {
          // Try to download by fetching the image as a blob first. Browsers often ignore
          // the `download` attribute for cross-origin links, so fetching and creating
          // an object URL forces a download when allowed by CORS.
          const response = await fetch(img.src);
          if (!response.ok) throw new Error('Network response was not ok');
          const blob = await response.blob();
          const blobUrl = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = blobUrl;
          // Derive a sensible filename from the image URL, fallback to thumbnail.jpg
          try {
            const urlObj = new URL(img.src);
            const segments = urlObj.pathname.split('/');
            a.download = segments[segments.length - 1] || 'thumbnail.jpg';
          } catch {
            a.download = 'thumbnail.jpg';
          }
          document.body.appendChild(a);
          a.click();
          a.remove();
          // Revoke object URL shortly after to free memory
          setTimeout(() => URL.revokeObjectURL(blobUrl), 1500);
        } catch {
          // If fetch failed (CORS or network), fallback to opening in a new tab so user
          // can still save the image manually.
          window.open(img.src, '_blank');
        }
      });

      // append modal pieces: create wrapper with content and an actions column
      content.appendChild(img);
      content.appendChild(optionsDiv);

      const wrapper = document.createElement('div');
      wrapper.className = 'thumbnail-modal-wrapper';

      const actionsDiv = document.createElement('div');
      actionsDiv.className = 'thumbnail-modal-actions';

      // style action buttons consistently
      closeBtn.classList.add('thumbnail-modal-action-btn');
      newTabBtn.classList.add('thumbnail-modal-action-btn');
      downloadBtn.classList.add('thumbnail-modal-action-btn');

      // put close first, then open (new-tab), then download (last)
      actionsDiv.appendChild(closeBtn);
      actionsDiv.appendChild(newTabBtn);
      actionsDiv.appendChild(downloadBtn);

      wrapper.appendChild(content);
      wrapper.appendChild(actionsDiv);
      overlay.appendChild(wrapper);

      overlay.addEventListener('click', e => {
        if (e.target === overlay) overlay.remove();
      });

      function escHandler(e) {
        if (e.key === 'Escape') {
          overlay.remove();
          window.removeEventListener('keydown', escHandler, true);
        }
      }
      window.addEventListener('keydown', escHandler, true);

      img.addEventListener('error', () => {
        const err = document.createElement('div');
        err.textContent = 'Не удалось загрузить изображение';
        err.style.color = 'white';
        content.appendChild(err);
      });

      document.body.appendChild(overlay);
    } catch (error) {
      console.error('[Thumbnail] Error showing modal:', error);
    }
  }

  let thumbnailPreviewCurrentVideoId = '';
  let thumbnailPreviewClosed = false;
  let thumbnailInsertionAttempts = 0;
  const MAX_ATTEMPTS = 10;
  const RETRY_DELAY = 500;

  function isWatchPage() {
    const url = new URL(window.location.href);
    return url.pathname === '/watch' && url.searchParams.has('v');
  }

  function addOrUpdateThumbnailImage() {
    if (!isWatchPage()) return;

    const newVideoId = new URLSearchParams(window.location.search).get('v');

    if (newVideoId !== thumbnailPreviewCurrentVideoId) {
      thumbnailPreviewClosed = false;
      // Remove old overlay when video changes to prevent showing stale thumbnails
      const oldOverlay = document.querySelector('#thumbnailPreview-player-overlay');
      if (oldOverlay) {
        oldOverlay.remove();
      }
    }

    if (!newVideoId || newVideoId === thumbnailPreviewCurrentVideoId || thumbnailPreviewClosed) {
      return;
    }

    thumbnailPreviewCurrentVideoId = newVideoId;

    function attemptInsertion() {
      const player =
        document.querySelector('#movie_player') || document.querySelector('ytd-player');
      if (!player) {
        thumbnailInsertionAttempts++;
        if (thumbnailInsertionAttempts < MAX_ATTEMPTS) {
          setTimeout(attemptInsertion, RETRY_DELAY);
        } else {
          thumbnailInsertionAttempts = 0;
        }
        return;
      }

      // Add or update a small overlay icon at top-left of the player
      const overlayId = 'thumbnailPreview-player-overlay';
      let overlay = player.querySelector(`#${overlayId}`);

      if (!overlay) {
        // create a standard thumb-overlay and adapt it for the top-left player position
        overlay = /** @type {any} */ (
          createThumbnailOverlay(thumbnailPreviewCurrentVideoId, player)
        );
        overlay.id = overlayId;
        overlay.dataset.videoId = thumbnailPreviewCurrentVideoId; // Store video ID
        // override position/size for player overlay (top-left)
        overlay.style.cssText = `
                    position: absolute;
                    top: 10%;
                    right: 8px;
                    width: 36px;
                    height: 36px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    border-radius: 6px;
                    cursor: pointer;
                    z-index: 1001;
                    transition: all 0.15s ease;
                    opacity: 0;
                `;

        // Add hover and focus behaviour so overlay becomes fully visible when interacted with
        overlay.tabIndex = 0; // make focusable for keyboard users
        overlay.onmouseenter = () => {
          try {
            overlay.style.opacity = '0.5';
          } catch {}
        };
        overlay.onmouseleave = () => {
          try {
            overlay.style.opacity = '0';
          } catch {}
        };
        overlay.onfocus = () => {
          try {
            overlay.style.opacity = '0.5';
          } catch {}
        };
        overlay.onblur = () => {
          try {
            overlay.style.opacity = '0';
          } catch {}
        };
        // allow Enter/Space to open the thumbnail
        overlay.addEventListener('keydown', e => {
          // cast to KeyboardEvent for lint/type safety
          const ke = /** @type {KeyboardEvent} */ (e);
          if (ke && (ke.key === 'Enter' || ke.key === ' ')) {
            ke.preventDefault();
            overlay.click();
          }
        });

        // ensure the player is positioned to allow absolute child
        const playerAny = /** @type {any} */ (player);
        if (/** @type {any} */ (getComputedStyle(playerAny)).position === 'static') {
          playerAny.style.position = 'relative';
        }
        playerAny.appendChild(overlay);
      } else {
        // overlay already exists — verify it matches current video ID, otherwise remove and recreate
        if (overlay.dataset.videoId !== thumbnailPreviewCurrentVideoId) {
          overlay.remove();
          // Recursively call to create new overlay
          attemptInsertion();
          return;
        }
      }

      thumbnailInsertionAttempts = 0;
    }

    attemptInsertion();
  }

  function createThumbnailOverlay(videoId, container) {
    const overlay = document.createElement('div');

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '16');
    svg.setAttribute('height', '16');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'white');
    svg.setAttribute('stroke-width', '2');
    svg.setAttribute('stroke-linecap', 'round');
    svg.setAttribute('stroke-linejoin', 'round');
    svg.style.transition = 'stroke 0.2s ease';

    const mainRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    mainRect.setAttribute('width', '18');
    mainRect.setAttribute('height', '18');
    mainRect.setAttribute('x', '3');
    mainRect.setAttribute('y', '3');
    mainRect.setAttribute('rx', '2');
    mainRect.setAttribute('ry', '2');
    svg.appendChild(mainRect);

    const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    circle.setAttribute('cx', '9');
    circle.setAttribute('cy', '9');
    circle.setAttribute('r', '2');
    svg.appendChild(circle);

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', 'm21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21');
    svg.appendChild(path);

    overlay.appendChild(svg);
    overlay.style.cssText = `
            position: absolute;
            bottom: 8px;
            left: 8px;
            background: rgba(0, 0, 0, 0.3);
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 4px;
            cursor: pointer;
            z-index: 1000;
            opacity: 0;
            transition: all 0.2s ease;
        `;

    overlay.onmouseenter = () => {
      overlay.style.background = 'rgba(0, 0, 0, 0.7)';
    };
    overlay.onmouseleave = () => {
      overlay.style.background = 'rgba(0, 0, 0, 0.3)';
    };

    overlay.onclick = async e => {
      e.preventDefault();
      e.stopPropagation();

      const isShorts =
        container.closest('ytm-shorts-lockup-view-model') ||
        container.closest('.shortsLockupViewModelHost') ||
        container.closest('[class*="shortsLockupViewModelHost"]') ||
        container.querySelector('a[href*="/shorts/"]');

      await openThumbnail(videoId, !!isShorts, overlay);
    };

    return overlay;
  }

  function addThumbnailOverlay(container) {
    if (container.querySelector('.thumb-overlay')) return;

    let videoId = null;
    let thumbnailContainer = null;

    const img = container.querySelector('img[src*="ytimg.com"]');
    if (img?.src) {
      videoId = extractVideoId(img.src);
      thumbnailContainer = img.closest('yt-thumbnail-view-model') || img.parentElement;
    }

    if (!videoId) {
      const link = container.querySelector('a[href*="/shorts/"]');
      if (link?.href) {
        videoId = extractShortsId(link.href);

        const shortsImg = container.querySelector('img[src*="ytimg.com"]');
        if (shortsImg) {
          thumbnailContainer =
            shortsImg.closest('.ytCoreImageHost') ||
            shortsImg.closest('[class*="ThumbnailContainer"]') ||
            shortsImg.closest('[class*="ImageHost"]') ||
            shortsImg.parentElement;
        }
      }
    }

    if (!videoId || !thumbnailContainer) return;

    if (getComputedStyle(thumbnailContainer).position === 'static') {
      thumbnailContainer.style.position = 'relative';
    }
    const overlay = createThumbnailOverlay(videoId, container);
    overlay.className = 'thumb-overlay';
    thumbnailContainer.appendChild(overlay);

    thumbnailContainer.onmouseenter = () => (overlay.style.opacity = '1');
    thumbnailContainer.onmouseleave = () => (overlay.style.opacity = '0');
  }

  function createAvatarOverlay() {
    const overlay = document.createElement('div');

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '16');
    svg.setAttribute('height', '16');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'white');
    svg.setAttribute('stroke-width', '2');
    svg.setAttribute('stroke-linecap', 'round');
    svg.setAttribute('stroke-linejoin', 'round');
    svg.style.transition = 'stroke 0.2s ease';

    const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    circle.setAttribute('cx', '12');
    circle.setAttribute('cy', '8');
    circle.setAttribute('r', '5');
    svg.appendChild(circle);

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', 'M20 21a8 8 0 0 0-16 0');
    svg.appendChild(path);

    overlay.appendChild(svg);

    overlay.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.7);
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            cursor: pointer;
            z-index: 1000;
            opacity: 0;
            transition: all 0.2s ease;
        `;

    overlay.onmouseenter = () => {
      overlay.style.background = 'rgba(0, 0, 0, 0.9)';
    };
    overlay.onmouseleave = () => {
      overlay.style.background = 'rgba(0, 0, 0, 0.7)';
    };

    return overlay;
  }

  function addAvatarOverlay(img) {
    const container = img.parentElement;
    if (container.querySelector('.avatar-overlay')) return;

    if (getComputedStyle(container).position === 'static') {
      container.style.position = 'relative';
    }

    const overlay = createAvatarOverlay();
    overlay.className = 'avatar-overlay';

    overlay.onclick = e => {
      e.preventDefault();
      e.stopPropagation();
      const highResUrl = img.src.replace(/=s\d+-c-k-c0x00ffffff-no-rj.*/, '=s0');
      showImageModal(highResUrl);
    };

    container.appendChild(overlay);

    container.onmouseenter = () => (overlay.style.opacity = '1');
    container.onmouseleave = () => (overlay.style.opacity = '0');
  }

  function createBannerOverlay() {
    const overlay = document.createElement('div');

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '16');
    svg.setAttribute('height', '16');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'white');
    svg.setAttribute('stroke-width', '2');
    svg.setAttribute('stroke-linecap', 'round');
    svg.setAttribute('stroke-linejoin', 'round');
    svg.style.transition = 'stroke 0.2s ease';

    const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    rect.setAttribute('x', '3');
    rect.setAttribute('y', '3');
    rect.setAttribute('width', '18');
    rect.setAttribute('height', '18');
    rect.setAttribute('rx', '2');
    rect.setAttribute('ry', '2');
    svg.appendChild(rect);

    const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    circle.setAttribute('cx', '9');
    circle.setAttribute('cy', '9');
    circle.setAttribute('r', '2');
    svg.appendChild(circle);

    const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
    polyline.setAttribute('points', '21,15 16,10 5,21');
    svg.appendChild(polyline);

    overlay.appendChild(svg);

    overlay.style.cssText = `
            position: absolute;
            bottom: 8px;
            left: 8px;
            background: rgba(0, 0, 0, 0.7);
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 4px;
            cursor: pointer;
            z-index: 1000;
            opacity: 0;
            transition: all 0.2s ease;
        `;

    overlay.onmouseenter = () => {
      overlay.style.background = 'rgba(0, 0, 0, 0.9)';
    };
    overlay.onmouseleave = () => {
      overlay.style.background = 'rgba(0, 0, 0, 0.7)';
    };

    return overlay;
  }

  function addBannerOverlay(img) {
    const container = img.parentElement;
    if (container.querySelector('.banner-overlay')) return;

    if (getComputedStyle(container).position === 'static') {
      container.style.position = 'relative';
    }

    const overlay = createBannerOverlay();
    overlay.className = 'banner-overlay';

    overlay.onclick = e => {
      e.preventDefault();
      e.stopPropagation();
      const highResUrl = img.src.replace(/=w\d+-.*/, '=s0');
      showImageModal(highResUrl);
    };

    container.appendChild(overlay);

    container.onmouseenter = () => (overlay.style.opacity = '1');
    container.onmouseleave = () => (overlay.style.opacity = '0');
  }

  function processAvatars() {
    const avatarSelectors = [
      'yt-avatar-shape img',
      '#avatar img',
      'ytd-channel-avatar-editor img',
      '.ytd-video-owner-renderer img[src*="yt"]',
      'img[src*="yt3.ggpht.com"]', // Добавляем прямой селектор для аватаров
      'img[src*="yt4.ggpht.com"]',
    ];

    avatarSelectors.forEach(selector => {
      document.querySelectorAll(selector).forEach(img => {
        if (!img.src) return;
        if (!img.src.includes('yt')) return;
        if (img.closest('.avatar-overlay')) return;

        // Проверяем, что это действительно аватар (квадратное изображение)
        const isAvatar = img.naturalWidth > 0 && img.naturalWidth === img.naturalHeight;

        if (isAvatar || img.src.includes('ggpht.com')) {
          addAvatarOverlay(img);
        }
      });
    });
  }

  function processBanners() {
    const bannerSelectors = [
      'yt-image-banner-view-model img',
      'ytd-c4-tabbed-header-renderer img[src*="yt"]',
      '#channel-header img[src*="banner"]',
      'img[src*="banner"]', // Более общий селектор для баннеров
    ];

    bannerSelectors.forEach(selector => {
      document.querySelectorAll(selector).forEach(img => {
        if (!img.src) return;
        if (img.closest('.banner-overlay')) return;

        const isBanner =
          (img.src.includes('banner') || img.src.includes('yt')) &&
          img.naturalWidth > img.naturalHeight * 2; // Баннеры обычно широкие

        if (isBanner || img.src.includes('banner')) {
          addBannerOverlay(img);
        }
      });
    });
  }

  function processThumbnails() {
    document.querySelectorAll('yt-thumbnail-view-model').forEach(addThumbnailOverlay);
    document.querySelectorAll('.ytd-thumbnail').forEach(addThumbnailOverlay);

    document.querySelectorAll('ytm-shorts-lockup-view-model').forEach(addThumbnailOverlay);
    document.querySelectorAll('.shortsLockupViewModelHost').forEach(addThumbnailOverlay);
    document.querySelectorAll('[class*="shortsLockupViewModelHost"]').forEach(addThumbnailOverlay);
  }

  function processAll() {
    processThumbnails();
    processAvatars();
    processBanners();
    addOrUpdateThumbnailImage();
  }

  function setupMutationObserver() {
    const observer = new MutationObserver(() => {
      setTimeout(processAll, 50);
    });

    // ✅ Safe observe with document.body check
    if (document.body) {
      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, {
          childList: true,
          subtree: true,
        });
      });
    }
  }

  function setupUrlChangeDetection() {
    let currentUrl = location.href;

    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;

    history.pushState = function () {
      originalPushState.apply(history, arguments);
      setTimeout(() => {
        if (location.href !== currentUrl) {
          currentUrl = location.href;
          setTimeout(addOrUpdateThumbnailImage, 500);
        }
      }, 100);
    };

    history.replaceState = function () {
      originalReplaceState.apply(history, arguments);
      setTimeout(() => {
        if (location.href !== currentUrl) {
          currentUrl = location.href;
          setTimeout(addOrUpdateThumbnailImage, 500);
        }
      }, 100);
    };

    window.addEventListener('popstate', function () {
      setTimeout(() => {
        if (location.href !== currentUrl) {
          currentUrl = location.href;
          setTimeout(addOrUpdateThumbnailImage, 500);
        }
      }, 100);
    });

    setInterval(function () {
      if (location.href !== currentUrl) {
        currentUrl = location.href;
        setTimeout(addOrUpdateThumbnailImage, 300);
      }
    }, 500);
  }

  function initialize() {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', function () {
        setTimeout(init, 100);
      });
    } else {
      setTimeout(init, 100);
    }
  }

  function init() {
    setupUrlChangeDetection();
    setupMutationObserver();
    processAll();
    setTimeout(processAll, 500);
    setTimeout(processAll, 1000);
    setTimeout(processAll, 2000);
  }

  initialize();
})();

// --- MODULE: shorts.js ---

// Shorts Keyboard controls
(function () {
  'use strict';

  // Internationalization (i18n) system
  const i18n = {
    en: {
      seekBackward: 'Seek backward',
      seekForward: 'Seek forward',
      volumeUp: 'Volume up',
      volumeDown: 'Volume down',
      muteUnmute: 'Mute/Unmute',
      showHideHelp: 'Show/Hide help',
      keyboardShortcuts: 'Keyboard Shortcuts',
      closeButton: 'Close',
      resetAll: 'Reset All',
      resetAllConfirm: 'Reset all shortcuts?',
      shortcutsReset: 'Shortcuts reset',
      editShortcut: 'Edit',
      pressAnyKey: 'Press any key to set as new shortcut',
      current: 'Current',
      cancel: 'Cancel',
      keyAlreadyUsed: 'Key "{key}" already used',
      shortcutUpdated: 'Shortcut updated',
      toggleCaptions: 'Toggle subtitles',
      captionsOn: 'Subtitles: On',
      captionsOff: 'Subtitles: Off',
      captionsUnavailable: 'Subtitles unavailable',
    },
    ru: {
      seekBackward: 'Перемотка назад',
      seekForward: 'Перемотка вперёд',
      volumeUp: 'Громче',
      volumeDown: 'Тише',
      muteUnmute: 'Вкл/Выкл звук',
      showHideHelp: 'Показать/Скрыть помощь',
      keyboardShortcuts: 'Клавиатурные сочетания',
      closeButton: 'Закрыть',
      resetAll: 'Сбросить всё',
      resetAllConfirm: 'Сбросить все сочетания клавиш?',
      shortcutsReset: 'Сочетания клавиш сброшены',
      editShortcut: 'Изменить',
      pressAnyKey: 'Нажмите любую клавишу для установки нового сочетания',
      current: 'Текущее',
      cancel: 'Отмена',
      keyAlreadyUsed: 'Клавиша "{key}" уже используется',
      shortcutUpdated: 'Сочетание клавиш обновлено',
      toggleCaptions: 'Переключить субтитры',
      captionsOn: 'Субтитры: Вкл',
      captionsOff: 'Субтитры: Выкл',
      captionsUnavailable: 'Субтитры недоступны',
    },
  };

  // Get browser language
  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  // Translation function
  function t(key, params = {}) {
    const lang = getLanguage();
    let text = i18n[lang][key] || i18n.en[key] || key;

    // Replace parameters in template
    Object.keys(params).forEach(paramKey => {
      text = text.replace(`{${paramKey}}`, params[paramKey]);
    });

    return text;
  }

  // Configuration
  const config = {
    enabled: true,
    shortcuts: {
      seekBackward: { key: 'ArrowLeft', description: t('seekBackward') },
      seekForward: { key: 'ArrowRight', description: t('seekForward') },
      volumeUp: { key: '+', description: t('volumeUp') },
      volumeDown: { key: '-', description: t('volumeDown') },
      mute: { key: 'm', description: t('muteUnmute') },
      toggleCaptions: { key: 'c', description: t('toggleCaptions') },
      showHelp: { key: '?', description: t('showHideHelp'), editable: false },
    },
    storageKey: 'youtube_shorts_keyboard_settings',
  };

  // State management
  const state = {
    helpVisible: false,
    lastAction: null,
    actionTimeout: null,
    editingShortcut: null,
    cachedVideo: null,
    lastVideoCheck: 0,
  };

  // Optimized video selector with caching
  const getCurrentVideo = (() => {
    const selectors = ['ytd-reel-video-renderer[is-active] video', '#shorts-player video', 'video'];

    return () => {
      const now = Date.now();
      if (state.cachedVideo?.isConnected && now - state.lastVideoCheck < 100) {
        return state.cachedVideo;
      }

      for (const selector of selectors) {
        // ✅ Use cached querySelector
        const video = YouTubeUtils.querySelector(selector);
        if (video) {
          state.cachedVideo = video;
          state.lastVideoCheck = now;
          return video;
        }
      }

      state.cachedVideo = null;
      return null;
    };
  })();

  // Optimized utilities
  const utils = {
    isInShortsPage: () => location.pathname.startsWith('/shorts/'),

    isInputFocused: () => {
      const el = document.activeElement;
      return el?.matches?.('input, textarea, [contenteditable="true"]') || el?.isContentEditable;
    },

    /**
     * Load settings from localStorage with validation
     * @returns {void}
     */
    loadSettings: () => {
      try {
        const saved = localStorage.getItem(config.storageKey);
        if (!saved) return;

        const parsed = JSON.parse(saved);
        if (typeof parsed !== 'object' || parsed === null) {
          console.warn('[Shorts] Invalid settings format');
          return;
        }

        // Validate enabled flag
        if (typeof parsed.enabled === 'boolean') {
          config.enabled = parsed.enabled;
        }

        // Validate shortcuts object
        if (parsed.shortcuts && typeof parsed.shortcuts === 'object') {
          const defaultShortcuts = utils.getDefaultShortcuts();

          for (const [action, shortcut] of Object.entries(parsed.shortcuts)) {
            // Only restore valid shortcut actions
            if (defaultShortcuts[action]) {
              if (shortcut && typeof shortcut === 'object') {
                // Validate key is a non-empty string
                if (
                  typeof shortcut.key === 'string' &&
                  shortcut.key.length > 0 &&
                  shortcut.key.length <= 20
                ) {
                  config.shortcuts[action] = {
                    key: shortcut.key,
                    description: defaultShortcuts[action].description,
                    editable: shortcut.editable !== false,
                  };
                }
              }
            }
          }
        }
      } catch (error) {
        console.error('[Shorts] Error loading settings:', error);
      }
    },

    /**
     * Save settings to localStorage with error handling
     * @returns {void}
     */
    saveSettings: () => {
      try {
        const settingsToSave = {
          enabled: config.enabled,
          shortcuts: config.shortcuts,
        };
        localStorage.setItem(config.storageKey, JSON.stringify(settingsToSave));
      } catch (error) {
        console.error('[Shorts] Error saving settings:', error);
      }
    },

    getDefaultShortcuts: () => ({
      seekBackward: { key: 'ArrowLeft', description: t('seekBackward') },
      seekForward: { key: 'ArrowRight', description: t('seekForward') },
      volumeUp: { key: '+', description: t('volumeUp') },
      volumeDown: { key: '-', description: t('volumeDown') },
      mute: { key: 'm', description: t('muteUnmute') },
      toggleCaptions: { key: 'c', description: t('toggleCaptions') },
      showHelp: { key: '?', description: t('showHideHelp'), editable: false },
    }),
  };

  // Optimized feedback system
  const feedback = (() => {
    let element = null;

    const create = () => {
      if (element) return element;

      element = document.createElement('div');
      element.id = 'shorts-keyboard-feedback';
      element.style.cssText = `
          position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
          background:var(--shorts-feedback-bg,rgba(255,255,255,.1));
          backdrop-filter:blur(16px) saturate(150%);
          border:1px solid var(--shorts-feedback-border,rgba(255,255,255,.15));
          border-radius:20px;
          color:var(--shorts-feedback-color,#fff);
          padding:18px 32px;font-size:20px;font-weight:700;
          z-index:10000;opacity:0;visibility:hidden;pointer-events:none;
          transition:all .3s cubic-bezier(.4,0,.2,1);text-align:center;
          box-shadow:0 8px 32px rgba(0,0,0,.4);
          background: rgba(155, 155, 155, 0.15);
          border: 1px solid rgba(255,255,255,0.2);
          box-shadow: 0 8px 32px 0 rgba(31,38,135,0.37);
          backdrop-filter: blur(12px) saturate(180%);
          -webkit-backdrop-filter: blur(12px) saturate(180%);
        `;
      document.body.appendChild(element);
      return element;
    };

    return {
      show: text => {
        state.lastAction = text;
        clearTimeout(state.actionTimeout);

        const el = create();
        el.textContent = text;

        requestAnimationFrame(() => {
          el.style.opacity = '1';
          el.style.visibility = 'visible';
          el.style.transform = 'translate(-50%, -50%) scale(1.05)';
        });

        state.actionTimeout = setTimeout(() => {
          el.style.opacity = '0';
          el.style.visibility = 'hidden';
          el.style.transform = 'translate(-50%, -50%) scale(0.95)';
        }, 1500);
      },
    };
  })();

  // Optimized actions
  const actions = {
    seekBackward: () => {
      const video = getCurrentVideo();
      if (video) {
        video.currentTime = Math.max(0, video.currentTime - 5);
        feedback.show('-5s');
      }
    },

    seekForward: () => {
      const video = getCurrentVideo();
      if (video) {
        video.currentTime = Math.min(video.duration || Infinity, video.currentTime + 5);
        feedback.show('+5s');
      }
    },

    toggleCaptions: () => {
      // Try to click a captions/subtitles button first
      try {
        const buttons = document.querySelectorAll('button[aria-label]');
        for (const b of buttons) {
          const aria = (b.getAttribute('aria-label') || '').toLowerCase();
          if (
            aria.includes('subtit') ||
            aria.includes('caption') ||
            aria.includes('субтит') ||
            aria.includes('субтитр') ||
            aria.includes('cc')
          ) {
            if (b.offsetParent !== null) {
              b.click();
              // It's hard to know exact state, so try to use textTracks below for feedback
              break;
            }
          }
        }
      } catch {
        // Continue to fallback
      }

      const video = getCurrentVideo();
      if (video && video.textTracks && video.textTracks.length) {
        const tracks = Array.from(video.textTracks).filter(
          t => t.kind === 'subtitles' || t.kind === 'captions' || !t.kind
        );
        if (tracks.length) {
          const anyShowing = tracks.some(tr => tr.mode === 'showing');
          tracks.forEach(tr => (tr.mode = anyShowing ? 'hidden' : 'showing'));
          feedback.show(anyShowing ? t('captionsOff') : t('captionsOn'));
          return;
        }
      }

      // If we couldn't toggle via textTracks or button, inform user
      feedback.show(t('captionsUnavailable'));
    },

    volumeUp: () => {
      const video = getCurrentVideo();
      if (video) {
        video.volume = Math.min(1, video.volume + 0.1);
        feedback.show(`${Math.round(video.volume * 100)}%`);
      }
    },

    volumeDown: () => {
      const video = getCurrentVideo();
      if (video) {
        video.volume = Math.max(0, video.volume - 0.1);
        feedback.show(`${Math.round(video.volume * 100)}%`);
      }
    },

    mute: () => {
      const video = getCurrentVideo();

      // Try to click a visible mute/volume button so the player UI updates its icon
      try {
        const buttons = document.querySelectorAll('button[aria-label]');
        for (const b of buttons) {
          const aria = (b.getAttribute('aria-label') || '').toLowerCase();
          if (
            aria.includes('mute') ||
            aria.includes('unmute') ||
            aria.includes('sound') ||
            aria.includes('volume') ||
            aria.includes('звук') ||
            aria.includes('громк')
          ) {
            if (b.offsetParent !== null) {
              b.click();
              // Give the player a moment to update state, then show feedback based on video.muted
              setTimeout(() => {
                const v = getCurrentVideo();
                if (v) feedback.show(v.muted ? '🔇' : '🔊');
              }, 60);
              return;
            }
          }
        }
      } catch {
        // ignore and fallback
      }

      // Fallback: toggle programmatically
      if (video) {
        video.muted = !video.muted;
        feedback.show(video.muted ? '🔇' : '🔊');
      }
    },

    showHelp: () => helpPanel.toggle(),
  };

  // Help panel system
  const helpPanel = (() => {
    let panel = null;

    const create = () => {
      if (panel) return panel;

      panel = document.createElement('div');
      panel.id = 'shorts-keyboard-help';
      panel.className = 'glass-panel shorts-help-panel';
      panel.setAttribute('role', 'dialog');
      panel.setAttribute('aria-modal', 'true');
      panel.tabIndex = -1;

      const render = () => {
        panel.innerHTML = `
            <div class="help-header">
              <h3>${t('keyboardShortcuts')}</h3>
              <button class="ytp-plus-settings-close help-close" type="button" aria-label="${t('closeButton')}">
                <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
                </svg>
              </button>
            </div>
            <div class="help-content">
              ${Object.entries(config.shortcuts)
                .map(
                  ([action, shortcut]) =>
                    `<div class="help-item">
                  <kbd data-action="${action}" ${shortcut.editable === false ? 'class="non-editable"' : ''}>${shortcut.key === ' ' ? 'Space' : shortcut.key}</kbd>
                  <span>${shortcut.description}</span>
                </div>`
                )
                .join('')}
            </div>
            <div class="help-footer">
              <button class="ytp-plus-button ytp-plus-button-primary reset-all-shortcuts">${t('resetAll')}</button>
            </div>
          `;

        panel.querySelector('.help-close').onclick = () => helpPanel.hide();
        panel.querySelector('.reset-all-shortcuts').onclick = () => {
          if (confirm(t('resetAllConfirm'))) {
            config.shortcuts = utils.getDefaultShortcuts();
            utils.saveSettings();
            feedback.show(t('shortcutsReset'));
            render();
          }
        };

        panel.querySelectorAll('kbd[data-action]:not(.non-editable)').forEach(kbd => {
          kbd.onclick = () =>
            editShortcut(kbd.dataset.action, config.shortcuts[kbd.dataset.action].key);
        });
      };

      render();
      document.body.appendChild(panel);
      return panel;
    };

    return {
      show: () => {
        const p = create();
        p.classList.add('visible');
        state.helpVisible = true;
        p.focus();
      },

      hide: () => {
        if (panel) {
          panel.classList.remove('visible');
          state.helpVisible = false;
        }
      },

      toggle: () => (state.helpVisible ? helpPanel.hide() : helpPanel.show()),

      refresh: () => {
        if (panel) {
          panel.remove();
          panel = null;
        }
      },
    };
  })();

  // Shortcut editing
  const editShortcut = (actionKey, currentKey) => {
    const dialog = document.createElement('div');
    dialog.className = 'glass-modal shortcut-edit-dialog';
    dialog.setAttribute('role', 'dialog');
    dialog.setAttribute('aria-modal', 'true');
    dialog.innerHTML = `
        <div class="glass-panel shortcut-edit-content">
          <h4>${t('editShortcut')}: ${config.shortcuts[actionKey].description}</h4>
          <p>${t('pressAnyKey')}</p>
          <div class="current-shortcut">${t('current')}: <kbd>${currentKey === ' ' ? 'Space' : currentKey}</kbd></div>
          <button class="ytp-plus-button ytp-plus-button-primary shortcut-cancel" type="button">${t('cancel')}</button>
        </div>
      `;

    document.body.appendChild(dialog);
    state.editingShortcut = actionKey;

    const handleKey = e => {
      e.preventDefault();
      e.stopPropagation();
      if (e.key === 'Escape') return cleanup();

      const conflict = Object.keys(config.shortcuts).find(
        key => key !== actionKey && config.shortcuts[key].key === e.key
      );
      if (conflict) {
        feedback.show(t('keyAlreadyUsed', { key: e.key }));
        return;
      }

      config.shortcuts[actionKey].key = e.key;
      utils.saveSettings();
      feedback.show(t('shortcutUpdated'));
      helpPanel.refresh();
      cleanup();
    };

    const cleanup = () => {
      document.removeEventListener('keydown', handleKey, true);
      dialog.remove();
      state.editingShortcut = null;
    };

    dialog.querySelector('.shortcut-cancel').onclick = cleanup;
    dialog.onclick = e => {
      const target = /** @type {EventTarget & HTMLElement} */ (e.target);
      if (target === dialog) cleanup();
    };
    document.addEventListener('keydown', handleKey, true);
  };

  // Optimized styles with glassmorphism
  const addStyles = () => {
    if (document.getElementById('shorts-keyboard-styles')) return;

    // ✅ Use StyleManager instead of createElement('style')
    const styles = `
                :root{--shorts-feedback-bg:rgba(255,255,255,.15);--shorts-feedback-border:rgba(255,255,255,.2);--shorts-feedback-color:#fff;--shorts-help-bg:rgba(255,255,255,.15);--shorts-help-border:rgba(255,255,255,.2);--shorts-help-color:#fff;}
                html[dark],body[dark]{--shorts-feedback-bg:rgba(34,34,34,.7);--shorts-feedback-border:rgba(255,255,255,.15);--shorts-feedback-color:#fff;--shorts-help-bg:rgba(34,34,34,.7);--shorts-help-border:rgba(255,255,255,.1);--shorts-help-color:#fff;}
                html:not([dark]){--shorts-feedback-bg:rgba(255,255,255,.95);--shorts-feedback-border:rgba(0,0,0,.08);--shorts-feedback-color:#222;--shorts-help-bg:rgba(255,255,255,.98);--shorts-help-border:rgba(0,0,0,.08);--shorts-help-color:#222;}
                .shorts-help-panel{position:fixed;top:50%;left:25%;transform:translate(-50%,-50%) scale(.9);z-index:10001;opacity:0;visibility:hidden;transition:all .3s ease;width:340px;max-width:95vw;max-height:80vh;overflow:hidden;outline:none;color:var(--shorts-help-color,#fff);}
                .shorts-help-panel.visible{opacity:1;visibility:visible;transform:translate(-50%,-50%) scale(1);}
                .help-header{display:flex;justify-content:space-between;align-items:center;padding:24px 24px 12px;border-bottom:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05);}
                html:not([dark]) .help-header{background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08);}
                .help-header h3{margin:0;font-size:20px;font-weight:700;}
                .help-close{display:flex;align-items:center;justify-content:center;padding:4px;}
                .help-content{padding:18px 24px;max-height:400px;overflow-y:auto;}
                .help-item{display:flex;align-items:center;margin-bottom:14px;gap:18px;}
                .help-item kbd{background:rgba(255,255,255,.15);color:inherit;padding:7px 14px;border-radius:8px;font-family:monospace;font-size:15px;font-weight:700;min-width:60px;text-align:center;border:1.5px solid rgba(255,255,255,.2);cursor:pointer;transition:all .2s;position:relative;}
                html:not([dark]) .help-item kbd{background:rgba(0,0,0,.06);color:#222;border:1.5px solid rgba(0,0,0,.08);}
                .help-item kbd:hover{background:rgba(255,255,255,.22);transform:scale(1.07);}
                .help-item kbd:after{content:"✎";position:absolute;top:-7px;right:-7px;font-size:11px;opacity:0;transition:opacity .2s;}
                .help-item kbd:hover:after{opacity:.7;}
                .help-item kbd.non-editable{cursor:default;opacity:.7;}
                .help-item kbd.non-editable:hover{background:rgba(255,255,255,.15);transform:none;}
                .help-item kbd.non-editable:after{display:none;}
                .help-item span{font-size:15px;color:rgba(255,255,255,.92);}
                html:not([dark]) .help-item span{color:#222;}
                .help-footer{padding:16px 24px 20px;border-top:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05);text-align:center;}
                html:not([dark]) .help-footer{background:rgba(0,0,0,.04);border-top:1px solid rgba(0,0,0,.08);}
                .reset-all-shortcuts{display:inline-flex;align-items:center;justify-content:center;gap:var(--yt-space-sm);}
                .shortcut-edit-dialog{z-index:10002;}
                .shortcut-edit-content{padding:28px 32px;min-width:320px;text-align:center;display:flex;flex-direction:column;gap:var(--yt-space-md);color:inherit;}
                html:not([dark]) .shortcut-edit-content{color:#222;}
                .shortcut-edit-content h4{margin:0 0 14px;font-size:17px;font-weight:700;}
                .shortcut-edit-content p{margin:0 0 18px;font-size:15px;color:rgba(255,255,255,.85);}
                html:not([dark]) .shortcut-edit-content p{color:#222;}
                .current-shortcut{margin:18px 0;font-size:15px;}
                .current-shortcut kbd{background:rgba(255,255,255,.15);padding:5px 12px;border-radius:6px;font-family:monospace;border:1.5px solid rgba(255,255,255,.2);}
                html:not([dark]) .current-shortcut kbd{background:rgba(0,0,0,.06);color:#222;border:1.5px solid rgba(0,0,0,.08);}
                .shortcut-cancel{display:inline-flex;align-items:center;justify-content:center;gap:var(--yt-space-sm);}
                @media(max-width:480px){.shorts-help-panel{width:98vw;max-height:85vh}.help-header{padding:16px 10px 8px 10px}.help-content{padding:12px 10px}.help-item{gap:10px}.help-item kbd{min-width:44px;font-size:13px;padding:5px 7px}.shortcut-edit-content{margin:20px;min-width:auto}}
                #shorts-keyboard-feedback{background:var(--shorts-feedback-bg,rgba(255,255,255,.15));color:var(--shorts-feedback-color,#fff);border:1.5px solid var(--shorts-feedback-border,rgba(255,255,255,.2));border-radius:20px;box-shadow:0 8px 32px 0 rgba(31,38,135,.37);backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);}
                html:not([dark]) #shorts-keyboard-feedback{background:var(--shorts-feedback-bg,rgba(255,255,255,.95));color:var(--shorts-feedback-color,#222);border:1.5px solid var(--shorts-feedback-border,rgba(0,0,0,.08));}
            `;
    YouTubeUtils.StyleManager.add('shorts-keyboard-styles', styles);
  };

  // Main keyboard handler
  const handleKeydown = e => {
    if (
      !config.enabled ||
      !utils.isInShortsPage() ||
      utils.isInputFocused() ||
      state.editingShortcut
    ) {
      return;
    }

    let key = e.key;
    if (e.code === 'NumpadAdd') key = '+';
    else if (e.code === 'NumpadSubtract') key = '-';

    const action = Object.keys(config.shortcuts).find(k => config.shortcuts[k].key === key);
    if (action && actions[action]) {
      e.preventDefault();
      e.stopPropagation();
      actions[action]();
    }
  };

  // Initialize
  const init = () => {
    utils.loadSettings();
    addStyles();

    // ✅ Register listeners in cleanupManager
    YouTubeUtils.cleanupManager.registerListener(document, 'keydown', handleKeydown, true);

    const clickHandler = e => {
      const target = /** @type {EventTarget & HTMLElement} */ (e.target);
      if (state.helpVisible && target.closest && !target.closest('#shorts-keyboard-help')) {
        helpPanel.hide();
      }
    };
    YouTubeUtils.cleanupManager.registerListener(document, 'click', clickHandler);

    document.addEventListener('keydown', e => {
      if (e.key === 'Escape' && state.helpVisible) {
        e.preventDefault();
        helpPanel.hide();
      }
    });
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

  if (utils.isInShortsPage() && !localStorage.getItem('shorts_keyboard_help_shown')) {
    setTimeout(() => {
      feedback.show('Press ? for shortcuts');
      localStorage.setItem('shorts_keyboard_help_shown', 'true');
    }, 2000);
  }
})();

// --- MODULE: stats.js ---

// Stats button and menu
(function () {
  'use strict';

  // Do not run this module inside YouTube Studio (studio.youtube.com)
  const isStudioPage = () => {
    try {
      const host = location.hostname || '';
      const href = location.href || '';
      return (
        host.includes('studio.youtube.com') ||
        host.includes('studio.') ||
        href.includes('studio.youtube.com')
      );
    } catch {
      return false;
    }
  };

  if (isStudioPage()) return;

  // Internationalization (i18n) system
  const i18n = {
    en: {
      stats: 'Stats',
      channelStats: 'Channel Stats',
      videoStats: 'Video Stats',
      liveStats: 'Live Stats',
      shortsStats: 'Shorts Stats',
      close: 'Close',
      channel: 'Channel',
      live: 'Live',
      shorts: 'Shorts',
      statisticsButton: 'Statistics Button',
      statisticsButtonDescription:
        'Show statistics button on videos and channel menu for quick access to statistics',
    },
    ru: {
      stats: 'Статистика',
      channelStats: 'Статистика канала',
      videoStats: 'Статистика видео',
      liveStats: 'Статистика трансляции',
      shortsStats: 'Статистика Shorts',
      close: 'Закрыть',
      channel: 'Канал',
      live: 'Эфир',
      shorts: 'Shorts',
      statisticsButton: 'Кнопка статистики',
      statisticsButtonDescription:
        'Показывать кнопку статистики на видео и в меню канала для быстрого доступа',
    },
  };

  // Get browser language
  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  // Translation function
  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  // Glassmorphism styles for stats button and menu (shorts-keyboard-feedback look)
  const styles = `
            .videoStats{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;margin-left:8px;background:rgba(255,255,255,0.12);box-shadow:0 12px 30px rgba(0,0,0,0.32);backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);border:1.25px solid rgba(255,255,255,0.12);transition:transform .18s ease,background .18s}
            html[dark] .videoStats{background:rgba(24,24,24,0.68);border:1.25px solid rgba(255,255,255,0.08)}html:not([dark]) .videoStats{background:rgba(255,255,255,0.12);border:1.25px solid rgba(0,0,0,0.06)}.videoStats:hover{transform:translateY(-2px)}.videoStats svg{width:18px;height:18px;fill:var(--yt-spec-text-primary,#030303)}html[dark] .videoStats svg{fill:#fff}html:not([dark]) .videoStats svg{fill:#222}
            .shortsStats{display:flex;align-items:center;justify-content:center;margin-top:16px;margin-bottom:16px;width:48px;height:48px;border-radius:50%;cursor:pointer;background:rgba(255,255,255,0.12);box-shadow:0 12px 30px rgba(0,0,0,0.32);backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);border:1.25px solid rgba(255,255,255,0.12);transition:transform .22s ease}html[dark] .shortsStats{background:rgba(24,24,24,0.68);border:1.25px solid rgba(255,255,255,0.08)}html:not([dark]) .shortsStats{background:rgba(255,255,255,0.12);border:1.25px solid rgba(0,0,0,0.06)}
            .shortsStats:hover{transform:translateY(-3px)}.shortsStats svg{width:24px;height:24px;fill:#222}html[dark] .shortsStats svg{fill:#fff}html:not([dark]) .shortsStats svg{fill:#222}
            .stats-menu-container{position:relative;display:inline-block}.stats-horizontal-menu{position:absolute;display:flex;left:100%;top:0;height:100%;visibility:hidden;opacity:0;transition:visibility 0s,opacity 0.2s linear;z-index:100}.stats-menu-container:hover .stats-horizontal-menu{visibility:visible;opacity:1}.stats-menu-button{margin-left:8px;white-space:nowrap}
            /* Modal overlay and content with stronger glassmorphism */
            .stats-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:linear-gradient(rgba(0,0,0,0.45),rgba(0,0,0,0.55));z-index:99999;display:flex;align-items:center;justify-content:center;animation:fadeInModal .18s;backdrop-filter:blur(20px) saturate(170%);-webkit-backdrop-filter:blur(20px) saturate(170%)}
            .stats-modal-content{background:rgba(24,24,24,0.72);border-radius:20px;box-shadow:0 18px 40px rgba(0,0,0,0.45);max-width:78vw;max-height:88vh;overflow:auto;position:relative;display:flex;flex-direction:column;align-items:center;gap:12px;animation:scaleInModal .18s;border:1.5px solid rgba(255,255,255,0.08);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%)}
            html[dark] .stats-modal-content{background:rgba(24,24,24,0.72)}html:not([dark]) .stats-modal-content{background:rgba(255,255,255,0.92);color:#222;border:1.25px solid rgba(0,0,0,0.06)}
            /* New layout: wrapper to put iframe and action buttons side-by-side */
            .stats-modal-wrapper{display:flex;align-items:flex-start;gap:12px}
            .stats-modal-actions{display:flex;flex-direction:column;gap:10px;margin-top:6px}
            .stats-modal-action-btn{width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,0.06);border:1px solid rgba(255,255,255,0.06);display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 6px 18px rgba(0,0,0,0.2);transition:transform .12s ease,background .12s;color:#fff}
            .stats-modal-action-btn:hover{transform:translateY(-2px)}
            /* keep the old absolute close for fallback as a separately-named class */
            .stats-modal-close-abs{position:absolute;top:12px;right:16px;background:transparent;color:#fff;border:none;font-size:26px;line-height:1;width:40px;height:40px;cursor:pointer;transition:transform .18s ease,color .18s;z-index:3;display:flex;align-items:center;justify-content:center;border-radius:10px}
            .stats-modal-close-abs:hover{color:#ff6b6b;transform:rotate(90deg) scale(1.25);}
            .stats-modal-iframe{width:70vw;height:68vh;box-shadow:0 12px 36px rgba(0,0,0,0.36);background:#111;border:1px solid rgba(255,255,255,0.06)}
            .stats-modal-title{font-size:18px;font-weight:600;color:#fff;margin-bottom:6px;text-align:center;text-shadow:0 6px 18px rgba(0,0,0,0.25)}html:not([dark]) .stats-modal-title{color:#111}
            @keyframes fadeInModal{from{opacity:0}to{opacity:1}}@keyframes scaleInModal{from{transform:scale(0.985);opacity:0.6}to{transform:scale(1);opacity:1}}
        `;

  // Settings state
  const SETTINGS_KEY = 'youtube_stats_button_enabled';
  let statsButtonEnabled = localStorage.getItem(SETTINGS_KEY) !== 'false';

  let previousUrl = location.href;
  let isChecking = false;
  let experimentalNavListenerKey = null;
  let channelFeatures = {
    hasStreams: false,
    hasShorts: false,
  };

  /**
   * Rate limiter for API calls
   * @type {Object}
   */
  const rateLimiter = {
    requests: new Map(),
    maxRequests: 10,
    timeWindow: 60000, // 1 minute

    /**
     * Check if request is allowed
     * @param {string} key - Request identifier
     * @returns {boolean} Whether request is allowed
     */
    canRequest: key => {
      const now = Date.now();
      const requests = rateLimiter.requests.get(key) || [];

      // Remove old requests outside time window
      const recentRequests = requests.filter(time => now - time < rateLimiter.timeWindow);

      if (recentRequests.length >= rateLimiter.maxRequests) {
        console.warn(
          `[YouTube+][Stats] Rate limit exceeded for ${key}. Max ${rateLimiter.maxRequests} requests per minute.`
        );
        return false;
      }

      recentRequests.push(now);
      rateLimiter.requests.set(key, recentRequests);
      return true;
    },

    /**
     * Clear rate limiter state
     */
    clear: () => {
      rateLimiter.requests.clear();
    },
  };

  function addStyles() {
    if (!document.querySelector('#youtube-enhancer-styles')) {
      // ✅ Use StyleManager instead of createElement('style')
      YouTubeUtils.StyleManager.add('youtube-enhancer-styles', styles);
    }
  }

  /**
   * Get current video URL with validation
   * @returns {string|null} Valid YouTube video URL or null
   */
  function getCurrentVideoUrl() {
    try {
      const url = window.location.href;

      // Validate URL is from YouTube domain
      if (!url.includes('youtube.com')) {
        return null;
      }

      const urlParams = new URLSearchParams(window.location.search);
      const videoId = urlParams.get('v');

      // Validate video ID format (11 characters, alphanumeric + - and _)
      if (videoId && /^[a-zA-Z0-9_-]{11}$/.test(videoId)) {
        return `https://www.youtube.com/watch?v=${videoId}`;
      }

      const shortsMatch = url.match(/\/shorts\/([^?]+)/);
      if (shortsMatch && /^[a-zA-Z0-9_-]{11}$/.test(shortsMatch[1])) {
        return `https://www.youtube.com/shorts/${shortsMatch[1]}`;
      }

      return null;
    } catch (error) {
      YouTubeUtils?.logError?.('Stats', 'Failed to get video URL', error);
      return null;
    }
  }

  /**
   * Get channel identifier with validation
   * @returns {string} Channel identifier
   */
  function getChannelIdentifier() {
    try {
      const url = window.location.href;
      let identifier = '';

      if (url.includes('/channel/')) {
        identifier = url.split('/channel/')[1].split('/')[0];
      } else if (url.includes('/@')) {
        identifier = url.split('/@')[1].split('/')[0];
      }

      // Validate identifier (alphanumeric, dashes, underscores)
      if (identifier && /^[a-zA-Z0-9_-]+$/.test(identifier)) {
        return identifier;
      }

      return '';
    } catch (error) {
      YouTubeUtils?.logError?.('Stats', 'Failed to get channel identifier', error);
      return '';
    }
  }

  /**
   * Check channel tabs with rate limiting and enhanced security
   * @param {string} url - Channel URL to check
   * @returns {Promise<void>}
   */
  async function checkChannelTabs(url) {
    if (isChecking) return;

    // Validate URL
    if (!url || typeof url !== 'string') {
      return;
    }

    try {
      // Parse and validate URL
      const parsedUrl = new URL(url);
      if (parsedUrl.hostname !== 'www.youtube.com' && parsedUrl.hostname !== 'youtube.com') {
        console.warn('[YouTube+][Stats] Invalid domain for channel check');
        return;
      }
    } catch (error) {
      YouTubeUtils?.logError?.('Stats', 'Invalid URL for channel check', error);
      return;
    }

    // Rate limiting
    if (!rateLimiter.canRequest('checkChannelTabs')) {
      return;
    }

    isChecking = true;

    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout

      const response = await fetch(url, {
        credentials: 'same-origin',
        signal: controller.signal,
        headers: {
          Accept: 'text/html',
        },
      });

      clearTimeout(timeoutId);

      if (!response.ok) {
        console.warn(`[YouTube+][Stats] HTTP ${response.status} when checking channel tabs`);
        isChecking = false;
        return;
      }

      const html = await response.text();

      // Limit response size to prevent memory issues
      if (html.length > 5000000) {
        // 5MB limit
        console.warn('[YouTube+][Stats] Response too large, skipping parse');
        isChecking = false;
        return;
      }

      const match = html.match(/var ytInitialData = (.+?);<\/script>/);

      if (!match || !match[1]) {
        isChecking = false;
        return;
      }

      // Safe JSON parse with try-catch
      let data;
      try {
        data = JSON.parse(match[1]);
      } catch (parseError) {
        YouTubeUtils?.logError?.('Stats', 'Failed to parse ytInitialData', parseError);
        isChecking = false;
        return;
      }

      const tabs = data?.contents?.twoColumnBrowseResultsRenderer?.tabs || [];

      let hasStreams = false;
      let hasShorts = false;

      tabs.forEach(tab => {
        const tabUrl = tab?.tabRenderer?.endpoint?.commandMetadata?.webCommandMetadata?.url;
        if (tabUrl && typeof tabUrl === 'string') {
          if (/\/streams$/.test(tabUrl)) hasStreams = true;
          if (/\/shorts$/.test(tabUrl)) hasShorts = true;
        }
      });

      channelFeatures = {
        hasStreams: hasStreams,
        hasShorts: hasShorts,
      };

      const existingMenu = document.querySelector('.stats-menu-container');
      if (existingMenu) {
        existingMenu.remove();
        createStatsMenu();
      }
    } catch (error) {
      if (error.name === 'AbortError') {
        console.warn('[YouTube+][Stats] Channel check timed out');
      } else {
        YouTubeUtils?.logError?.('Stats', 'Failed to check channel tabs', error);
      }
    } finally {
      isChecking = false;
    }
  }

  /**
   * Check if URL is a channel page
   * @param {string} url - URL to check
   * @returns {boolean} Whether URL is a channel page
   */
  function isChannelPage(url) {
    try {
      return (
        url &&
        typeof url === 'string' &&
        url.includes('youtube.com/') &&
        (url.includes('/channel/') || url.includes('/@')) &&
        !url.includes('/video/') &&
        !url.includes('/watch')
      );
    } catch {
      return false;
    }
  }

  /**
   * Check for URL changes with debouncing
   */
  const checkUrlChange =
    YouTubeUtils?.debounce?.(() => {
      try {
        const currentUrl = location.href;
        if (currentUrl !== previousUrl) {
          previousUrl = currentUrl;
          if (isChannelPage(currentUrl)) {
            setTimeout(() => checkChannelTabs(currentUrl), 500);
          }
        }
      } catch (error) {
        YouTubeUtils?.logError?.('Stats', 'URL change check failed', error);
      }
    }, 300) ||
    function () {
      try {
        const currentUrl = location.href;
        if (currentUrl !== previousUrl) {
          previousUrl = currentUrl;
          if (isChannelPage(currentUrl)) {
            setTimeout(() => checkChannelTabs(currentUrl), 500);
          }
        }
      } catch (error) {
        console.error('[YouTube+][Stats] URL change check failed:', error);
      }
    };

  function createStatsIcon(isShorts = false) {
    const icon = document.createElement('div');
    icon.className = isShorts ? 'shortsStats' : 'videoStats';

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 512 512');

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute(
      'd',
      'M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z'
    );

    svg.appendChild(path);
    icon.appendChild(svg);

    icon.addEventListener('click', function (e) {
      e.preventDefault();
      e.stopPropagation();
      const videoUrl = getCurrentVideoUrl();
      if (videoUrl) {
        openStatsModal(`https://stats.afkarxyz.fun/?directVideo=${encodeURIComponent(videoUrl)}`);
      }
    });

    return icon;
  }

  function insertIconForRegularVideo() {
    if (!statsButtonEnabled) return;
    const targetSelector = '#owner';
    const target = document.querySelector(targetSelector);

    if (target && !document.querySelector('.videoStats')) {
      const statsIcon = createStatsIcon();
      target.appendChild(statsIcon);
    }
  }

  function insertIconForShorts() {
    if (!statsButtonEnabled) return false;
    const likeButtonContainer = document.querySelector(
      'ytd-reel-video-renderer[is-active] #like-button'
    );

    if (likeButtonContainer && !document.querySelector('.shortsStats')) {
      const iconDiv = createStatsIcon(true);
      likeButtonContainer.parentNode.insertBefore(iconDiv, likeButtonContainer);
      return true;
    }
    return false;
  }

  function createButton(text, svgPath, viewBox, className, onClick) {
    const buttonViewModel = document.createElement('button-view-model');
    buttonViewModel.className = `yt-spec-button-view-model ${className}-view-model`;

    const button = document.createElement('button');
    button.className = `yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment ${className}-button`;
    button.setAttribute('aria-disabled', 'false');
    button.setAttribute('aria-label', text);
    button.style.display = 'flex';
    button.style.alignItems = 'center';
    button.style.justifyContent = 'center';
    button.style.gap = '8px';

    button.addEventListener('click', function (e) {
      e.preventDefault();
      e.stopPropagation();
      onClick();
    });

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', viewBox);
    svg.style.width = '20px';
    svg.style.height = '20px';
    svg.style.fill = 'currentColor';

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', svgPath);
    svg.appendChild(path);

    const buttonText = document.createElement('div');
    buttonText.className = `yt-spec-button-shape-next__button-text-content ${className}-text`;
    buttonText.textContent = text;
    buttonText.style.display = 'flex';
    buttonText.style.alignItems = 'center';

    const touchFeedback = document.createElement('yt-touch-feedback-shape');
    touchFeedback.style.borderRadius = 'inherit';

    const touchFeedbackDiv = document.createElement('div');
    touchFeedbackDiv.className =
      'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
    touchFeedbackDiv.setAttribute('aria-hidden', 'true');

    const strokeDiv = document.createElement('div');
    strokeDiv.className = 'yt-spec-touch-feedback-shape__stroke';

    const fillDiv = document.createElement('div');
    fillDiv.className = 'yt-spec-touch-feedback-shape__fill';

    touchFeedbackDiv.appendChild(strokeDiv);
    touchFeedbackDiv.appendChild(fillDiv);
    touchFeedback.appendChild(touchFeedbackDiv);

    button.appendChild(svg);
    button.appendChild(buttonText);
    button.appendChild(touchFeedback);
    buttonViewModel.appendChild(button);

    return buttonViewModel;
  }

  /**
   * Open stats modal with enhanced security
   * @param {string} url - URL to display in iframe
   */
  function openStatsModal(url) {
    // Validate URL
    if (!url || typeof url !== 'string') {
      console.error('[YouTube+][Stats] Invalid URL for modal');
      return;
    }

    try {
      // Parse and validate URL
      const parsedUrl = new URL(url);
      const allowedDomains = ['stats.afkarxyz.fun', 'livecounts.io', 'api.livecounts.io'];

      if (!allowedDomains.includes(parsedUrl.hostname)) {
        console.error(
          `[YouTube+][Stats] URL domain ${parsedUrl.hostname} not in allowlist:`,
          allowedDomains
        );
        return;
      }

      // Ensure HTTPS
      if (parsedUrl.protocol !== 'https:') {
        console.error('[YouTube+][Stats] Only HTTPS URLs are allowed');
        return;
      }
    } catch (error) {
      YouTubeUtils?.logError?.('Stats', 'Invalid URL for modal', error);
      return;
    }

    // Remove any existing overlays
    document.querySelectorAll('.stats-modal-overlay').forEach(m => m.remove());

    const overlay = document.createElement('div');
    overlay.className = 'stats-modal-overlay';

    const content = document.createElement('div');
    content.className = 'stats-modal-content';

    // Create iframe (main content)
    const iframe = document.createElement('iframe');
    iframe.className = 'stats-modal-iframe';
    iframe.src = url;
    iframe.setAttribute('frameborder', '0');
    iframe.setAttribute('allowfullscreen', 'true');
    iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
    iframe.style.background = '#222';

    // Action buttons column (to the right of iframe)
    const actionsDiv = document.createElement('div');
    actionsDiv.className = 'stats-modal-actions';

    // Close button (first) — styled like the Open button (use action-btn class)
    const closeBtn = document.createElement('button');
    closeBtn.className = 'stats-modal-action-btn stats-modal-close-btn';
    closeBtn.innerHTML = `
      <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
      </svg>
    `;
    closeBtn.title = t('close');
    closeBtn.setAttribute('aria-label', t('close'));
    closeBtn.addEventListener('click', e => {
      e.preventDefault();
      e.stopPropagation();
      overlay.remove();
    });

    // Open in new tab button (second)
    const openBtn = document.createElement('button');
    openBtn.className = 'stats-modal-open stats-modal-action-btn';
    openBtn.innerHTML = `
      <svg fill="currentColor" viewBox="0 0 24 24" width="18" height="18" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
        <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
        <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
        <g id="SVGRepo_iconCarrier"><path d="M14.293,9.707a1,1,0,0,1,0-1.414L18.586,4H16a1,1,0,0,1,0-2h5a1,1,0,0,1,1,1V8a1,1,0,0,1-2,0V5.414L15.707,9.707a1,1,0,0,1-1.414,0ZM3,22H8a1,1,0,0,0,0-2H5.414l4.293-4.293a1,1,0,0,0-1.414-1.414L4,18.586V16a1,1,0,0,0-2,0v5A1,1,0,0,0,3,22Z"></path></g>
      </svg>
    `;
    openBtn.title = t('clickToOpen');
    openBtn.setAttribute('aria-label', t('clickToOpen'));
    openBtn.addEventListener('click', e => {
      e.preventDefault();
      e.stopPropagation();
      window.open(url, '_blank');
    });

    // Append buttons into actions column (close first, then open)
    actionsDiv.appendChild(closeBtn);
    actionsDiv.appendChild(openBtn);

    // Wrapper to hold iframe + actions side-by-side
    const wrapper = document.createElement('div');
    wrapper.className = 'stats-modal-wrapper';
    // Put iframe inside content area so content styling remains
    content.appendChild(iframe);

    wrapper.appendChild(content);
    wrapper.appendChild(actionsDiv);
    overlay.appendChild(wrapper);

    // Close overlay when clicking outside
    overlay.addEventListener('click', e => {
      if (e.target === overlay) overlay.remove();
    });

    // ESC to close
    function escHandler(e) {
      if (e.key === 'Escape') {
        overlay.remove();
        window.removeEventListener('keydown', escHandler, true);
      }
    }
    window.addEventListener('keydown', escHandler, true);

    document.body.appendChild(overlay);
  }

  function createStatsMenu() {
    if (!statsButtonEnabled) return undefined;
    if (document.querySelector('.stats-menu-container')) {
      return undefined;
    }

    const containerDiv = document.createElement('div');
    containerDiv.className = 'yt-flexible-actions-view-model-wiz__action stats-menu-container';

    const mainButtonViewModel = document.createElement('button-view-model');
    mainButtonViewModel.className = 'yt-spec-button-view-model main-stats-view-model';

    const mainButton = document.createElement('button');
    mainButton.className =
      'yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment main-stats-button';
    mainButton.setAttribute('aria-disabled', 'false');
    mainButton.setAttribute('aria-label', t('stats'));
    mainButton.style.display = 'flex';
    mainButton.style.alignItems = 'center';
    mainButton.style.justifyContent = 'center';
    mainButton.style.gap = '8px';

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 512 512');
    svg.style.width = '20px';
    svg.style.height = '20px';
    svg.style.fill = 'currentColor';

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute(
      'd',
      'M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z'
    );
    svg.appendChild(path);

    const buttonText = document.createElement('div');
    buttonText.className = 'yt-spec-button-shape-next__button-text-content main-stats-text';
    buttonText.textContent = t('stats');
    buttonText.style.display = 'flex';
    buttonText.style.alignItems = 'center';

    const touchFeedback = document.createElement('yt-touch-feedback-shape');
    touchFeedback.style.borderRadius = 'inherit';

    const touchFeedbackDiv = document.createElement('div');
    touchFeedbackDiv.className =
      'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
    touchFeedbackDiv.setAttribute('aria-hidden', 'true');

    const strokeDiv = document.createElement('div');
    strokeDiv.className = 'yt-spec-touch-feedback-shape__stroke';

    const fillDiv = document.createElement('div');
    fillDiv.className = 'yt-spec-touch-feedback-shape__fill';

    touchFeedbackDiv.appendChild(strokeDiv);
    touchFeedbackDiv.appendChild(fillDiv);
    touchFeedback.appendChild(touchFeedbackDiv);

    mainButton.appendChild(svg);
    mainButton.appendChild(buttonText);
    mainButton.appendChild(touchFeedback);
    mainButtonViewModel.appendChild(mainButton);
    containerDiv.appendChild(mainButtonViewModel);

    const horizontalMenu = document.createElement('div');
    horizontalMenu.className = 'stats-horizontal-menu';

    const channelButtonContainer = document.createElement('div');
    channelButtonContainer.className = 'stats-menu-button channel-stats-container';

    const channelButton = createButton(
      t('channel'),
      'M64 48c-8.8 0-16 7.2-16 16l0 288c0 8.8 7.2 16 16 16l512 0c8.8 0 16-7.2 16-16l0-288c0-8.8-7.2-16-16-16L64 48zM0 64C0 28.7 28.7 0 64 0L576 0c35.3 0 64 28.7 64 64l0 288c0 35.3-28.7 64-64 64L64 416c-35.3 0-64-28.7-64-64L0 64zM120 464l400 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-400 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z',
      '0 0 640 512',
      'channel-stats',
      () => {
        const channelId = getChannelIdentifier();
        if (channelId) {
          openStatsModal(`https://stats.afkarxyz.fun/?directChannel=${channelId}`);
        }
      }
    );

    channelButtonContainer.appendChild(channelButton);
    horizontalMenu.appendChild(channelButtonContainer);

    if (channelFeatures.hasStreams) {
      const liveButtonContainer = document.createElement('div');
      liveButtonContainer.className = 'stats-menu-button live-stats-container';

      const liveButton = createButton(
        t('live'),
        'M99.8 69.4c10.2 8.4 11.6 23.6 3.2 33.8C68.6 144.7 48 197.9 48 256s20.6 111.3 55 152.8c8.4 10.2 7 25.3-3.2 33.8s-25.3 7-33.8-3.2C24.8 389.6 0 325.7 0 256S24.8 122.4 66 72.6c8.4-10.2 23.6-11.6 33.8-3.2zm376.5 0c10.2-8.4 25.3-7 33.8 3.2c41.2 49.8 66 113.8 66 183.4s-24.8 133.6-66 183.4c-8.4 10.2-23.6 11.6-33.8 3.2s-11.6-23.6-3.2-33.8c34.3-41.5 55-94.7 55-152.8s-20.6-111.3-55-152.8c-8.4-10.2-7-25.3 3.2-33.8zM248 256a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zm-61.1-78.5C170 199.2 160 226.4 160 256s10 56.8 26.9 78.5c8.1 10.5 6.3 25.5-4.2 33.7s-25.5 6.3-33.7-4.2c-23.2-29.8-37-67.3-37-108s13.8-78.2 37-108c8.1-10.5 23.2-12.3 33.7-4.2s12.3 23.2 4.2 33.7zM427 148c23.2 29.8 37 67.3 37 108s-13.8 78.2-37 108c-8.1 10.5-23.2 12.3-33.7 4.2s-12.3-23.2-4.2-33.7C406 312.8 416 285.6 416 256s-10-56.8-26.9-78.5c-8.1-10.5-6.3-25.5 4.2-33.7s25.5-6.3 33.7 4.2z',
        '0 0 576 512',
        'live-stats',
        () => {
          const channelId = getChannelIdentifier();
          if (channelId) {
            openStatsModal(`https://stats.afkarxyz.fun/?directStream=${channelId}`);
          }
        }
      );

      liveButtonContainer.appendChild(liveButton);
      horizontalMenu.appendChild(liveButtonContainer);
    }

    if (channelFeatures.hasShorts) {
      const shortsButtonContainer = document.createElement('div');
      shortsButtonContainer.className = 'stats-menu-button shorts-stats-container';

      const shortsButton = createButton(
        t('shorts'),
        'M80 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16l224 0c8.8 0 16-7.2 16-16l0-384c0-8.8-7.2-16-16-16L80 48zM16 64C16 28.7 44.7 0 80 0L304 0c35.3 0 64 28.7 64 64l0 384c0 35.3-28.7 64-64 64L80 512c-35.3 0-64-28.7-64-64L16 64zM160 400l64 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-64 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z',
        '0 0 384 512',
        'shorts-stats',
        () => {
          const channelId = getChannelIdentifier();
          if (channelId) {
            openStatsModal(`https://stats.afkarxyz.fun/?directShorts=${channelId}`);
          }
        }
      );

      shortsButtonContainer.appendChild(shortsButton);
      horizontalMenu.appendChild(shortsButtonContainer);
    }

    containerDiv.appendChild(horizontalMenu);

    const joinButton = document.querySelector(
      '.yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)'
    );
    if (joinButton) {
      joinButton.parentNode.appendChild(containerDiv);
    } else {
      const buttonContainer = document.querySelector('#subscribe-button + #buttons');
      if (buttonContainer) {
        buttonContainer.appendChild(containerDiv);
      }
    }

    return containerDiv;
  }

  function checkAndAddMenu() {
    if (!statsButtonEnabled) return;
    const joinButton = document.querySelector(
      '.yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)'
    );
    const statsMenu = document.querySelector('.stats-menu-container');

    if (joinButton && !statsMenu) {
      createStatsMenu();
    }
  }

  function checkAndInsertIcon() {
    if (!statsButtonEnabled) return;
    const isShorts = window.location.pathname.includes('/shorts/');
    if (isShorts) {
      const shortsObserver = new MutationObserver((_mutations, observer) => {
        if (insertIconForShorts()) {
          observer.disconnect();
        }
      });

      // ✅ Register observer in cleanupManager
      YouTubeUtils.cleanupManager.registerObserver(shortsObserver);

      const shortsContainer = document.querySelector('ytd-shorts');
      if (shortsContainer) {
        shortsObserver.observe(shortsContainer, {
          childList: true,
          subtree: true,
        });
        insertIconForShorts();
      }
    } else if (getCurrentVideoUrl()) {
      insertIconForRegularVideo();
    }
  }

  function addSettingsUI() {
    const section = document.querySelector(
      '.ytp-plus-settings-section[data-section="experimental"]'
    );
    if (!section || section.querySelector('.stats-button-settings-item')) return;

    const item = document.createElement('div');
    item.className = 'ytp-plus-settings-item stats-button-settings-item';
    item.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('statisticsButton')}</label>
          <div class="ytp-plus-settings-item-description">${t('statisticsButtonDescription')}</div>
        </div>
        <input type="checkbox" class="ytp-plus-settings-checkbox" ${statsButtonEnabled ? 'checked' : ''}>
      `;
    section.appendChild(item);

    item.querySelector('input').addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      statsButtonEnabled = target.checked;
      localStorage.setItem(SETTINGS_KEY, statsButtonEnabled ? 'true' : 'false');
      // Remove all stats buttons and menus
      document
        .querySelectorAll('.videoStats,.shortsStats,.stats-menu-container')
        .forEach(el => el.remove());
      if (statsButtonEnabled) {
        checkAndInsertIcon();
        checkAndAddMenu();
      }
    });
  }

  // Observe settings modal for experimental section
  const settingsObserver = new MutationObserver(mutations => {
    for (const { addedNodes } of mutations) {
      for (const node of addedNodes) {
        if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
          setTimeout(addSettingsUI, 50);
        }
      }
    }
    if (document.querySelector('.ytp-plus-settings-nav-item[data-section="experimental"].active')) {
      setTimeout(addSettingsUI, 50);
    }
  });

  // ✅ Register observer in cleanupManager
  YouTubeUtils.cleanupManager.registerObserver(settingsObserver);

  // ✅ Safe observe with document.body check
  if (document.body) {
    settingsObserver.observe(document.body, { childList: true, subtree: true });
  } else {
    document.addEventListener('DOMContentLoaded', () => {
      settingsObserver.observe(document.body, { childList: true, subtree: true });
    });
  }

  const handleExperimentalNavClick = e => {
    const target = /** @type {EventTarget & HTMLElement} */ (e.target);
    if (
      target.classList?.contains('ytp-plus-settings-nav-item') &&
      target.dataset?.section === 'experimental'
    ) {
      setTimeout(addSettingsUI, 50);
    }
  };

  if (!experimentalNavListenerKey) {
    experimentalNavListenerKey = YouTubeUtils.cleanupManager.registerListener(
      document,
      'click',
      handleExperimentalNavClick,
      true
    );
  }

  function init() {
    addStyles();
    if (statsButtonEnabled) {
      checkAndInsertIcon();
      checkAndAddMenu();
    }

    history.pushState = (function (f) {
      /** @this {any} */
      return function () {
        const fAny = /** @type {any} */ (f);
        const result = fAny.apply(this, arguments);
        checkUrlChange();
        return result;
      };
    })(history.pushState);

    history.replaceState = (function (f) {
      /** @this {any} */
      return function () {
        const fAny = /** @type {any} */ (f);
        const result = fAny.apply(this, arguments);
        checkUrlChange();
        return result;
      };
    })(history.replaceState);

    window.addEventListener('popstate', checkUrlChange);

    if (isChannelPage(location.href)) {
      checkChannelTabs(location.href);
    }
  }

  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        if (statsButtonEnabled) {
          checkAndInsertIcon();
          checkAndAddMenu();
        }
      }
    }
  });

  // ✅ Safe observe with document.body check
  if (document.body) {
    observer.observe(document.body, { childList: true, subtree: true });
  } else {
    document.addEventListener('DOMContentLoaded', () => {
      observer.observe(document.body, { childList: true, subtree: true });
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

  window.addEventListener('yt-navigate-finish', () => {
    if (statsButtonEnabled) {
      checkAndInsertIcon();
      checkAndAddMenu();
      if (isChannelPage(location.href)) {
        checkChannelTabs(location.href);
      }
    }
  });

  document.addEventListener('yt-action', function (event) {
    const ev = /** @type {CustomEvent<any>} */ (event);
    if (ev.detail && ev.detail.actionName === 'yt-reload-continuation-items-command') {
      if (statsButtonEnabled) {
        checkAndInsertIcon();
        checkAndAddMenu();
      }
    }
  });
})();

// count
(function () {
  'use strict';

  // Do not run this module inside YouTube Studio (studio.youtube.com)
  const isStudioPageCount = () => {
    try {
      const host = location.hostname || '';
      const href = location.href || '';
      return (
        host.includes('studio.youtube.com') ||
        host.includes('studio.') ||
        href.includes('studio.youtube.com')
      );
    } catch {
      return false;
    }
  };

  if (isStudioPageCount()) return;

  // Internationalization
  const i18n = {
    en: {
      displayOptions: 'Display Options',
      channelStatsTitle: 'Channel Stats',
      channelStatsDescription: 'Show live subscriber/views/videos overlay on channel banner',
      subscribers: 'Subscribers',
      views: 'Views',
      videos: 'Videos',
      fontFamily: 'Font Family',
      fontSize: 'Font Size',
      updateInterval: 'Update Interval',
      backgroundOpacity: 'Background Opacity',
      overlayAriaLabel: 'YouTube Channel Statistics Overlay',
      settingsAriaLabel: 'Open settings menu',
      settingsMenuAriaLabel: 'Statistics display settings',
      unknown: 'Unknown',
    },
    ru: {
      displayOptions: 'Параметры отображения',
      channelStatsTitle: 'Статистика канала',
      channelStatsDescription:
        'Показывать наложение с числом подписчиков/просмотров/видео на баннере канала',
      subscribers: 'Подписчики',
      views: 'Просмотры',
      videos: 'Видео',
      fontFamily: 'Шрифт',
      fontSize: 'Размер шрифта',
      updateInterval: 'Интервал обновления',
      backgroundOpacity: 'Прозрачность фона',
      overlayAriaLabel: 'Наложение статистики канала YouTube',
      settingsAriaLabel: 'Открыть меню настроек',
      settingsMenuAriaLabel: 'Настройки отображения статистики',
      unknown: 'Неизвестно',
    },
  };

  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  // Enhanced configuration with better defaults
  const CONFIG = {
    OPTIONS: ['subscribers', 'views', 'videos'],
    FONT_LINK: 'https://fonts.googleapis.com/css2?family=Rubik:wght@400;700&display=swap',
    STATS_API_URL: 'https://api.livecounts.io/youtube-live-subscriber-counter/stats/',
    DEFAULT_UPDATE_INTERVAL: 2000,
    DEFAULT_OVERLAY_OPACITY: 0.75,
    MAX_RETRIES: 3,
    CACHE_DURATION: 300000, // 5 minutes
    DEBOUNCE_DELAY: 100,
    STORAGE_KEY: 'youtube_channel_stats_settings',
  };

  // Global state management
  const state = {
    overlay: null,
    isUpdating: false,
    intervalId: null,
    currentChannelName: null,
    enabled: localStorage.getItem(CONFIG.STORAGE_KEY) !== 'false',
    updateInterval:
      parseInt(localStorage.getItem('youtubeEnhancerInterval')) || CONFIG.DEFAULT_UPDATE_INTERVAL,
    overlayOpacity:
      parseFloat(localStorage.getItem('youtubeEnhancerOpacity')) || CONFIG.DEFAULT_OVERLAY_OPACITY,
    lastSuccessfulStats: new Map(),
    previousStats: new Map(),
    previousUrl: location.href,
    isChecking: false,
    documentListenerKeys: new Set(),
  };

  // Utility functions
  const utils = {
    log: (message, ...args) => {
      console.log(`[YouTube Enhancer] ${message}`, ...args);
    },

    warn: (message, ...args) => {
      console.warn(`[YouTube Enhancer] ${message}`, ...args);
    },

    error: (message, ...args) => {
      console.error(`[YouTube Enhancer] ${message}`, ...args);
    },

    // Use shared debounce from YouTubeUtils
    debounce:
      window.YouTubeUtils?.debounce ||
      ((func, wait) => {
        let timeout;
        return function executedFunction(...args) {
          const later = () => {
            clearTimeout(timeout);
            func(...args);
          };
          clearTimeout(timeout);
          timeout = setTimeout(later, wait);
        };
      }),
  };

  const OPTIONS = CONFIG.OPTIONS;
  const FONT_LINK = CONFIG.FONT_LINK;
  const STATS_API_URL = CONFIG.STATS_API_URL;

  /**
   * Fetches channel data from YouTube
   * @param {string} url - The channel URL to fetch
   * @returns {Promise<Object|null>} The parsed channel data or null on error
   */
  async function fetchChannel(url) {
    if (state.isChecking) return null;
    state.isChecking = true;

    try {
      const response = await fetch(url, {
        credentials: 'same-origin',
      });

      if (!response.ok) return null;

      const html = await response.text();
      const match = html.match(/var ytInitialData = (.+?);<\/script>/);
      return match && match[1] ? JSON.parse(match[1]) : null;
    } catch (error) {
      utils.warn('Failed to fetch channel data:', error);
      return null;
    } finally {
      state.isChecking = false;
    }
  }

  async function getChannelInfo(url) {
    const data = await fetchChannel(url);
    if (!data) return null;

    try {
      const channelName = data?.metadata?.channelMetadataRenderer?.title || t('unknown');
      const channelId = data?.metadata?.channelMetadataRenderer?.externalId || null;

      return { channelName, channelId };
    } catch {
      return null;
    }
  }

  function isChannelPageUrl(url) {
    return (
      url.includes('youtube.com/') &&
      (url.includes('/channel/') || url.includes('/@')) &&
      !url.includes('/video/') &&
      !url.includes('/watch')
    );
  }

  function checkUrlChange() {
    const currentUrl = location.href;
    if (currentUrl !== state.previousUrl) {
      state.previousUrl = currentUrl;
      if (isChannelPageUrl(currentUrl)) {
        setTimeout(() => getChannelInfo(currentUrl), 500);
      }
    }
  }

  history.pushState = (function (f) {
    /** @this {any} */
    return function () {
      f.apply(this, arguments);
      checkUrlChange();
    };
  })(history.pushState);

  history.replaceState = (function (f) {
    /** @this {any} */
    return function () {
      f.apply(this, arguments);
      checkUrlChange();
    };
  })(history.replaceState);

  window.addEventListener('popstate', checkUrlChange);
  setInterval(checkUrlChange, 1000);

  function init() {
    try {
      utils.log('Initializing YouTube Enhancer v1.6');

      loadFonts();
      initializeLocalStorage();
      addStyles();
      if (state.enabled) {
        observePageChanges();
        addNavigationListener();

        if (isChannelPageUrl(location.href)) {
          getChannelInfo(location.href);
        }
      }

      utils.log('YouTube Enhancer initialized successfully');
    } catch (error) {
      utils.error('Failed to initialize YouTube Enhancer:', error);
    }
  }

  function loadFonts() {
    const fontLink = document.createElement('link');
    fontLink.rel = 'stylesheet';
    fontLink.href = FONT_LINK;
    (document.head || document.documentElement).appendChild(fontLink);
  }

  function initializeLocalStorage() {
    OPTIONS.forEach(option => {
      if (localStorage.getItem(`show-${option}`) === null) {
        localStorage.setItem(`show-${option}`, 'true');
      }
    });
  }

  function addStyles() {
    // ✅ Use StyleManager instead of createElement('style')
    const styles = `
        .channel-banner-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:12px;z-index:10;display:flex;justify-content:space-around;align-items:center;color:#fff;font-family:var(--stats-font-family,'Rubik',sans-serif);font-size:var(--stats-font-size,24px);box-sizing:border-box;transition:background-color .3s ease;backdrop-filter:blur(2px)}
        .settings-button{position:absolute;top:8px;right:8px;width:24px;height:24px;cursor:pointer;z-index:2;transition:transform .2s;opacity:.7}
        .settings-button:hover{transform:scale(1.1);opacity:1}
        .settings-menu{position:absolute;top:35px;right:8px;background:rgba(0,0,0,.95);padding:12px;border-radius:8px;z-index:10;display:none;backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.1);min-width:320px}
        .settings-menu.show{display:block}
        .stat-container{display:flex;flex-direction:column;align-items:center;justify-content:center;visibility:hidden;width:33%;height:100%;padding:0 1rem}
        .number-container{display:flex;align-items:center;justify-content:center;font-weight:700;min-height:3rem}
        .label-container{display:flex;align-items:center;margin-top:.5rem;font-size:1.2rem;opacity:.9}
        .label-container svg{width:1.5rem;height:1.5rem;margin-right:.5rem}
        .difference{font-size:1.8rem;height:2rem;margin-bottom:.5rem;transition:opacity .3s}
        .spinner-container{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center}
        .loading-spinner{animation:spin 1s linear infinite}
        @keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
        @media(max-width:768px){.channel-banner-overlay{flex-direction:column;padding:8px;min-height:160px}.settings-menu{width:280px;right:4px}}
        .setting-group{margin-bottom:12px}
        .setting-group:last-child{margin-bottom:0}
        .setting-group label{display:block;margin-bottom:4px;font-weight:600;color:#fff;font-size:14px}
        .setting-group input[type="range"]{width:100%;margin:4px 0}
        .setting-group input[type="checkbox"]{margin-right:8px}
        .setting-value{color:#aaa;font-size:12px;margin-top:2px}
        `;
    YouTubeUtils.StyleManager.add('channel-stats-overlay', styles);
  }

  function createSettingsButton() {
    const button = document.createElement('div');
    button.className = 'settings-button';

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    svg.setAttribute('viewBox', '0 0 512 512');

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('fill', 'white');
    path.setAttribute(
      'd',
      'M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z'
    );

    svg.appendChild(path);
    button.appendChild(svg);

    return button;
  }

  function createSettingsMenu() {
    const menu = document.createElement('div');
    menu.className = 'settings-menu';
    menu.style.gap = '15px';
    menu.style.width = '360px';
    menu.setAttribute('tabindex', '-1');
    menu.setAttribute('aria-modal', 'true');

    const displaySection = createDisplaySection();
    const controlsSection = createControlsSection();

    menu.appendChild(displaySection);
    menu.appendChild(controlsSection);

    return menu;
  }

  function createDisplaySection() {
    const displaySection = document.createElement('div');
    displaySection.style.flex = '1';

    const displayLabel = document.createElement('label');
    displayLabel.textContent = t('displayOptions');
    displayLabel.style.marginBottom = '10px';
    displayLabel.style.display = 'block';
    displayLabel.style.fontSize = '16px';
    displayLabel.style.fontWeight = 'bold';
    displaySection.appendChild(displayLabel);

    OPTIONS.forEach(option => {
      const checkboxContainer = document.createElement('div');
      checkboxContainer.style.display = 'flex';
      checkboxContainer.style.alignItems = 'center';
      checkboxContainer.style.marginTop = '5px';

      const checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.id = `show-${option}`;
      checkbox.checked = localStorage.getItem(`show-${option}`) !== 'false';
      // ✅ Применяем стиль как в настройках
      checkbox.className = 'ytp-plus-settings-checkbox';

      const checkboxLabel = document.createElement('label');
      checkboxLabel.htmlFor = `show-${option}`;
      checkboxLabel.textContent = t(option);
      checkboxLabel.style.cursor = 'pointer';
      checkboxLabel.style.color = 'white';
      checkboxLabel.style.fontSize = '14px';
      checkboxLabel.style.marginLeft = '8px';

      checkbox.addEventListener('change', () => {
        localStorage.setItem(`show-${option}`, String(checkbox.checked));
        updateDisplayState();
      });

      checkboxContainer.appendChild(checkbox);
      checkboxContainer.appendChild(checkboxLabel);
      displaySection.appendChild(checkboxContainer);
    });

    return displaySection;
  }

  function createControlsSection() {
    const controlsSection = document.createElement('div');
    controlsSection.style.flex = '1';

    // Font family selector
    const fontLabel = document.createElement('label');
    fontLabel.textContent = t('fontFamily');
    fontLabel.style.display = 'block';
    fontLabel.style.marginBottom = '5px';
    fontLabel.style.fontSize = '16px';
    fontLabel.style.fontWeight = 'bold';

    const fontSelect = document.createElement('select');
    fontSelect.className = 'font-family-select';
    fontSelect.style.width = '100%';
    fontSelect.style.marginBottom = '10px';
    const fonts = [
      { name: 'Rubik', value: 'Rubik, sans-serif' },
      { name: 'Impact', value: 'Impact, Charcoal, sans-serif' },
      { name: 'Verdana', value: 'Verdana, Geneva, sans-serif' },
      { name: 'Tahoma', value: 'Tahoma, Geneva, sans-serif' },
    ];
    const savedFont = localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif';
    fonts.forEach(f => {
      const opt = document.createElement('option');
      opt.value = f.value;
      opt.textContent = f.name;
      if (f.value === savedFont) opt.selected = true;
      fontSelect.appendChild(opt);
    });

    fontSelect.addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLSelectElement} */ (e.target);
      localStorage.setItem('youtubeEnhancerFontFamily', target.value);
      if (state.overlay) {
        // Only update .subscribers-number, .views-number, .videos-number
        state.overlay
          .querySelectorAll('.subscribers-number,.views-number,.videos-number')
          .forEach(el => {
            el.style.fontFamily = target.value;
          });
      }
    });

    // Font size slider
    const fontSizeLabel = document.createElement('label');
    fontSizeLabel.textContent = t('fontSize');
    fontSizeLabel.style.display = 'block';
    fontSizeLabel.style.marginBottom = '5px';
    fontSizeLabel.style.fontSize = '16px';
    fontSizeLabel.style.fontWeight = 'bold';

    const fontSizeSlider = document.createElement('input');
    fontSizeSlider.type = 'range';
    fontSizeSlider.min = '16';
    fontSizeSlider.max = '72';
    fontSizeSlider.value = localStorage.getItem('youtubeEnhancerFontSize') || '24';
    fontSizeSlider.step = '1';
    fontSizeSlider.className = 'font-size-slider';

    const fontSizeValue = document.createElement('div');
    fontSizeValue.className = 'font-size-value';
    fontSizeValue.textContent = `${fontSizeSlider.value}px`;
    fontSizeValue.style.fontSize = '14px';
    fontSizeValue.style.marginBottom = '15px';

    fontSizeSlider.addEventListener('input', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      fontSizeValue.textContent = `${target.value}px`;
      localStorage.setItem('youtubeEnhancerFontSize', target.value);
      if (state.overlay) {
        // Only update .subscribers-number, .views-number, .videos-number
        state.overlay
          .querySelectorAll('.subscribers-number,.views-number,.videos-number')
          .forEach(el => {
            el.style.fontSize = `${target.value}px`;
          });
      }
    });

    // ...existing code...
    const intervalLabel = document.createElement('label');
    intervalLabel.textContent = t('updateInterval');
    intervalLabel.style.display = 'block';
    intervalLabel.style.marginBottom = '5px';
    intervalLabel.style.fontSize = '16px';
    intervalLabel.style.fontWeight = 'bold';

    const intervalSlider = document.createElement('input');
    intervalSlider.type = 'range';
    intervalSlider.min = '2';
    intervalSlider.max = '10';
    intervalSlider.value = String(state.updateInterval / 1000);
    intervalSlider.step = '1';
    intervalSlider.className = 'interval-slider';

    const intervalValue = document.createElement('div');
    intervalValue.className = 'interval-value';
    intervalValue.textContent = `${intervalSlider.value}s`;
    intervalValue.style.marginBottom = '15px';
    intervalValue.style.fontSize = '14px';

    intervalSlider.addEventListener('input', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      const newInterval = parseInt(target.value) * 1000;
      intervalValue.textContent = `${target.value}s`;
      state.updateInterval = newInterval;
      localStorage.setItem('youtubeEnhancerInterval', String(newInterval));

      if (state.intervalId) {
        clearInterval(state.intervalId);
        state.intervalId = setInterval(() => {
          updateOverlayContent(state.overlay, state.currentChannelName);
        }, newInterval);

        // ✅ Register interval in cleanupManager
        YouTubeUtils.cleanupManager.registerInterval(state.intervalId);
      }
    });

    const opacityLabel = document.createElement('label');
    opacityLabel.textContent = t('backgroundOpacity');
    opacityLabel.style.display = 'block';
    opacityLabel.style.marginBottom = '5px';
    opacityLabel.style.fontSize = '16px';
    opacityLabel.style.fontWeight = 'bold';

    const opacitySlider = document.createElement('input');
    opacitySlider.type = 'range';
    opacitySlider.min = '50';
    opacitySlider.max = '90';
    opacitySlider.value = String(state.overlayOpacity * 100);
    opacitySlider.step = '5';
    opacitySlider.className = 'opacity-slider';

    const opacityValue = document.createElement('div');
    opacityValue.className = 'opacity-value';
    opacityValue.textContent = `${opacitySlider.value}%`;
    opacityValue.style.fontSize = '14px';

    opacitySlider.addEventListener('input', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      const newOpacity = parseInt(target.value) / 100;
      opacityValue.textContent = `${target.value}%`;
      state.overlayOpacity = newOpacity;
      localStorage.setItem('youtubeEnhancerOpacity', String(newOpacity));

      if (state.overlay) {
        state.overlay.style.backgroundColor = `rgba(0, 0, 0, ${newOpacity})`;
      }
    });

    controlsSection.appendChild(fontLabel);
    controlsSection.appendChild(fontSelect);
    controlsSection.appendChild(fontSizeLabel);
    controlsSection.appendChild(fontSizeSlider);
    controlsSection.appendChild(fontSizeValue);
    controlsSection.appendChild(intervalLabel);
    controlsSection.appendChild(intervalSlider);
    controlsSection.appendChild(intervalValue);
    controlsSection.appendChild(opacityLabel);
    controlsSection.appendChild(opacitySlider);
    controlsSection.appendChild(opacityValue);

    return controlsSection;
  }

  function createSpinner() {
    const spinnerContainer = document.createElement('div');
    spinnerContainer.style.position = 'absolute';
    spinnerContainer.style.top = '0';
    spinnerContainer.style.left = '0';
    spinnerContainer.style.width = '100%';
    spinnerContainer.style.height = '100%';
    spinnerContainer.style.display = 'flex';
    spinnerContainer.style.justifyContent = 'center';
    spinnerContainer.style.alignItems = 'center';
    spinnerContainer.classList.add('spinner-container');

    const spinner = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    spinner.setAttribute('viewBox', '0 0 512 512');
    spinner.setAttribute('width', '64');
    spinner.setAttribute('height', '64');
    spinner.classList.add('loading-spinner');
    spinner.style.animation = 'spin 1s linear infinite';

    const secondaryPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    secondaryPath.setAttribute(
      'd',
      'M0 256C0 114.9 114.1 .5 255.1 0C237.9 .5 224 14.6 224 32c0 17.7 14.3 32 32 32C150 64 64 150 64 256s86 192 192 192c69.7 0 130.7-37.1 164.5-92.6c-3 6.6-3.3 14.8-1 22.2c1.2 3.7 3 7.2 5.4 10.3c1.2 1.5 2.6 3 4.1 4.3c.8 .7 1.6 1.3 2.4 1.9c.4 .3 .8 .6 1.3 .9s.9 .6 1.3 .8c5 2.9 10.6 4.3 16 4.3c11 0 21.8-5.7 27.7-16c-44.3 76.5-127 128-221.7 128C114.6 512 0 397.4 0 256z'
    );
    secondaryPath.style.opacity = '0.4';
    secondaryPath.style.fill = 'white';

    const primaryPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    primaryPath.setAttribute(
      'd',
      'M224 32c0-17.7 14.3-32 32-32C397.4 0 512 114.6 512 256c0 46.6-12.5 90.4-34.3 128c-8.8 15.3-28.4 20.5-43.7 11.7s-20.5-28.4-11.7-43.7c16.3-28.2 25.7-61 25.7-96c0-106-86-192-192-192c-17.7 0-32-14.3-32-32z'
    );
    primaryPath.style.fill = 'white';

    spinner.appendChild(secondaryPath);
    spinner.appendChild(primaryPath);
    spinnerContainer.appendChild(spinner);
    return spinnerContainer;
  }

  function createSVGIcon(path) {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 640 512');
    svg.setAttribute('width', '2rem');
    svg.setAttribute('height', '2rem');
    svg.style.marginRight = '0.5rem';
    svg.style.display = 'none';

    const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    svgPath.setAttribute('d', path);
    svgPath.setAttribute('fill', 'white');

    svg.appendChild(svgPath);
    return svg;
  }

  function createStatContainer(className, iconPath) {
    const container = document.createElement('div');
    Object.assign(container.style, {
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      justifyContent: 'center',
      visibility: 'hidden',
      width: '33%',
      height: '100%',
      padding: '0 1rem',
    });

    const numberContainer = document.createElement('div');
    Object.assign(numberContainer.style, {
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      justifyContent: 'center',
    });

    const differenceElement = document.createElement('div');
    differenceElement.classList.add(`${className}-difference`);
    Object.assign(differenceElement.style, {
      fontSize: '2.5rem',
      height: '2.5rem',
      marginBottom: '1rem',
    });

    const digitContainer = createNumberContainer();
    digitContainer.classList.add(`${className}-number`);
    Object.assign(digitContainer.style, {
      fontSize: (localStorage.getItem('youtubeEnhancerFontSize') || '24') + 'px',
      fontWeight: 'bold',
      lineHeight: '1',
      height: '4rem',
      fontFamily: localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif',
      letterSpacing: '0.025em',
    });

    numberContainer.appendChild(differenceElement);
    numberContainer.appendChild(digitContainer);

    const labelContainer = document.createElement('div');
    Object.assign(labelContainer.style, {
      display: 'flex',
      alignItems: 'center',
      marginTop: '0.5rem',
    });

    const icon = createSVGIcon(iconPath);
    Object.assign(icon.style, {
      width: '2rem',
      height: '2rem',
      marginRight: '0.75rem',
    });

    const labelElement = document.createElement('div');
    labelElement.classList.add(`${className}-label`);
    labelElement.style.fontSize = '2rem';

    labelContainer.appendChild(icon);
    labelContainer.appendChild(labelElement);

    container.appendChild(numberContainer);
    container.appendChild(labelContainer);

    return container;
  }

  function createOverlay(bannerElement) {
    clearExistingOverlay();

    if (!bannerElement) return null;

    const overlay = document.createElement('div');
    overlay.classList.add('channel-banner-overlay');
    Object.assign(overlay.style, {
      position: 'absolute',
      top: '0',
      left: '0',
      width: '100%',
      height: '100%',
      backgroundColor: `rgba(0, 0, 0, ${state.overlayOpacity})`,
      borderRadius: '15px',
      zIndex: '10',
      display: 'flex',
      justifyContent: 'space-around',
      alignItems: 'center',
      color: 'white',
      fontFamily: localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif',
      fontSize: (localStorage.getItem('youtubeEnhancerFontSize') || '24') + 'px',
      boxSizing: 'border-box',
      transition: 'background-color 0.3s ease',
    });

    // Accessibility attributes
    overlay.setAttribute('role', 'region');
    overlay.setAttribute('aria-label', t('overlayAriaLabel'));
    overlay.setAttribute('tabindex', '-1');

    // Responsive design for mobile
    if (window.innerWidth <= 768) {
      overlay.style.flexDirection = 'column';
      overlay.style.padding = '10px';
      overlay.style.minHeight = '200px';
    }

    const settingsButton = createSettingsButton();
    settingsButton.setAttribute('tabindex', '0');
    settingsButton.setAttribute('aria-label', t('settingsAriaLabel'));
    settingsButton.setAttribute('role', 'button');

    const settingsMenu = createSettingsMenu();
    settingsMenu.setAttribute('aria-label', t('settingsMenuAriaLabel'));
    settingsMenu.setAttribute('role', 'dialog');

    overlay.appendChild(settingsButton);
    overlay.appendChild(settingsMenu);

    // Enhanced event handling with keyboard support
    const toggleMenu = show => {
      settingsMenu.classList.toggle('show', show);
      settingsButton.setAttribute('aria-expanded', show);
      if (show) {
        settingsMenu.focus();
      }
    };

    settingsButton.addEventListener('click', e => {
      e.stopPropagation();
      toggleMenu(!settingsMenu.classList.contains('show'));
    });

    settingsButton.addEventListener('keydown', e => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        toggleMenu(!settingsMenu.classList.contains('show'));
      }
    });

    // Close menu when clicking outside or pressing escape
    const documentClickHandler = e => {
      const target = /** @type {EventTarget & Node} */ (e.target);
      if (!settingsMenu.contains(target) && !settingsButton.contains(target)) {
        toggleMenu(false);
      }
    };
    const clickListenerKey = YouTubeUtils.cleanupManager.registerListener(
      document,
      'click',
      documentClickHandler
    );
    state.documentListenerKeys.add(clickListenerKey);

    const documentKeydownHandler = e => {
      if (e.key === 'Escape' && settingsMenu.classList.contains('show')) {
        toggleMenu(false);
        settingsButton.focus();
      }
    };
    const keyListenerKey = YouTubeUtils.cleanupManager.registerListener(
      document,
      'keydown',
      documentKeydownHandler
    );
    state.documentListenerKeys.add(keyListenerKey);

    const spinner = createSpinner();
    overlay.appendChild(spinner);

    const subscribersElement = createStatContainer(
      'subscribers',
      'M144 160c-44.2 0-80-35.8-80-80S99.8 0 144 0s80 35.8 80 80s-35.8 80-80 80zm368 0c-44.2 0-80-35.8-80-80s35.8-80 80-80s80 35.8 80 80s-35.8 80-80 80zM0 298.7C0 239.8 47.8 192 106.7 192h42.7c15.9 0 31 3.5 44.6 9.7c-1.3 7.2-1.9 14.7-1.9 22.3c0 38.2 16.8 72.5 43.3 96c-.2 0-.4 0-.7 0H21.3C9.6 320 0 310.4 0 298.7zM405.3 320c-.2 0-.4 0-.7 0c26.6-23.5 43.3-57.8 43.3-96c0-7.6-.7-15-1.9-22.3c13.6-6.3 28.7-9.7 44.6-9.7h42.7C592.2 192 640 239.8 640 298.7c0 11.8-9.6 21.3-21.3 21.3H405.3zM416 224c0 53-43 96-96 96s-96-43-96-96s43-96 96-96s96 43 96 96zM128 485.3C128 411.7 187.7 352 261.3 352H378.7C452.3 352 512 411.7 512 485.3c0 14.7-11.9 26.7-26.7 26.7H154.7c-14.7 0-26.7-11.9-26.7-26.7z'
    );
    const viewsElement = createStatContainer(
      'views',
      'M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z'
    );
    const videosElement = createStatContainer(
      'videos',
      'M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zM559.1 99.8c10.4 5.6 16.9 16.4 16.9 28.2V384c0 11.8-6.5 22.6-16.9 28.2s-23 5-32.9-1.6l-96-64L416 337.1V320 192 174.9l14.2-9.5 96-64c9.8-6.5 22.4-7.2 32.9-1.6z'
    );

    overlay.appendChild(subscribersElement);
    overlay.appendChild(viewsElement);
    overlay.appendChild(videosElement);

    bannerElement.appendChild(overlay);
    updateDisplayState();
    return overlay;
  }

  function fetchWithGM(url, headers = {}) {
    const requestHeaders = {
      Accept: 'application/json',
      ...headers,
    };
    // Access GM_xmlhttpRequest via window to avoid TS "Cannot find name" when d.ts isn't picked up
    const gm = /** @type {any} */ (window).GM_xmlhttpRequest;
    if (typeof gm === 'function') {
      return new Promise((resolve, reject) => {
        gm({
          method: 'GET',
          url,
          headers: requestHeaders,
          timeout: 10000,
          onload: response => {
            if (response.status >= 200 && response.status < 300) {
              try {
                resolve(JSON.parse(response.responseText));
              } catch (parseError) {
                reject(new Error(`Failed to parse response: ${parseError.message}`));
              }
            } else {
              reject(new Error(`Failed to fetch: ${response.status}`));
            }
          },
          onerror: error => reject(error),
          ontimeout: () => reject(new Error('Request timed out')),
        });
      });
    }

    utils.warn('GM_xmlhttpRequest unavailable, falling back to fetch API');
    return fetch(url, {
      method: 'GET',
      headers: requestHeaders,
      credentials: 'omit',
      mode: 'cors',
    })
      .then(response => {
        if (!response.ok) {
          throw new Error(`Failed to fetch: ${response.status}`);
        }
        return response.json();
      })
      .catch(error => {
        utils.error('Fallback fetch failed:', error);
        throw error;
      });
  }

  async function fetchChannelId(_channelName) {
    // Try meta tag first
    const metaTag = document.querySelector('meta[itemprop="channelId"]');
    if (metaTag && metaTag.content) return metaTag.content;

    // Try URL pattern
    const urlMatch = window.location.href.match(/channel\/(UC[\w-]+)/);
    if (urlMatch && urlMatch[1]) return urlMatch[1];

    // Try ytInitialData
    const channelInfo = await getChannelInfo(window.location.href);
    if (channelInfo && channelInfo.channelId) return channelInfo.channelId;
    throw new Error('Could not determine channel ID');
  }

  async function fetchChannelStats(channelId) {
    try {
      let retries = CONFIG.MAX_RETRIES;

      while (retries > 0) {
        try {
          const stats = await fetchWithGM(`${STATS_API_URL}${channelId}`, {
            origin: 'https://livecounts.io',
            referer: 'https://livecounts.io/',
          });

          // Validate response structure
          if (!stats || typeof stats.followerCount === 'undefined') {
            throw new Error('Invalid stats response structure');
          }

          // Cache successful response
          state.lastSuccessfulStats.set(channelId, {
            ...stats,
            timestamp: Date.now(),
          });
          return stats;
        } catch (e) {
          utils.warn('Fetch attempt failed:', e.message);
          retries--;
          if (retries > 0) {
            // Exponential backoff for retries
            await new Promise(resolve =>
              setTimeout(resolve, 1000 * (CONFIG.MAX_RETRIES - retries + 1))
            );
          }
        }
      }

      // Try to use cached data if available and recent (within 5 minutes)
      if (state.lastSuccessfulStats.has(channelId)) {
        const cached = state.lastSuccessfulStats.get(channelId);
        const isRecent = Date.now() - cached.timestamp < CONFIG.CACHE_DURATION;
        if (isRecent) {
          utils.log('Using cached stats for channel:', channelId);
          return cached;
        }
      }

      // Fallback: try to extract subscriber count from page
      const fallbackStats = {
        followerCount: 0,
        bottomOdos: [0, 0],
        error: true,
        timestamp: Date.now(),
      };

      // Try multiple selectors for subscriber count
      const subCountSelectors = [
        '#subscriber-count',
        '.yt-subscription-button-subscriber-count-branded-horizontal',
        '[id*="subscriber"]',
        '.ytd-subscribe-button-renderer',
      ];

      for (const selector of subCountSelectors) {
        const subCountElem = document.querySelector(selector);
        if (subCountElem) {
          const subText = subCountElem.textContent || subCountElem.innerText || '';
          const subMatch = subText.match(/[\d,\.]+[KMB]?/);
          if (subMatch) {
            const raw = subMatch[0].replace(/,/g, '');
            // parse into number safely
            let numCount = Number(raw.replace(/[KMB]/, '')) || 0;
            if (raw.includes('K')) {
              numCount = numCount * 1000;
            } else if (raw.includes('M')) {
              numCount = numCount * 1000000;
            } else if (raw.includes('B')) {
              numCount = numCount * 1000000000;
            }
            // Ensure followerCount is a number
            fallbackStats.followerCount = Math.floor(numCount);
            utils.log('Extracted fallback subscriber count:', fallbackStats.followerCount);
            break;
          }
        }
      }

      return fallbackStats;
    } catch (error) {
      utils.error('Failed to fetch channel stats:', error);
      return {
        followerCount: 0,
        bottomOdos: [0, 0],
        error: true,
        timestamp: Date.now(),
      };
    }
  }

  function clearExistingOverlay() {
    const existingOverlay = document.querySelector('.channel-banner-overlay');
    if (existingOverlay) {
      try {
        existingOverlay.remove();
      } catch {
        console.warn('[YouTube+] Failed to remove overlay');
      }
    }
    if (state.intervalId) {
      try {
        clearInterval(state.intervalId);
        // ✅ Unregister from cleanupManager if it was registered
        YouTubeUtils.cleanupManager.unregisterInterval(state.intervalId);
      } catch {
        console.warn('[YouTube+] Failed to clear interval');
      }
      state.intervalId = null;
    }
    if (state.documentListenerKeys && state.documentListenerKeys.size) {
      state.documentListenerKeys.forEach(key => {
        try {
          YouTubeUtils.cleanupManager.unregisterListener(key);
        } catch {
          console.warn('[YouTube+] Failed to unregister listener');
        }
      });
      state.documentListenerKeys.clear();
    }
    if (state.lastSuccessfulStats) state.lastSuccessfulStats.clear();
    if (state.previousStats) state.previousStats.clear();
    state.isUpdating = false;
    state.overlay = null;
    utils.log('Cleared existing overlay');
  }

  function createDigitElement() {
    const digit = document.createElement('span');
    Object.assign(digit.style, {
      display: 'inline-block',
      width: '0.6em',
      textAlign: 'center',
      marginRight: '0.025em',
      marginLeft: '0.025em',
    });
    return digit;
  }

  function createCommaElement() {
    const comma = document.createElement('span');
    comma.textContent = ',';
    Object.assign(comma.style, {
      display: 'inline-block',
      width: '0.3em',
      textAlign: 'center',
    });
    return comma;
  }

  function createNumberContainer() {
    const container = document.createElement('div');
    Object.assign(container.style, {
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      letterSpacing: '0.025em',
    });
    return container;
  }

  function updateDigits(container, newValue) {
    const newValueStr = newValue.toString();
    const digits = [];

    for (let i = newValueStr.length - 1; i >= 0; i -= 3) {
      const start = Math.max(0, i - 2);
      digits.unshift(newValueStr.slice(start, i + 1));
    }

    while (container.firstChild) {
      container.removeChild(container.firstChild);
    }

    for (let i = 0; i < digits.length; i++) {
      const group = digits[i];
      for (let j = 0; j < group.length; j++) {
        const digitElement = createDigitElement();
        digitElement.textContent = group[j];
        container.appendChild(digitElement);
      }
      if (i < digits.length - 1) {
        container.appendChild(createCommaElement());
      }
    }

    let elementIndex = 0;
    for (let i = 0; i < digits.length; i++) {
      const group = digits[i];
      for (let j = 0; j < group.length; j++) {
        const digitElement = container.children[elementIndex];
        const newDigit = parseInt(group[j]);
        const currentDigit = parseInt(digitElement.textContent || '0');

        if (currentDigit !== newDigit) {
          animateDigit(digitElement, currentDigit, newDigit);
        }
        elementIndex++;
      }
      if (i < digits.length - 1) {
        elementIndex++;
      }
    }
  }

  function animateDigit(element, start, end) {
    const duration = 1000;
    const startTime = performance.now();

    function update(currentTime) {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const easeOutQuart = 1 - Math.pow(1 - progress, 4);
      const current = Math.round(start + (end - start) * easeOutQuart);
      element.textContent = current;

      if (progress < 1) {
        requestAnimationFrame(update);
      }
    }

    requestAnimationFrame(update);
  }

  function showContent(overlay) {
    const spinnerContainer = overlay.querySelector('.spinner-container');
    if (spinnerContainer) {
      spinnerContainer.remove();
    }

    const containers = overlay.querySelectorAll('div[style*="visibility: hidden"]');
    containers.forEach(container => {
      container.style.visibility = 'visible';
    });

    const icons = overlay.querySelectorAll('svg[style*="display: none"]');
    icons.forEach(icon => {
      icon.style.display = 'block';
    });
  }

  function updateDifferenceElement(element, currentValue, previousValue) {
    if (!previousValue) return;

    const difference = currentValue - previousValue;
    if (difference === 0) {
      element.textContent = '';
      return;
    }

    const sign = difference > 0 ? '+' : '';
    element.textContent = `${sign}${difference.toLocaleString()}`;
    element.style.color = difference > 0 ? '#1ed760' : '#f3727f';

    setTimeout(() => {
      element.textContent = '';
    }, 1000);
  }

  function updateDisplayState() {
    const overlay = document.querySelector('.channel-banner-overlay');
    if (!overlay) return;

    const statContainers = overlay.querySelectorAll('div[style*="width"]');
    if (!statContainers.length) return;

    let visibleCount = 0;
    const visibleContainers = [];

    statContainers.forEach(container => {
      const numberContainer = container.querySelector('[class$="-number"]');
      if (!numberContainer) return;

      const type = numberContainer.className.replace('-number', '');

      const isVisible = localStorage.getItem(`show-${type}`) !== 'false';

      if (isVisible) {
        container.style.display = 'flex';
        visibleCount++;
        visibleContainers.push(container);
      } else {
        container.style.display = 'none';
      }
    });

    visibleContainers.forEach(container => {
      container.style.width = '';
      container.style.margin = '';

      switch (visibleCount) {
        case 1:
          container.style.width = '100%';
          break;
        case 2:
          container.style.width = '50%';
          break;
        case 3:
          container.style.width = '33.33%';
          break;
        default:
          container.style.display = 'none';
      }
    });

    // Only update font size and font family for .subscribers-number, .views-number, .videos-number
    const fontSize = localStorage.getItem('youtubeEnhancerFontSize') || '24';
    const fontFamily = localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif';
    overlay.querySelectorAll('.subscribers-number,.views-number,.videos-number').forEach(el => {
      el.style.fontSize = `${fontSize}px`;
      el.style.fontFamily = fontFamily;
    });

    overlay.style.display = 'flex';
  }

  async function updateOverlayContent(overlay, channelName) {
    if (state.isUpdating || channelName !== state.currentChannelName) return;
    state.isUpdating = true;

    try {
      const channelId = await fetchChannelId(channelName);
      const stats = await fetchChannelStats(channelId);

      // Check if channel changed during async operations
      if (channelName !== state.currentChannelName) {
        state.isUpdating = false;
        return;
      }

      if (stats.error) {
        const containers = overlay.querySelectorAll('[class$="-number"]');
        containers.forEach(container => {
          if (container.classList.contains('subscribers-number') && stats.followerCount > 0) {
            updateDigits(container, stats.followerCount);
          } else {
            container.textContent = '---';
          }
        });
        utils.warn('Using fallback stats due to API error');
        return;
      }

      const updateElement = (className, value, label) => {
        const numberContainer = overlay.querySelector(`.${className}-number`);
        const differenceElement = overlay.querySelector(`.${className}-difference`);
        const labelElement = overlay.querySelector(`.${className}-label`);

        if (numberContainer) {
          updateDigits(numberContainer, value);
        }

        if (differenceElement && state.previousStats.has(channelId)) {
          const previousValue =
            className === 'subscribers'
              ? state.previousStats.get(channelId).followerCount
              : state.previousStats.get(channelId).bottomOdos[className === 'views' ? 0 : 1];
          updateDifferenceElement(differenceElement, value, previousValue);
        }

        if (labelElement) {
          labelElement.textContent = label;
        }
      };

      updateElement('subscribers', stats.followerCount, 'Subscribers');
      updateElement('views', stats.bottomOdos[0], 'Views');
      updateElement('videos', stats.bottomOdos[1], 'Videos');

      if (!state.previousStats.has(channelId)) {
        showContent(overlay);
        utils.log('Displayed initial stats for channel:', channelName);
      }

      state.previousStats.set(channelId, stats);
    } catch (error) {
      utils.error('Failed to update overlay content:', error);
      const containers = overlay.querySelectorAll('[class$="-number"]');
      containers.forEach(container => {
        container.textContent = '---';
      });
    } finally {
      state.isUpdating = false;
    }
  }

  // Add settings UI to experimental section
  function addSettingsUI() {
    const section = document.querySelector(
      '.ytp-plus-settings-section[data-section="experimental"]'
    );
    if (!section || section.querySelector('.count-settings-item')) return;

    const item = document.createElement('div');
    item.className = 'ytp-plus-settings-item count-settings-item';
    item.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('channelStatsTitle')}</label>
          <div class="ytp-plus-settings-item-description">${t('channelStatsDescription')}</div>
        </div>
        <input type="checkbox" class="ytp-plus-settings-checkbox" ${state.enabled ? 'checked' : ''}>
      `;
    section.appendChild(item);

    item.querySelector('input').addEventListener('change', e => {
      const target = /** @type {EventTarget & HTMLInputElement} */ (e.target);
      state.enabled = target.checked;
      localStorage.setItem(CONFIG.STORAGE_KEY, state.enabled ? 'true' : 'false');
      if (!state.enabled) {
        clearExistingOverlay();
      } else {
        observePageChanges();
        addNavigationListener();
        setTimeout(() => {
          const bannerElement = document.getElementById('page-header-banner-sizer');
          if (bannerElement && isChannelPage()) {
            addOverlay(bannerElement);
          }
        }, 100);
      }
    });
  }

  // Observe settings modal for experimental section
  const settingsObserver = new MutationObserver(mutations => {
    for (const { addedNodes } of mutations) {
      for (const node of addedNodes) {
        if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
          setTimeout(addSettingsUI, 100);
          return;
        }
      }
    }
    if (document.querySelector('.ytp-plus-settings-nav-item[data-section="experimental"].active')) {
      setTimeout(addSettingsUI, 50);
    }
  });
  YouTubeUtils.cleanupManager.registerObserver(settingsObserver);

  // ✅ Safe observe with document.body check
  if (document.body) {
    settingsObserver.observe(document.body, { childList: true, subtree: true });
  } else {
    document.addEventListener('DOMContentLoaded', () => {
      settingsObserver.observe(document.body, { childList: true, subtree: true });
    });
  }

  const experimentalNavClickHandler = e => {
    const target = /** @type {EventTarget & HTMLElement} */ (e.target);
    if (
      target.classList?.contains('ytp-plus-settings-nav-item') &&
      target.dataset?.section === 'experimental'
    ) {
      setTimeout(addSettingsUI, 50);
    }
  };

  const listenerKey = YouTubeUtils.cleanupManager.registerListener(
    document,
    'click',
    experimentalNavClickHandler,
    true
  );
  state.documentListenerKeys.add(listenerKey);

  function addOverlay(bannerElement) {
    // Improved channel name extraction with better URL parsing
    let channelName = null;
    const pathname = window.location.pathname;

    if (pathname.startsWith('/@')) {
      channelName = pathname.split('/')[1].replace('@', '');
    } else if (pathname.startsWith('/channel/')) {
      channelName = pathname.split('/')[2];
    } else if (pathname.startsWith('/c/')) {
      channelName = pathname.split('/')[2];
    } else if (pathname.startsWith('/user/')) {
      channelName = pathname.split('/')[2];
    }

    // Skip if no valid channel name or already processing the same channel
    if (!channelName || (channelName === state.currentChannelName && state.overlay)) {
      return;
    }

    // Ensure banner element is properly positioned
    if (bannerElement && !bannerElement.style.position) {
      bannerElement.style.position = 'relative';
    }

    state.currentChannelName = channelName;
    state.overlay = createOverlay(bannerElement);

    if (state.overlay) {
      // Clear existing interval
      if (state.intervalId) {
        clearInterval(state.intervalId);
        state.intervalId = null;
      }

      // Debounced update function for better performance
      let lastUpdateTime = 0;
      const debouncedUpdate = () => {
        const now = Date.now();
        if (now - lastUpdateTime >= state.updateInterval - 100) {
          updateOverlayContent(state.overlay, channelName);
          lastUpdateTime = now;
        }
      };

      // Set up interval with debouncing
      state.intervalId = setInterval(debouncedUpdate, state.updateInterval);

      // ✅ Register interval in cleanupManager
      YouTubeUtils.cleanupManager.registerInterval(state.intervalId);

      // Initial update
      updateOverlayContent(state.overlay, channelName);
      utils.log('Added overlay for channel:', channelName);
    }
  }

  function isChannelPage() {
    return (
      window.location.pathname.startsWith('/@') ||
      window.location.pathname.startsWith('/channel/') ||
      window.location.pathname.startsWith('/c/')
    );
  }

  function observePageChanges() {
    if (!state.enabled) return undefined;

    // More robust banner detection with multiple fallback selectors
    const observer = new MutationObserver(_mutations => {
      // Throttle observations for better performance
      if (/** @type {any} */ (observer)._timeout) {
        YouTubeUtils.cleanupManager.unregisterTimeout(/** @type {any} */ (observer)._timeout);
        clearTimeout(/** @type {any} */ (observer)._timeout);
      }

      /** @type {any} */ (observer)._timeout = YouTubeUtils.cleanupManager.registerTimeout(
        setTimeout(() => {
          let bannerElement = document.getElementById('page-header-banner-sizer');

          // Try alternative selectors if main one fails
          if (!bannerElement) {
            const alternatives = [
              '[id*="banner"]',
              '.ytd-c4-tabbed-header-renderer',
              '#channel-header',
              '.channel-header',
            ];

            for (const selector of alternatives) {
              bannerElement = document.querySelector(selector);
              if (bannerElement) break;
            }
          }

          if (bannerElement && isChannelPage()) {
            // Ensure banner has proper positioning
            if (bannerElement.style.position !== 'relative') {
              bannerElement.style.position = 'relative';
            }
            addOverlay(bannerElement);
          } else if (!isChannelPage()) {
            // Clean up when not on channel page
            clearExistingOverlay();
            state.currentChannelName = null;
          }
        }, 100)
      ); // Small delay to batch rapid changes
    });

    // ✅ Safe observe with document.body check
    if (document.body) {
      observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: false, // Reduce observation scope for performance
      });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, {
          childList: true,
          subtree: true,
          attributes: false,
        });
      });
    }

    // Store timeout reference for cleanup
    /** @type {any} */ (observer)._timeout = null;

    // Store observer for cleanup on page unload
    if (typeof state.observers === 'undefined') {
      state.observers = [];
    }
    state.observers.push(observer);

    return observer;
  }

  function addNavigationListener() {
    if (!state.enabled) return;

    window.addEventListener('yt-navigate-finish', () => {
      if (!isChannelPage()) {
        clearExistingOverlay();
        state.currentChannelName = null;
        utils.log('Navigated away from channel page');
      } else {
        const bannerElement = document.getElementById('page-header-banner-sizer');
        if (bannerElement) {
          addOverlay(bannerElement);
          utils.log('Navigated to channel page');
        }
      }
    });
  }

  // Cleanup function for page unload
  function cleanup() {
    // Disconnect all observers
    if (state.observers && Array.isArray(state.observers)) {
      state.observers.forEach(observer => {
        try {
          observer.disconnect();
        } catch (e) {
          console.warn('[YouTube+] Failed to disconnect observer:', e);
        }
      });
      state.observers = [];
    }

    // Clear overlay and intervals
    clearExistingOverlay();

    utils.log('Cleanup completed');
  }

  // Register cleanup on page unload
  window.addEventListener('beforeunload', cleanup);

  init();
})();

// --- MODULE: comment.js ---

/**
 * Comment Manager Module
 * Provides bulk delete functionality and comment management tools for YouTube
 * @module CommentManager
 */
(function () {
  'use strict';

  // Internationalization (i18n) system
  const i18n = {
    en: {
      commentManager: 'Comment Manager',
      deleteSelected: 'Delete Selected',
      selectAll: 'Select All',
      clearAll: 'Clear All',
      selectComment: 'Select comment',
      togglePanel: 'Toggle panel',
      commentManagerControls: 'Comment manager controls',
      commentManagement: 'Comment Management',
      enableCommentManager: 'Enable comment manager',
      bulkDeleteDescription: 'Add checkboxes and bulk delete functionality to your comments',
    },
    ru: {
      commentManager: 'Менеджер комментариев',
      deleteSelected: 'Удалить выбранные',
      selectAll: 'Выбрать все',
      clearAll: 'Очистить все',
      selectComment: 'Выбрать комментарий',
      togglePanel: 'Переключить панель',
      commentManagerControls: 'Управление менеджером комментариев',
      commentManagement: 'Управление комментариями',
      enableCommentManager: 'Включить менеджер комментариев',
      bulkDeleteDescription: 'Добавить чекбоксы и функцию массового удаления к вашим комментариям',
    },
  };

  // Get browser language
  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  // Translation function
  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  /**
   * Configuration object for comment manager
   * @const {Object}
   */
  const CONFIG = {
    selectors: {
      deleteButtons: 'div[class^="VfPpkd-Bz112c-"]',
      menuButton: '[aria-haspopup="menu"]',
    },
    classes: {
      checkbox: 'comment-checkbox',
      checkboxAnchor: 'comment-checkbox-anchor',
      checkboxFloating: 'comment-checkbox-floating',
      container: 'comment-controls-container',
      panel: 'comment-controls-panel',
      header: 'comment-controls-header',
      title: 'comment-controls-title',
      actions: 'comment-controls-actions',
      button: 'comment-controls-button',
      buttonDanger: 'comment-controls-button--danger',
      buttonPrimary: 'comment-controls-button--primary',
      buttonSuccess: 'comment-controls-button--success',
      close: 'comment-controls-close',
      deleteButton: 'comment-controls-button-delete',
    },
    debounceDelay: 100,
    deleteDelay: 200,
    enabled: true,
    storageKey: 'youtube_comment_manager_settings',
  };

  // State management
  const state = {
    observer: null,
    isProcessing: false,
    settingsNavListenerKey: null,
    panelCollapsed: false,
  };

  // Optimized settings
  const settings = {
    load: () => {
      try {
        const saved = localStorage.getItem(CONFIG.storageKey);
        if (saved) CONFIG.enabled = JSON.parse(saved).enabled ?? true;
      } catch {}
    },
    save: () => {
      try {
        localStorage.setItem(CONFIG.storageKey, JSON.stringify({ enabled: CONFIG.enabled }));
      } catch {}
    },
  };

  // Utility functions: use shared debounce when available
  const debounce = (func, wait) => {
    try {
      const utilDebounce = window.YouTubeUtils && window.YouTubeUtils.debounce;
      if (typeof utilDebounce === 'function') {
        // Call the util to get a debounced function (instead of returning the util itself)
        const debounced = utilDebounce(func, wait);
        if (typeof debounced === 'function') return debounced;
        // If utilDebounce didn't return a function, fall back to local implementation
      }

      return ((f, w) => {
        let timeout;
        return (...args) => {
          clearTimeout(timeout);
          timeout = setTimeout(() => f(...args), w);
        };
      })(func, wait);
    } catch {
      // fallback
      let timeout;
      return (...args) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => func(...args), wait);
      };
    }
  };

  /**
   * Safely query a single element
   * @param {string} selector - CSS selector
   * @returns {HTMLElement|null} The first matching element or null
   */
  const $ = selector => /** @type {HTMLElement|null} */ (document.querySelector(selector));

  /**
   * Safely query multiple elements
   * @param {string} selector - CSS selector
   * @returns {NodeListOf<HTMLElement>} NodeList of matching elements
   */
  const $$ = selector =>
    /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(selector));

  /**
   * Log error with error boundary integration
   * @param {string} context - Error context
   * @param {Error|string|unknown} error - Error object or message
   */
  const logError = (context, error) => {
    const errorObj = error instanceof Error ? error : new Error(String(error));
    if (window.YouTubeErrorBoundary) {
      window.YouTubeErrorBoundary.logError(errorObj, { context });
    } else {
      console.error(`[YouTube+][CommentManager] ${context}:`, error);
    }
  };

  /**
   * Wraps function with error boundary protection
   * @template {Function} T
   * @param {T} fn - Function to wrap
   * @param {string} context - Error context for debugging
   * @returns {T} Wrapped function
   */
  const withErrorBoundary = (fn, context) => {
    if (window.YouTubeErrorBoundary?.withErrorBoundary) {
      return /** @type {T} */ (window.YouTubeErrorBoundary.withErrorBoundary(fn, 'CommentManager'));
    }
    return /** @type {any} */ (
      (...args) => {
        try {
          return fn(...args);
        } catch (error) {
          logError(context, error);
          return null;
        }
      }
    );
  };

  /**
   * Add checkboxes to comment elements for selection
   * Core functionality for bulk operations
   */
  const addCheckboxes = withErrorBoundary(() => {
    if (!CONFIG.enabled || state.isProcessing) return;

    const deleteButtons = $$(CONFIG.selectors.deleteButtons);

    deleteButtons.forEach(button => {
      const parent = button.parentNode;
      if (
        button.closest(CONFIG.selectors.menuButton) ||
        (parent && parent.querySelector && parent.querySelector(`.${CONFIG.classes.checkbox}`))
      ) {
        return;
      }

      const commentElement =
        button.closest('[class*="comment"]') || button.closest('[role="article"]') || parent;

      if (commentElement && commentElement instanceof Element) {
        if (!commentElement.hasAttribute('data-comment-text')) {
          commentElement.setAttribute(
            'data-comment-text',
            (commentElement.textContent || '').toLowerCase()
          );
        }
      }

      const checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.className = `${CONFIG.classes.checkbox} ytp-plus-settings-checkbox`;
      checkbox.setAttribute('aria-label', t('selectComment'));

      checkbox.addEventListener('change', updateDeleteButtonState);
      checkbox.addEventListener('click', e => e.stopPropagation());

      // Optimized positioning
      const dateElement =
        commentElement && commentElement.querySelector
          ? commentElement.querySelector(
              '[class*="date"],[class*="time"],time,[title*="20"],[aria-label*="ago"]'
            )
          : null;

      if (dateElement && dateElement instanceof Element) {
        dateElement.classList.add(CONFIG.classes.checkboxAnchor);
        checkbox.classList.add(CONFIG.classes.checkboxFloating);
        dateElement.appendChild(checkbox);
      } else if (parent && parent.insertBefore) {
        parent.insertBefore(checkbox, button);
      }
    });
  }, 'addCheckboxes');

  /**
   * Add control panel with bulk action buttons
   */
  const addControlButtons = withErrorBoundary(() => {
    if (!CONFIG.enabled || $(`.${CONFIG.classes.container}`)) return;

    const deleteButtons = $$(CONFIG.selectors.deleteButtons);
    if (!deleteButtons.length) return;

    const first = deleteButtons[0];
    const container = first && first.parentNode && first.parentNode.parentNode;
    if (!container || !(container instanceof Element)) return;

    const panel = document.createElement('div');
    panel.className = `${CONFIG.classes.container} ${CONFIG.classes.panel} glass-panel`;
    panel.setAttribute('role', 'region');
    panel.setAttribute('aria-label', t('commentManagerControls'));

    const header = document.createElement('div');
    header.className = CONFIG.classes.header;

    const title = document.createElement('div');
    title.className = CONFIG.classes.title;
    title.textContent = t('commentManager');

    const collapseButton = document.createElement('button');
    collapseButton.className = `${CONFIG.classes.close} ytp-plus-settings-close`;
    collapseButton.setAttribute('type', 'button');
    collapseButton.setAttribute('aria-expanded', String(!state.panelCollapsed));
    collapseButton.setAttribute('aria-label', t('togglePanel'));
    collapseButton.innerHTML = `
        <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
        </svg>
      `;

    const togglePanelState = collapsed => {
      state.panelCollapsed = collapsed;
      header.classList.toggle('is-collapsed', collapsed);
      actions.classList.toggle('is-hidden', collapsed);
      collapseButton.setAttribute('aria-expanded', String(!collapsed));
      panel.classList.toggle('is-collapsed', collapsed);
    };

    collapseButton.addEventListener('click', () => {
      state.panelCollapsed = !state.panelCollapsed;
      togglePanelState(state.panelCollapsed);
    });

    header.append(title, collapseButton);

    const actions = document.createElement('div');
    actions.className = CONFIG.classes.actions;

    const createActionButton = (label, className, onClick, options = {}) => {
      const button = document.createElement('button');
      button.type = 'button';
      button.textContent = label;
      button.className = `${CONFIG.classes.button} ${className}`;
      if (options.id) button.id = options.id;
      if (options.disabled) button.disabled = true;
      button.addEventListener('click', onClick);
      return button;
    };

    const deleteAllButton = createActionButton(
      t('deleteSelected'),
      `${CONFIG.classes.buttonDanger} ${CONFIG.classes.deleteButton}`,
      deleteSelectedComments,
      { disabled: true }
    );

    const selectAllButton = createActionButton(t('selectAll'), CONFIG.classes.buttonPrimary, () => {
      $$(`.${CONFIG.classes.checkbox}`).forEach(cb => (cb.checked = true));
      updateDeleteButtonState();
    });

    const clearAllButton = createActionButton(t('clearAll'), CONFIG.classes.buttonSuccess, () => {
      $$(`.${CONFIG.classes.checkbox}`).forEach(cb => (cb.checked = false));
      updateDeleteButtonState();
    });

    actions.append(deleteAllButton, selectAllButton, clearAllButton);
    togglePanelState(state.panelCollapsed);

    panel.append(header, actions);

    const refNode = deleteButtons[0] && deleteButtons[0].parentNode;
    if (refNode && refNode.parentNode) {
      container.insertBefore(panel, refNode);
    } else {
      container.appendChild(panel);
    }
  }, 'addControlButtons');

  /**
   * Update delete button state based on checkbox selection
   */
  const updateDeleteButtonState = withErrorBoundary(() => {
    const deleteAllButton = $(`.${CONFIG.classes.deleteButton}`);
    if (!deleteAllButton) return;

    const hasChecked = Array.from($$(`.${CONFIG.classes.checkbox}`)).some(cb => cb.checked);
    deleteAllButton.disabled = !hasChecked;
    deleteAllButton.style.opacity = hasChecked ? '1' : '0.6';
  }, 'updateDeleteButtonState');

  /**
   * Delete selected comments with confirmation
   */
  const deleteSelectedComments = withErrorBoundary(() => {
    const checkedBoxes = Array.from($$(`.${CONFIG.classes.checkbox}`)).filter(cb => cb.checked);

    if (!checkedBoxes.length || !confirm(`Delete ${checkedBoxes.length} comment(s)?`)) return;

    state.isProcessing = true;
    checkedBoxes.forEach((checkbox, index) => {
      setTimeout(() => {
        const deleteButton =
          checkbox.nextElementSibling ||
          checkbox.parentNode.querySelector(CONFIG.selectors.deleteButtons);
        deleteButton?.click();
      }, index * CONFIG.deleteDelay);
    });

    setTimeout(() => (state.isProcessing = false), checkedBoxes.length * CONFIG.deleteDelay + 1000);
  }, 'deleteSelectedComments');

  /**
   * Clean up all comment manager elements
   */
  const cleanup = withErrorBoundary(() => {
    $$(`.${CONFIG.classes.checkbox}`).forEach(el => el.remove());
    $(`.${CONFIG.classes.container}`)?.remove();
  }, 'cleanup');

  /**
   * Initialize or cleanup script based on enabled state
   */
  const initializeScript = withErrorBoundary(() => {
    if (CONFIG.enabled) {
      addCheckboxes();
      addControlButtons();
      updateDeleteButtonState();
    } else {
      cleanup();
    }
  }, 'initializeScript');

  /**
   * Add enhanced CSS styles for comment manager UI
   */
  const addStyles = withErrorBoundary(() => {
    if ($('#comment-delete-styles')) return;

    const styles = `
  .${CONFIG.classes.checkboxAnchor}{position:relative;display:inline-flex;align-items:center;gap:8px;width:auto;}
        .${CONFIG.classes.checkboxFloating}{position:absolute;top:-4px;right:-32px;margin:0;}
        /* Panel styled to match shorts feedback: glassmorphism, rounded corners, soft shadow */
        .${CONFIG.classes.panel}{position:fixed;top:50%;right:24px;transform:translateY(-50%);display:flex;flex-direction:column;gap:14px;z-index:10000;padding:16px 18px;background:var(--yt-glass-bg);border:1.5px solid var(--yt-glass-border);border-radius:20px;box-shadow:0 12px 40px rgba(0,0,0,0.45);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);min-width:220px;max-width:300px;color:var(--yt-text-primary);transition:transform .22s cubic-bezier(.4,0,.2,1),opacity .22s,box-shadow .2s}
        html:not([dark]) .${CONFIG.classes.panel}{background:var(--yt-glass-bg);}
        .${CONFIG.classes.header}{display:flex;align-items:center;justify-content:space-between;gap:12px;}
        .${CONFIG.classes.panel}.is-collapsed{padding:14px 18px;}
        .${CONFIG.classes.panel}.is-collapsed .${CONFIG.classes.title}{font-weight:500;opacity:.85;}
        .${CONFIG.classes.panel}.is-collapsed .${CONFIG.classes.close}{transform:rotate(45deg);}
        .${CONFIG.classes.panel}.is-collapsed .${CONFIG.classes.actions}{display:none!important;}
        .${CONFIG.classes.title}{font-size:15px;font-weight:600;letter-spacing:.3px;}
        .${CONFIG.classes.close}{background:transparent;border:none;cursor:pointer;padding:6px;border-radius:12px;display:flex;align-items:center;justify-content:center;color:var(--yt-text-primary);transition:all .2s ease;}
        .${CONFIG.classes.close}:hover{transform:rotate(90deg) scale(1.05);color:var(--yt-accent);}
        .${CONFIG.classes.actions}{display:flex;flex-direction:column;gap:10px;}
        .${CONFIG.classes.actions}.is-hidden{display:none!important;}
        .${CONFIG.classes.button}{padding:12px 16px;border-radius:var(--yt-radius-md);border:1px solid var(--yt-glass-border);cursor:pointer;font-size:13px;font-weight:500;background:var(--yt-button-bg);color:var(--yt-text-primary);transition:all .2s ease;text-align:center;}
        .${CONFIG.classes.button}:disabled{opacity:.5;cursor:not-allowed;}
        .${CONFIG.classes.button}:not(:disabled):hover{transform:translateY(-1px);box-shadow:var(--yt-shadow);}
        .${CONFIG.classes.buttonDanger}{background:rgba(255,99,71,.12);border-color:rgba(255,99,71,.25);color:#ff5c5c;}
        .${CONFIG.classes.buttonPrimary}{background:rgba(33,150,243,.12);border-color:rgba(33,150,243,.25);color:#2196f3;}
        .${CONFIG.classes.buttonSuccess}{background:rgba(76,175,80,.12);border-color:rgba(76,175,80,.25);color:#4caf50;}
        .${CONFIG.classes.buttonDanger}:not(:disabled):hover{background:rgba(255,99,71,.22);}
        .${CONFIG.classes.buttonPrimary}:not(:disabled):hover{background:rgba(33,150,243,.22);}
        .${CONFIG.classes.buttonSuccess}:not(:disabled):hover{background:rgba(76,175,80,.22);}
        @media(max-width:1280px){
          .${CONFIG.classes.panel}{top:auto;bottom:24px;transform:none;right:16px;}
        }
        @media(max-width:768px){
          .${CONFIG.classes.panel}{position:fixed;left:16px;right:16px;bottom:16px;top:auto;transform:none;max-width:none;}
          .${CONFIG.classes.actions}{flex-direction:row;flex-wrap:wrap;}
          .${CONFIG.classes.button}{flex:1;min-width:140px;}
        }
      `;
    YouTubeUtils.StyleManager.add('comment-delete-styles', styles);
  }, 'addStyles');

  /**
   * Add comment manager settings to YouTube+ settings panel
   */
  const addCommentManagerSettings = withErrorBoundary(() => {
    const advancedSection = $('.ytp-plus-settings-section[data-section="advanced"]');
    if (!advancedSection) return;

    // If already exists, move it to the bottom to ensure Comment Manager is last
    const existing = $('.comment-manager-settings-item');
    if (existing) {
      try {
        advancedSection.appendChild(existing);
      } catch {
        // ignore
      }
      return;
    }

    const settingsItem = document.createElement('div');
    settingsItem.className = 'ytp-plus-settings-item comment-manager-settings-item';
    settingsItem.innerHTML = `
        <div>
          <label class="ytp-plus-settings-item-label">${t('commentManagement')}</label>
          <div class="ytp-plus-settings-item-description">${t('bulkDeleteDescription')}</div>
        </div>
        <button class="ytp-plus-button" id="open-comment-history-page" style="margin:0 0 0 30px;padding:12px 16px;font-size:13px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2)">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">
            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
            <polyline points="15,3 21,3 21,9"/>
            <line x1="10" y1="14" x2="21" y2="3"/>
          </svg>
        </button>
      `;

    // Append to end (ensure it's the bottom-most item)
    advancedSection.appendChild(settingsItem);

    $('#open-comment-history-page').addEventListener('click', () => {
      window.open('https://www.youtube.com/feed/history/comment_history', '_blank');
    });
  }, 'addCommentManagerSettings');

  /**
   * Initialize comment manager module
   * Sets up observers, event listeners, and initial state
   */
  const init = withErrorBoundary(() => {
    settings.load();
    addStyles();

    // Setup observer with throttling
    state.observer?.disconnect();
    state.observer = new MutationObserver(debounce(initializeScript, CONFIG.debounceDelay));

    // ✅ Register observer in cleanupManager
    YouTubeUtils.cleanupManager.registerObserver(state.observer);

    // ✅ Safe observe with document.body check
    if (document.body) {
      state.observer.observe(document.body, { childList: true, subtree: true });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        state.observer.observe(document.body, { childList: true, subtree: true });
      });
    }

    // Initial setup
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
      initializeScript();
    }

    // Settings modal integration
    const settingsObserver = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
            setTimeout(addCommentManagerSettings, 100);
            return;
          }
        }
      }
    });

    // ✅ Register observer in cleanupManager
    YouTubeUtils.cleanupManager.registerObserver(settingsObserver);

    // ✅ Safe observe with document.body check
    if (document.body) {
      settingsObserver.observe(document.body, { childList: true, subtree: true });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        settingsObserver.observe(document.body, { childList: true, subtree: true });
      });
    }

    const handleAdvancedNavClick = e => {
      const target = /** @type {EventTarget & HTMLElement} */ (e.target);
      if (target.dataset?.section === 'advanced') {
        setTimeout(addCommentManagerSettings, 50);
      }
    };

    if (!state.settingsNavListenerKey) {
      state.settingsNavListenerKey = YouTubeUtils.cleanupManager.registerListener(
        document,
        'click',
        handleAdvancedNavClick,
        { passive: true, capture: true }
      );
    }
  }, 'init');

  // Start the module
  init();
})();

// --- MODULE: report.js ---

/* Report module: populates the settings 'report' section and provides report submission helpers.
 * Features:
 * - Small reporting form (type, title, description, email optional)
 * - Prepares debug info (version, UA, page URL, settings snapshot)
 * - Opens a prefilled GitHub issue in a new tab or copies the report to clipboard
 * - Designed to work in a userscript (no server required)
 */
(function () {
  'use strict';

  // Minimal guards for shared utils
  const Y = /** @type {any} */ (window).YouTubeUtils || {};

  // Internationalization for report module
  const i18n = {
    en: {
      shortTitle: 'Short title (one line)',
      emailOptional: 'Your email (optional)',
      descriptionPlaceholder: 'Describe the issue, steps to reproduce, expected vs actual',
      includeDebug: 'Include debug info (version, URL, settings)',
      openGitHub: 'Open GitHub Issue',
      copyReport: 'Copy Report',
      prepareEmail: 'Prepare Email',
      privacy:
        'By submitting you agree to include the provided information. Do not include passwords or personal tokens.',
      typeBug: 'Bug / Error',
      typeFeature: 'Feature Request',
      typeOther: 'Other',
      titleRequired: 'Title is required',
      titleMin: 'Title must be at least 5 characters',
      descRequired: 'Description is required',
      descMin: 'Description must be at least 10 characters',
      invalidEmail: 'Invalid email format',
      fixErrorsPrefix: 'Please fix the following errors:\n• ',
      opening: 'Opening...',
      copying: 'Copying...',
      copied: 'Copied!',
      openingGithubNotification: 'Opening GitHub in a new tab',
      failedOpenGithub: 'Failed to open GitHub issue',
      reportCopied: 'Report copied to clipboard',
      copyFailed: 'Copy failed — please copy manually',
    },
    ru: {
      shortTitle: 'Краткий заголовок (в одну строку)',
      emailOptional: 'Ваш email (необязательно)',
      descriptionPlaceholder:
        'Опишите проблему, шаги для воспроизведения, ожидаемое и фактическое поведение',
      includeDebug: 'Включить отладочную информацию (версия, URL, настройки)',
      openGitHub: 'Открыть заявку на GitHub',
      copyReport: 'Копировать отчет',
      prepareEmail: 'Подготовить письмо',
      privacy:
        'Отправляя, вы соглашаетесь включить указанную информацию. Не включайте пароли или личные токены.',
      typeBug: 'Ошибка',
      typeFeature: 'Запрос функции',
      typeOther: 'Другое',
      titleRequired: 'Требуется заголовок',
      titleMin: 'Заголовок должен быть не менее 5 символов',
      descRequired: 'Требуется описание',
      descMin: 'Описание должно быть не менее 10 символов',
      invalidEmail: 'Неправильный формат email',
      fixErrorsPrefix: 'Пожалуйста, исправьте следующие ошибки:\n• ',
      opening: 'Открываю...',
      copying: 'Копирую...',
      copied: 'Скопировано!',
      openingGithubNotification: 'Открываю GitHub в новой вкладке',
      failedOpenGithub: 'Не удалось открыть заявку на GitHub',
      reportCopied: 'Отчет скопирован в буфер обмена',
      copyFailed: 'Копирование не удалось — пожалуйста, скопируйте вручную',
    },
  };

  // Get browser language
  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  /**
   * Translation function
   * @param {string} key - Translation key
   * @returns {string} Translated text
   */
  function t(key) {
    const lang = getLanguage();
    return i18n[lang]?.[key] || i18n.en[key] || key;
  }

  /**
   * Create DOM element with properties and children
   * @param {string} tag - HTML tag name
   * @param {Object} props - Element properties
   * @param {Array} children - Child elements or text
   * @returns {HTMLElement} Created element
   */
  function mk(tag, props = {}, children = []) {
    const el = document.createElement(tag);
    Object.entries(props).forEach(([k, v]) => {
      if (k === 'class') {
        el.className = /** @type {string} */ (v);
      } else if (k === 'html') {
        el.innerHTML = /** @type {string} */ (v);
      } else if (k.startsWith('on') && typeof v === 'function') {
        el.addEventListener(k.substring(2).toLowerCase(), /** @type {EventListener} */ (v));
      } else {
        el.setAttribute(k, String(v));
      }
    });
    children.forEach(c =>
      typeof c === 'string' ? el.appendChild(document.createTextNode(c)) : el.appendChild(c)
    );
    return el;
  }

  /**
   * Sanitize HTML to prevent XSS attacks
   * @param {string} html - HTML string to sanitize
   * @returns {string} Sanitized HTML
   */
  function sanitizeHTML(html) {
    if (Y.sanitizeHTML && typeof Y.sanitizeHTML === 'function') {
      return Y.sanitizeHTML(html);
    }
    // Fallback sanitizer
    if (typeof html !== 'string') return '';
    const map = {
      '<': '&lt;',
      '>': '&gt;',
      '&': '&amp;',
      '"': '&quot;',
      "'": '&#39;',
      '/': '&#x2F;',
      '`': '&#x60;',
      '=': '&#x3D;',
    };
    return html.replace(/[<>&"'\/`=]/g, char => map[char] || char);
  }

  /**
   * Validate email address format
   * @param {string} email - Email to validate
   * @returns {boolean} Whether email is valid
   */
  function isValidEmail(email) {
    if (!email || typeof email !== 'string') return false;
    // Basic email regex - simple but effective
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email) && email.length <= 254; // RFC 5321
  }

  /**
   * Validate and sanitize title input
   * @param {string} title - Title to validate
   * @returns {string} Sanitized title
   */
  function validateTitle(title) {
    if (!title || typeof title !== 'string') return '';
    // Limit length and sanitize
    return sanitizeHTML(title.trim().substring(0, 200));
  }

  /**
   * Validate and sanitize description input
   * @param {string} description - Description to validate
   * @returns {string} Sanitized description
   */
  function validateDescription(description) {
    if (!description || typeof description !== 'string') return '';
    // Limit length and sanitize
    return sanitizeHTML(description.trim().substring(0, 5000));
  }

  /**
   * Collect debug information for reports
   * @returns {Object} Debug information object
   */
  function getDebugInfo() {
    try {
      const debug = {
        version: /** @type {any} */ (window.YouTubePlusDebug || {}).version || 'unknown',
        userAgent: navigator.userAgent || 'unknown',
        url: location.href || 'unknown',
        language: document.documentElement.lang || navigator.language || 'unknown',
        settings: typeof Y.SettingsManager === 'object' ? Y.SettingsManager.load() : null,
      };
      return debug;
    } catch (err) {
      if (Y && typeof Y.logError === 'function') {
        Y.logError('Report', 'Failed to collect debug info', err);
      }
      return {
        version: 'unknown',
        userAgent: 'unknown',
        url: 'unknown',
        language: 'unknown',
        settings: null,
        error: 'Failed to collect debug info',
      };
    }
  }

  /**
   * Build GitHub issue payload from report data
   * @param {Object} params - Report parameters
   * @param {string} params.type - Report type (bug/feature/other)
   * @param {string} params.title - Report title
   * @param {string} params.description - Report description
   * @param {string} params.email - Optional email
   * @param {boolean} params.includeDebug - Include debug info
   * @returns {{title: string, body: string}} Issue payload
   */
  function buildIssuePayload({ type, title, description, email, includeDebug }) {
    const debug = includeDebug ? getDebugInfo() : null;

    const lines = [];
    const typeLabel =
      type === 'bug' ? t('typeBug') : type === 'feature' ? t('typeFeature') : t('typeOther');
    lines.push(`**Type:** ${typeLabel}`);
    if (email) lines.push(`**Reporter email (optional):** ${email}`);
    lines.push('\n**Description:**\n');
    lines.push(description || '(no description)');
    if (debug) {
      lines.push('\n---\n**Debug info**\n');
      lines.push('```json');
      try {
        lines.push(JSON.stringify(debug, null, 2));
      } catch (err) {
        if (Y && typeof Y.logError === 'function') {
          Y.logError('Report', 'Failed to stringify debug info', err);
        }
        // Fallback to minimal debug info
        const minimalDebug = {
          version: debug.version || 'unknown',
          userAgent: debug.userAgent || 'unknown',
          url: debug.url || 'unknown',
        };
        try {
          lines.push(JSON.stringify(minimalDebug, null, 2));
        } catch {
          lines.push('{ "error": "Failed to stringify debug info" }');
        }
      }
      lines.push('```');
      lines.push('\n_Please do not include sensitive personal data._');
    }

    const body = lines.join('\n');
    const issueTitle =
      `${type === 'bug' ? '[Bug]' : type === 'feature' ? '[Feature]' : '[Report]'} ${title || ''}`.trim();
    return { title: issueTitle, body };
  }

  /**
   * Open GitHub issue in a new tab
   * @param {{title: string, body: string}} payload - Issue payload
   */
  function openGitHubIssue(payload) {
    try {
      // Repository configured for issue creation
      const repoOwner = 'diorhc';
      const repo = 'YTP';
      const url = `https://github.com/${repoOwner}/${repo}/issues/new?title=${encodeURIComponent(
        payload.title
      )}&body=${encodeURIComponent(payload.body)}`;
      window.open(url, '_blank');
    } catch (err) {
      if (Y && typeof Y.logError === 'function') {
        Y.logError('Report', 'Failed to open GitHub issue', err);
      }
      throw err;
    }
  }

  /**
   * Copy text to clipboard with fallback
   * @param {string} text - Text to copy
   * @returns {Promise<void>} Promise that resolves when copied
   */
  function copyToClipboard(text) {
    // Modern clipboard API
    if (navigator.clipboard && navigator.clipboard.writeText) {
      return navigator.clipboard.writeText(text);
    }
    // Fallback for older browsers
    return new Promise((resolve, reject) => {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.style.position = 'fixed';
      ta.style.left = '-9999px';
      ta.style.opacity = '0';
      document.body.appendChild(ta);
      try {
        ta.select();
        ta.setSelectionRange(0, text.length);
        const success = document.execCommand('copy');
        document.body.removeChild(ta);
        if (success) {
          resolve();
        } else {
          reject(new Error('execCommand failed'));
        }
      } catch (err) {
        document.body.removeChild(ta);
        reject(err);
      }
    });
  }

  /**
   * Render report section in settings modal with glassmorphism styling
   * @param {HTMLElement} modal - Settings modal element
   */
  function renderReportSection(modal) {
    if (!modal || !modal.querySelector) return;

    const section = modal.querySelector('.ytp-plus-settings-section[data-section="report"]');
    if (!section) return;

    // Clear existing content and build a small form
    section.innerHTML = '';

    const form = mk('div', {
      style:
        'display:flex;flex-direction:column;gap:var(--yt-space-sm);margin-top:var(--yt-space-md);',
    });

    const typeSelect = mk(
      'select',
      {
        style:
          'padding:var(--yt-space-sm);border-radius:var(--yt-radius-sm);background:var(--yt-input-bg);color:var(--yt-text-primary);border:1px solid var(--yt-glass-border);backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);font-size:14px;cursor:pointer;transition:var(--yt-transition);',
      },
      []
    );
    [
      { v: 'bug', l: t('typeBug') },
      { v: 'feature', l: t('typeFeature') },
      { v: 'other', l: t('typeOther') },
    ].forEach(opt => {
      const o = mk('option', { value: opt.v }, [opt.l]);
      typeSelect.appendChild(o);
    });

    const inputStyle =
      'padding:var(--yt-space-sm);border-radius:var(--yt-radius-sm);background:var(--yt-input-bg);color:var(--yt-text-primary);border:1px solid var(--yt-glass-border);backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);font-size:14px;transition:var(--yt-transition);box-sizing:border-box;';

    const titleInput = mk('input', {
      placeholder: t('shortTitle'),
      style: inputStyle,
    });
    const emailInput = mk('input', {
      placeholder: t('emailOptional'),
      type: 'email',
      style: inputStyle,
    });
    const descInput = mk('textarea', {
      placeholder: t('descriptionPlaceholder'),
      rows: 6,
      style: inputStyle + 'resize:vertical;font-family:inherit;',
    });

    // checkbox input is created separately so we can listen to changes and show a preview
    const debugCheckboxInput = mk('input', {
      type: 'checkbox',
      class: 'ytp-plus-settings-checkbox',
    });
    const includeDebug = mk(
      'label',
      {
        style:
          'font-size:13px;display:flex;gap:var(--yt-space-sm);align-items:center;color:var(--yt-text-primary);cursor:pointer;align-self:center;',
      },
      [debugCheckboxInput, ' ' + t('includeDebug')]
    );

    const actions = mk('div', {
      style: 'display:flex;gap:var(--yt-space-sm);margin-top:var(--yt-space-sm);flex-wrap:wrap;',
    });
    const submitBtn = mk('button', { class: 'glass-button' }, [t('openGitHub')]);
    const copyBtn = mk('button', { class: 'glass-button' }, [t('copyReport')]);
    const emailBtn = mk('button', { class: 'glass-button' }, [t('prepareEmail')]);

    actions.appendChild(submitBtn);
    actions.appendChild(copyBtn);
    actions.appendChild(emailBtn);

    form.appendChild(typeSelect);
    form.appendChild(titleInput);
    form.appendChild(emailInput);
    form.appendChild(descInput);
    form.appendChild(includeDebug);
    // Debug preview area: hidden by default, placed directly under the includeDebug checkbox
    // Use a container `div` so we can build structured, safe DOM (header + collapsible JSON)
    const debugPreview = mk(
      'div',
      {
        class: 'glass-card',
        style:
          'overflow:auto;max-height:240px;font-size:11px;display:none;margin-top:var(--yt-space-sm);padding:8px;box-sizing:border-box;',
      },
      []
    );
    form.appendChild(debugPreview);

    form.appendChild(actions);

    const privacy = mk(
      'div',
      {
        class: 'ytp-plus-settings-item-description',
        style: 'margin-top:var(--yt-space-sm);font-size:12px;color:var(--yt-text-secondary);',
      },
      [t('privacy')]
    );

    section.appendChild(form);
    section.appendChild(privacy);

    // (debugPreview is appended inside the form, directly under the checkbox)

    /**
     * Update debug preview based on checkbox state
     */
    function updateDebugPreview() {
      try {
        if (debugCheckboxInput.checked) {
          const d = getDebugInfo();

          // Clear previous content
          debugPreview.innerHTML = '';

          // Header with important fields
          const header = mk(
            'div',
            { style: 'display:flex;flex-direction:column;gap:6px;margin-bottom:6px;' },
            []
          );
          header.appendChild(
            mk('div', {}, ['Version: ', mk('strong', {}, [String(d.version || 'unknown')])])
          );
          header.appendChild(
            mk('div', {}, [
              'User agent: ',
              mk('code', { style: 'font-size:11px;color:var(--yt-text-secondary);' }, [
                String(d.userAgent || ''),
              ]),
            ])
          );

          const urlStr = String(d.url || 'unknown');
          let urlEl = mk('span', {}, [urlStr]);
          try {
            if (/^https?:\/\//i.test(urlStr)) {
              urlEl = mk(
                'a',
                {
                  href: urlStr,
                  target: '_blank',
                  rel: 'noopener noreferrer',
                  style: 'color:var(--yt-accent);word-break:break-all;',
                },
                [urlStr]
              );
            }
          } catch (e) {
            // leave as plain text if anything odd and log
            if (Y && typeof Y.logError === 'function') {
              Y.logError('Report', 'URL link creation failed', e);
            }
            urlEl = mk('span', {}, [String(urlStr)]);
          }
          header.appendChild(mk('div', {}, ['URL: ', urlEl]));
          header.appendChild(
            mk('div', {}, ['Language: ', mk('code', {}, [String(d.language || '')])])
          );

          debugPreview.appendChild(header);

          // Settings (if available) – collapsible
          if (d.settings) {
            const settingsDetails = mk('details', {}, [mk('summary', {}, ['Settings'])]);
            settingsDetails.appendChild(
              mk('pre', { style: 'white-space:pre-wrap;margin:6px 0 0 0;font-size:11px;' }, [
                JSON.stringify(d.settings, null, 2),
              ])
            );
            debugPreview.appendChild(settingsDetails);
          }

          // Full debug JSON (collapsible)
          const fullDetails = mk('details', {}, [mk('summary', {}, ['Full debug JSON'])]);
          fullDetails.appendChild(
            mk('pre', { style: 'white-space:pre-wrap;margin:6px 0 0 0;font-size:11px;' }, [
              JSON.stringify(d, null, 2),
            ])
          );
          debugPreview.appendChild(fullDetails);

          debugPreview.style.display = 'block';
        } else {
          debugPreview.innerHTML = '';
          debugPreview.style.display = 'none';
        }
      } catch (err) {
        if (Y && typeof Y.logError === 'function') {
          Y.logError('Report', 'updateDebugPreview failed', err);
        }
      }
    }

    // wire up checkbox to preview
    debugCheckboxInput.addEventListener('change', updateDebugPreview);

    /**
     * Gather and validate form data
     * @returns {{type: string, title: string, description: string, email: string, includeDebug: boolean, errors: string[]}}
     */
    function gather() {
      const type = /** @type {HTMLSelectElement} */ (typeSelect).value;
      const rawTitle = /** @type {HTMLInputElement} */ (titleInput).value.trim();
      const rawDescription = /** @type {HTMLTextAreaElement} */ (descInput).value.trim();
      const rawEmail = /** @type {HTMLInputElement} */ (emailInput).value.trim();
      const includeDebugValue = /** @type {HTMLInputElement} */ (
        includeDebug.querySelector('input')
      ).checked;

      const errors = [];

      // Validate title
      if (!rawTitle) {
        errors.push(t('titleRequired'));
      } else if (rawTitle.length < 5) {
        errors.push(t('titleMin'));
      }

      // Validate description
      if (!rawDescription) {
        errors.push(t('descRequired'));
      } else if (rawDescription.length < 10) {
        errors.push(t('descMin'));
      }

      // Validate email if provided
      if (rawEmail && !isValidEmail(rawEmail)) {
        errors.push(t('invalidEmail'));
      }

      return {
        type,
        title: validateTitle(rawTitle),
        description: validateDescription(rawDescription),
        email: rawEmail && isValidEmail(rawEmail) ? rawEmail : '',
        includeDebug: includeDebugValue,
        errors,
      };
    }

    submitBtn.addEventListener('click', e => {
      e.preventDefault();
      if (submitBtn.disabled) return; // Prevent double-click

      try {
        const data = gather();

        // Check for validation errors
        if (data.errors && data.errors.length > 0) {
          const errorMsg = t('fixErrorsPrefix') + data.errors.join('\n• ');
          if (Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
            Y.NotificationManager.show(errorMsg, { duration: 4000, type: 'error' });
          } else {
            console.warn('[Report] Validation errors:', data.errors);
          }
          return;
        }

        // Add loading state
        const originalText = submitBtn.textContent;
        submitBtn.disabled = true;
        submitBtn.textContent = t('opening');
        submitBtn.style.opacity = '0.6';

        const payload = buildIssuePayload(data);
        openGitHubIssue(payload);

        if (Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
          Y.NotificationManager.show(t('openingGithubNotification'), { duration: 2500 });
        }

        // Reset button after a delay
        setTimeout(() => {
          submitBtn.disabled = false;
          submitBtn.textContent = originalText;
          submitBtn.style.opacity = '1';
        }, 2000);
      } catch (err) {
        if (Y.logError) Y.logError('Report', 'Failed to open GitHub issue', err);
        if (Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
          Y.NotificationManager.show(t('failedOpenGithub'), {
            duration: 3000,
            type: 'error',
          });
        }
        submitBtn.disabled = false;
        submitBtn.textContent = t('openGitHub');
        submitBtn.style.opacity = '1';
      }
    });

    copyBtn.addEventListener('click', e => {
      e.preventDefault();
      if (copyBtn.disabled) return; // Prevent double-click

      try {
        const data = gather();

        // Check for validation errors
        if (data.errors && data.errors.length > 0) {
          const errorMsg = t('fixErrorsPrefix') + data.errors.join('\n• ');
          if (Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
            Y.NotificationManager.show(errorMsg, { duration: 4000, type: 'error' });
          } else {
            console.warn('[Report] Validation errors:', data.errors);
          }
          return;
        }

        // Add loading state
        const originalText = copyBtn.textContent;
        copyBtn.disabled = true;
        copyBtn.textContent = t('copying');
        copyBtn.style.opacity = '0.6';

        const payload = buildIssuePayload(data);
        const full = `Title: ${payload.title}\n\n${payload.body}`;

        copyToClipboard(full)
          .then(() => {
            if (Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
              Y.NotificationManager.show(t('reportCopied'), { duration: 2000 });
            }
            copyBtn.textContent = t('copied');
            copyBtn.style.opacity = '1';
            setTimeout(() => {
              copyBtn.disabled = false;
              copyBtn.textContent = originalText;
            }, 2000);
          })
          .catch(err => {
            if (Y && typeof Y.logError === 'function') Y.logError('Report', 'copy failed', err);
            if (Y && Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
              Y.NotificationManager.show(t('copyFailed'), {
                duration: 3000,
                type: 'error',
              });
            } else {
              console.warn('Copy failed; please copy manually', err);
            }
            copyBtn.disabled = false;
            copyBtn.textContent = originalText;
            copyBtn.style.opacity = '1';
          });
      } catch (err) {
        if (Y.logError) Y.logError('Report', 'Failed to copy report', err);
        copyBtn.disabled = false;
        copyBtn.textContent = t('copyReport');
        copyBtn.style.opacity = '1';
      }
    });

    emailBtn.addEventListener('click', e => {
      e.preventDefault();
      if (emailBtn.disabled) return; // Prevent double-click

      try {
        const data = gather();

        // Check for validation errors
        if (data.errors && data.errors.length > 0) {
          const errorMsg = t('fixErrorsPrefix') + data.errors.join('\n• ');
          if (Y.NotificationManager && typeof Y.NotificationManager.show === 'function') {
            Y.NotificationManager.show(errorMsg, { duration: 4000, type: 'error' });
          } else {
            console.warn('[Report] Validation errors:', data.errors);
          }
          return;
        }

        const originalText = emailBtn.textContent;
        emailBtn.disabled = true;
        emailBtn.textContent = t('opening');
        emailBtn.style.opacity = '0.6';

        const payload = buildIssuePayload(data);
        const subject = payload.title;
        // No dedicated mail address in userscript; open mail client with prefilled body
        const mailto = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(
          payload.body
        )}`;
        window.location.href = mailto;

        setTimeout(() => {
          emailBtn.disabled = false;
          emailBtn.textContent = originalText;
          emailBtn.style.opacity = '1';
        }, 2000);
      } catch (err) {
        if (Y.logError) Y.logError('Report', 'Failed to prepare email', err);
        emailBtn.disabled = false;
        emailBtn.textContent = t('prepareEmail');
        emailBtn.style.opacity = '1';
      }
    });
  }

  // Expose render function
  try {
    /** @type {any} */ (window).youtubePlusReport =
      /** @type {any} */ (window).youtubePlusReport || {};
    /** @type {any} */ (window).youtubePlusReport.render = renderReportSection;
  } catch (e) {
    if (Y.logError) Y.logError('Report', 'Failed to attach report module to window', e);
  }
})();

// --- MODULE: update.js ---

// Update checker module
(function () {
  'use strict';

  // Internationalization
  const i18n = {
    en: {
      updateAvailableTitle: 'YouTube + Update Available',
      version: 'Version',
      newFeatures: 'New features and improvements',
      installUpdate: 'Install Update',
      later: 'Later',
      enhancedExperience: 'Enhanced YouTube experience with powerful features',
      currentVersion: 'Current Version',
      lastChecked: 'Last checked',
      latestAvailable: 'Latest available',
      updateAvailable: 'Update Available',
      installing: 'Installing...',
      checkingForUpdates: 'Checking for updates...',
      upToDate: 'Up to date',
      checkForUpdates: 'Check for Updates',
      autoCheckUpdates: 'Automatically check for updates',
      manualInstallHint:
        'Could not open update automatically. Download it manually from GreasyFork.',
      updatePageFallback: 'Opening update page...',
      // Time / small messages
      never: 'Never',
      justNow: 'Just now',
      daysAgo: '{n}d ago',
      hoursAgo: '{n}h ago',
      minutesAgo: '{n}m ago',
      updateAvailableMsg: 'Update {version} available!',
      upToDateMsg: "You're using the latest version ({version})",
      updateCheckFailed: 'Update check failed: {msg}',
      dismiss: 'Dismiss',
    },
    ru: {
      updateAvailableTitle: 'Доступно обновление YouTube +',
      version: 'Версия',
      newFeatures: 'Новые функции и улучшения',
      installUpdate: 'Установить обновление',
      later: 'Позже',
      enhancedExperience: 'Расширенные возможности YouTube с мощными функциями',
      currentVersion: 'Текущая версия',
      lastChecked: 'Последняя проверка',
      latestAvailable: 'Доступна версия',
      updateAvailable: 'Доступно обновление',
      installing: 'Установка...',
      checkingForUpdates: 'Проверка обновлений...',
      upToDate: 'Актуальная версия',
      checkForUpdates: 'Проверить обновления',
      autoCheckUpdates: 'Автоматически проверять обновления',
      manualInstallHint:
        'Не удалось открыть обновление автоматически. Загрузите его вручную с GreasyFork.',
      updatePageFallback: 'Открываю страницу обновления...',
      // Time / small messages
      never: 'Никогда',
      justNow: 'Только что',
      daysAgo: '{n}д',
      hoursAgo: '{n}ч',
      minutesAgo: '{n}м',
      updateAvailableMsg: 'Доступно обновление {version}!',
      upToDateMsg: 'Вы используете последнюю версию ({version})',
      updateCheckFailed: 'Проверка обновлений не удалась: {msg}',
      dismiss: 'Закрыть',
    },
  };

  function getLanguage() {
    const lang = document.documentElement.lang || navigator.language || 'en';
    return lang.startsWith('ru') ? 'ru' : 'en';
  }

  function t(key) {
    const lang = getLanguage();
    return i18n[lang][key] || i18n.en[key] || key;
  }

  const UPDATE_CONFIG = {
    enabled: true,
    checkInterval: 24 * 60 * 60 * 1000, // 24 hours
    updateUrl: 'https://update.greasyfork.org/scripts/537017/YouTube%20%2B.meta.js',
    currentVersion: '2.0.3',
    storageKey: 'youtube_plus_update_check',
    notificationDuration: 8000,
    autoInstallUrl: 'https://update.greasyfork.org/scripts/537017/YouTube%20%2B.user.js',
  };

  const windowRef = typeof window !== 'undefined' ? window : null;
  const GM_namespace = windowRef?.GM || null;
  const GM_info_safe = windowRef?.GM_info || null;
  const GM_openInTab_safe = (() => {
    if (!windowRef) return null;
    if (typeof windowRef.GM_openInTab === 'function') return windowRef.GM_openInTab.bind(windowRef);
    if (GM_namespace?.openInTab) return GM_namespace.openInTab.bind(GM_namespace);
    return null;
  })();

  if (GM_info_safe?.script?.version) {
    UPDATE_CONFIG.currentVersion = GM_info_safe.script.version;
  }

  const updateState = {
    lastCheck: 0,
    lastVersion: UPDATE_CONFIG.currentVersion,
    updateAvailable: true,
    checkInProgress: false,
    updateDetails: null,
  };

  // Pluralization helper for time units (available to this module)
  function pluralizeTime(n, unit) {
    const lang = getLanguage();
    const num = Math.abs(Number(n)) || 0;

    if (lang === 'ru') {
      const forms = {
        day: ['день', 'дня', 'дней'],
        hour: ['час', 'часа', 'часов'],
        minute: ['минута', 'минуты', 'минут'],
      }[unit];
      const mod10 = num % 10;
      const mod100 = num % 100;
      let idx = 2;
      if (mod10 === 1 && mod100 !== 11) idx = 0;
      else if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) idx = 1;
      return `${num} ${forms[idx]}`;
    }

    // English (default)
    const enForms = {
      day: ['day', 'days'],
      hour: ['hour', 'hours'],
      minute: ['minute', 'minutes'],
    }[unit];
    return `${num} ${num === 1 ? enForms[0] : enForms[1]}`;
  }

  // Optimized utilities
  const utils = {
    /**
     * Load update settings from localStorage with validation
     * @returns {void}
     */
    loadSettings: () => {
      try {
        const saved = localStorage.getItem(UPDATE_CONFIG.storageKey);
        if (!saved) {
          return;
        }

        const parsed = JSON.parse(saved);

        // Validate parsed object structure
        if (typeof parsed !== 'object' || parsed === null) {
          console.error('[Update] Invalid settings structure');
          return;
        }

        // Validate individual properties with type checking
        if (typeof parsed.lastCheck === 'number' && parsed.lastCheck >= 0) {
          updateState.lastCheck = parsed.lastCheck;
        }

        if (typeof parsed.lastVersion === 'string' && /^\d+\.\d+\.\d+/.test(parsed.lastVersion)) {
          updateState.lastVersion = parsed.lastVersion;
        }

        if (typeof parsed.updateAvailable === 'boolean') {
          updateState.updateAvailable = parsed.updateAvailable;
        }

        if (parsed.updateDetails && typeof parsed.updateDetails === 'object') {
          // Validate updateDetails properties
          if (
            typeof parsed.updateDetails.version === 'string' &&
            /^\d+\.\d+\.\d+/.test(parsed.updateDetails.version)
          ) {
            updateState.updateDetails = parsed.updateDetails;
          }
        }
      } catch (e) {
        console.error('[Update] Failed to load update settings:', e);
      }
    },

    /**
     * Save update settings to localStorage
     * @returns {void}
     */
    saveSettings: () => {
      try {
        const dataToSave = {
          lastCheck: updateState.lastCheck,
          lastVersion: updateState.lastVersion,
          updateAvailable: updateState.updateAvailable,
          updateDetails: updateState.updateDetails,
        };

        localStorage.setItem(UPDATE_CONFIG.storageKey, JSON.stringify(dataToSave));
      } catch (e) {
        console.error('[Update] Failed to save update settings:', e);
      }
    },

    /**
     * Compare two version strings
     * @param {string} v1 - First version
     * @param {string} v2 - Second version
     * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
     */
    compareVersions: (v1, v2) => {
      // Validate version format
      if (typeof v1 !== 'string' || typeof v2 !== 'string') {
        console.error('[Update] Invalid version format - must be strings');
        return 0;
      }

      const normalize = v =>
        v
          .replace(/[^\d.]/g, '')
          .split('.')
          .map(n => parseInt(n) || 0);
      const [parts1, parts2] = [normalize(v1), normalize(v2)];
      const maxLength = Math.max(parts1.length, parts2.length);

      for (let i = 0; i < maxLength; i++) {
        const diff = (parts1[i] || 0) - (parts2[i] || 0);
        if (diff !== 0) {
          return diff;
        }
      }
      return 0;
    },

    /**
     * Parse metadata from update script with validation
     * @param {string} text - Metadata text
     * @returns {Object} Parsed metadata with version, description, downloadUrl
     */
    parseMetadata: text => {
      if (typeof text !== 'string' || text.length > 100000) {
        console.error('[Update] Invalid metadata text');
        return { version: null, description: '', downloadUrl: UPDATE_CONFIG.autoInstallUrl };
      }

      const extractField = field =>
        text.match(new RegExp(`@${field}\\s+([^\\r\\n]+)`))?.[1]?.trim();

      const version = extractField('version');
      const description = extractField('description') || '';
      const downloadUrl = extractField('downloadURL') || UPDATE_CONFIG.autoInstallUrl;

      // Validate extracted version
      if (version && !/^\d+\.\d+\.\d+/.test(version)) {
        console.error('[Update] Invalid version format in metadata:', version);
        return { version: null, description: '', downloadUrl: UPDATE_CONFIG.autoInstallUrl };
      }

      return {
        version,
        description: description.substring(0, 500), // Limit description length
        downloadUrl,
      };
    },

    formatTimeAgo: timestamp => {
      if (!timestamp) return t('never');
      const diffMs = Date.now() - timestamp;
      const diffDays = Math.floor(diffMs / 86400000);
      const diffHours = Math.floor(diffMs / 3600000);
      const diffMinutes = Math.floor(diffMs / 60000);

      if (diffDays > 0) return pluralizeTime(diffDays, 'day');
      if (diffHours > 0) return pluralizeTime(diffHours, 'hour');
      if (diffMinutes > 0) return pluralizeTime(diffMinutes, 'minute');
      return t('justNow');
    },

    showNotification: (text, type = 'info', duration = 3000) => {
      try {
        YouTubeUtils.NotificationManager.show(text, { type, duration });
      } catch (error) {
        console.log(`[YouTube+] ${type.toUpperCase()}:`, text, error);
      }
    },
  };

  /**
   * Install update with URL validation
   * @param {Object} details - Update details containing downloadUrl and version
   * @returns {boolean} True if installation initiated successfully
   */
  const installUpdate = (details = updateState.updateDetails) => {
    const downloadUrl = details?.downloadUrl || UPDATE_CONFIG.autoInstallUrl;
    if (!downloadUrl || typeof downloadUrl !== 'string') {
      console.error('[Update] Invalid download URL for installation');
      return false;
    }

    // Validate URL format and protocol
    try {
      const parsedUrl = new URL(downloadUrl);
      const allowedDomains = ['update.greasyfork.org', 'greasyfork.org'];

      if (parsedUrl.protocol !== 'https:') {
        console.error('[Update] Only HTTPS URLs allowed for updates');
        return false;
      }

      if (!allowedDomains.includes(parsedUrl.hostname)) {
        console.error('[Update] Update URL domain not in allowlist:', parsedUrl.hostname);
        return false;
      }
    } catch (error) {
      console.error('[Update] Invalid URL format:', error);
      return false;
    }

    const markDismissed = () => {
      if (details?.version && typeof details.version === 'string') {
        try {
          sessionStorage.setItem('update_dismissed', details.version);
        } catch (err) {
          console.error('[Update] Failed to persist dismissal state:', err);
        }
      }
    };

    try {
      if (GM_openInTab_safe) {
        GM_openInTab_safe(downloadUrl, { active: true, insert: true, setParent: true });
        markDismissed();
        return true;
      }
    } catch (gmError) {
      console.error('[YouTube+] GM_openInTab update install failed:', gmError);
    }

    try {
      const popup = window.open(downloadUrl, '_blank', 'noopener');
      if (popup) {
        markDismissed();
        return true;
      }
    } catch (popupError) {
      console.error('[YouTube+] window.open update install failed:', popupError);
    }

    try {
      window.location.assign(downloadUrl);
      markDismissed();
      return true;
    } catch (navigationError) {
      console.error('[YouTube+] Navigation to update URL failed:', navigationError);
    }

    return false;
  };

  // Enhanced update notification
  const showUpdateNotification = updateDetails => {
    const notification = document.createElement('div');
    notification.className = 'youtube-enhancer-notification update-notification';
    // Use centralized notification container for consistent placement. Keep visual styles but remove fixed positioning.
    notification.style.cssText = `
    z-index: 10001; max-width: 350px;
    background: linear-gradient(135deg, rgba(255, 69, 0, 0.95), rgba(255, 140, 0, 0.95));
    color: white; padding: 16px 20px; border-radius: 12px;
    box-shadow: 0 8px 32px rgba(255, 69, 0, 0.4); backdrop-filter: blur(16px);
    border: 1px solid rgba(255, 255, 255, 0.2);
  animation: slideInFromBottom 0.4s ease-out;
    `;

    notification.innerHTML = `
        <div style="position: relative; display: flex; align-items: flex-start; gap: 12px;">
          <div style="background: rgba(255, 255, 255, 0.2); border-radius: 8px; padding: 8px; flex-shrink: 0;">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M21 12c0 1-1 2-1 2s-1-1-1-2 1-2 1-2 1 1 1 2z"/>
              <path d="m21 12-5-5v3H8v4h8v3l5-5z"/>
            </svg>
          </div>
          <div style="flex: 1; min-width: 0;">
            <div style="font-weight: 600; font-size: 15px; margin-bottom: 4px;">${t('updateAvailableTitle')}</div>
            <div style="font-size: 13px; opacity: 0.9; margin-bottom: 12px;">
              ${t('version')} ${updateDetails.version} • ${updateDetails.description || t('newFeatures')}
            </div>
            <div style="display: flex; gap: 8px;">
              <button id="update-install-btn" style="
                background: rgba(255, 255, 255, 0.9); color: #ff4500; border: none;
                padding: 8px 16px; border-radius: 6px; cursor: pointer;
                font-size: 13px; font-weight: 600; transition: all 0.2s ease;
              ">${t('installUpdate')}</button>
              <button id="update-dismiss-btn" style="
                background: rgba(255, 255, 255, 0.1); color: white;
                border: 1px solid rgba(255, 255, 255, 0.3); padding: 8px 12px;
                border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s ease;
              ">${t('later')}</button>
            </div>
          </div>
          <button id="update-close-btn" aria-label="${t('dismiss')}" style="
            position: absolute; top: -6px; right: -6px; width: 24px; height: 24px;
            border-radius: 50%; border: none; cursor: pointer; display: flex;
            align-items: center; justify-content: center; font-size: 16px; line-height: 1;
            background: rgba(255, 255, 255, 0.15); color: white; transition: background 0.2s ease;
          ">&times;</button>
        </div>
        <style>
          @keyframes slideInFromBottom {
            from { transform: translateY(100%); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
          }

          @keyframes slideOutToBottom {
            from { transform: translateY(0); opacity: 1; }
            to { transform: translateY(100%); opacity: 0; }
          }

          #update-close-btn:hover {
            background: rgba(255, 255, 255, 0.25);
          }
        </style>
      `;

    // Append into centralized notification container (created if missing)
    const _containerId = 'youtube-enhancer-notification-container';
    let _container = document.getElementById(_containerId);
    if (!_container) {
      _container = document.createElement('div');
      _container.id = _containerId;
      _container.className = 'youtube-enhancer-notification-container';
      try {
        document.body.appendChild(_container);
      } catch {
        document.body.appendChild(notification);
      }
    }
    try {
      _container.insertBefore(notification, _container.firstChild);
    } catch {
      document.body.appendChild(notification);
    }

    const removeNotification = () => {
      // use explicit slide-out animation so it exits downward like the entry
      notification.style.animation = 'slideOutToBottom 0.35s ease-in forwards';
      setTimeout(() => notification.remove(), 360);
    };

    // Event handlers
    const installBtn = notification.querySelector('#update-install-btn');
    if (installBtn) {
      installBtn.addEventListener('click', () => {
        const success = installUpdate(updateDetails);
        if (success) {
          removeNotification();
          setTimeout(() => utils.showNotification(t('installing')), 500);
        } else {
          utils.showNotification(t('manualInstallHint'), 'error', 5000);
          window.open('https://greasyfork.org/en/scripts/537017-youtube', '_blank');
        }
      });
    }

    const dismissBtn = notification.querySelector('#update-dismiss-btn');
    if (dismissBtn) {
      dismissBtn.addEventListener('click', () => {
        if (updateDetails?.version) {
          sessionStorage.setItem('update_dismissed', updateDetails.version);
        }
        removeNotification();
      });
    }

    const closeBtn = notification.querySelector('#update-close-btn');
    if (closeBtn) {
      closeBtn.addEventListener('click', () => {
        if (updateDetails?.version) {
          sessionStorage.setItem('update_dismissed', updateDetails.version);
        }
        removeNotification();
      });
    }

    // Auto-dismiss
    setTimeout(() => {
      if (notification.isConnected) removeNotification();
    }, UPDATE_CONFIG.notificationDuration);
  };

  /**
   * Check for updates with URL validation and timeout protection
   * @param {boolean} force - Force update check even if recently checked
   * @returns {Promise<void>}
   */
  const checkForUpdates = async (force = false) => {
    if (!UPDATE_CONFIG.enabled || updateState.checkInProgress) {
      return;
    }

    const now = Date.now();
    if (!force && now - updateState.lastCheck < UPDATE_CONFIG.checkInterval) {
      return;
    }

    updateState.checkInProgress = true;

    try {
      // Validate update URL before fetch
      try {
        const parsedUrl = new URL(UPDATE_CONFIG.updateUrl);
        if (parsedUrl.protocol !== 'https:') {
          throw new Error('Update URL must use HTTPS');
        }
        if (!parsedUrl.hostname.includes('greasyfork.org')) {
          throw new Error('Update URL must be from greasyfork.org');
        }
      } catch (urlError) {
        console.error('[Update] Invalid update URL configuration:', urlError);
        updateState.checkInProgress = false;
        return;
      }

      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 10000);

      const response = await fetch(UPDATE_CONFIG.updateUrl, {
        method: 'GET',
        cache: 'no-cache',
        signal: controller.signal,
        headers: { Accept: 'text/plain', 'User-Agent': 'YouTube+ UpdateChecker' },
      });

      clearTimeout(timeoutId);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const metaText = await response.text();
      const updateDetails = utils.parseMetadata(metaText);

      if (updateDetails.version) {
        updateState.lastCheck = now;
        updateState.lastVersion = updateDetails.version;
        updateState.updateDetails = updateDetails;

        const comparison = utils.compareVersions(
          UPDATE_CONFIG.currentVersion,
          updateDetails.version
        );
        updateState.updateAvailable = comparison < 0;

        if (
          updateState.updateAvailable &&
          (force || sessionStorage.getItem('update_dismissed') !== updateDetails.version)
        ) {
          showUpdateNotification(updateDetails);
          console.log(`YouTube + Update available: ${updateDetails.version}`);
        } else if (force) {
          if (updateState.updateAvailable) {
            utils.showNotification(
              t('updateAvailableMsg').replace('{version}', updateDetails.version)
            );
          } else {
            utils.showNotification(
              t('upToDateMsg').replace('{version}', UPDATE_CONFIG.currentVersion)
            );
          }
        }

        utils.saveSettings();
      }
    } catch (error) {
      if (typeof console !== 'undefined' && console.error) {
        console.error('Update check failed:', error);
      }
      if (force) {
        utils.showNotification(
          t('updateCheckFailed').replace('{msg}', error.message),
          'error',
          4000
        );
      }
    } finally {
      updateState.checkInProgress = false;
    }
  };

  // Optimized settings UI
  const addUpdateSettings = () => {
    // ✅ Use cached querySelector
    const aboutSection = YouTubeUtils.querySelector(
      '.ytp-plus-settings-section[data-section="about"]'
    );
    if (!aboutSection || YouTubeUtils.querySelector('.update-settings-container')) return;

    const updateContainer = document.createElement('div');
    updateContainer.className = 'update-settings-container';
    updateContainer.style.cssText = `
        padding: 16px; margin-top: 20px; border-radius: 12px;
        background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
        border: 1px solid var(--yt-glass-border); backdrop-filter: blur(8px);
      `;

    const lastCheckTime = utils.formatTimeAgo(updateState.lastCheck);

    updateContainer.innerHTML = `
        <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
          <h3 style="margin: 0; font-size: 16px; font-weight: 600; color: var(--yt-spec-text-primary);">
            ${t('enhancedExperience')}
          </h3>
        </div>
        
        <div style="display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center; 
                    padding: 16px; background: rgba(255, 255, 255, 0.03); border-radius: 10px; margin-bottom: 16px;">
          <div>
            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
              <span style="font-size: 14px; font-weight: 600; color: var(--yt-spec-text-primary);">${t('currentVersion')}</span>
              <span style="font-size: 13px; font-weight: 600; color: var(--yt-spec-text-primary); 
                           padding: 3px 10px; background: rgba(255, 255, 255, 0.1); border-radius: 12px; 
                           border: 1px solid rgba(255, 255, 255, 0.2);">${UPDATE_CONFIG.currentVersion}</span>
            </div>
            <div style="font-size: 12px; color: var(--yt-spec-text-secondary);">
              ${t('lastChecked')}: <span style="font-weight: 500;">${lastCheckTime}</span>
              ${
                updateState.lastVersion && updateState.lastVersion !== UPDATE_CONFIG.currentVersion
                  ? `<br>${t('latestAvailable')}: <span style="color: #ff6666; font-weight: 600;">${updateState.lastVersion}</span>`
                  : ''
              }
            </div>
          </div>
          
          ${
            updateState.updateAvailable
              ? `
            <div style="display: flex; flex-direction: column; align-items: flex-end; gap: 8px;">
              <div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px; 
                          background: linear-gradient(135deg, rgba(255, 68, 68, 0.2), rgba(255, 68, 68, 0.3)); 
                          border: 1px solid rgba(255, 68, 68, 0.4); border-radius: 20px;">
                <div style="width: 6px; height: 6px; background: #ff4444; border-radius: 50%; animation: pulse 2s infinite;"></div>
                <span style="font-size: 11px; color: #ff6666; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
                  ${t('updateAvailable')}
                </span>
              </div>
              <button id="install-update-btn" style="background: linear-gradient(135deg, #ff4500, #ff6b35); 
                      color: white; border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; 
                      font-size: 12px; font-weight: 600; transition: all 0.3s ease; 
                      box-shadow: 0 4px 12px rgba(255, 69, 0, 0.3);">${t('installUpdate')}</button>
            </div>
          `
              : `
            <div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px; 
                        background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.3)); 
                        border: 1px solid rgba(34, 197, 94, 0.4); border-radius: 20px;">
              <div style="width: 6px; height: 6px; background: #22c55e; border-radius: 50%;"></div>
              <span style="font-size: 11px; color: #22c55e; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
                ${t('upToDate')}
              </span>
            </div>
          `
          }
        </div>
        
        <div style="display: flex; gap: 12px;">
          <button class="ytp-plus-button ytp-plus-button-primary" id="manual-update-check" 
                  style="flex: 1; padding: 12px; font-size: 13px; font-weight: 600;">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
              <path d="M21.5 2v6h-6M2.5 22v-6h6M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1"/>
            </svg>
            ${t('checkForUpdates')}
          </button>
          <button class="ytp-plus-button" id="open-update-page" 
                  style="padding: 12px 16px; font-size: 13px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">
              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
              <polyline points="15,3 21,3 21,9"/>
              <line x1="10" y1="14" x2="21" y2="3"/>
            </svg>
          </button>
        </div>

        <style>
          @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(1.1); } }
          @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        </style>
      `;

    aboutSection.appendChild(updateContainer);

    // Event listeners with optimization
    const attachClickHandler = (id, handler) => {
      const element = document.getElementById(id);
      if (element) YouTubeUtils.cleanupManager.registerListener(element, 'click', handler);
    };

    attachClickHandler('manual-update-check', async e => {
      const button = /** @type {EventTarget & HTMLElement} */ (e.target);
      const originalHTML = button.innerHTML;

      button.innerHTML = `
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 
               style="margin-right: 6px; animation: spin 1s linear infinite;">
            <path d="M21.5 2v6h-6M2.5 22v-6h6M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1"/>
          </svg>
          ${t('checkingForUpdates')}
        `;
      button.disabled = true;

      await checkForUpdates(true);

      setTimeout(() => {
        button.innerHTML = originalHTML;
        button.disabled = false;
      }, 1000);
    });

    attachClickHandler('install-update-btn', () => {
      const success = installUpdate();
      if (success) {
        utils.showNotification(t('installing'));
      } else {
        utils.showNotification(t('manualInstallHint'), 'error', 5000);
        window.open('https://greasyfork.org/en/scripts/537017-youtube', '_blank');
      }
    });

    attachClickHandler('open-update-page', () => {
      utils.showNotification(t('updatePageFallback'));
      window.open('https://greasyfork.org/en/scripts/537017-youtube', '_blank');
    });
  };

  // Optimized initialization
  const init = () => {
    utils.loadSettings();

    // Initial check with delay
    setTimeout(() => checkForUpdates(), 3000);

    // Periodic checks
    // ✅ Register interval in cleanupManager
    const intervalId = setInterval(() => checkForUpdates(), UPDATE_CONFIG.checkInterval);
    YouTubeUtils.cleanupManager.registerInterval(intervalId);
    window.addEventListener('beforeunload', () => clearInterval(intervalId));

    // Optimized settings modal observer
    let settingsObserved = false;
    const observer = new MutationObserver(mutations => {
      if (settingsObserved) return;

      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof Element && node.classList?.contains('ytp-plus-settings-modal')) {
            settingsObserved = true;
            setTimeout(addUpdateSettings, 100);
            return;
          }
        }
      }

      // ✅ Use cached querySelector
      const aboutNavItem = YouTubeUtils.querySelector(
        '.ytp-plus-settings-nav-item[data-section="about"].active:not([data-observed])'
      );
      if (aboutNavItem) {
        aboutNavItem.setAttribute('data-observed', '');
        setTimeout(addUpdateSettings, 50);
      }
    });

    // ✅ Register observer in cleanupManager
    YouTubeUtils.cleanupManager.registerObserver(observer);

    // ✅ Safe observe with document.body check
    if (document.body) {
      observer.observe(document.body, { childList: true, subtree: true });
    } else {
      document.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, { childList: true, subtree: true });
      });
    }

    // Optimized click handler
    // ✅ Register global listener in cleanupManager
    const clickHandler = e => {
      const target = /** @type {EventTarget & HTMLElement} */ (e.target);
      if (
        target.classList?.contains('ytp-plus-settings-nav-item') &&
        target.dataset?.section === 'about'
      ) {
        setTimeout(addUpdateSettings, 50);
      }
    };
    YouTubeUtils.cleanupManager.registerListener(document, 'click', clickHandler, {
      passive: true,
      capture: true,
    });

    if (typeof console !== 'undefined' && console.log) {
      console.log('YouTube + Update Checker initialized', {
        version: UPDATE_CONFIG.currentVersion,
        enabled: UPDATE_CONFIG.enabled,
        lastCheck: new Date(updateState.lastCheck).toLocaleString(),
        updateAvailable: updateState.updateAvailable,
      });
    }
  };

  // Start
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();

// --- MODULE: style.js ---

// Inject upstream "yt-thumbnail hover" and "yt-Immersive search" CSS
(function () {
  try {
    const host = typeof location !== 'undefined' ? location.hostname : '';
    if (!host) return;
    // Only apply on youtube domains
    if (!/(^|\.)youtube\.com$/.test(host) && !/\.youtube\.google/.test(host)) return;

    const css = `
/* yt-thumbnail hover */
#inline-preview-player {
  transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) 1s !important;
  transform: scale(1) !important;
}
#video-preview-container:has(#inline-preview-player) {
  transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
  border-radius: 1.2em !important;
  overflow: hidden !important;
  transform: scale(1) !important;
}
#video-preview-container:has(#inline-preview-player):hover {
  transform: scale(1.25) !important;
  box-shadow: #0008 0px 0px 60px !important;
  transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s !important;
}
ytd-app #content {
  opacity: 1 !important;
  transition: opacity 0.3s ease-in-out !important;
}
ytd-app:has(#video-preview-container:hover) #content {
  opacity: 0.5 !important;
  transition: opacity 4s ease-in-out 1s !important;
}

/* yt-Immersive search */
#page-manager, yt-searchbox {
  transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.35) !important;
}
#masthead yt-searchbox button[aria-label="Search"] {
  display: none !important;
}
.ytSearchboxComponentInputBox {
  border-radius: 2em !important;
}
yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) {
  position: relative !important;
  left: 0vw !important;
  top: -30vh !important;
  height: 40px !important;
  max-width: 600px !important;
  transform: scale(1) !important;
}
@media only screen and (min-width: 1400px) {
  yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) {
    height: 60px !important;
    max-width: 700px !important;
    transform: scale(1.1) !important;
  }
}
yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) .ytSearchboxComponentInputBox,
yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {
  background-color: #fffb !important;
  box-shadow: black 0 0 30px !important;
}
@media (prefers-color-scheme: dark) {
  yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) .ytSearchboxComponentInputBox,
  yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {
    background-color: #000b !important;
  }
}
yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {
  margin-top: 10px !important;
}
@media only screen and (min-width: 1400px) {
  yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {
    margin-top: 30px !important;
  }
}
.ytd-masthead #center:has(.ytSearchboxComponentInputBoxHasFocus) {
  height: 100vh !important;
  width: 100vw !important;
  left: 0 !important;
  top: 0 !important;
  position: fixed !important;
  justify-content: center !important;
  align-items: center !important;
}
#content:has(.ytSearchboxComponentInputBoxHasFocus) #page-manager {
  filter: blur(20px) !important;
  transform: scale(1.05) !important;
}

/* ===== No voice search button ===== */
#voice-search-button {
  display: none !important;
}

/* ===== YouTube Transparent header (from styles.json) ===== */
#masthead-container {
  #background.ytd-masthead {
    background-color: #00000000 !important;
  }
}

/* ===== Toggle side guide - полностью убирает боковую панель ===== */
ytd-mini-guide-renderer,
[theater=""] #contentContainer::after {
  display: none !important;
}
tp-yt-app-drawer > #contentContainer:not([opened=""]), 
#contentContainer:not([opened=""]) #guide-content, 
ytd-mini-guide-renderer, 
ytd-mini-guide-entry-renderer {
  background-color: var(--yt-spec-text-primary-inverse) !important;
  background: var(--yt-spec-text-primary-inverse) !important;
}
#content:not(:has(#contentContainer[opened=""])) #page-manager {
  margin-left: 0 !important;
}
ytd-app:not([guide-persistent-and-visible=""]) tp-yt-app-drawer > #contentContainer {
  background-color: var(--yt-spec-text-primary-inverse) !important;
}
ytd-alert-with-button-renderer {
  align-items: center !important;
  justify-content: center !important;
}

/* ===== Clean side guide - убирает YouTube Premium, Sports, Settings и footer из guide ===== */
ytd-guide-section-renderer:has([title="YouTube Premium"]),
ytd-guide-renderer #footer {
  display: none !important;
}
ytd-guide-section-renderer,
ytd-guide-collapsible-section-entry-renderer {
  border: none !important;
}

`;

    const ID = 'ytp-zen-features-style';
    if (document.getElementById(ID)) return;
    const style = document.createElement('style');
    style.id = ID;
    style.appendChild(document.createTextNode(css));
    (document.head || document.documentElement).appendChild(style);
  } catch (err) {
    // fail silently; don't break host page
    console.error('zen-youtube-features injection failed', err);
  }
})();