"use client";
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { subscribeQueueRemove } from "./watchQueueEvents";

const STORAGE_KEY = "pinkudex.watch-queue";

type Ctx = {
  ids: number[];
  has: (id: number) => boolean;
  add: (id: number) => void;
  addMany: (ids: number[]) => void;
  remove: (id: number) => void;
  removeMany: (ids: number[]) => void;
  toggle: (id: number) => void;
  clear: () => void;
};

const WatchQueueCtx = createContext<Ctx | null>(null);

function readStorage(): number[] {
  if (typeof window === "undefined") return [];
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return [];
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    return parsed.filter((n): n is number => typeof n === "number");
  } catch {
    return [];
  }
}

export function WatchQueueProvider({ children }: { children: React.ReactNode }) {
  const [ids, setIds] = useState<number[]>([]);
  const [hydrated, setHydrated] = useState(false);

  // Hydrate from localStorage. SSR renders an empty queue; this fills it
  // on mount so the server-rendered HTML always matches initial paint.
  useEffect(() => {
    setIds(readStorage());
    setHydrated(true);
  }, []);

  // Persist on change (after hydration, so we don't blow away storage with
  // the empty-array initial state).
  useEffect(() => {
    if (!hydrated) return;
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(ids));
    } catch {}
  }, [ids, hydrated]);

  // Cross-tab sync.
  useEffect(() => {
    function onStorage(e: StorageEvent) {
      if (e.key !== STORAGE_KEY) return;
      setIds(readStorage());
    }
    window.addEventListener("storage", onStorage);
    return () => window.removeEventListener("storage", onStorage);
  }, []);

  // External signal — covers fire this after their watched flag flips true.
  useEffect(() => subscribeQueueRemove((detail) => {
    const drop = new Set(detail);
    setIds((cur) => cur.filter((id) => !drop.has(id)));
  }), []);

  const add = useCallback((id: number) => {
    setIds((cur) => (cur.includes(id) ? cur : [...cur, id]));
  }, []);
  const addMany = useCallback((newIds: number[]) => {
    setIds((cur) => {
      const have = new Set(cur);
      const merged = [...cur];
      for (const id of newIds) if (!have.has(id)) { merged.push(id); have.add(id); }
      return merged;
    });
  }, []);
  const remove = useCallback((id: number) => {
    setIds((cur) => cur.filter((x) => x !== id));
  }, []);
  const removeMany = useCallback((dropIds: number[]) => {
    const drop = new Set(dropIds);
    setIds((cur) => cur.filter((id) => !drop.has(id)));
  }, []);
  const toggle = useCallback((id: number) => {
    setIds((cur) => (cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]));
  }, []);
  const clear = useCallback(() => setIds([]), []);

  const value = useMemo<Ctx>(() => ({
    ids,
    has: (id) => ids.includes(id),
    add,
    addMany,
    remove,
    removeMany,
    toggle,
    clear,
  }), [ids, add, addMany, remove, removeMany, toggle, clear]);

  return <WatchQueueCtx.Provider value={value}>{children}</WatchQueueCtx.Provider>;
}

export function useWatchQueue(): Ctx {
  const ctx = useContext(WatchQueueCtx);
  if (!ctx) throw new Error("useWatchQueue must be used within WatchQueueProvider");
  return ctx;
}
