// @design-system: domain/WishlistButton

'use client';

import { useRef, useCallback, useContext } from 'react';
import { useMutation } from '@/features/query/react-query';
import { announce } from '@/lib/a11y/announce';
import { cn } from '@/lib/cn';
import { Icon } from '@/components/ui/icons/Icon';
import { useT } from '@/lib/i18n/react';
import { WishlistContext } from '@/features/wishlist/WishlistContext.js';
import { getCsrfToken } from '@/lib/csrf';
import { useAuthHint } from '@/lib/hooks/useAuthHint';
import { usePersonalization } from '@/lib/hooks/usePersonalization';
import { useBrowserPathname } from '@/lib/hooks/useHydrated';
import { enqueueWishlistDesiredState } from '@/lib/offline/wishlistQueue';

export interface WishlistButtonProps {
  dealId: string;
  /** RTL mirror — flip icon horizontally. @default false */
  mirror?: boolean;
  /** Show visible text label alongside the icon. */
  showLabel?: boolean;
  /**
   * Visibility rule.
   * - 'always' (default): always rendered, opacity 1.
   * - 'saved-only': rendered only when `isSaved === true`; returns null otherwise.
   * - 'hover-or-saved': saved → always visible; unsaved → opacity-0, reveals on
   *   parent `group-hover` / `group-focus-within`, hidden via `.lg-hover-only` on
   *   touch / coarse-pointer devices. Parent must have the `group` class.
   */
  appearance?: 'always' | 'saved-only' | 'hover-or-saved';
  className?: string;
  onAuthRequired?: () => void;
}

async function toggleWishlistApi(dealId: string) {
  const res = await fetch('/api/wishlist/toggle', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': getCsrfToken() },
    body: JSON.stringify({ dealId, notify: false }),
  });
  if (res.status === 401)
    throw Object.assign(new Error('AUTH_REQUIRED'), { code: 'AUTH_REQUIRED' });
  if (!res.ok) throw new Error('Toggle failed');
  return res.json() as Promise<{ ok: true; saved: boolean; notify: boolean }>;
}

function isOfflineMutationError(err: Error): boolean {
  return typeof navigator !== 'undefined' && navigator.onLine === false
    ? true
    : err instanceof TypeError;
}

export function WishlistButton({
  dealId,
  mirror = false,
  showLabel,
  appearance = 'always',
  className,
  onAuthRequired,
}: WishlistButtonProps) {
  const t = useT('wishlist');
  const ctx = useContext(WishlistContext);
  const { loggedIn } = useAuthHint();
  const route = useBrowserPathname();
  const { data: personalData, isLoading: isPersonalizationLoading } = usePersonalization(route);

  // Derive saved state from context (always reflects current store, survives reload).
  // Falls back to personalization data for SSR/non-context rendering.
  const isSaved = ctx
    ? ctx.wishlistedDealIds.has(dealId)
    : (personalData?.wishlist?.includes(dealId) ?? false);

  // Capture pre-mutate value so onError can revert the optimistic context flip.
  const savedBeforeRef = useRef(false);

  const announceWishlist = useCallback(
    (saved: boolean, message = saved ? t('saved_toast') : t('removed_toast')) => {
      announce({
        message,
        dedupeKey: `wishlist:${dealId}:${saved ? 'saved' : 'removed'}`,
      });
    },
    [dealId, t],
  );

  const { mutate, isPending } = useMutation({
    mutationFn: () => toggleWishlistApi(dealId),
    onMutate: () => {
      savedBeforeRef.current = ctx?.wishlistedDealIds.has(dealId) ?? false;
      // Optimistically flip context so derived isSaved updates immediately.
      ctx?.toggleDeal(dealId, !savedBeforeRef.current);
    },
    onSuccess: (data) => {
      // Correct any optimistic drift with the server-confirmed value.
      ctx?.toggleDeal(dealId, data.saved);
      announceWishlist(data.saved);
    },
    onError: (err: Error & { code?: string }) => {
      if (err.code === 'AUTH_REQUIRED') {
        ctx?.toggleDeal(dealId, savedBeforeRef.current);
        onAuthRequired?.();
        return;
      }
      if (isOfflineMutationError(err)) {
        const desiredSaved = !savedBeforeRef.current;
        enqueueWishlistDesiredState(dealId, desiredSaved);
        announceWishlist(desiredSaved, t('queued_toast'));
        return;
      }
      ctx?.toggleDeal(dealId, savedBeforeRef.current);
      announceWishlist(savedBeforeRef.current, t('merge.toastError'));
    },
  });

  const handleClick = useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault();
      e.stopPropagation();
      if (!loggedIn) {
        // Guest: local toggle persisted via anonWishlistStore, merged on login.
        const nextSaved = !(ctx?.wishlistedDealIds.has(dealId) ?? false);
        ctx?.toggleDeal(dealId, nextSaved);
        announceWishlist(nextSaved);
        return;
      }
      mutate();
    },
    [loggedIn, mutate, ctx, dealId, announceWishlist],
  );

  if (appearance === 'saved-only' && !isSaved) return null;

  return (
    <>
      <button
        type="button"
        aria-pressed={isSaved}
        aria-label={isSaved ? t('remove_from_wishlist') : t('add_to_wishlist')}
        aria-disabled={!!ctx?.transitioning || isPending}
        disabled={!!ctx?.transitioning || isPending}
        onClick={handleClick}
        className={cn(
          'inline-flex items-center justify-center rounded-full p-2 transition duration-200',
          'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-focus-ring)]',
          isSaved
            ? 'text-[var(--color-error)] hover:text-[var(--color-error-hover)]'
            : 'text-[var(--color-muted)] hover:text-[var(--color-foreground)]',
          appearance === 'hover-or-saved' &&
            !isSaved && [
              'lg-hover-only',
              'opacity-0 motion-safe:transition-opacity motion-safe:duration-150 motion-safe:ease-out',
              'group-focus-within:opacity-100 group-hover:opacity-100',
              'focus-visible:opacity-100',
            ],
          isPersonalizationLoading && 'animate-pulse opacity-40 motion-reduce:animate-none',
          isPending && 'cursor-wait opacity-50',
          className,
        )}
      >
        <Icon
          name="Heart"
          size="md"
          mirror={mirror}
          className={isSaved ? 'fill-current' : undefined}
        />
        {showLabel && <span className="ms-1 text-sm font-medium">{t('button_label')}</span>}
      </button>
    </>
  );
}
