'use client';

import {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useState,
  useSyncExternalStore,
  type ReactNode,
} from 'react';
import { useAuthHint } from '@/lib/hooks/useAuthHint';
import { usePersonalization } from '@/lib/hooks/usePersonalization';
import {
  addToAnonWishlist,
  getAnonWishlistIds,
  removeFromAnonWishlist,
  STORAGE_KEY,
} from './anonWishlistStore';
import { getQueuedWishlistState, replayWishlistQueue } from '@/lib/offline/wishlistQueue';
import { captureCaught } from '@/lib/observability';
import { useEffect } from 'react';
import { useHydrated } from '@/lib/hooks/useHydrated';

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface WishlistContextValue {
  wishlistedDealIds: Set<string>;
  favoritedVendorIds: Set<string>;
  isAuthenticated: boolean;
  /** True until hydrated; on auth path also until first personalization query succeeds. */
  transitioning: boolean;
  toggleDeal: (dealId: string, saved: boolean) => void;
  toggleVendor: (vendorId: string, saved: boolean) => void;
}

export const WishlistContext = createContext<WishlistContextValue | null>(null);

// ---------------------------------------------------------------------------
// Provider
// ---------------------------------------------------------------------------

export function WishlistProvider({
  children,
  initialDealIds = [],
  initialVendorIds = [],
  isAuthenticated: isAuthenticatedProp,
}: {
  children: ReactNode;
  initialDealIds?: string[];
  initialVendorIds?: string[];
  /** Explicit auth override. When omitted, derived from useAuthHint(). */
  isAuthenticated?: boolean;
}) {
  const { loggedIn, csrf } = useAuthHint();
  const isAuthenticated = isAuthenticatedProp ?? loggedIn;

  // SSR-safe hydration flag via useSyncExternalStore (no useEffect setState)
  const hydrated = useHydrated();

  // ── AUTH PATH: server-driven via usePersonalization ────────────────────
  // useSyncExternalStore: server gets '/', client gets real pathname — no #418 mismatch.
  const route = useSyncExternalStore(
    (cb) => {
      window.addEventListener('popstate', cb);
      document.addEventListener('astro:page-load', cb);
      return () => {
        window.removeEventListener('popstate', cb);
        document.removeEventListener('astro:page-load', cb);
      };
    },
    () => window.location.pathname,
    () => '/',
  );
  const {
    data: personalData,
    isSuccess: serverReady,
    isError: personalizationError,
  } = usePersonalization(route);

  // diff-based optimistic state (added/removed on top of server seed)
  const [dealDiff, setDealDiff] = useState<{ added: Set<string>; removed: Set<string> }>(() => ({
    added: new Set(),
    removed: new Set(),
  }));
  const [vendorDiff, setVendorDiff] = useState<{ added: Set<string>; removed: Set<string> }>(
    () => ({
      added: new Set(),
      removed: new Set(),
    }),
  );

  // ── GUEST PATH: anonWishlistStore ─────────────────────────────────────
  // Lazy initializer reads localStorage synchronously on first client render.
  // Astro islands are client-only (no SSR hydration mismatch risk), so
  // typeof window check is sufficient — no need to wait for useSyncExternalStore.
  // This eliminates the aria-pressed=false flash on reload for guest wishlists.
  const [guestIds, setGuestIds] = useState<Set<string>>(() => {
    if (typeof window === 'undefined') return new Set();
    return new Set(getAnonWishlistIds());
  });

  // Cross-tab sync via storage event (callback → setState is allowed by rule).
  // useSyncExternalStore handles subscription + re-render cleanly.
  const guestIdsFromStorage = useSyncExternalStore(
    (onStoreChange) => {
      if (typeof window === 'undefined' || loggedIn) return () => {};
      const handler = (e: StorageEvent) => {
        if (e.key === STORAGE_KEY) onStoreChange();
      };
      window.addEventListener('storage', handler);
      return () => window.removeEventListener('storage', handler);
    },
    () => (hydrated && !loggedIn ? JSON.stringify(getAnonWishlistIds()) : '[]'),
    () => '[]',
  );

  // ── toggleDeal ─────────────────────────────────────────────────────────
  const toggleDeal = useCallback(
    (dealId: string, saved: boolean) => {
      if (!loggedIn) {
        // Guest: persist to localStorage. guestIdsFromStorage will re-derive on next render.
        if (saved) {
          addToAnonWishlist(dealId);
        } else {
          removeFromAnonWishlist(dealId);
        }
        // Local state sync for same-tab immediate feedback (storage event = cross-tab only)
        setGuestIds(new Set(getAnonWishlistIds()));
      } else {
        // Auth: optimistic diff.
        setDealDiff((prev) => {
          const added = new Set(prev.added);
          const removed = new Set(prev.removed);
          if (saved) {
            added.add(dealId);
            removed.delete(dealId);
          } else {
            removed.add(dealId);
            added.delete(dealId);
          }
          return { added, removed };
        });
      }
    },
    [loggedIn],
  );

  // ── toggleVendor ───────────────────────────────────────────────────────
  const toggleVendor = useCallback(
    (vendorId: string, saved: boolean) => {
      if (!loggedIn) {
        // Guest: no vendor wishlist — no-op.
        return;
      }
      setVendorDiff((prev) => {
        const added = new Set(prev.added);
        const removed = new Set(prev.removed);
        if (saved) {
          added.add(vendorId);
          removed.delete(vendorId);
        } else {
          removed.add(vendorId);
          added.delete(vendorId);
        }
        return { added, removed };
      });
    },
    [loggedIn],
  );

  // ── derived wishlist ids ───────────────────────────────────────────────
  const wishlistedDealIds = useMemo<Set<string>>(() => {
    if (!loggedIn) {
      // Cross-tab: prefer live storage snapshot; fall back to local state
      const liveIds: string[] = JSON.parse(guestIdsFromStorage) as string[];
      return liveIds.length > 0 ? new Set(liveIds) : guestIds;
    }
    // Auth: seed from SSR prop or server personalData, then apply diff.
    const seed =
      initialDealIds.length > 0 ? new Set(initialDealIds) : new Set(personalData?.wishlist ?? []);
    for (const [dealId, desiredSaved] of getQueuedWishlistState(csrf ?? '')) {
      if (desiredSaved) seed.add(dealId);
      else seed.delete(dealId);
    }
    const result = new Set(seed);
    for (const id of dealDiff.added) result.add(id);
    for (const id of dealDiff.removed) result.delete(id);
    return result;
  }, [
    loggedIn,
    guestIds,
    guestIdsFromStorage,
    initialDealIds,
    personalData?.wishlist,
    dealDiff,
    csrf,
  ]);

  const favoritedVendorIds = useMemo<Set<string>>(() => {
    if (!loggedIn) return new Set<string>();
    const seed =
      initialVendorIds.length > 0
        ? new Set(initialVendorIds)
        : new Set(personalData?.favoriteVendors ?? []);
    const result = new Set(seed);
    for (const id of vendorDiff.added) result.add(id);
    for (const id of vendorDiff.removed) result.delete(id);
    return result;
  }, [loggedIn, initialVendorIds, personalData?.favoriteVendors, vendorDiff]);

  const transitioning = useMemo<boolean>(
    () => !hydrated || (loggedIn && !serverReady && !personalizationError),
    [hydrated, loggedIn, serverReady, personalizationError],
  );

  useEffect(() => {
    if (!loggedIn || !hydrated || !csrf) return;

    const replay = () => {
      replayWishlistQueue(csrf).catch((err) => {
        captureCaught(err, {
          scope: 'features.wishlist.WishlistContext.replay',
          severity: 'warning',
        });
      });
    };

    replay();
    window.addEventListener('online', replay);
    return () => window.removeEventListener('online', replay);
  }, [loggedIn, hydrated, csrf]);

  return (
    <WishlistContext.Provider
      value={{
        wishlistedDealIds,
        favoritedVendorIds,
        isAuthenticated,
        transitioning,
        toggleDeal,
        toggleVendor,
      }}
    >
      {children}
    </WishlistContext.Provider>
  );
}

export function useWishlistContext(): WishlistContextValue {
  const ctx = useContext(WishlistContext);
  if (!ctx) throw new Error('useWishlistContext must be used inside WishlistProvider');
  return ctx;
}
