Nitro Type - Universal Music Player

Play music from Spotify, YouTube & Apple Music while you race on Nitro Type. Paste any playlist, album, or track URL — playback is bridged through YouTube with album art, shuffle, session resume, and more.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Nitro Type - Universal Music Player
// @namespace    https://nitrotype.info
// @version      2.1.2
// @license      GPL-3.0-or-later
// @author       Captain.Loveridge
// @description  Play music from Spotify, YouTube & Apple Music while you race on Nitro Type. Paste any playlist, album, or track URL — playback is bridged through YouTube with album art, shuffle, session resume, and more.
// @match        https://www.nitrotype.com/race
// @match        https://www.nitrotype.com/race?*
// @match        https://www.nitrotype.com/race/*
// @match        *://*.nitrotype.com/settings/mods*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        unsafeWindow
// @connect      open.spotify.com
// @connect      api.spotify.com
// @connect      *.invidious.io
// @connect      vid.puffyan.us
// @connect      invidious.snopyta.org
// @connect      invidious.nerdvpn.de
// @connect      inv.nadeko.net
// @connect      pipedapi.kavin.rocks
// @connect      pipedapi.adminforge.de
// @connect      pipedapi.syncpundit.io
// @connect      www.youtube.com
// @connect      music.youtube.com
// @connect      youtube.com
// @connect      www.google.com
// @connect      noembed.com
// @connect      itunes.apple.com
// @connect      music.apple.com
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  if (window.top !== window.self) return;

  const pageWindow = (typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window;
  const SCRIPT_SINGLETON_KEY = '__ntUniversalMusicSingleton';
  if (pageWindow[SCRIPT_SINGLETON_KEY]) {
    try { console.info('[NT Music] Duplicate instance detected; skipping.'); } catch (_) { }
    return;
  }
  pageWindow[SCRIPT_SINGLETON_KEY] = true;

  const SCRIPT_VERSION = '2.1.2';
  const YT_CACHE_VERSION = 1;
  const STATION_ID = 'spotify';
  const OEMBED_BASE = 'https://open.spotify.com/oembed?url=';
  const YT_IFRAME_API_SRC = 'https://www.youtube.com/iframe_api';
  const YT_READY_EVENT = 'nt-yt-iframe-api-ready';
  const YT_ERROR_EVENT = 'nt-yt-iframe-api-error';
  const EMBED_API_SRC = 'https://open.spotify.com/embed/iframe-api/v1';
  const EMBED_READY_EVENT = 'nt-spotify-embed-api-ready';
  const EMBED_ERROR_EVENT = 'nt-spotify-embed-api-error';
  const PROGRESS_TICK_MS = 1000;
  const WEAK_META_RETRY_MS = 15000;
  const FRAME_WATCH_TICK_MS = 250;
  const AUTO_ACTIVATE_DELAY_MS = 250;
  const PRE_CUE_INIT_DELAY_MS = 250;
  const FRAME_UI_REFRESH_MS = 1000;
  const NATIVE_AUDIO_REFRESH_MS = 3000;
  const TRACE_STORAGE_KEY = 'nt_music_runtime_trace';
  const TRACE_LIMIT = 120;

  const INVIDIOUS_INSTANCES = [
    'https://vid.puffyan.us',
    'https://invidious.snopyta.org',
    'https://invidious.nerdvpn.de',
    'https://inv.nadeko.net',
  ];

  const PIPED_INSTANCES = [
    'https://pipedapi.kavin.rocks',
    'https://pipedapi.adminforge.de',
    'https://pipedapi.syncpundit.io',
  ];

  const STORAGE_KEYS = {
    sourceUri: 'nt_spotify_embed_source_uri',
    sourceUrl: 'nt_spotify_embed_source_url',
    sessionActive: 'nt_spotify_session_active',
    queueIndex: 'nt_spotify_queue_index',
    playbackPositionSec: 'nt_spotify_playback_pos_sec',
    shuffleOrder: 'nt_spotify_shuffle_order',
    shufflePosition: 'nt_spotify_shuffle_position',
    queueMode: 'nt_spotify_queue_mode',
  };

  // (boundReactElements removed — unused after React approach was replaced by Howler lock)

  const state = {
    frame: null,
    frameDoc: null,
    frameWindow: null,
    frameWatchTimer: null,
    frameWatchStartTimer: null,
    frameSuppressRetryTimers: [],
    autoActivateTimer: null,
    preCueTimer: null,
    nextTrackPreResolveTimer: null,

    active: false,
    pendingSelection: false,
    progressTimer: null,

    ytApi: null,
    ytApiLoaded: false,
    ytApiLoadStarted: false,
    ytApiLoadFailed: false,
    ytApiWaiters: [],
    ytPlayer: null,
    ytPlayerPromise: null,
    ytPlayerReady: false,
    ytPlayerHost: null,

    queue: [],
    queueIndex: -1,
    queueMode: 'shuffle',
    shuffleOrder: [],
    shufflePosition: -1,
    playHistory: [],

    playback: {
      uri: '',
      position: 0,
      duration: 0,
      isPaused: true,
      isBuffering: false,
      updatedAt: 0,
    },

    trackMeta: null,
    outputVolume: 1,
    metaCache: new Map(),
    metaRetryAt: new Map(),
    sourceTrackListCache: new Map(),
    ytSearchCache: new Map(),
    ytSearchInFlightByUri: new Map(),

    spotifyAccessToken: '',
    spotifyAccessTokenExpiresAt: 0,
    spotifyIsAnonymous: false,
    spotifyIsPremium: false,

    embedApi: null,
    embedWaiters: [],
    embedHost: null,
    embedController: null,
    embedApiLoadStarted: false,
    embedApiLoadFailed: false,

    pendingToast: null,
    isActivating: false,
    suppressNativeStationEventsUntil: 0,
    originalStationLabel: '',
    originalIndicatorHTML: '',

    preCuePromise: null,
    preCueResult: null,
    preCueSourceUri: '',

    _audioObserverDoc: null,
    _audioObserverTop: null,
    _frameUiObserver: null,
    _frameUiSyncQueued: false,
    _lastFrameUiSync: 0,
    _lastNativeMuteCheck: 0,
    _stopCooldownUntil: 0,
    _spotifyItemActionUntil: 0,
    _pendingNativeStationHandoffTimer: 0,
    _pendingNativeStationHandoffToken: 0,
    _pendingSfxSyncTimer: 0,
    _autoActivateAttempted: false,
    _pendingSeekSec: 0,
    _lastForcedNativeMusicOff: 0,
    _howlerLocked: false,       // true while Howler._volume/_muted are property-locked
    _advancingQueue: false,     // semaphore to prevent double-fire of playNextInQueue
    _lastYTErrorCode: 0,        // debounce repeated YT error events
    _lastYTErrorTime: 0,
  };
  let musicRuntimeStarted = false;

  function formatTraceError(error) {
    if (!error) return '';
    if (typeof error === 'string') return error;
    const message = String(error.message || error.toString() || 'Unknown error');
    const stack = typeof error.stack === 'string' ? error.stack.split('\n').slice(0, 2).join(' | ') : '';
    return stack ? `${message} :: ${stack}` : message;
  }

  function getMusicTraceEntries() {
    try {
      const raw = localStorage.getItem(TRACE_STORAGE_KEY);
      const parsed = raw ? JSON.parse(raw) : [];
      return Array.isArray(parsed) ? parsed : [];
    } catch (_) {
      return [];
    }
  }

  function clearMusicTrace() {
    try {
      localStorage.removeItem(TRACE_STORAGE_KEY);
    } catch (_) {
      // noop
    }
  }

  function traceMusic(event, data) {
    try {
      const entries = getMusicTraceEntries();
      entries.push({
        t: Date.now(),
        iso: new Date().toISOString(),
        event: String(event || ''),
        data: data && typeof data === 'object' ? data : { value: data },
      });
      while (entries.length > TRACE_LIMIT) entries.shift();
      localStorage.setItem(TRACE_STORAGE_KEY, JSON.stringify(entries));
    } catch (_) {
      // noop
    }
  }

  function initNTRouteHelper(targetWindow = window) {
    const hostWindow = targetWindow || window;
    const existing = hostWindow.NTRouteHelper;
    if (existing && existing.__ntRouteHelperReady && typeof existing.subscribe === 'function') {
      return existing;
    }

    const helper = existing || {};
    const listeners = helper.listeners instanceof Set ? helper.listeners : new Set();
    let currentKey = `${hostWindow.location.pathname}${hostWindow.location.search}${hostWindow.location.hash}`;

    const notify = (reason) => {
      const nextKey = `${hostWindow.location.pathname}${hostWindow.location.search}${hostWindow.location.hash}`;
      if (reason !== 'init' && nextKey === currentKey) return;
      const previousKey = currentKey;
      currentKey = nextKey;
      listeners.forEach((listener) => {
        try {
          listener({
            reason,
            previous: previousKey,
            current: nextKey,
            pathname: hostWindow.location.pathname
          });
        } catch (error) {
          console.error('[NTRouteHelper] listener error', error);
        }
      });
      helper.currentKey = currentKey;
    };

    if (!helper.__ntRouteHelperWrapped) {
      const wrapHistoryMethod = (methodName) => {
        const current = hostWindow.history[methodName];
        if (typeof current !== 'function' || current.__ntRouteHelperWrapped) return;
        const wrapped = function () {
          const result = current.apply(this, arguments);
          queueMicrotask(() => notify(methodName));
          return result;
        };
        wrapped.__ntRouteHelperWrapped = true;
        wrapped.__ntRouteHelperOriginal = current;
        hostWindow.history[methodName] = wrapped;
      };
      wrapHistoryMethod('pushState');
      wrapHistoryMethod('replaceState');
      hostWindow.addEventListener('popstate', () => queueMicrotask(() => notify('popstate')));
      helper.__ntRouteHelperWrapped = true;
    }

    helper.listeners = listeners;
    helper.currentKey = currentKey;
    helper.version = '1.0.0';
    helper.__ntRouteHelperReady = true;
    helper.subscribe = function (listener, options = {}) {
      if (typeof listener !== 'function') return () => { };
      listeners.add(listener);
      if (options.immediate !== false) {
        try {
          listener({
            reason: 'init',
            previous: currentKey,
            current: currentKey,
            pathname: hostWindow.location.pathname
          });
        } catch (error) {
          console.error('[NTRouteHelper] immediate listener error', error);
        }
      }
      return () => listeners.delete(listener);
    };
    helper.notify = notify;

    hostWindow.NTRouteHelper = helper;
    return helper;
  }

  function isMusicRaceRoute(pathname = window.location.pathname) {
    return /^\/race(?:\/|$)/i.test(String(pathname || ''));
  }

  // ─── Mod Menu Manifest Bridge ───────────────────────────────────────────────
  const NTCFG_MUSIC_MANIFEST_ID = 'music-player';
  const NTCFG_MUSIC_MANIFEST_KEY = `ntcfg:manifest:${NTCFG_MUSIC_MANIFEST_ID}`;
  const NTCFG_MUSIC_VALUE_PREFIX = `ntcfg:${NTCFG_MUSIC_MANIFEST_ID}:`;
  const NTCFG_MUSIC_BRIDGE_VERSION = '1.5.0-bridge.1';
  const MUSIC_STORAGE_VERSION = 1;
  const MUSIC_STORAGE_VERSION_KEY = `${NTCFG_MUSIC_VALUE_PREFIX}__storage_version`;

  const MUSIC_SHARED_SETTINGS = {
    SOURCE_URL: {
      type: 'text',
      label: 'Source URL',
      default: '',
      group: 'Source',
      description: 'Spotify, YouTube, or Apple Music playlist/album/track URL.',
      placeholder: 'Paste playlist, album, or track URL'
    },
    DEFAULT_PLATFORM: {
      type: 'select',
      label: 'Default Platform',
      default: 'spotify',
      group: 'Source',
      description: 'Controls the label styling and initial queue hint when no source is configured.',
      options: [
        { value: 'spotify', label: 'Spotify' },
        { value: 'youtube', label: 'YouTube' },
        { value: 'apple-music', label: 'Apple Music' }
      ]
    },
    SESSION_RESUME: {
      type: 'boolean',
      label: 'Resume Last Session',
      default: true,
      group: 'Source',
      description: 'Restores queue position and shuffle mode after reload.'
    },
    AUTO_ACTIVATE: {
      type: 'boolean',
      label: 'Auto Activate on Race Load',
      default: true,
      group: 'Source',
      description: 'Immediately bring the player shell online on race pages.'
    },
    QUEUE_MODE: {
      type: 'select',
      label: 'Queue Mode',
      default: 'shuffle',
      group: 'Playback',
      description: 'Controls how tracks are ordered during playback.',
      options: [
        { value: 'shuffle', label: 'Shuffle' },
        { value: 'sequential', label: 'Sequential' }
      ]
    },
    SHOW_ALBUM_ART: {
      type: 'boolean',
      label: 'Show Album Art',
      default: true,
      group: 'Playback',
      description: 'Display album cover artwork in the inline now-playing card.'
    },
    MUTE_NATIVE_STATION: {
      type: 'boolean',
      label: 'Mute Native Nitro Type Station',
      default: true,
      group: 'Playback',
      description: 'Silence the built-in race station when this player is active.'
    },
    PROGRESS_TICK_MS: {
      type: 'number',
      label: 'Progress Tick (ms)',
      default: 1000,
      group: 'Playback',
      description: 'How often the inline player UI refreshes progress text.',
      min: 250,
      max: 5000,
      step: 50
    },
    DEBUG_LOGGING: {
      type: 'boolean',
      label: 'Debug Logging',
      default: false,
      group: 'Advanced',
      description: 'Enable verbose console logging for troubleshooting.'
    }
  };

  const getNtcfgMusicStorageKey = (settingKey) => `${NTCFG_MUSIC_VALUE_PREFIX}${settingKey}`;

  const coerceNtcfgMusicValue = (settingKey, value) => {
    const meta = MUSIC_SHARED_SETTINGS[settingKey];
    if (!meta) return value;

    if (meta.type === 'boolean') {
      if (typeof value === 'string') {
        const raw = value.trim().toLowerCase();
        if (raw === 'false' || raw === '0' || raw === 'off') return false;
        if (raw === 'true' || raw === '1' || raw === 'on') return true;
      }
      return !!value;
    }
    if (meta.type === 'number') {
      const fallback = Number(meta.default);
      const parsed = Number(value);
      let normalized = Number.isFinite(parsed) ? parsed : fallback;
      const min = Number(meta.min);
      const max = Number(meta.max);
      const step = Number(meta.step);
      if (Number.isFinite(step) && step >= 1) {
        normalized = Math.round(normalized);
      }
      if (Number.isFinite(min)) normalized = Math.max(min, normalized);
      if (Number.isFinite(max)) normalized = Math.min(max, normalized);
      return normalized;
    }
    if (meta.type === 'select') {
      const raw = String(value ?? '').trim();
      const options = Array.isArray(meta.options) ? meta.options : [];
      return options.some((opt) => String(opt.value) === raw) ? raw : meta.default;
    }
    return String(value ?? meta.default);
  };

  const readNtcfgMusicValue = (settingKey) => {
    const meta = MUSIC_SHARED_SETTINGS[settingKey];
    if (!meta) return undefined;
    try {
      const raw = localStorage.getItem(getNtcfgMusicStorageKey(settingKey));
      if (raw == null) return meta.default;
      const parsed = JSON.parse(raw);
      return coerceNtcfgMusicValue(settingKey, parsed);
    } catch {
      return meta.default;
    }
  };

  const writeNtcfgMusicValue = (settingKey, value) => {
    try {
      const serialized = JSON.stringify(value);
      if (localStorage.getItem(getNtcfgMusicStorageKey(settingKey)) !== serialized) {
        localStorage.setItem(getNtcfgMusicStorageKey(settingKey), serialized);
      }
    } catch {
      // ignore storage sync failures
    }
  };

  // GM_getValue/GM_setValue mapping for music player settings
  const MUSIC_GM_KEY_MAP = {
    SOURCE_URL: STORAGE_KEYS.sourceUrl,
    QUEUE_MODE: STORAGE_KEYS.queueMode
  };

  const syncNtcfgMusicSettingFromGM = (settingKey) => {
    const meta = MUSIC_SHARED_SETTINGS[settingKey];
    if (!meta) return;

    const gmKey = MUSIC_GM_KEY_MAP[settingKey];
    let currentValue;
    if (gmKey) {
      // Settings that map to existing GM storage keys
      currentValue = GM_getValue(gmKey, meta.default);
    } else {
      // Settings stored purely as GM values by setting key
      currentValue = GM_getValue(`ntmusic_${settingKey}`, meta.default);
    }
    const normalized = coerceNtcfgMusicValue(settingKey, currentValue);
    writeNtcfgMusicValue(settingKey, normalized);
  };

  const syncAllNtcfgMusicSettingsFromGM = () => {
    Object.keys(MUSIC_SHARED_SETTINGS).forEach(syncNtcfgMusicSettingFromGM);
  };

  const setNtcfgMusicValue = (settingKey, value) => {
    const meta = MUSIC_SHARED_SETTINGS[settingKey];
    if (!meta) return value;

    const normalized = coerceNtcfgMusicValue(settingKey, value);
    const gmKey = MUSIC_GM_KEY_MAP[settingKey];
    if (gmKey) {
      GM_setValue(gmKey, normalized);
    } else {
      GM_setValue(`ntmusic_${settingKey}`, normalized);
    }
    writeNtcfgMusicValue(settingKey, normalized);
    return normalized;
  };

  const applyNtcfgMusicValueDirect = (settingKey, value) => {
    const meta = MUSIC_SHARED_SETTINGS[settingKey];
    if (!meta) return;
    const normalized = coerceNtcfgMusicValue(settingKey, value);
    setNtcfgMusicValue(settingKey, normalized);
    handleMusicSettingChange(settingKey, normalized);
  };

  const applyNtcfgMusicValueIfChanged = (settingKey, value) => {
    const meta = MUSIC_SHARED_SETTINGS[settingKey];
    if (!meta) return;

    const normalized = coerceNtcfgMusicValue(settingKey, value);
    const gmKey = MUSIC_GM_KEY_MAP[settingKey];
    const currentRaw = gmKey ? GM_getValue(gmKey, meta.default) : GM_getValue(`ntmusic_${settingKey}`, meta.default);
    const currentValue = coerceNtcfgMusicValue(settingKey, currentRaw);

    if (JSON.stringify(currentValue) !== JSON.stringify(normalized)) {
      setNtcfgMusicValue(settingKey, normalized);
      handleMusicSettingChange(settingKey, normalized);
    }
  };

  // Live setting change handler — wires mod menu changes to actual code
  function handleMusicSettingChange(settingKey, value) {
    switch (settingKey) {
      case 'SOURCE_URL': {
        const url = String(value || '').trim();
        if (!url) {
          clearSavedSource();
          break;
        }
        if (typeof parseAndSaveSourceFromInput === 'function') {
          void parseAndSaveSourceFromInput(url, false);
          break;
        }
        const parsed = typeof parseSourceInput === 'function' ? parseSourceInput(url) : null;
        if (parsed) {
          saveSource(parsed);
          if (state.active) {
            void loadSource(parsed.uri);
          }
        } else {
          setNtcfgMusicValue('SOURCE_URL', url);
        }
        break;
      }
      case 'SHOW_ALBUM_ART':
        // Live toggle — re-render to show/hide art
        if (typeof renderNowPlaying === 'function' && state.active) {
          renderNowPlaying();
        }
        break;
      case 'QUEUE_MODE':
        // Update in-memory queue mode if it differs
        if (state.queueMode !== value) {
          state.queueMode = value;
          if (value === 'shuffle' && state.queue.length > 1) {
            generateShuffleOrder(state.queueIndex >= 0 ? state.queueIndex : 0);
          } else if (value === 'sequential') {
            state.shuffleOrder = [];
            state.shufflePosition = -1;
          }
        }
        break;
      case 'MUTE_NATIVE_STATION':
        // If toggled off while active, restore native audio immediately
        if (!value && state.active && state.frameDoc) {
          restoreNativeAudio(state.frameDoc);
        }
        // If toggled on while active, suppress native audio immediately
        if (value && state.active && state.frameDoc) {
          suppressNativeAudio(state.frameDoc);
        }
        break;
      case 'PROGRESS_TICK_MS':
        // Restart the progress timer with the new interval
        if (state.active) {
          startProgressTimer();
        }
        break;
      case 'DEBUG_LOGGING':
        _ntMusicApi.verbose = value === true;
        break;
      // SESSION_RESUME and AUTO_ACTIVATE are read at decision time — no live action needed
    }
  }

  const registerNtcfgMusicManifest = () => {
    try {
      const manifest = {
        id: NTCFG_MUSIC_MANIFEST_ID,
        name: 'Music Player',
        version: NTCFG_MUSIC_BRIDGE_VERSION,
        scriptVersion: SCRIPT_VERSION,
        storageVersion: MUSIC_STORAGE_VERSION,
        supportsGlobalReset: true,
        description: 'Universal music playback shell for Spotify, YouTube, and Apple Music sources.',
        sections: [
          { id: 'source', title: 'Source', subtitle: 'Where the queue comes from.', resetButton: true },
          { id: 'playback', title: 'Playback', subtitle: 'Queue behavior and in-race control feel.', resetButton: true },
          { id: 'advanced', title: 'Advanced', subtitle: 'Debug and diagnostic controls.', resetButton: true }
        ],
        settings: MUSIC_SHARED_SETTINGS
      };
      const serialized = JSON.stringify(manifest);
      if (localStorage.getItem(NTCFG_MUSIC_MANIFEST_KEY) !== serialized) {
        localStorage.setItem(NTCFG_MUSIC_MANIFEST_KEY, serialized);
      }
    } catch {
      // ignore manifest registration failures
    }
  };

  // Helper functions for reading settings at decision time
  function isMusicSessionResumeEnabled() {
    const val = readNtcfgMusicValue('SESSION_RESUME');
    return val !== false;
  }

  function isMusicAutoActivateEnabled() {
    const val = readNtcfgMusicValue('AUTO_ACTIVATE');
    return val !== false;
  }

  function isMusicMuteNativeEnabled() {
    const val = readNtcfgMusicValue('MUTE_NATIVE_STATION');
    return val !== false;
  }

  function getProgressTickMs() {
    const val = readNtcfgMusicValue('PROGRESS_TICK_MS');
    return typeof val === 'number' && val >= 250 ? val : 1000;
  }

  function isMusicDebugEnabled() {
    return readNtcfgMusicValue('DEBUG_LOGGING') === true;
  }

  function isMusicAlbumArtEnabled() {
    const val = readNtcfgMusicValue('SHOW_ALBUM_ART');
    return val !== false;
  }

  function getDefaultPlatform() {
    const val = readNtcfgMusicValue('DEFAULT_PLATFORM');
    return val || 'spotify';
  }

  function dispatchMusicActionResult(requestId, status, error = '') {
    if (!requestId) return;
    try {
      document.dispatchEvent(new CustomEvent('ntcfg:action-result', {
        detail: {
          requestId,
          script: NTCFG_MUSIC_MANIFEST_ID,
          status,
          error
        }
      }));
    } catch {
      // ignore dispatch failures
    }
  }

  function resetMusicSettingsToDefaults() {
    Object.entries(MUSIC_SHARED_SETTINGS).forEach(([settingKey, meta]) => {
      if (!meta || meta.type === 'note' || meta.type === 'action') return;
      setNtcfgMusicValue(settingKey, meta.default);
    });
    clearSavedSource();
    clearSessionState();
    clearQueue();
    state.preCueResult = null;
    state.preCueSourceUri = '';
    state.metaCache.clear();
    state.sourceTrackListCache.clear();
    state.ytSearchCache.clear();
  }

  // Listen for mod menu changes (same tab)
  document.addEventListener('ntcfg:change', (event) => {
    if (event?.detail?.script !== NTCFG_MUSIC_MANIFEST_ID) return;
    applyNtcfgMusicValueDirect(event.detail.key, event.detail.value);
  });

  document.addEventListener('ntcfg:action', (event) => {
    const detail = event?.detail || {};
    if (detail.script !== '*') return;
    if (detail.key !== 'clear-settings' || detail.scope !== 'prefs+caches') return;
    try {
      resetMusicSettingsToDefaults();
      GM_setValue(MUSIC_STORAGE_VERSION_KEY, MUSIC_STORAGE_VERSION);
      registerNtcfgMusicManifest();
      syncAllNtcfgMusicSettingsFromGM();
      document.dispatchEvent(new CustomEvent('ntcfg:manifest-updated', {
        detail: { script: NTCFG_MUSIC_MANIFEST_ID }
      }));
      dispatchMusicActionResult(detail.requestId, 'success');
    } catch (error) {
      dispatchMusicActionResult(detail.requestId, 'error', error?.message || String(error));
    }
  });

  // Listen for cross-tab changes
  window.addEventListener('storage', (event) => {
    const storageKey = String(event?.key || '');
    if (!storageKey.startsWith(NTCFG_MUSIC_VALUE_PREFIX) || event.newValue == null) return;
    const settingKey = storageKey.slice(NTCFG_MUSIC_VALUE_PREFIX.length);
    if (!MUSIC_SHARED_SETTINGS[settingKey]) return;
    try {
      applyNtcfgMusicValueIfChanged(settingKey, JSON.parse(event.newValue));
    } catch {
      // ignore invalid synced payloads
    }
  });

  // Register manifest, sync settings, and write alive key for mod menu detection
  registerNtcfgMusicManifest();
  syncAllNtcfgMusicSettingsFromGM();
  try { GM_setValue(MUSIC_STORAGE_VERSION_KEY, MUSIC_STORAGE_VERSION); } catch { /* ignore */ }
  const publishNtcfgMusicManifestHeartbeat = () => {
    try { localStorage.setItem('ntcfg:alive:' + NTCFG_MUSIC_MANIFEST_ID, String(Date.now())); } catch { /* ignore */ }
    try {
      document.dispatchEvent(new CustomEvent('ntcfg:manifest-updated', {
        detail: { script: NTCFG_MUSIC_MANIFEST_ID }
      }));
    } catch {
      // ignore event dispatch failures
    }
  };
  publishNtcfgMusicManifestHeartbeat();

  // ─── End Mod Menu Manifest Bridge ───────────────────────────────────────────

  // ─── Console API for debugging (type NTMusic in the browser console) ──
  var _ntMusicApi = {
    /** Print the current queue as a collapsed group */
    queue: function () {
      var q = state.queue;
      if (!q || !q.length) {
        console.log('%c[NT Music]%c No tracks in queue.', 'color:#1db954;font-weight:bold', 'color:inherit');
        return;
      }
      var current = state.queueIndex >= 0 ? state.queueIndex : -1;
      console.groupCollapsed(
        '%c[NT Music]%c ' + q.length + ' track' + (q.length === 1 ? '' : 's') + ' in queue',
        'color:#1db954;font-weight:bold', 'color:inherit'
      );
      for (var i = 0; i < q.length; i += 1) {
        var t = q[i];
        var prefix = i === current ? '\u25b6 ' : '  ';
        var title = t.title || t.spotifyUri || '(unknown)';
        var artist = t.artist || '';
        var ytId = t.ytVideoId || '\u2014';
        console.log(
          prefix + (i + 1) + '. ' + title + (artist ? ' \u2014 ' + artist : '') + '  [YT: ' + ytId + ']'
        );
      }
      console.groupEnd();
    },
    /** Print the currently playing track */
    now: function () {
      var meta = state.trackMeta;
      var entry = (state.queue && state.queue[state.queueIndex]) || null;
      if (!meta && !entry) {
        console.log('%c[NT Music]%c Nothing playing.', 'color:#1db954;font-weight:bold', 'color:inherit');
        return;
      }
      var title = (meta && meta.title) || (entry && entry.title) || '?';
      var artist = (meta && meta.artist) || (entry && entry.artist) || '?';
      var ytId = (entry && entry.ytVideoId) || '?';
      var pb = state.playback || {};
      var pos = Math.round((pb.position || 0) / 1000);
      var dur = Math.round((pb.duration || 0) / 1000);
      var paused = pb.isPaused ? ' (paused)' : '';
      console.log(
        '%c[NT Music]%c \u25b6 ' + title + ' \u2014 ' + artist +
        '  [' + pos + 's / ' + dur + 's]' + paused +
        '  [YT: ' + ytId + ']',
        'color:#1db954;font-weight:bold', 'color:inherit'
      );
    },
    /** Print version and state summary */
    status: function () {
      const summary = {
        version: SCRIPT_VERSION,
        active: state.active,
        queueLength: state.queue ? state.queue.length : 0,
        mode: state.queueMode || 'sequential',
        debug: _ntMusicApi.verbose,
        engine: state.embedController ? 'Spotify Embed' : 'YouTube',
      };
      console.log(
        '%c[NT Music]%c v' + summary.version +
        ' | Active: ' + summary.active +
        ' | Queue: ' + summary.queueLength + ' tracks' +
        ' | Mode: ' + summary.mode +
        ' | Debug: ' + (summary.debug ? 'on' : 'off') +
        ' | Engine: ' + summary.engine,
        'color:#1db954;font-weight:bold', 'color:inherit'
      );
      return summary;
    },
    trace: function () {
      const entries = getMusicTraceEntries();
      if (!entries.length) {
        console.log('%c[NT Music]%c No trace entries recorded.', 'color:#1db954;font-weight:bold', 'color:inherit');
        return [];
      }
      console.groupCollapsed('%c[NT Music]%c Trace (' + entries.length + ' entries)', 'color:#1db954;font-weight:bold', 'color:inherit');
      entries.forEach((entry) => {
        console.log(entry.iso + '  ' + entry.event, entry.data);
      });
      console.groupEnd();
      return entries;
    },
    clearTrace: function () {
      clearMusicTrace();
      console.log('%c[NT Music]%c Trace cleared.', 'color:#1db954;font-weight:bold', 'color:inherit');
    },
    /** Toggle verbose debug logging (YouTube search, Topic matching, etc.) */
    verbose: false,
    debug: function (enabled) {
      const nextValue = enabled === undefined ? !_ntMusicApi.verbose : Boolean(enabled);
      _ntMusicApi.verbose = nextValue;
      setNtcfgMusicValue('DEBUG_LOGGING', nextValue);
      console.log('%c[NT Music]%c Debug ' + (nextValue ? 'enabled' : 'disabled') + '.', 'color:#1db954;font-weight:bold', 'color:inherit');
      return nextValue;
    }
  };

  _ntMusicApi.verbose = isMusicDebugEnabled();

  try {
    (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).NTMusic = _ntMusicApi;
  } catch (e) {
    window.NTMusic = _ntMusicApi;
  }

  console.log(
    '%c[NT Music]%c v' + SCRIPT_VERSION + ' loaded \u2014 type %cNTMusic.queue()%c or %cNTMusic.now()%c in console to inspect',
    'color:#1db954;font-weight:bold', 'color:inherit',
    'color:#1db954', 'color:inherit',
    'color:#1db954', 'color:inherit'
  );

  const ntMusicRouteHelper = initNTRouteHelper(window);
  ntMusicRouteHelper.subscribe(() => {
    syncMusicRuntimeToRoute();
  }, { immediate: false });
  syncMusicRuntimeToRoute();

  function startMusicRuntime() {
    if (musicRuntimeStarted) return;
    musicRuntimeStarted = true;
    init();
  }

  function stopMusicRuntime() {
    if (!musicRuntimeStarted) return;
    musicRuntimeStarted = false;
    cleanupAll();
  }

  function syncMusicRuntimeToRoute() {
    if (isMusicRaceRoute()) {
      startMusicRuntime();
      return;
    }
    stopMusicRuntime();
  }

  function schedulePreCueFromSavedSource(delayMs = PRE_CUE_INIT_DELAY_MS) {
    if (state.preCueTimer) {
      window.clearTimeout(state.preCueTimer);
      state.preCueTimer = null;
    }

    if (!musicRuntimeStarted) return;

    const saved = getSavedSourceParsed();
    if (!saved) return;

    if (state.preCueSourceUri === saved.uri && (state.preCueResult || state.preCuePromise)) {
      return;
    }

    state.preCueTimer = window.setTimeout(async () => {
      state.preCueTimer = null;

      const currentSaved = getSavedSourceParsed();
      if (!currentSaved) return;

      if (state.preCueSourceUri === currentSaved.uri && (state.preCueResult || state.preCuePromise)) {
        return;
      }

      const preCuePromise = preCueFromSavedSource();
      state.preCuePromise = preCuePromise;
      try {
        await preCuePromise;
      } catch (error) {
        // Fall back to on-demand loading if pre-cue fails.
      } finally {
        if (state.preCuePromise === preCuePromise) {
          state.preCuePromise = null;
        }
      }
    }, Math.max(0, Number(delayMs) || 0));
  }

  function init() {
    // Restore queue mode from saved session before pre-cue
    var savedQueueMode = String(GM_getValue(STORAGE_KEYS.queueMode, '') || '').trim();
    if (savedQueueMode === 'shuffle' || savedQueueMode === 'sequential') {
      state.queueMode = savedQueueMode;
    }

    installYouTubeApiHook();
    installEmbedApiHook();
    installAudioContextTracker(getPageWindow());

    clearFrameWatchStartTimer();
    state.frameWatchStartTimer = window.setTimeout(() => {
      state.frameWatchStartTimer = null;
      startFrameWatcher();
    }, 1500);

    schedulePreCueFromSavedSource(PRE_CUE_INIT_DELAY_MS);

    window.addEventListener('message', onMusicMessageFromRace);
    window.addEventListener('beforeunload', cleanupAll);
  }

  function clearFrameWatchStartTimer() {
    if (state.frameWatchStartTimer) {
      window.clearTimeout(state.frameWatchStartTimer);
      state.frameWatchStartTimer = null;
    }
  }

  function stopFrameWatcher() {
    if (state.frameWatchTimer) {
      window.clearInterval(state.frameWatchTimer);
      state.frameWatchTimer = null;
    }
  }

  function resetFrameBindingState() {
    clearFrameSuppressRetryTimers();
    state._lastFrameUiSync = 0;
    state._lastNativeMuteCheck = 0;
    state.suppressNativeStationEventsUntil = 0;
    state.frame = null;
    state.frameDoc = null;
    state.frameWindow = null;
  }

  function clearFrameSuppressRetryTimers() {
    if (!Array.isArray(state.frameSuppressRetryTimers) || !state.frameSuppressRetryTimers.length) {
      state.frameSuppressRetryTimers = [];
      return;
    }
    state.frameSuppressRetryTimers.forEach((timerId) => {
      try {
        window.clearTimeout(timerId);
      } catch (_) {
        // noop
      }
    });
    state.frameSuppressRetryTimers = [];
  }

  function scheduleFrameSuppressRetryWave() {
    clearFrameSuppressRetryTimers();
    [50, 150, 300, 600, 1200, 2500].forEach((delayMs) => {
      const timerId = window.setTimeout(function () {
        state.frameSuppressRetryTimers = state.frameSuppressRetryTimers.filter((id) => id !== timerId);
        if (isSpotifyUiSelected() && state.frameDoc) {
          suppressNativeAudio(state.frameDoc);
        }
      }, delayMs);
      state.frameSuppressRetryTimers.push(timerId);
    });
  }

  // registerMenuCommands removed — no external menu commands needed

  function installYouTubeApiHook() {
    injectYouTubeApiBridge();
    window.addEventListener(YT_READY_EVENT, onYouTubeReadyEvent);
    window.addEventListener(YT_ERROR_EVENT, onYouTubeErrorEvent);

    const pageWindow = getPageWindow();
    try {
      if (pageWindow.__ntYouTubeApi && pageWindow.__ntYouTubeApi.Player) {
        onYouTubeApiReady(pageWindow.__ntYouTubeApi);
      } else if (pageWindow.YT && pageWindow.YT.Player) {
        onYouTubeApiReady(pageWindow.YT);
      }
    } catch (error) {
      // noop
    }
  }

  function getPageWindow() {
    if (typeof unsafeWindow !== 'undefined') return unsafeWindow;
    return window;
  }

  function injectYouTubeApiBridge() {
    const pageWindow = getPageWindow();
    if (pageWindow.__ntYTBridgeInstalled) return;
    pageWindow.__ntYTBridgeInstalled = true;
    const previous = pageWindow.onYouTubeIframeAPIReady;
    pageWindow.onYouTubeIframeAPIReady = function () {
      try { pageWindow.__ntYouTubeApi = pageWindow.YT || null; } catch (err) { }
      window.dispatchEvent(new CustomEvent(YT_READY_EVENT));
      if (typeof previous === 'function') {
        try { previous(); } catch (err) { }
      }
    };
  }

  function loadYouTubeApiScript() {
    if (state.ytApiLoadStarted) return;
    state.ytApiLoadStarted = true;

    if (document.querySelector('script[data-nt-youtube-api="1"]')) return;

    const script = document.createElement('script');
    script.async = true;
    script.src = YT_IFRAME_API_SRC;
    script.dataset.ntYoutubeApi = '1';
    script.onerror = () => {
      state.ytApiLoadFailed = true;
      failYouTubeApiWaiters(new Error('YouTube IFrame API failed to load'));
      window.dispatchEvent(new CustomEvent(YT_ERROR_EVENT));
    };
    document.body.appendChild(script);
  }

  function waitForYouTubeApi(timeoutMs) {
    if (state.ytApiLoaded && state.ytApi && state.ytApi.Player) {
      return Promise.resolve(state.ytApi);
    }
    if (state.ytApiLoadFailed) {
      return Promise.reject(new Error('YouTube IFrame API unavailable'));
    }

    loadYouTubeApiScript();

    return new Promise((resolve, reject) => {
      let settled = false;

      const waiter = {
        resolve: (api) => {
          if (settled) return;
          settled = true;
          resolve(api);
        },
        reject: (error) => {
          if (settled) return;
          settled = true;
          reject(error);
        },
      };

      const timer = window.setTimeout(() => {
        if (settled) return;
        settled = true;
        state.ytApiWaiters = state.ytApiWaiters.filter((entry) => entry !== waiter);
        reject(new Error('YouTube API timeout'));
      }, Number(timeoutMs) || 12000);

      const wrapped = {
        resolve: (api) => {
          window.clearTimeout(timer);
          waiter.resolve(api);
        },
        reject: (error) => {
          window.clearTimeout(timer);
          waiter.reject(error);
        },
      };

      state.ytApiWaiters.push(wrapped);
    });
  }

  function onYouTubeReadyEvent() {
    const pageWindow = getPageWindow();
    try {
      const api = pageWindow.__ntYouTubeApi || pageWindow.YT;
      if (api && api.Player) {
        onYouTubeApiReady(api);
      }
    } catch (error) {
      // noop
    }
  }

  function onYouTubeErrorEvent() {
    state.ytApiLoadFailed = true;
  }

  function onYouTubeApiReady(api) {
    if (!api || !api.Player) return;

    state.ytApi = api;
    state.ytApiLoaded = true;
    state.ytApiLoadFailed = false;

    const waiters = state.ytApiWaiters.splice(0, state.ytApiWaiters.length);
    waiters.forEach((waiter) => {
      try {
        waiter.resolve(api);
      } catch (error) {
        // noop
      }
    });
  }

  function failYouTubeApiWaiters(error) {
    const waiters = state.ytApiWaiters.splice(0, state.ytApiWaiters.length);
    waiters.forEach((waiter) => {
      try {
        waiter.reject(error);
      } catch (err) {
        // noop
      }
    });
  }

  function ensureYouTubePlayerHost() {
    const existing = document.getElementById('nt-yt-player-host');
    if (existing && existing.isConnected) {
      if (String(existing.tagName || '').toUpperCase() === 'DIV') {
        state.ytPlayerHost = existing;
        return existing.id;
      }
      try {
        existing.remove();
      } catch (error) {
        // noop
      }
    }

    const host = document.createElement('div');
    host.id = 'nt-yt-player-host';
    host.style.cssText = [
      'position: fixed',
      'right: -5000px',
      'bottom: -5000px',
      'width: 1px',
      'height: 1px',
      'overflow: hidden',
      'opacity: 0',
      'pointer-events: none',
      'z-index: -1',
    ].join(';');

    const mountRoot = document.body || document.documentElement;
    if (!mountRoot) {
      throw new Error('YouTube player host mount root unavailable');
    }

    mountRoot.appendChild(host);
    state.ytPlayerHost = host;
    return host.id;
  }

  async function ensureYouTubePlayer(initialVideoId) {
    if (state.ytPlayer) return state.ytPlayer;
    if (state.ytPlayerPromise) return state.ytPlayerPromise;

    traceMusic('ensure-youtube-player:start', {
      initialVideoId: String(initialVideoId || ''),
      existingHostTag: document.getElementById('nt-yt-player-host')
        ? document.getElementById('nt-yt-player-host').tagName
        : null,
    });

    state.ytPlayerPromise = (async () => {
      const yt = await waitForYouTubeApi(12000);
      const hostId = ensureYouTubePlayerHost();

      return new Promise((resolve, reject) => {
        try {
          const player = new yt.Player(hostId, {
            height: 1,
            width: 1,
            videoId: String(initialVideoId || ''),
            playerVars: {
              autoplay: 1,
              controls: 0,
              disablekb: 1,
              fs: 0,
              modestbranding: 1,
              rel: 0,
              playsinline: 1,
              origin: window.location.origin,
            },
            events: {
              onReady: () => {
                state.ytPlayer = player;
                state.ytPlayerReady = true;
                state.ytPlayerHost = document.getElementById(hostId) || state.ytPlayerHost;
                state.ytPlayerPromise = null;
                traceMusic('ensure-youtube-player:ready', {
                  hostId,
                  hostTag: state.ytPlayerHost ? state.ytPlayerHost.tagName : null,
                  hostSrc: state.ytPlayerHost && state.ytPlayerHost.tagName === 'IFRAME'
                    ? state.ytPlayerHost.src
                    : null,
                });
                resolve(player);
              },
              onStateChange: onYTStateChange,
              onError: onYTPlayerError,
            },
          });
        } catch (error) {
          state.ytPlayerPromise = null;
          traceMusic('ensure-youtube-player:construct-error', {
            initialVideoId: String(initialVideoId || ''),
            error: formatTraceError(error),
          });
          reject(error);
        }
      });
    })().catch((error) => {
      state.ytPlayerPromise = null;
      traceMusic('ensure-youtube-player:promise-error', {
        initialVideoId: String(initialVideoId || ''),
        error: formatTraceError(error),
      });
      throw error;
    });

    return state.ytPlayerPromise;
  }

  function onYTPlayerError(event) {
    const code = event && event.data;
    traceMusic('youtube-player-error', {
      code,
      queueIndex: state.queueIndex,
      currentUri: state.queue && state.queue[state.queueIndex]
        ? state.queue[state.queueIndex].spotifyUri
        : '',
      currentVideoId: state.queue && state.queue[state.queueIndex]
        ? state.queue[state.queueIndex].ytVideoId
        : '',
    });

    // Debounce — error 150 (embed-blocked) fires repeatedly for the same
    // video.  Only act on the first one; ignore duplicates within 3 s.
    var now = Date.now();
    if (state._lastYTErrorCode === code && now - state._lastYTErrorTime < 3000) return;
    state._lastYTErrorCode = code;
    state._lastYTErrorTime = now;

    console.warn('[NT Music] YouTube player error:', code);

    if (!state.active) return;

    // Error 150 / 101 = embedding not allowed.  Try to find an alternative
    // video for the SAME track before giving up and skipping.
    if ((code === 150 || code === 101) && state.queue && state.queue[state.queueIndex]) {
      var failedEntry = state.queue[state.queueIndex];
      var failedVideoId = failedEntry.ytVideoId;

      // Clear the cached video so re-resolution doesn't hit the same one
      if (failedEntry.spotifyUri) {
        state.ytSearchCache.delete(failedEntry.spotifyUri);
        var storageKey = 'yt_cache_v' + YT_CACHE_VERSION + '_' + failedEntry.spotifyUri;
        GM_deleteValue(storageKey);
      }
      failedEntry.ytVideoId = '';

      // Mark the failed video so the re-search can exclude it
      if (!failedEntry._failedVideoIds) failedEntry._failedVideoIds = [];
      if (failedVideoId) failedEntry._failedVideoIds.push(failedVideoId);

      // Only retry once per track to avoid infinite loops
      if (failedEntry._failedVideoIds.length <= 3) {
        window.setTimeout(function () {
          void resolveAndPlayTrack(state.queueIndex, true);
        }, 500);
        return;
      }
    }

    // Fallback: skip to next track
    window.setTimeout(function () {
      void playNextInQueue();
    }, 500);
  }

  function onYTStateChange(event) {
    const status = Number(event && event.data);

    if (status === 0) {
      syncPlaybackFromYT();
      state.playback.isPaused = true;
      state.playback.position = state.playback.duration;
      state.playback.updatedAt = Date.now();
      if (state.active) renderNowPlaying();
      if (state.active) {
        void playNextInQueue();
      }
      return;
    }

    if (status === 1) {
      // If we have a pending resume seek, execute it now that the video is actually playing
      if (state._pendingSeekSec > 0) {
        var seekTarget = state._pendingSeekSec;
        state._pendingSeekSec = 0;
        var p = state.ytPlayer;
        if (p && typeof p.seekTo === 'function') {
          p.seekTo(seekTarget, true);
          state.playback.position = seekTarget * 1000;
          state.playback.updatedAt = Date.now();
        }
      }
      syncPlaybackFromYT();
      state.playback.isPaused = false;
      state.playback.isBuffering = false;
      state.playback.updatedAt = Date.now();
      if (state.active) renderNowPlaying();
      schedulePreResolveNextTrack(3000);
      return;
    }

    if (status === 2) {
      syncPlaybackFromYT();
      state.playback.isPaused = true;
      state.playback.isBuffering = false;
      state.playback.updatedAt = Date.now();
      if (state.active) renderNowPlaying();
      return;
    }

    if (status === 3) {
      syncPlaybackFromYT();
      state.playback.isPaused = false;
      state.playback.isBuffering = true;
      state.playback.updatedAt = Date.now();
      if (state.active) renderNowPlaying();
    }
  }

  function safeYTCall(methodName, ...args) {
    const player = state.ytPlayer;
    if (!player) return false;

    const fn = player[methodName];
    if (typeof fn !== 'function') return false;

    try {
      fn.apply(player, args);
      return true;
    } catch (error) {
      return false;
    }
  }

  function syncPlaybackFromYT() {
    const player = state.ytPlayer;
    if (!player || !state.ytPlayerReady) return;

    try {
      const durationMs = Math.max(0, Math.round((Number(player.getDuration()) || 0) * 1000));
      const positionMs = Math.max(0, Math.round((Number(player.getCurrentTime()) || 0) * 1000));

      if (durationMs > 0) {
        state.playback.duration = durationMs;
      }
      state.playback.position = positionMs;
      state.playback.updatedAt = Date.now();

      const current = getCurrentQueueEntry();
      if (current) {
        state.playback.uri = current.spotifyUri;
      }
    } catch (error) {
      // noop
    }
  }

  function getCurrentQueueEntry() {
    if (!Array.isArray(state.queue) || !state.queue.length) return null;
    const idx = Number(state.queueIndex);
    if (!Number.isInteger(idx) || idx < 0 || idx >= state.queue.length) return null;
    return state.queue[idx];
  }

  function clearQueue() {
    state.queue = [];
    state.queueIndex = -1;
    state.shuffleOrder = [];
    state.shufflePosition = -1;
    state.playHistory = [];
  }

  async function buildQueue(parsedSource) {
    if (!parsedSource) return [];

    const platform = parsedSource.platform || 'spotify';

    // ── YouTube direct ──────────────────────────────────────────────
    if (platform === 'youtube') return buildQueueFromYouTube(parsedSource);

    // ── Apple Music ─────────────────────────────────────────────────
    if (platform === 'apple-music') return buildQueueFromAppleMusic(parsedSource);

    // ── Spotify (original path) ─────────────────────────────────────
    if (parsedSource.type === 'track') {
      const meta = await resolveTrackMetaForUri(parsedSource.uri);
      if (!meta) return [];

      return [
        {
          spotifyUri: meta.uri,
          title: String(meta.title || 'Spotify'),
          artist: String(meta.artist || ''),
          durationMs: Number(meta.durationMs) || 0,
          ytVideoId: '',
          art: String(meta.art || ''),
          openUrl: String(meta.openUrl || spotifyUriToOpenUrl(meta.uri)),
          sourceUri: parsedSource.uri,
          _resolving: false,
        },
      ];
    }

    const tracks = await fetchSpotifyTrackList(parsedSource.type, parsedSource.id, parsedSource.uri);
    if (!tracks.length) return [];

    return tracks.map((track) => ({
      spotifyUri: track.uri,
      title: String(track.title || 'Spotify'),
      artist: String(track.artist || ''),
      durationMs: Number(track.durationMs) || 0,
      ytVideoId: '',
      art: String(track.art || ''),
      openUrl: String(track.openUrl || spotifyUriToOpenUrl(track.uri)),
      sourceUri: parsedSource.uri,
      _resolving: false,
    }));
  }

  // ─── YouTube queue builder ──────────────────────────────────────────
  async function buildQueueFromYouTube(parsedSource) {
    traceMusic('build-queue-youtube:start', {
      type: parsedSource ? parsedSource.type : '',
      id: parsedSource ? parsedSource.id : '',
      uri: parsedSource ? parsedSource.uri : '',
    });

    if (parsedSource.type === 'track') {
      // Kick off the direct YouTube Music art lookup immediately (doesn't need title/artist).
      // Meanwhile, fetch noembed metadata in parallel for title + artist + thumbnail fallback.
      const directArtPromise = fetchYouTubeMusicArtDirect(parsedSource.id);
      const meta = await fetchYouTubeVideoMeta(parsedSource.id);

      // Check if the direct lookup already found album art; if not, fall back to
      // a YouTube Music search using the title+artist we just got from noembed.
      var musicArt = await directArtPromise;
      if (!musicArt && meta.title) {
        musicArt = await fetchYouTubeMusicArtBySearch(parsedSource.id, meta.title, meta.artist);
      }

      return [
        {
          spotifyUri: parsedSource.uri,      // 'youtube:video:VIDEO_ID'
          title: meta.title,
          artist: meta.artist,
          durationMs: 0,                     // YT player will report real duration
          ytVideoId: parsedSource.id,        // KEY: already resolved → skip search
          art: musicArt                      // 1st: YouTube Music album cover (lh3 square art)
            || meta.thumbnail                // 2nd: noembed thumbnail
            || 'https://i.ytimg.com/vi/' + parsedSource.id + '/hqdefault.jpg',  // 3rd: fallback
          openUrl: parsedSource.openUrl,
          sourceUri: parsedSource.uri,
          _resolving: false,
        },
      ];
    }

    if (parsedSource.type === 'playlist') {
      var playlistTracks = await fetchYouTubePlaylistTracks(parsedSource.id);
      traceMusic('build-queue-youtube:playlist-tracks', {
        playlistId: parsedSource.id,
        count: playlistTracks.length,
      });
      if (!playlistTracks.length) {
        toast('Could not read YouTube playlist. It may be private or empty.');
        return [];
      }
      return playlistTracks.map(function (t) {
        // Prefer playlist-level album art (YouTube Music albums) over video thumbnail
        var artUrl = t.playlistArt
          || 'https://i.ytimg.com/vi/' + t.videoId + '/hqdefault.jpg';
        return {
          spotifyUri: 'youtube:video:' + t.videoId,
          title: t.title || 'YouTube Video',
          artist: t.author || '',
          durationMs: (t.durationSec || 0) * 1000,
          ytVideoId: t.videoId,          // pre-resolved → skip search pipeline
          art: artUrl,
          openUrl: 'https://www.youtube.com/watch?v=' + t.videoId,
          sourceUri: parsedSource.uri,
          _resolving: false,
        };
      });
    }

    return [];
  }

  // ─── Apple Music queue builder ──────────────────────────────────────
  async function buildQueueFromAppleMusic(parsedSource) {
    if (parsedSource.type === 'track') {
      const meta = await fetchAppleMusicTrackMeta(parsedSource.id);
      if (!meta) {
        toast('Could not fetch Apple Music track info.');
        return [];
      }
      return [
        {
          spotifyUri: parsedSource.uri,      // 'apple-music:track:ID'
          title: meta.title,
          artist: meta.artist,
          durationMs: meta.durationMs,
          ytVideoId: '',                     // needs YouTube search
          art: meta.art,
          openUrl: meta.openUrl || parsedSource.openUrl,
          sourceUri: parsedSource.uri,
          _resolving: false,
        },
      ];
    }

    if (parsedSource.type === 'album') {
      const tracks = await fetchAppleMusicAlbumTracks(parsedSource.id);
      if (!tracks.length) {
        toast('Could not read Apple Music album tracks.');
        return [];
      }
      return tracks.map(function (t) {
        return {
          spotifyUri: 'apple-music:track:' + t.appleMusicId,
          title: t.title,
          artist: t.artist,
          durationMs: t.durationMs,
          ytVideoId: '',
          art: t.art,
          openUrl: t.openUrl || parsedSource.openUrl,
          sourceUri: parsedSource.uri,
          _resolving: false,
        };
      });
    }

    if (parsedSource.type === 'playlist') {
      var amTracks = await fetchAppleMusicPlaylistTracks(parsedSource.id, parsedSource.openUrl);
      if (!amTracks.length) {
        toast('Could not read Apple Music playlist. It may be private or the page format changed.');
        return [];
      }
      return amTracks.map(function (t) {
        var uri = t.appleMusicId
          ? 'apple-music:track:' + t.appleMusicId
          : 'apple-music:title:' + encodeURIComponent(t.title + ':' + t.artist);
        return {
          spotifyUri: uri,
          title: t.title,
          artist: t.artist,
          durationMs: t.durationMs || 0,
          ytVideoId: '',                   // needs YouTube search
          art: t.art || '',
          openUrl: t.openUrl || parsedSource.openUrl,
          sourceUri: parsedSource.uri,
          _resolving: false,
        };
      });
    }

    return [];
  }

  async function preCueFromSavedSource() {
    const saved = getSavedSourceParsed();
    if (!saved) return;
    const sourceUri = saved.uri;

    // Check if we have a saved session to resume
    const session = getSavedSessionState();

    // Set source URI early so loadSource() knows to await our promise
    // (must be set before any async work, otherwise loadSource can't match it)
    state.preCueSourceUri = sourceUri;

    try {
      const queue = await buildQueue(saved);
      if (!queue.length) {
        if (state.preCueSourceUri === sourceUri) {
          state.preCueSourceUri = '';
        }
        return;
      }

      let startIndex = 0;
      let shuffleOrder = [];
      let shufflePosition = -1;
      let resumePositionSec = 0;

      // Restore queue mode from session
      if (session) {
        state.queueMode = session.queueMode || 'shuffle';
      }

      if (session && session.queueIndex >= 0 && session.queueIndex < queue.length) {
        // Resume from saved session position
        startIndex = session.queueIndex;
        resumePositionSec = session.playbackPositionSec || 0;

        if (state.queueMode === 'shuffle' && session.shuffleOrder.length === queue.length) {
          shuffleOrder = session.shuffleOrder;
          shufflePosition = session.shufflePosition;
        } else if (state.queueMode === 'shuffle' && queue.length > 1) {
          // Saved shuffle doesn't match queue size — regenerate with saved index first
          const si = [];
          for (let ix = 0; ix < queue.length; ix += 1) si.push(ix);
          for (let ix = si.length - 1; ix > 0; ix -= 1) {
            const jx = Math.floor(Math.random() * (ix + 1));
            const t = si[ix]; si[ix] = si[jx]; si[jx] = t;
          }
          const sp = si.indexOf(startIndex);
          if (sp > 0) { si[sp] = si[0]; si[0] = startIndex; }
          shuffleOrder = si;
          shufflePosition = 0;
        }
      } else if (state.queueMode === 'shuffle' && queue.length > 1) {
        // No session — fresh shuffle
        startIndex = Math.floor(Math.random() * queue.length);
        const indices = [];
        for (let i = 0; i < queue.length; i += 1) indices.push(i);
        for (let i = indices.length - 1; i > 0; i -= 1) {
          const j = Math.floor(Math.random() * (i + 1));
          const tmp = indices[i];
          indices[i] = indices[j];
          indices[j] = tmp;
        }
        const pos = indices.indexOf(startIndex);
        if (pos > 0) {
          indices[pos] = indices[0];
          indices[0] = startIndex;
        }
        shuffleOrder = indices;
        shufflePosition = 0;
      }

      // Pre-resolve first track's YouTube video ID
      const firstTrack = queue[startIndex];
      if (firstTrack && !firstTrack.ytVideoId) {
        await hydrateQueueEntryMetadata(firstTrack);
        const result = await resolveYouTubeVideoForTrack(firstTrack);
        if (result && result.videoId) firstTrack.ytVideoId = result.videoId;
      }

      // Pre-resolve second track too
      const secondIndex = shuffleOrder.length > 1
        ? shuffleOrder[Math.min(shufflePosition + 1, shuffleOrder.length - 1)]
        : (queue.length > 1 ? (startIndex + 1) % queue.length : -1);
      if (secondIndex >= 0 && secondIndex !== startIndex && queue[secondIndex] && !queue[secondIndex].ytVideoId) {
        await hydrateQueueEntryMetadata(queue[secondIndex]);
        const result = await resolveYouTubeVideoForTrack(queue[secondIndex]);
        if (result && result.videoId) queue[secondIndex].ytVideoId = result.videoId;
      }

      // Also pre-load the YouTube IFrame API
      loadYouTubeApiScript();

      if (state.preCueSourceUri !== sourceUri) return;
      state.preCueResult = { queue, startIndex, shuffleOrder, shufflePosition, resumePositionSec };
    } catch (err) {
      if (state.preCueSourceUri !== sourceUri) return;
      console.warn('[NT Music] Pre-cue failed:', err);
      state.preCueResult = null;
    }
  }

  async function loadSource(sourceUri) {
    const parsed = parseSourceInput(sourceUri);
    traceMusic('load-source:start', {
      sourceUri,
      parsedPlatform: parsed ? parsed.platform : '',
      parsedType: parsed ? parsed.type : '',
      parsedId: parsed ? parsed.id : '',
      preCueSourceUri: state.preCueSourceUri,
    });
    if (!parsed) return false;

    // Internal URIs (e.g. apple-music:playlist:xxx) may not have a usable
    // openUrl.  Fill it in from the stored URL so downstream functions like
    // fetchAppleMusicPlaylistTracks receive the real page URL.
    if (!parsed.openUrl) {
      const storedUrl = String(GM_getValue(STORAGE_KEYS.sourceUrl, '') || '').trim();
      if (storedUrl) parsed.openUrl = storedUrl;
    }

    // Check if pre-cue has results for this exact source
    let queue;
    let preStartIndex = null;
    let preShuffleOrder = null;
    let preShufflePosition = null;
    let preResumePositionSec = 0;

    if (state.preCueResult && state.preCueSourceUri === parsed.uri) {
      queue = state.preCueResult.queue;
      preStartIndex = state.preCueResult.startIndex;
      preShuffleOrder = state.preCueResult.shuffleOrder;
      preShufflePosition = state.preCueResult.shufflePosition;
      preResumePositionSec = state.preCueResult.resumePositionSec || 0;
      state.preCueResult = null;
      state.preCueSourceUri = '';
    } else {
      // Wait for in-flight pre-cue to finish if it's for this source, otherwise build fresh
      if (state.preCuePromise && state.preCueSourceUri === parsed.uri) {
        try { await state.preCuePromise; } catch (e) { /* ignore */ }
        if (state.preCueResult && state.preCueSourceUri === parsed.uri) {
          queue = state.preCueResult.queue;
          preStartIndex = state.preCueResult.startIndex;
          preShuffleOrder = state.preCueResult.shuffleOrder;
          preShufflePosition = state.preCueResult.shufflePosition;
          preResumePositionSec = state.preCueResult.resumePositionSec || 0;
          state.preCueResult = null;
          state.preCueSourceUri = '';
        }
      }

      if (!queue) {
        queue = await buildQueue(parsed);
      }
    }

    traceMusic('load-source:queue-ready', {
      sourceUri: parsed.uri,
      queueLength: Array.isArray(queue) ? queue.length : 0,
      queueMode: state.queueMode,
      usedPreCue: preStartIndex != null,
    });

    if (!queue.length) {
      traceMusic('load-source:empty-queue', {
        sourceUri: parsed.uri,
        platform: parsed.platform,
        type: parsed.type,
      });
      if (parsed.type === 'album' || parsed.type === 'playlist') {
        toast('Could not read track list. Try a public playlist/album or a single track URL.');
      }
      return false;
    }

    clearQueue();
    state.queue = queue;
    state.queueIndex = 0;

    const savedSession = getSavedSessionState();
    let startIndex = preStartIndex != null ? preStartIndex : 0;
    if (preStartIndex == null && savedSession && savedSession.queueIndex >= 0 && savedSession.queueIndex < queue.length) {
      startIndex = savedSession.queueIndex;
    }
    state.playHistory = [];
    if (state.queueMode === 'shuffle' && queue.length > 1) {
      if (preShuffleOrder && preShuffleOrder.length === queue.length) {
        state.shuffleOrder = preShuffleOrder;
        state.shufflePosition = preShufflePosition != null ? preShufflePosition : 0;
      } else if (
        savedSession
        && savedSession.queueMode === 'shuffle'
        && Array.isArray(savedSession.shuffleOrder)
        && savedSession.shuffleOrder.length === queue.length
      ) {
        const normalizedShuffleOrder = savedSession.shuffleOrder.map((value) => Number(value));
        const isValidShuffleOrder = normalizedShuffleOrder.every((value, index, values) => (
          Number.isInteger(value)
          && value >= 0
          && value < queue.length
          && values.indexOf(value) === index
        ));

        if (isValidShuffleOrder) {
          state.shuffleOrder = normalizedShuffleOrder;
          const fallbackPos = Math.max(0, normalizedShuffleOrder.indexOf(startIndex));
          state.shufflePosition = (
            Number.isInteger(savedSession.shufflePosition)
            && savedSession.shufflePosition >= 0
            && savedSession.shufflePosition < normalizedShuffleOrder.length
          ) ? savedSession.shufflePosition : fallbackPos;
        } else {
          generateShuffleOrder(startIndex);
        }
      } else if (savedSession && savedSession.queueIndex >= 0 && savedSession.queueIndex < queue.length) {
        generateShuffleOrder(startIndex);
      } else {
        startIndex = Math.floor(Math.random() * queue.length);
        generateShuffleOrder(startIndex);
      }
    } else {
      state.shuffleOrder = [];
      state.shufflePosition = -1;
    }

    // If pre-cue didn't provide a resume position, check saved session directly
    var resumeSec = preResumePositionSec;
    if (!resumeSec) {
      if (savedSession && savedSession.queueIndex === startIndex) {
        resumeSec = savedSession.playbackPositionSec || 0;
      }
    }

    const started = await resolveAndPlayTrack(startIndex, true, resumeSec);
    traceMusic('load-source:resolve-result', {
      sourceUri: parsed.uri,
      startIndex,
      resumeSec,
      started,
    });
    if (!started) return false;

    setCurrentStationLabel(state.frameDoc, getPlatformLabel(parsed), parsed.platform);
    return true;
  }

  async function resolveAndPlayTrack(index, autoplay, seekToSec) {
    if (!Array.isArray(state.queue) || !state.queue.length) return false;
    if (index < 0 || index >= state.queue.length) return false;

    const entry = state.queue[index];
    if (!entry) return false;

    traceMusic('resolve-play-track:start', {
      index,
      autoplay: Boolean(autoplay),
      seekToSec: Number(seekToSec) || 0,
      uri: entry.spotifyUri || '',
      title: entry.title || '',
      artist: entry.artist || '',
      hasYtVideoId: Boolean(entry.ytVideoId),
    });

    await hydrateQueueEntryMetadata(entry);

    if (!entry.ytVideoId) {
      const result = await resolveYouTubeVideoForTrack(entry);
      if (!result || !result.videoId) {
        traceMusic('resolve-play-track:no-youtube-id', {
          index,
          uri: entry.spotifyUri || '',
          title: entry.title || '',
          artist: entry.artist || '',
        });
        console.warn('[NT Music] Could not resolve YouTube result for track:', entry);
        return false;
      }
      entry.ytVideoId = result.videoId;
      // Use album art thumbnail from YouTube Music search when available
      if (result.thumbnail && !entry.art) entry.art = result.thumbnail;
    }

    let loaded = false;

    // Embed controller only understands Spotify URIs — force YT player for
    // YouTube direct links, Apple Music tracks, and any other non-Spotify source.
    const useEmbed = getActivePlaybackEngine() === 'embed'
      && String(entry.spotifyUri || '').startsWith('spotify:');
    if (useEmbed) {
      void safeYTCall('pauseVideo');
      loaded = await loadSourceIntoEmbedController(entry.spotifyUri, autoplay);
    } else {
      if (state.embedController) void safeControllerCall('pause');

      await ensureYouTubePlayer(entry.ytVideoId);

      loaded = safeYTCall('loadVideoById', entry.ytVideoId);
      if (!loaded) {
        loaded = safeYTCall('cueVideoById', entry.ytVideoId);
        if (loaded && autoplay) {
          safeYTCall('playVideo');
        }
      }

      // If resuming, defer the seek until the player actually enters PLAYING state.
      // Seeking during BUFFERING gets overridden when playback starts.
      if (loaded && seekToSec && seekToSec > 0) {
        state._pendingSeekSec = seekToSec;
      }
    }

    traceMusic('resolve-play-track:load-result', {
      index,
      uri: entry.spotifyUri || '',
      ytVideoId: entry.ytVideoId || '',
      loaded,
      engine: useEmbed ? 'embed' : 'youtube',
    });
    if (!loaded) return false;

    state.queueIndex = index;

    // Persist session state on each track change
    saveSessionState();

    // Record in play history for back-button support
    state.playHistory.push(index);
    if (state.playHistory.length > 200) {
      state.playHistory = state.playHistory.slice(-100);
    }

    state.playback.uri = entry.spotifyUri;
    state.playback.position = (seekToSec && seekToSec > 0) ? seekToSec * 1000 : 0;
    state.playback.duration = Number(entry.durationMs) || 0;
    state.playback.isPaused = false;
    state.playback.isBuffering = false;
    state.playback.updatedAt = Date.now();

    state.trackMeta = {
      uri: entry.spotifyUri,
      title: entry.title,
      artist: entry.artist,
      art: entry.art,
      openUrl: String(entry.openUrl || sourceUriToOpenUrl(entry.spotifyUri)),
      youtubeUrl: `https://www.youtube.com/watch?v=${entry.ytVideoId}`,
      sourceUri: entry.sourceUri || '',
    };

    if (state.active) renderNowPlaying();
    schedulePreResolveNextTrack(useEmbed ? 2500 : 4000);

    return true;
  }

  function schedulePreResolveNextTrack(delayMs) {
    if (state.nextTrackPreResolveTimer) {
      window.clearTimeout(state.nextTrackPreResolveTimer);
      state.nextTrackPreResolveTimer = null;
    }

    if (!state.active) return;
    if (!Array.isArray(state.queue) || state.queue.length < 2) return;

    state.nextTrackPreResolveTimer = window.setTimeout(() => {
      state.nextTrackPreResolveTimer = null;
      void preResolveNextTrack();
    }, Math.max(0, Number(delayMs) || 0));
  }

  async function loadSourceIntoEmbedController(uri, shouldResume) {
    try {
      const api = await waitForEmbedApi(12000);
      ensureEmbedHost();

      if (!state.embedController) {
        await createEmbedController(api, uri);
      } else {
        await safeControllerCall('loadUri', uri);
      }

      if (shouldResume && state.embedController) {
        await safeControllerCall('resume');
      }

      return true;
    } catch (error) {
      console.error('[NT Music] Failed to load embed source:', error);
      return false;
    }
  }

  async function preResolveNextTrack() {
    if (!state.active) return;
    if (!Array.isArray(state.queue) || state.queue.length < 2) return;

    const maxPreResolve = Math.min(3, state.queue.length - 1);

    for (let i = 1; i <= maxPreResolve; i++) {
      let nextIndex;
      if (state.queueMode === 'shuffle') {
        nextIndex = peekNextShuffleQueueIndex(i);
        if (nextIndex === -1) break; // Beyond shuffle cycle boundary
      } else {
        nextIndex = (state.queueIndex + i) % state.queue.length;
      }

      const nextEntry = state.queue[nextIndex];
      if (!nextEntry) break;

      if (nextEntry.ytVideoId || nextEntry._resolving) continue;

      nextEntry._resolving = true;
      try {
        await hydrateQueueEntryMetadata(nextEntry);
        const result = await resolveYouTubeVideoForTrack(nextEntry);
        if (result && result.videoId) {
          nextEntry.ytVideoId = result.videoId;
        }
      } finally {
        nextEntry._resolving = false;
      }
    }
  }

  function generateShuffleOrder(startIndex) {
    const len = state.queue.length;
    if (len === 0) {
      state.shuffleOrder = [];
      state.shufflePosition = -1;
      return;
    }

    const indices = [];
    for (let i = 0; i < len; i++) indices.push(i);

    // Fisher-Yates shuffle
    for (let i = indices.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      const tmp = indices[i];
      indices[i] = indices[j];
      indices[j] = tmp;
    }

    // Place startIndex at position 0 so "next" begins from position 1
    if (typeof startIndex === 'number' && startIndex >= 0 && startIndex < len) {
      const pos = indices.indexOf(startIndex);
      if (pos > 0) {
        indices[pos] = indices[0];
        indices[0] = startIndex;
      }
      state.shufflePosition = 0;
    } else {
      state.shufflePosition = -1;
    }

    state.shuffleOrder = indices;
  }

  function getNextShuffleQueueIndex() {
    if (!state.shuffleOrder.length) return 0;

    const nextPos = state.shufflePosition + 1;

    if (nextPos >= state.shuffleOrder.length) {
      // Full cycle complete — reshuffle, put last-played at front to avoid immediate repeat
      const lastPlayed = state.shuffleOrder[state.shufflePosition] || 0;
      generateShuffleOrder(lastPlayed);
      state.shufflePosition = 1;
      return state.shuffleOrder.length > 1 ? state.shuffleOrder[1] : 0;
    }

    state.shufflePosition = nextPos;
    return state.shuffleOrder[nextPos];
  }

  function peekNextShuffleQueueIndex(offset) {
    if (!state.shuffleOrder.length) return 0;
    const peekPos = state.shufflePosition + offset;
    if (peekPos >= state.shuffleOrder.length) return -1;
    return state.shuffleOrder[peekPos];
  }

  function getCurrentPlaybackPositionMs() {
    const player = state.ytPlayer;
    if (player && state.ytPlayerReady && typeof player.getCurrentTime === 'function') {
      return Math.max(0, Math.round((Number(player.getCurrentTime()) || 0) * 1000));
    }
    return state.playback.position || 0;
  }

  async function playNextInQueue() {
    if (!Array.isArray(state.queue) || !state.queue.length) return;
    if (state._advancingQueue) return;
    state._advancingQueue = true;
    try {

      let nextIndex;
      if (state.queueMode === 'shuffle') {
        nextIndex = getNextShuffleQueueIndex();
      } else {
        nextIndex = (state.queueIndex + 1) % state.queue.length;
      }

      const started = await resolveAndPlayTrack(nextIndex, true);
      if (!started && state.queue.length > 1) {
        let fallback;
        if (state.queueMode === 'shuffle') {
          fallback = getNextShuffleQueueIndex();
        } else {
          fallback = (nextIndex + 1) % state.queue.length;
        }
        if (fallback !== nextIndex) {
          await resolveAndPlayTrack(fallback, true);
        }
      }
    } finally { state._advancingQueue = false; }
  }

  async function playPrevInQueue() {
    if (!Array.isArray(state.queue) || !state.queue.length) return;

    // Standard music player: if >3s into the track, restart it first
    const positionMs = getCurrentPlaybackPositionMs();
    if (positionMs > 3000) {
      const player = state.ytPlayer;
      if (player && state.ytPlayerReady && typeof player.seekTo === 'function') {
        player.seekTo(0, true);
        state.playback.position = 0;
        state.playback.updatedAt = Date.now();
        if (state.active) renderNowPlaying();
      }
      return;
    }

    // Go to previous track from play history
    if (state.playHistory.length > 1) {
      // Pop current track off history
      state.playHistory.pop();
      // Get the previous track
      const prevIndex = state.playHistory[state.playHistory.length - 1];

      // In shuffle mode, rewind shufflePosition so "next" replays same sequence
      if (state.queueMode === 'shuffle' && state.shuffleOrder.length > 0) {
        const posInShuffle = state.shuffleOrder.indexOf(prevIndex);
        if (posInShuffle !== -1) {
          state.shufflePosition = posInShuffle;
        }
      }

      // Pop this too — resolveAndPlayTrack will push it back
      state.playHistory.pop();

      const started = await resolveAndPlayTrack(prevIndex, true);
      if (!started && state.playHistory.length > 0) {
        const fallbackIndex = state.playHistory[state.playHistory.length - 1];
        state.playHistory.pop();
        await resolveAndPlayTrack(fallbackIndex, true);
      }
    } else {
      // No history — fall back to sequential previous
      let prevIndex = state.queueIndex - 1;
      if (prevIndex < 0) prevIndex = state.queue.length - 1;
      const started = await resolveAndPlayTrack(prevIndex, true);
      if (!started && state.queue.length > 1) {
        const fallback = (prevIndex - 1 + state.queue.length) % state.queue.length;
        await resolveAndPlayTrack(fallback, true);
      }
    }
  }

  async function resolveYouTubeVideoForTrack(track) {
    if (!track || !track.spotifyUri) return null;

    const uri = String(track.spotifyUri || '').trim();
    if (!uri) return null;

    // Skip cache if the cached video was already marked as failed (e.g. embed-blocked)
    var failedIds = (track._failedVideoIds && track._failedVideoIds.length) ? track._failedVideoIds : [];

    if (state.ytSearchCache.has(uri)) {
      var cachedResult = state.ytSearchCache.get(uri);
      if (!failedIds.length || failedIds.indexOf(cachedResult.videoId) === -1) {
        return cachedResult;
      }
      // Cached result is in the failed list — clear it and re-search
      state.ytSearchCache.delete(uri);
    }

    const storageKey = `yt_cache_v${YT_CACHE_VERSION}_${uri}`;
    const storedVideoId = String(GM_getValue(storageKey, '') || '').trim();
    if (/^[A-Za-z0-9_-]{11}$/.test(storedVideoId) && failedIds.indexOf(storedVideoId) === -1) {
      const cached = {
        videoId: storedVideoId,
        title: '',
        author: '',
        durationSec: 0,
      };
      state.ytSearchCache.set(uri, cached);
      return cached;
    }

    if (state.ytSearchInFlightByUri.has(uri)) {
      return state.ytSearchInFlightByUri.get(uri);
    }

    const task = (async () => {
      let title = String(track.title || '').trim();
      let artist = String(track.artist || '').trim();
      let expectedDurationSec = Math.round((Number(track.durationMs) || 0) / 1000);

      if (!hasUsableTitle(title) || !hasUsableArtist(artist)) {
        const meta = await resolveTrackMetaForUri(uri);
        if (meta) {
          if (!hasUsableTitle(title)) title = String(meta.title || '').trim();
          if (!hasUsableArtist(artist)) artist = String(meta.artist || '').trim();
          if (!expectedDurationSec && meta.durationMs) {
            expectedDurationSec = Math.round(Number(meta.durationMs) / 1000);
          }
          track.title = title || track.title;
          track.artist = artist || track.artist;
          if (meta.durationMs && !track.durationMs) track.durationMs = meta.durationMs;
          if (meta.art && !track.art) track.art = meta.art;
          if (meta.openUrl && !track.openUrl) track.openUrl = meta.openUrl;
        }
      }

      if (!hasUsableTitle(title)) {
        console.warn('[NT Music] Missing usable title; refusing to guess track.', { uri, title, artist });
        return null;
      }
      if (!hasUsableArtist(artist)) {
        console.warn('[NT Music] Missing artist metadata; using title-only search.', { uri, title });
      }

      const primaryArtist = extractPrimaryArtist(artist);
      const baseQuery = [artist, title].filter(Boolean).join(' ').trim();
      const shortQuery = [primaryArtist, title].filter(Boolean).join(' ').trim();
      if (!baseQuery) return null;

      const queries = dedupeStrings([
        primaryArtist && title ? `${title} ${primaryArtist} - Topic` : '',
        primaryArtist && title ? `"${primaryArtist} - Topic" ${title}` : '',
        primaryArtist && title ? `${primaryArtist} - ${title}` : '',
        primaryArtist && title ? `${primaryArtist} ${title} official audio` : '',
        primaryArtist && title ? `${primaryArtist} ${title} official video` : '',
        primaryArtist && title ? `${primaryArtist} ${title}` : '',
        artist !== primaryArtist && artist && title ? `${artist} ${title}` : '',
        `${shortQuery} audio`,
        `${baseQuery}`,
      ]);

      // Compute meta quality once — used to adjust acceptance strictness
      const mq = getTrackMetaQuality(track);

      // --- Two-pass search: prefer Topic channel results ---
      // Pass 1: Try the first two queries (Topic-targeted) and ONLY accept
      //         results from Topic channels.  This prevents viral non-Topic
      //         videos from eclipsing the exact studio recording.
      // Pass 2: Normal search across all queries, accepting any passing result.
      // --- Pass 1: Topic-channel hunting ---
      // Search multiple sources for Topic-channel results, starting with the
      // most reliable.  YouTube Music is checked first because its "Songs"
      // category maps directly to Topic channel videos.  The others are
      // fallbacks for edge cases where YT Music doesn't have the track.
      if (primaryArtist && title) {
        var topicCandidates = [];

        // Source 1 (primary): YouTube Music search — songs on YT Music ARE
        // Topic channel videos, making this the most reliable source by far.
        var ytMusicResults = await searchYouTubeMusicForTopic(title, primaryArtist);
        for (var r4 = 0; r4 < ytMusicResults.length; r4 += 1) topicCandidates.push(ytMusicResults[r4]);

        // Source 2: Google search — sometimes finds Topic content via site:
        var googleResults = await searchGoogleForTopic(title, primaryArtist);
        for (var r3 = 0; r3 < googleResults.length; r3 += 1) topicCandidates.push(googleResults[r3]);

        // Source 3 & 4: YouTube searches with Topic-targeted queries (least
        // reliable — standard YouTube search rarely surfaces Topic content)
        for (var tq = 0; tq < 2 && tq < queries.length; tq += 1) {
          var itResults = await searchYouTubeInnerTube(queries[tq]);
          for (var r = 0; r < itResults.length; r += 1) topicCandidates.push(itResults[r]);
          var webResults = await searchYouTubeWeb(queries[tq]);
          for (var r2 = 0; r2 < webResults.length; r2 += 1) topicCandidates.push(webResults[r2]);
        }

        // Filter to Topic-channel candidates that match the PRIMARY artist.
        // Without the artist check, "8-Bit Arcade - Topic" would pass for
        // an Owl City search, producing 8-bit covers instead of originals.
        var primaryNorm = normalizeSearchText(primaryArtist);
        var topicOnly = topicCandidates.filter(function (c) {
          var auth = String(c.author || '');
          if (!/\s-\sTopic$/i.test(auth)) return false;
          var channelArtist = auth.replace(/\s-\sTopic$/i, '').trim();
          var channelNorm = normalizeSearchText(channelArtist);
          // Accept if the channel artist matches primary artist, or if
          // one contains the other (handles "98°" vs "98 Degrees" etc.)
          return channelNorm === primaryNorm ||
            channelNorm.indexOf(primaryNorm) !== -1 ||
            primaryNorm.indexOf(channelNorm) !== -1;
        });
        if (_ntMusicApi.verbose) console.log('[NT Music] Topic search for "' + title + '" by ' + primaryArtist + ': ' + topicCandidates.length + ' raw candidates (' + ytMusicResults.length + ' from YT Music), ' + topicOnly.length + ' Topic-channel matches');
        if (topicOnly.length) {
          var best = pickBestYouTubeMatch(topicOnly, title, artist, expectedDurationSec, mq, failedIds);
          if (best) {
            if (_ntMusicApi.verbose) console.log('[NT Music] Topic winner: ' + best.videoId + ' "' + best.title + '" by ' + best.author);
            state.ytSearchCache.set(uri, best);
            GM_setValue(storageKey, best.videoId);
            return best;
          }
          if (_ntMusicApi.verbose) console.warn('[NT Music] Topic candidates found but none passed scoring for "' + title + '"');
        }
      }

      // Pass 2: Normal search — accept the best from any source
      for (let i = 0; i < queries.length; i += 1) {
        const query = queries[i];
        const result = await searchYouTube(query, title, artist, expectedDurationSec, mq, failedIds);
        if (result && result.videoId) {
          if (_ntMusicApi.verbose) console.log('[NT Music] Pass 2 picked: ' + result.videoId + ' "' + result.title + '" by ' + result.author + ' (query: ' + query + ')');
          state.ytSearchCache.set(uri, result);
          GM_setValue(storageKey, result.videoId);
          return result;
        }
      }

      return null;
    })();

    state.ytSearchInFlightByUri.set(uri, task);

    try {
      return await task;
    } finally {
      state.ytSearchInFlightByUri.delete(uri);
    }
  }

  async function searchYouTube(query, title, artist, expectedDurationSec, metaQuality, excludeVideoIds) {
    // Primary: YouTube InnerTube API (direct, most reliable)
    const innerTubeResults = await searchYouTubeInnerTube(query);
    if (innerTubeResults.length) {
      const best = pickBestYouTubeMatch(innerTubeResults, title, artist, expectedDurationSec, metaQuality, excludeVideoIds);
      if (best) return best;
    }

    // Fallback 1: YouTube web scrape (ytInitialData from search page)
    const webResults = await searchYouTubeWeb(query);
    if (webResults.length) {
      const best = pickBestYouTubeMatch(webResults, title, artist, expectedDurationSec, metaQuality, excludeVideoIds);
      if (best) return best;
    }

    // Fallback 2: Invidious (third-party, can be flaky)
    const invidiousResults = await searchInvidious(query);
    if (invidiousResults.length) {
      const best = pickBestYouTubeMatch(invidiousResults, title, artist, expectedDurationSec, metaQuality, excludeVideoIds);
      if (best) return best;
    }

    // Fallback 3: Piped (third-party, can be flaky)
    const pipedResults = await searchPiped(query);
    if (pipedResults.length) {
      const best = pickBestYouTubeMatch(pipedResults, title, artist, expectedDurationSec, metaQuality, excludeVideoIds);
      if (best) return best;
    }

    return null;
  }

  async function searchInvidious(query) {
    const results = [];

    for (let i = 0; i < INVIDIOUS_INSTANCES.length; i += 1) {
      const instance = INVIDIOUS_INSTANCES[i];
      const url = `${instance}/api/v1/search?q=${encodeURIComponent(query)}&type=video`;

      try {
        const response = await gmRequest({
          method: 'GET',
          url,
          headers: {
            Accept: 'application/json',
          },
          timeout: 9000,
        });

        if (response.status < 200 || response.status >= 300) continue;

        const payload = safeJsonParse(response.responseText);
        if (!Array.isArray(payload)) continue;

        payload.forEach((entry) => {
          const videoId = String(entry && entry.videoId ? entry.videoId : '').trim();
          if (!/^[A-Za-z0-9_-]{11}$/.test(videoId)) return;

          results.push({
            videoId,
            title: String(entry && entry.title ? entry.title : ''),
            author: String(entry && (entry.author || entry.uploader || entry.authorName) ? (entry.author || entry.uploader || entry.authorName) : ''),
            durationSec: Number(entry && (entry.lengthSeconds || entry.duration) ? (entry.lengthSeconds || entry.duration) : 0) || 0,
            views: Number(entry && (entry.viewCount || entry.views) ? (entry.viewCount || entry.views) : 0) || 0,
            isLive: Boolean(entry && (entry.liveNow || entry.isLive)),
            verified: Boolean(entry && (entry.authorVerified || entry.verified)),
          });
        });

        if (results.length) return results;
      } catch (error) {
        // try next instance
      }
    }

    return results;
  }

  async function searchPiped(query) {
    for (let i = 0; i < PIPED_INSTANCES.length; i += 1) {
      const instance = PIPED_INSTANCES[i];
      const filters = ['music_songs', 'videos'];

      for (let f = 0; f < filters.length; f += 1) {
        const filter = filters[f];
        const url = `${instance}/search?q=${encodeURIComponent(query)}&filter=${encodeURIComponent(filter)}`;

        try {
          const response = await gmRequest({
            method: 'GET',
            url,
            headers: {
              Accept: 'application/json',
            },
            timeout: 9000,
          });

          if (response.status < 200 || response.status >= 300) continue;

          const payload = safeJsonParse(response.responseText) || {};
          const items = Array.isArray(payload.items) ? payload.items : [];
          const results = [];

          items.forEach((entry) => {
            const urlValue = String(entry && entry.url ? entry.url : '');
            const videoId = extractVideoIdFromUrl(urlValue) || extractVideoIdFromUrl(String(entry && entry.videoUrl ? entry.videoUrl : ''));
            if (!videoId) return;

            const duration = Number(entry && entry.duration ? entry.duration : 0) || parseDurationText(String(entry && entry.durationText ? entry.durationText : ''));

            results.push({
              videoId,
              title: String(entry && entry.title ? entry.title : ''),
              author: String(entry && (entry.uploaderName || entry.uploader || entry.author) ? (entry.uploaderName || entry.uploader || entry.author) : ''),
              durationSec: Number(duration) || 0,
              views: Number(entry && (entry.views || entry.viewCount) ? (entry.views || entry.viewCount) : 0) || 0,
              isLive: Boolean(entry && (entry.isLive || entry.live)),
              verified: Boolean(entry && (entry.uploaderVerified || entry.verified)),
            });
          });

          if (results.length) return results;
        } catch (error) {
          // try next filter/instance
        }
      }
    }

    return [];
  }

  async function searchYouTubeWeb(query) {
    try {
      const response = await gmRequest({
        method: 'GET',
        url: `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`,
        headers: {
          Accept: 'text/html',
        },
        timeout: 10000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      const html = String(response.responseText || '');
      const candidates = extractYouTubeSearchCandidates(html);
      if (candidates.length) return candidates;

      const fallback = html.match(/"videoId":"([A-Za-z0-9_-]{11})"/);
      if (!fallback) return [];

      return [
        {
          videoId: String(fallback[1]),
          title: '',
          author: '',
          durationSec: 0,
          views: 0,
          isLive: false,
          verified: false,
        },
      ];
    } catch (error) {
      return [];
    }
  }

  /**
   * Search Google for a YouTube Topic-channel video.
   * Google is much better than YouTube's own search at surfacing Topic
   * channel content.  We query:
   *   site:youtube.com "{artist} - Topic" "{title}"
   * and extract YouTube video IDs from the results.
   */
  async function searchGoogleForTopic(title, artist) {
    if (!title || !artist) return [];
    try {
      const q = `site:youtube.com "${artist} - Topic" "${title}"`;
      const response = await gmRequest({
        method: 'GET',
        url: `https://www.google.com/search?q=${encodeURIComponent(q)}&num=5`,
        headers: {
          Accept: 'text/html',
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        },
        timeout: 8000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      var html = String(response.responseText || '');

      // Extract YouTube video IDs from Google result links
      // Google links look like: youtube.com/watch?v=XXXXXXXXXXX
      var idPattern = /youtube\.com\/watch\?v=([A-Za-z0-9_-]{11})/g;
      var seen = {};
      var results = [];
      var match;
      while ((match = idPattern.exec(html)) !== null) {
        var vid = match[1];
        if (seen[vid]) continue;
        seen[vid] = true;
        results.push({
          videoId: vid,
          title: '',       // We don't have metadata from Google results
          author: '',       // Will be enriched below if possible
          durationSec: 0,
          views: 0,
          isLive: false,
          verified: false,
        });
      }

      if (!results.length) return [];

      // Try to enrich the first few results with actual metadata via noembed
      for (var i = 0; i < Math.min(results.length, 3); i += 1) {
        try {
          var oembed = await gmRequest({
            method: 'GET',
            url: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v=' + results[i].videoId,
            headers: { Accept: 'application/json' },
            timeout: 5000,
          });
          if (oembed.status >= 200 && oembed.status < 300) {
            var data = safeJsonParse(oembed.responseText);
            if (data) {
              results[i].title = String(data.title || '');
              // noembed strips " - Topic" from author_name, so check if the
              // returned author matches our target artist — if so, restore the
              // " - Topic" suffix since our Google query explicitly filtered for it.
              var noembedAuthor = String(data.author_name || '');
              if (noembedAuthor && artist &&
                noembedAuthor.toLowerCase().trim() === artist.toLowerCase().trim()) {
                results[i].author = artist + ' - Topic';
              } else if (/\s-\sTopic$/i.test(noembedAuthor)) {
                // noembed kept the suffix (future-proofing)
                results[i].author = noembedAuthor;
              } else {
                results[i].author = noembedAuthor;
              }
            }
          }
        } catch (_) { }
      }

      return results;
    } catch (error) {
      return [];
    }
  }

  async function searchYouTubeInnerTube(query) {
    try {
      const response = await gmRequest({
        method: 'POST',
        url: 'https://www.youtube.com/youtubei/v1/search?prettyPrint=false',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
        data: JSON.stringify({
          query: query,
          context: {
            client: {
              clientName: 'WEB',
              clientVersion: '2.20240101.00.00',
              hl: 'en',
              gl: 'US',
            },
          },
          params: 'EgIQAQ%3D%3D',
        }),
        timeout: 10000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      const text = String(response.responseText || '');
      if (!text) return [];

      const blocks = extractJsonBlocksByKey(text, '"videoRenderer":', 30);
      const results = [];

      for (let i = 0; i < blocks.length; i += 1) {
        const renderer = safeJsonParse(blocks[i]);
        if (!renderer || !renderer.videoId) continue;

        const videoId = String(renderer.videoId).trim();
        if (!/^[A-Za-z0-9_-]{11}$/.test(videoId)) continue;

        const lengthLabel = readYouTubeText(renderer.lengthText);

        results.push({
          videoId,
          title: readYouTubeText(renderer.title),
          author: readYouTubeText(renderer.ownerText),
          durationSec: parseDurationText(lengthLabel),
          views: parseInnerTubeViewCount(renderer),
          isLive: Boolean(renderer.isLive) || /\blive\b/i.test(String(lengthLabel || '')),
          verified: checkInnerTubeVerified(renderer),
        });
      }

      return results;
    } catch (error) {
      return [];
    }
  }

  // ─── YouTube Music (WEB_REMIX) search for Topic channel content ───────
  // YouTube Music's "Songs" category maps directly to Topic channel videos.
  // Standard YouTube search almost never surfaces Topic content, but YT Music
  // is built around it — making this the most reliable source for Topic videos.

  async function searchYouTubeMusicForTopic(title, artist) {
    try {
      var query = title + ' ' + artist;
      var response = await gmRequest({
        method: 'POST',
        url: 'https://music.youtube.com/youtubei/v1/search?prettyPrint=false',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          Origin: 'https://music.youtube.com',
          Referer: 'https://music.youtube.com/',
        },
        data: JSON.stringify({
          query: query,
          context: {
            client: {
              clientName: 'WEB_REMIX',
              clientVersion: '1.20240101.01.00',
              hl: 'en',
              gl: 'US',
            },
          },
          params: 'EgWKAQIIAQ%3D%3D',    // Filter: Songs only
        }),
        timeout: 10000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      var text = String(response.responseText || '');
      if (!text) return [];

      // Extract musicResponsiveListItemRenderer blocks (same renderer used
      // for songs in YT Music search results and playlist items)
      var blocks = extractJsonBlocksByKey(text, '"musicResponsiveListItemRenderer":', 30);
      var results = [];

      for (var i = 0; i < blocks.length; i += 1) {
        var renderer = safeJsonParse(blocks[i]);
        if (!renderer) continue;

        // Extract video ID — try multiple paths
        var videoId = '';

        // Path 1: playlistItemData.videoId (same field as playlist items)
        if (renderer.playlistItemData && renderer.playlistItemData.videoId) {
          videoId = String(renderer.playlistItemData.videoId).trim();
        }

        // Path 2: overlay → watchEndpoint.videoId
        if (!videoId && renderer.overlay) {
          try {
            videoId = String(
              renderer.overlay.musicItemThumbnailOverlayRenderer
                .content.musicPlayButtonRenderer.playNavigationEndpoint
                .watchEndpoint.videoId || ''
            ).trim();
          } catch (_) { /* structure not present */ }
        }

        // Path 3: flexColumns navigation endpoint
        if (!videoId && Array.isArray(renderer.flexColumns)) {
          try {
            var navEp = renderer.flexColumns[0]
              .musicResponsiveListItemFlexColumnRenderer.text.runs[0]
              .navigationEndpoint;
            if (navEp && navEp.watchEndpoint) {
              videoId = String(navEp.watchEndpoint.videoId || '').trim();
            }
          } catch (_) { /* structure not present */ }
        }

        if (!videoId || !/^[A-Za-z0-9_-]{11}$/.test(videoId)) continue;

        // Extract title and artist from flexColumns
        var songTitle = '';
        var songArtist = '';
        var flexColumns = renderer.flexColumns;
        if (Array.isArray(flexColumns)) {
          songTitle = readMusicFlexColumnText(flexColumns[0]);
          // For search results, flexColumns[1] contains "Artist • Album"
          // — extract just the artist (first run) to avoid album noise
          songArtist = readMusicSearchArtist(flexColumns[1]);
        }

        // Extract thumbnail (album art) — YouTube Music provides proper cover art
        var thumbnail = '';
        try {
          var thumbObj = renderer.thumbnail;
          if (thumbObj && thumbObj.musicThumbnailRenderer) {
            var thumbList = thumbObj.musicThumbnailRenderer.thumbnail;
            if (thumbList && Array.isArray(thumbList.thumbnails)) {
              thumbnail = pickBestMusicThumbnail(thumbList.thumbnails);
            }
          }
        } catch (_) { /* thumbnail not available */ }

        // Extract duration from fixedColumns
        var durationSec = 0;
        var fixedColumns = renderer.fixedColumns;
        if (Array.isArray(fixedColumns) && fixedColumns[0]) {
          var fixedRenderer = fixedColumns[0].musicResponsiveListItemFixedColumnRenderer;
          if (fixedRenderer) {
            var durText = readYouTubeText(fixedRenderer.text);
            durationSec = parseDurationText(durText);
          }
        }

        // YouTube Music "Songs" results ARE Topic channel content —
        // append " - Topic" so the Topic filter in Pass 1 accepts them
        results.push({
          videoId: videoId,
          title: songTitle || '',
          author: (songArtist || artist) + ' - Topic',
          durationSec: durationSec,
          thumbnail: thumbnail,
          views: 0,
          isLive: false,
          verified: false,
        });
      }

      return results;
    } catch (error) {
      return [];
    }
  }

  /**
   * Extract the artist name from a YouTube Music search result flex column.
   * In search results, flexColumns[1] contains multiple runs like:
   *   ["Rascal Flatts", " • ", "Cars (Original Motion Picture Soundtrack)"]
   * We only want the first run (the artist name).
   */
  function readMusicSearchArtist(column) {
    if (!column) return '';
    var renderer = column.musicResponsiveListItemFlexColumnRenderer;
    if (!renderer || !renderer.text) return '';
    var runs = renderer.text.runs;
    if (!Array.isArray(runs) || !runs.length) return '';
    // First run is the primary artist; subsequent runs are separators + album
    return String(runs[0].text || '').trim();
  }

  // ─── YouTube Music Album Art Lookup ──────────────────────────────────
  // For a known video ID, fetch the proper album cover art from YouTube Music.
  // Two strategies:
  //   1. Direct: call the "next" endpoint and regex-extract lh3 URLs
  //   2. Search: search YouTube Music for title+artist, match by videoId
  // Strategy 2 is the reliable fallback — it reuses the same search + thumbnail
  // extraction pipeline that already works for playlists and Topic matching.

  async function fetchYouTubeMusicAlbumArt(videoId, title, artist) {
    // Strategy 1: Direct lookup via YouTube Music "next" endpoint.
    // Scan the entire response for lh3.googleusercontent.com URLs (album covers).
    var directArt = await fetchYouTubeMusicArtDirect(videoId);
    if (directArt) return directArt;

    // Strategy 2: Search YouTube Music for the track, match by videoId.
    // This reuses searchYouTubeMusicForTopic which we know returns lh3 thumbnails.
    if (title) {
      var searchArt = await fetchYouTubeMusicArtBySearch(videoId, title, artist || '');
      if (searchArt) return searchArt;
    }

    return '';
  }

  /** Strategy 1: Extract first lh3 album-art URL from the "next" endpoint response. */
  async function fetchYouTubeMusicArtDirect(videoId) {
    try {
      var response = await gmRequest({
        method: 'POST',
        url: 'https://music.youtube.com/youtubei/v1/next?prettyPrint=false',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          Origin: 'https://music.youtube.com',
          Referer: 'https://music.youtube.com/',
        },
        data: JSON.stringify({
          videoId: videoId,
          isAudioOnly: true,
          context: {
            client: {
              clientName: 'WEB_REMIX',
              clientVersion: '1.20240101.01.00',
              hl: 'en',
              gl: 'US',
            },
          },
        }),
        timeout: 8000,
      });

      if (response.status < 200 || response.status >= 300) return '';
      var text = String(response.responseText || '');
      if (!text) return '';

      // Scan the full response for lh3 album-art URLs.  The first match is
      // almost always the current track's album cover; later ones are related tracks.
      var urlPattern = /https:\/\/lh3\.googleusercontent\.com\/[A-Za-z0-9_\-\/+=]+(?:=[^\s"',}\]]+)?/g;
      var match = urlPattern.exec(text);
      if (match) {
        var artUrl = match[0];
        // Upscale to 300×300
        if (artUrl.indexOf('=w') !== -1) {
          artUrl = artUrl.replace(/=w\d+-h\d+[^\s"',}\]]*/, '=w300-h300-l90-rj');
        } else {
          artUrl += '=w300-h300-l90-rj';
        }
        return artUrl;
      }

      return '';
    } catch (_) {
      return '';
    }
  }

  /** Strategy 2: Search YouTube Music and use the thumbnail of the matching result. */
  async function fetchYouTubeMusicArtBySearch(videoId, title, artist) {
    try {
      var query = title + ' ' + artist;
      var response = await gmRequest({
        method: 'POST',
        url: 'https://music.youtube.com/youtubei/v1/search?prettyPrint=false',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          Origin: 'https://music.youtube.com',
          Referer: 'https://music.youtube.com/',
        },
        data: JSON.stringify({
          query: query,
          context: {
            client: {
              clientName: 'WEB_REMIX',
              clientVersion: '1.20240101.01.00',
              hl: 'en',
              gl: 'US',
            },
          },
          params: 'EgWKAQIIAQ%3D%3D',   // Filter: Songs only
        }),
        timeout: 10000,
      });

      if (response.status < 200 || response.status >= 300) return '';
      var text = String(response.responseText || '');
      if (!text) return '';

      var blocks = extractJsonBlocksByKey(text, '"musicResponsiveListItemRenderer":', 20);
      var exactMatch = '';
      var firstArt = '';

      for (var i = 0; i < blocks.length; i += 1) {
        var renderer = safeJsonParse(blocks[i]);
        if (!renderer) continue;

        // Extract thumbnail
        var thumbnail = '';
        try {
          var thumbObj = renderer.thumbnail;
          if (thumbObj && thumbObj.musicThumbnailRenderer) {
            var thumbList = thumbObj.musicThumbnailRenderer.thumbnail;
            if (thumbList && Array.isArray(thumbList.thumbnails)) {
              thumbnail = pickBestMusicThumbnail(thumbList.thumbnails);
            }
          }
        } catch (_) { /* skip */ }
        if (!thumbnail) continue;

        // Save first art as fallback
        if (!firstArt) firstArt = thumbnail;

        // Check for exact videoId match
        var vid = '';
        if (renderer.playlistItemData && renderer.playlistItemData.videoId) {
          vid = String(renderer.playlistItemData.videoId).trim();
        }
        if (vid === videoId) {
          exactMatch = thumbnail;
          break;
        }
      }

      return exactMatch || firstArt;
    } catch (_) {
      return '';
    }
  }

  // ─── YouTube Playlist Fetching ────────────────────────────────────────

  async function fetchYouTubePlaylistTracks(playlistId) {
    const cacheKey = 'youtube:playlist:' + playlistId + '::tracks';
    if (state.sourceTrackListCache.has(cacheKey)) {
      const cached = state.sourceTrackListCache.get(cacheKey);
      traceMusic('youtube-playlist:cache-hit', {
        playlistId,
        count: Array.isArray(cached) ? cached.length : 0,
      });
      return cached;
    }

    traceMusic('youtube-playlist:fetch-start', { playlistId });

    // Primary: YouTube Music API (WEB_REMIX) — gives per-track album art
    var tracks = await fetchYouTubePlaylistMusicApi(playlistId);
    traceMusic('youtube-playlist:music-api-result', {
      playlistId,
      count: tracks.length,
    });

    // Fallback: regular InnerTube browse API (WEB client)
    if (!tracks.length) {
      var result = await fetchYouTubePlaylistInnerTube(playlistId);
      traceMusic('youtube-playlist:innertube-result', {
        playlistId,
        count: result && Array.isArray(result.tracks) ? result.tracks.length : 0,
      });

      // Fallback: web scrape playlist page
      if (!result.tracks.length) {
        result = await fetchYouTubePlaylistWebScrape(playlistId);
        traceMusic('youtube-playlist:web-scrape-result', {
          playlistId,
          count: result && Array.isArray(result.tracks) ? result.tracks.length : 0,
        });
      }

      tracks = result.tracks;

      // If we got playlist-level album art (e.g. YouTube Music OLAK albums), stamp it on each track
      if (result.headerArt && tracks.length) {
        for (var i = 0; i < tracks.length; i += 1) {
          if (!tracks[i].playlistArt) tracks[i].playlistArt = result.headerArt;
        }
      }
    }

    if (tracks.length) {
      state.sourceTrackListCache.set(cacheKey, tracks);
    }

    traceMusic('youtube-playlist:final-result', {
      playlistId,
      count: tracks.length,
    });

    return tracks;
  }

  async function fetchYouTubePlaylistInnerTube(playlistId) {
    try {
      var response = await gmRequest({
        method: 'POST',
        url: 'https://www.youtube.com/youtubei/v1/browse?prettyPrint=false',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
        data: JSON.stringify({
          browseId: 'VL' + playlistId,
          context: {
            client: {
              clientName: 'WEB',
              clientVersion: '2.20240101.00.00',
              hl: 'en',
              gl: 'US',
            },
          },
        }),
        timeout: 15000,
      });

      if (response.status < 200 || response.status >= 300) return { tracks: [], headerArt: '' };

      var text = String(response.responseText || '');
      if (!text) return { tracks: [], headerArt: '' };

      var results = extractPlaylistVideoRenderers(text);

      // Extract playlist-level album art from header (YouTube Music albums)
      var headerArt = extractPlaylistHeaderArt(text);

      // Handle continuation for playlists >100 videos
      var continuationToken = extractContinuationToken(text);
      var safety = 0;

      while (continuationToken && safety < 20) {
        safety += 1;
        var contResponse = await gmRequest({
          method: 'POST',
          url: 'https://www.youtube.com/youtubei/v1/browse?prettyPrint=false',
          headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json',
          },
          data: JSON.stringify({
            continuation: continuationToken,
            context: {
              client: {
                clientName: 'WEB',
                clientVersion: '2.20240101.00.00',
                hl: 'en',
                gl: 'US',
              },
            },
          }),
          timeout: 15000,
        });

        if (contResponse.status < 200 || contResponse.status >= 300) break;

        var contText = String(contResponse.responseText || '');
        var contResults = extractPlaylistVideoRenderers(contText);
        if (!contResults.length) break;

        for (var ci = 0; ci < contResults.length; ci += 1) {
          results.push(contResults[ci]);
        }

        continuationToken = extractContinuationToken(contText);
      }

      return { tracks: results, headerArt: headerArt };
    } catch (error) {
      return { tracks: [], headerArt: '' };
    }
  }

  function extractPlaylistVideoRenderers(text) {
    var blocks = extractJsonBlocksByKey(String(text || ''), '"playlistVideoRenderer":', 500);
    var results = [];

    for (var i = 0; i < blocks.length; i += 1) {
      var renderer = safeJsonParse(blocks[i]);
      if (!renderer || !renderer.videoId) continue;

      var videoId = String(renderer.videoId).trim();
      if (!/^[A-Za-z0-9_-]{11}$/.test(videoId)) continue;

      var lengthLabel = readYouTubeText(renderer.lengthText);

      results.push({
        videoId: videoId,
        title: readYouTubeText(renderer.title),
        author: readYouTubeText(renderer.shortBylineText) || readYouTubeText(renderer.ownerText),
        durationSec: parseDurationText(lengthLabel),
      });
    }

    return results;
  }

  // ─── YouTube Music (WEB_REMIX) playlist fetcher ─────────────────────────
  // Uses the YouTube Music InnerTube client to get per-track album art.
  // Regular WEB client only gives video thumbnails; WEB_REMIX gives actual
  // album cover art from lh3.googleusercontent.com.

  async function fetchYouTubePlaylistMusicApi(playlistId) {
    try {
      var response = await gmRequest({
        method: 'POST',
        url: 'https://music.youtube.com/youtubei/v1/browse?prettyPrint=false',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          Origin: 'https://music.youtube.com',
          Referer: 'https://music.youtube.com/',
        },
        data: JSON.stringify({
          browseId: 'VL' + playlistId,
          context: {
            client: {
              clientName: 'WEB_REMIX',
              clientVersion: '1.20240101.01.00',
              hl: 'en',
              gl: 'US',
            },
          },
        }),
        timeout: 15000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      var text = String(response.responseText || '');
      if (!text) return [];

      var results = extractMusicResponsiveListItems(text);

      // Handle continuation for playlists >100 tracks
      var continuationToken = extractContinuationToken(text);
      var safety = 0;

      while (continuationToken && safety < 20) {
        safety += 1;
        var contResponse = await gmRequest({
          method: 'POST',
          url: 'https://music.youtube.com/youtubei/v1/browse?prettyPrint=false',
          headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json',
            Origin: 'https://music.youtube.com',
            Referer: 'https://music.youtube.com/',
          },
          data: JSON.stringify({
            continuation: continuationToken,
            context: {
              client: {
                clientName: 'WEB_REMIX',
                clientVersion: '1.20240101.01.00',
                hl: 'en',
                gl: 'US',
              },
            },
          }),
          timeout: 15000,
        });

        if (contResponse.status < 200 || contResponse.status >= 300) break;

        var contText = String(contResponse.responseText || '');
        var contResults = extractMusicResponsiveListItems(contText);
        if (!contResults.length) break;

        for (var ci = 0; ci < contResults.length; ci += 1) {
          results.push(contResults[ci]);
        }

        continuationToken = extractContinuationToken(contText);
      }

      return results;
    } catch (error) {
      return [];
    }
  }

  /**
   * Parse musicResponsiveListItemRenderer blocks from YouTube Music API response.
   * Each renderer contains: videoId, title, artist, album, duration, and album art.
   *
   * Structure:
   *   flexColumns[0] = song title (with videoId in watchEndpoint)
   *   flexColumns[1] = artist name
   *   flexColumns[2] = album name
   *   fixedColumns[0] = duration text
   *   thumbnail.musicThumbnailRenderer = album art
   *   playlistItemData.videoId = video ID
   */
  function extractMusicResponsiveListItems(text) {
    var blocks = extractJsonBlocksByKey(String(text || ''), '"musicResponsiveListItemRenderer":', 600);
    var results = [];

    for (var i = 0; i < blocks.length; i += 1) {
      var renderer = safeJsonParse(blocks[i]);
      if (!renderer) continue;

      // Extract video ID
      var videoId = '';
      if (renderer.playlistItemData && renderer.playlistItemData.videoId) {
        videoId = String(renderer.playlistItemData.videoId).trim();
      }
      if (!videoId || !/^[A-Za-z0-9_-]{11}$/.test(videoId)) continue;

      // Extract title, artist, album from flexColumns
      var title = '';
      var author = '';
      var album = '';
      var flexColumns = renderer.flexColumns;
      if (Array.isArray(flexColumns)) {
        title = readMusicFlexColumnText(flexColumns[0]);
        author = readMusicFlexColumnText(flexColumns[1]);
        album = readMusicFlexColumnText(flexColumns[2]);
      }

      // Extract duration from fixedColumns
      var durationSec = 0;
      var fixedColumns = renderer.fixedColumns;
      if (Array.isArray(fixedColumns) && fixedColumns[0]) {
        var fixedRenderer = fixedColumns[0].musicResponsiveListItemFixedColumnRenderer;
        if (fixedRenderer) {
          var durText = readYouTubeText(fixedRenderer.text);
          durationSec = parseDurationText(durText);
        }
      }

      // Extract album art from musicThumbnailRenderer
      var albumArt = '';
      var thumbObj = renderer.thumbnail;
      if (thumbObj && thumbObj.musicThumbnailRenderer) {
        var thumbs = thumbObj.musicThumbnailRenderer.thumbnail;
        if (thumbs && Array.isArray(thumbs.thumbnails)) {
          albumArt = pickBestMusicThumbnail(thumbs.thumbnails);
        }
      }

      results.push({
        videoId: videoId,
        title: title,
        author: author,
        album: album,
        durationSec: durationSec,
        playlistArt: albumArt,
      });
    }

    return results;
  }

  /**
   * Read the text content from a YouTube Music flex column.
   * Path: column.musicResponsiveListItemFlexColumnRenderer.text.runs[0].text
   */
  function readMusicFlexColumnText(column) {
    if (!column) return '';
    var renderer = column.musicResponsiveListItemFlexColumnRenderer;
    if (!renderer || !renderer.text) return '';
    return readYouTubeText(renderer.text);
  }

  /**
   * Pick the best album art thumbnail from YouTube Music thumbnails.
   * YT Music provides lh3.googleusercontent.com URLs at 60x60 and 120x120,
   * but we can resize them via URL parameter to get higher resolution.
   */
  function pickBestMusicThumbnail(thumbnails) {
    if (!Array.isArray(thumbnails) || !thumbnails.length) return '';

    // Pick the largest available thumbnail
    var best = '';
    var bestSize = 0;
    for (var i = 0; i < thumbnails.length; i += 1) {
      var t = thumbnails[i];
      if (!t || !t.url) continue;
      var size = Math.max(t.width || 0, t.height || 0);
      if (size > bestSize) {
        bestSize = size;
        best = String(t.url);
      }
    }

    if (!best) return '';

    // Upscale lh3.googleusercontent.com URLs to 300x300 for display
    // Format: ...=w120-h120-l90-rj → ...=w300-h300-l90-rj
    if (best.indexOf('lh3.googleusercontent.com') !== -1) {
      best = best.replace(/=w\d+-h\d+/, '=w300-h300');
    }

    return best;
  }

  function extractContinuationToken(text) {
    // Look for continuationCommand.token inside continuationEndpoint blocks
    var blocks = extractJsonBlocksByKey(String(text || ''), '"continuationCommand":', 5);
    for (var i = 0; i < blocks.length; i += 1) {
      var obj = safeJsonParse(blocks[i]);
      if (obj && typeof obj.token === 'string' && obj.token.length > 10) {
        return obj.token;
      }
    }
    return '';
  }

  /**
   * Extract playlist-level album art from YouTube InnerTube response.
   * YouTube Music album playlists (OLAK5uy_...) store square album art
   * in the playlist header, not in individual track renderers.
   * Looks in heroPlaylistThumbnailRenderer and playlistCustomThumbnailRenderer.
   */
  function extractPlaylistHeaderArt(text) {
    // Strategy 1: heroPlaylistThumbnailRenderer (primary album art in header banner)
    var heroBlocks = extractJsonBlocksByKey(String(text || ''), '"heroPlaylistThumbnailRenderer":', 3);
    for (var i = 0; i < heroBlocks.length; i += 1) {
      var hero = safeJsonParse(heroBlocks[i]);
      if (hero && hero.thumbnail && Array.isArray(hero.thumbnail.thumbnails)) {
        var best = pickBestSquareThumbnail(hero.thumbnail.thumbnails);
        if (best) return best;
      }
    }

    // Strategy 2: playlistCustomThumbnailRenderer (sidebar thumbnail)
    var customBlocks = extractJsonBlocksByKey(String(text || ''), '"playlistCustomThumbnailRenderer":', 3);
    for (var j = 0; j < customBlocks.length; j += 1) {
      var custom = safeJsonParse(customBlocks[j]);
      if (custom && custom.thumbnail && Array.isArray(custom.thumbnail.thumbnails)) {
        var best2 = pickBestSquareThumbnail(custom.thumbnail.thumbnails);
        if (best2) return best2;
      }
    }

    // Strategy 3: playlistHeaderRenderer may embed thumbnails directly
    var headerBlocks = extractJsonBlocksByKey(String(text || ''), '"playlistHeaderRenderer":', 2);
    for (var k = 0; k < headerBlocks.length; k += 1) {
      var header = safeJsonParse(headerBlocks[k]);
      if (!header) continue;
      // Check cinematic container
      var cinematic = header.cinematicContainer;
      if (cinematic) {
        var cBlocks = extractJsonBlocksByKey(JSON.stringify(cinematic), '"thumbnails":', 3);
        for (var ci = 0; ci < cBlocks.length; ci += 1) {
          var cObj = safeJsonParse(cBlocks[ci]);
          if (Array.isArray(cObj)) {
            var best3 = pickBestSquareThumbnail(cObj);
            if (best3) return best3;
          }
        }
      }
    }

    return '';
  }

  /**
   * Pick the best thumbnail URL from an array, preferring square (1:1) images
   * around 300-640px. Falls back to the largest available.
   */
  function pickBestSquareThumbnail(thumbnails) {
    if (!Array.isArray(thumbnails) || !thumbnails.length) return '';

    // Filter to square-ish thumbnails (aspect ratio close to 1:1)
    var square = thumbnails.filter(function (t) {
      if (!t || !t.url || !t.width || !t.height) return false;
      var ratio = t.width / t.height;
      return ratio > 0.85 && ratio < 1.18;
    });

    // Prefer square thumbnails; fall back to all if none are square
    var pool = square.length ? square : thumbnails;

    // Find the best size: prefer 300-640px range, then take largest
    var best = '';
    var bestScore = -1;
    for (var i = 0; i < pool.length; i += 1) {
      var t = pool[i];
      if (!t || !t.url) continue;
      var size = Math.max(t.width || 0, t.height || 0);
      // Ideal range 300-640 scores highest; outside that, score by size
      var score = (size >= 300 && size <= 640) ? (10000 + size) : size;
      if (score > bestScore) {
        bestScore = score;
        best = String(t.url);
      }
    }
    return best;
  }

  async function fetchYouTubePlaylistWebScrape(playlistId) {
    try {
      var response = await gmRequest({
        method: 'GET',
        url: 'https://www.youtube.com/playlist?list=' + encodeURIComponent(playlistId),
        headers: {
          Accept: 'text/html',
        },
        timeout: 15000,
      });

      if (response.status < 200 || response.status >= 300) return { tracks: [], headerArt: '' };

      var html = String(response.responseText || '');
      var results = extractPlaylistVideoRenderers(html);
      var headerArt = extractPlaylistHeaderArt(html);

      return { tracks: results, headerArt: headerArt };
    } catch (error) {
      return { tracks: [], headerArt: '' };
    }
  }

  function parseInnerTubeViewCount(renderer) {
    if (!renderer) return 0;

    const viewText = readYouTubeText(renderer.viewCountText) ||
      readYouTubeText(renderer.shortViewCountText) || '';

    if (!viewText) return 0;

    const cleaned = viewText.replace(/[^0-9.KMBkmb]/g, '').trim();
    if (!cleaned) return 0;

    const multiplierMatch = cleaned.match(/([0-9.]+)\s*([KMBkmb])?/);
    if (!multiplierMatch) return 0;

    let num = parseFloat(multiplierMatch[1]) || 0;
    const suffix = (multiplierMatch[2] || '').toUpperCase();

    if (suffix === 'K') num *= 1000;
    else if (suffix === 'M') num *= 1000000;
    else if (suffix === 'B') num *= 1000000000;

    return Math.round(num);
  }

  function checkInnerTubeVerified(renderer) {
    if (!renderer) return false;

    const badges = renderer.ownerBadges;
    if (!Array.isArray(badges)) return false;

    for (let i = 0; i < badges.length; i += 1) {
      const badge = badges[i];
      const meta = badge && badge.metadataBadgeRenderer;
      if (!meta) continue;

      const style = String(meta.style || '').toUpperCase();
      if (style.includes('VERIFIED') || style.includes('OFFICIAL')) return true;
    }

    return false;
  }

  function pickBestYouTubeMatch(candidates, trackTitle, trackArtist, expectedDurationSec, metaQuality, excludeVideoIds) {
    if (!Array.isArray(candidates) || !candidates.length) return null;
    var _excludeSet = (Array.isArray(excludeVideoIds) && excludeVideoIds.length) ? excludeVideoIds : null;

    const titleNorm = normalizeSearchText(trackTitle);
    const artistNorm = normalizeSearchText(trackArtist);
    if (!titleNorm) return null;

    const primaryArtist = extractPrimaryArtist(trackArtist);
    const primaryArtistNorm = normalizeSearchText(primaryArtist);
    const artistRequired = hasUsableArtist(trackArtist);
    const wantedTokens = tokenizeSearchText(`${trackTitle} ${trackArtist}`);
    const trackTitleLower = String(trackTitle || '').toLowerCase();

    // Hard-reject patterns — skip these candidates entirely
    const REJECT_PATTERN = /\b(?:originally performed|karaoke|backing track|in the style of|made famous by|made popular by|tribute to)\b/i;

    // Modern mismatch patterns — always penalise (never wanted from Spotify source)
    const MODERN_MISMATCH = /\b(?:sped up|slowed|reverb|nightcore|8d audio|8d|bass boosted|bassboosted|daycore|chipmunk)\b/i;

    let best = null;
    let bestScore = -Infinity;
    let bestHasArtistSignal = false;

    for (let i = 0; i < candidates.length; i += 1) {
      const candidate = candidates[i];
      if (!candidate || !candidate.videoId) continue;
      if (_excludeSet && _excludeSet.indexOf(candidate.videoId) !== -1) continue;
      if (candidate.isLive) continue;

      const durationSec = Number(candidate.durationSec) || 0;
      if (durationSec > 0 && durationSec < 45) continue;

      // Max duration ceiling — reject full-album uploads etc.
      const expected = Number(expectedDurationSec) || 0;
      if (expected > 0 && durationSec > 0 && durationSec > expected * 3) continue;

      const text = `${candidate.title || ''} ${candidate.author || ''}`.trim();

      // Hard reject karaoke, "originally performed by", tribute, etc.
      if (REJECT_PATTERN.test(text)) continue;

      const normText = normalizeSearchText(text);
      const tokens = tokenizeSearchText(text);

      let score = 0;

      // --- Title & artist matching ---
      const titleMatch = titleNorm && normText.includes(titleNorm);
      const primaryArtistMatch = primaryArtistNorm && normText.includes(primaryArtistNorm);
      const artistMatch = artistNorm && artistNorm !== primaryArtistNorm && normText.includes(artistNorm);

      if (titleMatch) score += 10;
      else score -= 8;

      if (primaryArtistMatch) score += 8;
      if (artistMatch) score += 3;
      if (artistRequired && !(primaryArtistMatch || artistMatch)) score -= 7;

      for (let t = 0; t < wantedTokens.length; t += 1) {
        if (tokens.includes(wantedTokens[t])) score += 1;
      }

      // --- Channel quality (tiered) ---
      const authorText = String(candidate.author || '').trim();
      const authorNorm = normalizeSearchText(authorText);
      const isTopicChannel = /\s-\sTopic$/i.test(authorText);
      const isVevoChannel = /VEVO$/i.test(authorText);

      var channelMatchesPrimary = false;
      var channelMatchesArtist = false;

      if (isTopicChannel) {
        // Tier 1a: Topic channel — exact studio recording, best possible match
        score += 14;
        channelMatchesPrimary = primaryArtistNorm && authorNorm.includes(primaryArtistNorm);
        channelMatchesArtist = artistNorm && authorNorm.includes(artistNorm);
        if (channelMatchesPrimary) score += 6;
        else if (channelMatchesArtist) score += 4;
      } else if (isVevoChannel) {
        // Tier 1b: VEVO — official music video, reliable but may differ from album audio
        score += 10;
        channelMatchesPrimary = primaryArtistNorm && authorNorm.includes(primaryArtistNorm);
        channelMatchesArtist = artistNorm && authorNorm.includes(artistNorm);
        if (channelMatchesPrimary) score += 6;
        else if (channelMatchesArtist) score += 4;
      } else {
        // Tier 2: Artist's own channel (not Topic/VEVO)
        channelMatchesPrimary = primaryArtistNorm && authorNorm.includes(primaryArtistNorm);
        channelMatchesArtist = artistNorm && authorNorm.includes(artistNorm);
        if (channelMatchesPrimary) {
          score += 7;
          if (candidate.verified) score += 2;
        } else if (channelMatchesArtist) {
          score += 4;
          if (candidate.verified) score += 1;
        }

        // Bonus for official audio/video in title
        if (/official audio|official video/i.test(text)) {
          score += 4;
        } else if (/\baudio\b|\blyrics\b/i.test(text)) {
          score += 2;
        }
      }

      // --- Content penalties (conditional on Spotify track title) ---
      // Only penalise these terms if the Spotify track title does NOT
      // contain them — e.g. a remix album should match remix results.
      var contentWords = ['cover', 'remix', 'live', 'instrumental', 'symphonic', 'orchestra', 'orchestral', 'acoustic'];
      for (var cw = 0; cw < contentWords.length; cw += 1) {
        var word = contentWords[cw];
        var re = new RegExp('\\b' + word + '\\b', 'i');
        if (re.test(text) && !re.test(trackTitleLower)) score -= 8;
      }

      // Always penalise — never legitimate from a Spotify source
      if (/\b(?:reaction|trailer|commercial)\b/i.test(text)) score -= 8;

      // Modern YouTube mismatch junk — always penalise
      if (MODERN_MISMATCH.test(text)) score -= 10;

      // Featured-artist mismatch — if the YouTube title has "ft." or "feat."
      // but the Spotify title does not, this is likely a different arrangement,
      // collaboration, or remix (e.g. "ONE DAY ft. Andel and Nalyn" vs "One Day").
      var ytHasFeat = /\b(?:feat\.?|ft\.?|featuring)\b/i.test(String(candidate.title || ''));
      var spotifyHasFeat = /\b(?:feat\.?|ft\.?|featuring)\b/i.test(trackTitleLower);
      if (ytHasFeat && !spotifyHasFeat) score -= 5;

      // Extra-artist mismatch — YouTube titles often list collaborators as
      // comma or ampersand-separated names before the " - " delimiter, e.g.
      // "Rascal Flatts, Lzzy Hale - Life Is A Highway".  If the Spotify
      // metadata has no knowledge of the extra artist(s), this is a different
      // version (duet, collab, remix) and should be penalised.
      var ytTitle = String(candidate.title || '');
      var dashIdx = ytTitle.indexOf(' - ');
      if (dashIdx > 0) {
        var artistPart = ytTitle.substring(0, dashIdx);
        // Split on comma, &, x, × — common multi-artist separators
        var ytArtists = artistPart.split(/\s*[,&x\u00d7]\s*/i).map(function (s) { return s.trim().toLowerCase(); }).filter(Boolean);
        if (ytArtists.length > 1) {
          var spotifyArtistLower = String(trackArtist || '').toLowerCase();
          var extraArtistFound = false;
          for (var ea = 0; ea < ytArtists.length; ea += 1) {
            var ytA = ytArtists[ea];
            if (ytA.length < 2) continue; // skip single-char fragments
            if (spotifyArtistLower.indexOf(ytA) === -1 && trackTitleLower.indexOf(ytA) === -1) {
              extraArtistFound = true;
              break;
            }
          }
          if (extraArtistFound) score -= 6;
        }
      }

      // --- Duration matching ---
      if (expected > 0 && durationSec > 0) {
        const ratio = Math.abs(durationSec - expected) / expected;
        if (ratio <= 0.08) score += 8;
        else if (ratio <= 0.15) score += 5;
        else if (ratio <= 0.25) score += 2;
        else if (ratio >= 0.5) score -= 5;
      }

      // --- Global bonuses ---
      if (candidate.verified) score += 1.5;
      score += Math.min(2, Math.log10(Math.max(1, Number(candidate.views) || 0)) / 5);

      // Track whether this candidate has ANY artist/channel signal
      var hasArtistSignal = primaryArtistMatch || artistMatch
        || channelMatchesPrimary || channelMatchesArtist;

      if (score > bestScore) {
        bestScore = score;
        best = candidate;
        bestHasArtistSignal = hasArtistSignal;
      }
    }

    if (!best) return null;

    // --- Acceptance threshold ---
    // When artist info is known, REQUIRE at least one artist or channel
    // match.  This prevents "right title, wrong artist" false positives
    // (e.g. "Stay" by The Kid LAROI matching "Stay" by Rihanna).
    if (artistRequired && !bestHasArtistSignal) return null;

    // Dynamic minimum score — higher-quality Spotify metadata means we
    // can afford to be stricter; lower quality means we cast a wider net.
    var mq = Number(metaQuality) || 0;
    var minScore;
    if (!artistRequired) {
      // Title-only search — be strict
      minScore = 11;
    } else if (mq >= 10) {
      // Full metadata (title + artist + duration + art) — be strict
      minScore = 10;
    } else if (mq >= 7) {
      // Good metadata — moderate
      minScore = 8;
    } else {
      // Sparse metadata — more lenient
      minScore = 6;
    }
    if (bestScore < minScore) return null;

    return {
      videoId: best.videoId,
      title: String(best.title || ''),
      author: String(best.author || ''),
      durationSec: Number(best.durationSec) || 0,
      _score: bestScore,
    };
  }

  function extractYouTubeSearchCandidates(html) {
    const blocks = extractJsonBlocksByKey(String(html || ''), '"videoRenderer":', 60);
    const results = [];

    for (let i = 0; i < blocks.length; i += 1) {
      const renderer = safeJsonParse(blocks[i]);
      if (!renderer || typeof renderer !== 'object') continue;

      const videoId = String(renderer.videoId || '').trim();
      if (!/^[A-Za-z0-9_-]{11}$/.test(videoId)) continue;

      const title = readYouTubeText(renderer.title);
      const author = readYouTubeText(renderer.ownerText);
      const durationLabel = readYouTubeText(renderer.lengthText);
      const durationSec = parseDurationText(durationLabel);
      const isLive = /live/i.test(String(durationLabel || '')) || Boolean(renderer.isLive);

      results.push({
        videoId,
        title,
        author,
        durationSec,
        views: 0,
        isLive,
        verified: false,
      });
    }

    return results;
  }

  function extractJsonBlocksByKey(text, key, maxCount) {
    const source = String(text || '');
    const found = [];

    let cursor = 0;
    const limit = Number(maxCount) > 0 ? Number(maxCount) : 40;

    while (cursor < source.length && found.length < limit) {
      const keyIndex = source.indexOf(key, cursor);
      if (keyIndex === -1) break;

      const braceStart = source.indexOf('{', keyIndex + key.length);
      if (braceStart === -1) break;

      const block = readBalancedJsonObject(source, braceStart);
      if (!block) {
        cursor = keyIndex + key.length;
        continue;
      }

      found.push(block.json);
      cursor = block.endIndex;
    }

    return found;
  }

  function readBalancedJsonObject(text, startIndex) {
    const source = String(text || '');

    let depth = 0;
    let inString = false;
    let escaped = false;

    for (let i = startIndex; i < source.length; i += 1) {
      const ch = source[i];

      if (inString) {
        if (escaped) {
          escaped = false;
        } else if (ch === '\\') {
          escaped = true;
        } else if (ch === '"') {
          inString = false;
        }
        continue;
      }

      if (ch === '"') {
        inString = true;
        continue;
      }

      if (ch === '{') {
        depth += 1;
        continue;
      }

      if (ch === '}') {
        depth -= 1;
        if (depth === 0) {
          return {
            json: source.slice(startIndex, i + 1),
            endIndex: i + 1,
          };
        }
      }
    }

    return null;
  }

  function readYouTubeText(node) {
    if (!node) return '';
    if (typeof node === 'string') return node;
    if (typeof node.simpleText === 'string') return node.simpleText;

    if (Array.isArray(node.runs)) {
      return node.runs
        .map((entry) => (entry && typeof entry.text === 'string' ? entry.text : ''))
        .filter(Boolean)
        .join('')
        .trim();
    }

    return '';
  }

  function extractVideoIdFromUrl(url) {
    const value = String(url || '').trim();
    if (!value) return '';

    const direct = value.match(/[?&]v=([A-Za-z0-9_-]{11})/);
    if (direct) return direct[1];

    const short = value.match(/(?:youtu\.be\/|\/shorts\/)([A-Za-z0-9_-]{11})/);
    if (short) return short[1];

    const cleanPath = value.match(/\/watch\/([A-Za-z0-9_-]{11})/);
    if (cleanPath) return cleanPath[1];

    return '';
  }

  function normalizeSearchText(value) {
    return String(value || '')
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, ' ')
      .trim();
  }

  function tokenizeSearchText(value) {
    const normalized = normalizeSearchText(value);
    if (!normalized) return [];
    return normalized.split(/\s+/).filter((token) => token.length > 1);
  }

  function extractPrimaryArtist(artistString) {
    const raw = String(artistString || '').trim();
    if (!raw) return '';

    // Always split on feat/ft/featuring (unambiguous collaboration markers)
    const featSplit = /\s*[,;&]\s*|\s+(?:feat\.?|ft\.?|featuring)\s+/i;
    const parts = raw.split(featSplit).map((p) => p.trim()).filter(Boolean);
    const primary = parts[0] || raw;

    // Only split on " and " / " with " / " x " if there are also commas/semicolons
    // present — this avoids breaking "Simon and Garfunkel", "Florence and The Machine"
    if (/[,;&]/.test(raw)) {
      const ambiguousSplit = /\s+(?:with|and|x)\s+/i;
      const subParts = primary.split(ambiguousSplit).map((p) => p.trim()).filter(Boolean);
      return subParts[0] || primary;
    }

    return primary;
  }

  function dedupeStrings(values) {
    if (!Array.isArray(values)) return [];

    const seen = new Set();
    const result = [];

    for (let i = 0; i < values.length; i += 1) {
      const raw = String(values[i] || '').trim();
      if (!raw) continue;

      const key = raw.toLowerCase();
      if (seen.has(key)) continue;

      seen.add(key);
      result.push(raw);
    }

    return result;
  }

  function isPlaceholderTitle(value) {
    const norm = normalizeSearchText(value);
    if (!norm) return true;
    if (norm === 'spotify') return true;
    if (norm === 'unknown track') return true;
    return false;
  }

  function isPlaceholderArtist(value) {
    const norm = normalizeSearchText(value);
    if (!norm) return true;
    if (norm === 'spotify') return true;
    if (norm === 'unknown artist') return true;
    if (norm === 'various artists') return true;
    return false;
  }

  function hasUsableTitle(value) {
    return !isPlaceholderTitle(value);
  }

  function hasUsableArtist(value) {
    return !isPlaceholderArtist(value);
  }

  function getTrackMetaQuality(meta) {
    if (!meta || typeof meta !== 'object') return 0;

    let score = 0;
    if (String(meta.uri || '').trim()) score += 1;
    if (hasUsableTitle(meta.title)) score += 4;
    if (hasUsableArtist(meta.artist)) score += 5;
    if (Number(meta.durationMs) > 0) score += 2;
    if (String(meta.art || '').trim()) score += 1;
    return score;
  }

  function isStrongTrackMeta(meta) {
    if (!meta) return false;
    return hasUsableTitle(meta.title) && hasUsableArtist(meta.artist);
  }

  function chooseMetaField(primaryValue, fallbackValue, isUsable) {
    const primary = String(primaryValue || '').trim();
    const fallback = String(fallbackValue || '').trim();
    if (isUsable(primary)) return primary;
    if (isUsable(fallback)) return fallback;
    return primary || fallback;
  }

  function chooseBetterTrackMeta(current, candidate) {
    if (!current) return candidate || null;
    if (!candidate) return current;

    const currentQuality = getTrackMetaQuality(current);
    const candidateQuality = getTrackMetaQuality(candidate);

    if (candidateQuality > currentQuality) {
      return mergeTrackMeta(candidate, current);
    }
    return mergeTrackMeta(current, candidate);
  }

  async function resolveTrackMetaForUri(uri) {
    const key = String(uri || '').trim();
    if (!key) return null;

    // Non-Spotify URIs cannot be resolved via Spotify APIs — their metadata
    // is populated by platform-specific fetchers in buildQueue* functions.
    if (!key.startsWith('spotify:')) return null;

    const cached = state.metaCache.has(key) ? state.metaCache.get(key) : null;
    if (cached && isStrongTrackMeta(cached)) {
      return cached;
    }
    if (cached && !isStrongTrackMeta(cached)) {
      const retryAt = Number(state.metaRetryAt.get(key) || 0);
      if (Date.now() < retryAt) {
        return cached;
      }
      state.metaRetryAt.set(key, Date.now() + WEAK_META_RETRY_MS);
    }

    const debug = [];
    let resolved = cached ? mergeTrackMeta(cached, null) : null;

    const fromApi = await fetchTrackMetaFromSpotifyApi(key);
    if (fromApi) {
      resolved = chooseBetterTrackMeta(resolved, fromApi);
    }
    debug.push({ source: 'spotify_api', ok: Boolean(fromApi), quality: getTrackMetaQuality(fromApi) });

    if (!isStrongTrackMeta(resolved) || !Number(resolved && resolved.durationMs)) {
      const fromPage = await fetchTrackMetaFromOpenPage(key);
      if (fromPage) {
        resolved = chooseBetterTrackMeta(resolved, fromPage);
      }
      debug.push({ source: 'open_page', ok: Boolean(fromPage), quality: getTrackMetaQuality(fromPage) });
    }

    if (!isStrongTrackMeta(resolved) || !Number(resolved && resolved.durationMs)) {
      const fromEmbed = await fetchTrackMetaFromEmbedPage(key);
      if (fromEmbed) {
        resolved = chooseBetterTrackMeta(resolved, fromEmbed);
      }
      debug.push({ source: 'embed_page', ok: Boolean(fromEmbed), quality: getTrackMetaQuality(fromEmbed) });
    }

    if (!resolved || !hasUsableTitle(resolved.title) || !String(resolved.art || '').trim()) {
      const fromOEmbed = await fetchTrackMetaFromOEmbed(key);
      if (fromOEmbed) {
        resolved = chooseBetterTrackMeta(resolved, fromOEmbed);
      }
      debug.push({ source: 'oembed', ok: Boolean(fromOEmbed), quality: getTrackMetaQuality(fromOEmbed) });
    }

    if (!resolved) {
      resolved = {
        uri: key,
        title: 'Spotify',
        artist: '',
        art: '',
        durationMs: 0,
        openUrl: spotifyUriToOpenUrl(key),
      };
    }

    resolved = mergeTrackMeta(resolved, {
      uri: key,
      openUrl: spotifyUriToOpenUrl(key),
    });

    if (!cached || getTrackMetaQuality(resolved) >= getTrackMetaQuality(cached)) {
      state.metaCache.set(key, resolved);
    } else {
      resolved = cached;
    }

    if (isStrongTrackMeta(resolved)) {
      state.metaRetryAt.delete(key);
    }

    return resolved;
  }

  function mergeTrackMeta(primary, fallback) {
    const first = primary || {};
    const second = fallback || {};

    return {
      uri: String(first.uri || second.uri || ''),
      title: chooseMetaField(first.title, second.title, hasUsableTitle) || 'Spotify',
      artist: chooseMetaField(first.artist, second.artist, hasUsableArtist),
      art: chooseMetaField(first.art, second.art, (value) => Boolean(String(value || '').trim())),
      durationMs: Number(first.durationMs) || Number(second.durationMs) || 0,
      openUrl: String(first.openUrl || second.openUrl || ''),
    };
  }

  async function fetchTrackMetaFromSpotifyApi(trackUri) {
    const parsed = parseSpotifyInput(trackUri);
    if (!parsed || parsed.type !== 'track') return null;

    const payload = await spotifyApiGet(
      `https://api.spotify.com/v1/tracks/${encodeURIComponent(parsed.id)}?market=from_token`
    );
    if (!payload) return null;

    const parsedTrack = parseSpotifyApiTrack(payload);
    if (!parsedTrack) return null;

    return {
      uri: parsedTrack.uri,
      title: parsedTrack.title,
      artist: parsedTrack.artist,
      art: parsedTrack.art,
      durationMs: parsedTrack.durationMs,
      openUrl: parsedTrack.openUrl,
    };
  }

  async function fetchTrackMetaFromOEmbed(trackUri) {
    const openUrl = spotifyUriToOpenUrl(trackUri);
    if (!openUrl) return null;

    try {
      const response = await gmRequest({
        method: 'GET',
        url: `${OEMBED_BASE}${encodeURIComponent(openUrl)}`,
        headers: {
          Accept: 'application/json',
        },
      });

      if (response.status < 200 || response.status >= 300) return null;

      const data = safeJsonParse(response.responseText) || {};
      const parsedTitle = parseOembedTitle(String(data.title || ''));
      const parsedArtist = chooseMetaField(parsedTitle.artist, String(data.author_name || ''), hasUsableArtist);

      return {
        uri: String(trackUri),
        title: parsedTitle.title || 'Spotify',
        artist: parsedArtist || '',
        art: String(data.thumbnail_url || ''),
        durationMs: 0,
        openUrl,
      };
    } catch (error) {
      return null;
    }
  }

  async function fetchTrackMetaFromOpenPage(trackUri) {
    const openUrl = spotifyUriToOpenUrl(trackUri);
    if (!openUrl) return null;

    try {
      const response = await gmRequest({
        method: 'GET',
        url: openUrl,
        headers: {
          Accept: 'text/html',
        },
      });

      if (response.status < 200 || response.status >= 300) return null;

      const html = String(response.responseText || '');
      return parseSpotifyTrackMetaFromHtml(html, trackUri, openUrl);
    } catch (error) {
      return null;
    }
  }

  async function fetchTrackMetaFromEmbedPage(trackUri) {
    const parsed = parseSpotifyInput(trackUri);
    if (!parsed || parsed.type !== 'track') return null;

    const embedUrl = `https://open.spotify.com/embed/track/${parsed.id}`;

    try {
      const response = await gmRequest({
        method: 'GET',
        url: embedUrl,
        headers: {
          Accept: 'text/html',
        },
        timeout: 12000,
      });

      if (response.status < 200 || response.status >= 300) return null;

      const html = String(response.responseText || '');
      const payloads = extractSpotifyStatePayloads(html);
      let best = null;

      for (let i = 0; i < payloads.length; i += 1) {
        const decoded = decodeSpotifyStatePayload(payloads[i]);
        if (!decoded) continue;

        const parsedTracks = extractTracksFromStateObject(decoded);
        for (let t = 0; t < parsedTracks.length; t += 1) {
          const candidate = parsedTracks[t];
          if (!candidate || String(candidate.uri || '').trim() !== trackUri) continue;
          best = chooseBetterTrackMeta(best, candidate);
        }
      }

      if (!best) {
        const fallbackTracks = extractTrackLinksFromEmbedHtml(html);
        const fallback = fallbackTracks.find((entry) => String(entry.uri || '').trim() === trackUri) || null;
        best = chooseBetterTrackMeta(best, fallback);
      }

      if (!best) return null;

      return mergeTrackMeta(best, {
        uri: trackUri,
        openUrl: spotifyUriToOpenUrl(trackUri),
      });
    } catch (error) {
      return null;
    }
  }

  function parseSpotifyTrackMetaFromHtml(html, trackUri, openUrl) {
    if (!html) return null;

    try {
      const doc = new DOMParser().parseFromString(String(html), 'text/html');

      const ogTitle = getMetaContent(doc, 'og:title', true);
      const ogDescription = getMetaContent(doc, 'og:description', true);
      const description = getMetaContent(doc, 'description', false);
      const musicDuration = Number(getMetaContent(doc, 'music:duration', false));
      const ogImage = getMetaContent(doc, 'og:image', true);
      const titleTag = String(doc.title || '').trim();

      const parsedFromDescription = parseArtistTitleFromSpotifyDescription(ogDescription || description);
      const parsedFromTitle = parseSpotifyDocumentTitle(titleTag);

      const title = firstNonEmptyString([
        ogTitle,
        parsedFromDescription && parsedFromDescription.title,
        parsedFromTitle && parsedFromTitle.title,
      ]) || 'Spotify';

      const artist = firstNonEmptyString([
        parsedFromDescription && parsedFromDescription.artist,
        parsedFromTitle && parsedFromTitle.artist,
      ]) || '';

      return {
        uri: String(trackUri || ''),
        title,
        artist,
        art: String(ogImage || ''),
        durationMs: Number.isFinite(musicDuration) && musicDuration > 0 ? musicDuration * 1000 : 0,
        openUrl: String(openUrl || ''),
      };
    } catch (error) {
      return null;
    }
  }

  function parseOembedTitle(rawTitle) {
    const cleaned = String(rawTitle || '').replace(/\s*\|\s*Spotify\s*$/i, '').trim();

    const songMatch = cleaned.match(/^(.*?)\s*-\s*song and lyrics by\s*(.*)$/i);
    if (songMatch) {
      return {
        title: songMatch[1].trim(),
        artist: songMatch[2].trim(),
      };
    }

    const byMatch = cleaned.match(/^(.*?)\s+by\s+(.*)$/i);
    if (byMatch) {
      return {
        title: byMatch[1].trim(),
        artist: byMatch[2].trim(),
      };
    }

    return {
      title: cleaned,
      artist: '',
    };
  }

  function parseArtistTitleFromSpotifyDescription(text) {
    const raw = String(text || '').trim();
    if (!raw) return null;

    const parts = raw.split(/\s*·\s*/).map((part) => part.trim()).filter(Boolean);
    if (parts.length >= 2) {
      return {
        artist: parts[0],
        title: parts[1],
      };
    }

    const listenSongMatch = raw.match(/^Listen to (.+?) on Spotify\.\s*Song\s*[·-]\s*(.+?)(?:\s*[·-]|$)/i);
    if (listenSongMatch) {
      return {
        title: listenSongMatch[1].trim(),
        artist: listenSongMatch[2].trim(),
      };
    }

    const commaSongMatch = raw.match(/^(.+?),\s*a\s+song\s+by\s+(.+?)\s+on Spotify/i);
    if (commaSongMatch) {
      return {
        title: commaSongMatch[1].trim(),
        artist: commaSongMatch[2].trim(),
      };
    }

    return null;
  }

  function parseSpotifyDocumentTitle(text) {
    const raw = String(text || '').trim();
    if (!raw) return null;

    const stripped = raw.replace(/\s*\|\s*Spotify\s*$/i, '').trim();

    const match = stripped.match(/^(.*?)\s*-\s*song and lyrics by\s*(.*?)$/i);
    if (match) {
      return {
        title: match[1].trim(),
        artist: match[2].trim(),
      };
    }

    const genericByMatch = stripped.match(/^(.*?)\s*-\s*(?:song|single|album|playlist)\s+by\s+(.*?)$/i);
    if (genericByMatch) {
      return {
        title: genericByMatch[1].trim(),
        artist: genericByMatch[2].trim(),
      };
    }

    return null;
  }

  function getMetaContent(doc, key, isProperty) {
    if (!doc || !key) return '';

    const selector = isProperty
      ? `meta[property="${cssEscapeForQuery(key)}"]`
      : `meta[name="${cssEscapeForQuery(key)}"]`;

    const node = doc.querySelector(selector);
    if (!node) return '';

    return String(node.getAttribute('content') || '').trim();
  }

  function cssEscapeForQuery(value) {
    return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  }

  async function fetchSpotifyTrackList(type, id, sourceUri) {
    const cacheKey = `${sourceUri}::tracks`;
    if (state.sourceTrackListCache.has(cacheKey)) {
      return state.sourceTrackListCache.get(cacheKey);
    }

    let tracks = await fetchSpotifyTrackListFromApi(type, id, sourceUri);

    // Middle fallback: scrape the full open.spotify.com page (may have more
    // tracks than the embed page for some playlists)
    if (!tracks.length) {
      tracks = await fetchSpotifyTrackListFromOpenPage(type, id, sourceUri);
    }

    if (!tracks.length) {
      tracks = await fetchSpotifyTrackListFromEmbedPage(type, id, sourceUri);
    }

    if (tracks.length) {
      state.sourceTrackListCache.set(cacheKey, tracks);
    }

    if (tracks.length >= 100 && state.spotifyIsAnonymous && type === 'playlist') {
      window.setTimeout(function () {
        toast('Loaded ' + tracks.length + ' tracks. Log in to open.spotify.com for full playlist access.');
      }, 2000);
    }

    return tracks;
  }

  async function fetchSpotifyTrackListFromApi(type, id, sourceUri) {
    const tracks = [];

    if (type === 'track') {
      const meta = await resolveTrackMetaForUri(`spotify:track:${id}`);
      if (!meta) return [];

      return [
        {
          uri: meta.uri,
          title: meta.title,
          artist: meta.artist,
          art: meta.art,
          durationMs: meta.durationMs,
          openUrl: meta.openUrl,
          sourceUri,
        },
      ];
    }

    if (type === 'playlist') {
      let nextUrl = `https://api.spotify.com/v1/playlists/${encodeURIComponent(id)}/tracks?market=from_token&limit=100&offset=0`;
      let safety = 0;

      while (nextUrl && safety < 60) {
        const payload = await spotifyApiGet(nextUrl);
        if (!payload) break;

        const items = Array.isArray(payload.items) ? payload.items : [];
        items.forEach((item) => {
          const parsedTrack = parseSpotifyApiTrack(item && item.track ? item.track : item);
          if (!parsedTrack) return;

          tracks.push({
            uri: parsedTrack.uri,
            title: parsedTrack.title,
            artist: parsedTrack.artist,
            art: parsedTrack.art,
            durationMs: parsedTrack.durationMs,
            openUrl: parsedTrack.openUrl,
            sourceUri,
          });
        });

        nextUrl = typeof payload.next === 'string' ? payload.next : '';
        safety += 1;
      }

      return dedupeTracksByUri(tracks);
    }

    if (type === 'album') {
      const albumMeta = await spotifyApiGet(
        `https://api.spotify.com/v1/albums/${encodeURIComponent(id)}?market=from_token&fields=images,tracks.items(uri,name,duration_ms,artists(name)),tracks.next`
      );
      if (!albumMeta) return [];

      const albumArt = pickImageUrl(albumMeta.images);

      const addItems = (items) => {
        if (!Array.isArray(items)) return;
        items.forEach((item) => {
          const parsedTrack = parseSpotifyApiTrack(item, albumArt);
          if (!parsedTrack) return;

          tracks.push({
            uri: parsedTrack.uri,
            title: parsedTrack.title,
            artist: parsedTrack.artist,
            art: parsedTrack.art,
            durationMs: parsedTrack.durationMs,
            openUrl: parsedTrack.openUrl,
            sourceUri,
          });
        });
      };

      addItems(albumMeta.tracks && albumMeta.tracks.items);

      let nextUrl = albumMeta.tracks && typeof albumMeta.tracks.next === 'string' ? albumMeta.tracks.next : '';
      let safety = 0;

      while (nextUrl && safety < 60) {
        const payload = await spotifyApiGet(nextUrl);
        if (!payload) break;

        addItems(payload.items);
        nextUrl = typeof payload.next === 'string' ? payload.next : '';
        safety += 1;
      }

      return dedupeTracksByUri(tracks);
    }

    return [];
  }

  async function fetchSpotifyTrackListFromOpenPage(type, id, sourceUri) {
    const openUrl = `https://open.spotify.com/${type}/${id}`;

    try {
      const response = await gmRequest({
        method: 'GET',
        url: openUrl,
        headers: {
          Accept: 'text/html',
        },
        timeout: 15000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      const html = String(response.responseText || '');
      const payloads = extractSpotifyStatePayloads(html);
      let parsedTracks = [];

      for (let i = 0; i < payloads.length; i += 1) {
        const decoded = decodeSpotifyStatePayload(payloads[i]);
        if (!decoded) continue;
        parsedTracks = extractTracksFromStateObject(decoded);
        if (parsedTracks.length) break;
      }

      if (!parsedTracks.length) {
        parsedTracks = extractTrackLinksFromEmbedHtml(html);
      }

      const tracks = parsedTracks.map((track) => ({
        uri: track.uri,
        title: String(track.title || 'Spotify'),
        artist: String(track.artist || ''),
        art: String(track.art || ''),
        durationMs: Number(track.durationMs) || 0,
        openUrl: String(track.openUrl || spotifyUriToOpenUrl(track.uri)),
        sourceUri,
      }));

      return dedupeTracksByUri(tracks);
    } catch (error) {
      return [];
    }
  }

  async function fetchSpotifyTrackListFromEmbedPage(type, id, sourceUri) {
    const embedUrl = `https://open.spotify.com/embed/${type}/${id}`;

    try {
      const response = await gmRequest({
        method: 'GET',
        url: embedUrl,
        headers: {
          Accept: 'text/html',
        },
        timeout: 12000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      const html = String(response.responseText || '');
      const payloads = extractSpotifyStatePayloads(html);
      let parsedTracks = [];

      for (let i = 0; i < payloads.length; i += 1) {
        const payload = payloads[i];
        const decoded = decodeSpotifyStatePayload(payload);
        if (!decoded) continue;

        parsedTracks = extractTracksFromStateObject(decoded);
        if (parsedTracks.length) break;
      }

      if (!parsedTracks.length) {
        parsedTracks = extractTrackLinksFromEmbedHtml(html);
      }

      const tracks = parsedTracks.map((track) => ({
        uri: track.uri,
        title: String(track.title || 'Spotify'),
        artist: String(track.artist || ''),
        art: String(track.art || ''),
        durationMs: Number(track.durationMs) || 0,
        openUrl: String(track.openUrl || spotifyUriToOpenUrl(track.uri)),
        sourceUri,
      }));

      return dedupeTracksByUri(tracks);
    } catch (error) {
      return [];
    }
  }

  function dedupeTracksByUri(tracks) {
    if (!Array.isArray(tracks)) return [];

    const seen = new Set();
    const result = [];

    tracks.forEach((track) => {
      if (!track || !track.uri) return;
      if (seen.has(track.uri)) return;

      seen.add(track.uri);
      result.push(track);
    });

    return result;
  }

  function extractSpotifyStatePayloads(html) {
    const payloads = [];

    try {
      const doc = new DOMParser().parseFromString(String(html || ''), 'text/html');

      const directSelectors = [
        'script#__NEXT_DATA__',
        'script#initial-state',
        'script#initialState',
        'script[data-testid="initial-state"]',
        'script#appServerState',
        'script#__APP_STATE__',
      ];

      directSelectors.forEach((selector) => {
        const node = doc.querySelector(selector);
        if (!node || !node.textContent) return;

        const payload = String(node.textContent || '').trim();
        if (payload) payloads.push(payload);
      });

      const scriptNodes = doc.querySelectorAll('script[data-type="resource"], script');
      scriptNodes.forEach((node) => {
        const content = String((node && node.textContent) || '').trim();
        if (!content) return;

        if (/__NEXT_DATA__|"spotify:track:"|%22spotify%3Atrack%3A|"trackList"|"items"/i.test(content)) {
          payloads.push(content);
        }
      });
    } catch (error) {
      // noop
    }

    return dedupeStrings(payloads);
  }

  function decodeSpotifyStatePayload(payload) {
    const raw = String(payload || '').trim();
    if (!raw) return null;

    const direct = safeJsonParse(raw);
    if (direct) return direct;

    const extractedJson = extractJsonObjectFromText(raw);
    if (extractedJson) {
      const parsedExtracted = safeJsonParse(extractedJson);
      if (parsedExtracted) return parsedExtracted;
    }

    const decodedBase64 = decodeBase64ToText(raw);
    if (decodedBase64) {
      const parsedBase64 = safeJsonParse(decodedBase64);
      if (parsedBase64) return parsedBase64;

      const utf8Decoded = decodeLatin1AsUtf8(decodedBase64);
      if (utf8Decoded) {
        const parsedUtf8 = safeJsonParse(utf8Decoded);
        if (parsedUtf8) return parsedUtf8;
      }
    }

    const urlDecoded = tryDecodeURIComponent(raw);
    if (urlDecoded && urlDecoded !== raw) {
      const parsedUrlDecoded = safeJsonParse(urlDecoded);
      if (parsedUrlDecoded) return parsedUrlDecoded;

      const parsedUrlExtracted = safeJsonParse(extractJsonObjectFromText(urlDecoded));
      if (parsedUrlExtracted) return parsedUrlExtracted;
    }

    return null;
  }

  function tryDecodeURIComponent(value) {
    const raw = String(value || '');
    if (!raw) return '';

    try {
      return decodeURIComponent(raw);
    } catch (error) {
      return raw;
    }
  }

  function extractJsonObjectFromText(text) {
    const source = String(text || '');
    if (!source) return '';

    const firstBrace = source.indexOf('{');
    if (firstBrace === -1) return '';

    const block = readBalancedJsonObject(source, firstBrace);
    return block ? block.json : '';
  }

  function decodeBase64ToText(input) {
    const normalized = String(input || '')
      .replace(/[\r\n\s]/g, '')
      .replace(/-/g, '+')
      .replace(/_/g, '/');

    if (!normalized) return '';

    let padded = normalized;
    if (padded.length % 4) {
      padded = `${padded}${'='.repeat(4 - (padded.length % 4))}`;
    }

    try {
      return atob(padded);
    } catch (error) {
      return '';
    }
  }

  function decodeLatin1AsUtf8(text) {
    const raw = String(text || '');
    if (!raw) return '';

    try {
      let encoded = '';
      for (let i = 0; i < raw.length; i += 1) {
        encoded += `%${raw.charCodeAt(i).toString(16).padStart(2, '0')}`;
      }
      return decodeURIComponent(encoded);
    } catch (error) {
      return '';
    }
  }

  function extractTracksFromStateObject(root) {
    if (!root || typeof root !== 'object') return [];

    const seenObjects = new Set();
    const stack = [root];
    const tracksByUri = new Map();

    let safety = 0;
    while (stack.length && safety < 200000) {
      const value = stack.pop();
      safety += 1;

      if (!value || typeof value !== 'object') continue;
      if (seenObjects.has(value)) continue;
      seenObjects.add(value);

      const parsedTrack = parseTrackLikeObject(value);
      if (parsedTrack && !tracksByUri.has(parsedTrack.uri)) {
        tracksByUri.set(parsedTrack.uri, parsedTrack);
      }

      if (Array.isArray(value)) {
        for (let i = value.length - 1; i >= 0; i -= 1) {
          const child = value[i];
          if (child && typeof child === 'object') stack.push(child);
        }
        continue;
      }

      const keys = Object.keys(value);
      for (let i = keys.length - 1; i >= 0; i -= 1) {
        const child = value[keys[i]];
        if (child && typeof child === 'object') stack.push(child);
      }
    }

    return Array.from(tracksByUri.values());
  }

  function parseTrackLikeObject(value) {
    if (!value || typeof value !== 'object') return null;

    const uri = String(value.uri || '').trim();
    if (!/^spotify:track:[A-Za-z0-9]{22}$/.test(uri)) return null;

    const title = firstNonEmptyString([
      value.name,
      value.title,
      value.track_name,
      value.trackName,
      value.track_title,
    ]) || 'Spotify';
    const artist = extractArtistText(value);
    const art = pickImageUrl(
      value.images ||
      (value.coverArt && value.coverArt.sources) ||
      (value.album && value.album.images) ||
      (value.albumOfTrack && value.albumOfTrack.coverArt && value.albumOfTrack.coverArt.sources) ||
      (value.visuals && value.visuals.coverArt && value.visuals.coverArt.sources)
    );

    return {
      uri,
      title,
      artist,
      art,
      durationMs: readDurationMs(value),
      openUrl: spotifyUriToOpenUrl(uri),
    };
  }

  function parseSpotifyApiTrack(track, fallbackArt) {
    if (!track || typeof track !== 'object') return null;

    const uri = String(track.uri || '').trim();
    if (!/^spotify:track:[A-Za-z0-9]{22}$/.test(uri)) return null;

    return {
      uri,
      title: firstNonEmptyString([track.name, track.title]) || 'Spotify',
      artist: extractArtistText(track),
      art: pickImageUrl((track.album && track.album.images) || track.images) || String(fallbackArt || ''),
      durationMs: readDurationMs(track),
      openUrl: spotifyUriToOpenUrl(uri),
    };
  }

  function extractTrackLinksFromEmbedHtml(html) {
    const source = String(html || '');
    if (!source) return [];

    const tracks = [];
    const seen = new Set();

    const re = /spotify:track:([A-Za-z0-9]{22})/g;
    let match;
    while ((match = re.exec(source))) {
      const uri = `spotify:track:${match[1]}`;
      if (seen.has(uri)) continue;
      seen.add(uri);
      tracks.push({
        uri,
        title: 'Spotify',
        artist: '',
        art: '',
        durationMs: 0,
        openUrl: spotifyUriToOpenUrl(uri),
      });
    }

    return tracks;
  }

  function extractArtistText(value) {
    if (!value || typeof value !== 'object') return '';

    const artists = [];
    const list = [];
    const seen = new Set();

    const addArtist = (raw) => {
      const text = String(raw || '').trim();
      if (!text) return;

      const normalized = text
        .replace(/\s*[·|].*$/, '')
        .replace(/\s*\([^)]*\)\s*$/, '')
        .trim();
      if (!normalized) return;

      const key = normalized.toLowerCase();
      if (seen.has(key)) return;
      seen.add(key);
      artists.push(normalized);
    };

    if (Array.isArray(value.artists)) {
      list.push(...value.artists);
    } else if (value.artists && typeof value.artists === 'object') {
      if (Array.isArray(value.artists.items)) list.push(...value.artists.items);
      if (Array.isArray(value.artists.nodes)) list.push(...value.artists.nodes);
      if (Array.isArray(value.artists.edges)) {
        value.artists.edges.forEach((edge) => {
          if (edge && edge.node) list.push(edge.node);
        });
      }
    }

    if (Array.isArray(value.artist)) {
      list.push(...value.artist);
    } else if (value.artist && typeof value.artist === 'object') {
      if (Array.isArray(value.artist.items)) list.push(...value.artist.items);
      if (value.artist.profile) list.push(value.artist);
    }

    list.forEach((entry) => {
      if (!entry) return;

      if (typeof entry === 'string') {
        addArtist(entry);
        return;
      }

      if (typeof entry.name === 'string' && entry.name.trim()) {
        addArtist(entry.name);
        return;
      }

      const profileName = entry.profile && typeof entry.profile.name === 'string'
        ? entry.profile.name.trim()
        : '';
      if (profileName) addArtist(profileName);
    });

    if (!artists.length && typeof value.artist === 'string' && value.artist.trim()) {
      addArtist(value.artist);
    }

    const directArtistFields = [
      value.artistName,
      value.artist_name,
      value.subtitle,
      value.byline,
      value.ownerName,
    ];

    for (let i = 0; i < directArtistFields.length; i += 1) {
      if (artists.length) break;
      addArtist(directArtistFields[i]);
    }

    if (!artists.length && value.album && typeof value.album === 'object') {
      const albumArtist = extractArtistText(value.album);
      if (albumArtist) addArtist(albumArtist);
    }

    return artists.join(', ');
  }

  async function hydrateQueueEntryMetadata(entry) {
    if (!entry || !entry.spotifyUri) return;

    const needsTitle = !hasUsableTitle(entry.title);
    const needsArtist = !hasUsableArtist(entry.artist);
    const needsArt = !String(entry.art || '').trim();
    const needsDuration = !Number(entry.durationMs);

    if (!needsTitle && !needsArtist && !needsArt && !needsDuration) return;

    const meta = await resolveTrackMetaForUri(entry.spotifyUri);
    if (!meta) return;

    if (needsTitle && hasUsableTitle(meta.title)) entry.title = meta.title;
    if (needsArtist && hasUsableArtist(meta.artist)) entry.artist = meta.artist;
    if (needsArt && meta.art) entry.art = meta.art;
    if (needsDuration && meta.durationMs) entry.durationMs = meta.durationMs;
    if (!entry.openUrl && meta.openUrl) entry.openUrl = meta.openUrl;
  }

  function readDurationMs(value) {
    if (!value || typeof value !== 'object') return 0;

    const direct = [
      value.duration_ms,
      value.durationMs,
      value.trackDurationMs,
      value.lengthMs,
    ];

    for (let i = 0; i < direct.length; i += 1) {
      const parsed = Number(direct[i]);
      if (Number.isFinite(parsed) && parsed > 0) return parsed;
    }

    const duration = value.duration;
    if (typeof duration === 'number' && Number.isFinite(duration) && duration > 0) {
      return duration;
    }

    if (duration && typeof duration === 'object') {
      const nested = [
        duration.totalMilliseconds,
        duration.milliseconds,
        duration.ms,
        duration.total,
      ];

      for (let i = 0; i < nested.length; i += 1) {
        const parsed = Number(nested[i]);
        if (Number.isFinite(parsed) && parsed > 0) return parsed;
      }
    }

    return 0;
  }

  function pickImageUrl(images) {
    if (!images) return '';
    if (typeof images === 'string') return images;

    if (Array.isArray(images)) {
      for (let i = 0; i < images.length; i += 1) {
        const entry = images[i];
        if (!entry) continue;

        if (typeof entry === 'string' && entry) return entry;
        if (typeof entry.url === 'string' && entry.url) return entry.url;
        if (entry.sources) {
          const nested = pickImageUrl(entry.sources);
          if (nested) return nested;
        }
      }
      return '';
    }

    if (typeof images === 'object') {
      if (typeof images.url === 'string' && images.url) return images.url;
      if (images.sources) return pickImageUrl(images.sources);
    }

    return '';
  }

  function firstNonEmptyString(values) {
    if (!Array.isArray(values)) return '';

    for (let i = 0; i < values.length; i += 1) {
      const candidate = values[i];
      if (typeof candidate !== 'string') continue;
      const trimmed = candidate.trim();
      if (trimmed) return trimmed;
    }

    return '';
  }

  async function getSpotifyAccessToken(forceRefresh) {
    if (
      !forceRefresh &&
      state.spotifyAccessToken &&
      Date.now() < Number(state.spotifyAccessTokenExpiresAt || 0) - 15000
    ) {
      return state.spotifyAccessToken;
    }

    try {
      const response = await gmRequest({
        method: 'GET',
        url: 'https://open.spotify.com/get_access_token?reason=transport&productType=web_player',
        headers: {
          Accept: 'application/json',
        },
      });

      if (response.status < 200 || response.status >= 300) return '';

      const payload = safeJsonParse(response.responseText) || {};
      const token = String(payload.accessToken || '').trim();
      state.spotifyIsAnonymous = Boolean(payload.isAnonymous);
      state.spotifyIsPremium = Boolean(payload.isPremium);
      if (!token) return '';

      const expiresAt = Number(payload.accessTokenExpirationTimestampMs || 0);
      state.spotifyAccessToken = token;
      state.spotifyAccessTokenExpiresAt = Number.isFinite(expiresAt) && expiresAt > 0
        ? expiresAt
        : Date.now() + 50 * 60 * 1000;

      return token;
    } catch (error) {
      return '';
    }
  }

  async function spotifyApiGet(url) {
    let token = await getSpotifyAccessToken(false);
    if (!token) return null;

    let response = await gmRequest({
      method: 'GET',
      url,
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${token}`,
      },
      timeout: 12000,
    });

    if (response.status === 401) {
      token = await getSpotifyAccessToken(true);
      if (!token) return null;

      response = await gmRequest({
        method: 'GET',
        url,
        headers: {
          Accept: 'application/json',
          Authorization: `Bearer ${token}`,
        },
        timeout: 12000,
      });
    }

    if (response.status < 200 || response.status >= 300) return null;
    return safeJsonParse(response.responseText);
  }

  function installEmbedApiHook() {
    injectEmbedApiBridge();
    window.addEventListener(EMBED_READY_EVENT, onEmbedReadyEvent);
    window.addEventListener(EMBED_ERROR_EVENT, onEmbedErrorEvent);

    const pageWindow = getPageWindow();
    try {
      if (pageWindow.__ntSpotifyIframeApi) {
        onEmbedApiReady(pageWindow.__ntSpotifyIframeApi);
      }
    } catch (error) {
      // noop
    }
  }

  function loadEmbedApiScript() {
    if (state.embedApiLoadStarted) return;
    state.embedApiLoadStarted = true;

    if (document.querySelector('script[data-nt-spotify-embed-api="1"]')) return;

    const script = document.createElement('script');
    script.async = true;
    script.src = EMBED_API_SRC;
    script.dataset.ntSpotifyEmbedApi = '1';
    script.onerror = () => {
      state.embedApiLoadFailed = true;
      failEmbedApiWaiters(new Error('Spotify embed API failed to load'));
      window.dispatchEvent(new CustomEvent(EMBED_ERROR_EVENT));
    };
    document.body.appendChild(script);
  }

  function waitForEmbedApi(timeoutMs) {
    if (state.embedApi) return Promise.resolve(state.embedApi);
    if (state.embedApiLoadFailed) return Promise.reject(new Error('Spotify embed API unavailable'));

    loadEmbedApiScript();

    return new Promise((resolve, reject) => {
      let settled = false;

      const done = (api) => {
        if (settled) return;
        settled = true;
        resolve(api);
      };

      const timer = window.setTimeout(() => {
        if (settled) return;
        settled = true;
        state.embedWaiters = state.embedWaiters.filter((fn) => fn !== wrapped);
        reject(new Error('Spotify embed API timeout'));
      }, timeoutMs || 12000);

      const wrapped = (api) => {
        window.clearTimeout(timer);
        done(api);
      };

      state.embedWaiters.push(wrapped);
    });
  }

  function injectEmbedApiBridge() {
    const pageWindow = getPageWindow();
    if (pageWindow.__ntSpotifyEmbedBridgeInstalled) return;
    pageWindow.__ntSpotifyEmbedBridgeInstalled = true;
    const previous = pageWindow.onSpotifyIframeApiReady;
    pageWindow.onSpotifyIframeApiReady = function (IFrameAPI) {
      pageWindow.__ntSpotifyIframeApi = IFrameAPI;
      window.dispatchEvent(new CustomEvent(EMBED_READY_EVENT));
      if (typeof previous === 'function') {
        try { previous(IFrameAPI); } catch (err) { }
      }
    };
  }

  function onEmbedReadyEvent() {
    const pageWindow = getPageWindow();
    try {
      if (pageWindow.__ntSpotifyIframeApi) {
        onEmbedApiReady(pageWindow.__ntSpotifyIframeApi);
      }
    } catch (error) {
      // noop
    }
  }

  function onEmbedErrorEvent() {
    state.embedApiLoadFailed = true;
  }

  function onEmbedApiReady(api) {
    if (!api) return;
    state.embedApi = api;
    state.embedApiLoadFailed = false;

    const waiters = state.embedWaiters.splice(0, state.embedWaiters.length);
    waiters.forEach((resolve) => {
      try {
        resolve(api);
      } catch (error) {
        // noop
      }
    });
  }

  function failEmbedApiWaiters(error) {
    const waiters = state.embedWaiters.splice(0, state.embedWaiters.length);
    waiters.forEach((waiterFn) => {
      try {
        // Pass null to signal failure — callers check for null/falsy api
        waiterFn(null);
      } catch (err) {
        // noop
      }
    });
  }

  function getActivePlaybackEngine() {
    return state.spotifyIsPremium ? 'embed' : 'yt';
  }

  function createEmbedController(api, uri) {
    return new Promise((resolve, reject) => {
      try {
        api.createController(
          state.embedHost,
          {
            uri,
            width: 320,
            height: 80,
          },
          (controller) => {
            if (!controller || typeof controller.addListener !== 'function') {
              reject(new Error('Spotify embed controller unavailable'));
              return;
            }
            state.embedController = controller;
            wireControllerEvents(controller);
            resolve(controller);
          }
        );
      } catch (error) {
        reject(error);
      }
    });
  }

  function ensureEmbedHost() {
    if (state.embedHost && document.body.contains(state.embedHost)) return;

    const host = document.createElement('div');
    host.id = 'nt-spotify-embed-host';
    host.style.cssText = [
      'position: fixed',
      'right: -5000px',
      'bottom: -5000px',
      'width: 320px',
      'height: 80px',
      'overflow: hidden',
      'pointer-events: none',
      'z-index: -1',
    ].join(';');

    document.body.appendChild(host);
    state.embedHost = host;
  }

  async function updateTrackMetaFromUri(uri) {
    if (!uri) return;
    try {
      const meta = await resolveTrackMetaForUri(uri);
      if (meta) {
        state.trackMeta = chooseBetterTrackMeta(state.trackMeta, meta);
        if (state.active) renderNowPlaying();
      }
    } catch (err) {
      // noop — best-effort metadata update
    }
  }

  function wireControllerEvents(controller) {
    if (!controller || typeof controller.addListener !== 'function') return;

    controller.addListener('playback_started', (event) => {
      if (getActivePlaybackEngine() !== 'embed') return;

      const data = event && event.data ? event.data : null;
      if (!data) return;

      if (data.playingURI) {
        state.playback.uri = data.playingURI;
        void updateTrackMetaFromUri(data.playingURI);
      }

      state.playback.isPaused = false;
      state.playback.updatedAt = Date.now();
      if (state.active) renderNowPlaying();
    });

    controller.addListener('playback_update', (event) => {
      if (getActivePlaybackEngine() !== 'embed') return;

      const data = event && event.data ? event.data : null;
      if (!data) return;

      state.playback.uri = data.playingURI || state.playback.uri;
      state.playback.position = Number(data.position) || 0;
      state.playback.duration = Number(data.duration) || 0;
      state.playback.isPaused = Boolean(data.isPaused);
      state.playback.isBuffering = Boolean(data.isBuffering);
      state.playback.updatedAt = Date.now();

      if (state.playback.uri) {
        if (!state.trackMeta || state.trackMeta.uri !== state.playback.uri) {
          void updateTrackMetaFromUri(state.playback.uri);
        }
      }

      if (state.active) renderNowPlaying();
    });
  }

  async function safeControllerCall(methodName, arg) {
    const controller = state.embedController;
    if (!controller) return false;
    const fn = controller[methodName];
    if (typeof fn !== 'function') return false;

    try {
      const result = arg === undefined ? fn.call(controller) : fn.call(controller, arg);
      if (result && typeof result.then === 'function') {
        await result.catch(() => { });
      }
      return true;
    } catch (error) {
      return false;
    }
  }

  function startFrameWatcher() {
    syncFrameBinding();
    stopFrameWatcher();
    state.frameWatchTimer = window.setInterval(syncFrameBinding, FRAME_WATCH_TICK_MS);
  }

  function ensureRootBindings(doc) {
    if (!doc || doc.__ntSpotifyRootBound) return;
    doc.documentElement.addEventListener('click', handleGlobalClick, true);
    doc.documentElement.addEventListener('pointerdown', handleGlobalClick, true);
    doc.documentElement.addEventListener('mousedown', handleGlobalClick, true);
    doc.documentElement.addEventListener('touchstart', handleGlobalClick, true);
    doc.__ntSpotifyRootBound = true;
  }

  function clampSpotifyOutputVolume(value) {
    const parsed = Number(value);
    if (!Number.isFinite(parsed)) return 1;
    return Math.min(1, Math.max(0.2, parsed));
  }

  function clearAutoActivateTimer() {
    if (state.autoActivateTimer) {
      window.clearTimeout(state.autoActivateTimer);
      state.autoActivateTimer = null;
    }
  }

  function isSpotifyControlVisuallyActive() {
    return state.active ? !state.playback.isPaused : state.pendingSelection;
  }

  function isInlineEditorOpen(transport) {
    return Boolean(transport && transport.dataset.editorOpen === '1');
  }

  async function activateSpotifyFromSavedSourceOrEditor() {
    if (getSavedSourceParsed()) {
      await activateSpotifyMode({ preferSavedOnly: true });
      return;
    }
    openSpotifyInputEditor(state.frameDoc, { showToast: false });
  }

  function getNativeMusicControls(doc) {
    if (!doc) return {};
    const option = doc.querySelector('.race-header-controls--option.option-music');
    const sfxOption = doc.querySelector('.race-header-controls--option.option-sfx');
    const volumeRoot = option ? option.querySelector('.race-header-controls--volume-control') : null;
    const volumeInput = volumeRoot ? volumeRoot.querySelector('input[type="range"]') : null;
    const sfxVolumeRoot = sfxOption ? sfxOption.querySelector('.race-header-controls--volume-control') : null;
    const sfxVolumeInput = sfxVolumeRoot ? sfxVolumeRoot.querySelector('input[type="range"]') : null;
    return {
      option,
      optionLabel: option ? option.querySelector('.race-header-controls--option-label span') : null,
      sfxOption,
      sfxLabel: sfxOption ? sfxOption.querySelector('.race-header-controls--option-label span') : null,
      sfxVolumeRoot,
      sfxVolumeInput,
      volumeRoot,
      volumeInput,
      currentIndicator: doc.querySelector('.music-selector--current > .music-selector--indicator'),
      currentLabel: doc.querySelector('.music-selector--current > .music-selector--indicator + div'),
      selectedNative: doc.querySelector('.music-selector--list--item.selected:not(.nt-spotify-item)'),
      proxyRoot: option ? option.querySelector('.nt-spotify-native-music-proxy') : null,
      proxyButton: option ? option.querySelector('.nt-spotify-native-music-proxy-hitbox') : null,
      proxyVolumeRoot: option ? option.querySelector('.nt-spotify-native-music-proxy-volume') : null,
      proxyVolumeInput: option ? option.querySelector('.nt-spotify-native-music-proxy-volume input[type="range"]') : null,
    };
  }

  function stopProxyOwnedEvent(event, preventDefault) {
    if (!event) return;
    if (preventDefault !== false && event.cancelable) {
      event.preventDefault();
    }
    if (typeof event.stopImmediatePropagation === 'function') {
      event.stopImmediatePropagation();
    }
    if (typeof event.stopPropagation === 'function') {
      event.stopPropagation();
    }
  }

  function syncNativeMusicProxyUi(doc) {
    const controls = getNativeMusicControls(doc);
    const proxyButton = controls.proxyButton;
    const proxyVolumeRoot = controls.proxyVolumeRoot;
    const proxyVolumeInput = controls.proxyVolumeInput;
    if (!proxyButton || !proxyVolumeRoot || !proxyVolumeInput) return;

    const volume = clampSpotifyOutputVolume(state.outputVolume);
    const shouldLookActive = isSpotifyControlVisuallyActive();

    proxyButton.setAttribute('aria-pressed', shouldLookActive ? 'true' : 'false');
    proxyButton.setAttribute('aria-label', state.active ? 'Toggle Spotify playback' : 'Activate Spotify');
    if (doc.activeElement !== proxyVolumeInput) {
      proxyVolumeInput.value = String(volume);
    }
    proxyVolumeRoot.style.setProperty('--volume-value', `${Math.round(volume * 100)}%`);
  }

  function ensureNativeMusicProxy(doc) {
    const controls = getNativeMusicControls(doc);
    const option = controls.option;
    if (!option) return null;

    let proxyRoot = controls.proxyRoot;
    if (!proxyRoot) {
      proxyRoot = doc.createElement('div');
      proxyRoot.className = 'nt-spotify-native-music-proxy';
      proxyRoot.style.cssText = [
        'position:absolute',
        'inset:0',
        'z-index:4',
        'overflow:visible',
        'pointer-events:none',
      ].join(';');

      const button = doc.createElement('button');
      button.type = 'button';
      button.className = 'nt-spotify-native-music-proxy-hitbox';
      button.style.cssText = [
        'position:absolute',
        'inset:0',
        'margin:0',
        'padding:0',
        'border:0',
        'background:transparent',
        'cursor:pointer',
        'pointer-events:auto',
      ].join(';');

      const volumeRoot = doc.createElement('div');
      volumeRoot.className = 'race-header-controls--volume-control nt-spotify-native-music-proxy-volume';
      volumeRoot.style.pointerEvents = 'auto';

      const volumeLabel = doc.createElement('div');
      volumeLabel.className = 'race-header-controls--volume-control--label';
      volumeLabel.textContent = 'Volume';

      const volumeContainer = doc.createElement('div');
      volumeContainer.className = 'race-header-controls--volume-control--container';
      volumeContainer.style.pointerEvents = 'auto';

      const volumeInput = doc.createElement('input');
      volumeInput.type = 'range';
      volumeInput.min = '0.2';
      volumeInput.max = '1';
      volumeInput.step = '0.025';
      volumeInput.style.pointerEvents = 'auto';

      const volumeBar = doc.createElement('div');
      volumeBar.className = 'race-header-controls--volume-control--bar';

      volumeContainer.appendChild(volumeInput);
      volumeContainer.appendChild(volumeBar);
      volumeRoot.appendChild(volumeLabel);
      volumeRoot.appendChild(volumeContainer);
      proxyRoot.appendChild(button);
      proxyRoot.appendChild(volumeRoot);
      option.appendChild(proxyRoot);
    }

    if (proxyRoot.dataset.ntSpotifyBound !== '1') {
      const button = proxyRoot.querySelector('.nt-spotify-native-music-proxy-hitbox');
      const volumeInput = proxyRoot.querySelector('.nt-spotify-native-music-proxy-volume input[type="range"]');

      if (button) {
        ['pointerdown', 'mousedown', 'touchstart'].forEach((type) => {
          button.addEventListener(type, (event) => {
            stopProxyOwnedEvent(event, false);
          });
        });
        button.addEventListener('click', (event) => {
          stopProxyOwnedEvent(event, true);
          if (!state.active) {
            void activateSpotifyFromSavedSourceOrEditor();
            return;
          }
          void toggleSpotifyPlaybackFromControls();
        });
        button.addEventListener('keydown', (event) => {
          if (event.key !== 'Enter' && event.key !== ' ') return;
          stopProxyOwnedEvent(event, true);
          if (!state.active) {
            void activateSpotifyFromSavedSourceOrEditor();
            return;
          }
          void toggleSpotifyPlaybackFromControls();
        });
      }

      if (volumeInput) {
        ['pointerdown', 'mousedown', 'touchstart', 'click'].forEach((type) => {
          volumeInput.addEventListener(type, (event) => {
            stopProxyOwnedEvent(event, type === 'click');
          });
        });
        ['input', 'change'].forEach((type) => {
          volumeInput.addEventListener(type, (event) => {
            stopProxyOwnedEvent(event, false);
            const nextVolume = clampSpotifyOutputVolume(volumeInput.value);
            state.outputVolume = nextVolume;
            applySpotifyOutputVolume();
            syncNativeMusicProxyUi(doc);
          });
        });
      }

      proxyRoot.dataset.ntSpotifyBound = '1';
    }

    syncNativeMusicProxyUi(doc);
    return proxyRoot;
  }

  function ensureNativeSfxBindings(doc) {
    const controls = getNativeMusicControls(doc);
    const sfxOption = controls.sfxOption;
    const sfxVolumeInput = controls.sfxVolumeInput;

    if (sfxOption && sfxOption.dataset.ntSpotifySfxBound !== '1') {
      ['click', 'pointerdown', 'mousedown', 'touchstart'].forEach((type) => {
        sfxOption.addEventListener(type, () => {
          if (!isSpotifyUiSelected()) return;
          scheduleNativeSfxSync(doc);
        }, true);
      });
      sfxOption.dataset.ntSpotifySfxBound = '1';
    }

    if (sfxVolumeInput && sfxVolumeInput.dataset.ntSpotifySfxBound !== '1') {
      ['input', 'change', 'click', 'pointerdown', 'mousedown', 'touchstart'].forEach((type) => {
        sfxVolumeInput.addEventListener(type, () => {
          if (!isSpotifyUiSelected()) return;
          scheduleNativeSfxSync(doc);
        }, true);
      });
      sfxVolumeInput.dataset.ntSpotifySfxBound = '1';
    }
  }

  function isNativeSfxEnabled(doc) {
    const controls = getNativeMusicControls(doc || state.frameDoc);
    const option = controls.sfxOption;
    const label = String(controls.sfxLabel && controls.sfxLabel.textContent || '').trim().toLowerCase();
    if (option && option.classList && option.classList.contains('is-on')) {
      return true;
    }
    return label === 'sfx on';
  }

  function isNativeStationPlaying(doc) {
    const targetDoc = doc || state.frameDoc;
    if (!targetDoc) return false;
    const selector = targetDoc.querySelector('.music-selector');
    if (selector && selector.classList.contains('playing')) {
      return true;
    }
    const indicator = targetDoc.querySelector('.music-selector--current > .music-selector--indicator');
    return Boolean(indicator && indicator.classList.contains('playing'));
  }

  function isNativeMusicEnabled(doc) {
    const targetDoc = doc || state.frameDoc;
    if (!targetDoc) return false;

    if (isNativeStationPlaying(targetDoc)) {
      return true;
    }

    if (!isSpotifyUiSelected()) {
      const controls = getNativeMusicControls(targetDoc);
      const option = controls.option;
      const label = String(controls.optionLabel && controls.optionLabel.textContent || '').trim().toLowerCase();
      if (option && option.classList && option.classList.contains('is-on')) {
        return true;
      }
      return label === 'music on';
    }

    return false;
  }

  function restoreNativeMusicControlVisuals(doc) {
    const controls = getNativeMusicControls(doc || state.frameDoc);
    if (!controls.option) return;
    const nativeLooksActive = isNativeStationPlaying(doc || state.frameDoc);
    controls.option.classList.toggle('is-on', nativeLooksActive);
    controls.option.classList.toggle('false', !nativeLooksActive);
    controls.option.removeAttribute('aria-pressed');
    if (controls.optionLabel) {
      controls.optionLabel.textContent = nativeLooksActive ? 'Music ON' : 'Music OFF';
    }
  }

  function forceNativeMusicOff(doc, options) {
    const targetDoc = doc || state.frameDoc;
    const opts = options || {};
    if (!targetDoc || !isMusicMuteNativeEnabled()) return false;
    if (!isNativeMusicEnabled(targetDoc)) return false;

    const now = Date.now();
    if (!opts.force && now - Number(state._lastForcedNativeMusicOff || 0) < 120) {
      return false;
    }

    const controls = getNativeMusicControls(targetDoc);
    if (!controls.option) return false;

    state._lastForcedNativeMusicOff = now;
    try {
      controls.option.click();
      return true;
    } catch (_) { }

    try {
      controls.option.dispatchEvent(new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
        view: targetDoc.defaultView || window
      }));
      return true;
    } catch (_) { }

    return false;
  }

  function shouldHardMuteNativeAudio(doc) {
    if (!isMusicMuteNativeEnabled()) return false;
    return !isNativeSfxEnabled(doc);
  }

  function openSpotifyInputEditor(doc, options) {
    if (!doc) return;
    const showToast = !(options && options.showToast === false);
    injectTopTransportControls(doc);
    claimSpotifySelection(doc, { openEditor: true });
    if (showToast) {
      toast('Paste a Spotify, YouTube, or Apple Music URL to get started.');
    }
  }

  function applySpotifyOutputVolume() {
    const volume = clampSpotifyOutputVolume(state.outputVolume);
    state.outputVolume = volume;
    safeYTCall('setVolume', Math.round(volume * 100));
    void safeControllerCall('setVolume', volume);
  }

  function isSpotifyUiSelected() {
    return Boolean(state.active || state.pendingSelection);
  }

  function areNativeStationEventsSuppressed() {
    return Boolean(
      state.isActivating
      || Date.now() < Number(state.suppressNativeStationEventsUntil || 0)
    );
  }

  function claimSpotifySelection(doc, options) {
    const targetDoc = doc || state.frameDoc;
    const opts = options || {};
    const savedSource = getSavedSourceParsed();
    const managedPlatform = (savedSource && savedSource.platform) || getDefaultPlatform() || 'spotify';
    clearPendingNativeStationHandoff();
    state.pendingSelection = true;
    state._stopCooldownUntil = 0;
    if (!targetDoc) return;
    suppressNativeAudio(targetDoc);
    startAudioObservers(targetDoc);
    markSpotifyItemSelected(targetDoc, true);
    setCurrentStationLabel(targetDoc, getPlatformLabel(savedSource), managedPlatform);
    if (opts.openEditor !== false) {
      setInlineEditorOpen(targetDoc, true);
    }
    syncTopTransportControls(targetDoc);
    syncNativeMusicControls(targetDoc);
  }

  function releaseSpotifySelection(doc, options) {
    const targetDoc = doc || state.frameDoc;
    const opts = options || {};
    clearPendingNativeStationHandoff();
    clearAutoActivateTimer();
    state._autoActivateAttempted = true;
    state.pendingSelection = false;
    state.suppressNativeStationEventsUntil = 0;
    if (!targetDoc) return;
    if (!state.active && opts.closeEditor !== false) {
      setInlineEditorOpen(targetDoc, false);
    }
    markSpotifyItemSelected(targetDoc, false);
    if (!state.active) {
      if (opts.restoreLabel !== false) {
        restoreNativeStationLabel(targetDoc);
      }
      if (opts.restoreAudio !== false) {
        restoreNativeAudio(targetDoc);
      }
      stopAudioObservers();
      syncTopTransportControls(targetDoc);
      syncNativeMusicControls(targetDoc);
    }
  }

  function shouldHandleSpotifyItemEvent(event) {
    const type = String(event && event.type ? event.type : '');
    const now = Date.now();
    if (type === 'pointerdown') {
      state._spotifyItemActionUntil = now + 400;
      return true;
    }
    if (type === 'mousedown') {
      if (state._spotifyItemActionUntil > now) return false;
      state._spotifyItemActionUntil = now + 400;
      return true;
    }
    if (type === 'click') {
      if (state._spotifyItemActionUntil > now) return false;
      return true;
    }
    return false;
  }

  function handleSpotifyItemActivation(event) {
    state._stopCooldownUntil = 0;
    if (event && event.preventDefault) event.preventDefault();
    if (event && event.stopPropagation) event.stopPropagation();
    if (event && typeof event.stopImmediatePropagation === 'function') {
      event.stopImmediatePropagation();
    }
    if (!shouldHandleSpotifyItemEvent(event)) {
      return;
    }
    if (!state.active && !getSavedSourceParsed()) {
      openSpotifyInputEditor(state.frameDoc);
      return;
    }
    if (!state.active) {
      void activateSpotifyMode({ preferSavedOnly: true });
    } else if (state.active && state.playback.isPaused) {
      void toggleSpotifyPlaybackFromControls();
    }
  }

  function syncNativeMusicControls(doc) {
    const controls = getNativeMusicControls(doc);
    const option = controls.option;
    const optionLabel = controls.optionLabel;
    const volumeRoot = controls.volumeRoot;
    const volumeInput = controls.volumeInput;

    if (volumeInput) {
      const parsedNativeVolume = Number(volumeInput.value);
      if (Number.isFinite(parsedNativeVolume) && !isSpotifyUiSelected()) {
        state.outputVolume = clampSpotifyOutputVolume(parsedNativeVolume);
      }
    }

    if (!option) return;

    if (!isSpotifyUiSelected()) {
      if (controls.proxyRoot) controls.proxyRoot.remove();
      if (volumeRoot) {
        volumeRoot.style.visibility = '';
        volumeRoot.removeAttribute('aria-hidden');
      }
      if (option.dataset.ntSpotifyManaged === '1') {
        option.removeAttribute('data-ntSpotifyManaged');
      }
      restoreNativeMusicControlVisuals(doc);
      return;
    }

    option.dataset.ntSpotifyManaged = '1';
    ensureNativeMusicProxy(doc);

    const volume = clampSpotifyOutputVolume(state.outputVolume);
    if (volumeRoot) {
      volumeRoot.style.visibility = 'hidden';
      volumeRoot.setAttribute('aria-hidden', 'true');
    }

    const shouldLookActive = isSpotifyControlVisuallyActive();
    option.classList.toggle('is-on', shouldLookActive);
    option.classList.toggle('false', !shouldLookActive);
    option.setAttribute('aria-pressed', shouldLookActive ? 'true' : 'false');
    if (optionLabel) {
      optionLabel.textContent = state.active
        ? (state.playback.isPaused ? 'Music OFF' : 'Music ON')
        : 'Music ON';
    }
    syncNativeMusicProxyUi(doc);
  }

  function syncFrameUiNow(doc) {
    if (!doc) return;
    try { ensureNativeSfxBindings(doc); } catch (e) { }
    if (isSpotifyUiSelected()) {
      const savedSource = getSavedSourceParsed();
      if (savedSource && !hasExplicitNativeSelectionEvidence(doc)) {
        try {
          setCurrentStationLabel(doc, getPlatformLabel(savedSource), savedSource.platform);
        } catch (e) { }
      }
    }
    if (!isSpotifyUiSelected()) {
      const controls = getNativeMusicControls(doc);
      const currentPlatform = String(controls.currentIndicator && controls.currentIndicator.getAttribute('data-platform') || '').trim().toLowerCase();
      if (isManagedStationPlatform(currentPlatform)) {
        try { restoreNativeStationLabel(doc); } catch (e) { }
      }
    }
    const suppressNativeStationEvents = areNativeStationEventsSuppressed();
    if (isSpotifyUiSelected() && !suppressNativeStationEvents && looksLikeNativeStationSelection(doc)) {
      const controls = getNativeMusicControls(doc);
      scheduleNativeStationHandoff(doc, {
        stationId: extractStationIdFromMenuItem(controls.selectedNative)
      });
      try { injectSpotifyItem(doc); } catch (e) { }
      try { injectTopTransportControls(doc); } catch (e) { }
      return;
    }
    try { injectSpotifyItem(doc); } catch (e) { }
    try { injectTopTransportControls(doc); } catch (e) { }
    try { markSpotifyItemSelected(doc, isSpotifyUiSelected()); } catch (e) { }
    try { syncTopTransportControls(doc); } catch (e) { }
    try { syncNativeMusicControls(doc); } catch (e) { }
  }

  function queueImmediateFrameUiSync(doc) {
    if (!doc || state.frameDoc !== doc || state._frameUiSyncQueued) return;
    state._frameUiSyncQueued = true;

    const flush = function () {
      state._frameUiSyncQueued = false;
      if (state.frameDoc !== doc) return;
      syncFrameUiNow(doc);
    };

    if (typeof window.requestAnimationFrame === 'function') {
      window.requestAnimationFrame(flush);
    } else {
      window.setTimeout(flush, 0);
    }
  }

  function reassertSpotifySuppression(doc) {
    const targetDoc = doc || state.frameDoc;
    if (!targetDoc) return;
    if (!isSpotifyUiSelected()) return;
    if (hasExplicitNativeSelectionEvidence(targetDoc)) return;
    try { suppressNativeAudio(targetDoc); } catch (_) { }
    try { reinforceNativeMusicOff(targetDoc, [0, 35, 100, 220, 420]); } catch (_) { }
  }

  function nodeTouchesMusicSelector(node) {
    if (!node || node.nodeType !== 1) return false;
    if (typeof node.closest === 'function' && node.closest(
      '.race-header-controls--music, .race-header-controls--station-selector, .music-selector, .race-header-controls--option.option-music'
    )) {
      return true;
    }
    if (typeof node.matches === 'function' && node.matches(
      '.race-header-controls--music, .race-header-controls--station-selector, .music-selector, .music-selector--controls, .music-selector--current, .music-selector--indicator, .music-selector--list, .music-selector--list--item, .race-header-controls--option.option-music'
    )) {
      return true;
    }
    if (typeof node.querySelector === 'function' && node.querySelector(
      '.race-header-controls--music, .race-header-controls--station-selector, .music-selector, .music-selector--controls, .music-selector--current, .music-selector--indicator, .music-selector--list, .music-selector--list--item, .race-header-controls--option.option-music'
    )) {
      return true;
    }
    return false;
  }

  function stopFrameUiObserver() {
    if (state._frameUiObserver) {
      try { state._frameUiObserver.disconnect(); } catch (_) { }
      state._frameUiObserver = null;
    }
    state._frameUiSyncQueued = false;
  }

  function startFrameUiObserver(doc) {
    stopFrameUiObserver();
    if (!doc || !doc.body) return;

    const callback = function (mutations) {
      if (state.frameDoc !== doc) return;
      for (let i = 0; i < mutations.length; i += 1) {
        const mutation = mutations[i];
        if (mutation.type === 'characterData') {
          const parent = mutation.target && mutation.target.parentElement;
          if (parent && nodeTouchesMusicSelector(parent)) {
            reassertSpotifySuppression(doc);
            queueImmediateFrameUiSync(doc);
            return;
          }
        }

        if (mutation.type === 'attributes' && nodeTouchesMusicSelector(mutation.target)) {
          reassertSpotifySuppression(doc);
          queueImmediateFrameUiSync(doc);
          return;
        }

        const added = mutation.addedNodes;
        if (added) {
          for (let j = 0; j < added.length; j += 1) {
            if (nodeTouchesMusicSelector(added[j])) {
              reassertSpotifySuppression(doc);
              queueImmediateFrameUiSync(doc);
              return;
            }
          }
        }

        const removed = mutation.removedNodes;
        if (removed) {
          for (let j = 0; j < removed.length; j += 1) {
            if (nodeTouchesMusicSelector(removed[j])) {
              reassertSpotifySuppression(doc);
              queueImmediateFrameUiSync(doc);
              return;
            }
          }
        }
      }
    };

    try {
      const obs = new MutationObserver(callback);
      obs.observe(doc.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['class', 'data-platform'],
        characterData: true
      });
      state._frameUiObserver = obs;
    } catch (_) { }
  }

  function maybeStopForNativeStationSelection(doc) {
    if (!doc || !state.active) return false;
    if (areNativeStationEventsSuppressed()) return false;

    const controls = getNativeMusicControls(doc);
    const selectedNative = controls.selectedNative;
    if (selectedNative) {
      scheduleNativeStationHandoff(doc, {
        stationId: extractStationIdFromMenuItem(selectedNative)
      });
      return true;
    }

    if (hasExplicitNativeSelectionEvidence(doc, controls)) {
      scheduleNativeStationHandoff(doc);
      return true;
    }

    return false;
  }

  function syncFrameBinding() {
    let frame = state.frame;
    let doc = null;

    if (frame) {
      try {
        if (document.contains(frame)) {
          doc = frame.contentDocument;
        }
      } catch (error) {
        doc = null;
      }
    }

    if (!doc || !doc.body) {
      frame = document.querySelector('main.structure-content iframe');
      if (!frame) {
        frame = document.querySelector('iframe[src*="/race"]');
      }
      // Fallback search through all same-origin iframes if specific classes changed
      if (!frame) {
        const frames = document.querySelectorAll('iframe');
        for (const f of frames) {
          try {
            if (f.contentDocument && f.contentDocument.body) {
              frame = f;
              break;
            }
          } catch (e) { }
        }
      }

      if (!frame) {
        detachFrame();
        return;
      }

      try {
        doc = frame.contentDocument;
      } catch (error) {
        return;
      }
    }

    if (!doc || !doc.body) return;

    if (state.frameDoc === doc) {
      ensureRootBindings(doc);
      const now = Date.now();
      if (!state._lastFrameUiSync || now - state._lastFrameUiSync >= FRAME_UI_REFRESH_MS) {
        state._lastFrameUiSync = now;
        syncFrameUiNow(doc);
      }
      if (isSpotifyUiSelected()) {
        try {
          if (!hasExplicitNativeSelectionEvidence(doc) && isNativeMusicEnabled(doc)) {
            reassertSpotifySuppression(doc);
          }
        } catch (e) { }
        try {
          if (maybeStopForNativeStationSelection(doc)) {
            return;
          }
        } catch (e) { }
      }
      if (isSpotifyUiSelected() && (!state._lastNativeMuteCheck || now - state._lastNativeMuteCheck >= NATIVE_AUDIO_REFRESH_MS)) {
        state._lastNativeMuteCheck = now;
        try { suppressNativeAudio(doc); } catch (e) { }
      }
      return;
    }

    detachFrame();
    attachFrame(frame, doc);
  }

  function detachFrame() {
    clearAutoActivateTimer();
    clearPendingNativeStationHandoff();
    clearPendingSfxSync();
    resetFrameBindingState();
    stopFrameUiObserver();
    stopAudioObservers();
  }

  function attachFrame(frame, doc) {
    state._lastFrameUiSync = 0;
    state._lastNativeMuteCheck = 0;
    state.frame = frame;
    state.frameDoc = doc;
    state.frameWindow = frame.contentWindow || null;
    ensureFrameFocusGuard(state.frameWindow);
    installAudioContextTracker(state.frameWindow);
    ensureRootBindings(doc);

    syncFrameUiNow(doc);
    refreshSpotifyUI();
    startFrameUiObserver(doc);

    if (state.pendingToast) {
      toast(state.pendingToast, 'success');
      state.pendingToast = null;
    }

    if (isSpotifyUiSelected()) {
      renderOverlay(doc);
      renderNowPlaying();
      // New race loaded while Spotify is playing — keep native audio silent.
      // Howler on the new frame may not exist yet, so we retry aggressively
      // in the first second to catch it the instant it initialises.
      suppressNativeAudio(doc);
      scheduleFrameSuppressRetryWave();
      startAudioObservers(doc);
    } else if (!state._autoActivateAttempted) {
      // Auto-activate if the previous session was active (resume on page reload)
      if (isMusicAutoActivateEnabled()) {
        var session = getSavedSessionState();
        if (session) {
          state.suppressNativeStationEventsUntil = Date.now() + 2500;
          claimSpotifySelection(doc, { openEditor: false });
          // Give the race iframe time to finish settling before loading
          // third-party music APIs and resuming a saved session.
          clearAutoActivateTimer();
          state.autoActivateTimer = window.setTimeout(function () {
            state.autoActivateTimer = null;
            if (!state.active && state.frameDoc === doc && state.frameWindow) {
              state._autoActivateAttempted = true;
              void activateSpotifyMode({ preferSavedOnly: true });
            }
          }, AUTO_ACTIVATE_DELAY_MS);
        }
      }
    }
  }

  function handleGlobalClick(event) {
    const itemNode = event.target ? event.target.closest('.nt-spotify-item') : null;
    const anyStationItem = event.target ? event.target.closest('.music-selector--list--item') : null;

    if (itemNode) {
      handleSpotifyItemActivation(event);
      return;
    }

    // Detect click on a non-Spotify station item — deactivate Spotify
    if (anyStationItem && !anyStationItem.classList.contains('nt-spotify-item') && isSpotifyUiSelected()) {
      const type = String(event && event.type || '');
      if (!type || !['click', 'pointerdown', 'mousedown', 'touchstart'].includes(type)) {
        return;
      }
      scheduleNativeStationHandoff(state.frameDoc, {
        stationId: extractStationIdFromMenuItem(anyStationItem),
        force: true
      });
      return;
    }
  }

  function ensureFrameFocusGuard(frameWindow) {
    if (!frameWindow) return;
    if (frameWindow.__ntSpotifyFocusGuardInstalled) return;
    if (!frameWindow.HTMLElement || !frameWindow.HTMLElement.prototype) return;

    const originalFocus = frameWindow.HTMLElement.prototype.focus;
    if (typeof originalFocus !== 'function') return;

    frameWindow.HTMLElement.prototype.focus = function patchedSpotifyFocus(...args) {
      try {
        if (frameWindow.__ntSpotifyFocusLocked) {
          const inEditor = this && typeof this.closest === 'function'
            ? this.closest('.nt-spotify-inline-editor')
            : null;
          if (!inEditor) return;
        }
      } catch (error) {
        // noop
      }
      return originalFocus.apply(this, args);
    };

    frameWindow.__ntSpotifyFocusGuardInstalled = true;
    frameWindow.__ntSpotifyFocusLocked = false;
  }

  function setFrameFocusLock(locked) {
    if (!state.frameWindow) return;
    try {
      state.frameWindow.__ntSpotifyFocusLocked = Boolean(locked);
    } catch (error) {
      // noop
    }
  }

  function onMusicMessageFromRace(event) {
    if (!event || !event.data || event.data.type !== 'music-player') return;
    if (state.frameWindow && event.source !== state.frameWindow) return;

    if (event.data.action === 'set-station') {
      const stationId = extractStationIdFromMusicMessage(event.data);
      traceMusic('race-message:set-station', {
        stationId,
        active: state.active,
        suppressed: areNativeStationEventsSuppressed(),
        hasFrameDoc: Boolean(state.frameDoc),
      });
      if (stationId === 'spotify' || stationId === STATION_ID) {
        // Only auto-activate if we're not in a cooldown window after an
        // intentional stop — otherwise the postMessage from NT's code
        // immediately re-activates what the user just turned off.
        if (!state.active && Date.now() > (state._stopCooldownUntil || 0)) {
          void activateSpotifyMode({ preferSavedOnly: true });
        }
        return;
      }

      if (state.active) {
        if (areNativeStationEventsSuppressed()) {
          return;
        }
        if (!stationId) {
          return;
        }
        scheduleNativeStationHandoff(state.frameDoc, {
          stationId,
          force: true
        });
      }
      return;
    }

  }

  function extractStationIdFromMusicMessage(payload) {
    if (!payload || typeof payload !== 'object') return '';

    const raw = [
      payload.stationId,
      payload.id,
      payload.station,
      payload.selectedStation,
      payload.data && payload.data.stationId,
      payload.data && payload.data.id,
      payload.data && payload.data.station,
      payload.data && payload.data.selectedStation,
    ];

    for (let i = 0; i < raw.length; i += 1) {
      const value = String(raw[i] || '').trim().toLowerCase();
      if (value) return value;
    }
    return '';
  }

  function getStationMenuItemById(doc, stationId) {
    if (!doc || !stationId) return null;
    const safeId = String(stationId)
      .replace(/\\/g, '\\\\')
      .replace(/"/g, '\\"');

    try {
      return doc.querySelector(`.music-selector--list--item[data-station-id="${safeId}"]`);
    } catch (error) {
      return null;
    }
  }

  function extractStationIdFromMenuItem(item) {
    if (!item || typeof item.getAttribute !== 'function') return '';
    return String(item.getAttribute('data-station-id') || '').trim().toLowerCase();
  }

  function clearPendingNativeStationHandoff() {
    if (state._pendingNativeStationHandoffTimer) {
      window.clearTimeout(state._pendingNativeStationHandoffTimer);
      state._pendingNativeStationHandoffTimer = 0;
    }
    state._pendingNativeStationHandoffToken += 1;
  }

  function isManagedStationPlatform(platform) {
    const normalized = String(platform || '').trim().toLowerCase();
    return normalized === 'spotify' || normalized === 'youtube' || normalized === 'apple-music';
  }

  function currentStationLooksSpotify(doc, controls) {
    const nextControls = controls || getNativeMusicControls(doc);
    const currentIndicator = nextControls.currentIndicator;
    const currentLabel = String(nextControls.currentLabel && nextControls.currentLabel.textContent || '').trim().toLowerCase();
    const currentPlatform = String(currentIndicator && currentIndicator.getAttribute('data-platform') || '').trim().toLowerCase();
    const expectedSpotifyLabel = String(getPlatformLabel(getSavedSourceParsed()) || 'Spotify').trim().toLowerCase();
    return isManagedStationPlatform(currentPlatform)
      || currentLabel === 'spotify'
      || currentLabel === expectedSpotifyLabel;
  }

  function hasExplicitNativeSelectionEvidence(doc, controls) {
    if (!doc) return false;

    const nextControls = controls || getNativeMusicControls(doc);
    const selectedNative = nextControls.selectedNative;
    if (selectedNative) {
      return true;
    }

    const currentIndicator = nextControls.currentIndicator;
    const currentPlatform = String(currentIndicator && currentIndicator.getAttribute('data-platform') || '').trim().toLowerCase();
    return Boolean(currentPlatform && !isManagedStationPlatform(currentPlatform));
  }

  function looksLikeNativeStationSelection(doc, stationId) {
    if (!doc) return false;

    const controls = getNativeMusicControls(doc);
    const selectedNative = controls.selectedNative;
    if (selectedNative) {
      const selectedStationId = extractStationIdFromMenuItem(selectedNative);
      if (!stationId || !selectedStationId || selectedStationId === stationId) {
        return true;
      }
    }

    if (hasExplicitNativeSelectionEvidence(doc, controls)) {
      return true;
    }
    return false;
  }

  function scheduleNativeStationHandoff(doc, options) {
    const targetDoc = doc || state.frameDoc;
    if (!targetDoc || !isSpotifyUiSelected()) return;

    const opts = options || {};
    const stationId = String(opts.stationId || '').trim().toLowerCase();
    const force = Boolean(opts.force);
    const token = state._pendingNativeStationHandoffToken + 1;

    clearPendingNativeStationHandoff();
    state._pendingNativeStationHandoffToken = token;
    traceMusic('native-handoff:scheduled', {
      stationId,
      force,
      delayMs: Number.isFinite(Number(opts.delayMs)) ? Number(opts.delayMs) : 0,
    });
    if (force) {
      state.suppressNativeStationEventsUntil = 0;
    }

    state._pendingNativeStationHandoffTimer = window.setTimeout(() => {
      state._pendingNativeStationHandoffTimer = 0;

      const finalize = () => {
        if (state._pendingNativeStationHandoffToken !== token) return;

        const liveDoc = state.frameDoc || targetDoc;
        if (!liveDoc || !isSpotifyUiSelected()) return;
        if (!force && !looksLikeNativeStationSelection(liveDoc, stationId)) return;

        clearPendingNativeStationHandoff();
        traceMusic('native-handoff:finalize', {
          stationId,
          force,
          active: state.active,
        });
        if (state.active) {
          stopSpotifyMode();
        } else {
          releaseSpotifySelection(liveDoc, {
            closeEditor: true,
            restoreLabel: true,
            restoreAudio: true
          });
        }
      };

      if (typeof window.requestAnimationFrame === 'function') {
        window.requestAnimationFrame(() => {
          window.requestAnimationFrame(finalize);
        });
      } else {
        finalize();
      }
    }, Number.isFinite(Number(opts.delayMs)) ? Number(opts.delayMs) : 0);
  }

  async function activateSpotifyMode(options) {
    if (state.isActivating) return;
    state.isActivating = true;
    traceMusic('activate-mode:start', {
      preferSavedOnly: Boolean(options && options.preferSavedOnly),
      currentSourceUri: state.playback.uri || '',
    });

    try {
      void getSpotifyAccessToken(false);

      const preferSavedOnly = Boolean(options && options.preferSavedOnly);
      const source = preferSavedOnly ? getSavedSourceParsed() : await ensureSourceConfigured();
      traceMusic('activate-mode:source-selected', {
        preferSavedOnly,
        hasSource: Boolean(source),
        sourceUri: source ? source.uri : '',
        sourceType: source ? source.type : '',
        sourcePlatform: source ? source.platform : '',
      });
      if (!source) {
        if (preferSavedOnly) {
          openSpotifyInputEditor(state.frameDoc, { showToast: false });
          focusSpotifyInput();
          toast('Paste a Spotify, YouTube, or Apple Music URL to get started.');
        }
        return;
      }

      state._autoActivateAttempted = true;

      // Show loading state immediately so the user sees feedback
      state.active = true;
      state.pendingSelection = false;
      state.trackMeta = null;
      resetPlaybackState();
      const nativeControls = getNativeMusicControls(state.frameDoc);
      if (nativeControls.volumeInput) {
        state.outputVolume = clampSpotifyOutputVolume(nativeControls.volumeInput.value);
      }
      // Restore volume in case it was zeroed during stopSpotifyMode
      applySpotifyOutputVolume();
      state.playback.uri = source.uri;

      // *** IMMEDIATELY silence NT's native audio ***
      // This must happen BEFORE any async work (loadSource) so there's
      // no gap where NT's music plays over ours.
      suppressNativeAudio(state.frameDoc);
      startAudioObservers(state.frameDoc);

      if (state.frameDoc) {
        markSpotifyItemSelected(state.frameDoc, true);
        setCurrentStationLabel(state.frameDoc, getPlatformLabel(source), source ? source.platform : undefined);
        renderOverlay(state.frameDoc);
      }
      renderNowPlaying();

      const loaded = await loadSource(source.uri);
      // Guard: user may have stopped Spotify mode during the async load
      if (!state.active) return;
      if (!loaded) {
        traceMusic('activate-mode:load-failed', {
          sourceUri: source.uri,
          sourceType: source.type,
          sourcePlatform: source.platform,
        });
        // Rollback — loading failed
        state.active = false;
        state.pendingSelection = true;
        state.playback.uri = '';
        if (state.frameDoc) {
          markSpotifyItemSelected(state.frameDoc, true);  // keep selected so UI stays visible
          removeOverlay(state.frameDoc);
          // Show transport bar with editor open so user can fix the URL
          const transport = state.frameDoc.querySelector('.nt-spotify-transport');
          if (transport) transport.style.display = 'flex';
          setInlineEditorOpen(state.frameDoc, true);
          suppressNativeAudio(state.frameDoc);
        }
        toast('Unable to load source — paste a different URL.');
        return;
      }

      state.suppressNativeStationEventsUntil = Date.now() + 4000;
      traceMusic('activate-mode:load-succeeded', {
        sourceUri: source.uri,
        queueLength: state.queue.length,
        queueIndex: state.queueIndex,
      });

      startProgressTimer();
      applySpotifyOutputVolume();
      renderNowPlaying();
    } catch (error) {
      traceMusic('activate-mode:error', {
        error: formatTraceError(error),
      });
      console.error('[NT Music] Activation failed:', error);
      state.active = false;
      state.pendingSelection = false;
      state.playback.uri = '';
      toast('Playback activation failed.');
    } finally {
      state.isActivating = false;
    }
  }

  function stopSpotifyMode() {
    traceMusic('stop-mode', {
      currentUri: state.playback.uri || '',
      queueLength: state.queue.length,
      queueIndex: state.queueIndex,
    });
    clearPendingNativeStationHandoff();
    clearAutoActivateTimer();
    state._autoActivateAttempted = true;
    state.active = false;
    state.pendingSelection = false;
    state.suppressNativeStationEventsUntil = 0;
    state._stopCooldownUntil = Date.now() + 3000; // prevent re-activation for 3 s
    // User explicitly deactivated — clear session so we don't auto-resume
    clearSessionState();
    stopProgressTimer();
    stopAudioObservers();

    // Zero volume instantly — faster than waiting for stopVideo cross-iframe round-trip
    void safeYTCall('setVolume', 0);
    // Then stop playback entirely
    void safeYTCall('stopVideo');
    void safeYTCall('pauseVideo');
    if (state.embedController) void safeControllerCall('pause');

    clearQueue();
    resetPlaybackState();

    if (state.frameDoc) {
      markSpotifyItemSelected(state.frameDoc, false);
      removeOverlay(state.frameDoc);
      syncTopTransportControls(state.frameDoc);
      restoreNativeStationLabel(state.frameDoc);
      restoreNativeAudio(state.frameDoc);
      syncNativeMusicControls(state.frameDoc);
    }
  }

  // ---------------------------------------------------------------------------
  // Native audio suppression — keep NT's own radio silent while Spotify is on
  // ---------------------------------------------------------------------------

  function suppressNativeAudio(doc) {
    // Respect the mute native station toggle from mod menu
    if (!isMusicMuteNativeEnabled()) return;
    forceNativeMusicOff(doc, { force: true });
    const hardMute = shouldHardMuteNativeAudio(doc);

    // 1) Hard mute only when SFX are disabled. If SFX are enabled, use
    //    a softer stop strategy so Nitro's effect sounds can still play.
    if (hardMute) {
      lockHowlerVolume(true);
    } else {
      lockHowlerVolume(false);
    }
    syncTrackedAudioContextsSuspended(hardMute);

    // 2) PostMessage tells NT's music controller to stop
    try { window.top.postMessage({ type: 'music-player', action: 'stop' }, '*'); } catch (_) { }

    // 3) Hard-stop Howler only when SFX are disabled. When SFX are enabled,
    //    rely on the native Music OFF toggle instead so sound effects can keep
    //    using Nitro's shared audio stack.
    if (hardMute) {
      stopHowlerPlayback(doc, { hardMute });
    }

    // 4) Belt-and-suspenders muting for tag-based audio/video, but only in
    //    hard-mute mode so sound effects can continue when SFX are enabled.
    if (hardMute) {
      muteAudioElements(doc);
      muteAudioElements(document);
    }
  }

  function restoreNativeAudio(doc) {
    lockHowlerVolume(false);
    syncTrackedAudioContextsSuspended(false);
    unmuteAudioElements(doc);
    unmuteAudioElements(document);
  }

  function stopHowlerPlayback(doc, options) {
    const opts = options || {};
    const hardMute = Boolean(opts.hardMute);
    var wins = [];
    try { wins.push(getPageWindow()); } catch (_) { }
    try { if (state.frameWindow) wins.push(state.frameWindow); } catch (_) { }

    for (var i = 0; i < wins.length; i += 1) {
      var win = wins[i];
      if (!win) continue;
      try {
        if (!win.Howler) continue;
        try { win.Howler.stop(); } catch (_) { }

        var howls = win.Howler._howls;
        if (howls && howls.length) {
          for (var h = 0; h < howls.length; h += 1) {
            try {
              if (hardMute) {
                try { howls[h].mute(true); } catch (_) { }
                try { howls[h].volume(0); } catch (_) { }
              }
              try { howls[h].stop(); } catch (_) { }
            } catch (_) { }
          }
        }

        if (hardMute) {
          try {
            var hCtx = win.Howler.ctx || win.Howler._ctx;
            if (hCtx && hCtx.state === 'running') hCtx.suspend();
          } catch (_) { }
        }
      } catch (_) { }
    }
  }

  // ---------------------------------------------------------------------------
  // Howler.js property lock
  // ---------------------------------------------------------------------------
  // NT uses Howler.js for its music.  Simply calling Howler.mute(true) or
  // Howler.stop() is not enough because NT's own code immediately calls
  // Howler.mute(false) or plays new sounds on each race load.
  //
  // Instead we use Object.defineProperty to replace Howler._volume and
  // Howler._muted with locked getters that always return 0 / true.
  // This makes it physically impossible for Howler to produce audio until
  // we remove the lock — no amount of React re-renders or NT JS can
  // override it.
  // ---------------------------------------------------------------------------

  function lockHowlerVolume(lock) {
    var wins = [];
    try { wins.push(getPageWindow()); } catch (_) { }
    try { if (state.frameWindow) wins.push(state.frameWindow); } catch (_) { }

    for (var i = 0; i < wins.length; i += 1) {
      var win = wins[i];
      if (!win) continue;
      try {
        if (!win.Howler) continue;

        if (lock) {
          if (!win._ntHowlerLocked) {
            // Save originals
            win._ntHowlerOrigVol = win.Howler._volume;
            win._ntHowlerOrigMuted = win.Howler._muted;

            // Lock _volume → always 0
            Object.defineProperty(win.Howler, '_volume', {
              get: function () { return 0; },
              set: function () { /* no-op — NT can't change it */ },
              configurable: true,
            });

            // Lock _muted → always true
            Object.defineProperty(win.Howler, '_muted', {
              get: function () { return true; },
              set: function () { /* no-op */ },
              configurable: true,
            });

            win._ntHowlerLocked = true;
          }

          // Stop all currently-playing sounds (safe to call repeatedly)
          try { win.Howler.stop(); } catch (_) { }

          // Also mute + stop each individual Howl instance
          try {
            var howls = win.Howler._howls;
            if (howls && howls.length) {
              for (var h = 0; h < howls.length; h += 1) {
                try { howls[h].mute(true); } catch (_) { }
                try { howls[h].stop(); } catch (_) { }
                try { howls[h].volume(0); } catch (_) { }
              }
            }
          } catch (_) { }

          // Also suspend Howler's AudioContext so decode/playback truly halts
          try {
            var hCtx = win.Howler.ctx || win.Howler._ctx;
            if (hCtx && hCtx.state === 'running') hCtx.suspend();
          } catch (_) { }

        } else {
          // --- Unlock ---
          if (win._ntHowlerLocked) {
            // Remove our property overrides — reveals the original instance value
            delete win.Howler._volume;
            delete win.Howler._muted;

            // Restore saved values
            win.Howler._volume = (win._ntHowlerOrigVol !== undefined)
              ? win._ntHowlerOrigVol : 1;
            win.Howler._muted = !!win._ntHowlerOrigMuted;

            // Resume AudioContext
            try {
              var hCtx2 = win.Howler.ctx || win.Howler._ctx;
              if (hCtx2 && hCtx2.state === 'suspended') hCtx2.resume();
            } catch (_) { }

            win._ntHowlerLocked = false;
            delete win._ntHowlerOrigVol;
            delete win._ntHowlerOrigMuted;
          }
        }
      } catch (e) {
        console.warn('[NT Music] lockHowlerVolume error:', e);
      }
    }
  }

  function syncTrackedAudioContextsSuspended(shouldSuspend) {
    var wins = [];
    try { wins.push(getPageWindow()); } catch (_) { }
    try { if (state.frameWindow) wins.push(state.frameWindow); } catch (_) { }

    for (var i = 0; i < wins.length; i += 1) {
      var win = wins[i];
      if (!win || !win._ntAudioContexts || !win._ntAudioContexts.length) continue;
      for (var j = 0; j < win._ntAudioContexts.length; j += 1) {
        var ctx = win._ntAudioContexts[j];
        if (!ctx || !ctx.state) continue;
        try {
          if (shouldSuspend) {
            if (ctx.state === 'running') ctx.suspend();
          } else if (ctx.state === 'suspended') {
            ctx.resume();
          }
        } catch (_) { }
      }
    }
  }

  function clearPendingSfxSync() {
    if (state._pendingSfxSyncTimer) {
      window.clearTimeout(state._pendingSfxSyncTimer);
      state._pendingSfxSyncTimer = 0;
    }
  }

  function reinforceNativeMusicOff(doc, delays) {
    const targetDoc = doc || state.frameDoc;
    if (!targetDoc || !isSpotifyUiSelected()) return;
    const steps = Array.isArray(delays) ? delays : [0, 60, 160, 320];
    steps.forEach((delay) => {
      window.setTimeout(() => {
        if (!isSpotifyUiSelected() || !state.frameDoc) return;
        try { forceNativeMusicOff(state.frameDoc, { force: true }); } catch (_) { }
        try { window.top.postMessage({ type: 'music-player', action: 'stop' }, '*'); } catch (_) { }
      }, Math.max(0, Number(delay) || 0));
    });
  }

  function scheduleNativeSfxSync(doc) {
    const targetDoc = doc || state.frameDoc;
    if (!targetDoc || !isSpotifyUiSelected()) return;

    clearPendingSfxSync();
    state._pendingSfxSyncTimer = window.setTimeout(() => {
      state._pendingSfxSyncTimer = 0;
      const liveDoc = state.frameDoc || targetDoc;
      if (!liveDoc || !isSpotifyUiSelected()) return;

      if (isNativeSfxEnabled(liveDoc)) {
        // Let Nitro's SFX stack run again, but keep native music forced off.
        restoreNativeAudio(liveDoc);
        reinforceNativeMusicOff(liveDoc, [0, 40, 120, 260, 500]);
      } else {
        suppressNativeAudio(liveDoc);
      }
    }, 0);
  }

  /**
   * Intercept AudioContext creation on the given window so we can
   * suspend/resume contexts created AFTER our script loads.
   */
  function installAudioContextTracker(win) {
    if (!win || win._ntAudioCtxTracked) return;
    try {
      win._ntAudioContexts = win._ntAudioContexts || [];
      const OrigAC = win.AudioContext || win.webkitAudioContext;
      if (!OrigAC) return;

      const WrappedAC = function () {
        const ctx = arguments.length ? new OrigAC(arguments[0]) : new OrigAC();
        win._ntAudioContexts.push(ctx);
        if (isSpotifyUiSelected() && shouldHardMuteNativeAudio(state.frameDoc)) {
          try { ctx.suspend(); } catch (_) { }
        }
        return ctx;
      };
      WrappedAC.prototype = OrigAC.prototype;
      if (win.AudioContext) win.AudioContext = WrappedAC;
      if (win.webkitAudioContext) win.webkitAudioContext = WrappedAC;
      win._ntAudioCtxTracked = true;
    } catch (_) { }
  }

  function muteAudioElements(root) {
    if (!root) return;
    try {
      const els = root.querySelectorAll('audio, video');
      for (let i = 0; i < els.length; i += 1) {
        const el = els[i];
        // Don't mute our own YouTube player
        if (el.closest && el.closest('#nt-yt-player-host')) continue;
        try { el.muted = true; el.pause(); } catch (_) { }
      }
    } catch (_) { }
  }

  function unmuteAudioElements(root) {
    if (!root) return;
    try {
      const els = root.querySelectorAll('audio, video');
      for (let i = 0; i < els.length; i += 1) {
        const el = els[i];
        if (el.closest && el.closest('#nt-yt-player-host')) continue;
        try { el.muted = false; } catch (_) { }
      }
    } catch (_) { }
  }

  // ---------------------------------------------------------------------------
  // MutationObserver — catch <audio>/<video> elements the instant they appear
  // ---------------------------------------------------------------------------

  function startAudioObservers(doc) {
    stopAudioObservers();

    const callback = function (mutations) {
      if (!isSpotifyUiSelected()) return;
      if (!shouldHardMuteNativeAudio(state.frameDoc)) return;
      for (let i = 0; i < mutations.length; i += 1) {
        const added = mutations[i].addedNodes;
        if (!added) continue;
        for (let j = 0; j < added.length; j += 1) {
          const node = added[j];
          if (node.nodeType !== 1) continue;
          const tag = node.tagName;
          if (tag === 'AUDIO' || tag === 'VIDEO') {
            if (node.closest && node.closest('#nt-yt-player-host')) continue;
            try { node.muted = true; node.pause(); } catch (_) { }
          }
          if (typeof node.querySelectorAll === 'function') {
            try {
              const els = node.querySelectorAll('audio, video');
              for (let k = 0; k < els.length; k += 1) {
                const el = els[k];
                if (el.closest && el.closest('#nt-yt-player-host')) continue;
                try { el.muted = true; el.pause(); } catch (_) { }
              }
            } catch (_) { }
          }
        }
      }
    };

    const opts = { childList: true, subtree: true };

    if (doc && doc.body) {
      try {
        const obs = new MutationObserver(callback);
        obs.observe(doc.body, opts);
        state._audioObserverDoc = obs;
      } catch (_) { }
    }

    if (document.body && document !== doc) {
      try {
        const obs = new MutationObserver(callback);
        obs.observe(document.body, opts);
        state._audioObserverTop = obs;
      } catch (_) { }
    }
  }

  function stopAudioObservers() {
    if (state._audioObserverDoc) {
      try { state._audioObserverDoc.disconnect(); } catch (_) { }
      state._audioObserverDoc = null;
    }
    if (state._audioObserverTop) {
      try { state._audioObserverTop.disconnect(); } catch (_) { }
      state._audioObserverTop = null;
    }
  }

  function resetPlaybackState() {
    state.playback.uri = '';
    state.playback.position = 0;
    state.playback.duration = 0;
    state.playback.isPaused = true;
    state.playback.isBuffering = false;
    state.playback.updatedAt = 0;
  }

  var _lastSessionSaveTime = 0;
  var SESSION_SAVE_INTERVAL_MS = 15000; // save session state every 15s

  function startProgressTimer() {
    stopProgressTimer();
    state.progressTimer = window.setInterval(() => {
      if (!state.active) return;
      syncPlaybackFromYT();
      renderNowPlaying();

      // Periodically persist session state for resume-on-reload
      var now = Date.now();
      if (now - _lastSessionSaveTime > SESSION_SAVE_INTERVAL_MS) {
        _lastSessionSaveTime = now;
        saveSessionState();
      }
    }, getProgressTickMs());
  }

  function stopProgressTimer() {
    if (state.progressTimer) {
      window.clearInterval(state.progressTimer);
      state.progressTimer = null;
    }
  }

  function getDisplayPlayback() {
    const meta = state.trackMeta;
    const hasUri = Boolean(state.playback.uri || (meta && meta.uri));

    if (!hasUri) {
      return {
        title: 'Nothing playing',
        artist: '',
        art: '',
        status: 'SPOTIFY',
        progressPct: 0,
      };
    }

    let position = state.playback.position;
    const duration = state.playback.duration;

    if (!state.playback.isPaused && state.playback.updatedAt) {
      if (getActivePlaybackEngine() === 'embed') {
        position += Date.now() - state.playback.updatedAt;
      } else {
        const player = state.ytPlayer;
        if (player && typeof player.getCurrentTime === 'function') {
          position = Math.max(0, Math.round((Number(player.getCurrentTime()) || 0) * 1000));
        } else {
          position += Date.now() - state.playback.updatedAt;
        }
      }
    }

    if (duration > 0) {
      position = Math.max(0, Math.min(duration, position));
    } else {
      position = Math.max(0, position);
    }

    const progressPct = duration > 0 ? (position / duration) * 100 : 0;

    return {
      title: meta && meta.title ? meta.title : 'Loading track...',
      artist: meta && meta.artist ? meta.artist : '',
      art: meta && meta.art ? meta.art : '',
      status: state.playback.isPaused ? 'PAUSED' : 'NOW PLAYING',
      progressPct: Math.max(0, Math.min(100, progressPct)),
    };
  }

  function renderNowPlaying() {
    const doc = state.frameDoc;
    if (!doc || !state.active) return;

    let nodes = getOverlayNodes(doc);
    if (!nodes) {
      renderOverlay(doc);
      nodes = getOverlayNodes(doc);
    }
    if (!nodes) {
      syncTopTransportControls(doc);
      return;
    }

    const display = getDisplayPlayback();

    nodes.overlay.style.display = 'flex';
    const showArt = display.art && isMusicAlbumArtEnabled();
    nodes.coverWrap.style.display = showArt ? 'block' : 'none';

    if (showArt) {
      nodes.cover.src = display.art;
      nodes.cover.alt = `${display.title} cover`;
    } else {
      nodes.cover.removeAttribute('src');
      nodes.cover.alt = '';
    }

    nodes.title.textContent = display.title;
    nodes.title.title = display.title;
    nodes.artist.textContent = display.artist;
    nodes.artist.title = display.artist;
    syncTopTransportControls(doc);
  }

  function renderOverlay(doc) {
    injectTopTransportControls(doc);
    const transport = doc.querySelector('.nt-spotify-transport');
    if (!transport) return;
    if (transport.querySelector('.nt-spotify-nowplaying-inline')) return;

    const overlay = doc.createElement('div');
    overlay.className = 'nt-spotify-nowplaying-inline';
    overlay.title = 'Click to edit Spotify URL';
    overlay.style.cssText = [
      'margin-left:4px',
      'display: flex',
      'align-items: center',
      'gap: 6px',
      'width: 180px',
      'padding: 1px 6px',
      'border-radius: 8px',
      'background: transparent',
      'border: none',
      'color: #fff',
      'font-size: 10px',
      'line-height: 1.1',
      'flex-shrink: 0',
      'overflow: hidden',
      'pointer-events: auto',
      'cursor: pointer',
    ].join(';');

    const coverWrap = doc.createElement('div');
    coverWrap.className = 'nt-spotify-cover-wrap';
    coverWrap.style.cssText = 'width:18px;height:18px;flex-shrink:0;display:none;';

    const cover = doc.createElement('img');
    cover.className = 'nt-spotify-cover';
    cover.style.cssText = 'width:18px;height:18px;border-radius:4px;display:block;';
    coverWrap.appendChild(cover);

    const meta = doc.createElement('div');
    meta.style.cssText = 'flex:1;min-width:0;';

    const title = doc.createElement('div');
    title.className = 'nt-spotify-title';
    title.style.cssText = 'font-weight:700;font-size:9px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%;';

    const artist = doc.createElement('div');
    artist.className = 'nt-spotify-artist';
    artist.style.cssText = 'color:#c9c9c9;font-size:8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%;';

    meta.appendChild(title);
    meta.appendChild(artist);

    overlay.appendChild(coverWrap);
    overlay.appendChild(meta);

    transport.insertBefore(overlay, transport.firstChild || null);
  }

  function removeOverlay(doc) {
    const overlay = doc.querySelector('.nt-spotify-nowplaying-inline');
    if (overlay) overlay.remove();
  }

  function getOverlayNodes(doc) {
    const overlay = doc.querySelector('.nt-spotify-nowplaying-inline');
    if (!overlay) return null;

    return {
      overlay,
      coverWrap: overlay.querySelector('.nt-spotify-cover-wrap'),
      cover: overlay.querySelector('.nt-spotify-cover'),
      title: overlay.querySelector('.nt-spotify-title'),
      artist: overlay.querySelector('.nt-spotify-artist'),
    };
  }

  function injectSpotifyItem(doc) {
    const list = doc.querySelector('.music-selector--list');
    if (!list) return;

    let item = list.querySelector('.nt-spotify-item');
    if (!item) {
      item = doc.createElement('div');
      item.className = 'music-selector--list--item nt-spotify-item';
      item.setAttribute('data-station-id', STATION_ID);

      const name = doc.createElement('div');
      name.className = 'music-selector--list--name';
      name.style.cssText = 'display:flex;align-items:center;gap:6px;';

      const iconWrap = doc.createElement('span');
      iconWrap.className = 'nt-spotify-item-icon';
      iconWrap.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;flex-shrink:0;';
      iconWrap.appendChild(createSpotifyIcon(doc, 14));
      name.appendChild(iconWrap);

      const label = doc.createElement('span');
      label.className = 'nt-spotify-item-label';
      name.appendChild(label);

      const note = doc.createElement('div');
      note.className = 'music-selector--list--broadcasting nt-spotify-item-note';
      note.style.display = 'none';
      note.style.alignItems = 'center';

      const broadcastIcon = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
      broadcastIcon.setAttribute('class', 'icon icon-broadcast');
      const use = doc.createElementNS('http://www.w3.org/2000/svg', 'use');
      use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '/dist/site/images/icons/icons.css.svg#icon-broadcast');
      broadcastIcon.appendChild(use);
      note.appendChild(broadcastIcon);

      item.appendChild(name);
      item.appendChild(note);
      list.appendChild(item);
    }

    const legacyControls = item.querySelector('.nt-spotify-controls');
    if (legacyControls) legacyControls.remove();

    if (item.dataset.ntSpotifyBound !== '1') {
      ['pointerdown', 'mousedown', 'touchstart', 'click'].forEach((type) => {
        item.addEventListener(type, handleSpotifyItemActivation, true);
      });
      item.addEventListener('keydown', (event) => {
        if (event.key !== 'Enter' && event.key !== ' ') return;
        handleSpotifyItemActivation(event);
      }, true);
      item.dataset.ntSpotifyBound = '1';
    }

    // Update label and icon to reflect current platform
    const currentLabel = getSpotifyMenuLabel();
    const saved = getSavedSourceParsed();
    const platform = saved ? saved.platform : 'spotify';

    const labelNode = item.querySelector('.nt-spotify-item-label');
    if (labelNode) {
      labelNode.textContent = currentLabel;
    }

    const iconWrap = item.querySelector('.nt-spotify-item-icon');
    if (iconWrap) {
      const currentPlatform = iconWrap.getAttribute('data-platform') || 'spotify';
      if (currentPlatform !== platform) {
        iconWrap.replaceChildren(createPlatformIcon(doc, 14, platform));
        iconWrap.setAttribute('data-platform', platform);
      }
    }

    syncSpotifyControlValues(doc, item);
    item.style.cursor = 'pointer';
    markSpotifyItemSelected(doc, isSpotifyUiSelected());
  }

  // bindCurrentStationStart removed — handled by handleGlobalClick event delegation

  function injectTopTransportControls(doc) {
    if (!doc) return;

    const musicRoot = doc.querySelector('.race-header-controls--music');
    if (!musicRoot) return;

    let transport = musicRoot.querySelector('.nt-spotify-transport');
    if (!transport) {
      transport = doc.createElement('div');
      transport.className = 'nt-spotify-transport';
      transport.style.cssText = [
        'display:none',
        'align-items:center',
        'flex-wrap:nowrap',
        'overflow:hidden',
        'gap:2px',
        'margin-left:6px',
        'padding:3px 6px',
        'border-radius:10px',
        'background: rgba(0, 0, 0, 0.6)',
        'border: none',
        'pointer-events:auto',
        'position:relative',
        'z-index:5',
      ].join(';');

      const prevBtn = createTransportIconButton(doc, 'nt-spotify-top-prev-btn', 'prev', false, 'Previous');
      const playBtn = createTransportIconButton(doc, 'nt-spotify-top-play-btn', 'play', true, 'Play / Pause');
      const nextBtn = createTransportIconButton(doc, 'nt-spotify-top-next-btn', 'next', false, 'Next');
      const shuffleBtn = createTransportIconButton(doc, 'nt-spotify-top-shuffle-btn', 'shuffle', false, 'Shuffle On/Off');
      const linkBtn = createTransportIconButton(doc, 'nt-spotify-top-link-btn', 'link', false, 'Set Spotify URL');

      transport.appendChild(prevBtn);
      transport.appendChild(playBtn);
      transport.appendChild(nextBtn);
      transport.appendChild(shuffleBtn);
      transport.appendChild(linkBtn);

      const editor = doc.createElement('div');
      editor.className = 'nt-spotify-inline-editor';
      editor.style.cssText = [
        'display:none',
        'position:absolute',
        'top:100%',
        'left:0',
        'min-width:340px',
        'width:max-content',
        'max-width:min(520px, calc(100vw - 32px))',
        'margin-top:4px',
        'align-items:center',
        'gap:6px',
        'background:rgba(0,0,0,0.85)',
        'border-radius:8px',
        'padding:4px 6px',
        'border:1px solid rgba(255,255,255,0.18)',
        'box-shadow:0 8px 20px rgba(0,0,0,0.32)',
        'box-sizing:border-box',
        'z-index:10',
      ].join(';');

      const input = doc.createElement('input');
      input.className = 'nt-spotify-top-url-input';
      input.type = 'text';
      input.placeholder = 'Paste Spotify, YouTube, or Apple Music URL';
      input.autocomplete = 'off';
      input.spellcheck = false;
      input.style.cssText = [
        'width:100%',
        'flex:1 1 auto',
        'min-width:170px',
        'box-sizing:border-box',
        'height:26px',
        'padding:4px 8px',
        'border-radius:6px',
        'border:1px solid rgba(255,255,255,0.22)',
        'background:rgba(0,0,0,0.45)',
        'color:#fff',
        'font-size:11px',
        'outline:none',
      ].join(';');

      const saveBtn = createSpotifyControlButton(doc, 'Save', 'nt-spotify-top-save-btn', true);
      const clearBtn = createSpotifyControlButton(doc, 'Clear', 'nt-spotify-top-clear-btn', false);

      editor.appendChild(input);
      editor.appendChild(saveBtn);
      editor.appendChild(clearBtn);

      // Position transport + editor wrapper so the editor can drop below
      // without being clipped by the transport's overflow:hidden.
      const wrapper = doc.createElement('div');
      wrapper.className = 'nt-spotify-transport-wrapper';
      wrapper.style.cssText = 'position:relative;display:inline-flex;align-items:center;z-index:5;';
      wrapper.appendChild(transport);
      wrapper.appendChild(editor);
      transport.dataset.editorOpen = '0';

      const stationSelector = musicRoot.querySelector('.race-header-controls--station-selector');
      if (stationSelector && stationSelector.parentElement === musicRoot) {
        stationSelector.insertAdjacentElement('afterend', wrapper);
      } else {
        musicRoot.appendChild(wrapper);
      }
    }

    const oldStopBtn = transport.querySelector('.nt-spotify-top-stop-btn');
    if (oldStopBtn) oldStopBtn.remove();

    if (!transport.dataset.ntSpotifyBound) {
      const wrapper = transport.closest('.nt-spotify-transport-wrapper') || transport.parentElement;
      const prevBtn = transport.querySelector('.nt-spotify-top-prev-btn');
      const playBtn = transport.querySelector('.nt-spotify-top-play-btn');
      const nextBtn = transport.querySelector('.nt-spotify-top-next-btn');
      const shuffleBtn = transport.querySelector('.nt-spotify-top-shuffle-btn');
      const linkBtn = transport.querySelector('.nt-spotify-top-link-btn');
      const input = wrapper.querySelector('.nt-spotify-top-url-input');
      const saveBtn = wrapper.querySelector('.nt-spotify-top-save-btn');
      const clearBtn = wrapper.querySelector('.nt-spotify-top-clear-btn');

      if (prevBtn) {
        prevBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        prevBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          void playPrevInQueue();
        });
      }

      if (playBtn) {
        playBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        playBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          void toggleSpotifyPlaybackFromControls();
        });
      }

      if (nextBtn) {
        nextBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        nextBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          void playNextInQueue();
        });
      }

      if (shuffleBtn) {
        shuffleBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        shuffleBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          toggleShuffleMode();
          syncTopTransportControls(doc);
        });
      }

      if (linkBtn) {
        linkBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        linkBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          const isOpen = isInlineEditorOpen(transport);
          setInlineEditorOpen(doc, !isOpen);
        });
      }

      if (input) {
        input.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
          setFrameFocusLock(true);
        });
        input.addEventListener('mousedown', (event) => {
          event.stopPropagation();
          setFrameFocusLock(true);
        });
        input.addEventListener('click', (event) => {
          event.stopPropagation();
        });
        const stopKeyEvent = (event) => {
          if (!event) return;
          event.stopPropagation();
          if (typeof event.stopImmediatePropagation === 'function') {
            event.stopImmediatePropagation();
          }
        };
        input.addEventListener('keydown', (event) => {
          stopKeyEvent(event);
          if (event.key === 'Enter') {
            event.preventDefault();
            void parseAndSaveSourceFromInput(input.value, true);
          }
        }, true);
        input.addEventListener('keyup', stopKeyEvent, true);
        input.addEventListener('keypress', stopKeyEvent, true);
        input.addEventListener('focus', () => {
          setFrameFocusLock(true);
          input.classList.add('protected');
        });
        input.addEventListener('blur', () => {
          input.classList.remove('protected');
          window.setTimeout(() => {
            const active = doc.activeElement;
            const stillInEditor = active && typeof active.closest === 'function'
              ? active.closest('.nt-spotify-inline-editor')
              : null;
            if (!stillInEditor) {
              setFrameFocusLock(false);
            }
          }, 75);
        });
      }

      if (saveBtn && input) {
        saveBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        saveBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          void parseAndSaveSourceFromInput(input.value, true);
        });
      }

      if (clearBtn) {
        clearBtn.addEventListener('pointerdown', (event) => {
          event.stopPropagation();
        });
        clearBtn.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();
          clearSavedSource();
          setInlineEditorOpen(doc, true);
        });
      }

      transport.addEventListener('click', (event) => {
        const nowPlaying = event.target && event.target.closest
          ? event.target.closest('.nt-spotify-nowplaying-inline')
          : null;
        if (!nowPlaying) return;

        event.preventDefault();
        event.stopPropagation();
        const isOpen = isInlineEditorOpen(transport);
        setInlineEditorOpen(doc, !isOpen);
      });

      transport.addEventListener('pointerdown', (event) => {
        const nowPlaying = event.target && event.target.closest
          ? event.target.closest('.nt-spotify-nowplaying-inline')
          : null;
        if (!nowPlaying) return;
        event.stopPropagation();
      });

      const stopTransportEvent = (event) => {
        if (!event) return;
        event.stopPropagation();
        if (typeof event.stopImmediatePropagation === 'function') {
          event.stopImmediatePropagation();
        }
      };
      transport.addEventListener('mousedown', stopTransportEvent, false);
      transport.addEventListener('click', stopTransportEvent, false);
      transport.addEventListener('keydown', (event) => {
        const insideEditor = event.target && event.target.closest
          ? event.target.closest('.nt-spotify-inline-editor')
          : null;
        if (!insideEditor) return;
        stopTransportEvent(event);
      }, true);
      transport.addEventListener('keyup', (event) => {
        const insideEditor = event.target && event.target.closest
          ? event.target.closest('.nt-spotify-inline-editor')
          : null;
        if (!insideEditor) return;
        stopTransportEvent(event);
      }, true);
      transport.addEventListener('keypress', (event) => {
        const insideEditor = event.target && event.target.closest
          ? event.target.closest('.nt-spotify-inline-editor')
          : null;
        if (!insideEditor) return;
        stopTransportEvent(event);
      }, true);

      if (input) {
        const savedUrl = String(GM_getValue(STORAGE_KEYS.sourceUrl, '') || '').trim();
        input.value = savedUrl;
      }

      wrapper.addEventListener('mouseleave', () => {
        if (!isInlineEditorOpen(transport)) return;
        const active = doc.activeElement;
        const inputEl = wrapper.querySelector('.nt-spotify-top-url-input');
        if (active && inputEl && active === inputEl) return;
        setInlineEditorOpen(doc, false);
      });

      const nowPlayingMeta = transport.querySelector('.nt-spotify-nowplaying-inline');
      if (nowPlayingMeta) {
        nowPlayingMeta.addEventListener('keydown', (event) => {
          if (event.key !== 'Enter' && event.key !== ' ') return;
          event.preventDefault();
          const isOpen = isInlineEditorOpen(transport);
          setInlineEditorOpen(doc, !isOpen);
        });
      }

      transport.dataset.ntSpotifyBound = '1';
    }
  }

  function setInlineEditorOpen(doc, open) {
    if (!doc) return;
    const transport = doc.querySelector('.nt-spotify-transport');
    if (!transport) return;
    // Editor is a sibling of transport inside the wrapper, not a child
    const wrapper = transport.closest('.nt-spotify-transport-wrapper') || transport.parentElement;
    const editor = wrapper
      ? wrapper.querySelector('.nt-spotify-inline-editor')
      : transport.querySelector('.nt-spotify-inline-editor');
    if (!editor) return;

    const isOpen = Boolean(open);
    editor.style.display = isOpen ? 'flex' : 'none';
    transport.dataset.editorOpen = isOpen ? '1' : '0';
    setFrameFocusLock(isOpen);

    if (isOpen) {
      const input = editor.querySelector('.nt-spotify-top-url-input');
      const savedUrl = String(GM_getValue(STORAGE_KEYS.sourceUrl, '') || '').trim();
      if (input && doc.activeElement !== input) {
        input.value = savedUrl;
        window.setTimeout(() => {
          try {
            input.focus();
            input.select();
          } catch (error) {
            // noop
          }
        }, 0);
      }
    } else if (!state.active && !state.pendingSelection) {
      // Editor closed and Spotify isn't active — hide the transport bar
      transport.style.display = 'none';
      markSpotifyItemSelected(doc, false);
      restoreNativeStationLabel(doc);
    }
  }

  function toggleShuffleMode() {
    state.queueMode = state.queueMode === 'shuffle' ? 'sequential' : 'shuffle';
    setNtcfgMusicValue('QUEUE_MODE', state.queueMode);

    if (state.queueMode === 'shuffle') {
      if (state.queue.length > 1) {
        generateShuffleOrder(state.queueIndex >= 0 ? state.queueIndex : 0);
      }
      toast('Shuffle ON');
    } else {
      state.shuffleOrder = [];
      state.shufflePosition = -1;
      toast('Shuffle OFF');
    }

    schedulePreResolveNextTrack(250);
  }

  function createSpotifyControlButton(doc, label, className, isPrimary) {
    const btn = doc.createElement('button');
    btn.type = 'button';
    btn.className = className;
    btn.textContent = label;
    btn.style.cssText = [
      'appearance:none',
      `border:1px solid ${isPrimary ? 'rgba(29,185,84,0.6)' : 'rgba(255,255,255,0.22)'}`,
      `background:${isPrimary ? 'linear-gradient(90deg, rgba(29,185,84,0.25), rgba(29,185,84,0.1))' : 'rgba(0,0,0,0.32)'}`,
      'color:#fff',
      'border-radius:6px',
      'padding:4px 10px',
      'font-size:11px',
      'font-weight:600',
      'cursor:pointer',
      'line-height:1.2',
      'white-space:nowrap',
      'transition:opacity .15s ease',
    ].join(';');
    return btn;
  }

  function createTransportIconButton(doc, className, iconType, isPrimary, ariaLabel) {
    const btn = doc.createElement('button');
    btn.type = 'button';
    btn.className = className;
    btn.setAttribute('aria-label', ariaLabel || iconType || 'control');
    btn.style.cssText = [
      'appearance:none',
      'border:none',
      'background:rgba(255,255,255,0.04)',
      'color:#fff',
      'border-radius:4px',
      'width:28px',
      'height:28px',
      'display:inline-flex',
      'align-items:center',
      'justify-content:center',
      'cursor:pointer',
      'transition:opacity .15s ease, background-color .15s ease',
      'padding:0',
      'line-height:1',
      'flex:0 0 auto',
    ].join(';');

    btn.dataset.iconType = iconType;
    const icon = createTransportIcon(doc, iconType);
    if (icon) btn.appendChild(icon);
    return btn;
  }

  function createTransportIcon(doc, type) {
    const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '15');
    svg.setAttribute('height', '15');
    svg.setAttribute('aria-hidden', 'true');
    svg.style.display = 'block';

    const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('fill', '#ffffff');

    if (type === 'play') {
      path.setAttribute('d', 'M8 5v14l11-7z');
    } else if (type === 'pause') {
      path.setAttribute('d', 'M7 5h4v14H7zm6 0h4v14h-4z');
    } else if (type === 'prev') {
      path.setAttribute('d', 'M6 6h3v12H6zm4.5 6L18 6v12z');
    } else if (type === 'next') {
      path.setAttribute('d', 'M15 6h3v12h-3zM6 6l7.5 6L6 18z');
    } else if (type === 'shuffle') {
      path.setAttribute('d', 'M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zM14.83 13.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z');
    } else if (type === 'link') {
      path.setAttribute('d', 'M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z');
    } else {
      path.setAttribute('d', 'M8 5v14l11-7z');
    }

    svg.appendChild(path);
    return svg;
  }

  function syncSpotifyControlValues(doc, item) {
    if (!doc) return;

    const transport = doc.querySelector('.nt-spotify-transport');
    const wrapper = transport ? (transport.closest('.nt-spotify-transport-wrapper') || transport.parentElement) : null;
    const input = wrapper
      ? wrapper.querySelector('.nt-spotify-top-url-input')
      : (item ? item.querySelector('.nt-spotify-top-url-input') : null);
    const savedUrl = String(GM_getValue(STORAGE_KEYS.sourceUrl, '') || '').trim();

    if (input && doc.activeElement !== input) {
      input.value = savedUrl;
    }
  }

  async function toggleSpotifyPlaybackFromControls() {
    if (!state.active) {
      await activateSpotifyMode({ preferSavedOnly: true });
      return;
    }

    if (getActivePlaybackEngine() === 'embed') {
      void safeControllerCall('togglePlay');
    } else {
      if (state.playback.isPaused) {
        safeYTCall('playVideo');
        state.playback.isPaused = false;
        state.playback.updatedAt = Date.now();
      } else {
        safeYTCall('pauseVideo');
        syncPlaybackFromYT();
        state.playback.isPaused = true;
        state.playback.updatedAt = Date.now();
      }
    }

    renderNowPlaying();
    refreshSpotifyUI();
  }

  function syncTopTransportControls(doc) {
    if (!doc) return;
    const transport = doc.querySelector('.nt-spotify-transport');
    if (!transport) return;

    // Hide the entire transport bar when Spotify is not active
    // (but keep it visible if the editor is open for URL entry)
    const editorOpen = isInlineEditorOpen(transport);
    transport.style.display = (isSpotifyUiSelected() || editorOpen) ? 'flex' : 'none';

    if (!state.active) return;
    if (isSpotifyUiSelected() && !areNativeStationEventsSuppressed() && looksLikeNativeStationSelection(doc)) return;

    const playBtn = transport.querySelector('.nt-spotify-top-play-btn');
    const prevBtn = transport.querySelector('.nt-spotify-top-prev-btn');
    const nextBtn = transport.querySelector('.nt-spotify-top-next-btn');
    const shuffleBtn = transport.querySelector('.nt-spotify-top-shuffle-btn');

    if (playBtn) {
      const iconType = state.playback.isPaused ? 'play' : 'pause';
      const currentIcon = String(playBtn.dataset.iconType || '');
      if (currentIcon !== iconType || !playBtn.querySelector('svg')) {
        playBtn.dataset.iconType = iconType;
        playBtn.replaceChildren(createTransportIcon(doc, iconType));
      }
    }

    [prevBtn, nextBtn].forEach((btn) => {
      if (!btn) return;
      btn.disabled = false;
      btn.style.opacity = '1';
      btn.style.pointerEvents = 'auto';
    });

    if (shuffleBtn) {
      const isShuffle = state.queueMode === 'shuffle';
      shuffleBtn.style.opacity = isShuffle ? '1' : '0.65';
      shuffleBtn.style.background = isShuffle
        ? 'rgba(29,185,84,0.28)'
        : 'rgba(255,255,255,0.04)';
      shuffleBtn.style.pointerEvents = 'auto';
      shuffleBtn.disabled = false;
    }

    const savedSource = getSavedSourceParsed();
    setCurrentStationLabel(doc, getPlatformLabel(savedSource), savedSource ? savedSource.platform : undefined);
  }

  async function parseAndSaveSourceFromInput(rawInput, startAfterSave) {
    const parsed = parseSourceInput(rawInput);
    if (!parsed) {
      toast('Invalid URL — paste a Spotify, YouTube, or Apple Music link.');
      focusSpotifyInput();
      return null;
    }

    saveSource(parsed);
    toast(getPlatformLabel(parsed) + ' source saved.');
    refreshSpotifyUI();

    if (startAfterSave) {
      await activateSpotifyMode({ preferSavedOnly: true });
      return parsed;
    }

    if (state.active) {
      await loadSource(parsed.uri);
    }

    return parsed;
  }

  function clearSavedSource() {
    const shouldKeepSpotifySelected = isSpotifyUiSelected();
    if (state.active) {
      state.active = false;
      state.pendingSelection = shouldKeepSpotifySelected;
      state.suppressNativeStationEventsUntil = 0;
      clearSessionState();
      stopProgressTimer();
      void safeYTCall('setVolume', 0);
      void safeYTCall('stopVideo');
      void safeYTCall('pauseVideo');
      if (state.embedController) void safeControllerCall('pause');
      if (state.frameDoc) {
        removeOverlay(state.frameDoc);
      }
    }
    if (state.preCueTimer) {
      window.clearTimeout(state.preCueTimer);
      state.preCueTimer = null;
    }
    GM_deleteValue(STORAGE_KEYS.sourceUri);
    setNtcfgMusicValue('SOURCE_URL', '');
    state.trackMeta = null;
    resetPlaybackState();
    clearQueue();
    state.preCuePromise = null;
    state.preCueResult = null;
    state.preCueSourceUri = '';
    state.sourceTrackListCache.clear();
    if (shouldKeepSpotifySelected && state.frameDoc) {
      claimSpotifySelection(state.frameDoc, { openEditor: false });
    }
    toast('Saved source cleared.');
    refreshSpotifyUI();
  }

  function focusSpotifyInput() {
    const doc = state.frameDoc;
    if (!doc) return;
    const input = doc.querySelector('.nt-spotify-top-url-input') || doc.querySelector('.nt-spotify-top-url-input');
    if (!input) return;
    setInlineEditorOpen(doc, true);
    input.focus();
    input.select();
  }

  function getSpotifyMenuLabel() {
    const saved = getSavedSourceParsed();
    return getPlatformLabel(saved);
  }

  function markSpotifyItemSelected(doc, selected) {
    if (!doc) return;
    const item = doc.querySelector('.nt-spotify-item');
    if (!item) return;

    if (selected && isSpotifyUiSelected()) {
      const controls = getNativeMusicControls(doc);
      if (controls.selectedNative && !currentStationLooksSpotify(doc, controls) && !areNativeStationEventsSuppressed()) {
        return;
      }
    }

    if (selected) {
      const rows = doc.querySelectorAll('.music-selector--list--item');
      rows.forEach((row) => {
        if (!row || row === item) return;
        row.classList.remove('selected');
        const rowNote = row.querySelector('.music-selector--list--broadcasting');
        if (rowNote) rowNote.style.display = 'none';
      });
    }

    item.classList.toggle('selected', Boolean(selected));
    const note = item.querySelector('.nt-spotify-item-note');
    if (note) {
      note.style.display = selected ? 'flex' : 'none';
    }
  }

  function refreshSpotifyUI() {
    if (!state.frameDoc) return;
    injectSpotifyItem(state.frameDoc);
    injectTopTransportControls(state.frameDoc);
    markSpotifyItemSelected(state.frameDoc, isSpotifyUiSelected());
    const item = state.frameDoc.querySelector('.nt-spotify-item');
    if (item) {
      syncSpotifyControlValues(state.frameDoc, item);
    }
    syncTopTransportControls(state.frameDoc);
    syncNativeMusicControls(state.frameDoc);
  }

  function setCurrentStationLabel(doc, text, platform) {
    if (!doc) return;
    const node = doc.querySelector('.music-selector--current > .music-selector--indicator + div');
    if (!node) return;
    // Save original label before first overwrite
    if (!state.originalStationLabel && node.textContent !== text) {
      state.originalStationLabel = node.textContent;
    }
    node.textContent = text;

    // Replace the 3-dot indicator with the platform icon
    var indicator = doc.querySelector('.music-selector--current > .music-selector--indicator');
    if (indicator) {
      // Determine platform from arg or from saved source
      var savedSource = getSavedSourceParsed();
      var plat = platform || (savedSource && savedSource.platform) || getDefaultPlatform() || 'spotify';
      var currentPlat = indicator.getAttribute('data-platform') || '';
      if (currentPlat !== plat) {
        // Save original indicator HTML before first replacement
        if (!state.originalIndicatorHTML) {
          state.originalIndicatorHTML = indicator.innerHTML;
        }
        indicator.innerHTML = '';
        indicator.style.display = 'inline-flex';
        indicator.style.alignItems = 'center';
        indicator.style.justifyContent = 'center';
        var icon = createPlatformIcon(doc, 14, plat);
        indicator.appendChild(icon);
        indicator.setAttribute('data-platform', plat);
      }
    }
  }

  function restoreNativeStationLabel(doc) {
    if (!doc) return;
    const indicator = doc.querySelector('.music-selector--current > .music-selector--indicator');
    const node = doc.querySelector('.music-selector--current > .music-selector--indicator + div');
    const currentPlatform = String(indicator && indicator.getAttribute('data-platform') || '').trim().toLowerCase();
    const currentLabel = String(node && node.textContent || '').trim();
    const expectedSpotifyLabel = String(getPlatformLabel(getSavedSourceParsed()) || 'Spotify').trim().toLowerCase();

    // Try to read the currently-selected native station's name
    const selectedNative = doc.querySelector('.music-selector--list--item.selected:not(.nt-spotify-item)');
    if (selectedNative) {
      const nameEl = selectedNative.querySelector('.music-selector--list--name');
      const nativeName = nameEl ? String(nameEl.textContent || '').trim() : '';
      if (nativeName) {
        if (node) node.textContent = nativeName;
      }
    } else if (
      node &&
      currentLabel &&
      !isManagedStationPlatform(currentPlatform) &&
      currentLabel.toLowerCase() !== 'spotify' &&
      currentLabel.toLowerCase() !== expectedSpotifyLabel
    ) {
      // Nitro has already rendered the new native label — keep it as-is.
    } else if (state.originalStationLabel) {
      // Fallback: restore saved original
      if (node) node.textContent = state.originalStationLabel;
    }

    // Restore original 3-dot indicator
    if (indicator && state.originalIndicatorHTML) {
      indicator.innerHTML = state.originalIndicatorHTML;
      indicator.style.display = '';
      indicator.style.alignItems = '';
      indicator.style.justifyContent = '';
      indicator.removeAttribute('data-platform');
    }
  }

  function createSpotifyIcon(doc, size) {
    const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', String(size));
    svg.setAttribute('height', String(size));
    svg.setAttribute('aria-hidden', 'true');
    svg.style.display = 'block';

    const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('fill', '#1DB954');
    path.setAttribute(
      'd',
      'M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.52 17.34a.72.72 0 0 1-1.02.24c-2.82-1.74-6.36-2.1-10.56-1.14a.72.72 0 0 1-.36-1.4c4.56-1.02 8.52-.6 11.64 1.32.36.24.48.72.3 1.02zm1.44-3.3a.9.9 0 0 1-1.26.3c-3.24-1.98-8.16-2.58-11.94-1.38a.9.9 0 1 1-.54-1.72c4.32-1.32 9.72-.66 13.44 1.62.42.24.6.78.3 1.2zm.12-3.36c-3.9-2.34-10.32-2.52-13.98-1.38a1.08 1.08 0 1 1-.66-2.04c4.26-1.26 11.28-1.02 15.72 1.62.48.3.66.96.42 1.44-.3.42-.96.6-1.5.36z'
    );

    svg.appendChild(path);
    return svg;
  }

  function createYouTubeIcon(doc, size) {
    const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', String(size));
    svg.setAttribute('height', String(size));
    svg.setAttribute('aria-hidden', 'true');
    svg.style.display = 'block';

    const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('fill', '#FF0000');
    path.setAttribute(
      'd',
      'M23.5 6.2a3 3 0 0 0-2.1-2.1C19.5 3.6 12 3.6 12 3.6s-7.5 0-9.4.5A3 3 0 0 0 .5 6.2C0 8.1 0 12 0 12s0 3.9.5 5.8a3 3 0 0 0 2.1 2.1c1.9.5 9.4.5 9.4.5s7.5 0 9.4-.5a3 3 0 0 0 2.1-2.1c.5-1.9.5-5.8.5-5.8s0-3.9-.5-5.8zM9.6 15.6V8.4l6.3 3.6-6.3 3.6z'
    );

    svg.appendChild(path);
    return svg;
  }

  function createAppleMusicIcon(doc, size) {
    const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', String(size));
    svg.setAttribute('height', String(size));
    svg.setAttribute('aria-hidden', 'true');
    svg.style.display = 'block';

    const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('fill', '#FA2D48');
    path.setAttribute(
      'd',
      'M23.99 6.07a6.16 6.16 0 0 0-.14-1.43 3.77 3.77 0 0 0-.6-1.22 3.65 3.65 0 0 0-1.22-.91 3.7 3.7 0 0 0-1.27-.42A13.3 13.3 0 0 0 18.7 2H5.3a12.6 12.6 0 0 0-2.06.1 3.7 3.7 0 0 0-1.27.41 3.52 3.52 0 0 0-1.22.92 3.72 3.72 0 0 0-.6 1.22A6 6 0 0 0 .01 6.07C0 6.68 0 7.29 0 7.91v8.18c0 .62 0 1.23.01 1.84a6.16 6.16 0 0 0 .14 1.43 3.77 3.77 0 0 0 .6 1.22 3.52 3.52 0 0 0 1.22.92 3.7 3.7 0 0 0 1.27.41c.68.08 1.37.1 2.06.1H18.7c.69 0 1.38-.02 2.06-.1a3.7 3.7 0 0 0 1.27-.42 3.65 3.65 0 0 0 1.22-.91 3.72 3.72 0 0 0 .6-1.22 6 6 0 0 0 .14-1.44c.01-.6.01-1.21.01-1.83V7.9c0-.61 0-1.22-.01-1.83zM16.94 13.7v2.23a2 2 0 0 1-.04.4 1.39 1.39 0 0 1-.55.89 1.81 1.81 0 0 1-.86.38 2.29 2.29 0 0 1-.92-.02 1.43 1.43 0 0 1-.74-.44 1.18 1.18 0 0 1-.3-.73 1.26 1.26 0 0 1 .2-.82 1.58 1.58 0 0 1 .73-.55c.37-.14.75-.23 1.13-.32.35-.08.52-.25.55-.61V10.7l-.01-.19a.63.63 0 0 0-.3-.46.8.8 0 0 0-.44-.13l-.13.02-4.23.97a.72.72 0 0 0-.56.57l-.01.14v5.63a2.06 2.06 0 0 1-.04.42 1.4 1.4 0 0 1-.55.87 1.79 1.79 0 0 1-.86.39 2.32 2.32 0 0 1-.92-.02 1.41 1.41 0 0 1-.74-.44 1.2 1.2 0 0 1-.3-.73 1.28 1.28 0 0 1 .2-.83 1.59 1.59 0 0 1 .73-.54c.37-.14.76-.23 1.13-.33.34-.08.51-.25.54-.6V8.94a1.6 1.6 0 0 1 .1-.56 1.1 1.1 0 0 1 .7-.7l4.84-1.27a2.1 2.1 0 0 1 .43-.08.68.68 0 0 1 .59.26.83.83 0 0 1 .15.4c.01.08.02.16.02.24v6.48z'
    );

    svg.appendChild(path);
    return svg;
  }

  function createPlatformIcon(doc, size, platform) {
    if (platform === 'youtube') return createYouTubeIcon(doc, size);
    if (platform === 'apple-music') return createAppleMusicIcon(doc, size);
    return createSpotifyIcon(doc, size);
  }

  function clearSourceTrackListCache(sourceUri) {
    const key = String(sourceUri || '').trim();
    if (!key) return;
    state.sourceTrackListCache.delete(`${key}::tracks`);
  }

  function invalidatePreCueState() {
    if (state.preCueTimer) {
      window.clearTimeout(state.preCueTimer);
      state.preCueTimer = null;
    }
    state.preCuePromise = null;
    state.preCueResult = null;
    state.preCueSourceUri = '';
  }

  function getSavedSourceParsed() {
    const savedUri = String(GM_getValue(STORAGE_KEYS.sourceUri, '') || '').trim();
    const savedUrl = String(GM_getValue(STORAGE_KEYS.sourceUrl, '') || '').trim();

    if (!savedUri && !savedUrl) return null;

    const parsedFromSaved = (savedUri ? parseSourceInput(savedUri) : null)
      || (savedUrl ? parseSourceInput(savedUrl) : null);

    if (parsedFromSaved) {
      // Merge stored open URL if the parsed result has none (internal URIs
      // like apple-music:playlist:xxx can't reconstruct the full URL).
      if (!parsedFromSaved.openUrl && savedUrl) {
        parsedFromSaved.openUrl = savedUrl;
      }
      return parsedFromSaved;
    }
    return null;
  }

  async function ensureSourceConfigured() {
    const savedUrl = String(GM_getValue(STORAGE_KEYS.sourceUrl, '') || '').trim();
    const fromSaved = getSavedSourceParsed();
    if (fromSaved) return fromSaved;

    const input = window.prompt('Paste a Spotify, YouTube, or Apple Music URL:', savedUrl || '');
    if (!input) return null;

    const parsed = parseSourceInput(input);
    if (!parsed) {
      toast('Invalid URL — paste a Spotify, YouTube, or Apple Music link.');
      return null;
    }

    saveSource(parsed);
    refreshSpotifyUI();
    return parsed;
  }

  function saveSource(parsed, options = {}) {
    const previousSavedUri = String(GM_getValue(STORAGE_KEYS.sourceUri, '') || '').trim();
    traceMusic('save-source', {
      previousSavedUri,
      nextSourceUri: parsed ? parsed.uri : '',
      nextPlatform: parsed ? parsed.platform : '',
      nextType: parsed ? parsed.type : '',
    });

    GM_setValue(STORAGE_KEYS.sourceUri, parsed.uri);
    // Only overwrite the stored URL if we have a real one — internal URIs
    // (e.g. apple-music:playlist:xxx) can't reconstruct a usable open URL,
    // so we preserve whatever was stored from the original paste.
    if (parsed.openUrl) {
      setNtcfgMusicValue('SOURCE_URL', parsed.openUrl);
    }

    clearSourceTrackListCache(parsed.uri);
    if (previousSavedUri && previousSavedUri !== parsed.uri) {
      clearSourceTrackListCache(previousSavedUri);
    }
    invalidatePreCueState();

    if (options.schedulePreCue !== false && musicRuntimeStarted) {
      schedulePreCueFromSavedSource(150);
    }
  }

  // ─── Session persistence ─────────────────────────────────────────────
  // Saves enough state so that after a page reload the player can resume
  // at the same track and approximate position without user interaction.

  function saveSessionState() {
    if (!state.active || !state.queue.length) return;

    GM_setValue(STORAGE_KEYS.sessionActive, true);
    GM_setValue(STORAGE_KEYS.queueIndex, state.queueIndex);
    GM_setValue(STORAGE_KEYS.queueMode, state.queueMode || 'shuffle');

    // Save current playback position in seconds
    var posSec = Math.max(0, Math.floor(Number(state.playback.position || 0) / 1000) || 0);
    var player = state.ytPlayer;
    if (player && state.ytPlayerReady && typeof player.getCurrentTime === 'function') {
      const ytPosSec = Math.max(0, Math.floor(Number(player.getCurrentTime()) || 0));
      if (ytPosSec > 0 || posSec === 0) {
        posSec = ytPosSec;
      }
    }
    GM_setValue(STORAGE_KEYS.playbackPositionSec, posSec);

    // Save shuffle state
    if (state.queueMode === 'shuffle' && state.shuffleOrder.length) {
      GM_setValue(STORAGE_KEYS.shuffleOrder, JSON.stringify(state.shuffleOrder));
      GM_setValue(STORAGE_KEYS.shufflePosition, state.shufflePosition);
    }
  }

  function clearSessionState() {
    GM_setValue(STORAGE_KEYS.sessionActive, false);
    GM_setValue(STORAGE_KEYS.queueIndex, 0);
    GM_setValue(STORAGE_KEYS.playbackPositionSec, 0);
    GM_setValue(STORAGE_KEYS.shuffleOrder, '');
    GM_setValue(STORAGE_KEYS.shufflePosition, -1);
  }

  function getSavedSessionState() {
    // Respect the session resume toggle from mod menu
    if (!isMusicSessionResumeEnabled()) return null;
    var active = Boolean(GM_getValue(STORAGE_KEYS.sessionActive, false));
    if (!active) return null;
    var queueIndex = parseInt(GM_getValue(STORAGE_KEYS.queueIndex, 0), 10) || 0;
    var playbackPositionSec = parseInt(GM_getValue(STORAGE_KEYS.playbackPositionSec, 0), 10) || 0;
    var queueMode = String(GM_getValue(STORAGE_KEYS.queueMode, 'shuffle') || 'shuffle');
    var shuffleOrderRaw = String(GM_getValue(STORAGE_KEYS.shuffleOrder, '') || '');
    var shufflePosition = parseInt(GM_getValue(STORAGE_KEYS.shufflePosition, -1), 10);
    var shuffleOrder = [];
    if (shuffleOrderRaw) {
      try { shuffleOrder = JSON.parse(shuffleOrderRaw); } catch (_) { }
      if (!Array.isArray(shuffleOrder)) shuffleOrder = [];
    }
    return {
      queueIndex: queueIndex,
      playbackPositionSec: playbackPositionSec,
      queueMode: queueMode,
      shuffleOrder: shuffleOrder,
      shufflePosition: isNaN(shufflePosition) ? -1 : shufflePosition,
    };
  }

  function parseSpotifyInput(input) {
    const raw = String(input || '').trim();
    if (!raw) return null;

    const uriMatch = raw.match(/^spotify:(track|album|playlist):([A-Za-z0-9]{22})$/i);
    if (uriMatch) {
      const type = uriMatch[1].toLowerCase();
      const id = uriMatch[2];
      return {
        type,
        id,
        uri: `spotify:${type}:${id}`,
        openUrl: `https://open.spotify.com/${type}/${id}`,
      };
    }

    try {
      const url = new URL(raw);
      const host = url.hostname.toLowerCase();
      if (host !== 'open.spotify.com' && host !== 'play.spotify.com') {
        return null;
      }

      const parts = url.pathname.split('/').filter(Boolean);
      if (!parts.length) return null;

      let offset = 0;
      if (parts[0] === 'embed') offset = 1;

      const type = (parts[offset] || '').toLowerCase();
      const id = (parts[offset + 1] || '').split('?')[0];
      if (!id) return null;
      if (type !== 'track' && type !== 'album' && type !== 'playlist') return null;

      return {
        type,
        id,
        uri: `spotify:${type}:${id}`,
        openUrl: `https://open.spotify.com/${type}/${id}`,
      };
    } catch (error) {
      return null;
    }
  }

  function spotifyUriToOpenUrl(uri) {
    const parsed = parseSpotifyInput(uri);
    return parsed ? parsed.openUrl : '';
  }

  // ─── YouTube URL Parser ─────────────────────────────────────────────
  function parseYouTubeInput(input) {
    const raw = String(input || '').trim();
    if (!raw) return null;

    // Handle internal URIs: youtube:video:VIDEO_ID or youtube:playlist:PLAYLIST_ID
    const internalMatch = raw.match(/^youtube:(video|playlist):(.+)$/);
    if (internalMatch) {
      const type = internalMatch[1] === 'video' ? 'track' : 'playlist';
      const id = internalMatch[2];
      if (type === 'track' && /^[A-Za-z0-9_-]{11}$/.test(id)) {
        return {
          platform: 'youtube',
          type: 'track',
          id: id,
          uri: `youtube:video:${id}`,
          openUrl: `https://www.youtube.com/watch?v=${id}`,
        };
      }
      if (type === 'playlist' && /^[A-Za-z0-9_-]{10,}$/.test(id)) {
        return {
          platform: 'youtube',
          type: 'playlist',
          id: id,
          uri: `youtube:playlist:${id}`,
          openUrl: `https://www.youtube.com/playlist?list=${id}`,
        };
      }
    }

    try {
      const url = new URL(raw);
      const host = url.hostname.toLowerCase().replace(/^www\./, '');

      // youtube.com / music.youtube.com / m.youtube.com
      if (host === 'youtube.com' || host === 'music.youtube.com' || host === 'm.youtube.com') {
        // Standard watch URL: ?v=VIDEO_ID
        const v = url.searchParams.get('v');
        if (v && /^[A-Za-z0-9_-]{11}$/.test(v)) {
          return {
            platform: 'youtube',
            type: 'track',
            id: v,
            uri: `youtube:video:${v}`,
            openUrl: `https://www.youtube.com/watch?v=${v}`,
          };
        }
        // Shorts: /shorts/VIDEO_ID
        const shortsMatch = url.pathname.match(/^\/shorts\/([A-Za-z0-9_-]{11})/);
        if (shortsMatch) {
          return {
            platform: 'youtube',
            type: 'track',
            id: shortsMatch[1],
            uri: `youtube:video:${shortsMatch[1]}`,
            openUrl: `https://www.youtube.com/watch?v=${shortsMatch[1]}`,
          };
        }
        // Embed: /embed/VIDEO_ID
        const embedMatch = url.pathname.match(/^\/embed\/([A-Za-z0-9_-]{11})/);
        if (embedMatch) {
          return {
            platform: 'youtube',
            type: 'track',
            id: embedMatch[1],
            uri: `youtube:video:${embedMatch[1]}`,
            openUrl: `https://www.youtube.com/watch?v=${embedMatch[1]}`,
          };
        }
        // Playlist: ?list=PLAYLIST_ID
        const list = url.searchParams.get('list');
        if (list && /^[A-Za-z0-9_-]{10,}$/.test(list)) {
          return {
            platform: 'youtube',
            type: 'playlist',
            id: list,
            uri: `youtube:playlist:${list}`,
            openUrl: `https://www.youtube.com/playlist?list=${list}`,
          };
        }
      }

      // youtu.be short URLs
      if (host === 'youtu.be') {
        const id = url.pathname.slice(1).split('/')[0].split('?')[0];
        if (id && /^[A-Za-z0-9_-]{11}$/.test(id)) {
          return {
            platform: 'youtube',
            type: 'track',
            id: id,
            uri: `youtube:video:${id}`,
            openUrl: `https://www.youtube.com/watch?v=${id}`,
          };
        }
      }
    } catch (_) {
      // Not a URL
    }

    return null;
  }

  // ─── Apple Music URL Parser ─────────────────────────────────────────
  function parseAppleMusicInput(input) {
    const raw = String(input || '').trim();
    if (!raw) return null;

    // Handle internal URIs: apple-music:track:ID, apple-music:album:ID, apple-music:playlist:ID
    const internalMatch = raw.match(/^apple-music:(track|album|playlist):(.+)$/);
    if (internalMatch) {
      const type = internalMatch[1];
      const id = internalMatch[2];
      return {
        platform: 'apple-music',
        type: type,
        id: id,
        uri: `apple-music:${type}:${id}`,
        openUrl: '', // No URL to reconstruct — the saved URL will be used as fallback
      };
    }

    try {
      const url = new URL(raw);
      const host = url.hostname.toLowerCase();
      if (host !== 'music.apple.com') return null;

      // Paths: /{country}/album/{slug}/{albumId}?i={trackId}
      //        /{country}/album/{slug}/{albumId}
      //        /{country}/playlist/{slug}/{playlistId}
      //        /{country}/song/{slug}/{songId}
      const parts = url.pathname.split('/').filter(Boolean);
      if (parts.length < 3) return null;

      const resourceType = parts[1];

      if (resourceType === 'album') {
        // albumId is the last numeric segment
        const albumId = parts.length >= 4 ? parts[3] : parts[2];
        if (!albumId || !/^\d+$/.test(albumId)) return null;

        // ?i=trackId → single track from album
        const trackId = url.searchParams.get('i');
        if (trackId && /^\d+$/.test(trackId)) {
          return {
            platform: 'apple-music',
            type: 'track',
            id: trackId,
            albumId: albumId,
            uri: `apple-music:track:${trackId}`,
            openUrl: raw,
          };
        }

        // Whole album
        return {
          platform: 'apple-music',
          type: 'album',
          id: albumId,
          uri: `apple-music:album:${albumId}`,
          openUrl: raw,
        };
      }

      if (resourceType === 'song') {
        const songId = parts.length >= 4 ? parts[3] : parts[2];
        if (!songId || !/^\d+$/.test(songId)) return null;
        return {
          platform: 'apple-music',
          type: 'track',
          id: songId,
          uri: `apple-music:track:${songId}`,
          openUrl: raw,
        };
      }

      if (resourceType === 'playlist') {
        const playlistId = parts.length >= 4 ? parts[3] : parts[2];
        if (!playlistId) return null;
        return {
          platform: 'apple-music',
          type: 'playlist',
          id: playlistId,
          uri: `apple-music:playlist:${playlistId}`,
          openUrl: raw,
        };
      }

      return null;
    } catch (_) {
      return null;
    }
  }

  // ─── Unified Source Parser ──────────────────────────────────────────
  // Handles Spotify, YouTube, and Apple Music inputs.
  // Returns { platform, type, id, uri, openUrl, ... } or null.
  function parseSourceInput(input) {
    // Spotify first (backward compatible — most common case)
    const spotify = parseSpotifyInput(input);
    if (spotify) {
      spotify.platform = 'spotify';
      return spotify;
    }

    // YouTube
    const youtube = parseYouTubeInput(input);
    if (youtube) return youtube;

    // Apple Music
    const apple = parseAppleMusicInput(input);
    if (apple) return apple;

    return null;
  }

  // ─── Platform-agnostic URI → open URL ───────────────────────────────
  function sourceUriToOpenUrl(uri) {
    const parsed = parseSourceInput(uri);
    return parsed ? parsed.openUrl : '';
  }

  // ─── Platform display name from parsed source ──────────────────────
  function getPlatformLabel(parsedOrUri) {
    const p = typeof parsedOrUri === 'string'
      ? parseSourceInput(parsedOrUri)
      : parsedOrUri;
    const fallbackPlatform = getDefaultPlatform();
    const fallbackLabel = fallbackPlatform === 'youtube' ? 'YouTube'
      : fallbackPlatform === 'apple-music' ? 'Apple Music'
        : 'Spotify';
    if (!p) return fallbackLabel;
    if (p.platform === 'youtube') return 'YouTube';
    if (p.platform === 'apple-music') return 'Apple Music';
    if (p.platform === 'spotify') return 'Spotify';
    return fallbackLabel;
  }

  // ─── YouTube Video Metadata (via noembed) ───────────────────────────
  function fetchYouTubeVideoMeta(videoId) {
    return new Promise(function (resolve) {
      var fallback = { title: 'YouTube Video', artist: '', thumbnail: '' };
      GM_xmlhttpRequest({
        method: 'GET',
        url: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v=' + videoId,
        timeout: 8000,
        onload: function (resp) {
          const data = safeJsonParse(resp.responseText);
          if (data && data.title) {
            resolve({
              title: String(data.title || ''),
              artist: String(data.author_name || ''),
              thumbnail: String(data.thumbnail_url || ''),
            });
          } else {
            resolve(fallback);
          }
        },
        onerror: function () { resolve(fallback); },
        ontimeout: function () { resolve(fallback); },
      });
    });
  }

  // ─── Apple Music Metadata (via iTunes Lookup API) ───────────────────
  function fetchAppleMusicTrackMeta(trackId) {
    return new Promise(function (resolve) {
      GM_xmlhttpRequest({
        method: 'GET',
        url: 'https://itunes.apple.com/lookup?id=' + encodeURIComponent(trackId),
        timeout: 10000,
        onload: function (resp) {
          const data = safeJsonParse(resp.responseText);
          if (data && data.results && data.results.length) {
            const t = data.results[0];
            resolve({
              title: String(t.trackName || t.collectionName || ''),
              artist: String(t.artistName || ''),
              durationMs: Number(t.trackTimeMillis) || 0,
              art: String(t.artworkUrl100 || '').replace('100x100', '300x300'),
              openUrl: String(t.trackViewUrl || ''),
            });
          } else {
            resolve(null);
          }
        },
        onerror: function () { resolve(null); },
        ontimeout: function () { resolve(null); },
      });
    });
  }

  function fetchAppleMusicAlbumTracks(albumId) {
    return new Promise(function (resolve) {
      GM_xmlhttpRequest({
        method: 'GET',
        url: 'https://itunes.apple.com/lookup?id=' + encodeURIComponent(albumId) + '&entity=song',
        timeout: 15000,
        onload: function (resp) {
          const data = safeJsonParse(resp.responseText);
          if (data && data.results && data.results.length > 1) {
            const tracks = data.results.filter(function (r) { return r.wrapperType === 'track'; });
            resolve(tracks.map(function (t) {
              return {
                title: String(t.trackName || ''),
                artist: String(t.artistName || ''),
                durationMs: Number(t.trackTimeMillis) || 0,
                art: String(t.artworkUrl100 || '').replace('100x100', '300x300'),
                openUrl: String(t.trackViewUrl || ''),
                appleMusicId: String(t.trackId || ''),
              };
            }));
          } else {
            resolve([]);
          }
        },
        onerror: function () { resolve([]); },
        ontimeout: function () { resolve([]); },
      });
    });
  }

  // ─── Apple Music Playlist Scraping ───────────────────────────────────

  async function fetchAppleMusicPlaylistTracks(playlistId, playlistUrl) {
    var cacheKey = 'apple-music:playlist:' + playlistId + '::tracks';
    if (state.sourceTrackListCache.has(cacheKey)) {
      return state.sourceTrackListCache.get(cacheKey);
    }

    try {
      var url = playlistUrl || ('https://music.apple.com/us/playlist/' + playlistId);
      var response = await gmRequest({
        method: 'GET',
        url: url,
        headers: {
          Accept: 'text/html',
        },
        timeout: 20000,
      });

      if (response.status < 200 || response.status >= 300) return [];

      var html = String(response.responseText || '');
      if (!html) return [];

      // Strategy 1: Extract from embedded JSON in <script> tags
      var tracks = extractAppleMusicTracksFromServerData(html);

      // Strategy 2: Extract from track-lockup regex patterns
      if (!tracks.length) {
        tracks = extractAppleMusicTracksFromLockups(html);
      }

      // Strategy 3: Extract from JSON-LD structured data
      if (!tracks.length) {
        tracks = extractAppleMusicTracksFromMetaTags(html);
      }

      if (!tracks.length) return [];

      // Enrich tracks that have Apple Music IDs via iTunes Lookup
      await enrichAppleMusicTracks(tracks);

      if (tracks.length) {
        state.sourceTrackListCache.set(cacheKey, tracks);
      }

      return tracks;
    } catch (error) {
      console.warn('[NT Music] Apple Music fetch failed:', error.message || error);
      return [];
    }
  }

  function extractAppleMusicTracksFromServerData(html) {
    var tracks = [];
    var seen = new Set();

    try {
      var doc = new DOMParser().parseFromString(html, 'text/html');
      var scriptNodes = doc.querySelectorAll('script');

      for (var i = 0; i < scriptNodes.length; i += 1) {
        var content = String(scriptNodes[i].textContent || '').trim();
        if (!content) continue;

        // Look for script content that contains song/track data
        if (!/"songs"|"type":"songs"|trackName|songName|"name"/.test(content)) continue;

        var decoded = decodeAppleMusicPayload(content);
        if (!decoded) continue;

        var extracted = traverseForAppleMusicTracks(decoded, seen);
        if (extracted.length) {
          for (var j = 0; j < extracted.length; j += 1) {
            tracks.push(extracted[j]);
          }
        }
      }

      // Also check meta[name="serialized-server-data"] attributes
      var metaNodes = doc.querySelectorAll('meta[name="serialized-server-data"]');
      for (var m = 0; m < metaNodes.length; m += 1) {
        var metaContent = String(metaNodes[m].getAttribute('content') || '');
        var metaDecoded = decodeAppleMusicPayload(metaContent);
        if (!metaDecoded) continue;
        var metaExtracted = traverseForAppleMusicTracks(metaDecoded, seen);
        if (metaExtracted.length) {
          for (var k = 0; k < metaExtracted.length; k += 1) {
            tracks.push(metaExtracted[k]);
          }
        }
      }
    } catch (error) {
      // noop
    }

    return tracks;
  }

  function decodeAppleMusicPayload(content) {
    var raw = String(content || '').trim();
    if (!raw) return null;

    // Direct JSON
    var direct = safeJsonParse(raw);
    if (direct) return direct;

    // Base64 decode
    var b64 = decodeBase64ToText(raw);
    if (b64) {
      var parsed64 = safeJsonParse(b64);
      if (parsed64) return parsed64;
    }

    // URL decode
    try {
      var urlDecoded = decodeURIComponent(raw);
      if (urlDecoded && urlDecoded !== raw) {
        var parsedUrl = safeJsonParse(urlDecoded);
        if (parsedUrl) return parsedUrl;
      }
    } catch (_) { }

    // Extract JSON object from text
    var extracted = extractJsonObjectFromText(raw);
    if (extracted) return safeJsonParse(extracted);

    return null;
  }

  function traverseForAppleMusicTracks(root, seen) {
    if (!root || typeof root !== 'object') return [];

    var seenObjects = new Set();
    var stack = [root];
    var tracks = [];

    var safety = 0;
    while (stack.length && safety < 200000) {
      var value = stack.pop();
      safety += 1;

      if (!value || typeof value !== 'object') continue;
      if (seenObjects.has(value)) continue;
      seenObjects.add(value);

      var track = parseAppleMusicTrackLikeObject(value);
      if (track && !seen.has(track.dedupeKey)) {
        seen.add(track.dedupeKey);
        tracks.push(track);
      }

      if (Array.isArray(value)) {
        for (var i = value.length - 1; i >= 0; i -= 1) {
          if (value[i] && typeof value[i] === 'object') stack.push(value[i]);
        }
        continue;
      }

      var keys = Object.keys(value);
      for (var k = keys.length - 1; k >= 0; k -= 1) {
        var child = value[keys[k]];
        if (child && typeof child === 'object') stack.push(child);
      }
    }

    return tracks;
  }

  function parseAppleMusicTrackLikeObject(value) {
    if (!value || typeof value !== 'object') return null;

    var title = '';
    var artist = '';
    var appleMusicId = '';
    var durationMs = 0;
    var art = '';

    // Format 1: Apple Music API-style — { type: "songs", attributes: { name, artistName } }
    if (value.type === 'songs' && value.attributes && typeof value.attributes === 'object') {
      var attrs = value.attributes;
      title = String(attrs.name || '').trim();
      artist = String(attrs.artistName || '').trim();
      durationMs = Number(attrs.durationInMillis) || 0;
      appleMusicId = String(value.id || '').trim();
      if (attrs.artwork && attrs.artwork.url) {
        art = String(attrs.artwork.url || '')
          .replace('{w}', '300').replace('{h}', '300')
          .replace('{f}', 'jpg').replace('{c}', 'bb');
      }
    }

    // Format 2: songName / trackName / title / name with artistName
    if (!title) {
      title = String(value.songName || value.trackName || '').trim();
      // Apple Music playlist items use flat 'title' key
      if (!title && value.title && typeof value.title === 'string') {
        title = String(value.title).trim();
      }
      if (!title && value.name && typeof value.name === 'string' && value.artistName) {
        title = String(value.name).trim();
      }
      artist = String(value.artistName || value.artist_name || value.byline || '').trim();
      // Fallback: subtitleLinks[0].title (Apple Music playlist format)
      if (!artist && Array.isArray(value.subtitleLinks) && value.subtitleLinks.length) {
        artist = String((value.subtitleLinks[0] && value.subtitleLinks[0].title) || '').trim();
      }
      appleMusicId = String(value.trackId || value.songId || value.adamId || '').trim();
      // Apple Music playlist items store ID in contentDescriptor.identifiers.storeAdamID
      if (!appleMusicId && value.contentDescriptor && value.contentDescriptor.identifiers) {
        appleMusicId = String(value.contentDescriptor.identifiers.storeAdamID || '').trim();
      }
      // Fallback: extract numeric ID from track-lockup id format ("track-lockup - pl.xxx - 1234567890")
      if (!appleMusicId && typeof value.id === 'string') {
        var idParts = value.id.match(/(\d{6,})$/);
        if (idParts) appleMusicId = idParts[1];
      }
      durationMs = Number(value.durationInMillis || value.trackTimeMillis || value.duration) || 0;
      // Apple Music playlist artwork: artwork.dictionary.url
      if (!art && value.artwork && value.artwork.dictionary && value.artwork.dictionary.url) {
        art = String(value.artwork.dictionary.url || '')
          .replace('{w}', '300').replace('{h}', '300')
          .replace('{f}', 'jpg').replace('{c}', 'bb');
      }
    }

    if (!title) return null;
    // Reject if the "title" is too short or looks like a non-track field
    if (title.length < 2) return null;
    // Require artist to be present for confidence
    if (!artist) return null;

    // Only accept numeric Apple Music IDs
    if (appleMusicId && !/^\d+$/.test(appleMusicId)) appleMusicId = '';

    var dedupeKey = appleMusicId
      ? 'apple-music:track:' + appleMusicId
      : 'apple-music-title:' + title.toLowerCase() + ':' + artist.toLowerCase();

    return { title: title, artist: artist, appleMusicId: appleMusicId, durationMs: durationMs, art: art, openUrl: '', dedupeKey: dedupeKey };
  }

  function extractAppleMusicTracksFromLockups(html) {
    var tracks = [];
    var seen = new Set();

    // Use extractJsonBlocksByKey to find track-lockup blocks — handles nested JSON
    var blocks = extractJsonBlocksByKey(html, '"track-lockup', 200);

    for (var i = 0; i < blocks.length; i += 1) {
      var obj = safeJsonParse(blocks[i]);
      if (!obj) continue;

      var title = String(obj.title || '').trim();
      if (!title) continue;

      var artistName = String(obj.artistName || '').trim();
      if (!artistName && Array.isArray(obj.subtitleLinks)) {
        artistName = obj.subtitleLinks
          .map(function (link) { return String((link && link.title) || '').trim(); })
          .filter(Boolean)
          .join(', ');
      } else if (!artistName && typeof obj.subtitle === 'string') {
        artistName = String(obj.subtitle).trim();
      }

      var amId = '';
      if (obj.contentDescriptor && obj.contentDescriptor.identifiers) {
        amId = String(obj.contentDescriptor.identifiers.storeAdamID || '').trim();
      }
      if (!amId && typeof obj.id === 'string') {
        var idMatch = obj.id.match(/(\d{6,})$/);
        if (idMatch) amId = idMatch[1];
      }

      var durationMs = Number(obj.duration) || 0;

      var art = '';
      if (obj.artwork && obj.artwork.dictionary && obj.artwork.dictionary.url) {
        art = String(obj.artwork.dictionary.url || '')
          .replace('{w}', '300').replace('{h}', '300')
          .replace('{f}', 'jpg').replace('{c}', 'bb');
      }

      var dedupeKey = amId
        ? 'apple-music:track:' + amId
        : 'apple-music-title:' + title.toLowerCase() + ':' + artistName.toLowerCase();
      if (seen.has(dedupeKey)) continue;
      seen.add(dedupeKey);

      tracks.push({
        title: title,
        artist: artistName,
        appleMusicId: amId,
        durationMs: durationMs,
        art: art,
        openUrl: '',
        dedupeKey: dedupeKey,
      });
    }

    return tracks;
  }

  function extractAppleMusicTracksFromMetaTags(html) {
    var tracks = [];

    try {
      var doc = new DOMParser().parseFromString(html, 'text/html');

      // Look for JSON-LD structured data
      var ldScripts = doc.querySelectorAll('script[type="application/ld+json"]');
      for (var i = 0; i < ldScripts.length; i += 1) {
        var data = safeJsonParse(ldScripts[i].textContent);
        if (!data) continue;

        // MusicPlaylist or MusicAlbum schema
        if ((data['@type'] === 'MusicPlaylist' || data['@type'] === 'MusicAlbum') && Array.isArray(data.track)) {
          data.track.forEach(function (t) {
            if (!t || !t.name) return;
            var title = String(t.name || '').trim();
            var artist = '';
            if (t.byArtist) {
              artist = String(
                typeof t.byArtist === 'string' ? t.byArtist
                  : (t.byArtist.name || '')
              ).trim();
            }
            // Extract Apple Music ID from URL: https://music.apple.com/us/song/track-name/1234567890
            var trackUrl = String(t.url || '').trim();
            var appleMusicId = '';
            var idFromUrl = trackUrl.match(/\/(\d{6,})(?:\?|$)/);
            if (idFromUrl) appleMusicId = idFromUrl[1];
            // Extract duration from ISO 8601 (e.g. "PT3M45S")
            var durationMs = 0;
            if (t.duration) {
              var durMatch = String(t.duration).match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/i);
              if (durMatch) {
                durationMs = ((parseInt(durMatch[1] || '0', 10) * 3600)
                  + (parseInt(durMatch[2] || '0', 10) * 60)
                  + (parseInt(durMatch[3] || '0', 10))) * 1000;
              }
            }
            tracks.push({
              title: title,
              artist: artist,
              appleMusicId: appleMusicId,
              durationMs: durationMs,
              art: '',
              openUrl: trackUrl,
              dedupeKey: appleMusicId
                ? 'apple-music:track:' + appleMusicId
                : 'apple-music-title:' + title.toLowerCase() + ':' + artist.toLowerCase(),
            });
          });
        }
      }
    } catch (_) { }

    return tracks;
  }

  async function enrichAppleMusicTracks(tracks) {
    // Collect tracks with numeric Apple Music IDs for batch lookup
    var withIds = tracks.filter(function (t) {
      return t.appleMusicId && /^\d+$/.test(t.appleMusicId);
    });

    if (!withIds.length) return;

    // iTunes Lookup API supports multiple IDs per request
    var batchSize = 150;
    for (var offset = 0; offset < withIds.length; offset += batchSize) {
      var batch = withIds.slice(offset, offset + batchSize);
      var ids = batch.map(function (t) { return t.appleMusicId; }).join(',');

      try {
        var response = await gmRequest({
          method: 'GET',
          url: 'https://itunes.apple.com/lookup?id=' + encodeURIComponent(ids),
          headers: { Accept: 'application/json' },
          timeout: 15000,
        });

        if (response.status < 200 || response.status >= 300) continue;

        var data = safeJsonParse(response.responseText);
        if (!data || !Array.isArray(data.results)) continue;

        // Index results by trackId
        var lookup = {};
        data.results.forEach(function (r) {
          if (r.wrapperType === 'track' && r.trackId) {
            lookup[String(r.trackId)] = r;
          }
        });

        // Enrich original tracks
        batch.forEach(function (t) {
          var r = lookup[t.appleMusicId];
          if (!r) return;
          if (!t.title || t.title === 'Untitled') t.title = String(r.trackName || t.title);
          if (!t.artist) t.artist = String(r.artistName || '');
          if (!t.durationMs) t.durationMs = Number(r.trackTimeMillis) || 0;
          if (!t.art) t.art = String(r.artworkUrl100 || '').replace('100x100', '300x300');
          t.openUrl = String(r.trackViewUrl || '');
        });
      } catch (_) {
        // continue with un-enriched data
      }
    }
  }

  function toast(message, type) {
    const doc = state.frameDoc;
    if (!doc || !doc.body) {
      state.pendingToast = message;
      return;
    }

    removeToast(doc);

    const toastEl = doc.createElement('div');
    toastEl.className = 'nt-spotify-toast';
    toastEl.textContent = message;

    const bg = type === 'success' ? 'rgba(18, 128, 67, 0.95)' : 'rgba(0, 0, 0, 0.88)';
    toastEl.style.cssText = [
      'position: fixed',
      'left: 50%',
      'bottom: 24px',
      'transform: translateX(-50%)',
      'z-index: 10000',
      'padding: 8px 12px',
      'border-radius: 999px',
      `background: ${bg}`,
      'color: #fff',
      'font-size: 12px',
      'font-weight: 600',
      'box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3)',
      'pointer-events: none',
    ].join(';');

    doc.body.appendChild(toastEl);
    window.setTimeout(() => {
      toastEl.remove();
    }, 2800);
  }

  function removeToast(doc) {
    const currentToast = doc.querySelector('.nt-spotify-toast');
    if (currentToast) currentToast.remove();
  }

  function parseDurationText(label) {
    const raw = String(label || '').trim();
    if (!raw) return 0;

    if (/^\d+$/.test(raw)) {
      return Number(raw) || 0;
    }

    if (!/^\d{1,2}:\d{2}(?::\d{2})?$/.test(raw)) return 0;

    const parts = raw.split(':').map((value) => Number(value));
    if (parts.some((value) => !Number.isFinite(value))) return 0;

    if (parts.length === 2) {
      return parts[0] * 60 + parts[1];
    }

    return parts[0] * 3600 + parts[1] * 60 + parts[2];
  }

  function safeJsonParse(text) {
    try {
      return JSON.parse(text);
    } catch (error) {
      return null;
    }
  }

  function gmRequest(options) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: options.method,
        url: options.url,
        headers: options.headers,
        data: options.data,
        timeout: options.timeout || 20000,
        onload: resolve,
        onerror: reject,
        ontimeout: () => reject(new Error(`Request timed out: ${options.url}`)),
      });
    });
  }

  function cleanupAll() {
    // Persist session state before unload so we can resume on next page load
    if (state.active && state.queue.length) {
      try { saveSessionState(); } catch (_) { }
    }

    stopProgressTimer();

    // Unmute native audio that we may have muted
    if (state.active && state.frameDoc) {
      try { restoreNativeAudio(state.frameDoc); } catch (_) { }
    }
    stopAudioObservers();

    clearFrameWatchStartTimer();

    clearAutoActivateTimer();

    if (state.preCueTimer) {
      window.clearTimeout(state.preCueTimer);
      state.preCueTimer = null;
    }

    if (state.nextTrackPreResolveTimer) {
      window.clearTimeout(state.nextTrackPreResolveTimer);
      state.nextTrackPreResolveTimer = null;
    }

    state._autoActivateAttempted = false;

    stopFrameWatcher();

    void safeYTCall('pauseVideo');
    if (state.embedController) void safeControllerCall('pause');

    if (state.embedHost) {
      state.embedHost.remove();
      state.embedHost = null;
    }
    state.embedController = null;

    if (state.ytPlayer && typeof state.ytPlayer.destroy === 'function') {
      try {
        state.ytPlayer.destroy();
      } catch (error) {
        // noop
      }
    }
    state.ytPlayer = null;
    state.ytPlayerReady = false;
    state.ytPlayerPromise = null;

    const liveYtPlayerHost = document.getElementById('nt-yt-player-host');
    if (liveYtPlayerHost) {
      try {
        liveYtPlayerHost.remove();
      } catch (error) {
        // noop
      }
    }
    if (state.ytPlayerHost) {
      try {
        state.ytPlayerHost.remove();
      } catch (error) {
        // noop
      }
      state.ytPlayerHost = null;
    }

    detachFrame();

    state.active = false;
    state.pendingSelection = false;
    state.isActivating = false;
    state.trackMeta = null;
    state._pendingSeekSec = 0;
    state._stopCooldownUntil = 0;
    state.originalStationLabel = '';
    state.originalIndicatorHTML = '';
    clearQueue();
    resetPlaybackState();

    state.preCuePromise = null;
    state.preCueResult = null;
    state.preCueSourceUri = '';

    window.removeEventListener('message', onMusicMessageFromRace);
    window.removeEventListener('beforeunload', cleanupAll);
    window.removeEventListener(YT_READY_EVENT, onYouTubeReadyEvent);
    window.removeEventListener(YT_ERROR_EVENT, onYouTubeErrorEvent);
    window.removeEventListener(EMBED_READY_EVENT, onEmbedReadyEvent);
    window.removeEventListener(EMBED_ERROR_EVENT, onEmbedErrorEvent);
  }
})();