/**
 * useListSSE — Bounded-backoff EventSource wrapper for live list sync (LIST-04).
 *
 * Decisions implemented:
 *   D-10: Full refetch on reconnect — invalidates ['list', listId] on open.
 *   D-11: Silent bounded-backoff reconnect (250ms→500ms→1s→2s→4s→cap 8s).
 *         After MAX_ATTEMPTS failures: onStateChange('disconnected'), stop retrying.
 *   D-12: Polling fallback (refetchInterval:30000) lives in ListDetail — always active.
 *
 * Security (Pitfall 7, T-04-01):
 *   withCredentials: true ensures the OIDC session cookie is sent.
 *
 * Implementation notes (Pitfall 3 — no reconnect storm):
 *   On onerror: close the EventSource BEFORE scheduling setTimeout.
 *   This prevents the browser's built-in reconnect stacking with our manual one.
 *   Refs (not state) for esRef/attemptsRef/timerRef — avoids re-render loops.
 *
 * Source: RESEARCH.md Finding 4 verbatim pattern.
 */

import { useEffect, useRef, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';

export type SyncState = 'connected' | 'reconnecting' | 'disconnected';

/**
 * Backoff schedule per D-11: 250ms → 500ms → 1000ms → 2000ms → 4000ms → cap 8000ms.
 * 6 steps → MAX_ATTEMPTS = 6; after exhaustion, state → 'disconnected'.
 */
const BACKOFF_STEPS_MS = [250, 500, 1000, 2000, 4000, 8000];
const MAX_ATTEMPTS = BACKOFF_STEPS_MS.length;

export interface UseListSSEOptions {
  listId: number;
  onStateChange: (state: SyncState) => void;
}

/**
 * Opens a scoped SSE connection to /api/sse/lists and invalidates React Query
 * cache on events — driving a full refetch (D-10).
 *
 * Lifecycle:
 *   Mount  → connect() → EventSource opened
 *   Open   → attemptsRef reset → onStateChange('connected') → invalidate cache
 *   Event  → invalidate ['list', listId]
 *   Error  → es.close() → if attempts left: onStateChange('reconnecting') + setTimeout(connect, backoff)
 *                          else: onStateChange('disconnected') — stop
 *   Unmount → es.close() + clearTimeout(timerRef)
 */
export function useListSSE({ listId, onStateChange }: UseListSSEOptions): void {
  const queryClient = useQueryClient();
  const esRef = useRef<EventSource | null>(null);
  const attemptsRef = useRef(0);
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // handleListChange is stable — invalidate the list on any list-change event
  const handleListChange = useCallback(() => {
    // fire-and-forget: cache invalidation; React Query handles the refetch lifecycle
    void queryClient.invalidateQueries({ queryKey: ['list', listId] });
  }, [queryClient, listId]);

  const connect = useCallback(() => {
    // Close any existing connection (prevents stacking)
    esRef.current?.close();

    const es = new EventSource('/api/sse/lists', { withCredentials: true });
    esRef.current = es;

    // Subscribe to all list-change event types (D-10 — trigger refetch on any change)
    es.addEventListener('item:added', handleListChange);
    es.addEventListener('item:updated', handleListChange);
    es.addEventListener('item:deleted', handleListChange);
    es.addEventListener('list:updated', handleListChange);
    es.addEventListener('list:deleted', handleListChange);

    es.onopen = () => {
      // Reset attempt counter on successful open (D-11)
      attemptsRef.current = 0;
      onStateChange('connected');
      // Full refetch on (re)connect — D-10: no Last-Event-ID replay, just refetch; fire-and-forget
      void queryClient.invalidateQueries({ queryKey: ['list', listId] });
    };

    es.onerror = () => {
      // Close BEFORE scheduling retry — prevents browser auto-reconnect stacking (Pitfall 3)
      es.close();

      const attempt = attemptsRef.current;

      if (attempt >= MAX_ATTEMPTS) {
        // Backoff exhausted — surface "Updates paused" indicator and stop (D-11)
        onStateChange('disconnected');
        // D-12 polling fallback (refetchInterval:30000 in ListDetail) keeps data fresh
        return;
      }

      onStateChange('reconnecting');
      const delay = BACKOFF_STEPS_MS[attempt];
      attemptsRef.current = attempt + 1;
      timerRef.current = setTimeout(connect, delay);
    };
  }, [listId, queryClient, onStateChange, handleListChange]);

  useEffect(() => {
    connect();
    return () => {
      // Cleanup: close EventSource + cancel any pending reconnect timer (Pitfall 3)
      esRef.current?.close();
      if (timerRef.current !== null) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
      }
    };
  }, [connect]);
}
