import { loadPersistedRadioStations, loadRadioStations } from './radio/loadRadioStations.ts';
import { defaultSelectedRadioCountryCodes, managedCountryCode, radioCountries } from './radio/radioCountries.ts';
import {
  getLastManagedCatalogSource,
  loadManagedStations,
  loadPersistedManagedStations,
} from './radio/loadManagedStations.ts';
import {
  clearPlayerPersistence,
  deletePersistedUserStationById,
  getPersistenceBackend,
  getPersistedSelectedRadioCountryCodes,
  getPlayerPersistenceSnapshot,
  getPersistedCastBothMode,
  getPersistedFavoriteStationIds,
  getPersistedLastStationCountry,
  getPersistedLastStationId,
  getPersistedRecentStationHistory,
  getPersistedStationSongHistory,
  getPersistedStationHealth,
  getPersistedStationUsageCounts,
  getPersistedUserStations,
  getPersistedVolume,
  hydratePlayerPersistence,
  importPlayerPersistence,
  persistCastBothMode,
  persistFavoriteStationIds,
  persistLastExportedAt,
  persistLastImportedAt,
  persistLastStationCountry,
  persistLastStationId,
  persistSelectedRadioCountryCodes,
  persistRecentStationHistory,
  persistStationSongHistory,
  persistStationHealth,
  persistStationUsageCounts,
  persistVolume,
  upsertPersistedUserStation,
} from './storage/playerPersistence.ts';
import { clearPersistedManagedStationCatalog } from './storage/stationCatalogPersistence.ts';
import {
  ARTWORK_SHINE_EFFECTS,
  ICON_SPARKLE_EFFECTS,
  IMPORT_EXPORT_SCHEMA,
  IMPORT_EXPORT_VERSION,
  PLAYBACK_START_TIMEOUT_MS,
  RADIO_BROWSER_COUNTRIES_API_ENDPOINT,
  RADIO_PLACEHOLDER_LOGO,
  RECENT_STATION_HISTORY_LIMIT,
  SERVICE_WORKER_MANAGED_CATALOG_UPDATED_MESSAGE,
  SERVICE_WORKER_MANAGED_CATALOG_UPDATE_FAILED_MESSAGE,
  SERVICE_WORKER_STATION_CATALOG_UPDATED_MESSAGE,
  SERVICE_WORKER_STATION_CATALOG_UPDATE_FAILED_MESSAGE,
  SERVICE_WORKER_SYNC_COUNTRIES_MESSAGE,
  STATION_LIBRARY_PAGE_SIZE,
} from './player/constants.js';
import { playerDom } from './player/domRefs.js';
import {
  applyStationTheme,
  getMetadataFetchUrl,
  getStationHomepage,
  getStationMetadataUrl,
  isMobileInstallViewport,
  isMobileViewport,
  isStandalonePwa,
  normalizeStationRecord,
  prefersReducedMotion,
  setImgWithFallback,
  toHttpsIfHttp,
  uniqueNonEmpty,
} from './player/stationHelpers.js';

// Web version of RadioPlayer — HTML5 Audio + Google Cast Web Sender SDK.

// ── Audio engine ──────────────────────────────────────────────────────────────
const audio = new Audio();
audio.preload = 'none';

// ── Cast state ────────────────────────────────────────────────────────────────
// 'local' = HTML5 audio, 'cast' = Google Cast session active, 'airplay' = AirPlay route selected
let castMode = 'local';
let castContext = null;   // cast.framework.CastContext instance
let castPlayerController = null; // cast.framework.RemotePlayerController
let castInitialized = false;
let airPlayAvailable = false;
// When true, local audio also plays alongside the Cast session.
let castBothMode = false;
let deferredInstallPrompt = null;

// State
let stations = [];
let currentIndex = 0;
let isPlaying = false;
let isMuted = false;
let currentVolume = 0.8; // 0–1 float
let stationLibraryTab = 'all';
let stationLibraryQuery = '';
let stationLibraryCategory = 'all';
let stationLibraryCountry = 'all';
let stationLibrarySort = 'recommended';
let stationLibraryLanguage = 'all';
let stationLibraryCodec = 'all';
let stationLibraryBitrate = 'all';
let stationLibraryHealthyOnly = false;
let selectedRadioCountryCodes = new Set(defaultSelectedRadioCountryCodes);
let draftSelectedRadioCountryCodes = new Set(defaultSelectedRadioCountryCodes);
let availableRadioCountries = radioCountries.map((country) => ({
  ...country,
  stationCount: null,
  pinned: country.code === managedCountryCode,
}));
let availableRadioCountriesLoaded = false;
let availableRadioCountriesRequest = null;
let countrySelectionQuery = '';
let countrySelectionSaving = false;
let stationLibraryPage = 0;
let stationLibraryPageTotal = 1;
let stationCatalogState = 'idle';
let stationCatalogError = '';
let playbackError = '';
let sleepTimerTimeoutId = null;
let sleepTimerDeadline = null;
let sleepTimerSelectionMinutes = 0;
let wakeAlarmTimeoutId = null;
let wakeAlarmDeadline = null;
let wakeAlarmSelectionMinutes = 0;
let sessionCountdownIntervalId = null;
let playbackStartupAttemptId = 0;
let playbackStartupTimeoutId = null;
let activeLocalPlaybackAttempt = null;
let stationCountryFilterOpen = false;
let artworkShineTimeoutId = null;
let artworkShineClearTimeoutId = null;
let stationTitleRafId = null;
let catalogSyncStatusTimeoutId = null;
let catalogSyncHistoryTimeoutId = null;
let managedCatalogStatusTimeoutId = null;

let iconSparkleTimeoutId = null;
const STATION_SONG_HISTORY_LIMIT = 8;
const {
  stationNameEl,
  stationSubtitleEl,
  stationHealthSummaryEl,
  stationHealthDetailEl,
  sessionSchedulerBtn,
  sessionSchedulerBadge,
  sessionSummaryBtn,
  sessionSummaryText,
  sleepTimerStatusEl,
  wakeAlarmStatusEl,
  sleepTimerButtons,
  wakeAlarmButtons,
  songHistoryPanelEl,
  songHistoryListEl,
  songHistoryClearBtn,
  managedCatalogStatusEl,
  catalogSyncStatusEl,
  catalogSyncStatusTextEl,
  catalogSyncStatusTimeEl,
  catalogSyncHistoryEl,
  nowPlayingEl,
  nowArtistEl,
  nowTitleEl,
  statusTextEl,
  statusDotEl,
  engineBadgeEl,
  playBtn,
  iconPlay,
  iconStop,
  prevBtn,
  nextBtn,
  volumeSlider,
  volumeValue,
  muteBtn,
  iconVolume,
  iconMuted,
  castOverlay,
  closeOverlayBtn,
  deviceListEl,
  coverflowStageEl,
  coverflowPrevBtn,
  coverflowNextBtn,
  artworkPlaceholder,
  logoTextEl,
  logoImgEl,
  stationLibraryEl,
  playerLayoutEl,
  stationLibraryListEl,
  stationLibrarySummaryEl,
  stationSearchInput,
  stationLibraryCloseBtn,
  stationLibraryFiltersToggleBtn,
  clearManagedCatalogBtn,
  stationCategoryListEl,
  stationCountryFilterWrapEl,
  stationCountryFilterBtn,
  stationCountryFilterMenu,
  stationCountryFilterText,
  stationCountryFilterFlag,
  stationAdvancedFiltersBtn,
  stationAdvancedFiltersOverlay,
  stationAdvancedFiltersCloseBtn,
  stationAdvancedFiltersDoneBtn,
  countrySelectionOverlay,
  countrySelectionSearchInput,
  countrySelectionSummaryEl,
  countrySelectionEmptyEl,
  countrySelectionListEl,
  countrySelectionDefaultsBtn,
  countrySelectionAllBtn,
  countrySelectionSaveBtn,
  countrySelectionCancelBtn,
  stationSortBtn,
  stationSortText,
  stationSortMenu,
  stationSortWrapEl,
  stationLibraryPaginationEl,
  stationLibraryPagePrevBtn,
  stationLibraryPageNextBtn,
  stationLibraryPageInfo,
  stationLanguageFilterWrapEl,
  stationLanguageFilterBtn,
  stationLanguageFilterText,
  stationLanguageFilterMenu,
  stationCodecFilterWrapEl,
  stationCodecFilterBtn,
  stationCodecFilterText,
  stationCodecFilterMenu,
  stationBitrateFilterWrapEl,
  stationBitrateFilterBtn,
  stationBitrateFilterText,
  stationBitrateFilterMenu,
  stationHealthFilterBtn,
  stationHealthFilterText,
  stationTabBtns,
  installPromptBannerEl,
  installPromptActionBtn,
  installPromptDismissBtn,
  editBtn,
  stationsListBtn,
  shortcutHelpBtn,
  installAppBtn,
  castBtn,
  castBtnCastIcon,
  castBtnAirPlayIcon,
  editorOverlay,
  sessionSchedulerOverlay,
  sessionSchedulerCloseBtn,
  sessionSchedulerDoneBtn,
  shortcutHelpOverlay,
  shortcutHelpCloseBtn,
  editorCloseBtn,
  editorListEl,
  editorPersistenceNoteEl,
  editorPersistenceBackendEl,
  editorBackupActivityNoteEl,
  exportUserDataBtn,
  importUserDataBtn,
  importUserDataInput,
  resetUserDataBtn,
  resetUserDataBackupCheckbox,
  addStationForm,
  usTitle,
  usUrl,
  usLogo,
  usWww,
  usId,
  castOutputRow,
  castOutputBtn,
  castOutputText,
} = playerDom;
let stationSortOpen = false;
let stationLanguageFilterOpen = false;
let stationCodecFilterOpen = false;
let stationBitrateFilterOpen = false;
let stationAdvancedFiltersOpen = false;
let stationLibraryFiltersCollapsed = false;

const radioCountryByCode = new Map(radioCountries.map((country) => [country.code, country]));
const radioCountryCodeByName = new Map(radioCountries.map((country) => [country.name, country.code]));
const radioCountryNameByCode = new Map(radioCountries.map((country) => [country.code, country.name]));

// ── Utilities ────────────────────────────────────────────────────────────────

async function lockPortraitOrientation() {
  if (!isMobileViewport()) return;
  if (!isStandalonePwa()) return;
  if (!screen?.orientation?.lock) return;

  try {
    await screen.orientation.lock('portrait');
  } catch (e) {
    // Some browsers require a user gesture or simply do not allow locking.
    console.debug('Portrait orientation lock not available:', e);
  }
}

function hideInstallPromptUI() {
  installAppBtn?.classList.add('hidden');
  installPromptBannerEl?.classList.add('hidden');
}

function showInstallPromptUI() {
  if (!deferredInstallPrompt) return;
  if (isMobileInstallViewport()) {
    installPromptBannerEl?.classList.remove('hidden');
  } else {
    installAppBtn?.classList.remove('hidden');
  }
}

function clearArtworkShineTimers() {
  if (artworkShineTimeoutId) {
    clearTimeout(artworkShineTimeoutId);
    artworkShineTimeoutId = null;
  }
  if (artworkShineClearTimeoutId) {
    clearTimeout(artworkShineClearTimeoutId);
    artworkShineClearTimeoutId = null;
  }
  if (artworkPlaceholder) {
    artworkPlaceholder.classList.remove(...ARTWORK_SHINE_EFFECTS);
  }
}

function scheduleArtworkShine() {
  if (!artworkPlaceholder || prefersReducedMotion()) return;

  const trigger = () => {
    artworkShineTimeoutId = null;
    clearArtworkShineTimers();

    if (!artworkPlaceholder || prefersReducedMotion()) return;

    const effect = ARTWORK_SHINE_EFFECTS[Math.floor(Math.random() * ARTWORK_SHINE_EFFECTS.length)];
    const effectDuration = 1800 + Math.floor(Math.random() * 900);
    artworkPlaceholder.classList.add(effect);

    artworkShineClearTimeoutId = window.setTimeout(() => {
      artworkPlaceholder.classList.remove(effect);
      artworkShineClearTimeoutId = null;
    }, effectDuration);

    const nextDelay = 7000 + Math.floor(Math.random() * 15000);
    artworkShineTimeoutId = window.setTimeout(trigger, nextDelay);
  };

  clearArtworkShineTimers();
  trigger();
}

function triggerSparkleOnSelected() {
  if (prefersReducedMotion() || !coverflowStageEl) return;
  const item = coverflowStageEl.querySelector('.coverflow-item.selected');
  if (!item) return;
  const effect = ICON_SPARKLE_EFFECTS[Math.floor(Math.random() * ICON_SPARKLE_EFFECTS.length)];
  item.classList.remove(...ICON_SPARKLE_EFFECTS);
  void item.offsetWidth;
  item.classList.add(effect);
  window.setTimeout(() => item.classList.remove(effect), 1600);
}

function scheduleIconSparkle() {
  if (prefersReducedMotion()) return;

  const trigger = () => {
    iconSparkleTimeoutId = null;
    if (prefersReducedMotion() || !coverflowStageEl) {
      iconSparkleTimeoutId = window.setTimeout(trigger, 6000 + Math.floor(Math.random() * 10000));
      return;
    }

    const items = Array.from(coverflowStageEl.querySelectorAll('.coverflow-item:not(.selected)'));
    if (items.length > 0) {
      const item = items[Math.floor(Math.random() * items.length)];
      const effect = ICON_SPARKLE_EFFECTS[Math.floor(Math.random() * ICON_SPARKLE_EFFECTS.length)];
      item.classList.remove(...ICON_SPARKLE_EFFECTS);
      // Force reflow so re-adding same class restarts animation
      void item.offsetWidth;
      item.classList.add(effect);
      window.setTimeout(() => item.classList.remove(effect), 1600);
    }

    iconSparkleTimeoutId = window.setTimeout(trigger, 4000 + Math.floor(Math.random() * 8000));
  };

  if (iconSparkleTimeoutId) clearTimeout(iconSparkleTimeoutId);
  iconSparkleTimeoutId = window.setTimeout(trigger, 3000 + Math.floor(Math.random() * 5000));
}


// ── Global error handlers ────────────────────────────────────────────────────

window.addEventListener('error', (ev) => {
  try {
    console.error('Uncaught error', ev.error || ev.message || ev);
    if (statusTextEl) statusTextEl.textContent = 'Error: ' + (ev.error?.message ?? ev.message ?? 'Unknown');
  } catch (e) { /* ignore */ }
});

window.addEventListener('unhandledrejection', (ev) => {
  try {
    console.error('Unhandled rejection', ev.reason);
    if (statusTextEl) statusTextEl.textContent = 'Error: ' + (ev.reason?.message ?? String(ev.reason));
  } catch (e) { /* ignore */ }
});

// ── Audio event wiring ───────────────────────────────────────────────────────

audio.addEventListener('waiting', () => {
  if (!isPlaying) return;
  if (statusTextEl) statusTextEl.textContent = 'Buffering...';
  if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--text-muted)';
});

audio.addEventListener('playing', () => {
  clearPlaybackStartupTimeout();
  completeLocalPlaybackAttemptAsSuccess();
  playbackError = '';
  if (statusTextEl) statusTextEl.textContent = 'Playing';
  if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--success)';
  isPlaying = true;
  updateUI();
});

audio.addEventListener('stalled', () => {
  if (!isPlaying) return;
  if (statusTextEl) statusTextEl.textContent = 'Reconnecting...';
  if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--text-muted)';
});

audio.addEventListener('error', () => {
  clearPlaybackStartupTimeout();
  const err = audio.error;
  const msg = err ? audioErrorMessage(err.code) : 'Stream error';
  const station = stations[currentIndex];
  completeLocalPlaybackAttemptAsFailure(station, msg, { audioErrorCode: err?.code ?? null });
  playbackError = getPlaybackFailureMessage(station, msg);
  isPlaying = false;
  updateUI();
});

audio.addEventListener('pause', () => {
  if (!audio.ended) {
    clearPlaybackStartupTimeout();
  }
});

audio.addEventListener('emptied', () => {
  clearPlaybackStartupTimeout();
});

audio.addEventListener('ended', () => {
  // Live streams shouldn't end; if they do, try to reconnect.
  if (isPlaying) {
    const url = audio.src;
    setTimeout(() => {
      audio.src = url;
      audio.load();
      audio.play().catch(() => {});
    }, 2000);
  }
});

function audioErrorMessage(code) {
  switch (code) {
    case 1: return 'Playback aborted';
    case 2: return 'Network error';
    case 3: return 'Decode error';
    case 4: return 'Stream not supported';
    default: return 'Stream error';
  }
}

function createDefaultStationHealth() {
  return {
    attempts: 0,
    successes: 0,
    failures: 0,
    lastAttemptedAt: null,
    lastSucceededAt: null,
    lastFailedAt: null,
    lastFailureReason: null,
    timeoutCount: 0,
    lastProbeMillis: null,
  };
}

function loadRecentStationHistory() {
  return getPersistedRecentStationHistory();
}

function saveRecentStationHistory(history) {
  persistRecentStationHistory(Array.isArray(history) ? history : []);
}

function loadStationSongHistory() {
  return getPersistedStationSongHistory();
}

function saveStationSongHistory(history) {
  persistStationSongHistory(history && typeof history === 'object' ? history : {});
}

function loadStationHealth() {
  return getPersistedStationHealth();
}

function saveStationHealth(health) {
  persistStationHealth(health && typeof health === 'object' ? health : {});
}

function recordRecentStationPlay(station) {
  if (!station?.id) return;

  const nextHistory = [
    {
      stationId: station.id,
      playedAt: new Date().toISOString(),
      country: getStationCountry(station) || null,
    },
    ...loadRecentStationHistory(),
  ].slice(0, RECENT_STATION_HISTORY_LIMIT);

  saveRecentStationHistory(nextHistory);
}

function recordSongHistory(station, songInfo) {
  if (!station?.id || !songInfo?.artist || !songInfo?.title) return;

  const history = loadStationSongHistory();
  const nextEntry = {
    artist: String(songInfo.artist).trim(),
    title: String(songInfo.title).trim(),
    seenAt: new Date().toISOString(),
  };
  if (!nextEntry.artist || !nextEntry.title) return;

  const currentHistory = Array.isArray(history[station.id]) ? history[station.id] : [];
  const dedupedHistory = currentHistory.filter((entry) => (
    String(entry?.artist || '').trim().toLowerCase() !== nextEntry.artist.toLowerCase()
    || String(entry?.title || '').trim().toLowerCase() !== nextEntry.title.toLowerCase()
  ));

  history[station.id] = [nextEntry, ...dedupedHistory].slice(0, STATION_SONG_HISTORY_LIMIT);
  saveStationSongHistory(history);
}

function classifyPlaybackFailure(station, detail, options = {}) {
  const originalUrl = typeof station?.url === 'string' ? station.url.trim() : '';
  const upgradedUrl = toHttpsIfHttp(originalUrl) || originalUrl;
  const detailText = String(detail || '').toLowerCase();

  if (window.location.protocol === 'https:' && originalUrl.startsWith('http://') && upgradedUrl !== originalUrl) {
    return 'insecure-stream-blocked';
  }

  if (options.isTimeout) {
    return 'startup-timeout';
  }

  if (options.audioErrorCode === 4 || detailText.includes('not supported')) {
    return 'unsupported-format';
  }

  if (
    options.audioErrorCode === 2
    || detailText.includes('network')
    || detailText.includes('server responded')
    || detailText.includes('unable to start playback')
    || detailText.includes('certificate')
    || detailText.includes('tls')
  ) {
    return 'tls-or-network-error';
  }

  return 'unknown';
}

