import { useCallback, useEffect, useRef, useState } from 'react'
import { pipeline, env } from '@xenova/transformers'

// Configure the transformers library
env.allowRemoteModels = true
env.allowLocalModels = false
env.useBrowserCache = true

// Resample audio to 16kHz for Whisper
function resampleAudio(audioData: Float32Array, fromSampleRate: number, toSampleRate: number): Float32Array {
  if (fromSampleRate === toSampleRate) {
    return audioData
  }

  if (!audioData || audioData.length === 0) {
    throw new Error('Invalid audio data for resampling')
  }

  const ratio = fromSampleRate / toSampleRate
  const newLength = Math.floor(audioData.length / ratio)

  if (newLength <= 0) {
    throw new Error('Invalid resampled length')
  }

  const resampled = new Float32Array(newLength)

  for (let i = 0; i < newLength; i++) {
    const sourceIndex = Math.floor(i * ratio)
    if (sourceIndex >= 0 && sourceIndex < audioData.length) {
      resampled[i] = audioData[sourceIndex]
    } else {
      resampled[i] = 0
    }
  }

  return resampled
}

interface UseWhisperTranscriptionOptions {
  onTranscriptUpdate?: (text: string) => void
  onError?: (error: Error) => void
  onModelProgress?: (progress: number, status: string) => void
  language?: string
  enableStreaming?: boolean
}

