/**
 * useSSE Hook
 *
 * Wrapper around SSEContext that collects and manages events.
 * Provides a clean interface for subscribing to SSE events with channel filtering.
 *
 * Features:
 * - Event batching to prevent race conditions when multiple events arrive rapidly
 * - Deduplication by event ID
 * - Configurable channel filtering
 *
 * Examples:
 *   const { events } = useSSE(); // All events
 *   const { events } = useSSE({ channels: ['inventory.alerts'] });
 *   const { events } = useSSE({ channels: ['*.notifications'] });
 */

import { useContext, useEffect, useState, useCallback, useRef, useMemo } from 'react';
import { SSEContext } from '../contexts/SSEContext';
import type { Event, Alert, Notification, Recommendation } from '../api/types/events';
import { convertLegacyAlert } from '../api/types/events';

interface UseSSEConfig {
  channels?: string[];
}

interface UseSSEReturn {
  events: Event[];
  isConnected: boolean;
  clearEvents: () => void;
}

const MAX_EVENTS = 200; // Keep last 200 events in memory
const BATCH_DELAY_MS = 50; // Batch events for 50ms to prevent race conditions

/**
 * Normalize event data to consistent Event format
 */
function normalizeEvent(eventType: string, data: any): Event {
  // Check if it's new format (has event_class) or legacy format
  if (data.event_class === 'alert' || data.event_class === 'notification' || data.event_class === 'recommendation') {
    // Ensure event_type is set even for new format events
    return {
      ...data,
      event_type: data.event_type || data.type || eventType,
    } as Event;
  } else if (data.item_type === 'alert' || data.item_type === 'recommendation') {
    return convertLegacyAlert(data);
  } else if (eventType === 'recommendation') {
    return {
      ...data,
      event_class: 'recommendation',
      event_domain: data.event_domain || 'ai_insights',
      event_type: data.event_type || data.type || 'recommendation',
    } as Recommendation;
  } else if (eventType === 'notification') {
    return {
      ...data,
      event_type: data.event_type || data.type || 'notification',
    } as Notification;
  } else {
    return {
      ...data,
      event_class: 'alert',
      event_domain: data.event_domain || 'operations',
      event_type: data.event_type || data.type || 'alert',
    } as Alert;
  }
}

/**
 * Deduplicate events by ID, keeping the newest version
 */
function deduplicateEvents(events: Event[]): Event[] {
  const seen = new Map<string, Event>();
  for (const event of events) {
    if (!seen.has(event.id)) {
      seen.set(event.id, event);
    }
  }
  return Array.from(seen.values());
}