function beginLocalPlaybackAttempt(station, attemptId, probeMillis = null) {
  if (!station?.id) {
    activeLocalPlaybackAttempt = null;
    return;
  }

  const health = loadStationHealth();
  const nextHealth = { ...createDefaultStationHealth(), ...(health[station.id] || {}) };
  nextHealth.attempts += 1;
  nextHealth.lastAttemptedAt = new Date().toISOString();
  if (Number.isFinite(probeMillis) && probeMillis >= 0) {
    nextHealth.lastProbeMillis = probeMillis;
  }
  health[station.id] = nextHealth;
  saveStationHealth(health);

  activeLocalPlaybackAttempt = {
    attemptId,
    stationId: station.id,
    probeMillis: Number.isFinite(probeMillis) && probeMillis >= 0 ? probeMillis : null,
    settled: false,
  };
}

function completeLocalPlaybackAttemptAsSuccess() {
  const attempt = activeLocalPlaybackAttempt;
  if (!attempt || attempt.settled || attempt.attemptId !== playbackStartupAttemptId) return;

  const health = loadStationHealth();
  const nextHealth = { ...createDefaultStationHealth(), ...(health[attempt.stationId] || {}) };
  nextHealth.successes += 1;
  nextHealth.lastSucceededAt = new Date().toISOString();
  if (Number.isFinite(attempt.probeMillis) && attempt.probeMillis >= 0) {
    nextHealth.lastProbeMillis = attempt.probeMillis;
  }
  health[attempt.stationId] = nextHealth;
  saveStationHealth(health);
  attempt.settled = true;
}

function completeLocalPlaybackAttemptAsFailure(station, detail, options = {}) {
  const stationId = station?.id || activeLocalPlaybackAttempt?.stationId;
  if (!stationId) {
    activeLocalPlaybackAttempt = null;
    return;
  }

  const attempt = activeLocalPlaybackAttempt;
  if (attempt?.settled) return;

  const health = loadStationHealth();
  const nextHealth = { ...createDefaultStationHealth(), ...(health[stationId] || {}) };
  nextHealth.failures += 1;
  nextHealth.lastFailedAt = new Date().toISOString();
  nextHealth.lastFailureReason = classifyPlaybackFailure(station, detail, options);
  if (options.isTimeout) {
    nextHealth.timeoutCount += 1;
  }
  health[stationId] = nextHealth;
  saveStationHealth(health);

  if (attempt) {
    attempt.settled = true;
  }
}

function getPlaybackFailureMessage(station, detail = 'Stream error') {
  const stationTitle = station ? getStationTitle(station) : 'this station';
  const originalUrl = typeof station?.url === 'string' ? station.url.trim() : '';
  const upgradedUrl = toHttpsIfHttp(originalUrl) || originalUrl;
  const isSecurePage = window.location.protocol === 'https:';
  const isAutoUpgradedHttpStream = originalUrl.startsWith('http://') && upgradedUrl !== originalUrl;

  if (isSecurePage && isAutoUpgradedHttpStream) {
    return `Unable to play ${stationTitle}. This station only exposes an insecure HTTP stream or a misconfigured HTTPS certificate, so browsers block it on this secure site.`;
  }

  return `Unable to play ${stationTitle}. ${detail}.`;
}

function clearPlaybackStartupTimeout() {
  if (playbackStartupTimeoutId) {
    clearTimeout(playbackStartupTimeoutId);
    playbackStartupTimeoutId = null;
  }
}

function armPlaybackStartupTimeout(station, attemptId) {
  clearPlaybackStartupTimeout();
  playbackStartupTimeoutId = window.setTimeout(() => {
    if (attemptId !== playbackStartupAttemptId) return;
    if (audio.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA || !audio.paused) return;

    audio.pause();
    completeLocalPlaybackAttemptAsFailure(station, 'The stream did not respond in time', { isTimeout: true });
    playbackError = getPlaybackFailureMessage(station, 'The stream did not respond in time');
    isPlaying = false;
    updateUI();
  }, PLAYBACK_START_TIMEOUT_MS);
}

function downloadJsonFile(filename, data) {
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
  const objectUrl = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = objectUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
  URL.revokeObjectURL(objectUrl);
}

function updateEditorPersistenceInfo() {
  const backend = getPersistenceBackend();
  if (editorPersistenceBackendEl) {
    editorPersistenceBackendEl.textContent = backend === 'indexeddb' ? 'IndexedDB' : 'LocalStorage fallback';
  }
  if (editorPersistenceNoteEl) {
    editorPersistenceNoteEl.title = backend === 'indexeddb'
      ? 'RadioPlayer is using IndexedDB for local persistence.'
      : 'IndexedDB is unavailable, so RadioPlayer is using localStorage fallback.';
  }
}

function formatBackupTimestamp(value) {
  if (!value) return null;

  try {
    return new Intl.DateTimeFormat(undefined, {
      dateStyle: 'medium',
      timeStyle: 'short',
    }).format(new Date(value));
  } catch (error) {
    return null;
  }
}

function updateEditorBackupActivityInfo() {
  if (!editorBackupActivityNoteEl) return;

  const snapshot = getPlayerPersistenceSnapshot();
  const parts = [];
  const lastExportedAt = formatBackupTimestamp(snapshot.lastExportedAt);
  const lastImportedAt = formatBackupTimestamp(snapshot.lastImportedAt);

  if (lastExportedAt) {
    parts.push(`Last export: ${lastExportedAt}`);
  }

  if (lastImportedAt) {
    parts.push(`Last import: ${lastImportedAt}`);
  }

  editorBackupActivityNoteEl.textContent = parts.length > 0 ? parts.join(' | ') : 'No backup activity yet.';
}

function buildBackupSummary(snapshotLike) {
  const customStationCount = Array.isArray(snapshotLike?.userStations) ? snapshotLike.userStations.length : 0;
  const favoriteCount = Array.isArray(snapshotLike?.favoriteStationIds)
    ? snapshotLike.favoriteStationIds.filter(Boolean).length
    : 0;
  const recentStationCount = Array.isArray(snapshotLike?.recentStationHistory)
    ? new Set(snapshotLike.recentStationHistory.map((entry) => entry?.stationId).filter(Boolean)).size
    : snapshotLike?.stationUsageCounts && typeof snapshotLike.stationUsageCounts === 'object' && !Array.isArray(snapshotLike.stationUsageCounts)
      ? Object.values(snapshotLike.stationUsageCounts).filter((value) => Number(value) > 0).length
      : 0;
  const countryFilter = typeof snapshotLike?.lastStationCountry === 'string' && snapshotLike.lastStationCountry.trim().length > 0
    ? snapshotLike.lastStationCountry
    : null;
  const hasLastStation = typeof snapshotLike?.lastStationId === 'string' && snapshotLike.lastStationId.trim().length > 0;
  const notes = [
    `${customStationCount} custom station${customStationCount === 1 ? '' : 's'}`,
    `${favoriteCount} favorite${favoriteCount === 1 ? '' : 's'}`,
    `${recentStationCount} recent station entr${recentStationCount === 1 ? 'y' : 'ies'}`,
  ];

  if (countryFilter) {
    notes.push(`country filter: ${countryFilter}`);
  }

  if (hasLastStation) {
    notes.push('restores last selected station');
  }

  return {
    customStationCount,
    favoriteCount,
    recentStationCount,
    countryFilter,
    hasLastStation,
    notes,
  };
}

function formatBackupSummary(summary) {
  if (!summary || !Array.isArray(summary.notes) || summary.notes.length === 0) {
    return 'No summary available.';
  }

  return summary.notes.join(', ');
}

function createBackupPayload(reason = 'manual') {
  const exportedAt = new Date().toISOString();
  persistLastExportedAt(exportedAt);
  updateEditorBackupActivityInfo();

  const data = getPlayerPersistenceSnapshot();
  return {
    app: 'RadioPlayer',
    schema: IMPORT_EXPORT_SCHEMA,
    version: 1,
    exportedAt,
    reason,
    backend: getPersistenceBackend(),
    summary: buildBackupSummary(data),
    data,
  };
}

function downloadBackupPayload(payload) {
  const timestamp = payload.exportedAt.replace(/[:.]/g, '-');
  const suffix = payload.reason === 'pre-reset' ? '-before-reset' : '';
  downloadJsonFile(`radioplayer-local-data-${timestamp}${suffix}.json`, payload);
}

function parseImportedUserDataPayload(text) {
  const parsed = JSON.parse(text);
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error('Invalid backup file format');
  }

  if ('data' in parsed || 'version' in parsed || 'app' in parsed || 'schema' in parsed) {
    if (parsed.app !== 'RadioPlayer') {
      throw new Error('Backup file does not belong to RadioPlayer');
    }

    if ('schema' in parsed && parsed.schema !== IMPORT_EXPORT_SCHEMA) {
      throw new Error('Unsupported backup schema');
    }

    if (parsed.version !== IMPORT_EXPORT_VERSION) {
      throw new Error('Unsupported backup version');
    }

    if (!('data' in parsed) || !parsed.data || typeof parsed.data !== 'object' || Array.isArray(parsed.data)) {
      throw new Error('Backup file is missing data');
    }

    return {
      data: parsed.data,
      exportedAt: typeof parsed.exportedAt === 'string' ? parsed.exportedAt : null,
      summary: parsed.summary && typeof parsed.summary === 'object' && !Array.isArray(parsed.summary)
        ? parsed.summary
        : buildBackupSummary(parsed.data),
    };
  }

  return {
    data: parsed,
    exportedAt: null,
    summary: buildBackupSummary(parsed),
  };
}

async function refreshPersistedState(statusMessage) {
  if (isPlaying) {
    await stop();
  }

  restoreSavedVolume();
  restoreCastBothMode();
  restoreLastStationCountry();
  updateEditorPersistenceInfo();
  updateEditorBackupActivityInfo();
  await loadStations();
  renderUserStationsList();
  updateUI();

  if (statusTextEl && statusMessage) {
    statusTextEl.textContent = statusMessage;
  }
}

function handleExportUserData() {
  const payload = createBackupPayload('manual');
  downloadBackupPayload(payload);

  if (statusTextEl) {
    statusTextEl.textContent = 'Local data exported';
  }
}

async function handleImportUserDataFile(file) {
  if (!file) return;

  try {
    const text = await file.text();
    const payload = parseImportedUserDataPayload(text);
    const exportedAt = formatBackupTimestamp(payload.exportedAt);
    const summaryText = formatBackupSummary(payload.summary);
    const confirmed = window.confirm(
      `Import this RadioPlayer backup${exportedAt ? ` from ${exportedAt}` : ''}?\n\n${summaryText}`,
    );
    if (!confirmed) {
      if (importUserDataInput) importUserDataInput.value = '';
      return;
    }

    await importPlayerPersistence(payload.data);
    persistLastImportedAt(new Date().toISOString());
    if (importUserDataInput) importUserDataInput.value = '';
    await refreshPersistedState('Local data imported');
  } catch (error) {
    console.error('Import failed', error);
    if (importUserDataInput) importUserDataInput.value = '';
    if (statusTextEl) statusTextEl.textContent = error?.message || 'Import failed';
  }
}

async function handleResetUserData() {
  const shouldBackupBeforeReset = resetUserDataBackupCheckbox?.checked !== false;
  const confirmed = window.confirm('Delete all locally stored RadioPlayer settings, favourites, usage history, and custom stations on this device?');
  if (!confirmed) return;

  if (shouldBackupBeforeReset) {
    downloadBackupPayload(createBackupPayload('pre-reset'));
  }

  await clearPlayerPersistence();
  if (importUserDataInput) importUserDataInput.value = '';
  await refreshPersistedState(shouldBackupBeforeReset ? 'Local data backed up and cleared' : 'Local data cleared');
}

// ── Volume ───────────────────────────────────────────────────────────────────

function saveVolumeToStorage(val) {
  persistVolume(val);
}

function getSavedVolume() {
  const value = getPersistedVolume();
  return Number.isFinite(value) ? value : null;
}

function restoreSavedVolume() {
  const saved = getSavedVolume();
  const vol = saved !== null ? saved : 80;
  if (volumeSlider) volumeSlider.value = String(vol);
  if (volumeValue) volumeValue.textContent = `${vol}%`;
  currentVolume = vol / 100;
  audio.volume = currentVolume;
}

// ── Station persistence ───────────────────────────────────────────────────────

function saveLastStationId(id) {
  if (id) persistLastStationId(id);
}

function getLastStationId() {
  return getPersistedLastStationId();
}

function saveLastStationCountry(country) {
  if (country) persistLastStationCountry(country);
}

function getLastStationCountry() {
  return getPersistedLastStationCountry();
}

function restoreLastStationCountry() {
  const savedCountry = getLastStationCountry();
  stationLibraryCountry = savedCountry || 'all';
}

function normalizeSelectedRadioCountryCodes(codes) {
  const normalized = Array.isArray(codes) ? codes : Array.from(codes || []);
  const uniqueCodes = Array.from(new Set(
    normalized
      .map((entry) => (typeof entry === 'string' ? entry.trim().toUpperCase() : ''))
      .filter((entry) => /^[A-Z]{2}$/.test(entry)),
  ));

  if (!uniqueCodes.includes(managedCountryCode)) {
    uniqueCodes.unshift(managedCountryCode);
  }

  return uniqueCodes.length > 0 ? uniqueCodes : [...defaultSelectedRadioCountryCodes];
}

function getSelectedRadioCountryCodes() {
  return normalizeSelectedRadioCountryCodes(selectedRadioCountryCodes);
}

function restoreSelectedRadioCountryCodes() {
  selectedRadioCountryCodes = new Set(normalizeSelectedRadioCountryCodes(getPersistedSelectedRadioCountryCodes()));
  draftSelectedRadioCountryCodes = new Set(getSelectedRadioCountryCodes());
}

function getAvailableRadioCountryNameByCode(countryCode) {
  const code = String(countryCode || '').trim().toUpperCase();
  if (!code) return '';
  return availableRadioCountries.find((country) => country.code === code)?.name
    || radioCountryNameByCode.get(code)
    || radioCountryByCode.get(code)?.name
    || code;
}

function mergeAvailableRadioCountries(countries) {
  const mergedCountries = new Map();

  radioCountries.forEach((country) => {
    mergedCountries.set(country.code, {
      name: country.name,
      code: country.code,
      stationCount: null,
      pinned: country.code === managedCountryCode,
    });
  });

  countries.forEach((country) => {
    const code = String(country?.code || '').trim().toUpperCase();
    if (!/^[A-Z]{2}$/.test(code)) return;

    const existing = mergedCountries.get(code);
    mergedCountries.set(code, {
      name: String(country?.name || existing?.name || code).trim() || code,
      code,
      stationCount: Number.isFinite(country?.stationCount) && Number(country.stationCount) >= 0
        ? Number(country.stationCount)
        : existing?.stationCount ?? null,
      pinned: code === managedCountryCode,
    });
  });

  availableRadioCountries = Array.from(mergedCountries.values()).sort((left, right) => {
    if (left.code === managedCountryCode) return -1;
    if (right.code === managedCountryCode) return 1;
    return left.name.localeCompare(right.name, undefined, { sensitivity: 'base' });
  });
}

async function ensureAvailableRadioCountriesLoaded() {
  if (availableRadioCountriesLoaded) {
    return availableRadioCountries;
  }

  if (availableRadioCountriesRequest) {
    return availableRadioCountriesRequest;
  }

  availableRadioCountriesRequest = (async () => {
    try {
      const response = await fetch(RADIO_BROWSER_COUNTRIES_API_ENDPOINT, {
        cache: 'no-store',
        headers: {
          accept: 'application/json',
        },
        mode: 'cors',
      });

      if (!response.ok) {
        throw new Error(`Failed loading countries: ${response.status}`);
      }

      const payload = await response.json();
      if (!Array.isArray(payload)) {
        throw new Error('Invalid country list response');
      }

      const remoteCountries = payload
        .map((entry) => ({
          code: String(entry?.iso_3166_1 || '').trim().toUpperCase(),
          name: String(entry?.name || '').trim(),
          stationCount: Number(entry?.stationcount),
        }))
        .filter((entry) => /^[A-Z]{2}$/.test(entry.code) && entry.name.length > 0);

      mergeAvailableRadioCountries(remoteCountries);
    } catch (error) {
      console.debug('Falling back to bundled country list', error);
      mergeAvailableRadioCountries([]);
    } finally {
      availableRadioCountriesLoaded = true;
      availableRadioCountriesRequest = null;
    }

    return availableRadioCountries;
  })();

  return availableRadioCountriesRequest;
}

function renderCountrySelectionOverlay() {
  if (!countrySelectionListEl || !countrySelectionSummaryEl || !countrySelectionEmptyEl) return;

  if (countrySelectionSaveBtn) {
    countrySelectionSaveBtn.disabled = countrySelectionSaving;
    countrySelectionSaveBtn.textContent = countrySelectionSaving ? 'Saving...' : 'Save';
  }
  if (countrySelectionDefaultsBtn) countrySelectionDefaultsBtn.disabled = countrySelectionSaving;
  if (countrySelectionAllBtn) countrySelectionAllBtn.disabled = countrySelectionSaving;
  if (countrySelectionCancelBtn) countrySelectionCancelBtn.disabled = countrySelectionSaving;

  const normalizedQuery = countrySelectionQuery.trim().toLowerCase();
  const entries = availableRadioCountries.filter((country) => {
    if (!normalizedQuery) return true;
    return country.name.toLowerCase().includes(normalizedQuery) || country.code.toLowerCase().includes(normalizedQuery);
  });

  const selectedCount = normalizeSelectedRadioCountryCodes(draftSelectedRadioCountryCodes).length;
  const loadingLabel = availableRadioCountriesLoaded ? '' : ' Loading available countries...';
  countrySelectionSummaryEl.textContent = countrySelectionSaving
    ? 'Saving country selection...'
    : `${selectedCount} countries enabled.${loadingLabel}`;

  countrySelectionEmptyEl.classList.toggle('hidden', entries.length > 0);
  countrySelectionListEl.innerHTML = '';

  if (entries.length === 0) {
    return;
  }

  entries.forEach((country) => {
    const item = document.createElement('li');
    item.className = 'device country-selection-item';

    const row = document.createElement('label');
    row.className = 'country-selection-option';

    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.checked = country.code === managedCountryCode || draftSelectedRadioCountryCodes.has(country.code);
    checkbox.disabled = country.code === managedCountryCode || countrySelectionSaving;

    checkbox.addEventListener('change', () => {
      if (checkbox.checked) {
        draftSelectedRadioCountryCodes.add(country.code);
      } else {
        draftSelectedRadioCountryCodes.delete(country.code);
      }
      renderCountrySelectionOverlay();
    });

    const flag = document.createElement('span');
    flag.className = 'country-selection-flag';
    flag.setAttribute('aria-hidden', 'true');
    flag.textContent = country.code === managedCountryCode ? '🇸🇮' : countryCodeToFlagEmoji(country.code);

    const copy = document.createElement('div');
    copy.className = 'country-selection-copy';

    const title = document.createElement('div');
    title.className = 'device-main';
    title.textContent = country.code === managedCountryCode ? `${country.name} (managed)` : country.name;

    const sub = document.createElement('div');
    sub.className = 'device-sub';
    if (country.code === managedCountryCode) {
      sub.textContent = 'Always enabled managed catalog';
    } else if (Number.isFinite(country.stationCount) && country.stationCount > 0) {
      sub.textContent = `${country.code} • ${country.stationCount.toLocaleString()} stations available`;
    } else {
      sub.textContent = country.code;
    }

    copy.appendChild(title);
    copy.appendChild(sub);
    row.appendChild(checkbox);
    row.appendChild(flag);
    row.appendChild(copy);
    item.appendChild(row);
    countrySelectionListEl.appendChild(item);
  });
}

