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.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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