import { useCallback, useEffect, useRef, useState } from 'react'

export type SSEStatus = 'connecting' | 'open' | 'closed'

export interface UseSSEOptions<T> {
  /** URL del endpoint SSE */
  url: string
  /** Si conecta automaticamente al montar (default true) */
  enabled?: boolean
  /** Si reconecta automaticamente al perder la conexion (default true) */
  reconnect?: boolean
  /** Delay inicial de reconexion en ms (default 1000). Se duplica con backoff hasta max_reconnect_interval. */
  reconnect_interval?: number
  /** Delay maximo de reconexion en ms (default 30000) */
  max_reconnect_interval?: number
  /** Nombre del evento a escuchar (default 'message'). Para multiples eventos usar on_event. */
  event_name?: string
  /** Callback por evento recibido. Recibe el dato parseado. */
  on_event?: (data: T) => void
  /** Callback al error */
  on_error?: (error: Event) => void
}

export interface UseSSEResult<T> {
  /** Todos los eventos recibidos en esta sesion */
  data: T[]
  /** Ultimo evento recibido */
  last_event: T | null
  /** Estado de la conexion */
  status: SSEStatus
  /** Error de conexion si ocurrio */
  error: Event | null
  /** Cerrar la conexion manualmente */
  close: () => void
  /** Reconectar manualmente */
  connect: () => void
}

/**
 * Hook para Server-Sent Events con auto-reconexion y backoff exponencial.
 * Parsea cada evento como JSON. Acumula todos los eventos en data[].
 */
export function useSSE<T>(opts: UseSSEOptions<T>): UseSSEResult<T> {
  const {
    url,
    enabled = true,
    reconnect = true,
    reconnect_interval = 1000,
    max_reconnect_interval = 30_000,
    event_name = 'message',
    on_event,
    on_error,
  } = opts

  const [data, setData] = useState<T[]>([])
  const [lastEvent, setLastEvent] = useState<T | null>(null)
  const [status, setStatus] = useState<SSEStatus>('closed')
  const [error, setError] = useState<Event | null>(null)

  const esRef = useRef<EventSource | null>(null)
  const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const currentIntervalRef = useRef(reconnect_interval)
  const mountedRef = useRef(true)
  const closedManuallyRef = useRef(false)

  const clearReconnectTimer = () => {
    if (reconnectTimerRef.current !== null) {
      clearTimeout(reconnectTimerRef.current)
      reconnectTimerRef.current = null
    }
  }

  const connect = useCallback(() => {
    if (!mountedRef.current) return

    // Cerrar conexion previa si existe
    if (esRef.current) {
      esRef.current.close()
      esRef.current = null
    }

    closedManuallyRef.current = false
    currentIntervalRef.current = reconnect_interval

    setStatus('connecting')
    setError(null)

    const es = new EventSource(url)
    esRef.current = es

    es.addEventListener(event_name, (e: MessageEvent) => {
      if (!mountedRef.current) return

      let parsed: T
      try {
        parsed = JSON.parse(e.data) as T
      } catch {
        parsed = e.data as unknown as T
      }

      setData((prev: T[]) => [...prev, parsed])
      setLastEvent(parsed)
      on_event?.(parsed)
      // Reset backoff on successful message
      currentIntervalRef.current = reconnect_interval
    })

    es.onopen = () => {
      if (!mountedRef.current) return
      setStatus('open')
      currentIntervalRef.current = reconnect_interval
    }

    es.onerror = (e) => {
      if (!mountedRef.current) return
      setError(e)
      on_error?.(e)
      setStatus('closed')
      es.close()
      esRef.current = null

      if (reconnect && !closedManuallyRef.current) {
        const delay = Math.min(currentIntervalRef.current, max_reconnect_interval)
        currentIntervalRef.current = Math.min(delay * 2, max_reconnect_interval)

        reconnectTimerRef.current = setTimeout(() => {
          if (mountedRef.current && !closedManuallyRef.current) {
            connect()
          }
        }, delay)
      }
    }
  }, [url, event_name, reconnect, reconnect_interval, max_reconnect_interval, on_event, on_error])

  const close = useCallback(() => {
    closedManuallyRef.current = true
    clearReconnectTimer()
    if (esRef.current) {
      esRef.current.close()
      esRef.current = null
    }
    setStatus('closed')
  }, [])

  useEffect(() => {
    mountedRef.current = true

    if (enabled) {
      connect()
    }

    return () => {
      mountedRef.current = false
      clearReconnectTimer()
      if (esRef.current) {
        esRef.current.close()
        esRef.current = null
      }
    }
  }, [enabled, url]) // eslint-disable-line react-hooks/exhaustive-deps

  return { data, last_event: lastEvent, status, error, close, connect }
}
