import { getToken } from '@/utils/auth'

let ws = null
let reconnectTimer = null
let manualClose = false
let currentOnMessage = null
let retryCount = 0

const WS_PATH = '/ws'
const MAX_RECONNECT_DELAY = 30000

function getWsBaseUrl() {
  const baseApi = import.meta.env.VITE_APP_BASE_API || ''

  if (/^https?:\/\//.test(baseApi)) {
    return baseApi.replace(/^http/, 'ws').replace(/\/$/, '')
  }

  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  return `${protocol}//${window.location.host}${baseApi}`.replace(/\/$/, '')
}

function buildWsUrl() {
  const url = new URL(`${getWsBaseUrl()}${WS_PATH}`)
  const token = getToken()

  if (token) {
    url.searchParams.set('token', token)
  }

  return url.toString()
}

export function connectWs(onMessage) {
  if (!getToken()) {
    console.warn('[WebSocket] missing token')
    return
  }

  currentOnMessage = onMessage
  manualClose = false
  clearReconnectTimer()

  if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
    return
  }

  ws = new WebSocket(buildWsUrl())

  ws.onopen = () => {
    retryCount = 0
    console.log('[WebSocket] connected')
  }

  ws.onmessage = (event) => {
    let data = event.data

    try {
      data = JSON.parse(event.data)
    } catch (e) {
      // Keep plain text messages as-is.
    }

    currentOnMessage && currentOnMessage(data, event)
  }

  ws.onclose = () => {
    console.log('[WebSocket] closed')
    ws = null

    if (!manualClose) {
      reconnectTimer = setTimeout(() => {
        console.log('[WebSocket] reconnecting')
        connectWs(currentOnMessage)
      }, getReconnectDelay())
    }
  }

  ws.onerror = (error) => {
    console.error('[WebSocket] error', error)
  }
}

export function closeWs() {
  manualClose = true
  retryCount = 0
  clearReconnectTimer()

  if (ws) {
    ws.close()
    ws = null
  }
}

export function reconnectWs(onMessage = currentOnMessage) {
  closeWs()
  manualClose = false
  connectWs(onMessage)
}

export function sendWs(data) {
  if (!ws || ws.readyState !== WebSocket.OPEN) {
    console.warn('[WebSocket] send failed, socket is not open')
    return false
  }

  ws.send(typeof data === 'string' ? data : JSON.stringify(data))
  return true
}

export function getWsState() {
  return ws ? ws.readyState : WebSocket.CLOSED
}

function getReconnectDelay() {
  retryCount += 1
  return Math.min(5000 * retryCount, MAX_RECONNECT_DELAY)
}

function clearReconnectTimer() {
  if (reconnectTimer) {
    clearTimeout(reconnectTimer)
    reconnectTimer = null
  }
}
