'use client';

/**
 * React hook for real-time notifications via WebSocket.
 *
 * Provides:
 * - In-memory notification list updated in real-time from WS events
 * - Unread count (maintained as a ref to avoid stale closures)
 * - TanStack Query integration (notificationList cache updated on WS push)
 * - Toast notifications for new items (with deduplication)
 * - REST fallback: polls on mount to bootstrap the list
 *
 * Usage:
 *   const { notifications, unreadCount, markRead, markAllRead, archive } = useNotification();
 */

import { useEffect, useRef, useState, useCallback } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { notificationList } from '@/client';
import type { NotificationResponse } from '@/client/types.gen';
import type { RoomWsClient } from '@/lib/room-ws-client';
import type { NotificationCreatedPayload } from '@/lib/ws-protocol';

interface UseNotificationOptions {
  /** The WebSocket client to subscribe to notification events. */
  wsClient?: RoomWsClient | null;
  /** Polling interval in ms for REST fallback (when WS is unavailable). Default: 30s */
  pollInterval?: number;
  /** Maximum notifications to keep in memory. Default: 100 */
  maxNotifications?: number;
  /** Whether to show a toast for new notifications. Default: true */
  showToast?: boolean;
  /** Called when a notification is received. Return false to suppress the toast. */
  onNotification?: (n: NotificationResponse, deepLinkUrl?: string) => boolean | void;
}

const NOTIFICATION_QUERY_KEY = ['notifications'] as const;
const SEEN_NOTIFICATION_IDS_KEY = 'seen_notification_ids';

/** Get IDs of notifications we've already shown a toast for (session memory). */
function getSeenIds(): Set<string> {
  try {
    const raw = sessionStorage.getItem(SEEN_NOTIFICATION_IDS_KEY);
    return raw ? new Set(JSON.parse(raw) as string[]) : new Set();
  } catch {
    return new Set();
  }
}

function saveSeenIds(ids: Set<string>): void {
  try {
    sessionStorage.setItem(SEEN_NOTIFICATION_IDS_KEY, JSON.stringify([...ids]));
  } catch {
    // Ignore storage errors
  }
}

export interface UseNotificationReturn {
  /** Current notification list (merged WS + REST). */
  notifications: NotificationResponse[];
  /** Unread notification count. */
  unreadCount: number;
  /** Total notification count. */
  totalCount: number;
  /** Mark a single notification as read. */
  markRead: (id: string) => Promise<void>;
  /** Mark all notifications as read. */
  markAllRead: () => Promise<void>;
  /** Archive a notification. */
  archive: (id: string) => Promise<void>;
  /** Reload notifications from REST API. */
  refetch: () => void;
  /** Whether the WS connection is receiving events. */
  isLive: boolean;
}

export function useNotification(options: UseNotificationOptions = {}): UseNotificationReturn {
  const {
    wsClient,
    pollInterval = 30_000,
    maxNotifications = 100,
    showToast = true,
    onNotification,
  } = options;

  const queryClient = useQueryClient();
  const [liveNotifications, setLiveNotifications] = useState<NotificationResponse[]>([]);
  const [isLive, setIsLive] = useState(false);
  const seenIdsRef = useRef<Set<string>>(getSeenIds());
  const wsCallbackRef = useRef(onNotification);
  wsCallbackRef.current = onNotification;

  // Bootstrap from REST API on mount
  const { data: restData, refetch } = useQuery({
    queryKey: NOTIFICATION_QUERY_KEY,
    queryFn: async () => {
      const resp = await notificationList({ query: { limit: maxNotifications } });
      return resp.data?.data ?? null;
    },
    staleTime: pollInterval,
    refetchInterval: pollInterval,
  });

  const restNotifications: NotificationResponse[] = restData?.notifications ?? [];
  const restUnreadCount: number = restData?.unread_count ?? 0;

  // Merge WS notifications with REST data, preferring WS data (more fresh)
  const notifications = liveNotifications.length > 0
    ? liveNotifications
    : restNotifications;

  const unreadCount = liveNotifications.length > 0
    ? liveNotifications.filter((n) => !n.is_read).length
    : restUnreadCount;

  // Register WS callback
  useEffect(() => {
    if (!wsClient) return;

    const handleNotification = (payload: NotificationCreatedPayload) => {
      const n = payload.notification as NotificationResponse;
      const deepLinkUrl = payload.deep_link_url;

      setIsLive(true);
      setLiveNotifications((prev) => {
        // Avoid duplicates
        if (prev.some((existing) => existing.id === n.id)) return prev;
        const updated = [n, ...prev].slice(0, maxNotifications);
        return updated;
      });

      // Show toast for unread notifications
      if (!n.is_read && showToast) {
        const suppressToast = wsCallbackRef.current?.(n, deepLinkUrl) === false;
        if (!suppressToast) {
          // Deduplicate toasts within the session
          const seenIds = seenIdsRef.current;
          if (!seenIds.has(n.id)) {
            seenIds.add(n.id);
            saveSeenIds(seenIds);

            toast.info(n.title, {
              description: n.content ?? undefined,
              action: deepLinkUrl
                ? {
                    label: 'View',
                    onClick: () => {
                      window.location.href = deepLinkUrl;
                    },
                  }
                : undefined,
              duration: 5000,
            });
          }
        }
      }
    };

    wsClient.updateCallbacks({ onNotification: handleNotification as (payload: NotificationCreatedPayload) => void });

    return () => {
      wsClient.updateCallbacks({ onNotification: undefined });
      setIsLive(false);
    };
  }, [wsClient, maxNotifications, showToast]);

  // Mutation helpers
  const markRead = useCallback(
    async (id: string) => {
      const { notificationMarkRead } = await import('@/client');
      await notificationMarkRead({ path: { notification_id: id } });
      // Optimistically update both WS list and REST cache
      setLiveNotifications((prev) =>
        prev.map((n) => (n.id === id ? { ...n, is_read: true } : n)),
      );
      queryClient.setQueryData<typeof restData>(NOTIFICATION_QUERY_KEY, (old) => {
        if (!old) return old;
        return {
          ...old,
          notifications: old.notifications.map((n) =>
            n.id === id ? { ...n, is_read: true } : n,
          ),
          unread_count: Math.max(0, (old.unread_count ?? 1) - 1),
        };
      });
    },
    [queryClient],
  );

  const markAllRead = useCallback(async () => {
    const { notificationMarkAllRead } = await import('@/client');
    await notificationMarkAllRead();
    setLiveNotifications((prev) => prev.map((n) => ({ ...n, is_read: true })));
    queryClient.setQueryData<typeof restData>(NOTIFICATION_QUERY_KEY, (old) => {
      if (!old) return old;
      return { ...old, unread_count: 0, notifications: old.notifications.map((n) => ({ ...n, is_read: true })) };
    });
    toast.success('All notifications marked as read');
  }, [queryClient]);

  const archive = useCallback(
    async (id: string) => {
      const { notificationArchive } = await import('@/client');
      await notificationArchive({ path: { notification_id: id } });
      setLiveNotifications((prev) => prev.filter((n) => n.id !== id));
      queryClient.setQueryData<typeof restData>(NOTIFICATION_QUERY_KEY, (old) => {
        if (!old) return old;
        return {
          ...old,
          notifications: old.notifications.filter((n) => n.id !== id),
          total: Math.max(0, (old.total ?? 1) - 1),
        };
      });
    },
    [queryClient],
  );

  return {
    notifications,
    unreadCount,
    totalCount: notifications.length,
    markRead,
    markAllRead,
    archive,
    refetch,
    isLive,
  };
}