async function openCountrySelectionOverlay() {
  if (!countrySelectionOverlay) return;

  draftSelectedRadioCountryCodes = new Set(getSelectedRadioCountryCodes());
  countrySelectionQuery = '';
  countrySelectionSaving = false;
  if (countrySelectionSearchInput) {
    countrySelectionSearchInput.value = '';
  }

  renderCountrySelectionOverlay();
  countrySelectionOverlay.classList.remove('hidden');
  countrySelectionOverlay.setAttribute('aria-hidden', 'false');
  closeStationCountryFilter();

  void ensureAvailableRadioCountriesLoaded().then(() => {
    renderCountrySelectionOverlay();
  });

  window.setTimeout(() => countrySelectionSearchInput?.focus(), 30);
}

function closeCountrySelectionOverlay() {
  if (!countrySelectionOverlay) return;
  countrySelectionOverlay.classList.add('hidden');
  countrySelectionOverlay.setAttribute('aria-hidden', 'true');
  countrySelectionSaving = false;
}

function getNormalizedStationCountryCode(station) {
  const directCode = typeof station?.countryCode === 'string' ? station.countryCode.trim().toUpperCase() : '';
  if (/^[A-Z]{2}$/.test(directCode)) return directCode;

  const countryValue = typeof station?.country === 'string' ? station.country.trim() : '';
  if (/^[A-Z]{2}$/.test(countryValue)) return countryValue.toUpperCase();

  return '';
}

function isEnabledRadioCountryStation(station) {
  const countryCode = getNormalizedStationCountryCode(station);
  if (!countryCode) return true;
  return getSelectedRadioCountryCodes().includes(countryCode);
}

async function sendSelectedCountriesToServiceWorker({ syncNow = false } = {}) {
  if (!('serviceWorker' in navigator)) {
    return { ok: false, skipped: true };
  }

  const registration = await navigator.serviceWorker.ready.catch(() => null);
  const target = navigator.serviceWorker.controller || registration?.active || registration?.waiting;
  if (!target) {
    return { ok: false, skipped: true };
  }

  return new Promise((resolve) => {
    const channel = new MessageChannel();
    const timeoutId = window.setTimeout(() => {
      resolve({ ok: false, timedOut: true });
    }, syncNow ? 25000 : 8000);

    channel.port1.onmessage = (event) => {
      clearTimeout(timeoutId);
      resolve(event.data || { ok: true });
    };

    target.postMessage({
      type: SERVICE_WORKER_SYNC_COUNTRIES_MESSAGE,
      countryCodes: getSelectedRadioCountryCodes(),
      syncNow,
    }, [channel.port2]);
  });
}

async function saveCountrySelection() {
  if (countrySelectionSaving) return;

  countrySelectionSaving = true;
  renderCountrySelectionOverlay();

  try {
    selectedRadioCountryCodes = new Set(normalizeSelectedRadioCountryCodes(draftSelectedRadioCountryCodes));
    persistSelectedRadioCountryCodes(selectedRadioCountryCodes);
    await sendSelectedCountriesToServiceWorker({ syncNow: true });
    await loadStations();
    if (statusTextEl) {
      statusTextEl.textContent = 'Country selection updated';
    }
    closeCountrySelectionOverlay();
  } catch (error) {
    console.error('Saving country selection failed', error);
    if (statusTextEl) {
      statusTextEl.textContent = 'Unable to refresh selected countries';
    }
  } finally {
    countrySelectionSaving = false;
    renderCountrySelectionOverlay();
  }
}

// ── castBothMode persistence & UI ────────────────────────────────────────────

function saveCastBothMode(val) {
  persistCastBothMode(Boolean(val));
}

function restoreCastBothMode() {
  castBothMode = getPersistedCastBothMode();
}

function updateCastOutputToggleUI() {
  if (!castOutputRow || !castOutputBtn || !castOutputText) return;

  if (castMode === 'cast') {
    castOutputRow.classList.remove('hidden');
  } else {
    castOutputRow.classList.add('hidden');
    return;
  }

  if (castBothMode) {
    castOutputBtn.setAttribute('aria-pressed', 'true');
    castOutputText.textContent = 'Cast + Local';
    castOutputBtn.title = 'Currently: Cast + This computer  —  click to cast only';
  } else {
    castOutputBtn.setAttribute('aria-pressed', 'false');
    castOutputText.textContent = 'Cast only';
    castOutputBtn.title = 'Currently: Cast only  —  click to also play on this computer';
  }
}

function toggleCastBothMode() {
  castBothMode = !castBothMode;
  saveCastBothMode(castBothMode);
  updateCastOutputToggleUI();

  if (castMode !== 'cast' || !isPlaying) return;

  if (castBothMode) {
    // Start local playback in parallel
    playLocal();
  } else {
    // Stop local audio, keep Cast going
    audio.pause();
    audio.src = '';
  }
}

function supportsAirPlay() {
  return typeof audio.webkitShowPlaybackTargetPicker === 'function';
}

function isRemoteOutputActive() {
  return castMode === 'cast' || castMode === 'airplay';
}

function updateAirPlaySubtitle() {
  const station = stations[currentIndex];
  if (!stationSubtitleEl || !station) return;

  const detailSuffix = getStationDetails(station);
  stationSubtitleEl.textContent = detailSuffix ? `AirPlay output • ${detailSuffix}` : 'AirPlay output';
}

function syncAirPlayState() {
  const isWireless = Boolean(audio.webkitCurrentPlaybackTargetIsWireless);
  if (isWireless) {
    castMode = 'airplay';
    if (statusTextEl && isPlaying) statusTextEl.textContent = 'Playing via AirPlay';
    updateAirPlaySubtitle();
  } else if (castMode === 'airplay') {
    castMode = 'local';
  }

  updateEngineBadge();
  updateCastButtonUI();
  updateCastOutputToggleUI();
  updateUI();
}

function initAirPlay() {
  try {
    if ('disableRemotePlayback' in audio) {
      audio.disableRemotePlayback = false;
    }
  } catch (e) {
    console.debug('disableRemotePlayback not configurable:', e);
  }

  if (!supportsAirPlay()) {
    airPlayAvailable = false;
    updateCastButtonUI();
    return;
  }

  airPlayAvailable = true;

  audio.addEventListener('webkitplaybacktargetavailabilitychanged', (event) => {
    airPlayAvailable = event.availability === 'available';
    if (!airPlayAvailable && castMode === 'airplay') {
      castMode = 'local';
      updateEngineBadge();
      updateCastOutputToggleUI();
      updateUI();
    }
    updateCastButtonUI();
  });

  audio.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', () => {
    syncAirPlayState();
  });

  syncAirPlayState();
}

// ── User Stations (IndexedDB snapshot) ──────────────────────────────────────

function loadUserStations() {
  return getPersistedUserStations();
}

// ── Station library, favourites, quick picks ────────────────────────────────

function loadFavoriteStationIds() {
  return getPersistedFavoriteStationIds();
}

function saveFavoriteStationIds(ids) {
  persistFavoriteStationIds(ids);
}

function loadStationUsageCounts() {
  return getPersistedStationUsageCounts();
}

function saveStationUsageCounts(counts) {
  persistStationUsageCounts(counts || {});
}

function incrementStationUsage(station) {
  if (!station?.id) return;
  const counts = loadStationUsageCounts();
  counts[station.id] = Math.max(0, Number(counts[station.id]) || 0) + 1;
  saveStationUsageCounts(counts);
}

function getRecentStationActivityById() {
  const recentActivityById = new Map();

  loadRecentStationHistory().forEach((entry) => {
    if (!entry?.stationId || recentActivityById.has(entry.stationId)) {
      return;
    }

    const playedAtMs = Date.parse(entry.playedAt);
    recentActivityById.set(entry.stationId, Number.isFinite(playedAtMs) ? playedAtMs : 0);
  });

  return recentActivityById;
}

function getStationHealthScore(station, stationHealth) {
  if (!station?.id) return 0.5;

  const health = stationHealth?.[station.id];
  if (!health) return 0.5;

  const attempts = Math.max(0, Number(health.attempts) || 0);
  const successes = Math.max(0, Number(health.successes) || 0);
  const failures = Math.max(0, Number(health.failures) || 0);
  const timeoutCount = Math.max(0, Number(health.timeoutCount) || 0);
  const successRate = attempts > 0 ? successes / attempts : 0.5;
  const failurePressure = attempts > 0 ? failures / attempts : 0;
  const timeoutPenalty = Math.min(timeoutCount * 0.08, 0.3);
  const failurePenalty = Math.min(failurePressure * 0.35, 0.35);

  return Math.max(0, Math.min(1, successRate - timeoutPenalty - failurePenalty));
}

function getRecommendedStationScore(station, context) {
  const healthScore = getStationHealthScore(station, context.stationHealth);
  const usageCount = Math.max(0, Number(context.counts?.[station.id]) || 0);
  const usageBoost = Math.min(usageCount / 20, 0.18);
  const recentPlayedAt = context.recentActivityById.get(station.id) || 0;
  const recentBoost = recentPlayedAt > 0 ? 0.12 : 0;
  const favoriteBoost = context.favourites.has(station.id) ? 0.2 : 0;
  return healthScore + usageBoost + recentBoost + favoriteBoost;
}

function getStationProxySupportDetail(station) {
  const originalUrl = typeof station?.url === 'string' ? station.url.trim() : '';
  const upgradedUrl = toHttpsIfHttp(originalUrl) || originalUrl;
  const isSecurePage = window.location.protocol === 'https:';

  if (isSecurePage && originalUrl.startsWith('http://') && upgradedUrl !== originalUrl) {
    return 'This stream is still HTTP-only on a secure site, so playback will need proxy support.';
  }

  return null;
}

function createStationInfoIndicator(detail, variant = '') {
  const indicator = document.createElement('span');
  indicator.className = `station-info-indicator${variant ? ` ${variant}` : ''}`;
  indicator.textContent = 'i';
  indicator.title = detail;
  indicator.setAttribute('aria-label', detail);
  indicator.setAttribute('role', 'img');
  indicator.setAttribute('tabindex', '0');
  return indicator;
}

function buildStationLibraryInfoDetail(station, { healthBadge = null, proxySupportDetail = null, useCount = 0 } = {}) {
  const lines = [];
  const stationTags = getStationTags(station);
  const subtitle = getStationSubtitle(station);

  if (healthBadge?.detail) {
    lines.push(`${healthBadge.label}: ${healthBadge.detail}`);
  }

  if (proxySupportDetail) {
    lines.push(`Proxy: ${proxySupportDetail}`);
  }

  if (stationTags.length > 0) {
    lines.push(`Tags: ${stationTags.join(', ')}`);
  }

  if (subtitle) {
    lines.push(`Info: ${subtitle}`);
  }

  if (useCount > 0) {
    lines.push(`Plays: ${useCount}`);
  }

  return lines.join('\n');
}

function updateManagedCatalogStatus(source = 'unknown') {
  if (!managedCatalogStatusEl) return;

  if (managedCatalogStatusTimeoutId) {
    clearTimeout(managedCatalogStatusTimeoutId);
    managedCatalogStatusTimeoutId = null;
  }

  const sourceLabels = {
    remote: 'Managed catalog: backend',
    'cached-remote': 'Managed catalog: cached backend',
    bundled: 'Managed catalog: bundled fallback',
  };

  const label = sourceLabels[source];
  if (!label) {
    managedCatalogStatusEl.textContent = '';
    managedCatalogStatusEl.classList.add('hidden');
    return;
  }

  managedCatalogStatusEl.textContent = label;
  managedCatalogStatusEl.classList.remove('hidden');

  managedCatalogStatusTimeoutId = window.setTimeout(() => {
    managedCatalogStatusEl.textContent = '';
    managedCatalogStatusEl.classList.add('hidden');
    managedCatalogStatusTimeoutId = null;
  }, 3500);
}

function formatCatalogSyncTime(value) {
  const parsed = value ? new Date(value) : new Date();
  if (Number.isNaN(parsed.getTime())) {
    return '';
  }

  return new Intl.DateTimeFormat(undefined, {
    hour: '2-digit',
    minute: '2-digit',
  }).format(parsed);
}

function renderCatalogSyncHistory(record) {
  if (!catalogSyncHistoryEl) {
    return;
  }

  if (catalogSyncHistoryTimeoutId) {
    clearTimeout(catalogSyncHistoryTimeoutId);
    catalogSyncHistoryTimeoutId = null;
  }

  const parsed = record?.syncedAt ? new Date(record.syncedAt) : null;
  const formattedTime = parsed && !Number.isNaN(parsed.getTime())
    ? new Intl.DateTimeFormat(undefined, {
      month: 'short',
      day: 'numeric',
      hour: '2-digit',
      minute: '2-digit',
    }).format(parsed)
    : '';

  if (!record || !formattedTime) {
    catalogSyncHistoryEl.textContent = '';
    catalogSyncHistoryEl.classList.add('hidden');
    return;
  }

  const label = record.kind === 'managed' ? 'Managed catalog' : 'Stations';
  catalogSyncHistoryEl.textContent = `Last background refresh: ${label} · ${formattedTime}`;
  catalogSyncHistoryEl.classList.remove('hidden');

  catalogSyncHistoryTimeoutId = window.setTimeout(() => {
    catalogSyncHistoryEl.textContent = '';
    catalogSyncHistoryEl.classList.add('hidden');
    catalogSyncHistoryTimeoutId = null;
  }, 3500);
}

function persistCatalogSyncHistory(kind, syncedAt) {
  const normalizedSyncedAt = syncedAt && !Number.isNaN(new Date(syncedAt).getTime())
    ? new Date(syncedAt).toISOString()
    : new Date().toISOString();

  renderCatalogSyncHistory({ kind, syncedAt: normalizedSyncedAt });
}

function compactCatalogSyncError(error) {
  if (typeof error !== 'string') {
    return '';
  }

  const compact = error.replace(/\s+/g, ' ').trim();
  if (!compact) {
    return '';
  }

  return compact.length > 72 ? `${compact.slice(0, 69)}...` : compact;
}

function showCatalogSyncStatus(message, syncedAt = null, tone = 'info', detail = '') {
  if (!catalogSyncStatusEl || !message) return;

  if (catalogSyncStatusTimeoutId) {
    clearTimeout(catalogSyncStatusTimeoutId);
    catalogSyncStatusTimeoutId = null;
  }

  const compactDetail = compactCatalogSyncError(detail);
  const visibleMessage = compactDetail ? `${message}: ${compactDetail}` : message;
  const accessibleMessage = detail ? `${message}: ${detail}` : message;

  if (catalogSyncStatusTextEl) {
    catalogSyncStatusTextEl.textContent = visibleMessage;
  } else {
    catalogSyncStatusEl.textContent = visibleMessage;
  }

  catalogSyncStatusEl.title = accessibleMessage;
  catalogSyncStatusEl.setAttribute('aria-label', accessibleMessage);

  if (catalogSyncStatusTimeEl) {
    const formattedTime = formatCatalogSyncTime(syncedAt);
    catalogSyncStatusTimeEl.textContent = formattedTime;
    catalogSyncStatusTimeEl.dateTime = syncedAt && !Number.isNaN(new Date(syncedAt).getTime())
      ? new Date(syncedAt).toISOString()
      : new Date().toISOString();
  }

  catalogSyncStatusEl.classList.toggle('is-warning', tone === 'warning');
  catalogSyncStatusEl.classList.remove('hidden');

  catalogSyncStatusTimeoutId = window.setTimeout(() => {
    if (catalogSyncStatusTextEl) {
      catalogSyncStatusTextEl.textContent = '';
    } else {
      catalogSyncStatusEl.textContent = '';
    }
    if (catalogSyncStatusTimeEl) {
      catalogSyncStatusTimeEl.textContent = '';
      catalogSyncStatusTimeEl.removeAttribute('datetime');
    }
    catalogSyncStatusEl.removeAttribute('title');
    catalogSyncStatusEl.removeAttribute('aria-label');
    catalogSyncStatusEl.classList.remove('is-warning');
    catalogSyncStatusEl.classList.add('hidden');
    catalogSyncStatusTimeoutId = null;
  }, 3500);
}

function getStationHealthBadge(station, stationHealth = loadStationHealth()) {
  const health = stationHealth?.[station?.id];
  if (!health || Number(health.attempts) <= 0) {
    return null;
  }

  const score = getStationHealthScore(station, stationHealth);
  const lastFailureAt = health.lastFailedAt ? Date.parse(health.lastFailedAt) : 0;
  const lastSuccessAt = health.lastSucceededAt ? Date.parse(health.lastSucceededAt) : 0;

  if (health.failures > 0 && lastFailureAt >= lastSuccessAt) {
    return {
      label: 'Unstable',
      tone: 'warning',
      detail: 'Recent playback attempts have failed for this station.',
    };
  }

  if (health.successes > 0 && score >= 0.7) {
    return {
      label: 'Healthy',
      tone: 'healthy',
      detail: 'Recent playback attempts have been successful.',
    };
  }

  if (health.failures > 0 || score < 0.55) {
    return {
      label: 'Unstable',
      tone: 'warning',
      detail: 'This station has mixed or degraded playback results.',
    };
  }

  return null;
}

function getStationHealthDetail(station, stationHealth = loadStationHealth()) {
  const badge = getStationHealthBadge(station, stationHealth);
  if (!badge) return null;

  if (badge.tone !== 'warning') {
    return null;
  }

  const health = stationHealth?.[station?.id];
  switch (health?.lastFailureReason) {
    case 'startup-timeout':
      return 'Recent failure: the stream did not respond in time.';
    case 'tls-or-network-error':
      return 'Recent failure: network or TLS negotiation failed.';
    case 'unsupported-format':
      return 'Recent failure: the browser could not play this stream format.';
    case 'insecure-stream-blocked':
      return 'Recent failure: the browser blocked an insecure stream on this secure site.';
    case 'unknown':
    default:
      return 'Recent failure: playback was unstable or ended with an unknown stream error.';
  }
}

function updateActiveStationHealthSummary(station) {
  if (!stationHealthSummaryEl || !station || castMode !== 'local') {
    stationHealthSummaryEl?.classList.add('hidden');
    stationHealthDetailEl?.classList.add('hidden');
    if (stationHealthSummaryEl) {
      stationHealthSummaryEl.replaceChildren();
      stationHealthSummaryEl.textContent = '';
      stationHealthSummaryEl.className = 'station-health-summary hidden';
      stationHealthSummaryEl.removeAttribute('title');
      stationHealthSummaryEl.removeAttribute('aria-label');
    }
    if (stationHealthDetailEl) {
      stationHealthDetailEl.textContent = '';
      stationHealthDetailEl.className = 'station-health-detail hidden';
    }
    return;
  }

  const stationHealth = loadStationHealth();
  const proxySupportDetail = getStationProxySupportDetail(station);
  const healthBadge = getStationHealthBadge(station, stationHealth);
  if (proxySupportDetail) {
    stationHealthSummaryEl.className = 'station-health-summary station-health-summary-info';
    stationHealthSummaryEl.textContent = 'Proxy support';
    stationHealthSummaryEl.title = proxySupportDetail;
    stationHealthSummaryEl.setAttribute('aria-label', proxySupportDetail);
    if (stationHealthDetailEl) {
      stationHealthDetailEl.className = 'station-health-detail hidden';
      stationHealthDetailEl.textContent = '';
    }
    return;
  }

  if (!healthBadge) {
    stationHealthSummaryEl.className = 'station-health-summary hidden';
    stationHealthSummaryEl.replaceChildren();
    stationHealthSummaryEl.textContent = '';
    stationHealthSummaryEl.removeAttribute('title');
    stationHealthSummaryEl.removeAttribute('aria-label');
    if (stationHealthDetailEl) {
      stationHealthDetailEl.className = 'station-health-detail hidden';
      stationHealthDetailEl.textContent = '';
    }
    return;
  }

  stationHealthSummaryEl.className = `station-health-summary station-health-summary-${healthBadge.tone}`;
  stationHealthSummaryEl.replaceChildren();
  stationHealthSummaryEl.textContent = healthBadge.label;
  stationHealthSummaryEl.title = healthBadge.detail;
  stationHealthSummaryEl.setAttribute('aria-label', healthBadge.detail);

  if (stationHealthDetailEl) {
    const detail = getStationHealthDetail(station, stationHealth);
    if (detail) {
      stationHealthDetailEl.className = `station-health-detail station-health-detail-${healthBadge.tone}`;
      stationHealthDetailEl.textContent = detail;
    } else {
      stationHealthDetailEl.className = 'station-health-detail hidden';
      stationHealthDetailEl.textContent = '';
    }
  }
}

