import { useCallback, useEffect, useRef } from 'react';
import { useInfiniteQuery, useQueryClient } from '@/features/query/react-query';
import { useT } from '@/lib/i18n/react';
import { ErrorState } from '@/components/ui/feedback/ErrorState';
import { EmptyState } from '@/components/ui/feedback/EmptyState';
import { Spinner } from '@/components/ui/feedback/Spinner';
import { Button } from '@/components/ui/primitives/Button';
import { Icon } from '@/components/ui/icons/Icon';
import { NotificationItem } from '@/components/ui/notification/NotificationItem';
import type { NotificationRow } from '@/server/db/queries/live-notifications';
import { authenticatedFetch } from '@/lib/authenticated-fetch';

const PAGE_SIZE = 20;

interface NotifPage {
  items: NotificationRow[];
  unreadCount: number;
  nextCursor: string | null;
}

async function fetchPage(afterCursor: string | null): Promise<NotifPage> {
  const params = new URLSearchParams({ limit: String(PAGE_SIZE) });
  if (afterCursor) params.set('afterCursor', afterCursor);
  const res = await fetch(`/api/notifications?${params.toString()}`);
  if (!res.ok) throw new Error('Failed to load notifications');
  const json = (await res.json()) as { ok: boolean; data: NotifPage };
  if (!json.ok) throw new Error('Failed to load notifications');
  return json.data;
}

async function markRead(ids: string[]): Promise<void> {
  const res = await authenticatedFetch('/api/notifications', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids }),
  });
  const json = (await res.json()) as { ok: boolean };
  if (!json.ok) throw new Error('Failed to mark notification as read');
}

export interface NotificationListProps {
  onUnreadCountChange?: (count: number) => void;
}

export function NotificationList({ onUnreadCountChange }: NotificationListProps) {
  const t = useT('notif');
  const qc = useQueryClient();
  const bottomRef = useRef<HTMLDivElement>(null);
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } =
    useInfiniteQuery<
      NotifPage,
      Error,
      { pages: NotifPage[]; pageParams: (string | null)[] },
      string[],
      string | null
    >({
      queryKey: ['notifications'],
      queryFn: ({ pageParam }) => fetchPage(pageParam),
      initialPageParam: null,
      getNextPageParam: (lastPage) => lastPage?.nextCursor ?? undefined,
    });

  useEffect(() => {
    const lastPage = data?.pages[data.pages.length - 1];
    if (lastPage && onUnreadCountChange) {
      onUnreadCountChange(lastPage.unreadCount);
    }
  }, [data, onUnreadCountChange]);

  useEffect(() => {
    const sentinel = bottomRef.current;
    if (!sentinel) return;
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting && hasNextPage && !isFetchingNextPage) {
          void fetchNextPage();
        }
      },
      { threshold: 0.1 },
    );
    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  const handleRead = useCallback(
    async (id: string) => {
      await markRead([id]);
      qc.setQueryData<{ pages: NotifPage[]; pageParams: (string | null)[] }>(
        ['notifications'],
        (old) => {
          if (!old) return old;
          return {
            ...old,
            pages: old.pages.map((page) => ({
              ...page,
              unreadCount: Math.max(
                0,
                page.unreadCount -
                  ((page.items ?? []).some((n) => n.id === id && !n.readAt) ? 1 : 0),
              ),
              items: (page.items ?? []).map((n) =>
                n.id === id ? { ...n, readAt: new Date() } : n,
              ),
            })),
          };
        },
      );
      await qc.invalidateQueries({ queryKey: ['notifications'] });
    },
    [qc],
  );

  const allItems = data?.pages.flatMap((p) => p.items ?? []) ?? [];

  if (isError) {
    return (
      <ErrorState
        title={t('list_error')}
        action={
          <Button variant="secondary" size="sm" onClick={() => void refetch()}>
            {t('list_retry')}
          </Button>
        }
      />
    );
  }

  if (isLoading) {
    return (
      <div className="flex min-h-30 items-center justify-center">
        <Spinner size="md" />
        <span className="sr-only">{t('list_loading')}</span>
      </div>
    );
  }

  if (allItems.length === 0) {
    return (
      <EmptyState
        icon={<Icon name="Bell" size="lg" color="muted" />}
        title={t('list_empty')}
        description={t('list_empty_desc')}
      />
    );
  }

  return (
    <div className="flex max-h-[70dvh] flex-col overflow-y-auto">
      <ul aria-label={t('bell_aria')} className="flex flex-col gap-0.5 py-1">
        {allItems.map((item) => (
          <NotificationItem key={item.id} notification={item} onRead={handleRead} />
        ))}
      </ul>
      <div ref={bottomRef} aria-hidden className="h-1 shrink-0" />
      {isFetchingNextPage && (
        <div className="flex justify-center py-3">
          <Spinner size="sm" />
        </div>
      )}
    </div>
  );
}
