/**
 * WebSocket manager for real-time chat communication.
 * Handles connection, reconnection, and message routing.
 */
class WebSocketManager {
  constructor() {
    this.ws = null
    this.listeners = new Map()
    this.reconnectTimer = null
    this.reconnectDelay = 1000
    this.maxReconnectDelay = 30000
    this.token = null
    this.subscribedRooms = new Set()
  }

  connect(token) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) return

    this.token = token
    this._authFailed = false

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const host = window.location.host
    this.ws = new WebSocket(`${protocol}//${host}/ws?token=${encodeURIComponent(token)}`)

    this.ws.onopen = () => {
      console.log('[WS] Connected')
      this.reconnectDelay = 1000
      this._authFailed = false

      // Re-subscribe to all rooms we were watching
      for (const roomId of this.subscribedRooms) {
        console.log('[WS] Re-joining room:', roomId)
        this._rawSend({ type: 'join_room', room_id: roomId })
      }

      this.emit('connected')
    }

    this.ws.onmessage = (event) => {
      try {
        const msg = JSON.parse(event.data)
        console.log('[WS] Received:', msg.type)
        this.emit(msg.type, msg)
      } catch (e) {
        console.error('[WS] Parse error:', e)
      }
    }

    this.ws.onclose = (event) => {
      console.log('[WS] Disconnected, code:', event.code)
      this.emit('disconnected')

      // Code 1008 = Policy Violation (server rejected auth)
      // Code 4401 = custom auth failure
      // Also detect immediate close without open (HTTP 401 on upgrade)
      if (event.code === 1008 || event.code === 4401 || !event.wasClean) {
        // If we never successfully connected, the token is likely invalid
        if (this._authFailed || (!event.wasClean && this.reconnectDelay > 4000)) {
          console.warn('[WS] Auth appears invalid, stopping reconnect')
          this.token = null
          this.emit('auth_failed')
          return
        }
        this._authFailed = true
      }

      if (this.token) {
        this.scheduleReconnect()
      }
    }

    this.ws.onerror = (error) => {
      console.error('[WS] Error:', error)
    }
  }

  disconnect() {
    this.token = null
    this.subscribedRooms.clear()
    this.listeners.clear()
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = null
    }
    if (this.ws) {
      this.ws.close()
      this.ws = null
    }
  }

  scheduleReconnect() {
    if (this.reconnectTimer) return
    console.log(`[WS] Reconnecting in ${this.reconnectDelay}ms...`)
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null
      this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay)
      if (this.token) {
        this.connect(this.token)
      }
    }, this.reconnectDelay)
  }

  _rawSend(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data))
      return true
    }
    return false
  }

  send(data) {
    if (!this._rawSend(data)) {
      console.warn('[WS] Cannot send - not connected:', data.type)
    }
  }

  joinRoom(roomId) {
    this.subscribedRooms.add(roomId)
    this.send({ type: 'join_room', room_id: roomId })
  }

  sendMessage(roomId, content, mentions = [], imageUrl = null) {
    console.log('[WS] Sending message to room:', roomId)
    const msg = {
      type: 'send_message',
      room_id: roomId,
      content,
      mentions,
    }
    if (imageUrl) msg.image_url = imageUrl
    this.send(msg)
  }

  sendTyping(roomId) {
    // Throttle typing notifications to once per 2 seconds
    if (this._typingThrottle) return
    this._typingThrottle = true
    setTimeout(() => { this._typingThrottle = false }, 2000)
    this.send({ type: 'typing', room_id: roomId })
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set())
    }
    this.listeners.get(event).add(callback)
    return () => this.listeners.get(event)?.delete(callback)
  }

  emit(event, data) {
    const handlers = this.listeners.get(event)
    if (handlers) {
      handlers.forEach((cb) => cb(data))
    }
  }
}

// Singleton instance
export const ws = new WebSocketManager()
