"use client";

import { useCallback, useEffect, useState } from "react";

// Server-backed (needs a logged-in customer, unlike the cart which works
// for guests via localStorage — a wishlist tied to nothing would just
// evaporate on the next visit, which defeats the point) — so this can't
// reuse cart.ts's useSyncExternalStore-over-localStorage pattern. Instead:
// a plain fetch on mount + a custom window event so every WishlistButton/
// the Navbar badge on the page stays in sync after any one of them toggles
// an item, without a shared cache library.
const WISHLIST_EVENT = "ep-wishlist-updated";

type WishlistItem = { id: number; productId: number; variant: string; purchasedAt: string | null };

let cachedItems: WishlistItem[] | null = null;

async function fetchWishlist(): Promise<WishlistItem[]> {
  const res = await fetch("/api/account/wishlist", { cache: "no-store" });
  if (!res.ok) return [];
  const data: { items?: WishlistItem[] } = await res.json();
  return data.items ?? [];
}

function broadcast() {
  window.dispatchEvent(new Event(WISHLIST_EVENT));
}

export function useWishlist() {
  const [items, setItems] = useState<WishlistItem[]>(cachedItems ?? []);
  const [loading, setLoading] = useState(cachedItems === null);

  const load = useCallback(async () => {
    const fresh = await fetchWishlist();
    cachedItems = fresh;
    setItems(fresh);
    setLoading(false);
  }, []);

  useEffect(() => {
    load();
    window.addEventListener(WISHLIST_EVENT, load);
    return () => window.removeEventListener(WISHLIST_EVENT, load);
  }, [load]);

  const isWishlisted = useCallback(
    (productId: number, variant = "") => items.some((i) => i.productId === productId && i.variant === variant),
    [items],
  );

  // Optimistic: flips the LOCAL list immediately (this component's own
  // `items`/`cachedItems`), reconciles with the server response (or
  // reverts on failure) rather than waiting for the round trip — same
  // "feels instant" reasoning as AddToCartButton. Crucially, `broadcast()`
  // (which tells every OTHER useWishlist() instance — e.g. the Navbar
  // badge — to refetch) only fires AFTER the request settles, never
  // before: broadcasting immediately after the optimistic update used to
  // race the POST itself — another instance's resulting `load()` could
  // hit the server before the toggle had actually been persisted there,
  // fetch the pre-toggle list, and then never get told to refetch again,
  // leaving e.g. the Navbar count permanently one behind the real value.
  const toggle = useCallback(async (productId: number, variant = "") => {
    const wasWishlisted = cachedItems?.some((i) => i.productId === productId && i.variant === variant) ?? false;
    const optimistic = wasWishlisted
      ? (cachedItems ?? []).filter((i) => !(i.productId === productId && i.variant === variant))
      : [...(cachedItems ?? []), { id: -1, productId, variant, purchasedAt: null }];
    cachedItems = optimistic;
    setItems(optimistic);

    try {
      const res = await fetch("/api/account/wishlist", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ productId, variant }),
      });
      if (res.status === 401) {
        cachedItems = wasWishlisted ? [...optimistic, { id: -1, productId, variant, purchasedAt: null }] : optimistic.filter((i) => i.productId !== productId);
        setItems(cachedItems);
        return { ok: false as const, unauthorized: true as const };
      }
      if (!res.ok) throw new Error("request failed");
      const data: { ok: boolean; wishlisted?: boolean } = await res.json();
      if (!data.ok) throw new Error("toggle failed");
      // Reconcile with the server's own id (needed for a later toggle-off
      // that hasn't refetched the list yet) rather than trusting the
      // optimistic placeholder id (-1) forever.
      await load();
      broadcast();
      return { ok: true as const, wishlisted: data.wishlisted ?? !wasWishlisted };
    } catch {
      cachedItems = wasWishlisted ? [...optimistic, { id: -1, productId, variant, purchasedAt: null }] : optimistic.filter((i) => i.productId !== productId);
      setItems(cachedItems);
      return { ok: false as const };
    }
  }, []);

  return { items, loading, isWishlisted, toggle, count: items.length };
}