function sortStationEntries(entries, context) {
  const sortedEntries = entries.slice();

  sortedEntries.sort((left, right) => {
    const leftTitle = getStationTitle(left.station);
    const rightTitle = getStationTitle(right.station);
    const leftRecentPlayedAt = context.recentActivityById.get(left.station.id) || 0;
    const rightRecentPlayedAt = context.recentActivityById.get(right.station.id) || 0;
    const leftUsageCount = Math.max(0, Number(context.counts?.[left.station.id]) || 0);
    const rightUsageCount = Math.max(0, Number(context.counts?.[right.station.id]) || 0);
    const leftFavorite = context.favourites.has(left.station.id) ? 1 : 0;
    const rightFavorite = context.favourites.has(right.station.id) ? 1 : 0;

    switch (stationLibrarySort) {
      case 'name':
        return leftTitle.localeCompare(rightTitle, undefined, { sensitivity: 'base' }) || left.index - right.index;
      case 'recent':
        return rightRecentPlayedAt - leftRecentPlayedAt || rightUsageCount - leftUsageCount || left.index - right.index;
      case 'favorites':
        return rightFavorite - leftFavorite || leftTitle.localeCompare(rightTitle, undefined, { sensitivity: 'base' }) || left.index - right.index;
      case 'health': {
        const leftHealthScore = getStationHealthScore(left.station, context.stationHealth);
        const rightHealthScore = getStationHealthScore(right.station, context.stationHealth);
        return rightHealthScore - leftHealthScore || leftTitle.localeCompare(rightTitle, undefined, { sensitivity: 'base' }) || left.index - right.index;
      }
      case 'recommended':
      default: {
        const leftRecommendedScore = getRecommendedStationScore(left.station, context);
        const rightRecommendedScore = getRecommendedStationScore(right.station, context);
        return rightRecommendedScore - leftRecommendedScore || leftTitle.localeCompare(rightTitle, undefined, { sensitivity: 'base' }) || left.index - right.index;
      }
    }
  });

  return sortedEntries;
}

function getStationTitle(station) {
  return station?.name || station?.title || station?.id || 'Station';
}

function isMobileTitleViewport() {
  return window.matchMedia('(max-width: 760px)').matches;
}

function renderStationTitle(title, shouldScroll = false) {
  if (!stationNameEl) return;

  stationNameEl.replaceChildren();

  if (!shouldScroll) {
    stationNameEl.classList.remove('station-title-marquee');
    const plainTitle = document.createElement('span');
    plainTitle.className = 'station-title-text';
    plainTitle.textContent = title;
    stationNameEl.appendChild(plainTitle);
    return;
  }

  stationNameEl.classList.add('station-title-marquee');
  const track = document.createElement('span');
  track.className = 'station-title-track';

  const firstCopy = document.createElement('span');
  firstCopy.className = 'station-title-copy';
  firstCopy.textContent = title;

  const secondCopy = document.createElement('span');
  secondCopy.className = 'station-title-copy';
  secondCopy.setAttribute('aria-hidden', 'true');
  secondCopy.textContent = title;

  track.append(firstCopy, secondCopy);
  stationNameEl.appendChild(track);
}

function updateStationTitleLayout(title) {
  if (!stationNameEl) return;

  const titleText = String(title || '').trim() || 'Station';

  if (stationTitleRafId) {
    cancelAnimationFrame(stationTitleRafId);
    stationTitleRafId = null;
  }

  renderStationTitle(titleText, false);

  if (!isMobileTitleViewport()) return;

  stationTitleRafId = window.requestAnimationFrame(() => {
    stationTitleRafId = null;
    if (!stationNameEl || !isMobileTitleViewport()) return;
    if (stationNameEl.scrollWidth > stationNameEl.clientWidth + 2) {
      renderStationTitle(titleText, true);
    }
  });
}

function getStationCountry(station) {
  return station?.countryCode || station?.country || station?.region || '';
}

function getStationTags(station) {
  return Array.isArray(station?.tags) ? station.tags.filter(Boolean) : [];
}

function getStationTechnicalLabel(station) {
  const parts = [];
  if (station?.codec) parts.push(String(station.codec).toUpperCase());
  if (Number.isFinite(station?.bitrate) && station.bitrate > 0) parts.push(`${station.bitrate} kbps`);
  return parts.join(' • ');
}

function getStationLanguageValues(station) {
  const rawLanguages = [
    station?.language,
    station?.languagecodes,
    station?.languages,
    station?.raw?.language,
    station?.raw?.languages,
  ].flatMap((value) => {
    if (Array.isArray(value)) return value;
    if (typeof value === 'string') return value.split(/[;,/|]/g);
    return [];
  });

  return Array.from(new Set(
    rawLanguages
      .map((value) => String(value || '').trim())
      .filter(Boolean)
      .map((value) => value.replace(/\s+/g, ' ')),
  ));
}

function getStationPrimaryLanguage(station) {
  return getStationLanguageValues(station)[0] || '';
}

function getStationCodecValue(station) {
  return String(station?.codec || '').trim().toUpperCase();
}

function getStationBitrateBucket(station) {
  const bitrate = Number(station?.bitrate);
  if (!Number.isFinite(bitrate) || bitrate <= 0) return 'unknown';
  if (bitrate <= 96) return 'low';
  if (bitrate < 192) return 'mid';
  return 'high';
}

function getStationDetails(station) {
  return [
    getCountryDisplayName(getStationCountry(station)),
    getStationPrimaryLanguage(station),
    getStationTechnicalLabel(station),
  ].filter(Boolean).join(' • ');
}

function getStationSearchText(station) {
  return [
    getStationTitle(station),
    getStationCountry(station),
    getStationSubtitle(station),
    getStationPrimaryLanguage(station),
    getStationTechnicalLabel(station),
    getStationCategory(station),
    ...getStationTags(station),
  ].join(' ').toLowerCase();
}

function getStationLogoUrl(station) {
  return station?.logo || station?.poster || station?.raw?.assets?.logo || station?.raw?.logo || station?.raw?.assets?.poster || station?.raw?.poster || '';
}

function getStationLogoCandidates(station) {
  const logoUrl = getStationLogoUrl(station);
  return uniqueNonEmpty([
    toHttpsIfHttp(logoUrl),
    logoUrl,
    RADIO_PLACEHOLDER_LOGO,
  ]);
}

function getStationSubtitle(station) {
  return station?.slogan
    || station?.raw?.slogan
    || station?.raw?.defaultText
    || '';
}

function getStationCategory(station) {
  if (station?._user) return 'Custom';
  if (station?.category) return station.category;
  const raw = station?.raw || {};
  const explicit = raw.category || raw.genre || raw.format || raw.type;
  if (explicit) return String(explicit);

  const text = `${station?.id || ''} ${station?.name || ''} ${raw.title || ''} ${raw.slogan || ''} ${getStationTags(station).join(' ')}`.toLowerCase();
  if (/news|talk|politic|speech|discussion/.test(text)) return 'Talk';
  if (/jazz|blues|soul/.test(text)) return 'Jazz';
  if (/classical|orchestra|symphony|opera/.test(text)) return 'Classical';
  if (/electronic|dance|house|techno|trance|edm/.test(text)) return 'Electronic';
  if (/ambient|chill|downtempo/.test(text)) return 'Ambient';
  if (/rock|metal/.test(text)) return 'Rock';
  if (/folk|veseljak|zvoki|narod|country/.test(text)) return 'Folk';
  if (/aktual|pop|hit|dance|top/.test(text)) return 'Pop';
  if (/80|90|70|retro|gold|old/.test(text)) return 'Retro';
  if (/maribor|celje|kranj|triglav|velenje|korosk|koper|regional/.test(text)) return 'Regional';
  return 'General';
}

function getStationEntries() {
  return stations.map((station, index) => ({ station, index }));
}

function getCategoryNames() {
  const entries = getStationEntries().filter(({ station }) => stationLibraryCountry === 'all' || getStationCountry(station) === stationLibraryCountry);
  return Array.from(new Set(entries.map(({ station }) => getStationCategory(station)))).sort((a, b) => a.localeCompare(b));
}

function getCountryNames() {
  return Array.from(new Set(stations.map(getStationCountry).filter(Boolean))).sort((a, b) => a.localeCompare(b));
}

function getLanguageOptions() {
  return Array.from(new Set(
    stations.flatMap((station) => getStationLanguageValues(station)),
  )).sort((a, b) => a.localeCompare(b));
}

function getCodecOptions() {
  return Array.from(new Set(
    stations.map((station) => getStationCodecValue(station)).filter(Boolean),
  )).sort((a, b) => a.localeCompare(b));
}

function getCountryDisplayName(countryValue) {
  const value = String(countryValue || '').trim();
  if (!value) return '';
  if (value === 'all') return 'All countries';
  if (value === 'SI') return 'Slovenia (managed)';
  if (/^[A-Z]{2}$/i.test(value)) {
    const countryName = getAvailableRadioCountryNameByCode(value.toUpperCase());
    if (countryName) return countryName;
  }
  return value;
}

function getCountryFilterDisplayName(countryValue) {
  const value = String(countryValue || '').trim();
  if (!value) return '';
  if (value === 'all') return 'All countries';
  if (value.toUpperCase() === 'SI') return 'SLOVENIA (managed)';
  if (/^[A-Z]{2}$/i.test(value)) {
    const countryName = getAvailableRadioCountryNameByCode(value.toUpperCase());
    if (countryName) return countryName;
  }
  return value;
}

function getCountryCodeFromValue(countryValue) {
  const value = String(countryValue || '').trim();
  if (!value || value === 'all') return '';
  if (/^[A-Z]{2}$/i.test(value)) return value.toUpperCase();
  return radioCountryCodeByName.get(value)
    || availableRadioCountries.find((country) => country.name === value)?.code
    || '';
}

function resetStationLibraryPage() {
  stationLibraryPage = 0;
}

function goToPreviousStationLibraryPage() {
  if (stationLibraryPage <= 0) return;
  stationLibraryPage -= 1;
  renderStationLibrary();
}

function goToNextStationLibraryPage() {
  if (stationLibraryPage >= stationLibraryPageTotal - 1) return;
  stationLibraryPage += 1;
  renderStationLibrary();
}

function countryCodeToFlagEmoji(countryCode) {
  const code = String(countryCode || '').trim().toUpperCase();
  if (!/^[A-Z]{2}$/.test(code)) return '🌐';
  return String.fromCodePoint(...code.split('').map((char) => 127397 + char.charCodeAt(0)));
}

function renderLibrarySelectOptions({ wrapEl, btnEl, menuEl, textEl, open, value, fallbackLabel, options, onSelect }) {
  if (!btnEl || !menuEl || !textEl) return;

  const currentLabel = options.find((option) => option.value === value)?.label ?? fallbackLabel;
  textEl.textContent = currentLabel;
  btnEl.setAttribute('aria-expanded', open ? 'true' : 'false');
  wrapEl?.classList.toggle('open', open);
  menuEl.classList.toggle('open', open);

  if (!open) {
    menuEl.innerHTML = '';
    return;
  }

  menuEl.innerHTML = '';
  options.forEach((option) => {
    const button = document.createElement('button');
    button.type = 'button';
    button.className = 'library-select-option' + (option.value === value ? ' active' : '');
    button.setAttribute('role', 'option');
    button.setAttribute('aria-selected', option.value === value ? 'true' : 'false');

    const text = document.createElement('span');
    text.className = 'library-select-option-text';
    text.textContent = option.label;
    button.appendChild(text);

    button.addEventListener('click', () => onSelect(option.value));
    menuEl.appendChild(button);
  });
}

function syncStationLibraryMetadataFilters() {
  const languageOptions = getLanguageOptions();
  if (stationLibraryLanguage !== 'all' && !languageOptions.includes(stationLibraryLanguage)) {
    stationLibraryLanguage = 'all';
  }
  renderLibrarySelectOptions({
    wrapEl: stationLanguageFilterWrapEl,
    btnEl: stationLanguageFilterBtn,
    menuEl: stationLanguageFilterMenu,
    textEl: stationLanguageFilterText,
    open: stationLanguageFilterOpen,
    value: stationLibraryLanguage,
    fallbackLabel: 'All languages',
    options: [
      { value: 'all', label: 'All languages' },
      ...languageOptions.map((language) => ({ value: language, label: language })),
    ],
    onSelect: (nextValue) => {
      stationLibraryLanguage = nextValue;
      stationLanguageFilterOpen = false;
      resetStationLibraryPage();
      renderStationLibrary();
    },
  });

  const codecOptions = getCodecOptions();
  if (stationLibraryCodec !== 'all' && !codecOptions.includes(stationLibraryCodec)) {
    stationLibraryCodec = 'all';
  }
  renderLibrarySelectOptions({
    wrapEl: stationCodecFilterWrapEl,
    btnEl: stationCodecFilterBtn,
    menuEl: stationCodecFilterMenu,
    textEl: stationCodecFilterText,
    open: stationCodecFilterOpen,
    value: stationLibraryCodec,
    fallbackLabel: 'All codecs',
    options: [
      { value: 'all', label: 'All codecs' },
      ...codecOptions.map((codec) => ({ value: codec, label: codec })),
    ],
    onSelect: (nextValue) => {
      stationLibraryCodec = nextValue;
      stationCodecFilterOpen = false;
      resetStationLibraryPage();
      renderStationLibrary();
    },
  });

  renderLibrarySelectOptions({
    wrapEl: stationBitrateFilterWrapEl,
    btnEl: stationBitrateFilterBtn,
    menuEl: stationBitrateFilterMenu,
    textEl: stationBitrateFilterText,
    open: stationBitrateFilterOpen,
    value: stationLibraryBitrate,
    fallbackLabel: 'Any bitrate',
    options: [
      { value: 'all', label: 'Any bitrate' },
      { value: 'low', label: 'Up to 96 kbps' },
      { value: 'mid', label: '97-191 kbps' },
      { value: 'high', label: '192+ kbps' },
    ],
    onSelect: (nextValue) => {
      stationLibraryBitrate = nextValue;
      stationBitrateFilterOpen = false;
      resetStationLibraryPage();
      renderStationLibrary();
    },
  });

  if (stationHealthFilterBtn) {
    stationHealthFilterBtn.setAttribute('aria-pressed', stationLibraryHealthyOnly ? 'true' : 'false');
    stationHealthFilterBtn.classList.toggle('active', stationLibraryHealthyOnly);
  }
  if (stationHealthFilterText) {
    stationHealthFilterText.textContent = stationLibraryHealthyOnly ? 'Works well recently' : 'All stations';
  }
}

function getCountryFlag(countryName) {
  if (!countryName || countryName === 'all') return '🌐';
  const countryCode = getCountryCodeFromValue(countryName);
  return countryCode ? countryCodeToFlagEmoji(countryCode) : '🏳️';
}

function toggleStationCountryFilter(forceOpen) {
  stationCountryFilterOpen = typeof forceOpen === 'boolean' ? forceOpen : !stationCountryFilterOpen;
  renderStationLibrary();
}

function closeStationCountryFilter() {
  if (!stationCountryFilterOpen) return;
  stationCountryFilterOpen = false;
  renderStationLibrary();
}

const SORT_OPTIONS = [
  { value: 'recommended', label: 'Recommended' },
  { value: 'name',        label: 'Name' },
  { value: 'recent',      label: 'Recently played' },
  { value: 'favorites',   label: 'Favorites first' },
  { value: 'health',      label: 'Healthiest' },
];

function closeLanguageFilter() {
  if (!stationLanguageFilterOpen) return;
  stationLanguageFilterOpen = false;
  syncStationLibraryMetadataFilters();
}

function closeCodecFilter() {
  if (!stationCodecFilterOpen) return;
  stationCodecFilterOpen = false;
  syncStationLibraryMetadataFilters();
}

function closeBitrateFilter() {
  if (!stationBitrateFilterOpen) return;
  stationBitrateFilterOpen = false;
  syncStationLibraryMetadataFilters();
}

function openAdvancedFiltersOverlay() {
  if (!stationAdvancedFiltersOverlay) return;
  stationAdvancedFiltersOpen = true;
  stationAdvancedFiltersOverlay.classList.remove('hidden');
  stationAdvancedFiltersOverlay.setAttribute('aria-hidden', 'false');
}

function closeAdvancedFiltersOverlay() {
  if (!stationAdvancedFiltersOverlay) return;
  stationAdvancedFiltersOpen = false;
  stationAdvancedFiltersOverlay.classList.add('hidden');
  stationAdvancedFiltersOverlay.setAttribute('aria-hidden', 'true');
  closeSortFilter();
  closeLanguageFilter();
  closeCodecFilter();
  closeBitrateFilter();
}

function closeSortFilter() {
  if (!stationSortOpen) return;
  stationSortOpen = false;
  renderSortOptions();
}

function renderSortOptions() {
  if (!stationSortBtn || !stationSortMenu || !stationSortText) return;

  const current = SORT_OPTIONS.find((o) => o.value === stationLibrarySort) ?? SORT_OPTIONS[0];
  stationSortText.textContent = current.label;
  stationSortBtn.setAttribute('aria-expanded', stationSortOpen ? 'true' : 'false');
  stationSortWrapEl?.classList.toggle('open', stationSortOpen);
  stationSortMenu.classList.toggle('open', stationSortOpen);

  if (!stationSortOpen) {
    stationSortMenu.innerHTML = '';
    return;
  }

  stationSortMenu.innerHTML = '';
  SORT_OPTIONS.forEach(({ value, label }) => {
    const opt = document.createElement('button');
    opt.type = 'button';
    opt.className = 'library-select-option' + (stationLibrarySort === value ? ' active' : '');
    opt.setAttribute('role', 'option');
    opt.setAttribute('aria-selected', stationLibrarySort === value ? 'true' : 'false');
    const text = document.createElement('span');
    text.className = 'library-select-option-text';
    text.textContent = label;
    opt.appendChild(text);
    opt.addEventListener('click', () => {
      stationLibrarySort = value;
      stationSortOpen = false;
      resetStationLibraryPage();
      renderSortOptions();
      renderStationLibrary();
    });
    stationSortMenu.appendChild(opt);
  });
}

