// 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

// 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');

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

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 getMetadataFetchUrl(url) {
  if (!url || typeof url !== 'string') return '';

  const safeUrl = toHttpsIfHttp(url) || url;
  if (!import.meta.env.DEV) 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', () => {
  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', () => {
  if (!isPlaying) return;
  const err = audio.error;
  const msg = err ? audioErrorMessage(err.code) : 'Stream error';
  if (statusTextEl) statusTextEl.textContent = msg;
  if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--danger)';
  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; }
}

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

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

async function loadStations() {
  try {
    stopCurrentSongPollers();
    const resp = await fetch('stations.json');
    const raw = await resp.json();

    stations = raw
      .map((s) => {
        if (s.name && s.url) return s;
        const name = s.title || s.id || s.name || 'Unknown';
        const url = s.liveAudio || s.liveVideo || s.liveStream || s.url || '';
        return {
          id: s.id || name,
          name,
          url,
          logo: s.logo || s.poster || '',
          enabled: typeof s.enabled === 'boolean' ? s.enabled : true,
          raw: s,
        };
      })
      .filter((s) => s.enabled !== false && s.url && s.url.length > 0);

    const userNormalized = loadUserStations()
      .map((s) => {
        const name = s.title || s.name || s.id || 'UserStation';
        const url = s.url || s.liveAudio || s.liveVideo || '';
        return {
          id: s.id || `user-${name.replace(/\s+/g, '-')}`,
          name,
          url,
          logo: s.logo || '',
          enabled: typeof s.enabled === 'boolean' ? s.enabled : true,
          raw: s,
          _user: true,
        };
      })
      .filter((s) => s.url && s.url.length > 0);

    stations = stations.concat(userNormalized);
    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();
      startCurrentSongPollers();
    }
  } catch (e) {
    console.error('Failed to load stations', e);
    if (statusTextEl) statusTextEl.textContent = 'Error loading 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 = '';

    stations.forEach((s, 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 = s.logo || s.raw?.logo || s.raw?.poster || '';
      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 setStationByIndex(idxClicked);
        else openStationsOverlay();
      });
      item.addEventListener('keydown', async (ev) => {
        if (ev.key !== 'Enter' && ev.key !== ' ') return;
        ev.preventDefault();
        const idxSelected = Number(item.dataset.idx);
        if (idxSelected !== currentIndex) await setStationByIndex(idxSelected);
        else openStationsOverlay();
      });
      item.addEventListener('dblclick', () => {
        if (Number(item.dataset.idx) === currentIndex) openStationsOverlay();
      });

      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();
        setStationByIndex((currentIndex - 1 + stations.length) % stations.length);
      };
    }
    if (coverflowNextBtn) {
      coverflowNextBtn.onpointerdown = (ev) => ev.stopPropagation();
      coverflowNextBtn.onclick = (ev) => {
        ev.stopPropagation(); ev.preventDefault();
        setStationByIndex((currentIndex + 1) % stations.length);
      };
    }

    host.onpointerdown = (ev) => {
      if (!stations || stations.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;
        setStationByIndex((currentIndex - 1 + stations.length) % stations.length);
      } else if (coverflowAccum <= -threshold) {
        coverflowAccum = 0;
        setStationByIndex((currentIndex + 1) % stations.length);
      }
    };
    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 (!stations || stations.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) setStationByIndex((currentIndex + 1) % stations.length);
      else setStationByIndex((currentIndex - 1 + stations.length) % stations.length);
      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 = stations?.length ?? 0;
    if (n <= 0) return;

    const stageWidth = coverflowStageEl.clientWidth || 320;
    const isMobile = window.matchMedia('(max-width: 760px)').matches;
    const isNarrow = window.matchMedia('(max-width: 380px)').matches;
    const maxVisible = 1;
    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;

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

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

      if (Math.abs(offset) > maxVisible) {
        el.classList.remove('selected');
        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 (offset === 0) 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();
  currentIndex = idx;
  saveLastStationId(stations[currentIndex].id);
  loadStation(currentIndex);
  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(s.raw?.currentSong || s.raw?.lastSongs);
  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 = 'Live Stream';
}

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

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

  applyStationTheme(station);

  if (stationNameEl) stationNameEl.textContent = station.name;
  if (stationSubtitleEl) stationSubtitleEl.textContent = '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');
    }

    const rawLogo = station.logo || station.raw?.logo || '';
    const rawPoster = station.raw?.poster || station.poster || '';

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

      const candidates = uniqueNonEmpty([
        toHttpsIfHttp(rawLogo), rawLogo,
        toHttpsIfHttp(rawPoster), rawPoster,
      ]);

      setImgWithFallback(logoImgEl, candidates, () => {
        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) stationSubtitleEl.textContent = `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;

  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);
    if (statusTextEl) statusTextEl.textContent = 'Click play to start';
    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() {
  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() {
  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') stationSubtitleEl.textContent = 'Live Stream';
  } else {
    if (iconPlay) iconPlay.classList.remove('hidden');
    if (iconStop) iconStop.classList.add('hidden');
    if (playBtn) playBtn.classList.remove('playing');
    if (statusTextEl) statusTextEl.textContent = 'Ready';
    if (statusDotEl) statusDotEl.style.backgroundColor = 'var(--text-muted)';
    if (stationSubtitleEl) stationSubtitleEl.textContent = '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');
    deviceListEl.innerHTML = '<li class="device"><div class="device-main">No stations found</div><div class="device-sub">Check stations.json</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 = s.logo || s.raw?.logo || s.raw?.poster || '';
    const title = s.name || s.title || s.id || 'Station';
    const subtitle = s.raw?.www || 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;
      left.appendChild(img);
    } else {
      const fb = document.createElement('div');
      fb.className = 'station-card-fallback';
      fb.textContent = title.charAt(0).toUpperCase();
      left.appendChild(fb);
    }

    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 || '';
    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.liveAudio || '';
  if (usLogo) usLogo.value = s.logo || '';
  if (usWww) usWww.value = s.www || s.website || '';
  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()}`,
    title: usTitle?.value.trim() ?? '',
    url: urlValue,
    logo: usLogo?.value.trim() ?? '',
    www: usWww?.value.trim() ?? '',
    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);

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

  editBtn?.addEventListener('click', openEditorOverlay);
  stationsListBtn?.addEventListener('click', openStationsOverlay);
  installAppBtn?.addEventListener('click', promptInstallApp);
  castBtn?.addEventListener('click', requestCastSession);
  editorCloseBtn?.addEventListener('click', closeEditorOverlay);

  artworkPlaceholder?.addEventListener('click', openStationsOverlay);
  castOutputBtn?.addEventListener('click', toggleCastBothMode);
  window.addEventListener('resize', updateCoverflowTransforms);

  // Keyboard shortcuts
  document.addEventListener('keydown', (e) => {
    if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) 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();
  });
}

