import { loadRadioStations } from './radio/loadRadioStations.ts';
import { radioCountries } from './radio/radioCountries.ts';
import { loadManagedStations } from './radio/loadManagedStations.ts';

// 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
let castMode = 'local';
let castContext = null;   // cast.framework.CastContext instance
let castPlayerController = null; // cast.framework.RemotePlayerController
let castInitialized = 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 stationLibraryPage = 0;
let stationLibraryPageTotal = 1;
let stationCatalogState = 'idle';
let stationCatalogError = '';
let playbackError = '';
let stationCountryFilterOpen = false;
let artworkShineTimeoutId = null;
let artworkShineClearTimeoutId = null;
let stationTitleRafId = null;

const STATION_LIBRARY_PAGE_SIZE = 12;
const ARTWORK_SHINE_EFFECTS = ['shine-sweep', 'shine-glint', 'shine-flare'];

const RADIO_PLACEHOLDER_LOGO = `${import.meta.env.BASE_URL}images/radio-placeholder.svg`;

// UI Elements
const stationNameEl = document.getElementById('station-name');
const stationSubtitleEl = document.getElementById('station-subtitle');
const nowPlayingEl = document.getElementById('now-playing');
const nowArtistEl = document.getElementById('now-artist');
const nowTitleEl = document.getElementById('now-title');
const statusTextEl = document.getElementById('status-text');
const statusDotEl = document.querySelector('.status-dot');
const engineBadgeEl = document.getElementById('engine-badge');
const engineLabelEl = document.getElementById('engine-label');
const playBtn = document.getElementById('play-btn');
const iconPlay = document.getElementById('icon-play');
const iconStop = document.getElementById('icon-stop');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const volumeSlider = document.getElementById('volume-slider');
const volumeValue = document.getElementById('volume-value');
const muteBtn = document.getElementById('mute-btn');
const iconVolume = document.getElementById('icon-volume');
const iconMuted = document.getElementById('icon-muted');
const castOverlay = document.getElementById('cast-overlay');
const closeOverlayBtn = document.getElementById('close-overlay');
const deviceListEl = document.getElementById('device-list');
const coverflowStageEl = document.getElementById('artwork-coverflow-stage');
const coverflowPrevBtn = document.getElementById('artwork-prev');
const coverflowNextBtn = document.getElementById('artwork-next');
const artworkPlaceholder = document.querySelector('.artwork-placeholder');
const logoTextEl = document.querySelector('.station-logo-text');
const logoImgEl = document.getElementById('station-logo-img');
const stationLibraryEl = document.getElementById('station-library');
const playerLayoutEl = document.querySelector('.player-layout');
const stationLibraryListEl = document.getElementById('station-library-list');
const stationLibrarySummaryEl = document.getElementById('station-library-summary');
const stationSearchInput = document.getElementById('station-search-input');
const stationLibraryCloseBtn = document.getElementById('station-library-close');
const stationCategoryListEl = document.getElementById('station-category-list');
const stationCountryFilterWrapEl = document.querySelector('[data-country-filter]');
const stationCountryFilterBtn = document.getElementById('station-country-filter-btn');
const stationCountryFilterMenu = document.getElementById('station-country-filter-menu');
const stationCountryFilterText = document.getElementById('station-country-filter-text');
const stationCountryFilterFlag = document.getElementById('station-country-filter-flag');
const stationLibraryPaginationEl = document.getElementById('station-library-pagination');
const stationLibraryPagePrevBtn = document.getElementById('station-library-page-prev');
const stationLibraryPageNextBtn = document.getElementById('station-library-page-next');
const stationLibraryPageInfo = document.getElementById('station-library-page-info');
const stationTabBtns = document.querySelectorAll('[data-station-tab]');
const installPromptBannerEl = document.getElementById('install-prompt-banner');
const installPromptActionBtn = document.getElementById('install-prompt-action');
const installPromptDismissBtn = document.getElementById('install-prompt-dismiss');

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

