// RangerHQ Tuner — Offscreen audio host.
// Owns the <audio> element and the 25-second metadata polling loop.
// MV3 service workers can't host audio (they get killed when idle),
// so all live media + polling lives here.

import { TARGETS, TYPES } from '../lib/messages.js';
import { getSource } from '../sources/index.js';
// NOTE: we intentionally do NOT import history.js here. chrome.storage is
// not reliably available in offscreen documents across Chrome versions,
// so we route writes through the service worker via LOG_TRACK_REQUEST.

const audio = document.getElementById('player');

// Poll interval matches rangerhq-radio's WP plugin — battle-tested at 25s.
const POLL_MS = 25 * 1000;

let pollTimer       = null;
let currentStation  = null; // { id, sourceId, name, ... }
let lastSeenSig     = '';   // dedupe metadata broadcasts within a session

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.target !== TARGETS.OFFSCREEN) return;

  switch (msg.type) {
    case TYPES.PLAY:
      currentStation = msg.station || null;
      audio.src = msg.streamUrl;
      // Kick off metadata polling IMMEDIATELY — in parallel with audio
      // buffering. The first poll fires now and broadcasts METADATA_UPDATED
      // as soon as SomaFM responds (~500ms), instead of waiting 2-5s for
      // the audio HTTP stream to fill its buffer.
      startPolling();
      audio.play()
        .then(() => sendResponse({ ok: true }))
        .catch(err => {
          sendResponse({ ok: false, error: err.message });
          stopPolling();
        });
      return true; // async sendResponse

    case TYPES.PAUSE:
      audio.pause();
      stopPolling();
      sendResponse({ ok: true });
      break;

    case TYPES.SET_VOLUME:
      audio.volume = Math.max(0, Math.min(1, msg.volume));
      sendResponse({ ok: true });
      break;

    case TYPES.GET_STATE:
      sendResponse({
        ok: true,
        paused:     audio.paused,
        currentSrc: audio.currentSrc,
        volume:     audio.volume,
      });
      break;
  }
});

/* ---------- Audio events ---------- */

audio.addEventListener('playing', () => broadcast({ type: TYPES.STATE_CHANGED, state: 'playing' }));
audio.addEventListener('pause',   () => broadcast({ type: TYPES.STATE_CHANGED, state: 'paused' }));
audio.addEventListener('waiting', () => broadcast({ type: TYPES.STATE_CHANGED, state: 'buffering' }));
audio.addEventListener('error',   () => {
  stopPolling();
  broadcast({ type: TYPES.ERROR, code: audio.error?.code });
});

function broadcast(payload) {
  chrome.runtime.sendMessage({ target: TARGETS.POPUP, ...payload }).catch(() => {});
}

/* ---------- Metadata polling + history logging ---------- */

function startPolling() {
  stopPolling();
  pollNow(); // fire immediately so UI gets first metadata fast
  pollTimer = setInterval(pollNow, POLL_MS);
}

function stopPolling() {
  if (pollTimer) clearInterval(pollTimer);
  pollTimer = null;
  lastSeenSig = '';
}

async function pollNow() {
  if (!currentStation) return;
  const source = getSource(currentStation.sourceId);
  if (!source) return;

  let np;
  try {
    np = await source.getNowPlaying(currentStation);
  } catch {
    return;
  }
  if (!np || !np.title) return;

  // Broadcast every poll where we have metadata, so the UI's track line
  // is always current (even if the song hasn't changed, the popup might
  // have been opened fresh and needs to render).
  broadcast({ type: TYPES.METADATA_UPDATED, nowPlaying: np, station: currentStation });

  // Log to history — but only when the (artist|title) changed since we
  // last saw it within this play-session. The history.js layer also
  // dedups against the persisted last entry, so this is belt + braces.
  const sig = `${(np.artist || '').toLowerCase().trim()}|${(np.title || '').toLowerCase().trim()}`;
  if (sig === lastSeenSig) return;
  lastSeenSig = sig;

  // Ask the service worker to do the actual storage write. The SW will
  // broadcast TRACK_LOGGED back to the UI surfaces on success.
  chrome.runtime.sendMessage({
    target: TARGETS.SW,
    type:   TYPES.LOG_TRACK_REQUEST,
    entry: {
      artist:    np.artist,
      title:     np.title,
      station:   currentStation.name,
      stationId: currentStation.id,
    },
  }).catch(() => { /* SW asleep is fine — next poll will retry */ });
}
