"use client";

/**
 * Browser-local persistence for favorites + recently played (Phase 4).
 * No accounts: everything stays in localStorage on the user's device.
 * All access is guarded — private mode / SSR never throws.
 */

import { MAX_RECENTLY_PLAYED, STORAGE_KEYS } from "@/lib/site";

export interface RecentEntry {
  slug: string;
  at: number;
}

/* ---------- pure list helpers (testable without a browser) ---------- */

export function toggleSlug(list: string[], slug: string): string[] {
  return list.includes(slug)
    ? list.filter((s) => s !== slug)
    : [...list, slug];
}

/** Move slug to front, stamp time, cap length. Most recent first. */
export function pushRecent(
  list: RecentEntry[],
  slug: string,
  now: number = Date.now(),
  max: number = MAX_RECENTLY_PLAYED,
): RecentEntry[] {
  return [{ slug, at: now }, ...list.filter((e) => e.slug !== slug)].slice(
    0,
    Math.max(1, max),
  );
}

/* ---------- storage access ---------- */

function readRaw(key: string): unknown {
  try {
    if (typeof window === "undefined") return null;
    const raw = window.localStorage.getItem(key);
    return raw ? (JSON.parse(raw) as unknown) : null;
  } catch {
    return null;
  }
}

function writeRaw(key: string, value: unknown): void {
  try {
    if (typeof window === "undefined") return;
    window.localStorage.setItem(key, JSON.stringify(value));
  } catch {
    // Storage unavailable (private mode, quota) — feature degrades silently.
  }
  // Same-tab listeners: the native "storage" event only fires cross-tab.
  try {
    window.dispatchEvent(new Event(`pixelarcade:${key}`));
  } catch {
    /* noop */
  }
}

function isStringArray(value: unknown): value is string[] {
  return (
    Array.isArray(value) && value.every((v) => typeof v === "string")
  );
}

function isRecentArray(value: unknown): value is RecentEntry[] {
  return (
    Array.isArray(value) &&
    value.every(
      (v) =>
        typeof v === "object" &&
        v !== null &&
        typeof (v as RecentEntry).slug === "string",
    )
  );
}

export function getFavorites(): string[] {
  const raw = readRaw(STORAGE_KEYS.favorites);
  return isStringArray(raw) ? [...new Set(raw)] : [];
}

/**
 * Cached snapshot: stable reference while the stored value is unchanged,
 * so subscribers never re-render in a loop.
 */
let favoritesCache: string[] | null = null;

export function getFavoritesSnapshot(): string[] {
  const current = getFavorites();
  if (
    favoritesCache !== null &&
    favoritesCache.length === current.length &&
    favoritesCache.every((s, i) => s === current[i])
  ) {
    return favoritesCache;
  }
  favoritesCache = current;
  return current;
}

export function setFavorite(slug: string, favorite: boolean): string[] {
  const next = favorite
    ? [...new Set([...getFavorites(), slug])]
    : getFavorites().filter((s) => s !== slug);
  favoritesCache = next;
  writeRaw(STORAGE_KEYS.favorites, next);
  return next;
}

export function toggleFavorite(slug: string): string[] {
  return setFavorite(slug, !getFavorites().includes(slug));
}

export function getRecentlyPlayed(): RecentEntry[] {
  const raw = readRaw(STORAGE_KEYS.recentlyPlayed);
  if (!isRecentArray(raw)) return [];
  const seen = new Set<string>();
  return raw
    .filter((e) => {
      if (seen.has(e.slug)) return false;
      seen.add(e.slug);
      return true;
    })
    .slice(0, MAX_RECENTLY_PLAYED);
}

export function recordRecentlyPlayed(slug: string): RecentEntry[] {
  const next = pushRecent(getRecentlyPlayed(), slug);
  recentCache = next;
  writeRaw(STORAGE_KEYS.recentlyPlayed, next);
  return next;
}

export function clearRecentlyPlayed(): void {
  recentCache = [];
  writeRaw(STORAGE_KEYS.recentlyPlayed, []);
}

/** Cached snapshot with a stable reference while history is unchanged. */
let recentCache: RecentEntry[] | null = null;

export function getRecentlyPlayedSnapshot(): RecentEntry[] {
  const current = getRecentlyPlayed();
  if (
    recentCache !== null &&
    recentCache.length === current.length &&
    recentCache.every(
      (e, i) => e.slug === current[i].slug && e.at === current[i].at,
    )
  ) {
    return recentCache;
  }
  recentCache = current;
  return current;
}

/** Subscribe to favorite changes (cross-tab + same-tab). Returns cleanup. */
export function onFavoritesChange(callback: () => void): () => void {
  const eventName = `pixelarcade:${STORAGE_KEYS.favorites}`;
  window.addEventListener("storage", callback);
  window.addEventListener(eventName, callback);
  return () => {
    window.removeEventListener("storage", callback);
    window.removeEventListener(eventName, callback);
  };
}

/** Subscribe to history changes. Returns cleanup. */
export function onRecentlyPlayedChange(callback: () => void): () => void {
  const eventName = `pixelarcade:${STORAGE_KEYS.recentlyPlayed}`;
  window.addEventListener("storage", callback);
  window.addEventListener(eventName, callback);
  return () => {
    window.removeEventListener("storage", callback);
    window.removeEventListener(eventName, callback);
  };
}