// Editor
const editBtn = document.getElementById('edit-stations-btn');
const stationsListBtn = document.getElementById('stations-list-btn');
const installAppBtn = document.getElementById('install-app-btn');
const castBtn = document.getElementById('cast-btn');
const editorOverlay = document.getElementById('editor-overlay');
const editorCloseBtn = document.getElementById('editor-close-btn');
const editorListEl = document.getElementById('editor-list');
const addStationForm = document.getElementById('add-station-form');
const usTitle = document.getElementById('us_title');
const usUrl = document.getElementById('us_url');
const usLogo = document.getElementById('us_logo');
const usWww = document.getElementById('us_www');
const usId = document.getElementById('us_id');
const usIndex = document.getElementById('us_index');

// Cast output toggle
const castOutputRow = document.getElementById('cast-output-row');
const castOutputBtn = document.getElementById('cast-output-btn');
const castOutputText = document.getElementById('cast-output-text');

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

function prefersReducedMotion() {
  try {
    return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  } catch (e) {
    return false;
  }
}

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

function isStandalonePwa() {
  return window.matchMedia('(display-mode: standalone)').matches
    || window.matchMedia('(display-mode: fullscreen)').matches
    || window.navigator.standalone === true;
}

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 isMobileInstallViewport() {
  return window.matchMedia('(max-width: 760px)').matches;
}

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

const STATION_THEMES = [
  { accent: '#4dd7c8', accent2: '#ffb45c', accent3: '#8fb3ff', page: '#171b22', panel: '#111821' },
  { accent: '#ff7aa8', accent2: '#ffd166', accent3: '#8fb3ff', page: '#22151d', panel: '#1b131a' },
  { accent: '#7ce38b', accent2: '#53c7ff', accent3: '#f0c86a', page: '#132018', panel: '#101a16' },
  { accent: '#b28cff', accent2: '#6ee7d8', accent3: '#ff9f6e', page: '#19162a', panel: '#141221' },
  { accent: '#ff9f43', accent2: '#4dd7c8', accent3: '#feca57', page: '#241912', panel: '#1c1510' },
  { accent: '#6ecbff', accent2: '#b28cff', accent3: '#ff7aa8', page: '#111a25', panel: '#101720' },
  { accent: '#f7d36b', accent2: '#ff7a7a', accent3: '#6ee7d8', page: '#211b10', panel: '#1a160e' },
  { accent: '#9cff6e', accent2: '#ff8bd1', accent3: '#78a7ff', page: '#14200f', panel: '#111a0e' },
  { accent: '#ff6f61', accent2: '#ffd166', accent3: '#80ed99', page: '#241513', panel: '#1c1110' },
  { accent: '#64dfdf', accent2: '#c77dff', accent3: '#f6bd60', page: '#101f22', panel: '#0e191c' },
  { accent: '#a3cef1', accent2: '#f28482', accent3: '#f6bd60', page: '#121b24', panel: '#101720' },
  { accent: '#d0f4de', accent2: '#ff99c8', accent3: '#a9def9', page: '#10201a', panel: '#0e1815' },
];

function toHttpsIfHttp(url) {
  if (!url || typeof url !== 'string') return '';
  return url.startsWith('http://') ? ('https://' + url.slice('http://'.length)) : url;
}

function hashString(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = ((hash << 5) - hash) + str.charCodeAt(i);
    hash |= 0;
  }
  return Math.abs(hash);
}

function hexToRgb(hex) {
  const normalized = String(hex || '').replace('#', '').trim();
  if (!/^[0-9a-f]{6}$/i.test(normalized)) return null;
  const num = parseInt(normalized, 16);
  return `${(num >> 16) & 255}, ${(num >> 8) & 255}, ${num & 255}`;
}

function getStationTheme(station) {
  const rawTheme = station?.raw?.theme || station?.theme || {};
  const key = `${station?.id || ''}:${station?.name || station?.title || ''}`;
  const fallback = STATION_THEMES[hashString(key) % STATION_THEMES.length];
  return {
    accent: rawTheme.accent || rawTheme.primary || station?.raw?.color || fallback.accent,
    accent2: rawTheme.accent2 || rawTheme.secondary || fallback.accent2,
    accent3: rawTheme.accent3 || fallback.accent3,
    page: rawTheme.page || rawTheme.background || fallback.page,
    panel: rawTheme.panel || fallback.panel,
  };
}

