// RangerHQ Tuner — History, Favourites, and 4-button search URLs.
//
// Mirrors rangerhq-radio's inc/history.php exactly (verified 2026-06-08).
// Differences from the WP plugin:
//   • Chrome has one user per profile, so we use chrome.storage.local,
//     not per-user usermeta.
//   • All keys are namespaced under "tuner.*" so clearAll() is trivial.
//
// Entry shape (mirror WP plugin):
//   { artist: string, title: string, station: string, stationId: string, at: number }
//
// Rules (mirror WP plugin):
//   • Cap at historyCap entries (default 500), FIFO.
//   • Dedup against the LAST entry only (sig = lower(artist|title|stationId)).
//   • Skip entries where artist === "(unknown)" (SomaFM dead-air placeholder).

export const HISTORY_KEY  = 'tuner.history';
export const FAVS_KEY     = 'tuner.favourites';
export const CAP_KEY      = 'tuner.historyCap';
export const DEFAULT_CAP  = 500;
export const MIN_CAP      = 50;
export const MAX_CAP      = 500;

/* ---------- Safe storage accessor ----------
 * chrome.storage is not guaranteed in every extension context (e.g. some
 * older Chrome versions hide it from offscreen documents). Anywhere it
 * isn't present, our readers return empty defaults and writers no-op
 * instead of throwing. This way history.js is safe to import from any
 * surface — UI pages get real storage, edge contexts degrade silently.
 */
function storage() {
  if (typeof chrome === 'undefined') return null;
  if (!chrome.storage || !chrome.storage.local) return null;
  return chrome.storage.local;
}

/* ---------- Readers ---------- */

export async function getHistory() {
  const s = storage();
  if (!s) return [];
  const o = await s.get(HISTORY_KEY);
  return Array.isArray(o[HISTORY_KEY]) ? o[HISTORY_KEY] : [];
}

export async function getFavourites() {
  const s = storage();
  if (!s) return [];
  const o = await s.get(FAVS_KEY);
  return Array.isArray(o[FAVS_KEY]) ? o[FAVS_KEY] : [];
}

export async function getHistoryCap() {
  const s = storage();
  if (!s) return DEFAULT_CAP;
  const o = await s.get(CAP_KEY);
  const n = o[CAP_KEY];
  return (typeof n === 'number' && n >= MIN_CAP && n <= MAX_CAP) ? n : DEFAULT_CAP;
}

/* ---------- Sanitisation + dedup ---------- */

function sanitizeEntry(entry) {
  if (!entry || typeof entry !== 'object') return null;
  const artist    = String(entry.artist    || '').trim().slice(0, 200);
  const title     = String(entry.title     || '').trim().slice(0, 200);
  const station   = String(entry.station   || '').trim().slice(0, 100);
  const stationId = String(entry.stationId || '').trim().slice(0, 100);
  if (!artist || !title) return null;
  if (artist.toLowerCase() === '(unknown)') return null;
  return { artist, title, station, stationId, at: Date.now() };
}

export function entrySignature(e) {
  return [
    (e.artist    || '').toLowerCase().trim(),
    (e.title     || '').toLowerCase().trim(),
    (e.stationId || '').toLowerCase().trim(),
  ].join('|');
}

/* ---------- Mutations ---------- */

/** Append a track to history (deduped + capped). Returns true if appended. */
export async function logTrack(entry) {
  const s = storage();
  if (!s) return false;

  const clean = sanitizeEntry(entry);
  if (!clean) return false;

  const history = await getHistory();
  if (history.length > 0) {
    const lastSig = entrySignature(history[history.length - 1]);
    if (entrySignature(clean) === lastSig) return false;
  }

  history.push(clean);
  const cap = await getHistoryCap();
  if (history.length > cap) history.splice(0, history.length - cap);
  await s.set({ [HISTORY_KEY]: history });
  return true;
}

/** Toggle favourite. Returns new state: true = now favourited, false = removed. */
export async function toggleFavourite(entry) {
  const s = storage();
  if (!s) return false;

  const clean = sanitizeEntry({ ...entry, at: 0 }); // at gets reset
  if (!clean) return false;
  const sig = entrySignature(clean);

  const favs = await getFavourites();
  const found = favs.findIndex(f => entrySignature(f) === sig);
  if (found >= 0) {
    favs.splice(found, 1);
    await s.set({ [FAVS_KEY]: favs });
    return false;
  }
  clean.at = Date.now();
  favs.push(clean);
  await s.set({ [FAVS_KEY]: favs });
  return true;
}

export async function isFavourited(entry) {
  const clean = sanitizeEntry({ ...entry, at: 0 });
  if (!clean) return false;
  const sig = entrySignature(clean);
  const favs = await getFavourites();
  return favs.some(f => entrySignature(f) === sig);
}

export async function clearHistory() {
  const s = storage();
  if (!s) return;
  await s.remove(HISTORY_KEY);
}

export async function clearFavourites() {
  const s = storage();
  if (!s) return;
  await s.remove(FAVS_KEY);
}

/** Wipe every `tuner.*` key. Used by the "Clear EVERYTHING" button. */
export async function clearAll() {
  const s = storage();
  if (!s) return;
  const all = await s.get(null);
  const keys = Object.keys(all).filter(k => k.startsWith('tuner.'));
  if (keys.length) await s.remove(keys);
}

export async function setHistoryCap(n) {
  const s = storage();
  if (!s) return DEFAULT_CAP;
  const cap = Math.max(MIN_CAP, Math.min(MAX_CAP, Math.round(Number(n) || DEFAULT_CAP)));
  await s.set({ [CAP_KEY]: cap });
  const history = await getHistory();
  if (history.length > cap) {
    await s.set({ [HISTORY_KEY]: history.slice(-cap) });
  }
  return cap;
}

/* ---------- 4-button search URLs ---------- */
// Mirrors WP plugin lines 126-129 verbatim. NO auth, NO API key, NO quota.
// Public search URLs that open in a new tab — user picks what to listen to.

export function searchUrls(artist, title) {
  const enc = encodeURIComponent(`${artist || ''} ${title || ''}`.trim());
  return {
    spotify:  `https://open.spotify.com/search/${enc}`,
    youtube:  `https://www.youtube.com/results?search_query=${enc}`,
    apple:    `https://music.apple.com/search?term=${enc}`,
    bandcamp: `https://bandcamp.com/search?q=${enc}`,
  };
}

/* ---------- Display helpers ---------- */

export function formatRelativeTime(ts) {
  const diffSec = Math.floor((Date.now() - ts) / 1000);
  if (diffSec < 60)         return 'just now';
  if (diffSec < 3600)       return `${Math.floor(diffSec / 60)}m ago`;
  if (diffSec < 86400)      return `${Math.floor(diffSec / 3600)}h ago`;
  if (diffSec < 604800)     return `${Math.floor(diffSec / 86400)}d ago`;
  return new Date(ts).toLocaleDateString();
}
