// Vintage Stereo — tuner logic for the glass-front console stereo
// Loads stations from /stations.json, manages playback through an <audio>
// element, and drives the analog dial / VU meters / status panel.

(() => {
  'use strict';

  const els = {
    console:        document.getElementById('console'),
    dialRail:       document.getElementById('dialRail'),
    dialCursor:     document.getElementById('dialCursor'),
    dialFrequency:  document.getElementById('dialFrequency'),
    dialStation:    document.getElementById('dialStation'),
    vuLeft:         document.getElementById('vuLeft'),
    vuRight:        document.getElementById('vuRight'),
    powerBtn:       document.getElementById('powerBtn'),
    modeBtn:        document.getElementById('modeBtn'),
    modeName:       document.getElementById('modeName'),
    muteBtn:        document.getElementById('muteBtn'),
    prevBtn:        document.getElementById('prevBtn'),
    nextBtn:        document.getElementById('nextBtn'),
    volumeSlider:   document.getElementById('volumeSlider'),
    volumeReadout:  document.getElementById('volumeReadout'),
    presetLabel:    document.getElementById('presetLabel'),
    stationList:    document.getElementById('stationList'),
    player:         document.getElementById('player'),
    statusText:     document.getElementById('statusText'),
    signalText:     document.getElementById('signalText'),
    nowPlaying:     document.getElementById('nowPlaying'),
    streamInfo:     document.getElementById('streamInfo'),
  };

  const FILTER_MODES = [
    { name: 'ALL',     match: () => true },
    { name: 'AMBIENT', match: (s) => /ambient|space|lounge|chill|downtempo|nasa/i.test(s.genre + ' ' + s.name) },
    { name: 'ROCK',    match: (s) => /rock|indie|pop|folk|synth|wave|electronic|secret|beat/i.test(s.genre + ' ' + s.name) },
    { name: 'MIXED',   match: (s) => /paradise|eclectic|mix|indie|kexp|public/i.test(s.genre + ' ' + s.name) },
  ];

  const STATE = {
    stations: [],
    visibleStations: [],
    currentIndex: -1,
    power: false,
    muted: false,
    volume: 0.7,
    filterMode: 0,
  };

  // ====== Loading ======

  async function loadStations() {
    try {
      const res = await fetch('stations.json', { cache: 'no-cache' });
      if (!res.ok) throw new Error('HTTP ' + res.status);
      const data = await res.json();
      STATE.stations = (data.stations || [])
        .slice()
        .sort((a, b) => a.freq - b.freq);
      applyFilter();
      if (STATE.visibleStations.length > 0) {
        tuneTo(0);
      } else {
        setStatus('No stations in this mode');
        els.dialStation.textContent = 'NO STATIONS';
      }
      updatePrevNextDisabled();
    } catch (err) {
      setStatus('Error: ' + err.message);
      els.dialStation.textContent = 'OFFLINE';
      els.dialFrequency.textContent = '---.-';
    }
  }

  function applyFilter() {
    const mode = FILTER_MODES[STATE.filterMode];
    const filtered = STATE.stations.filter(mode.match);
    STATE.visibleStations = filtered.length > 0 ? filtered : STATE.stations.slice();
    renderStationList();
    updatePresetLabel();
    const cur = STATE.stations[STATE.currentIndex];
    if (!cur || !STATE.visibleStations.includes(cur)) {
      // Current station was filtered out — pick the visible station closest by frequency
      // to the current station's frequency (not always the first visible station).
      if (STATE.visibleStations.length > 0) {
        let bestRealIdx = STATE.stations.indexOf(STATE.visibleStations[0]);
        let bestDiff = Math.abs(STATE.stations[bestRealIdx].freq - (cur ? cur.freq : 0));
        for (let i = 1; i < STATE.visibleStations.length; i++) {
          const real = STATE.stations.indexOf(STATE.visibleStations[i]);
          const d = Math.abs(STATE.stations[real].freq - (cur ? cur.freq : 0));
          if (d < bestDiff) { bestDiff = d; bestRealIdx = real; }
        }
        if (bestRealIdx !== STATE.currentIndex) {
          STATE.currentIndex = bestRealIdx;
          updateDialFromStation();
          updateStationListSelection();
          // Station changed — restart playback to match the displayed selection.
          if (STATE.power) startStream();
        }
      }
    } else if (STATE.power) {
      // Current station is still in the filtered set, but MODE has changed — restart
      // playback so any per-mode audio-affecting state (volume, readyState) catches up.
      startStream();
    }
  }

  // ====== Rendering ======

  function renderStationList() {
    els.stationList.innerHTML = '';
    STATE.visibleStations.forEach((s) => {
      const realIdx = STATE.stations.indexOf(s);
      const li = document.createElement('li');
      li.setAttribute('role', 'option');
      li.dataset.index = String(realIdx);
      const freq = document.createElement('span');
      freq.className = 'station-freq';
      freq.textContent = s.freq.toFixed(1);
      const info = document.createElement('span');
      info.className = 'station-info';
      const name = document.createElement('span');
      name.className = 'station-name';
      name.textContent = s.name;
      const genre = document.createElement('span');
      genre.className = 'station-genre';
      genre.textContent = s.genre;
      info.appendChild(name);
      info.appendChild(genre);
      li.appendChild(freq);
      li.appendChild(info);
      li.addEventListener('click', () => {
        tuneTo(realIdx);
        // tuneTo() already restarts the stream if powered — no need to also play().
      });
      els.stationList.appendChild(li);
    });
  }

  function updateStationListSelection() {
    els.stationList.querySelectorAll('li').forEach((li) => {
      const idx = Number(li.dataset.index);
      li.setAttribute('aria-selected', idx === STATE.currentIndex ? 'true' : 'false');
    });
    const sel = els.stationList.querySelector('li[aria-selected="true"]');
    if (sel) sel.scrollIntoView({ block: 'nearest' });
  }

  function updatePresetLabel() {
    const total = STATE.visibleStations.length;
    const cur = total > 0 ? (visibleIndexOfCurrent() + 1) : 0;
    els.presetLabel.textContent = cur.toString().padStart(2, '0') + ' / ' + total.toString().padStart(2, '0');
  }

  function visibleIndexOfCurrent() {
    if (STATE.currentIndex < 0) return -1;
    const cur = STATE.stations[STATE.currentIndex];
    return STATE.visibleStations.indexOf(cur);
  }

  // ====== Tuning ======

  function updateDialFromStation() {
    if (STATE.currentIndex < 0 || STATE.stations.length === 0) return;
    const s = STATE.stations[STATE.currentIndex];
    const t = (s.freq - 88) / (105.4 - 88);
    const pct = Math.max(0, Math.min(1, t)) * 100;
    els.dialCursor.style.left = pct + '%';
    els.dialFrequency.textContent = s.freq.toFixed(1);
    els.dialStation.textContent = s.name.toUpperCase();
    els.nowPlaying.textContent = s.name + ' \u00b7 ' + s.genre;
    updatePresetLabel();
  }

  function tuneTo(index) {
    if (index < 0 || index >= STATE.stations.length) return;
    if (!STATE.visibleStations.includes(STATE.stations[index])) {
      // Defensive: caller asked for a filtered-out station — pick the closest visible
      // station by frequency instead of resetting the active filter.
      const targetFreq = STATE.stations[index].freq;
      let bestRealIdx = STATE.stations.indexOf(STATE.visibleStations[0]);
      let bestDiff = Math.abs(STATE.stations[bestRealIdx].freq - targetFreq);
      for (let i = 1; i < STATE.visibleStations.length; i++) {
        const real = STATE.stations.indexOf(STATE.visibleStations[i]);
        const d = Math.abs(STATE.stations[real].freq - targetFreq);
        if (d < bestDiff) { bestDiff = d; bestRealIdx = real; }
      }
      index = bestRealIdx;
    }
    STATE.currentIndex = index;
    updateDialFromStation();
    updateStationListSelection();
    updatePrevNextDisabled();
    if (STATE.power) startStream();
  }

  function tuneToVisibleIndex(vi) {
    if (vi < 0 || vi >= STATE.visibleStations.length) return;
    const target = STATE.visibleStations[vi];
    const realIdx = STATE.stations.indexOf(target);
    if (realIdx !== STATE.currentIndex) tuneTo(realIdx);
  }

  function tuneToFreq(freq) {
    if (STATE.visibleStations.length === 0) return;
    let bestRealIdx = STATE.stations.indexOf(STATE.visibleStations[0]);
    let bestDiff = Math.abs(STATE.stations[bestRealIdx].freq - freq);
    for (let i = 1; i < STATE.visibleStations.length; i++) {
      const real = STATE.stations.indexOf(STATE.visibleStations[i]);
      const d = Math.abs(STATE.stations[real].freq - freq);
      if (d < bestDiff) { bestDiff = d; bestRealIdx = real; }
    }
    if (bestRealIdx !== STATE.currentIndex) tuneTo(bestRealIdx);
  }

  // ====== Playback ======

  function startStream() {
    const s = STATE.stations[STATE.currentIndex];
    if (!s) return;
    const targetUrl = s.url;
    if (els.player.src !== targetUrl) {
      els.player.src = targetUrl;
      els.player.load();
    } else {
      // Same URL, but caller wants a fresh start — rewind and reload to flush
      // any buffered state from a previous mode/stream.
      try { els.player.currentTime = 0; } catch (_) { /* some streams reject */ }
      els.player.load();
    }
    const playPromise = els.player.play();
    if (playPromise && typeof playPromise.catch === 'function') {
      playPromise.catch((err) => {
        setStatus('Audio error: ' + err.name);
      });
    }
  }

  function stopStream() {
    try { els.player.pause(); } catch (_) { /* ignore */ }
    els.player.removeAttribute('src');
    els.player.load();
    els.console.dataset.streaming = 'false';
  }

  function play() {
    if (!STATE.power) return;
    startStream();
  }

  // ====== Power ======

  function setPower(on) {
    STATE.power = on;
    els.console.dataset.power = on ? 'on' : 'off';
    els.powerBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
    setStatus(on ? 'Power on' : 'Standby');
    setSignal(on ? 'Tuning' : 'Idle', on);
    if (on) startStream();
    else stopStream();
    updatePrevNextDisabled();
  }

  function updatePrevNextDisabled() {
    const visibleIdx = visibleIndexOfCurrent();
    const total = STATE.visibleStations.length;
    const canPrev = visibleIdx > 0;
    const canNext = visibleIdx >= 0 && visibleIdx < total - 1;
    els.prevBtn.disabled = !canPrev;
    els.nextBtn.disabled = !canNext;
  }

  // ====== Volume / Mute ======

  function applyVolume() {
    const v = STATE.muted ? 0 : STATE.volume;
    els.player.volume = v;
  }

  function toggleMute() {
    STATE.muted = !STATE.muted;
    els.muteBtn.setAttribute('aria-pressed', STATE.muted ? 'true' : 'false');
    applyVolume();
  }

  function setStatus(msg) {
    els.statusText.textContent = msg;
    if (!STATE.power) els.nowPlaying.textContent = 'Power: ' + msg.toLowerCase();
  }

  function setSignal(msg, on) {
    els.signalText.textContent = msg;
  }

  // ====== Mode (genre filter) ======

  function cycleMode() {
    STATE.filterMode = (STATE.filterMode + 1) % FILTER_MODES.length;
    applyFilter();
    const name = FILTER_MODES[STATE.filterMode].name;
    els.modeName.textContent = name;
    els.modeBtn.setAttribute('aria-label', 'Cycle genre mode. Currently ' + name + '.');
    setStatus('Mode: ' + name);
    updatePrevNextDisabled();
  }

  // ====== VU meter animation ======

  let vuAnimHandle = null;
  let leftEnergy = 0;
  let rightEnergy = 0;

  function animateVu() {
    if (!STATE.power) {
      els.vuLeft.style.transform = 'rotate(0deg)';
      els.vuRight.style.transform = 'rotate(0deg)';
      vuAnimHandle = requestAnimationFrame(animateVu);
      return;
    }

    if (els.player.paused || els.player.readyState < 2) {
      leftEnergy  = leftEnergy  * 0.85 + (Math.random() * 4 - 2) * 0.15;
      rightEnergy = rightEnergy * 0.85 + (Math.random() * 4 - 2) * 0.15;
    } else {
      const base = -8;
      const peak = Math.random() < 0.06 ? 32 : Math.random() * 16;
      const l = base + peak + (Math.random() - 0.5) * 5;
      const r = base + peak + (Math.random() - 0.5) * 5;
      leftEnergy  = leftEnergy  * 0.6 + l * 0.4;
      rightEnergy = rightEnergy * 0.6 + r * 0.4;
    }

    els.vuLeft.style.transform  = 'rotate(' + leftEnergy.toFixed(1) + 'deg)';
    els.vuRight.style.transform = 'rotate(' + rightEnergy.toFixed(1) + 'deg)';
    vuAnimHandle = requestAnimationFrame(animateVu);
  }

  // ====== Dial interaction ======

  let dragging = false;

  function railXToFreq(clientX) {
    const rect = els.dialRail.getBoundingClientRect();
    const x = Math.max(0, Math.min(rect.width, clientX - rect.left));
    const t = x / rect.width;
    return 88 + t * (105.4 - 88);
  }

  function onDialPointerDown(e) {
    dragging = true;
    els.dialRail.setPointerCapture(e.pointerId);
    tuneToFreq(railXToFreq(e.clientX));
  }

  function onDialPointerMove(e) {
    if (!dragging) return;
    tuneToFreq(railXToFreq(e.clientX));
  }

  function onDialPointerUp(e) {
    dragging = false;
    try { els.dialRail.releasePointerCapture(e.pointerId); } catch (_) { /* ignore */ }
  }

  function onDialWheel(e) {
    e.preventDefault();
    if (STATE.visibleStations.length === 0) return;
    const dir = e.deltaY > 0 ? 1 : -1;
    const vi = visibleIndexOfCurrent();
    tuneToVisibleIndex(Math.max(0, Math.min(STATE.visibleStations.length - 1, vi + dir)));
  }

  function onDialKey(e) {
    if (e.key === 'ArrowLeft') {
      e.preventDefault();
      const vi = visibleIndexOfCurrent();
      if (vi > 0) tuneToVisibleIndex(vi - 1);
    } else if (e.key === 'ArrowRight') {
      e.preventDefault();
      const vi = visibleIndexOfCurrent();
      if (vi >= 0 && vi < STATE.visibleStations.length - 1) tuneToVisibleIndex(vi + 1);
    } else if (e.key === ' ' || e.key === 'Enter') {
      e.preventDefault();
      toggleMute();
    }
  }

  // ====== Streaming indicator ======

  function updateStreamIndicator() {
    const streaming = STATE.power
      && !els.player.paused
      && els.player.readyState >= 2
      && els.player.error === null;
    els.console.dataset.streaming = streaming ? 'true' : 'false';
    if (STATE.power) {
      if (streaming) {
        const s = STATE.stations[STATE.currentIndex];
        els.streamInfo.textContent = s ? s.name : '';
        els.streamInfo.classList.add('live');
        setSignal('Streaming', true);
      } else if (els.player.error) {
        setSignal('No signal', false);
        els.streamInfo.classList.remove('live');
        els.streamInfo.textContent = '';
      } else {
        setSignal('Tuning', true);
        els.streamInfo.classList.remove('live');
      }
    } else {
      els.streamInfo.classList.remove('live');
      els.streamInfo.textContent = '';
    }
  }

  // ====== Wire up ======

  function init() {
    els.console.dataset.power = 'off';
    els.console.dataset.streaming = 'false';
    els.player.volume = STATE.volume;
    els.modeName.textContent = FILTER_MODES[STATE.filterMode].name;
    els.modeBtn.setAttribute('aria-label', 'Cycle genre mode. Currently ' + FILTER_MODES[STATE.filterMode].name + '.');

    els.player.addEventListener('playing', updateStreamIndicator);
    els.player.addEventListener('pause',   updateStreamIndicator);
    els.player.addEventListener('waiting', updateStreamIndicator);
    els.player.addEventListener('stalled', updateStreamIndicator);
    els.player.addEventListener('error',   () => {
      setSignal('No signal', false);
      els.streamInfo.classList.remove('live');
      els.streamInfo.textContent = 'stream error';
    });

    els.powerBtn.addEventListener('click', () => setPower(!STATE.power));
    els.muteBtn.addEventListener('click', toggleMute);
    els.modeBtn.addEventListener('click', cycleMode);

    els.prevBtn.addEventListener('click', () => {
      const vi = visibleIndexOfCurrent();
      if (vi > 0) tuneToVisibleIndex(vi - 1);
    });
    els.nextBtn.addEventListener('click', () => {
      const vi = visibleIndexOfCurrent();
      if (vi >= 0 && vi < STATE.visibleStations.length - 1) tuneToVisibleIndex(vi + 1);
    });

    els.volumeSlider.addEventListener('input', (e) => {
      const pct = Number(e.target.value);
      STATE.volume = pct / 100;
      els.volumeReadout.textContent = pct;
      if (STATE.muted && pct > 0) toggleMute();
      applyVolume();
    });

    els.dialRail.addEventListener('pointerdown', onDialPointerDown);
    els.dialRail.addEventListener('pointermove', onDialPointerMove);
    els.dialRail.addEventListener('pointerup', onDialPointerUp);
    els.dialRail.addEventListener('pointercancel', onDialPointerUp);
    els.dialRail.addEventListener('wheel', onDialWheel, { passive: false });
    els.dialRail.addEventListener('keydown', onDialKey);

    setInterval(updateStreamIndicator, 1500);

    vuAnimHandle = requestAnimationFrame(animateVu);
    loadStations();
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();