async function promptInstallApp() {
  if (!deferredInstallPrompt) return;
  deferredInstallPrompt.prompt();
  try { await deferredInstallPrompt.userChoice; } catch (e) { /* ignore */ }
  deferredInstallPrompt = null;
  installAppBtn?.classList.add('hidden');
}

// ── 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();
    await loadStations();
    setupEventListeners();
    ensureArtworkPointerFallback();
    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));
  }
}

// ── Service Worker registration (PWA) ────────────────────────────────────────

if ('serviceWorker' in navigator && import.meta.env.DEV) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.getRegistrations()
      .then((registrations) => Promise.all(registrations.map((reg) => reg.unregister())))
      .catch((err) => console.debug('ServiceWorker dev cleanup failed:', err));

    if ('caches' in window) {
      caches.keys()
        .then((keys) => Promise.all(keys.map((key) => caches.delete(key))))
        .catch((err) => console.debug('Cache dev cleanup failed:', err));
    }
  });
} else if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('sw.js')
      .then((reg) => console.log('ServiceWorker registered:', reg.scope))
      .catch((err) => console.debug('ServiceWorker registration failed:', err));
  });
}

window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault();
  deferredInstallPrompt = event;
  installAppBtn?.classList.remove('hidden');
});

window.addEventListener('appinstalled', () => {
  deferredInstallPrompt = null;
  installAppBtn?.classList.add('hidden');
});

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