function applyStationTheme(station) {
  if (!station) return;
  const theme = getStationTheme(station);
  const root = document.documentElement;
  root.style.setProperty('--accent', theme.accent);
  root.style.setProperty('--accent-2', theme.accent2);
  root.style.setProperty('--accent-3', theme.accent3);
  root.style.setProperty('--page-bg', theme.page);
  root.style.setProperty('--theme-panel', theme.panel);
  root.style.setProperty('--accent-rgb', hexToRgb(theme.accent) || '77, 215, 200');
  root.style.setProperty('--accent-2-rgb', hexToRgb(theme.accent2) || '255, 180, 92');
  root.style.setProperty('--accent-3-rgb', hexToRgb(theme.accent3) || '143, 179, 255');
  root.style.setProperty('--theme-page-rgb', hexToRgb(theme.page) || '17, 19, 24');
  root.style.setProperty('--theme-panel-rgb', hexToRgb(theme.panel) || '16, 20, 27');
  root.style.setProperty('--accent-glow', `rgba(${hexToRgb(theme.accent) || '77, 215, 200'}, 0.34)`);
}

function getStationStreamUrl(station) {
  if (!station) return '';
  return station.streamUrl
    || station.url
    || station.streams?.audio
    || station.streams?.primary
    || station.liveAudio
    || station.liveStream
    || station.liveVideo
    || '';
}

function getStationHomepage(station) {
  if (!station) return '';
  return station.url
    || station.homepage
    || station.website
    || station.www
    || station.raw?.homepage
    || station.raw?.website
    || station.raw?.www
    || '';
}

function getStationVideoUrl(station) {
  return station?.streams?.video || station?.liveVideo || '';
}

function getStationMetadataUrl(station) {
  return station?.metadata?.lastSongs
    || station?.currentSong
    || station?.lastSongs
    || station?.raw?.metadata?.lastSongs
    || station?.raw?.currentSong
    || station?.raw?.lastSongs
    || '';
}

function normalizeStationRecord(station, isUserStation = false) {
  const name = station?.name || station?.title || station?.id || 'Unknown';
  const url = getStationStreamUrl(station);
  const bitrate = Number(station?.bitrate);

  return {
    id: station?.id || `${isUserStation ? 'user' : 'station'}-${name.replace(/\s+/g, '-')}`,
    name,
    url,
    logo: station?.logoUrl || station?.assets?.logo || station?.logo || '',
    poster: station?.assets?.poster || station?.poster || '',
    slogan: station?.slogan || station?.description || station?.defaultText || '',
    category: station?.category || station?.genre || '',
    country: station?.country || '',
    countryCode: station?.countryCode || '',
    language: station?.language || '',
    region: station?.region || '',
    tags: Array.isArray(station?.tags) ? station.tags : [],
    codec: station?.codec || '',
    bitrate: Number.isFinite(bitrate) && bitrate > 0 ? bitrate : null,
    homepage: station?.homepage || station?.website || station?.www || '',
    website: station?.website || station?.homepage || station?.www || '',
    metadata: station?.metadata || (station?.lastSongs ? { lastSongs: station.lastSongs } : {}),
    streams: station?.streams || {
      audio: url,
      ...(getStationVideoUrl(station) ? { video: getStationVideoUrl(station) } : {}),
    },
    enabled: typeof station?.enabled === 'boolean' ? station.enabled : true,
    source: station?.source || station?.raw?.source || '',
    sourceStationUuid: station?.sourceStationUuid || station?.raw?.sourceStationUuid || '',
    raw: station,
    _user: isUserStation,
  };
}

function getMetadataFetchUrl(url) {
  if (!url || typeof url !== 'string') return '';

  const safeUrl = toHttpsIfHttp(url) || url;
  if (!import.meta.env.DEV) {
    try {
      const parsed = new URL(safeUrl);
      if (parsed.hostname === 'data.radio.si') {
        return '';
      }
    } catch (e) {
      return safeUrl;
    }

    return safeUrl;
  }

  try {
    const parsed = new URL(safeUrl);
    if (parsed.hostname === 'data.radio.si') {
      return `/radio-si-data${parsed.pathname}${parsed.search}`;
    }
  } catch (e) {
    return safeUrl;
  }

  return safeUrl;
}

function uniqueNonEmpty(urls) {
  const out = [];
  const seen = new Set();
  for (const u of urls) {
    if (!u || typeof u !== 'string') continue;
    const trimmed = u.trim();
    if (!trimmed || seen.has(trimmed)) continue;
    seen.add(trimmed);
    out.push(trimmed);
  }
  return out;
}