export const useWhisperTranscription = ({
  onTranscriptUpdate,
  onError,
  onModelProgress,
  language = 'en',
  enableStreaming = true
}: UseWhisperTranscriptionOptions = {}) => {
  const [isRecording, setIsRecording] = useState(false)
  const [isTranscribing, setIsTranscribing] = useState(false)
  const [transcript, setTranscript] = useState('')
  const [modelLoaded, setModelLoaded] = useState(false)
  const [modelLoading, setModelLoading] = useState(false)

  const transcriberRef = useRef<ReturnType<typeof pipeline> extends Promise<infer T> ? T : never>(null!)
  const streamRef = useRef<MediaStream | null>(null)
  const mediaRecorderRef = useRef<MediaRecorder | null>(null)
  const audioChunksRef = useRef<Blob[]>([])
  const isRecordingRef = useRef(false)
  const transcriptRef = useRef('')
  const periodicTranscriptionRef = useRef<NodeJS.Timeout | null>(null)

  // Initialize Whisper model
  const initializeTranscriber = useCallback(async () => {
    if (transcriberRef.current || modelLoading) return transcriberRef.current

    setModelLoading(true)

    try {
      const modelNames = ['Xenova/whisper-tiny.en', 'Xenova/whisper-tiny']

      let transcriber = null
      let lastError = null

      for (const modelName of modelNames) {
        try {
          transcriber = await pipeline('automatic-speech-recognition', modelName, {
            quantized: true,
            progress_callback: (progress: { status: string; file?: string; progress?: number }) => {
              if (progress.status === 'downloading' && progress.progress) {
                onModelProgress?.(progress.progress, `Downloading ${progress.file || 'model'}...`)
              } else if (progress.status === 'loading') {
                onModelProgress?.(100, 'Loading model...')
              }
            }
          })

          transcriberRef.current = transcriber
          setModelLoaded(true)
          setModelLoading(false)
          return transcriber
        } catch (error) {
          console.warn(`Failed to load model ${modelName}:`, error)
          lastError = error
          continue
        }
      }

      throw lastError || new Error('Failed to load any Whisper model')
    } catch (error) {
      console.error('Failed to load Whisper model:', error)
      setModelLoading(false)
      onError?.(error as Error)
      throw error
    }
  }, [onError, onModelProgress, modelLoading])

  // Process audio chunks for transcription
  const processAudioChunks = useCallback(async () => {
    if (audioChunksRef.current.length === 0) return

    if (!transcriberRef.current) {
      console.warn('Transcriber not initialized')
      return
    }

    try {
      setIsTranscribing(true)

      // Get MIME type from MediaRecorder
      let mimeType = 'audio/webm;codecs=opus'
      if (mediaRecorderRef.current?.mimeType) {
        mimeType = mediaRecorderRef.current.mimeType
      }

      const validChunks = audioChunksRef.current.filter(chunk => chunk && chunk.size > 1000)
      if (validChunks.length === 0) return

      const audioBlob = new Blob(validChunks, { type: mimeType })
      if (audioBlob.size < 10000) return

      const arrayBuffer = await audioBlob.arrayBuffer()
      const audioContext = new AudioContext()

      let audioBuffer: AudioBuffer
      try {
        audioBuffer = await audioContext.decodeAudioData(arrayBuffer)
      } catch {
        // Try alternative MIME type
        try {
          const altBlob = new Blob(validChunks, { type: 'audio/webm' })
          const altBuffer = await altBlob.arrayBuffer()
          audioBuffer = await audioContext.decodeAudioData(altBuffer)
        } catch (altError) {
          await audioContext.close()
          throw new Error('Failed to decode audio. Format may not be supported.')
        }
      }

      await audioContext.close()

      const audioData = audioBuffer.getChannelData(0)

      // Resample to 16kHz
      let processedAudioData: Float32Array = audioData
      if (audioBuffer.sampleRate !== 16000) {
        processedAudioData = resampleAudio(audioData, audioBuffer.sampleRate, 16000)
      }

      // Skip if too quiet
      const rms = Math.sqrt(processedAudioData.reduce((sum, val) => sum + val * val, 0) / processedAudioData.length)
      if (rms < 0.001) return

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const result = await (transcriberRef.current as any)(processedAudioData, {
        language,
        task: 'transcribe',
        return_timestamps: false
      })

      const newText = (result as { text?: string })?.text?.trim() || ''

      if (newText) {
        const processedText = newText.endsWith('.') || newText.endsWith('?') || newText.endsWith('!')
          ? newText
          : newText + '.'

        transcriptRef.current += (transcriptRef.current ? ' ' : '') + processedText
        setTranscript(transcriptRef.current)
        onTranscriptUpdate?.(processedText)
      }

      // Clear processed chunks
      audioChunksRef.current = []
    } catch (error) {
      console.error('Error processing audio:', error)
      onError?.(error as Error)
    } finally {
      setIsTranscribing(false)
    }
  }, [language, onTranscriptUpdate, onError])

  // Process accumulated chunks for streaming
  const processAccumulatedChunks = useCallback(async () => {
    try {
      const chunks = audioChunksRef.current
      if (chunks.length < 3) return

      // Get recent chunks (last 2 seconds worth)
      const recentChunks = chunks.slice(-5)
      const validChunks = recentChunks.filter(chunk => chunk && chunk.size > 2000)
      if (validChunks.length < 2) return

      let mimeType = 'audio/webm;codecs=opus'
      if (mediaRecorderRef.current?.mimeType) {
        mimeType = mediaRecorderRef.current.mimeType
      }

      const tempBlob = new Blob(validChunks, { type: mimeType })
      if (tempBlob.size < 20000) return

      const audioBuffer = await tempBlob.arrayBuffer()
      const audioContext = new AudioContext()

      let audioBufferFromBlob: AudioBuffer
      try {
        audioBufferFromBlob = await audioContext.decodeAudioData(audioBuffer)
      } catch {
        await audioContext.close()
        return
      }

      await audioContext.close()

      const audioData = audioBufferFromBlob.getChannelData(0)
      if (!audioData || audioData.length === 0) return

      let processedAudioData: Float32Array = audioData
      if (audioBufferFromBlob.sampleRate !== 16000) {
        processedAudioData = resampleAudio(audioData, audioBufferFromBlob.sampleRate, 16000)
      }

      // Check for meaningful audio
      const rms = Math.sqrt(processedAudioData.reduce((sum, val) => sum + val * val, 0) / processedAudioData.length)
      if (rms < 0.001) return

      // Limit to 2 seconds
      const maxSamples = 32000
      if (processedAudioData.length > maxSamples) {
        processedAudioData = processedAudioData.slice(-maxSamples)
      }

      if (!transcriberRef.current) return

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const result = await (transcriberRef.current as any)(processedAudioData, {
        language,
        task: 'transcribe',
        return_timestamps: false,
        no_speech_threshold: 0.3
      })

      const text = (result as { text?: string })?.text?.trim() || ''
      if (text && text.length > 2) {
        const processedText = text.endsWith('.') || text.endsWith('?') || text.endsWith('!')
          ? text
          : text + '.'

        // Append if not duplicate
        if (!transcriptRef.current.endsWith(processedText)) {
          transcriptRef.current += (transcriptRef.current ? ' ' : '') + processedText
          setTranscript(transcriptRef.current)
          onTranscriptUpdate?.(processedText)
        }
      }
    } catch (error) {
      console.error('Streaming transcription error:', error)
    }
  }, [language, onTranscriptUpdate])

  // Start recording
  const startRecording = useCallback(async () => {
    try {
      // Initialize model if needed
      if (!modelLoaded) {
        await initializeTranscriber()
      }

      audioChunksRef.current = []

      // Clear periodic timer
      if (periodicTranscriptionRef.current) {
        clearInterval(periodicTranscriptionRef.current)
        periodicTranscriptionRef.current = null
      }

      // Get microphone access
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true,
          sampleRate: 44100,
          channelCount: 1
        }
      })

      streamRef.current = stream

      // Create MediaRecorder with fallback options
      let mediaRecorder: MediaRecorder | null = null
      const mimeOptions = [
        { mimeType: 'audio/webm;codecs=opus' },
        { mimeType: 'audio/webm' },
        { mimeType: 'audio/ogg;codecs=opus' },
        { mimeType: 'audio/mp4' }
      ]

      for (const option of mimeOptions) {
        if (MediaRecorder.isTypeSupported(option.mimeType)) {
          mediaRecorder = new MediaRecorder(stream, option)
          break
        }
      }

      if (!mediaRecorder) {
        throw new Error('No supported audio format found')
      }

      mediaRecorderRef.current = mediaRecorder

      mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 1000) {
          audioChunksRef.current.push(event.data)

          // Limit chunks to prevent memory issues
          if (audioChunksRef.current.length > 20) {
            audioChunksRef.current = audioChunksRef.current.slice(-15)
          }
        }
      }

      mediaRecorder.onstop = () => {
        processAudioChunks()
      }

      mediaRecorder.onstart = () => {
        setIsRecording(true)
        isRecordingRef.current = true

        // Start streaming transcription
        if (enableStreaming) {
          periodicTranscriptionRef.current = setInterval(() => {
            if (isRecordingRef.current) {
              processAccumulatedChunks()
            }
          }, 1000)
        }
      }

      // Start with 1 second chunks
      mediaRecorder.start(1000)
      isRecordingRef.current = true
      setIsRecording(true)
    } catch (error) {
      console.error('Error starting recording:', error)
      onError?.(error as Error)
    }
  }, [processAudioChunks, processAccumulatedChunks, onError, enableStreaming, modelLoaded, initializeTranscriber])

  // Stop recording
  const stopRecording = useCallback(async () => {
    try {
      if (periodicTranscriptionRef.current) {
        clearInterval(periodicTranscriptionRef.current)
        periodicTranscriptionRef.current = null
      }

      if (mediaRecorderRef.current && isRecordingRef.current) {
        mediaRecorderRef.current.stop()
      }

      if (streamRef.current) {
        streamRef.current.getTracks().forEach(track => track.stop())
        streamRef.current = null
      }

      isRecordingRef.current = false
      setIsRecording(false)
    } catch (error) {
      console.error('Error stopping recording:', error)
      onError?.(error as Error)
    }
  }, [onError])

  // Clear transcript
  const clearTranscript = useCallback(() => {
    transcriptRef.current = ''
    setTranscript('')
  }, [])

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (periodicTranscriptionRef.current) {
        clearInterval(periodicTranscriptionRef.current)
      }
      if (mediaRecorderRef.current?.state === 'recording') {
        mediaRecorderRef.current.stop()
      }
      if (streamRef.current) {
        streamRef.current.getTracks().forEach(track => track.stop())
      }
    }
  }, [])

  return {
    isRecording,
    isTranscribing,
    transcript,
    modelLoaded,
    modelLoading,
    startRecording,
    stopRecording,
    clearTranscript,
    initializeTranscriber
  }
}

export default useWhisperTranscription
