/**
 * Persistent WebSocket hook — replaces useSSE.
 *
 * Protocol:
 *   1. Connect to ws[s]://host/api/ws/connect?token=<jwt>
 *   2. Send { cursor: <last_seq> } immediately (0 = first connect)
 *   3. Receive replayed missed events, then { type: "ready" }
 *   4. Live events: { seq, type, data }
 *   5. Keepalive: server sends { type: "ping" } every 25s
 *
 * The hook stores `lastSeq` in IndexedDB so it survives page reloads.
 * On every clean reconnect, missed events are replayed automatically.
 */

import { useCallback, useEffect, useRef } from 'react'
import db from '../db/posdb'

const INITIAL_RECONNECT_DELAY = 2_000
const MAX_RECONNECT_DELAY = 30_000
const CURSOR_KEY = 'ws_last_seq'

// ── Cursor persistence (IDB) ──────────────────────────────────────────────────

async function loadCursor() {
  try {
    const row = await db.kv?.get(CURSOR_KEY)
    return row?.value ?? 0
  } catch {
    return 0
  }
}

async function saveCursor(seq) {
  try {
    await db.kv?.put({ key: CURSOR_KEY, value: seq })
  } catch {}
}

// ── Hook ──────────────────────────────────────────────────────────────────────

export function useWebSocket({ token, onEvent, onConnect, onDisconnect, enabled = true }) {
  const onEventRef = useRef(onEvent)
  const onConnectRef = useRef(onConnect)
  const onDisconnectRef = useRef(onDisconnect)
  useEffect(() => { onEventRef.current = onEvent }, [onEvent])
  useEffect(() => { onConnectRef.current = onConnect }, [onConnect])
  useEffect(() => { onDisconnectRef.current = onDisconnect }, [onDisconnect])

  const wsRef = useRef(null)
  const reconnectTimer = useRef(null)
  const reconnectDelay = useRef(INITIAL_RECONNECT_DELAY)
  const unmounted = useRef(false)
  const connectRef = useRef(null)
  const lastSeq = useRef(0)

  useEffect(() => {
    if (!token || !enabled) return
    unmounted.current = false

    // Load persisted cursor before first connect
    loadCursor().then(seq => {
      lastSeq.current = seq
      if (!unmounted.current) connectRef.current?.()
    })

    async function connect() {
      if (unmounted.current) return
      if (wsRef.current) {
        wsRef.current.onclose = null  // prevent double-reconnect
        wsRef.current.close()
        wsRef.current = null
      }

      // Build WS URL — same host, swap http(s) → ws(s)
      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
      const url = `${protocol}//${window.location.host}/api/ws/connect?token=${encodeURIComponent(token)}`

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

      ws.onopen = () => {
        reconnectDelay.current = INITIAL_RECONNECT_DELAY
        // Send cursor so server can replay missed events
        ws.send(JSON.stringify({ cursor: lastSeq.current }))
      }

      ws.onmessage = (e) => {
        let msg
        try { msg = JSON.parse(e.data) } catch { return }

        if (msg.type === 'ping') return  // keepalive — no response needed
        if (msg.type === 'ready') {
          // Replay complete — we're now on the live stream
          onConnectRef.current?.()
          return
        }

        // Live or replayed event
        if (msg.seq && msg.seq > lastSeq.current) {
          lastSeq.current = msg.seq
          saveCursor(msg.seq)
        }
        if (msg.type && msg.data !== undefined) {
          onEventRef.current?.(msg.type, msg.data)
        }
      }

      ws.onerror = () => {
        // onerror always fires before onclose — let onclose handle reconnect
      }

      ws.onclose = () => {
        wsRef.current = null
        onDisconnectRef.current?.()
        if (unmounted.current) return
        // Schedule next attempt with current delay, then increase for the one after
        reconnectTimer.current = setTimeout(() => {
          reconnectDelay.current = Math.min(
            reconnectDelay.current * 1.5,
            MAX_RECONNECT_DELAY,
          )
          connect()
        }, reconnectDelay.current)
      }
    }

    connectRef.current = connect

    // When the browser detects network restoration, immediately reset backoff and reconnect.
    // This fires as soon as the OS reports connectivity — no need to wait for the
    // backed-off timer or for the user to switch tabs.
    function onNetworkOnline() {
      clearTimeout(reconnectTimer.current)
      reconnectDelay.current = INITIAL_RECONNECT_DELAY
      connect()
    }

    window.addEventListener('online', onNetworkOnline)

    return () => {
      unmounted.current = true
      clearTimeout(reconnectTimer.current)
      window.removeEventListener('online', onNetworkOnline)
      if (wsRef.current) {
        wsRef.current.onclose = null
        wsRef.current.close()
        wsRef.current = null
      }
    }
  }, [token, enabled])

  const reconnect = useCallback(() => {
    clearTimeout(reconnectTimer.current)
    reconnectDelay.current = INITIAL_RECONNECT_DELAY
    connectRef.current?.()
  }, [])

  return { reconnect }
}
