// RangerHQ Tuner — Popup controller.
// The popup is a dumb view. State of truth lives in chrome.storage.local
// and the offscreen <audio> element. We just render and dispatch.

import { TARGETS, TYPES } from '../lib/messages.js';
import { listAllStations, getSource } from '../sources/index.js';
import { THEME_KEY, THEME_DEFAULT, initTheme, applyTheme } from '../lib/theme.js';

const els = {
  statePill:  document.getElementById('state-pill'),
  npStation:  document.getElementById('np-station'),
  npTrack:    document.getElementById('np-track'),
  btnPlay:    document.getElementById('btn-play'),
  volume:     document.getElementById('volume'),
  search:     document.getElementById('search'),
  list:       document.getElementById('station-list'),
  navOpenTab: document.getElementById('nav-open-tab'),
  navHistory: document.getElementById('nav-history'),
  navOptions: document.getElementById('nav-options'),
};

const NEWTAB_URL = chrome.runtime.getURL('src/newtab/newtab.html');

const STORAGE_KEYS = {
  stations:    'tuner.stationsCache',
  cachedAt:    'tuner.stationsCachedAt',
  currentId:   'tuner.currentStationId',
  volume:      'tuner.volume',
  isPlaying:   'tuner.isPlaying',
};

const CATALOGUE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours

let stations = [];                  // full list
let currentStation = null;          // last picked
let playing = false;
let lastQuery = '';

init().catch(err => {
  console.error('Tuner popup init failed:', err);
  setState('error');
  els.npTrack.textContent = `Init failed: ${err.message}`;
});

async function init() {
  // 0. Apply theme preference (v0.5.0) before anything paints, to avoid
  // a brief dark flash if the user has picked light.
  await initTheme();

  // 1. Hydrate UI from cached state so the popup renders fast.
  const stored = await chrome.storage.local.get(Object.values(STORAGE_KEYS));
  if (typeof stored[STORAGE_KEYS.volume] === 'number') {
    els.volume.value = String(Math.round(stored[STORAGE_KEYS.volume] * 100));
  }
  playing = !!stored[STORAGE_KEYS.isPlaying];
  setState(playing ? 'playing' : 'idle');
  reflectPlayButton();

  // 2. Load stations — from cache if fresh, else fetch.
  const cached = stored[STORAGE_KEYS.stations];
  const cachedAt = stored[STORAGE_KEYS.cachedAt];
  const fresh = cached && cachedAt && (Date.now() - cachedAt < CATALOGUE_TTL_MS);

  if (fresh) {
    stations = cached;
  } else {
    stations = await listAllStations();
    await chrome.storage.local.set({
      [STORAGE_KEYS.stations]: stations,
      [STORAGE_KEYS.cachedAt]: Date.now(),
    });
  }

  // 3. Restore current station selection.
  const currentId = stored[STORAGE_KEYS.currentId];
  currentStation = stations.find(s => s.id === currentId) || null;
  reflectFirstRunHint();
  renderNowPlaying();

  // 4. Render station list.
  renderStations();

  // 5. Wire up controls.
  els.btnPlay.addEventListener('click', onPlayToggle);
  els.volume.addEventListener('input', onVolume);
  els.search.addEventListener('input', () => {
    lastQuery = els.search.value.trim().toLowerCase();
    renderStations();
  });

  // Nav buttons — popup closes automatically when a tab opens, which is
  // the expected UX. No popup.close() call needed.
  els.navOpenTab.addEventListener('click', () => {
    chrome.tabs.create({ url: NEWTAB_URL });
  });
  els.navHistory.addEventListener('click', () => {
    chrome.tabs.create({ url: `${NEWTAB_URL}#history` });
  });
  els.navOptions.addEventListener('click', () => {
    chrome.runtime.openOptionsPage();
  });

  // 6. Listen for state changes broadcast from offscreen.
  chrome.runtime.onMessage.addListener((msg) => {
    if (msg.target !== TARGETS.POPUP) return;
    if (msg.type === TYPES.STATE_CHANGED) {
      setState(msg.state);
      playing = (msg.state === 'playing');
      reflectPlayButton();
      chrome.storage.local.set({ [STORAGE_KEYS.isPlaying]: playing });
    } else if (msg.type === TYPES.ERROR) {
      setState('error');
      els.npTrack.textContent = `Stream error (code ${msg.code ?? '?'})`;
    } else if (msg.type === TYPES.METADATA_UPDATED) {
      if (msg.nowPlaying) {
        els.npTrack.textContent = formatNowPlaying(msg.nowPlaying);
      }
    }
  });

  // 7. Cross-surface sync — if the New Tab Page (or another popup
  // instance) picks a station, mirror it here without a reload.
  chrome.storage.onChanged.addListener((changes, area) => {
    if (area !== 'local') return;

    if (changes[THEME_KEY]) {
      applyTheme(changes[THEME_KEY].newValue || THEME_DEFAULT);
    }

    if (changes[STORAGE_KEYS.currentId] && stations.length) {
      const newId = changes[STORAGE_KEYS.currentId].newValue;
      currentStation = stations.find(s => s.id === newId) || null;
      els.btnPlay.disabled = !currentStation;
      reflectFirstRunHint();
      renderNowPlaying();
      renderStations();
    }

    if (changes[STORAGE_KEYS.isPlaying]) {
      playing = !!changes[STORAGE_KEYS.isPlaying].newValue;
      reflectPlayButton();
    }
  });

  // 8. Enable the play button if we have a station picked.
  els.btnPlay.disabled = !currentStation;
}