function renderCountryFilterOptions() {
  if (!stationCountryFilterMenu || !stationCountryFilterBtn || !stationCountryFilterText) return;

  const countries = getCountryNames();
  if (stationCatalogState === 'ready' && stationLibraryCountry !== 'all' && !countries.includes(stationLibraryCountry)) {
    stationLibraryCountry = 'all';
    saveLastStationCountry('all');
    resetStationLibraryPage();
  }

  stationCountryFilterBtn.setAttribute('aria-expanded', stationCountryFilterOpen ? 'true' : 'false');
  stationCountryFilterText.textContent = getCountryFilterDisplayName(stationLibraryCountry);
  if (stationCountryFilterFlag) {
    stationCountryFilterFlag.textContent = getCountryFlag(stationLibraryCountry);
  }
  stationCountryFilterWrapEl?.classList.toggle('open', stationCountryFilterOpen);
  stationCountryFilterMenu.innerHTML = '';
  stationCountryFilterMenu.classList.toggle('open', stationCountryFilterOpen);

  const addOption = (label, value) => {
    const option = document.createElement('button');
    option.type = 'button';
    option.className = 'library-select-option' + (stationLibraryCountry === value ? ' active' : '');
    option.setAttribute('role', 'option');
    option.setAttribute('aria-selected', stationLibraryCountry === value ? 'true' : 'false');

    const flag = document.createElement('span');
    flag.className = 'library-select-option-flag';
    flag.setAttribute('aria-hidden', 'true');
    flag.textContent = getCountryFlag(value);

    const text = document.createElement('span');
    text.className = 'library-select-option-text';
    text.textContent = label;

    option.appendChild(flag);
    option.appendChild(text);

    option.addEventListener('click', () => {
      stationLibraryCountry = value;
      stationLibraryCategory = 'all';
      saveLastStationCountry(value);
      resetStationLibraryPage();
      stationCountryFilterOpen = false;
      renderStationLibrary();
    });
    stationCountryFilterMenu.appendChild(option);
  };

  addOption('All countries', 'all');
  const orderedCountries = countries
    .slice()
    .sort((left, right) => {
      const leftPriority = left.toUpperCase() === 'SI' ? -1 : 0;
      const rightPriority = right.toUpperCase() === 'SI' ? -1 : 0;
      if (leftPriority !== rightPriority) return leftPriority - rightPriority;
      return getCountryFilterDisplayName(left).localeCompare(getCountryFilterDisplayName(right));
    });

  orderedCountries.forEach((country) => addOption(getCountryFilterDisplayName(country), country));

  const manageButton = document.createElement('button');
  manageButton.type = 'button';
  manageButton.className = 'library-select-manage-btn';
  manageButton.textContent = 'Choose countries...';
  manageButton.addEventListener('click', () => {
    void openCountrySelectionOverlay();
  });
  stationCountryFilterMenu.appendChild(manageButton);
}

function getFilteredStationEntries() {
  const favourites = loadFavoriteStationIds();
  const counts = loadStationUsageCounts();
  const recentActivityById = getRecentStationActivityById();
  const stationHealth = loadStationHealth();
  const query = stationLibraryQuery.trim().toLowerCase();

  let entries = getStationEntries();
  if (stationLibraryTab === 'favourites') {
    entries = entries.filter(({ station }) => favourites.has(station.id));
  } else if (stationLibraryTab === 'recent') {
    entries = entries
      .map((entry) => ({
        ...entry,
        count: Number(counts[entry.station.id]) || 0,
        recentPlayedAt: recentActivityById.get(entry.station.id) || 0,
      }))
      .filter((entry) => entry.recentPlayedAt > 0 || entry.count > 0)
      .sort((a, b) => b.recentPlayedAt - a.recentPlayedAt || b.count - a.count || a.index - b.index);
  }

  if (stationLibraryCountry !== 'all') {
    entries = entries.filter(({ station }) => getStationCountry(station) === stationLibraryCountry);
  }

  if (stationLibraryTab === 'categories' && stationLibraryCategory !== 'all') {
    entries = entries.filter(({ station }) => getStationCategory(station) === stationLibraryCategory);
  }

  if (stationLibraryLanguage !== 'all') {
    entries = entries.filter(({ station }) => getStationLanguageValues(station).includes(stationLibraryLanguage));
  }

  if (stationLibraryCodec !== 'all') {
    entries = entries.filter(({ station }) => getStationCodecValue(station) === stationLibraryCodec);
  }

  if (stationLibraryBitrate !== 'all') {
    entries = entries.filter(({ station }) => getStationBitrateBucket(station) === stationLibraryBitrate);
  }

  if (stationLibraryHealthyOnly) {
    entries = entries.filter(({ station }) => getStationHealthScore(station, stationHealth) >= 0.7);
  }

  if (query) {
    entries = entries.filter(({ station }) => getStationSearchText(station).includes(query));
  }

  return sortStationEntries(entries, {
    favourites,
    counts,
    recentActivityById,
    stationHealth,
  });
}

function getQuickPickEntries() {
  const favourites = loadFavoriteStationIds();
  const entries = getStationEntries();
  const favEntries = entries.filter(({ station }) => favourites.has(station.id));
  if (favEntries.length > 0) return favEntries;

  const counts = loadStationUsageCounts();
  return entries
    .map((entry) => ({ ...entry, count: Number(counts[entry.station.id]) || 0 }))
    .sort((a, b) => {
      if (b.count !== a.count) return b.count - a.count;
      return a.index - b.index;
    })
    .slice(0, 10);
}

function setStationLibraryTab(tab) {
  stationLibraryTab = tab || 'all';
  if (stationLibraryTab !== 'categories') stationLibraryCategory = 'all';
  resetStationLibraryPage();
  renderStationLibrary();
}

function toggleFavoriteStation(station) {
  if (!station?.id) return;
  const favourites = loadFavoriteStationIds();
  if (favourites.has(station.id)) favourites.delete(station.id);
  else favourites.add(station.id);
  saveFavoriteStationIds(favourites);
  renderStationLibrary({ preserveScroll: true });
  renderCoverflow();
}

function makeStationLogo(station, className, fallbackClassName) {
  const wrap = document.createElement('div');
  const title = getStationTitle(station);
  const img = document.createElement('img');
  img.className = className;
  img.alt = `${title} logo`;
  img.referrerPolicy = 'no-referrer';
  setImgWithFallback(img, getStationLogoCandidates(station), () => {
    wrap.innerHTML = '';
    const fallback = document.createElement('div');
    fallback.className = fallbackClassName;
    fallback.textContent = title.charAt(0).toUpperCase();
    wrap.appendChild(fallback);
  });
  wrap.appendChild(img);

  return wrap.firstElementChild || wrap;
}

function renderCategoryChips() {
  if (!stationCategoryListEl) return;
  stationCategoryListEl.innerHTML = '';

  const chips = ['all', ...getCategoryNames()];
  if (stationLibraryCategory !== 'all' && !chips.includes(stationLibraryCategory)) {
    stationLibraryCategory = 'all';
  }

  chips.forEach((category) => {
    const btn = document.createElement('button');
    btn.className = 'category-chip' + (stationLibraryCategory === category ? ' active' : '');
    btn.type = 'button';
    btn.textContent = category === 'all' ? 'All categories' : category;
    btn.addEventListener('click', () => {
      stationLibraryCategory = category;
      resetStationLibraryPage();
      renderStationLibrary();
    });
    stationCategoryListEl.appendChild(btn);
  });
}

function renderStationLibrary(options = {}) {
  try {
    if (!stationLibraryListEl) return;
    const preserveScroll = options?.preserveScroll === true;
    const previousScrollTop = preserveScroll ? stationLibraryListEl.scrollTop : 0;
    const restoreScrollPosition = () => {
      if (!preserveScroll) return;
      stationLibraryListEl.scrollTop = previousScrollTop;
    };
    stationLibraryListEl.innerHTML = '';
    if (!preserveScroll) {
      stationLibraryListEl.scrollTop = 0;
    }
    stationLibraryEl?.classList.toggle('show-categories', stationLibraryTab === 'categories');

    stationTabBtns.forEach((btn) => {
      const active = btn.dataset.stationTab === stationLibraryTab;
      btn.classList.toggle('active', active);
      btn.setAttribute('aria-selected', active ? 'true' : 'false');
    });

    renderCountryFilterOptions();
    renderSortOptions();
    syncStationLibraryMetadataFilters();
    renderCategoryChips();

    if (stationCatalogState === 'loading') {
      if (stationLibrarySummaryEl) stationLibrarySummaryEl.textContent = 'Loading stations...';
      if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
      if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
      if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
      stationLibraryPaginationEl?.classList.add('hidden');
      const empty = document.createElement('li');
      empty.className = 'library-empty';
      empty.textContent = 'Loading the local radio catalog...';
      stationLibraryListEl.appendChild(empty);
      restoreScrollPosition();
      return;
    }

    if (stationCatalogState === 'error') {
      if (stationLibrarySummaryEl) stationLibrarySummaryEl.textContent = 'Unable to load stations';
      if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
      if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
      if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
      stationLibraryPaginationEl?.classList.add('hidden');
      const empty = document.createElement('li');
      empty.className = 'library-empty';
      empty.textContent = stationCatalogError || 'The local station catalog could not be loaded.';
      stationLibraryListEl.appendChild(empty);
      restoreScrollPosition();
      return;
    }

    if (!stations.length) {
      if (stationLibrarySummaryEl) stationLibrarySummaryEl.textContent = 'No stations available';
      if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
      if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
      if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
      stationLibraryPaginationEl?.classList.add('hidden');
      const empty = document.createElement('li');
      empty.className = 'library-empty';
      empty.textContent = 'The local station catalog is empty.';
      stationLibraryListEl.appendChild(empty);
      restoreScrollPosition();
      return;
    }

    const favourites = loadFavoriteStationIds();
    const counts = loadStationUsageCounts();
    const entries = getFilteredStationEntries();

    const tabLabel = stationLibraryTab === 'favourites' ? 'favourite' : stationLibraryTab === 'recent' ? 'recent' : 'available';
    if (stationLibrarySummaryEl) {
      const countryLabel = stationLibraryCountry === 'all' ? '' : ` in ${getCountryDisplayName(stationLibraryCountry)}`;
      stationLibrarySummaryEl.textContent = `${entries.length} ${tabLabel} station${entries.length === 1 ? '' : 's'}${countryLabel}`;
    }

    const totalPages = Math.max(1, Math.ceil(entries.length / STATION_LIBRARY_PAGE_SIZE));
    stationLibraryPageTotal = totalPages;
    stationLibraryPaginationEl?.classList.toggle('hidden', totalPages <= 1);
    if (stationLibraryPage >= totalPages) {
      stationLibraryPage = totalPages - 1;
    }
    const pageStart = stationLibraryPage * STATION_LIBRARY_PAGE_SIZE;
    const pageEntries = entries.slice(pageStart, pageStart + STATION_LIBRARY_PAGE_SIZE);

    if (entries.length === 0) {
      if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
      if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
      if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
      const empty = document.createElement('li');
      empty.className = 'library-empty';
      empty.textContent = stationLibraryTab === 'favourites'
        ? 'No favourites yet. Use the star button to build your quick list.'
        : 'No stations match this filter.';
      stationLibraryListEl.appendChild(empty);
      restoreScrollPosition();
      return;
    }

      if (stationLibraryPageInfo) {
        stationLibraryPageInfo.textContent = `Page ${stationLibraryPage + 1} of ${totalPages}`;
      }

      if (stationLibraryPagePrevBtn) {
        stationLibraryPagePrevBtn.disabled = stationLibraryPage <= 0;
        stationLibraryPagePrevBtn.setAttribute('aria-disabled', stationLibraryPage <= 0 ? 'true' : 'false');
      }

      if (stationLibraryPageNextBtn) {
        stationLibraryPageNextBtn.disabled = stationLibraryPage >= totalPages - 1;
        stationLibraryPageNextBtn.setAttribute('aria-disabled', stationLibraryPage >= totalPages - 1 ? 'true' : 'false');
      }

      const stationHealth = loadStationHealth();

      pageEntries.forEach(({ station, index, count }) => {
      const title = getStationTitle(station);
      const healthBadge = getStationHealthBadge(station, stationHealth);
      const proxySupportDetail = getStationProxySupportDetail(station);
      const li = document.createElement('li');

      const row = document.createElement('div');
      row.className = 'library-station' + (index === currentIndex ? ' current' : '');
      row.setAttribute('role', 'button');
      row.setAttribute('tabindex', '0');
      row.setAttribute('aria-current', index === currentIndex ? 'true' : 'false');
      row.title = title;

      row.appendChild(makeStationLogo(station, 'library-station-logo', 'library-station-fallback'));

      const copy = document.createElement('div');
      copy.className = 'library-station-copy';
      const titleRow = document.createElement('div');
      titleRow.className = 'library-station-title-row';
      const titleEl = document.createElement('div');
      titleEl.className = 'library-station-title';
      titleEl.textContent = title;
      titleRow.appendChild(titleEl);
      const meta = document.createElement('div');
      meta.className = 'library-station-meta';
      const country = document.createElement('span');
      country.className = 'library-station-country';
      country.textContent = getCountryDisplayName(getStationCountry(station)) || getStationCategory(station);
      const tech = document.createElement('span');
      tech.className = 'library-station-tech';
      tech.textContent = getStationTechnicalLabel(station) || getStationCategory(station);
      meta.appendChild(country);
      meta.appendChild(tech);
      copy.appendChild(titleRow);
      copy.appendChild(meta);
      const useCount = Number(counts[station.id] ?? count) || 0;

      const infoDetail = buildStationLibraryInfoDetail(station, {
        healthBadge,
        proxySupportDetail,
        useCount,
      });

      if (infoDetail) {
        titleRow.appendChild(createStationInfoIndicator(infoDetail, 'station-info-indicator-inline station-info-indicator-neutral'));
      }

      row.appendChild(copy);

      const favBtn = document.createElement('span');
      favBtn.className = 'favorite-btn' + (favourites.has(station.id) ? ' active' : '');
      favBtn.setAttribute('role', 'button');
      favBtn.setAttribute('aria-pressed', favourites.has(station.id) ? 'true' : 'false');
      favBtn.setAttribute('tabindex', '0');
      favBtn.setAttribute('aria-label', favourites.has(station.id) ? `Remove ${title} from favourites` : `Add ${title} to favourites`);
      favBtn.textContent = favourites.has(station.id) ? '★' : '☆';
      favBtn.addEventListener('click', (ev) => {
        ev.preventDefault();
        ev.stopPropagation();
        toggleFavoriteStation(station);
      });
      favBtn.addEventListener('keydown', (ev) => {
        if (ev.key !== 'Enter' && ev.key !== ' ') return;
        ev.preventDefault();
        ev.stopPropagation();
        toggleFavoriteStation(station);
      });
      row.appendChild(favBtn);

      row.addEventListener('click', async () => {
        await activateStationByIndex(index);
      });
      row.addEventListener('keydown', async (ev) => {
        if (ev.key !== 'Enter' && ev.key !== ' ') return;
        ev.preventDefault();
        await activateStationByIndex(index);
      });

      li.appendChild(row);
      stationLibraryListEl.appendChild(li);
    });

    restoreScrollPosition();
  } catch (e) {
    console.debug('renderStationLibrary failed', e);
  }
}

function openStationLibrary() {
  if (!stationLibraryEl) {
    openStationsOverlay();
    return;
  }
  renderStationLibrary();
  const mobileDrawer = window.matchMedia('(max-width: 1100px)').matches;
  if (mobileDrawer) {
    playerLayoutEl?.classList.remove('library-collapsed');
    stationLibraryEl.classList.add('open');
    document.body.classList.add('library-open');
  } else {
    playerLayoutEl?.classList.remove('library-collapsed');
  }
  setTimeout(() => {
    if (stationLibraryFiltersCollapsed) stationLibraryFiltersToggleBtn?.focus();
    else stationSearchInput?.focus();
  }, 30);
}

function closeStationLibrary() {
  const mobileDrawer = window.matchMedia('(max-width: 1100px)').matches;
  if (mobileDrawer) {
    stationLibraryEl?.classList.remove('open');
    document.body.classList.remove('library-open');
  } else {
    playerLayoutEl?.classList.add('library-collapsed');
  }
}

function toggleStationLibrary() {
  if (!stationLibraryEl) {
    openStationsOverlay();
    return;
  }

  const mobileDrawer = window.matchMedia('(max-width: 1100px)').matches;
  if (mobileDrawer) {
    if (stationLibraryEl.classList.contains('open')) closeStationLibrary();
    else openStationLibrary();
    return;
  }

  if (playerLayoutEl?.classList.contains('library-collapsed')) openStationLibrary();
  else closeStationLibrary();
}

function applyStationLibraryFiltersState() {
  if (!stationLibraryEl || !stationLibraryFiltersToggleBtn) return;

  stationLibraryEl.classList.toggle('filters-collapsed', stationLibraryFiltersCollapsed);
  stationLibraryFiltersToggleBtn.setAttribute('aria-expanded', stationLibraryFiltersCollapsed ? 'false' : 'true');

  const label = stationLibraryFiltersCollapsed
    ? 'Expand search and filters'
    : 'Collapse search and filters';

  stationLibraryFiltersToggleBtn.setAttribute('aria-label', label);
  stationLibraryFiltersToggleBtn.title = label;
}

function toggleStationLibraryFilters(forceCollapsed) {
  stationLibraryFiltersCollapsed = typeof forceCollapsed === 'boolean'
    ? forceCollapsed
    : !stationLibraryFiltersCollapsed;

  if (stationLibraryFiltersCollapsed) {
    closeStationCountryFilter();
    closeSortFilter();
    closeLanguageFilter();
    closeCodecFilter();
    closeBitrateFilter();
  }

  applyStationLibraryFiltersState();
}

async function moveQuickPick(delta) {
  const entries = getQuickPickEntries();
  if (!entries.length) return;
  const currentQuickIndex = entries.findIndex(({ index }) => index === currentIndex);
  let nextQuickIndex;
  if (currentQuickIndex >= 0) {
    nextQuickIndex = (currentQuickIndex + delta + entries.length) % entries.length;
  } else {
    nextQuickIndex = delta > 0 ? 0 : entries.length - 1;
  }
  await setStationByIndex(entries[nextQuickIndex].index);
}

async function activateStationByIndex(idx) {
  const stationChanged = idx !== currentIndex;
  await setStationByIndex(idx);
  if (!isPlaying || stationChanged) {
    await play();
  }
}

function buildMergedStations(raw, managedRaw) {
  const normalizedRaw = raw
    .filter((station) => isEnabledRadioCountryStation(station))
    .map((station) => normalizeStationRecord(station))
    .filter((station) => station.enabled !== false && station.url && station.url.length > 0);

  const normalizedManaged = managedRaw
    .map((station) => normalizeStationRecord(station))
    .filter((station) => station.enabled !== false && station.url && station.url.length > 0);

  const userNormalized = loadUserStations()
    .map((station) => normalizeStationRecord(station, true))
    .filter((station) => station.url && station.url.length > 0);

  const mergedStations = [];
  const seenStationIds = new Set();
  const seenStreamUrls = new Set();

  const pushStation = (station) => {
    if (!station?.id || !station?.url) return;
    if (seenStationIds.has(station.id) || seenStreamUrls.has(station.url)) return;
    seenStationIds.add(station.id);
    seenStreamUrls.add(station.url);
    mergedStations.push(station);
  };

  normalizedManaged.forEach(pushStation);
  normalizedRaw.forEach(pushStation);
  userNormalized.forEach(pushStation);

  return mergedStations;
}

function applyLoadedStations(mergedStations, { preferredStationId = null } = {}) {
  stations = mergedStations;
  updateManagedCatalogStatus(getLastManagedCatalogSource());
  stationCatalogState = stations.length > 0 ? 'ready' : 'empty';

  if (stations.length > 0) {
    const fallbackStationId = preferredStationId || getLastStationId();
    if (fallbackStationId) {
      const foundIndex = stations.findIndex((station) => station.id === fallbackStationId);
      currentIndex = foundIndex >= 0 ? foundIndex : 0;
    } else {
      currentIndex = 0;
    }

    loadStation(currentIndex);
    renderCoverflow();
    renderStationLibrary();
    startCurrentSongPollers();
  } else {
    renderCoverflow();
    renderStationLibrary();
  }
}

// ── Load stations ────────────────────────────────────────────────────────────