function setImgWithFallback(imgEl, urls, onFinalError) {
  const candidates = uniqueNonEmpty(urls);
  let i = 0;

  if (!imgEl || candidates.length === 0) {
    if (imgEl) { imgEl.onload = null; imgEl.onerror = null; imgEl.src = ''; }
    if (onFinalError) onFinalError();
    return;
  }

  const tryNext = () => {
    if (i >= candidates.length) {
      if (onFinalError) onFinalError();
      return;
    }
    imgEl.src = candidates[i++];
  };

  imgEl.onerror = tryNext;
  try { imgEl.referrerPolicy = 'no-referrer'; } catch (e) {}
  tryNext();
}

// ── 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', () => {
  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', () => {
  const err = audio.error;
  const msg = err ? audioErrorMessage(err.code) : 'Stream error';
  const station = stations[currentIndex];
  playbackError = station ? `Unable to play ${getStationTitle(station)}. ${msg}.` : `Unable to play this station. ${msg}.`;
  isPlaying = false;
  updateUI();
});

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';
  }
}

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

function saveVolumeToStorage(val) {
  try { localStorage.setItem('volume', String(val)); } catch (e) { /* ignore */ }
}

function getSavedVolume() {
  try {
    const v = localStorage.getItem('volume');
    if (!v) return null;
    const n = Number(v);
    return Number.isFinite(n) && n >= 0 && n <= 100 ? n : null;
  } catch (e) { return 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) {
  try { if (id) localStorage.setItem('lastStationId', id); } catch (e) { /* ignore */ }
}

function getLastStationId() {
  try { return localStorage.getItem('lastStationId'); } catch (e) { return null; }
}

function saveLastStationCountry(country) {
  try { if (country) localStorage.setItem('lastStationCountry', country); } catch (e) { /* ignore */ }
}

function getLastStationCountry() {
  try { return localStorage.getItem('lastStationCountry'); } catch (e) { return null; }
}

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

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

function saveCastBothMode(val) {
  try { localStorage.setItem('castBothMode', val ? '1' : '0'); } catch (e) { /* ignore */ }
}

function restoreCastBothMode() {
  try {
    castBothMode = localStorage.getItem('castBothMode') === '1';
  } catch (e) { castBothMode = false; }
}

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 = '';
  }
}

// ── User Stations (localStorage) ────────────────────────────────────────────

function loadUserStations() {
  try {
    const raw = localStorage.getItem('userStations');
    return raw ? JSON.parse(raw) : [];
  } catch (e) {
    console.error('Error reading user stations', e);
    return [];
  }
}

function saveUserStations(arr) {
  try {
    localStorage.setItem('userStations', JSON.stringify(arr || []));
  } catch (e) {
    console.error('Error saving user stations', e);
  }
}

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

function loadFavoriteStationIds() {
  try {
    const parsed = JSON.parse(localStorage.getItem('favoriteStationIds') || '[]');
    return new Set(Array.isArray(parsed) ? parsed.filter(Boolean) : []);
  } catch (e) {
    return new Set();
  }
}

function saveFavoriteStationIds(ids) {
  try {
    localStorage.setItem('favoriteStationIds', JSON.stringify(Array.from(ids)));
  } catch (e) {
    console.debug('Failed saving favourites', e);
  }
}

function loadStationUsageCounts() {
  try {
    const parsed = JSON.parse(localStorage.getItem('stationUsageCounts') || '{}');
    return parsed && typeof parsed === 'object' ? parsed : {};
  } catch (e) {
    return {};
  }
}

function saveStationUsageCounts(counts) {
  try {
    localStorage.setItem('stationUsageCounts', JSON.stringify(counts || {}));
  } catch (e) {
    console.debug('Failed saving station usage', e);
  }
}

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 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?.country || station?.countryCode || 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 getStationDetails(station) {
  return [getStationCountry(station), getStationTechnicalLabel(station)].filter(Boolean).join(' • ');
}

