'use client'

import { useCallback, useEffect, useRef, useState } from 'react'
import type { Room } from 'livekit-client'

export type VoiceStatus =
  | 'idle'
  | 'connecting'
  | 'listening'
  | 'thinking'
  | 'speaking'
  | 'error'

export interface FinalVoiceTranscript {
  role: 'user' | 'assistant'
  content: string
  segmentId: string
}

export function useTripVoice({
  tripId,
  language,
  enabled,
  onTranscript,
}: {
  tripId: string
  language: string
  enabled: boolean
  onTranscript: (transcript: FinalVoiceTranscript) => void
}) {
  const [status, setStatus] = useState<VoiceStatus>('idle')
  const [error, setError] = useState<string | null>(null)
  const roomRef = useRef<Room | null>(null)
  const browserIdRef = useRef('')
  const audioElementsRef = useRef<HTMLMediaElement[]>([])
  const finalSegmentsRef = useRef(new Set<string>())
  const connectionAttemptRef = useRef(0)
  const transcriptHandlerRef = useRef(onTranscript)

  useEffect(() => {
    transcriptHandlerRef.current = onTranscript
  }, [onTranscript])

  const cleanupAudio = useCallback(() => {
    for (const element of audioElementsRef.current) element.remove()
    audioElementsRef.current = []
  }, [])

  const stop = useCallback(async () => {
    connectionAttemptRef.current += 1
    const room = roomRef.current
    roomRef.current = null
    if (room) {
      try {
        await room.localParticipant.setMicrophoneEnabled(false)
      } catch {
        // The room may already be disconnected.
      }
      room.disconnect()
    }
    cleanupAudio()
    finalSegmentsRef.current.clear()
    setStatus('idle')
    setError(null)
  }, [cleanupAudio])

  useEffect(() => {
    return () => {
      connectionAttemptRef.current += 1
      const room = roomRef.current
      roomRef.current = null
      if (room) room.disconnect()
      cleanupAudio()
    }
  }, [cleanupAudio, tripId, language])

  const start = useCallback(async () => {
    if (!enabled || roomRef.current || status === 'connecting') return
    setStatus('connecting')
    setError(null)
    const connectionAttempt = ++connectionAttemptRef.current
    try {
      if (!browserIdRef.current) browserIdRef.current = crypto.randomUUID()
      const response = await fetch(`/api/trips/${tripId}/chat/voice-token`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          browser_id: browserIdRef.current,
          language,
        }),
      })
      const tokenData = await response.json().catch(() => ({}))
      if (!response.ok) throw new Error(tokenData.error || 'Voice service is unavailable.')
      if (connectionAttempt !== connectionAttemptRef.current) return

      const { Room, RoomEvent, Track } = await import('livekit-client')
      const room = new Room({ adaptiveStream: true, dynacast: true })
      roomRef.current = room

      room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
        const role: FinalVoiceTranscript['role'] = participant?.isLocal ? 'user' : 'assistant'
        for (const rawSegment of segments) {
          const segment = rawSegment as typeof rawSegment & { isFinal?: boolean }
          const content = String(segment.text || '').trim()
          const isFinal = Boolean(segment.final || segment.isFinal)
          if (!content || !isFinal) continue
          const segmentId = String(segment.id || `${role}:${content}`)
          if (finalSegmentsRef.current.has(segmentId)) continue
          finalSegmentsRef.current.add(segmentId)
          transcriptHandlerRef.current({ role, content, segmentId })
          setStatus(role === 'user' ? 'thinking' : 'listening')
        }
      })

      room.on(RoomEvent.DataReceived, (payload, _participant, _kind, topic) => {
        if (topic !== 'logistics.metadata') return
        try {
          const packet = JSON.parse(new TextDecoder().decode(payload))
          if (packet?.type !== 'logistics-bot-state') return
          if (packet.state === 'thinking') setStatus('thinking')
          else if (packet.state === 'speaking') setStatus('speaking')
          else if (packet.state === 'listening' || packet.state === 'idle') setStatus('listening')
        } catch {
          // Ignore malformed optional metadata; audio can continue.
        }
      })

      room.on(RoomEvent.TrackSubscribed, (track) => {
        if (track.kind !== Track.Kind.Audio) return
        const element = track.attach() as HTMLMediaElement
        element.autoplay = true
        element.style.display = 'none'
        document.body.appendChild(element)
        audioElementsRef.current.push(element)
      })

      room.on(RoomEvent.TrackUnsubscribed, (track) => {
        for (const element of track.detach()) {
          element.remove()
          audioElementsRef.current = audioElementsRef.current.filter((item) => item !== element)
        }
      })

      room.on(RoomEvent.Disconnected, () => {
        roomRef.current = null
        cleanupAudio()
        setStatus('idle')
      })

      await room.connect(tokenData.url, tokenData.token)
      if (connectionAttempt !== connectionAttemptRef.current) {
        room.disconnect()
        return
      }
      await room.localParticipant.setMicrophoneEnabled(true)
      setStatus('listening')
    } catch (voiceError) {
      if (connectionAttempt !== connectionAttemptRef.current) return
      const message = voiceError instanceof Error ? voiceError.message : 'Voice connection failed.'
      const room = roomRef.current
      roomRef.current = null
      if (room) room.disconnect()
      cleanupAudio()
      setError(message)
      setStatus('error')
    }
  }, [cleanupAudio, enabled, language, status, tripId])

  const toggle = useCallback(async () => {
    if (roomRef.current || status === 'connecting') await stop()
    else await start()
  }, [start, status, stop])

  return {
    status,
    error,
    connected: ['listening', 'thinking', 'speaking'].includes(status),
    start,
    stop,
    toggle,
  }
}
