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

export type WebSocketStatus = 'connecting' | 'open' | 'closing' | 'closed'

export interface UseWebSocketOptions<TRecv> {
  /** URL del WebSocket (ej: ws://localhost:8484/ws/chat) */
  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) */
  reconnect_interval?: number
  /** Delay maximo de reconexion en ms (default 30000) */
  max_reconnect_interval?: number
  /** Numero maximo de mensajes a mantener en el buffer (default 100) */
  max_messages?: number
  /** Callback por cada mensaje recibido */
  on_message?: (data: TRecv) => void
  /** Callback al conectar */
  on_open?: () => void
  /** Callback al cerrar */
  on_close?: (event: CloseEvent) => void
  /** Callback al error */
  on_error?: (error: Event) => void
}

export interface UseWebSocketResult<TSend, TRecv> {
  /** Enviar un mensaje (serializado como JSON) */
  send: (data: TSend) => void
  /** Ultimo mensaje recibido */
  last_message: TRecv | null
  /** Buffer de mensajes recibidos (limitado a max_messages) */
  messages: TRecv[]
  /** Estado de la conexion */
  status: WebSocketStatus
  /** Cerrar la conexion manualmente */
  close: () => void
  /** Reconectar manualmente */
  connect: () => void
}

/**
 * Hook WebSocket con auto-reconexion y backoff exponencial.
 * Serializa mensajes enviados como JSON. Parsea mensajes recibidos como JSON.
 * Mantiene buffer de los ultimos N mensajes.
 */
export function useWebSocket<TSend, TRecv>(
  opts: UseWebSocketOptions<TRecv>,
): UseWebSocketResult<TSend, TRecv> {
  const {
    url,
    enabled = true,
    reconnect = true,
    reconnect_interval = 1000,
    max_reconnect_interval = 30_000,
    max_messages = 100,
    on_message,
    on_open,
    on_close,
    on_error,
  } = opts

  const [messages, setMessages] = useState<TRecv[]>([])
  const [lastMessage, setLastMessage] = useState<TRecv | null>(null)
  const [status, setStatus] = useState<WebSocketStatus>('closed')

  const wsRef = useRef<WebSocket | null>(null)
  const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const currentIntervalRef = useRef(reconnect_interval)
  const mountedRef = useRef(true)
  const closedManuallyRef = useRef(false)
  const pendingMessages = useRef<TSend[]>([])

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

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

    if (wsRef.current) {
      wsRef.current.close()
      wsRef.current = null
    }

    closedManuallyRef.current = false
    currentIntervalRef.current = reconnect_interval

    setStatus('connecting')

    const ws = new WebSocket(url)
    wsRef.current = ws

    ws.onopen = () => {
      if (!mountedRef.current) return
      setStatus('open')
      currentIntervalRef.current = reconnect_interval
      on_open?.()

      // Enviar mensajes pendientes
      for (const msg of pendingMessages.current) {
        ws.send(JSON.stringify(msg))
      }
      pendingMessages.current = []
    }

    ws.onmessage = (e: MessageEvent) => {
      if (!mountedRef.current) return

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

      setLastMessage(parsed)
      setMessages((prev: TRecv[]) => {
        const next = [...prev, parsed]
        return next.length > max_messages ? next.slice(next.length - max_messages) : next
      })
      on_message?.(parsed)
    }

    ws.onclose = (e) => {
      if (!mountedRef.current) return
      setStatus('closed')
      wsRef.current = null
      on_close?.(e)

      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)
      }
    }

    ws.onerror = (e) => {
      if (!mountedRef.current) return
      on_error?.(e)
    }
  }, [url, reconnect, reconnect_interval, max_reconnect_interval, max_messages, on_message, on_open, on_close, on_error])

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

  const send = useCallback((data: TSend) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify(data))
    } else {
      // Buffer el mensaje para enviarlo cuando conecte
      pendingMessages.current.push(data)
    }
  }, [])

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

    if (enabled) {
      connect()
    }

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

  return {
    send,
    last_message: lastMessage,
    messages,
    status,
    close,
    connect,
  }
}