function getStationSearchText(station) {
  return [
    getStationTitle(station),
    getStationCountry(station),
    getStationSubtitle(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 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 = radioCountryNameByCode.get(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 = radioCountryNameByCode.get(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) || '';
}

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 countryCodeToFlagUrl(countryCode) {
  const code = String(countryCode || '').trim().toLowerCase();
  if (!/^[a-z]{2}$/.test(code)) return '';
  return `https://flagcdn.com/w20/${code}.png`;
}

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

function getCountryFlagUrl(countryName) {
  if (!countryName || countryName === 'all') return 'https://flagcdn.com/w20/eu.png';
  const countryCode = getCountryCodeFromValue(countryName);
  return countryCode ? countryCodeToFlagUrl(countryCode) : '';
}

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

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

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.src = getCountryFlagUrl(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');
    const flagUrl = getCountryFlagUrl(value);
    flag.className = 'library-select-option-flag';
    flag.setAttribute('aria-hidden', 'true');
    if (flagUrl) {
      const flagImg = document.createElement('img');
      flagImg.src = flagUrl;
      flagImg.alt = '';
      flagImg.setAttribute('aria-hidden', 'true');
      flagImg.className = 'library-select-option-flag-img';
      flagImg.loading = 'lazy';
      flagImg.referrerPolicy = 'no-referrer';
      flagImg.addEventListener('error', () => {
        flagImg.remove();
        flag.textContent = value === 'all' ? '🌐' : '🏳️';
      }, { once: true });
      flag.appendChild(flagImg);
    } else {
      flag.textContent = value === 'all' ? '🌐' : '🏳️';
    }

    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));
}

function getFilteredStationEntries() {
  const favourites = loadFavoriteStationIds();
  const counts = loadStationUsageCounts();
  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 }))
      .filter((entry) => entry.count > 0)
      .sort((a, b) => 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 (query) {
    entries = entries.filter(({ station }) => getStationSearchText(station).includes(query));
  }

  return entries;
}

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();
  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() {
  try {
    if (!stationLibraryListEl) return;
    stationLibraryListEl.innerHTML = '';
    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();
    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);
      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);
      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);
      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);
      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');
      }

      pageEntries.forEach(({ station, index, count }) => {
      const title = getStationTitle(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 titleEl = document.createElement('div');
      titleEl.className = 'library-station-title';
      titleEl.textContent = title;
      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(titleEl);
      copy.appendChild(meta);

      const tags = document.createElement('div');
      tags.className = 'library-station-tags';
      const stationTags = getStationTags(station).slice(0, 3);
      stationTags.forEach((tag) => {
        const chip = document.createElement('span');
        chip.className = 'library-tag';
        chip.textContent = tag;
        tags.appendChild(chip);
      });

      const useCount = Number(counts[station.id] ?? count) || 0;
      if (useCount > 0) {
        const plays = document.createElement('span');
        plays.className = 'library-tag muted';
        plays.textContent = `${useCount} plays`;
        tags.appendChild(plays);
      } else if (stationTags.length === 0 && getStationSubtitle(station)) {
        const fallback = document.createElement('span');
        fallback.className = 'library-tag muted';
        fallback.textContent = getStationSubtitle(station);
        tags.appendChild(fallback);
      }

      if (tags.childElementCount > 0) {
        copy.appendChild(tags);
      }

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

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

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

async function loadStations() {
  try {
    stationCatalogState = 'loading';
    stationCatalogError = '';
    resetStationLibraryPage();
    renderStationLibrary();
    stopCurrentSongPollers();
    const raw = await loadRadioStations();
    const managedRaw = await loadManagedStations().catch(() => []);

    const normalizedRaw = raw
      .map((s) => normalizeStationRecord(s))
      .filter((s) => s.enabled !== false && s.url && s.url.length > 0);

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

    const userNormalized = loadUserStations()
      .map((s) => normalizeStationRecord(s, true))
      .filter((s) => s.url && s.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);

    stations = mergedStations;
  stationCatalogState = stations.length > 0 ? 'ready' : 'empty';
    console.debug('loadStations: loaded', stations.length, 'stations');

    if (stations.length > 0) {
      const lastId = getLastStationId();
      if (lastId) {
        const found = stations.findIndex((s) => s.id === lastId);
        currentIndex = found >= 0 ? found : 0;
      } else {
        currentIndex = 0;
      }
      loadStation(currentIndex);
      renderCoverflow();
      renderStationLibrary();
      startCurrentSongPollers();
    } else {
      renderCoverflow();
      renderStationLibrary();
    }
  } catch (e) {
    console.error('Failed to load stations', e);
    stations = [];
    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]);
  loadStation(currentIndex);
  renderCoverflow();
  renderStationLibrary();
  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) {
      station.currentSongInfo = 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 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';
}

// ── 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';
  if (nowPlayingEl) nowPlayingEl.classList.add('hidden');
  if (nowArtistEl) nowArtistEl.textContent = '';
  if (nowTitleEl) nowTitleEl.textContent = '';

  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) {}
}

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

