'use client'

/**
 * Push-to-talk кнопка-микрофон. Тап → MicVAD ловит речь → автостоп по тишине →
 * /api/voice/stt → /api/voice/chat. Отправляет локальные voice-local события
 * для VoiceOverlay (wake/listening/command), финальный response приходит
 * через SSE с сервера.
 *
 * Когда добавим wake-word (Шаг 3) — этот же код переиспользуется, только
 * стартовать VAD будет автоматически по детекту wake-слова.
 */
import { useEffect, useRef, useState } from 'react'
import { Mic, MicOff } from 'lucide-react'

type Agent = 'cosmo' | 'lusya'
type ControllerState = 'idle' | 'loading' | 'active' | 'busy' | 'error'

const AGENT: Agent = 'cosmo' // на этом этапе всегда Cosmo; Люся через wake-word на Шаге 3

function emitLocal(event: string, agent: Agent, text?: string) {
  window.dispatchEvent(
    new CustomEvent('voice-local', {
      detail: { event, agent, text, timestamp: new Date().toISOString() },
    }),
  )
}

// Float32Array @ 16kHz → WAV blob (mono, 16-bit PCM).
function floatToWav(audio: Float32Array, sampleRate = 16000): Blob {
  const numSamples = audio.length
  const buffer = new ArrayBuffer(44 + numSamples * 2)
  const view = new DataView(buffer)
  // RIFF header
  writeStr(view, 0, 'RIFF')
  view.setUint32(4, 36 + numSamples * 2, true)
  writeStr(view, 8, 'WAVE')
  writeStr(view, 12, 'fmt ')
  view.setUint32(16, 16, true)        // fmt chunk size
  view.setUint16(20, 1, true)         // PCM
  view.setUint16(22, 1, true)         // channels
  view.setUint32(24, sampleRate, true)
  view.setUint32(28, sampleRate * 2, true) // byte rate
  view.setUint16(32, 2, true)         // block align
  view.setUint16(34, 16, true)        // bits per sample
  writeStr(view, 36, 'data')
  view.setUint32(40, numSamples * 2, true)
  // PCM samples
  let offset = 44
  for (let i = 0; i < numSamples; i++, offset += 2) {
    const s = Math.max(-1, Math.min(1, audio[i]))
    view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
  }
  return new Blob([buffer], { type: 'audio/wav' })
}

function writeStr(view: DataView, offset: number, s: string) {
  for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))
}

export default function VoiceController() {
  const [state, setState] = useState<ControllerState>('idle')
  const vadRef = useRef<any>(null)
  const busyRef = useRef(false)

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      try { vadRef.current?.destroy?.() } catch {}
      vadRef.current = null
    }
  }, [])

  const handleSpeechEnd = async (audio: Float32Array) => {
    if (busyRef.current) return
    if (audio.length < 16000 * 0.4) return // <0.4с — мусор/эхо
    busyRef.current = true
    setState('busy')
    emitLocal('listening', AGENT) // показываем что обрабатываем

    try {
      const wav = floatToWav(audio, 16000)
      const sttResp = await fetch('/api/voice/stt', {
        method: 'POST',
        headers: { 'Content-Type': 'audio/wav' },
        body: wav,
      })
      if (!sttResp.ok) throw new Error(`stt ${sttResp.status}`)
      const { text } = await sttResp.json()
      const userText = (text || '').trim()
      if (!userText || userText.length < 2) {
        emitLocal('idle', AGENT)
        return
      }

      // Chat-эндпоинт сам эмитит command/response через voice-bus → SSE → orb.
      const chatResp = await fetch('/api/voice/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text: userText, agent: AGENT }),
      })
      if (!chatResp.ok) throw new Error(`chat ${chatResp.status}`)
      // Ответ уже зачитывается через SSE — здесь больше делать нечего.
    } catch (e) {
      console.error('[voice] pipeline error:', e)
      emitLocal('error', AGENT, 'Не получилось')
    } finally {
      busyRef.current = false
      setState((s) => (s === 'busy' ? 'active' : s))
    }
  }

  const start = async () => {
    if (state !== 'idle' && state !== 'error') return
    setState('loading')
    try {
      const { MicVAD } = await import('@ricky0123/vad-web')
      const vad = await MicVAD.new({
        model: 'v5',
        baseAssetPath: '/vad/',
        onnxWASMBasePath: '/vad/',
        // sensitivity tuned conservatively — тише = меньше ложных срабатываний эха
        positiveSpeechThreshold: 0.6,
        negativeSpeechThreshold: 0.45,
        minSpeechMs: 160,    // ~5 фреймов по 32мс
        redemptionMs: 750,   // тишина перед onSpeechEnd
        onSpeechStart: () => {
          emitLocal('wake', AGENT)
        },
        onSpeechEnd: handleSpeechEnd,
      })
      vadRef.current = vad
      vad.start()
      setState('active')
    } catch (e) {
      console.error('[voice] VAD init failed:', e)
      setState('error')
      emitLocal('error', AGENT, 'Микрофон недоступен')
    }
  }

  const stop = () => {
    try { vadRef.current?.pause?.() } catch {}
    try { vadRef.current?.destroy?.() } catch {}
    vadRef.current = null
    setState('idle')
    emitLocal('idle', AGENT)
  }

  const onTap = () => {
    if (state === 'idle' || state === 'error') start()
    else stop()
  }

  const isActive = state === 'active' || state === 'busy'
  const isLoading = state === 'loading'

  return (
    <button
      onClick={onTap}
      data-swipe-ignore
      aria-label={isActive ? 'Выключить микрофон' : 'Включить микрофон'}
      style={{
        position: 'fixed',
        right: 24,
        bottom: 24,
        zIndex: 250,
        width: 64,
        height: 64,
        borderRadius: '50%',
        border: 'none',
        cursor: 'pointer',
        background: isActive
          ? 'linear-gradient(135deg, #7c3aed 0%, #a5b4fc 100%)'
          : 'rgba(255,255,255,0.08)',
        backdropFilter: 'blur(20px)',
        WebkitBackdropFilter: 'blur(20px)' as any,
        boxShadow: isActive
          ? '0 0 32px rgba(124, 58, 237, 0.6), 0 8px 24px rgba(0,0,0,0.4)'
          : '0 4px 12px rgba(0,0,0,0.3)',
        color: isActive ? '#fff' : 'rgba(255,255,255,0.65)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        transition: 'background 0.25s, box-shadow 0.25s, transform 0.15s',
        transform: isLoading ? 'scale(0.92)' : 'scale(1)',
      }}
    >
      {isActive ? <Mic size={28} /> : <MicOff size={28} />}
    </button>
  )
}