async function loadStations() {
  try {
    stationCatalogState = 'loading';
    stationCatalogError = '';
    resetStationLibraryPage();
    renderStationLibrary();
    stopCurrentSongPollers();
    const persistedRadioPromise = loadPersistedRadioStations().catch(() => null);
    const persistedManagedPromise = loadPersistedManagedStations().catch(() => null);
    const liveRadioPromise = loadRadioStations();
    const liveManagedPromise = loadManagedStations().catch(() => []);

    const [persistedRaw, persistedManaged] = await Promise.all([
      persistedRadioPromise,
      persistedManagedPromise,
    ]);

    const hasPersistedCatalog = (persistedRaw?.length ?? 0) > 0 || (persistedManaged?.length ?? 0) > 0;
    if (hasPersistedCatalog) {
      applyLoadedStations(buildMergedStations(persistedRaw ?? [], persistedManaged ?? []));
      console.debug('loadStations: hydrated from persisted station catalog');
    }

    try {
      const [raw, managedRaw] = await Promise.all([liveRadioPromise, liveManagedPromise]);
      const preferredStationId = hasPersistedCatalog ? stations[currentIndex]?.id ?? null : null;
      applyLoadedStations(buildMergedStations(raw, managedRaw), { preferredStationId });
      lastManagedCatalogLoadTime = Date.now();
      console.debug('loadStations: loaded', stations.length, 'stations');
      console.debug('managed catalog source:', getLastManagedCatalogSource());
    } catch (refreshError) {
      if (!hasPersistedCatalog) {
        throw refreshError;
      }

      console.warn('Failed to refresh station catalogs, continuing with persisted data.', refreshError);
      stationCatalogError = '';
      if (statusTextEl) {
        statusTextEl.textContent = 'Ready';
      }
    }
  } catch (e) {
    console.error('Failed to load stations', e);
    stations = [];
    updateManagedCatalogStatus('unknown');
    stationCatalogState = 'error';
    stationCatalogError = e?.message || 'Error loading the local station catalog.';
    renderCoverflow();
    renderStationLibrary();
    if (statusTextEl) statusTextEl.textContent = 'Unable to load stations';
  }
}

// ── Coverflow ────────────────────────────────────────────────────────────────

let coverflowPointerId = null;
let coverflowStartX = 0;
let coverflowLastX = 0;
let coverflowAccum = 0;
let coverflowMoved = false;
let coverflowWheelLock = false;

function renderCoverflow() {
  try {
    if (!coverflowStageEl) return;
    coverflowStageEl.innerHTML = '';

    const quickPickEntries = getQuickPickEntries();
    quickPickEntries.forEach(({ station: s, index: idx }) => {
      const item = document.createElement('div');
      item.className = 'coverflow-item';
      item.dataset.idx = String(idx);
      item.setAttribute('role', 'button');
      item.setAttribute('tabindex', '0');
      const fallbackLabel = (s?.name ?? '?').trim();
      item.title = fallbackLabel;
      item.setAttribute('aria-label', `Select ${fallbackLabel}`);

    const rawLogoUrl = getStationLogoUrl(s);
      if (rawLogoUrl) {
        const img = document.createElement('img');
        img.alt = `${s.name} logo`;
        setImgWithFallback(img, [toHttpsIfHttp(rawLogoUrl), rawLogoUrl], () => {
          item.innerHTML = '';
          item.classList.add('fallback');
          item.textContent = fallbackLabel;
        });
        item.appendChild(img);
      } else {
        item.classList.add('fallback');
        item.textContent = fallbackLabel;
      }

      item.addEventListener('click', async (ev) => {
        ev.preventDefault();
        ev.stopPropagation();
        if (coverflowMoved) return;
        const idxClicked = Number(item.dataset.idx);
        if (idxClicked !== currentIndex) await activateStationByIndex(idxClicked);
        else openStationLibrary();
      });
      item.addEventListener('keydown', async (ev) => {
        if (ev.key !== 'Enter' && ev.key !== ' ') return;
        ev.preventDefault();
        const idxSelected = Number(item.dataset.idx);
        if (idxSelected !== currentIndex) await activateStationByIndex(idxSelected);
        else openStationLibrary();
      });
      item.addEventListener('dblclick', () => {
        if (Number(item.dataset.idx) === currentIndex) openStationLibrary();
      });

      coverflowStageEl.appendChild(item);
    });

    wireCoverflowInteractions();
    updateCoverflowTransforms();
  } catch (e) {
    console.debug('renderCoverflow failed', e);
  }
}

function wireCoverflowInteractions() {
  try {
    const host = document.getElementById('artwork-coverflow');
    if (!host) return;

    if (coverflowPrevBtn) {
      coverflowPrevBtn.onpointerdown = (ev) => ev.stopPropagation();
      coverflowPrevBtn.onclick = (ev) => {
        ev.stopPropagation(); ev.preventDefault();
        moveQuickPick(-1);
      };
    }
    if (coverflowNextBtn) {
      coverflowNextBtn.onpointerdown = (ev) => ev.stopPropagation();
      coverflowNextBtn.onclick = (ev) => {
        ev.stopPropagation(); ev.preventDefault();
        moveQuickPick(1);
      };
    }

    host.onpointerdown = (ev) => {
      if (getQuickPickEntries().length <= 1) return;
      if (ev.target?.closest?.('.coverflow-arrow')) return;
      coverflowPointerId = ev.pointerId;
      coverflowStartX = ev.clientX;
      coverflowLastX = ev.clientX;
      coverflowAccum = 0;
      coverflowMoved = false;
      try { host.setPointerCapture(ev.pointerId); } catch (e) {}
    };
    host.onpointermove = (ev) => {
      if (coverflowPointerId === null || ev.pointerId !== coverflowPointerId) return;
      const dx = ev.clientX - coverflowLastX;
      coverflowLastX = ev.clientX;
      if (Math.abs(ev.clientX - coverflowStartX) > 6) coverflowMoved = true;
      coverflowAccum += dx;
      const threshold = 36;
      if (coverflowAccum >= threshold) {
        coverflowAccum = 0;
        moveQuickPick(-1);
      } else if (coverflowAccum <= -threshold) {
        coverflowAccum = 0;
        moveQuickPick(1);
      }
    };
    host.onpointerup = (ev) => {
      if (coverflowPointerId === null || ev.pointerId !== coverflowPointerId) return;
      coverflowPointerId = null;
      setTimeout(() => { coverflowMoved = false; }, 0);
      try { host.releasePointerCapture(ev.pointerId); } catch (e) {}
    };
    host.onpointercancel = () => { coverflowPointerId = null; coverflowMoved = false; };

    host.onwheel = (ev) => {
      if (getQuickPickEntries().length <= 1 || coverflowWheelLock) return;
      const delta = Math.abs(ev.deltaX) > Math.abs(ev.deltaY) ? ev.deltaX : ev.deltaY;
      if (Math.abs(delta) < 6) return;
      ev.preventDefault();
      coverflowWheelLock = true;
      if (delta > 0) moveQuickPick(1);
      else moveQuickPick(-1);
      setTimeout(() => { coverflowWheelLock = false; }, 160);
    };
  } catch (e) {
    console.debug('wireCoverflowInteractions failed', e);
  }
}

function updateCoverflowTransforms() {
  try {
    if (!coverflowStageEl) return;
    const items = coverflowStageEl.querySelectorAll('.coverflow-item');
    const n = items.length;
    if (n <= 0) return;
    let selectedRailIndex = Array.from(items).findIndex((el) => Number(el.dataset.idx) === currentIndex);
    if (selectedRailIndex < 0) selectedRailIndex = 0;

    const stageWidth = coverflowStageEl.clientWidth || 320;
    const isMobile = window.matchMedia('(max-width: 760px)').matches;
    const isNarrow = window.matchMedia('(max-width: 380px)').matches;
    const spacing = isMobile ? Math.min(78, Math.max(62, stageWidth / 3.25)) : Math.min(116, Math.max(94, stageWidth / 3.1));
    const depth = isMobile ? 26 : 36;
    const rotation = isMobile ? 0 : 8;
    const scaleStep = isMobile ? 0.08 : 0.1;
  const maxVisible = isMobile ? 1 : isNarrow ? 1 : Math.max(1, Math.min(4, Math.floor((stageWidth - 120) / (spacing * 0.95))));

    items.forEach((el, railIndex) => {
      const idx = Number(el.dataset.idx);
      let offset = railIndex - selectedRailIndex;
      const half = Math.floor(n / 2);
      if (n > 2 && offset > half) offset -= n;
      if (n > 2 && offset < -half) offset += n;

      el.dataset.offset = String(offset);
      el.setAttribute('aria-current', idx === currentIndex ? 'true' : 'false');

      if (Math.abs(offset) > maxVisible) {
        el.classList.toggle('selected', idx === currentIndex);
        el.style.opacity = '0';
        el.style.pointerEvents = 'none';
        el.style.transform = `translate(-50%, -50%) translateX(${Math.sign(offset || 1) * (stageWidth / 2)}px) scale(0.72)`;
        return;
      }

      const abs = Math.abs(offset);
      const dir = offset === 0 ? 0 : (offset > 0 ? 1 : -1);
      const isAdjacent = abs === 1;
      el.style.opacity = String(1 - abs * 0.2);
      el.style.zIndex = String(100 - abs);
      el.style.pointerEvents = 'auto';
      el.style.transform = `translate(-50%, -50%) translateX(${dir * abs * spacing}px) translateZ(${-abs * depth}px) rotateY(${dir * -rotation * abs}deg) scale(${isAdjacent ? 0.88 : 1 - abs * scaleStep})`;
      if (idx === currentIndex) el.classList.add('selected');
      else el.classList.remove('selected');
    });
  } catch (e) {
    console.debug('updateCoverflowTransforms failed', e);
  }
}

async function setStationByIndex(idx) {
  if (idx < 0 || idx >= stations.length) return;
  const wasPlaying = isPlaying;
  if (wasPlaying) await stop();
  playbackError = '';
  currentIndex = idx;
  saveLastStationId(stations[currentIndex].id);
  incrementStationUsage(stations[currentIndex]);
  recordRecentStationPlay(stations[currentIndex]);
  loadStation(currentIndex);
  renderCoverflow();
  renderStationLibrary({ preserveScroll: true });
  updateCoverflowTransforms();
  if (wasPlaying) await play();
}

// ── Current Song Polling ─────────────────────────────────────────────────────

const currentSongPollers = new Map();

function stopCurrentSongPollers() {
  for (const entry of currentSongPollers.values()) {
    try { if (entry?.intervalId) clearInterval(entry.intervalId); } catch (e) {}
    try { if (entry?.timeoutId) clearTimeout(entry.timeoutId); } catch (e) {}
  }
  currentSongPollers.clear();
}

function startCurrentSongPollers() {
  stopCurrentSongPollers();
  const s = stations[currentIndex];
  if (!s) return;

  const url = getMetadataFetchUrl(getStationMetadataUrl(s));
  if (!url || typeof url !== 'string' || url.length === 0) return;

  const doFetch = () => {
    fetchAndStoreCurrentSong(s, currentIndex, url);
  };

  doFetch();
  const iid = setInterval(doFetch, 10000);
  currentSongPollers.set(s.id || currentIndex, { intervalId: iid, timeoutId: null });
}

async function fetchAndStoreCurrentSong(station, idx, url) {
  try {
    let rawBody = null;
    try {
      const resp = await fetch(url, { cache: 'no-store' });
      rawBody = await resp.text();
    } catch (e) {
      // CORS or network error — silently skip; now-playing simply won't display.
      return;
    }

    if (!rawBody) return;

    let data = null;
    try {
      const first = JSON.parse(rawBody);
      data = typeof first === 'string' ? JSON.parse(first) : first;
    } catch (e) {
      return;
    }

    let now = null;
    if (data) {
      if (data.currentSong?.artist || data.currentSong?.title) {
        now = { artist: data.currentSong.artist || '', title: data.currentSong.title || '' };
      } else if (Array.isArray(data.lastSongs) && data.lastSongs.length > 0) {
        const first = data.lastSongs[0];
        if (first?.artist || first?.title) now = { artist: first.artist || '', title: first.title || '' };
      } else if (data.artist || data.title) {
        now = { artist: data.artist || '', title: data.title || '' };
      }
    }

    if (now) {
      const previousArtist = String(station.currentSongInfo?.artist || '').trim().toLowerCase();
      const previousTitle = String(station.currentSongInfo?.title || '').trim().toLowerCase();
      station.currentSongInfo = now;
      if (previousArtist !== String(now.artist || '').trim().toLowerCase() || previousTitle !== String(now.title || '').trim().toLowerCase()) {
        recordSongHistory(station, now);
      }
      if (idx === currentIndex) updateNowPlayingUI();

      // If provider gives timing info, schedule a single-shot refresh at song end.
      try {
        const key = station.id || idx;
        const providerCS = data?.currentSong ?? null;
        const startStr = providerCS?.playTimeStartSec ?? providerCS?.playTimeStart ?? null;
        const lengthStr = providerCS?.playTimeLengthSec ?? providerCS?.playTimeLength ?? null;
        if (startStr && lengthStr) {
          const nowDate = new Date();
          const parts = startStr.split(':').map(Number);
          if (parts.length >= 2) {
            const startDate = new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate(), parts[0], parts[1], parts[2] || 0, 0);
            const deltaStart = startDate.getTime() - nowDate.getTime();
            if (deltaStart > 12 * 3600000) startDate.setDate(startDate.getDate() - 1);
            if (deltaStart < -12 * 3600000) startDate.setDate(startDate.getDate() + 1);

            const lenParts = lengthStr.split(':').map(Number);
            let lenSec = 0;
            if (lenParts.length === 3) lenSec = lenParts[0] * 3600 + lenParts[1] * 60 + lenParts[2];
            else if (lenParts.length === 2) lenSec = lenParts[0] * 60 + lenParts[1];
            else lenSec = Number(lenParts[0]) || 0;

            const msUntilEnd = startDate.getTime() + lenSec * 1000 - nowDate.getTime();
            if (msUntilEnd > 1000) {
              const entry = currentSongPollers.get(key);
              if (entry?.intervalId) try { clearInterval(entry.intervalId); } catch (e) {}
              if (entry?.timeoutId) try { clearTimeout(entry.timeoutId); } catch (e) {}

              const timeoutId = setTimeout(async () => {
                try { await fetchAndStoreCurrentSong(station, idx, url); } catch (e) { /* ignore */ }
                finally { if (currentIndex === idx) startCurrentSongPollers(); }
              }, msUntilEnd + 250);

              currentSongPollers.set(key, { intervalId: null, timeoutId });
            }
          }
        }
      } catch (e) {
        console.debug('Failed scheduling next-song fetch', e);
      }
    }
  } catch (e) {
    console.debug('currentSong fetch failed for', url, e.message || e);
  }
}

function formatRelativeTimer(deadline) {
  if (!deadline) return 'Off';
  const diffMs = deadline - Date.now();
  if (diffMs <= 0) return 'Now';
  const totalSeconds = Math.ceil(diffMs / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return seconds > 0
      ? `In ${hours}h ${minutes}m ${seconds}s`
      : `In ${hours}h ${minutes}m`;
  }

  if (minutes > 0) {
    return `In ${minutes}m ${seconds}s`;
  }

  return `In ${seconds}s`;
}

function stopSessionCountdownTicker() {
  if (!sessionCountdownIntervalId) return;
  clearInterval(sessionCountdownIntervalId);
  sessionCountdownIntervalId = null;
}

function refreshSessionCountdownUI() {
  updateSleepTimerUI();
  updateWakeAlarmUI();
}

function ensureSessionCountdownTicker() {
  const hasActiveSession = Boolean(sleepTimerDeadline || wakeAlarmDeadline);
  if (!hasActiveSession) {
    stopSessionCountdownTicker();
    return;
  }

  if (sessionCountdownIntervalId) return;

  sessionCountdownIntervalId = window.setInterval(() => {
    refreshSessionCountdownUI();
    if (!sleepTimerDeadline && !wakeAlarmDeadline) {
      stopSessionCountdownTicker();
    }
  }, 1000);
}

function buildSessionSummaryText() {
  const sleepText = sleepTimerDeadline ? `Sleep ${formatRelativeTimer(sleepTimerDeadline).toLowerCase()}` : 'Sleep off';
  const wakeText = wakeAlarmDeadline ? `Wake ${formatRelativeTimer(wakeAlarmDeadline).toLowerCase()}` : 'Wake off';
  return `${sleepText} · ${wakeText}`;
}

function updateSleepTimerUI() {
  if (sleepTimerStatusEl) {
    sleepTimerStatusEl.textContent = sleepTimerDeadline ? `Playback stops ${formatRelativeTimer(sleepTimerDeadline).toLowerCase()}.` : 'No sleep timer set.';
  }
  sleepTimerButtons?.forEach((button) => {
    const minutes = Number(button.getAttribute('data-sleep-minutes'));
    button.classList.toggle('active', minutes > 0 && minutes === sleepTimerSelectionMinutes);
  });
  const hasActiveSession = Boolean(sleepTimerDeadline || wakeAlarmDeadline);
  sessionSchedulerBtn?.classList.toggle('session-active', hasActiveSession);
  sessionSummaryBtn?.setAttribute('aria-expanded', sessionSchedulerOverlay && !sessionSchedulerOverlay.classList.contains('hidden') ? 'true' : 'false');
  if (sessionSchedulerBadge) {
    sessionSchedulerBadge.classList.toggle('hidden', !hasActiveSession);
  }
  if (sessionSummaryText) {
    sessionSummaryText.textContent = buildSessionSummaryText();
  }
}

function updateWakeAlarmUI() {
  if (wakeAlarmStatusEl) {
    wakeAlarmStatusEl.textContent = wakeAlarmDeadline ? `Playback resumes ${formatRelativeTimer(wakeAlarmDeadline).toLowerCase()}.` : 'No wake alarm set.';
  }
  wakeAlarmButtons?.forEach((button) => {
    const minutes = Number(button.getAttribute('data-wake-minutes'));
    button.classList.toggle('active', minutes > 0 && minutes === wakeAlarmSelectionMinutes);
  });
  const hasActiveSession = Boolean(sleepTimerDeadline || wakeAlarmDeadline);
  sessionSchedulerBtn?.classList.toggle('session-active', hasActiveSession);
  if (sessionSchedulerBadge) {
    sessionSchedulerBadge.classList.toggle('hidden', !hasActiveSession);
  }
  if (sessionSummaryText) {
    sessionSummaryText.textContent = buildSessionSummaryText();
  }
  sessionSummaryBtn?.setAttribute('aria-expanded', sessionSchedulerOverlay && !sessionSchedulerOverlay.classList.contains('hidden') ? 'true' : 'false');
}

function clearSleepTimer() {
  if (sleepTimerTimeoutId) clearTimeout(sleepTimerTimeoutId);
  sleepTimerTimeoutId = null;
  sleepTimerDeadline = null;
  sleepTimerSelectionMinutes = 0;
  updateSleepTimerUI();
  ensureSessionCountdownTicker();
}

function clearWakeAlarm() {
  if (wakeAlarmTimeoutId) clearTimeout(wakeAlarmTimeoutId);
  wakeAlarmTimeoutId = null;
  wakeAlarmDeadline = null;
  wakeAlarmSelectionMinutes = 0;
  updateWakeAlarmUI();
  ensureSessionCountdownTicker();
}

function scheduleSleepTimer(minutes) {
  clearSleepTimer();
  if (!Number.isFinite(minutes) || minutes <= 0) return;
  sleepTimerSelectionMinutes = minutes;
  sleepTimerDeadline = Date.now() + minutes * 60000;
  sleepTimerTimeoutId = window.setTimeout(async () => {
    clearSleepTimer();
    await stop();
    if (statusTextEl) statusTextEl.textContent = 'Sleep timer finished';
  }, minutes * 60000);
  updateSleepTimerUI();
  ensureSessionCountdownTicker();
}

function scheduleWakeAlarm(minutes) {
  clearWakeAlarm();
  if (!Number.isFinite(minutes) || minutes <= 0) return;
  wakeAlarmSelectionMinutes = minutes;
  wakeAlarmDeadline = Date.now() + minutes * 60000;
  wakeAlarmTimeoutId = window.setTimeout(async () => {
    clearWakeAlarm();
    try {
      await play();
      if (statusTextEl) statusTextEl.textContent = 'Wake alarm started playback';
    } catch (error) {
      if (statusTextEl) statusTextEl.textContent = 'Wake alarm is ready';
    }
  }, minutes * 60000);
  updateWakeAlarmUI();
  ensureSessionCountdownTicker();
}

