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

interface WebSocketHandlers<T> {
  onClose?: (event: CloseEvent) => void,
  onError?: (event: Event) => void,
  onMessage?: (data: T) => void,
  onOpen?: (event: Event) => void,
}

interface WebSocketOptions<T> {
  allowConnection?: boolean, // Разрешено ли подключение
  autoReconnect?: boolean, // Автоматическое переподключение при ошибке
  handlers?: WebSocketHandlers<T>, // Обработчики событий
  maxReconnectAttempts?: number, // Максимальное количество попыток переподключения
  reconnectInterval?: number, // Интервал между попытками переподключения
  url: string, // Url запроса
}

export const useWebSocket = <T, >({
  allowConnection = true,
  autoReconnect = false,
  handlers = {},
  maxReconnectAttempts = 5,
  reconnectInterval = 5000,
  url,
}: WebSocketOptions<T>) => {
  const {
    onClose,
    onError,
    onMessage,
    onOpen,
  } = handlers

  const webSocketRef = useRef<WebSocket | null>(null)
  const timeoutIdRef = useRef<NodeJS.Timeout | null>(null)
  const reconnectAttemptsRef = useRef<number>(0)
  const onCloseRef = useRef(onClose)
  const onErrorRef = useRef(onError)
  const onMessageRef = useRef(onMessage)
  const onOpenRef = useRef(onOpen)
  const [webSocketState, setWebSocketState] = useState(WebSocket.CONNECTING)

  const sendMessage = (message: Record<string, unknown>) => {
    if (webSocketRef.current?.readyState !== WebSocket.OPEN) return

    const body = JSON.stringify(message)

    webSocketRef.current?.send(body)
  }

  const closeConnection = () => {
    setWebSocketState(WebSocket.CLOSING)
    webSocketRef.current?.close()
  }

  useEffect(() => {
    onCloseRef.current = onClose
    onErrorRef.current = onError
    onMessageRef.current = onMessage
    onOpenRef.current = onOpen
  }, [onClose, onError, onMessage, onOpen])

  useEffect(() => {
    const connect = () => {
      if (!allowConnection) return

      const ws = new WebSocket(url)

      ws.addEventListener('open', handleOpen)
      ws.addEventListener('message', handleMessage)
      ws.addEventListener('close', handleClose)
      ws.addEventListener('error', handleError)

      webSocketRef.current = ws
      setWebSocketState(WebSocket.CONNECTING)
    }

    const handleOpen = (event: Event) => {
      setWebSocketState(WebSocket.OPEN)
      onOpenRef.current?.(event)
      reconnectAttemptsRef.current = 0
    }

    const handleMessage = (event: MessageEvent) => {
      const data = JSON.parse(event.data)

      onMessageRef.current?.(data)
    }

    const handleClose = (event: CloseEvent) => {
      setWebSocketState(WebSocket.CLOSED)
      onCloseRef.current?.(event)
    }

    const handleError = (event: Event) => {
      setWebSocketState(WebSocket.CLOSED)
      onErrorRef.current?.(event)

      if (autoReconnect && reconnectAttemptsRef.current < maxReconnectAttempts) {
        timeoutIdRef.current = setTimeout(connect, reconnectInterval)
        reconnectAttemptsRef.current++
      }
    }

    connect()

    return () => {
      webSocketRef.current?.removeEventListener('open', handleOpen)
      webSocketRef.current?.removeEventListener('message', handleMessage)
      webSocketRef.current?.removeEventListener('close', handleClose)
      webSocketRef.current?.removeEventListener('error', handleError)
      webSocketRef.current?.close()

      timeoutIdRef.current && clearTimeout(timeoutIdRef.current)
    }
  }, [url, autoReconnect, reconnectInterval, maxReconnectAttempts, allowConnection])

  return {
    closeConnection,
    sendMessage,
    webSocketState,
  }
}

