import { Injectable } from '@angular/core'
import { BehaviorSubject, Subject } from 'rxjs'
import { ManagerOptions, Socket, SocketOptions } from 'socket.io-client'
import { environment } from 'src/environments/environment'

export type LiveChatMessage = {
  id: string
  username: string
  text: string
  createdAt: number
}

@Injectable({
  providedIn: 'root'
})
export class LiveChatService {
  private io: (uri: string, opts?: Partial<ManagerOptions & SocketOptions>) => Socket
  private socket: Socket
  private joinedStreamKey: string
  private username?: string

  private messagesSubject = new BehaviorSubject<LiveChatMessage[]>([])
  private presenceSubject = new BehaviorSubject<number>(0)
  private errorSubject = new Subject<string>()

  readonly messages$ = this.messagesSubject.asObservable()
  readonly presence$ = this.presenceSubject.asObservable()
  readonly errors$ = this.errorSubject.asObservable()

  async joinRoom (streamKey: string, username?: string) {
    this.joinedStreamKey = streamKey
    this.username = username

    await this.initSocket()

    this.messagesSubject.next([])
    this.presenceSubject.next(0)
    this.socket.emit('join-room', { streamKey, username })
  }

  leaveRoom () {
    if (!this.socket || !this.joinedStreamKey) return

    this.socket.emit('leave-room')
    this.joinedStreamKey = undefined
    this.messagesSubject.next([])
    this.presenceSubject.next(0)
  }

  disconnect () {
    this.leaveRoom()

    if (!this.socket) return

    this.socket.disconnect()
    this.socket = undefined
  }

  sendMessage (text: string) {
    if (!this.socket || !this.joinedStreamKey) return

    this.socket.emit('send-message', { text })
  }

  private async initSocket () {
    if (this.socket) return

    await this.importIOIfNeeded()

    this.socket = this.io(environment.apiUrl + '/live-chat')

    this.socket.on('connect', () => {
      if (!this.joinedStreamKey || !this.username) return

      this.socket.emit('join-room', {
        streamKey: this.joinedStreamKey,
        username: this.username
      })
    })

    this.socket.on('recent-messages', payload => {
      if (payload?.streamKey !== this.joinedStreamKey) return
      this.messagesSubject.next(payload.messages || [])
    })

    this.socket.on('new-message', payload => {
      if (payload?.streamKey !== this.joinedStreamKey || !payload.message) return

      this.messagesSubject.next([
        ...this.messagesSubject.value,
        payload.message
      ])
    })

    this.socket.on('presence-update', payload => {
      if (payload?.streamKey !== this.joinedStreamKey) return
      this.presenceSubject.next(payload.count || 0)
    })

    this.socket.on('room-closed', payload => {
      if (payload?.streamKey !== this.joinedStreamKey) return

      this.joinedStreamKey = undefined
      this.messagesSubject.next([])
      this.presenceSubject.next(0)
      this.errorSubject.next('This live chat was cleared because the stream ended or was rescheduled.')
    })

    this.socket.on('chat-error', payload => {
      this.errorSubject.next(payload?.message || 'Chat error')
    })
  }

  private async importIOIfNeeded () {
    if (this.io) return

    this.io = (await import('socket.io-client')).io
  }
}