function updateCastButtonUI() {
  if (!castBtn) return;
  const sdkReady = !!castContext;
  castBtn.classList.toggle('cast-ready', sdkReady);
  castBtn.classList.toggle('cast-active', castMode === 'cast');
  castBtn.setAttribute('aria-pressed', castMode === 'cast' ? 'true' : 'false');
  castBtn.title = sdkReady ? 'Cast to device' : 'Cast is not ready yet';
}

async function requestCastSession() {
  if (!castContext) {
    if (statusTextEl) statusTextEl.textContent = 'Cast 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 {
    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;

  playbackError = '';
  if (statusTextEl) statusTextEl.textContent = 'Buffering...';
  if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--text-muted)';

  const streamUrl = toHttpsIfHttp(station.url) || station.url;
  audio.src = streamUrl;
  audio.volume = isMuted ? 0 : currentVolume;
  audio.load();

  try {
    await audio.play();
    isPlaying = true;
    updateUI();
  } catch (e) {
    console.warn('audio.play() failed:', e.message);
    playbackError = e?.name === 'NotAllowedError'
      ? 'Playback was blocked until you interact with the page.'
      : `Unable to start ${getStationTitle(station)}.`;
    isPlaying = false;
    updateUI();
  }
}

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

  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() {
  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 !== 'cast' && 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 = playbackError;
      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 !== 'cast') stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
  }
  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);
}

// ── 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;
  }

  for (let idx = 0; idx < stations.length; idx++) {
    const s = stations[idx];
    const li = document.createElement('li');
    li.className = 'station-card' + (currentIndex === idx ? ' selected' : '');

    const logoUrl = getStationLogoUrl(s);
    const title = s.name || s.title || s.id || 'Station';
    const subtitle = getStationDetails(s) || getStationHomepage(s) || s.id || '';

    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;
    body.appendChild(tEl);
    body.appendChild(sEl);

    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() {
  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 (usIndex) usIndex.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, idx) => {
    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 || '';
    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-idx="${idx}" class="btn edit-btn">Edit</button>
        <button data-idx="${idx}" class="btn delete-btn">Delete</button>
      </div>
    </div>`;
    editorListEl.appendChild(li);
  });

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

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

function editUserStation(idx) {
  const list = loadUserStations();
  const s = list[idx];
  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 || '';
  if (usIndex) usIndex.value = String(idx);
}

function deleteUserStation(idx) {
  const list = loadUserStations();
  list.splice(idx, 1);
  saveUserStations(list);
  loadStations();
  renderUserStationsList();
}

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

  // 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,
  };

  const idx = usIndex?.value === '' ? -1 : Number(usIndex?.value);
  if (idx >= 0 && idx < list.length) list[idx] = station;
  else list.push(station);

  saveUserStations(list);
  renderUserStationsList();
  loadStations();
  if (addStationForm) addStationForm.reset();
  if (usIndex) usIndex.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);
  stationLibraryCloseBtn?.addEventListener('click', closeStationLibrary);
  stationCountryFilterBtn?.addEventListener('click', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();
    toggleStationCountryFilter();
  });
  stationSearchInput?.addEventListener('input', () => {
    stationLibraryQuery = stationSearchInput.value || '';
    resetStationLibraryPage();
    renderStationLibrary();
  });
  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) return;
    if (stationCountryFilterWrapEl?.contains(ev.target)) return;
    closeStationCountryFilter();
  });

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

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();

    restoreSavedVolume();
    restoreCastBothMode();
    restoreLastStationCountry();
    await loadStations();
    setupEventListeners();
    lockPortraitOrientation();
    ensureArtworkPointerFallback();
    scheduleArtworkShine();
    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();
});

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