export function useSSEEvents(config: UseSSEConfig = {}): UseSSEReturn {
  const context = useContext(SSEContext);
  const [events, setEvents] = useState<Event[]>([]);

  // Refs for batching - these persist across renders without causing re-renders
  const eventBufferRef = useRef<Event[]>([]);
  const flushTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const processedIdsRef = useRef<Set<string>>(new Set());

  if (!context) {
    throw new Error('useSSE must be used within SSEProvider');
  }

  // Memoize channels key to prevent unnecessary effect re-runs
  const channelsKey = useMemo(() =>
    JSON.stringify(config.channels?.slice().sort() || []),
    [config.channels]
  );

  // Flush buffered events to state - batches multiple events into single state update
  // IMPORTANT: No dependencies - this function's identity must be stable
  const flushEvents = useCallback(() => {
    if (eventBufferRef.current.length === 0) return;

    const bufferedEvents = eventBufferRef.current;
    eventBufferRef.current = [];

    setEvents(prev => {
      // Combine buffered events with existing events
      const combined = [...bufferedEvents, ...prev];
      // Deduplicate and limit
      const deduplicated = deduplicateEvents(combined);
      return deduplicated.slice(0, MAX_EVENTS);
    });
  }, []); // No dependencies - stable identity

  // Add event to buffer and schedule flush
  // IMPORTANT: Only depends on flushEvents which is now stable
  const bufferEvent = useCallback((event: Event) => {
    // Skip if already processed recently (extra safety)
    if (processedIdsRef.current.has(event.id)) {
      return;
    }

    // Mark as processed
    processedIdsRef.current.add(event.id);

    // Limit processed IDs cache size
    if (processedIdsRef.current.size > 1000) {
      const firstId = Array.from(processedIdsRef.current)[0];
      processedIdsRef.current.delete(firstId);
    }

    // Add to buffer
    eventBufferRef.current.push(event);

    // Clear existing timeout and set new one
    if (flushTimeoutRef.current) {
      clearTimeout(flushTimeoutRef.current);
    }

    // Flush after delay to batch rapid events
    flushTimeoutRef.current = setTimeout(flushEvents, BATCH_DELAY_MS);
  }, [flushEvents]); // flushEvents is now stable (no deps)

  // Extract addEventListener to avoid re-running effect when other context values change
  const { addEventListener } = context;

  useEffect(() => {
    const unsubscribers: (() => void)[] = [];

    // Unified event handler for all event types
    const createHandler = (eventType: string) => (data: any) => {
      const event = normalizeEvent(eventType, data);
      bufferEvent(event);
    };

    // Subscribe to all event types
    unsubscribers.push(addEventListener('alert', createHandler('alert')));
    unsubscribers.push(addEventListener('notification', createHandler('notification')));
    unsubscribers.push(addEventListener('recommendation', createHandler('recommendation')));

    // Listen to 'initial_state' event (batch load on connection)
    const handleInitialState = (data: any) => {
      if (Array.isArray(data)) {
        const initialEvents = data.map(item => normalizeEvent('alert', item));
        // For initial state, set directly without buffering
        setEvents(deduplicateEvents(initialEvents).slice(0, MAX_EVENTS));
        // Mark all as processed
        initialEvents.forEach(e => processedIdsRef.current.add(e.id));
      }
    };

    unsubscribers.push(addEventListener('initial_state', handleInitialState));
    unsubscribers.push(addEventListener('initial_items', handleInitialState));

    return () => {
      // Cleanup subscriptions only - do NOT clear flush timeout here
      // The flush timeout should complete even during cleanup
      unsubscribers.forEach(unsub => unsub());
    };
  }, [addEventListener, channelsKey, bufferEvent]);

  // Separate cleanup effect for flush timeout on unmount only
  useEffect(() => {
    return () => {
      if (flushTimeoutRef.current) {
        clearTimeout(flushTimeoutRef.current);
      }
    };
  }, []); // Empty deps = only runs on unmount

  const clearEvents = useCallback(() => {
    setEvents([]);
    eventBufferRef.current = [];
    processedIdsRef.current.clear();
  }, []);

  return {
    events,
    isConnected: context.isConnected,
    clearEvents,
  };
}

/**
 * useSSEWithDedupe Hook
 *
 * Enhanced version that deduplicates events more aggressively
 * based on event_type + entity_id for state change notifications.
 */
export function useSSEEventsWithDedupe(config: UseSSEConfig = {}) {
  const { events: rawEvents, ...rest } = useSSEEvents(config);
  const [deduplicatedEvents, setDeduplicatedEvents] = useState<Event[]>([]);

  useEffect(() => {
    // Deduplicate notifications by event_type + entity_id
    const seen = new Set<string>();
    const deduplicated: Event[] = [];

    for (const event of rawEvents) {
      let key: string;

      if (event.event_class === 'notification') {
        const notification = event as Notification;
        // Deduplicate by entity (keep only latest state)
        if (notification.entity_type && notification.entity_id) {
          key = `${notification.event_type}:${notification.entity_type}:${notification.entity_id}`;
        } else {
          key = event.id;
        }
      } else {
        // For alerts and recommendations, use ID
        key = event.id;
      }

      if (!seen.has(key)) {
        seen.add(key);
        deduplicated.push(event);
      }
    }

    setDeduplicatedEvents(deduplicated);
  }, [rawEvents]);

  return {
    events: deduplicatedEvents,
    ...rest,
  };
}