function openSessionSchedulerOverlay() {
  if (!sessionSchedulerOverlay) return;
  sessionSchedulerOverlay.classList.remove('hidden');
  sessionSchedulerOverlay.setAttribute('aria-hidden', 'false');
  sessionSummaryBtn?.setAttribute('aria-expanded', 'true');
}

function closeSessionSchedulerOverlay() {
  if (!sessionSchedulerOverlay) return;
  sessionSchedulerOverlay.classList.add('hidden');
  sessionSchedulerOverlay.setAttribute('aria-hidden', 'true');
  sessionSummaryBtn?.setAttribute('aria-expanded', 'false');
}

function renderSongHistory() {
  if (!songHistoryPanelEl || !songHistoryListEl) return;

  const station = stations[currentIndex];
  const historyMap = loadStationSongHistory();
  const entries = station?.id ? (historyMap[station.id] || []) : [];

  songHistoryListEl.innerHTML = '';
  songHistoryPanelEl.classList.toggle('hidden', entries.length === 0);
  if (!entries.length) return;

  entries.forEach((entry) => {
    const item = document.createElement('li');
    item.className = 'song-history-item';

    const title = document.createElement('div');
    title.className = 'song-history-track';
    title.textContent = entry.title;

    const meta = document.createElement('div');
    meta.className = 'song-history-meta';
    meta.textContent = `${entry.artist} · ${new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(new Date(entry.seenAt))}`;

    item.append(title, meta);
    songHistoryListEl.appendChild(item);
  });
}

function updateNowPlayingUI() {
  const station = stations[currentIndex];
  if (!station) return;
  if (nowPlayingEl && nowArtistEl && nowTitleEl) {
    if (station.currentSongInfo?.artist && station.currentSongInfo?.title) {
      nowArtistEl.textContent = station.currentSongInfo.artist;
      nowTitleEl.textContent = station.currentSongInfo.title;
      nowPlayingEl.classList.remove('hidden');
    } else {
      nowArtistEl.textContent = '';
      nowTitleEl.textContent = '';
      nowPlayingEl.classList.add('hidden');
    }
  }
  if (stationSubtitleEl) stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
  renderSongHistory();
}

// ── Load station UI ───────────────────────────────────────────────────────────

function loadStation(index) {
  if (index < 0 || index >= stations.length) return;
  const station = stations[index];

  applyStationTheme(station);

  updateStationTitleLayout(station.name);
  if (stationSubtitleEl) stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
  updateActiveStationHealthSummary(station);
  if (nowPlayingEl) nowPlayingEl.classList.add('hidden');
  if (nowArtistEl) nowArtistEl.textContent = '';
  if (nowTitleEl) nowTitleEl.textContent = '';
  renderSongHistory();

  try {
    if (logoTextEl && station.name) {
      logoTextEl.textContent = String(station.name).trim();
      logoTextEl.classList.add('logo-name');
    }

    if (logoImgEl) {
      logoImgEl.classList.add('hidden');
      if (logoTextEl) logoTextEl.classList.remove('hidden');

      setImgWithFallback(logoImgEl, getStationLogoCandidates(station), () => {
        logoImgEl.classList.add('hidden');
        if (logoTextEl) logoTextEl.classList.remove('hidden');
      });

      logoImgEl.onload = () => {
        logoImgEl.classList.remove('hidden');
        if (logoTextEl) logoTextEl.classList.add('hidden');
      };
    }
  } catch (e) { /* non-fatal */ }

  try { updateCoverflowTransforms(); } catch (e) {}
  try { startCurrentSongPollers(); } catch (e) {}
  window.setTimeout(triggerSparkleOnSelected, 120);
}

function setupServiceWorkerCatalogRefreshListener() {
  if (!('serviceWorker' in navigator)) {
    return;
  }

  navigator.serviceWorker.addEventListener('message', (event) => {
    const messageType = event.data?.type;
    if (
      messageType !== SERVICE_WORKER_STATION_CATALOG_UPDATED_MESSAGE
      && messageType !== SERVICE_WORKER_STATION_CATALOG_UPDATE_FAILED_MESSAGE
      && messageType !== SERVICE_WORKER_MANAGED_CATALOG_UPDATED_MESSAGE
      && messageType !== SERVICE_WORKER_MANAGED_CATALOG_UPDATE_FAILED_MESSAGE
    ) {
      return;
    }

    if (messageType === SERVICE_WORKER_STATION_CATALOG_UPDATE_FAILED_MESSAGE) {
      showCatalogSyncStatus(
        'Background station sync failed',
        event.data?.failedAt ?? null,
        'warning',
        event.data?.error ?? '',
      );
      return;
    }

    if (messageType === SERVICE_WORKER_MANAGED_CATALOG_UPDATE_FAILED_MESSAGE) {
      showCatalogSyncStatus(
        'Managed catalog refresh failed',
        event.data?.failedAt ?? null,
        'warning',
        event.data?.error ?? '',
      );
      return;
    }

    if (stationCatalogState === 'loading') {
      return;
    }

    void loadStations().then(() => {
      if (messageType === SERVICE_WORKER_MANAGED_CATALOG_UPDATED_MESSAGE) {
        persistCatalogSyncHistory('managed', new Date().toISOString());
        showCatalogSyncStatus('Managed catalog refreshed in background');
        return;
      }

      persistCatalogSyncHistory('stations', event.data?.meta?.syncedAt ?? null);
      showCatalogSyncStatus('Stations updated in background', event.data?.meta?.syncedAt ?? null);
    }).catch(() => {
      // Keep current UI if the refresh fails.
    });
  });
}

// ── Google Cast initialisation ────────────────────────────────────────────────

// Called by the Cast SDK once it has loaded (window.__onGCastApiAvailable callback).
function initCast() {
  if (castInitialized) {
    updateCastButtonUI();
    return;
  }

  try {
    castInitialized = true;
    castContext = cast.framework.CastContext.getInstance();
    castContext.setOptions({
      receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
      autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
    });

    const remotePlayer = new cast.framework.RemotePlayer();
    castPlayerController = new cast.framework.RemotePlayerController(remotePlayer);

    // Sync volume slider with Cast session volume
    castPlayerController.addEventListener(
      cast.framework.RemotePlayerEventType.VOLUME_LEVEL_CHANGED,
      () => {
        if (castMode !== 'cast') return;
        const vol = Math.round(remotePlayer.volumeLevel * 100);
        if (volumeSlider) volumeSlider.value = String(vol);
        if (volumeValue) volumeValue.textContent = `${vol}%`;
        currentVolume = remotePlayer.volumeLevel;
      }
    );

    // Track Cast session state changes
    castContext.addEventListener(
      cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
      (ev) => {
        const SS = cast.framework.SessionState;
        if (ev.sessionState === SS.SESSION_STARTED || ev.sessionState === SS.SESSION_RESUMED) {
          castMode = 'cast';
          updateEngineBadge();
          updateCastButtonUI();
          updateCastOutputToggleUI();
          if (isPlaying) {
            // Hand off to Cast; local audio continues only in castBothMode
            if (!castBothMode) {
              audio.pause();
              audio.src = '';
            }
            castPlayCurrent();
          }
        } else if (ev.sessionState === SS.SESSION_ENDED || ev.sessionState === SS.SESSION_START_FAILED) {
          castMode = 'local';
          updateEngineBadge();
          updateCastButtonUI();
          updateCastOutputToggleUI();
          updateUI();
        }
      }
    );

    console.log('Cast SDK initialised');
    updateCastButtonUI();
  } catch (e) {
    castInitialized = false;
    console.warn('Cast init failed:', e);
    updateCastButtonUI();
  }
}

// The Cast SDK calls this global when it is ready.
window['__onGCastApiAvailable'] = (isAvailable) => {
  if (isAvailable) initCast();
  else updateCastButtonUI();
};

if (window.cast?.framework && window.chrome?.cast) {
  initCast();
}

initAirPlay();

function updateCastButtonUI() {
  if (!castBtn) return;
  const sdkReady = !!castContext;
  const airPlayReady = airPlayAvailable || supportsAirPlay();
  const outputReady = sdkReady || airPlayReady;
  const remoteActive = isRemoteOutputActive();
  const isAirPlayActive = castMode === 'airplay';
  castBtn.classList.toggle('cast-ready', outputReady);
  castBtn.classList.toggle('cast-active', remoteActive);
  castBtn.classList.toggle('cast-airplay-active', isAirPlayActive);
  castBtn.setAttribute('aria-pressed', remoteActive ? 'true' : 'false');
  const label = castMode === 'cast'
    ? 'Casting'
    : castMode === 'airplay'
      ? 'AirPlay'
      : airPlayReady && !sdkReady
        ? 'AirPlay'
        : sdkReady && !airPlayReady
          ? 'Cast'
          : 'Output';
  const title = sdkReady
    ? (airPlayReady ? 'Cast or AirPlay to device' : 'Cast to device')
    : (airPlayReady ? 'AirPlay to device' : 'Cast and AirPlay are not available');

  castBtn.title = remoteActive ? `${label} active` : title;
  castBtn.setAttribute('aria-label', remoteActive ? `${label} active` : title);
  castBtnCastIcon?.classList.toggle('hidden', isAirPlayActive);
  castBtnAirPlayIcon?.classList.toggle('hidden', !isAirPlayActive);
}

async function requestCastSession() {
  if (!castContext && supportsAirPlay()) {
    try {
      audio.webkitShowPlaybackTargetPicker();
      if (statusTextEl) statusTextEl.textContent = 'Choose an AirPlay device';
    } catch (e) {
      console.debug('AirPlay picker request failed:', e);
      if (statusTextEl) statusTextEl.textContent = 'AirPlay not available';
    } finally {
      updateCastButtonUI();
    }
    return;
  }

  if (!castContext) {
    if (statusTextEl) statusTextEl.textContent = 'Cast and AirPlay are not available';
    updateCastButtonUI();
    return;
  }

  try {
    await castContext.requestSession();
  } catch (e) {
    console.debug('Cast session request failed:', e);
  } finally {
    updateCastButtonUI();
  }
}

async function castPlayCurrent() {
  try {
    const session = castContext?.getCurrentSession();
    if (!session) return;

    const station = stations[currentIndex];
    if (!station) return;

    const streamUrl = toHttpsIfHttp(station.url) || station.url;
    const contentType = streamUrl.includes('.ogg') ? 'audio/ogg' : 'audio/mpeg';

    const mediaInfo = new chrome.cast.media.MediaInfo(streamUrl, contentType);
    mediaInfo.streamType = chrome.cast.media.StreamType.LIVE;
    const meta = new chrome.cast.media.MusicTrackMediaMetadata();
    meta.title = station.name;
    meta.artist = station.currentSongInfo?.artist ?? '';
    meta.songName = station.currentSongInfo?.title ?? '';
    if (station.logo) meta.images = [new chrome.cast.Image(toHttpsIfHttp(station.logo) || station.logo)];
    mediaInfo.metadata = meta;

    const request = new chrome.cast.media.LoadRequest(mediaInfo);
    await session.loadMedia(request);

    isPlaying = true;
    if (statusTextEl) statusTextEl.textContent = 'Casting...';
    if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--success)';
    if (stationSubtitleEl) {
      const detailSuffix = getStationDetails(station);
      stationSubtitleEl.textContent = detailSuffix
        ? `Casting to ${session.getCastDevice().friendlyName} • ${detailSuffix}`
        : `Casting to ${session.getCastDevice().friendlyName}`;
    }
    updateUI();
  } catch (e) {
    console.error('Cast play failed:', e);
    if (statusTextEl) statusTextEl.textContent = 'Cast error — playing locally';
    castMode = 'local';
    updateEngineBadge();
    playLocal();
  }
}

function updateEngineBadge() {
  if (!engineBadgeEl) return;
  if (castMode === 'cast') {
    engineBadgeEl.className = 'engine-badge engine-cast';
    engineBadgeEl.title = 'Google Cast playback';
    engineBadgeEl.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 16.1A5 5 0 0 1 5.9 20"/><path d="M2 12.05A9 9 0 0 1 9.95 20"/><path d="M2 8V6a14 14 0 0 1 14 14h-2"/></svg><span>CAST</span>`;
  } else if (castMode === 'airplay') {
    engineBadgeEl.className = 'engine-badge engine-airplay';
    engineBadgeEl.title = 'AirPlay playback';
    engineBadgeEl.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 17h14"/><path d="M7 7h10a2 2 0 0 1 2 2v6"/><path d="M5 15V9a2 2 0 0 1 2-2"/><path d="m12 20 3-3h-6l3 3Z"/></svg><span>AIRPLAY</span>`;
  } else {
    engineBadgeEl.className = 'engine-badge engine-html';
    engineBadgeEl.title = 'HTML5 Audio playback';
    engineBadgeEl.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 15V9"/><path d="M8 19V5"/><path d="M12 16V8"/><path d="M16 18V6"/><path d="M20 15V9"/></svg><span>HTML5</span>`;
  }
}

// ── Playback ──────────────────────────────────────────────────────────────────

async function playLocal() {
  const station = stations[currentIndex];
  if (!station) return;

  playbackStartupAttemptId += 1;
  const attemptId = playbackStartupAttemptId;
  playbackError = '';
  if (statusTextEl) statusTextEl.textContent = 'Buffering...';
  if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--text-muted)';

  const streamUrl = toHttpsIfHttp(station.url) || station.url;
  beginLocalPlaybackAttempt(station, attemptId, null);

  audio.src = streamUrl;
  audio.volume = isMuted ? 0 : currentVolume;
  audio.load();
  armPlaybackStartupTimeout(station, attemptId);

  try {
    await audio.play();
    if (attemptId !== playbackStartupAttemptId) return;
    isPlaying = true;
    updateUI();
  } catch (e) {
    if (attemptId !== playbackStartupAttemptId) return;
    clearPlaybackStartupTimeout();
    completeLocalPlaybackAttemptAsFailure(station, 'Unable to start playback', {});
    console.warn('audio.play() failed:', e.message);
    playbackError = e?.name === 'NotAllowedError'
      ? 'Playback was blocked until you interact with the page.'
      : getPlaybackFailureMessage(station, 'Unable to start playback');
    isPlaying = false;
    updateUI();
  }
}

async function play() {
  const station = stations[currentIndex];
  if (!station) return;
  triggerSparkleOnSelected();

  if (castMode === 'cast') {
    // Only kill local audio if NOT in both-mode
    if (!castBothMode) {
      audio.pause();
      audio.src = '';
    }
    await castPlayCurrent();
    // In both-mode also start local playback
    if (castBothMode) {
      playLocal(); // intentionally not awaited — fire-and-forget alongside cast
    }
  } else {
    await playLocal();
  }
}

async function stop() {
  playbackStartupAttemptId += 1;
  clearPlaybackStartupTimeout();
  activeLocalPlaybackAttempt = null;
  playbackError = '';
  if (castMode === 'cast') {
    try {
      const session = castContext?.getCurrentSession();
      const media = session?.getMediaSession();
      if (media) {
        media.stop(new chrome.cast.media.StopRequest(), () => {}, () => {});
      }
    } catch (e) { console.warn('Cast stop error:', e); }
  }
  // Always stop local audio
  audio.pause();
  audio.src = '';
  isPlaying = false;
  updateUI();
}

async function togglePlay() {
  if (isPlaying) await stop();
  else await play();
}

async function playNext() {
  if (stations.length === 0) return;
  await setStationByIndex((currentIndex + 1) % stations.length);
}

async function playPrev() {
  if (stations.length === 0) return;
  await setStationByIndex((currentIndex - 1 + stations.length) % stations.length);
}

function updateUI() {
  const station = stations[currentIndex];
  if (isPlaying) {
    if (iconPlay) iconPlay.classList.add('hidden');
    if (iconStop) iconStop.classList.remove('hidden');
    if (playBtn) playBtn.classList.add('playing');
    if (statusTextEl && statusTextEl.textContent === 'Ready') statusTextEl.textContent = 'Playing';
    if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--success)';
    if (stationSubtitleEl && castMode === 'local' && station) stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
  } else {
    if (iconPlay) iconPlay.classList.remove('hidden');
    if (iconStop) iconStop.classList.add('hidden');
    if (playBtn) playBtn.classList.remove('playing');
    if (playbackError) {
      if (statusTextEl) statusTextEl.textContent = '';
      if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--danger)';
    } else {
      if (statusTextEl) statusTextEl.textContent = stationCatalogState === 'loading' ? 'Loading stations...' : 'Ready';
      if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--text-muted)';
    }
    if (stationSubtitleEl && station && castMode === 'local') stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
  }
  if (station) updateActiveStationHealthSummary(station);
  updateEngineBadge();
  updateCastOutputToggleUI();
  updateCastButtonUI();
}

// ── Volume / Mute ─────────────────────────────────────────────────────────────

function handleVolumeInput() {
  const val = Number(volumeSlider.value);
  if (volumeValue) volumeValue.textContent = `${val}%`;
  currentVolume = val / 100;
  if (!isMuted) audio.volume = currentVolume;
  // Sync volume to active Cast session
  if (castMode === 'cast') {
    try {
      const session = castContext?.getCurrentSession();
      if (session) session.setVolume(currentVolume, () => {}, () => {});
    } catch (e) { /* ignore */ }
  }
  saveVolumeToStorage(val);
}

function toggleMute() {
  isMuted = !isMuted;
  audio.volume = isMuted ? 0 : currentVolume;
  if (iconVolume) iconVolume.classList.toggle('hidden', isMuted);
  if (iconMuted) iconMuted.classList.toggle('hidden', !isMuted);
}

function openShortcutHelpOverlay() {
  if (!shortcutHelpOverlay) return;
  shortcutHelpOverlay.classList.remove('hidden');
  shortcutHelpOverlay.setAttribute('aria-hidden', 'false');
}

function closeShortcutHelpOverlay() {
  if (!shortcutHelpOverlay) return;
  shortcutHelpOverlay.classList.add('hidden');
  shortcutHelpOverlay.setAttribute('aria-hidden', 'true');
}

// ── Stations overlay ──────────────────────────────────────────────────────────

async function openStationsOverlay() {
  if (!castOverlay || !deviceListEl) return;
  castOverlay.classList.remove('hidden');
  castOverlay.setAttribute('aria-hidden', 'false');
  deviceListEl.classList.add('stations-grid');
  deviceListEl.innerHTML = '';

  const titleEl = document.getElementById('deviceTitle');
  if (titleEl) titleEl.textContent = 'Stations';

  if (!stations || stations.length === 0) {
    deviceListEl.classList.remove('stations-grid');
    if (stationCatalogState === 'loading') {
      deviceListEl.innerHTML = '<li class="device"><div class="device-main">Loading stations</div><div class="device-sub">Reading the local radio catalog</div></li>';
    } else if (stationCatalogState === 'error') {
      deviceListEl.innerHTML = `<li class="device"><div class="device-main">Stations unavailable</div><div class="device-sub">${escapeHtml(stationCatalogError || 'Unable to load local radio data.')}</div></li>`;
    } else {
      deviceListEl.innerHTML = '<li class="device"><div class="device-main">No stations found</div><div class="device-sub">The local radio catalog is empty</div></li>';
    }
    return;
  }

  const stationHealth = loadStationHealth();

  for (let idx = 0; idx < stations.length; idx++) {
    const s = stations[idx];
    const li = document.createElement('li');

    const logoUrl = getStationLogoUrl(s);
    const title = s.name || s.title || s.id || 'Station';
    const subtitle = getStationDetails(s) || getStationHomepage(s) || s.id || '';
    li.className = 'station-card' + (currentIndex === idx ? ' selected' : '');

    const left = document.createElement('div');
    left.className = 'station-card-left';

    if (logoUrl) {
      const img = document.createElement('img');
      img.className = 'station-card-logo';
      img.alt = `${title} logo`;
      img.referrerPolicy = 'no-referrer';
      const fallback = document.createElement('div');
      fallback.className = 'station-card-fallback';
      fallback.textContent = title.charAt(0).toUpperCase();
      img.onerror = () => { left.replaceChild(fallback, img); };
      img.src = toHttpsIfHttp(logoUrl) || logoUrl || RADIO_PLACEHOLDER_LOGO;
      left.appendChild(img);
    } else {
      const img = document.createElement('img');
      img.className = 'station-card-logo';
      img.alt = `${title} logo`;
      img.src = RADIO_PLACEHOLDER_LOGO;
      left.appendChild(img);
    }

    const body = document.createElement('div');
    body.className = 'station-card-body';
    const tEl = document.createElement('div');
    tEl.className = 'station-card-title';
    tEl.textContent = title;
    const sEl = document.createElement('div');
    sEl.className = 'station-card-sub';
    sEl.textContent = subtitle;
    const healthBadge = getStationHealthBadge(s, stationHealth);
    body.appendChild(tEl);
    body.appendChild(sEl);
    if (healthBadge) {
      const badgeEl = document.createElement('div');
      badgeEl.className = `station-card-health station-card-health-${healthBadge.tone}`;
      badgeEl.textContent = healthBadge.label;
      badgeEl.title = healthBadge.detail;
      body.appendChild(badgeEl);
    }

    li.appendChild(left);
    li.appendChild(body);

    li.onclick = async () => {
      closeCastOverlay();
      await setStationByIndex(idx);
      try { await play(); } catch (e) { console.error('Failed to play station from grid', e); }
    };

    deviceListEl.appendChild(li);
  }
}