function renderStations() {
  if (!stations.length) {
    els.list.innerHTML = '<li class="station-empty">No stations available.</li>';
    return;
  }
  const q = lastQuery;
  const filtered = q
    ? stations.filter(s =>
        s.name.toLowerCase().includes(q) ||
        s.genre.toLowerCase().includes(q) ||
        s.description.toLowerCase().includes(q))
    : stations;

  if (!filtered.length) {
    els.list.innerHTML = '<li class="station-empty">No matches.</li>';
    return;
  }

  els.list.innerHTML = '';
  for (const s of filtered) {
    const li = document.createElement('li');
    li.className = 'station-row';
    if (currentStation && currentStation.id === s.id) li.classList.add('is-active');
    li.setAttribute('role', 'option');
    li.dataset.id = s.id;

    const img = document.createElement('img');
    img.className = 'station-art';
    img.alt = '';
    img.src = s.artworkUrl || '';
    img.onerror = () => { img.style.visibility = 'hidden'; };

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

    const name = document.createElement('div');
    name.className = 'station-name';
    name.textContent = s.name;

    const genre = document.createElement('div');
    genre.className = 'station-genre';
    genre.textContent = s.genre || s.description.slice(0, 60);

    meta.appendChild(name);
    meta.appendChild(genre);
    li.appendChild(img);
    li.appendChild(meta);

    li.addEventListener('click', () => onPickStation(s));

    els.list.appendChild(li);
  }
}

function renderNowPlaying() {
  if (currentStation) {
    els.npStation.textContent = currentStation.name;
    els.npTrack.textContent = playing ? 'Now playing — fetching metadata…' : 'Ready to play';
  } else {
    els.npStation.textContent = '—';
    els.npTrack.textContent = 'Pick a station to begin';
  }
}

async function onPickStation(station) {
  currentStation = station;
  els.btnPlay.disabled = false;
  reflectFirstRunHint();
  await chrome.storage.local.set({ [STORAGE_KEYS.currentId]: station.id });
  renderNowPlaying();
  renderStations();
  // User explicitly picked → start playing.
  await playCurrent();
}

async function onPlayToggle() {
  if (!currentStation) return;
  if (playing) {
    await sendToSW({ type: TYPES.PAUSE });
  } else {
    await playCurrent();
  }
}

async function playCurrent() {
  if (!currentStation) return;
  setState('buffering');
  try {
    const source = getSource(currentStation.sourceId);
    if (!source) throw new Error(`Unknown source ${currentStation.sourceId}`);
    const streamUrl = await source.resolveStreamUrl(currentStation);
    // Push current volume first so the first frame plays at the right level.
    await sendToSW({ type: TYPES.SET_VOLUME, volume: Number(els.volume.value) / 100 });
    // Pass a stripped station object — offscreen needs id/sourceId/name to
    // poll metadata + label history entries; the rest is UI-only.
    const stationLite = {
      id:       currentStation.id,
      sourceId: currentStation.sourceId,
      name:     currentStation.name,
    };
    const resp = await sendToSW({ type: TYPES.PLAY, streamUrl, station: stationLite });
    if (!resp?.ok) throw new Error(resp?.error || 'PLAY failed');
    // The offscreen poll loop will broadcast METADATA_UPDATED within ~1s,
    // so we don't fire a separate one-shot fetch from here any more.
  } catch (err) {
    console.error(err);
    setState('error');
    els.npTrack.textContent = `Couldn't play: ${err.message}`;
  }
}

async function onVolume() {
  const v = Number(els.volume.value) / 100;
  await chrome.storage.local.set({ [STORAGE_KEYS.volume]: v });
  await sendToSW({ type: TYPES.SET_VOLUME, volume: v });
}

function reflectPlayButton() {
  els.btnPlay.setAttribute('aria-pressed', playing ? 'true' : 'false');
  els.btnPlay.textContent = playing ? '❚❚ Pause' : '▶ Play';
}

// v0.4.0 first-run UX hint — body class drives the pulse + arrow CSS in
// popup.css. Active when no station has been picked yet; cleared the
// moment a station is selected (here or in another surface via storage
// sync).
function reflectFirstRunHint() {
  document.body.classList.toggle('is-first-run', !currentStation);
}

function setState(state) {
  els.statePill.dataset.state = state;
  els.statePill.textContent = state;
}

function formatNowPlaying(np) {
  const left = np.artist ? `${np.artist} — ` : '';
  return `${left}${np.title}`;
}

function sendToSW(msg) {
  return chrome.runtime.sendMessage({ target: TARGETS.SW, ...msg });
}