function closeCastOverlay() {
  if (!castOverlay) return;
  castOverlay.classList.add('hidden');
  castOverlay.setAttribute('aria-hidden', 'true');
  const titleEl = document.getElementById('deviceTitle');
  if (titleEl) titleEl.textContent = 'Stations';
  if (deviceListEl) deviceListEl.classList.remove('stations-grid');
}

// ── Editor overlay ────────────────────────────────────────────────────────────

function openEditorOverlay() {
  updateEditorPersistenceInfo();
  updateEditorBackupActivityInfo();
  renderUserStationsList();
  if (editorOverlay) { editorOverlay.classList.remove('hidden'); editorOverlay.setAttribute('aria-hidden', 'false'); }
}

function closeEditorOverlay() {
  if (editorOverlay) { editorOverlay.classList.add('hidden'); editorOverlay.setAttribute('aria-hidden', 'true'); }
  if (addStationForm) addStationForm.reset();
  if (usId) usId.value = '';
}

function renderUserStationsList() {
  const list = loadUserStations();
  if (!editorListEl) return;
  editorListEl.innerHTML = '';
  if (!list || list.length === 0) {
    editorListEl.innerHTML = '<li class="device"><div class="device-main">No user stations</div><div class="device-sub">Add your stream using the form below</div></li>';
    return;
  }

  list.forEach((s) => {
    const li = document.createElement('li');
    li.className = 'device';
    const main = s.title || s.name || s.id || 'User Station';
    const sub = s.url || s.streams?.audio || '';
    const stationId = String(s.id || '');
    li.innerHTML = `<div class="editor-station-row">
      <div class="editor-station-copy">
        <div class="device-main">${escapeHtml(main)}</div>
        <div class="device-sub">${escapeHtml(sub)}</div>
      </div>
      <div class="editor-station-actions">
        <button data-station-id="${escapeHtml(stationId)}" class="btn edit-btn">Edit</button>
        <button data-station-id="${escapeHtml(stationId)}" class="btn delete-btn">Delete</button>
      </div>
    </div>`;
    editorListEl.appendChild(li);
  });

  editorListEl.querySelectorAll('.edit-btn').forEach((b) => {
    b.addEventListener('click', () => editUserStation(b.getAttribute('data-station-id')));
  });
  editorListEl.querySelectorAll('.delete-btn').forEach((b) => {
    b.addEventListener('click', () => deleteUserStation(b.getAttribute('data-station-id')));
  });
}

function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function editUserStation(stationId) {
  const list = loadUserStations();
  const s = list.find((station) => String(station?.id || '') === String(stationId || ''));
  if (!s) return;
  if (usTitle) usTitle.value = s.title || s.name || '';
  if (usUrl) usUrl.value = s.url || s.streams?.audio || s.liveAudio || '';
  if (usLogo) usLogo.value = s.logo || s.assets?.logo || '';
  if (usWww) usWww.value = s.website || s.www || '';
  if (usId) usId.value = s.id || '';
}

function deleteUserStation(stationId) {
  void deletePersistedUserStationById(stationId).then(() => {
    void loadStations();
    renderUserStationsList();
  });
}

addStationForm?.addEventListener('submit', (e) => {
  e.preventDefault();

  // Validate URL before saving
  const urlValue = usUrl?.value.trim() ?? '';
  try { new URL(urlValue); } catch (_) {
    if (statusTextEl) statusTextEl.textContent = 'Invalid stream URL';
    return;
  }

  const station = {
    id: usId?.value || `user-${Date.now()}`,
    name: usTitle?.value.trim() ?? '',
    category: 'Custom',
    country: '',
    language: '',
    website: usWww?.value.trim() ?? '',
    assets: {
      logo: usLogo?.value.trim() ?? '',
    },
    streams: {
      audio: urlValue,
    },
    enabled: true,
  };

  void upsertPersistedUserStation(station).then(() => {
    renderUserStationsList();
    void loadStations();
    if (addStationForm) addStationForm.reset();
    if (usId) usId.value = '';
  });
});

// ── Artwork pointer fallback ──────────────────────────────────────────────────

function ensureArtworkPointerFallback() {
  try {
    if (artworkPlaceholder) artworkPlaceholder.style.cursor = 'pointer';
  } catch (e) { /* ignore */ }
}

// ── Event listeners ───────────────────────────────────────────────────────────

function setupEventListeners() {
  playBtn?.addEventListener('click', togglePlay);
  prevBtn?.addEventListener('click', playPrev);
  nextBtn?.addEventListener('click', playNext);
  volumeSlider?.addEventListener('input', handleVolumeInput);
  muteBtn?.addEventListener('click', toggleMute);
  stationLibraryPagePrevBtn?.addEventListener('click', goToPreviousStationLibraryPage);
  stationLibraryPageNextBtn?.addEventListener('click', goToNextStationLibraryPage);

  closeOverlayBtn?.addEventListener('click', closeCastOverlay);
  castOverlay?.addEventListener('click', (e) => { if (e.target === castOverlay) closeCastOverlay(); });

  editBtn?.addEventListener('click', openEditorOverlay);
  stationsListBtn?.addEventListener('click', toggleStationLibrary);
  installAppBtn?.addEventListener('click', promptInstallApp);
  castBtn?.addEventListener('click', requestCastSession);
  editorCloseBtn?.addEventListener('click', closeEditorOverlay);
  countrySelectionCancelBtn?.addEventListener('click', closeCountrySelectionOverlay);
  countrySelectionOverlay?.addEventListener('click', (e) => {
    if (e.target === countrySelectionOverlay) closeCountrySelectionOverlay();
  });
  countrySelectionSearchInput?.addEventListener('input', () => {
    countrySelectionQuery = countrySelectionSearchInput.value || '';
    renderCountrySelectionOverlay();
  });
  countrySelectionDefaultsBtn?.addEventListener('click', () => {
    draftSelectedRadioCountryCodes = new Set(defaultSelectedRadioCountryCodes);
    renderCountrySelectionOverlay();
  });
  countrySelectionAllBtn?.addEventListener('click', async () => {
    await ensureAvailableRadioCountriesLoaded();
    draftSelectedRadioCountryCodes = new Set(availableRadioCountries.map((country) => country.code));
    renderCountrySelectionOverlay();
  });
  countrySelectionSaveBtn?.addEventListener('click', () => {
    void saveCountrySelection();
  });
  exportUserDataBtn?.addEventListener('click', handleExportUserData);
  importUserDataBtn?.addEventListener('click', () => importUserDataInput?.click());
  importUserDataInput?.addEventListener('change', () => {
    const [file] = importUserDataInput.files || [];
    void handleImportUserDataFile(file);
  });
  resetUserDataBtn?.addEventListener('click', () => {
    void handleResetUserData();
  });
  stationAdvancedFiltersBtn?.addEventListener('click', openAdvancedFiltersOverlay);
  stationAdvancedFiltersCloseBtn?.addEventListener('click', closeAdvancedFiltersOverlay);
  stationAdvancedFiltersDoneBtn?.addEventListener('click', closeAdvancedFiltersOverlay);
  sessionSchedulerBtn?.addEventListener('click', openSessionSchedulerOverlay);
  sessionSummaryBtn?.addEventListener('click', openSessionSchedulerOverlay);
  sessionSchedulerCloseBtn?.addEventListener('click', closeSessionSchedulerOverlay);
  sessionSchedulerDoneBtn?.addEventListener('click', closeSessionSchedulerOverlay);
  shortcutHelpBtn?.addEventListener('click', openShortcutHelpOverlay);
  shortcutHelpCloseBtn?.addEventListener('click', closeShortcutHelpOverlay);
  songHistoryClearBtn?.addEventListener('click', () => {
    const station = stations[currentIndex];
    if (!station?.id) return;
    const history = loadStationSongHistory();
    delete history[station.id];
    saveStationSongHistory(history);
    renderSongHistory();
  });
  stationLibraryCloseBtn?.addEventListener('click', closeStationLibrary);
  stationLibraryFiltersToggleBtn?.addEventListener('click', () => {
    toggleStationLibraryFilters();
  });
  clearManagedCatalogBtn?.addEventListener('click', async () => {
    try {
      await clearPersistedManagedStationCatalog();
      showCatalogSyncStatus('Cleared persisted managed catalog');
      await loadStations();
    } catch (e) {
      console.error('clearManagedCatalog failed', e);
      showCatalogSyncStatus('Failed to clear managed catalog', null, 'warning', e?.message || '');
    }
  });
  stationCountryFilterBtn?.addEventListener('click', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();
    closeSortFilter();
    closeLanguageFilter();
    closeCodecFilter();
    closeBitrateFilter();
    toggleStationCountryFilter();
  });
  stationSearchInput?.addEventListener('input', () => {
    stationLibraryQuery = stationSearchInput.value || '';
    resetStationLibraryPage();
    renderStationLibrary();
  });
  stationLanguageFilterBtn?.addEventListener('click', (event) => {
    event.stopPropagation();
    stationLanguageFilterOpen = !stationLanguageFilterOpen;
    closeStationCountryFilter();
    closeSortFilter();
    closeCodecFilter();
    closeBitrateFilter();
    syncStationLibraryMetadataFilters();
  });
  stationCodecFilterBtn?.addEventListener('click', (event) => {
    event.stopPropagation();
    stationCodecFilterOpen = !stationCodecFilterOpen;
    closeStationCountryFilter();
    closeSortFilter();
    closeLanguageFilter();
    closeBitrateFilter();
    syncStationLibraryMetadataFilters();
  });
  stationBitrateFilterBtn?.addEventListener('click', (event) => {
    event.stopPropagation();
    stationBitrateFilterOpen = !stationBitrateFilterOpen;
    closeStationCountryFilter();
    closeSortFilter();
    closeLanguageFilter();
    closeCodecFilter();
    syncStationLibraryMetadataFilters();
  });
  stationHealthFilterBtn?.addEventListener('click', () => {
    stationLibraryHealthyOnly = !stationLibraryHealthyOnly;
    resetStationLibraryPage();
    renderStationLibrary();
  });
  sleepTimerButtons?.forEach((button) => {
    button.addEventListener('click', () => {
      scheduleSleepTimer(Number(button.getAttribute('data-sleep-minutes')));
    });
  });
  wakeAlarmButtons?.forEach((button) => {
    button.addEventListener('click', () => {
      scheduleWakeAlarm(Number(button.getAttribute('data-wake-minutes')));
    });
  });
  stationSortBtn?.addEventListener('click', (e) => {
    e.stopPropagation();
    stationSortOpen = !stationSortOpen;
    closeStationCountryFilter();
    closeLanguageFilter();
    closeCodecFilter();
    closeBitrateFilter();
    renderSortOptions();
  });
  stationTabBtns.forEach((btn) => {
    btn.addEventListener('click', () => setStationLibraryTab(btn.dataset.stationTab || 'all'));
  });

  artworkPlaceholder?.addEventListener('click', openStationLibrary);
  castOutputBtn?.addEventListener('click', toggleCastBothMode);
  installPromptActionBtn?.addEventListener('click', promptInstallApp);
  installPromptDismissBtn?.addEventListener('click', hideInstallPromptUI);
  window.addEventListener('resize', () => {
    updateCoverflowTransforms();
    updateStationTitleLayout(getStationTitle(stations[currentIndex]));
  });
  document.addEventListener('click', (ev) => {
    if (stationCountryFilterOpen && !stationCountryFilterWrapEl?.contains(ev.target)) {
      closeStationCountryFilter();
    }
    if (stationSortOpen && !stationSortWrapEl?.contains(ev.target)) {
      closeSortFilter();
    }
    if (stationLanguageFilterOpen && !stationLanguageFilterWrapEl?.contains(ev.target)) {
      closeLanguageFilter();
    }
    if (stationCodecFilterOpen && !stationCodecFilterWrapEl?.contains(ev.target)) {
      closeCodecFilter();
    }
    if (stationBitrateFilterOpen && !stationBitrateFilterWrapEl?.contains(ev.target)) {
      closeBitrateFilter();
    }
    if (stationAdvancedFiltersOverlay && ev.target === stationAdvancedFiltersOverlay) {
      closeAdvancedFiltersOverlay();
    }
    if (sessionSchedulerOverlay && ev.target === sessionSchedulerOverlay) {
      closeSessionSchedulerOverlay();
    }
  });

  // Keyboard shortcuts
  document.addEventListener('keydown', (e) => {
    if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
    if (stationCountryFilterOpen && e.code === 'Escape') {
      e.preventDefault();
      closeStationCountryFilter();
      return;
    }
    if (stationSortOpen && e.code === 'Escape') {
      e.preventDefault();
      closeSortFilter();
      return;
    }
    if (stationLanguageFilterOpen && e.code === 'Escape') {
      e.preventDefault();
      closeLanguageFilter();
      return;
    }
    if (stationCodecFilterOpen && e.code === 'Escape') {
      e.preventDefault();
      closeCodecFilter();
      return;
    }
    if (stationBitrateFilterOpen && e.code === 'Escape') {
      e.preventDefault();
      closeBitrateFilter();
      return;
    }
    if (stationAdvancedFiltersOpen && e.code === 'Escape') {
      e.preventDefault();
      closeAdvancedFiltersOverlay();
      return;
    }
    if (countrySelectionOverlay && !countrySelectionOverlay.classList.contains('hidden') && e.code === 'Escape') {
      e.preventDefault();
      closeCountrySelectionOverlay();
      return;
    }
    if (shortcutHelpOverlay && !shortcutHelpOverlay.classList.contains('hidden') && e.code === 'Escape') {
      e.preventDefault();
      closeShortcutHelpOverlay();
      return;
    }
    if (sessionSchedulerOverlay && !sessionSchedulerOverlay.classList.contains('hidden') && e.code === 'Escape') {
      e.preventDefault();
      closeSessionSchedulerOverlay();
      return;
    }
    if ((e.key === '?' || (e.shiftKey && e.code === 'Slash'))) {
      e.preventDefault();
      openShortcutHelpOverlay();
      return;
    }
    if (e.code === 'Space') { e.preventDefault(); togglePlay(); }
    else if (e.code === 'ArrowRight') playNext();
    else if (e.code === 'ArrowLeft') playPrev();
    else if (e.code === 'KeyM') toggleMute();
    else if (e.code === 'Escape') { closeStationLibrary(); closeCastOverlay(); closeEditorOverlay(); closeShortcutHelpOverlay(); closeSessionSchedulerOverlay(); }
  });

  applyStationLibraryFiltersState();
  updateSleepTimerUI();
  updateWakeAlarmUI();
}

async function promptInstallApp() {
  if (!deferredInstallPrompt) return;
  deferredInstallPrompt.prompt();
  try { await deferredInstallPrompt.userChoice; } catch (e) { /* ignore */ }
  deferredInstallPrompt = null;
  hideInstallPromptUI();
}

// ── Media Session API (for OS media controls / lock screen) ──────────────────

function updateMediaSession() {
  if (!('mediaSession' in navigator)) return;
  const station = stations[currentIndex];
  if (!station) return;
  try {
    navigator.mediaSession.metadata = new MediaMetadata({
      title: station.name,
      artist: station.currentSongInfo?.artist ?? '',
      album: 'Live Radio',
      artwork: station.logo ? [{ src: toHttpsIfHttp(station.logo), sizes: '512x512' }] : [],
    });
    navigator.mediaSession.setActionHandler('play', () => play());
    navigator.mediaSession.setActionHandler('pause', () => stop());
    navigator.mediaSession.setActionHandler('previoustrack', () => playPrev());
    navigator.mediaSession.setActionHandler('nexttrack', () => playNext());
  } catch (e) { /* ignore */ }
}

// ── Init ──────────────────────────────────────────────────────────────────────

let hasInitialized = false;

async function init() {
  if (hasInitialized) return;
  hasInitialized = true;

  try {
    console.group('RadioPlayer init');
    console.log('location:', location.href);
    console.log('userAgent:', navigator.userAgent);
    console.groupEnd();

    await hydratePlayerPersistence();
    restoreSavedVolume();
    restoreCastBothMode();
    restoreLastStationCountry();
    restoreSelectedRadioCountryCodes();
    await loadStations();
    setupEventListeners();
    setupServiceWorkerCatalogRefreshListener();
    void sendSelectedCountriesToServiceWorker();
    lockPortraitOrientation();
    ensureArtworkPointerFallback();
    scheduleArtworkShine();
    scheduleIconSparkle();
    updateUI();

    // Update Media Session when station or song changes
    audio.addEventListener('playing', updateMediaSession);
  } catch (e) {
    console.error('Error during init', e);
    if (statusTextEl) statusTextEl.textContent = 'Init error: ' + (e?.message ?? String(e));
  }
}

window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault();
  deferredInstallPrompt = event;
  showInstallPromptUI();
});

window.addEventListener('appinstalled', () => {
  deferredInstallPrompt = null;
  hideInstallPromptUI();
});

window.addEventListener('orientationchange', () => {
  lockPortraitOrientation();
});

// Refresh the managed catalog when the page returns to the foreground after
// being hidden for at least 1 hour. This is the cross-browser fallback for
// browsers that don't support periodicSync (e.g. iOS Safari, Firefox).
const MANAGED_CATALOG_REFRESH_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
let lastManagedCatalogLoadTime = 0;
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState !== 'visible') return;
  const now = Date.now();
  if (now - lastManagedCatalogLoadTime >= MANAGED_CATALOG_REFRESH_INTERVAL_MS) {
    void loadStations();
  }
});

// Runtime debug helpers (use from console)
window.__rp_inspectManagedCatalog = async function() {
  try {
    const persisted = await loadPersistedManagedStations();
    console.log('Persisted managed catalog:', persisted);
    console.log('In-memory stations count:', stations.length);
    return { persisted, stations };
  } catch (e) {
    console.error('inspectManagedCatalog failed', e);
  }
};

window.__rp_clearManagedCatalog = async function() {
  try {
    await clearPersistedManagedStationCatalog();
    console.log('Cleared persisted managed catalog. Call loadStations() to reload catalogs.');
  } catch (e) {
    console.error('clearManagedCatalog failed', e);
  }
};

window.__rp_refreshManagedCatalog = async function() {
  try {
    await clearPersistedManagedStationCatalog();
    await loadManagedStations();
    await loadStations();
    console.log('Refreshed managed catalog from backend and reloaded stations.');
  } catch (e) {
    console.error('refreshManagedCatalog failed', e);
  }
};

export function initPlayer() {
  return init();
}
