import { useState, useRef, useCallback, useEffect } from 'react';
import { MicVAD, utils } from '@ricky0123/vad-web';
import { transcribeAudio } from '@/api/voice';
import { useChatStore } from '@/store/chatStore';
import type { WSClientMessage } from '@/types/chat';

interface UseVoiceInputOptions {
  onTranscription: (text: string) => void;
  language?: string;
  /** 提供后启用流式模式：通过 WebSocket 分片发送音频，实时返回中间结果 */
  sendMessage?: (msg: WSClientMessage) => void;
}

interface UseVoiceInputReturn {
  isListening: boolean;
  isSpeaking: boolean;
  isSupported: boolean;
  /** 流式模式：voice_interim 中间识别文本 */
  interimText: string;
  /** 流式模式：voice_final 最终识别文本 */
  finalText: string;
  error: string | null;
  startListening: () => Promise<void>;
  stopListening: () => void;
}

export function useVoiceInput({
  onTranscription,
  language = 'zh',
  sendMessage,
}: UseVoiceInputOptions): UseVoiceInputReturn {
  const [isListening, setIsListening] = useState(false);
  const [isSpeaking, setIsSpeaking] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const vadRef = useRef<MicVAD | null>(null);
  const inSpeechRef = useRef(false);
  const seqRef = useRef(0);

  const isSupported =
    typeof window !== 'undefined' &&
    typeof AudioWorkletNode !== 'undefined' &&
    typeof WebAssembly !== 'undefined';

  const isStreaming = !!sendMessage;

  // Subscribe to streaming results from store
  const voiceInterimText = useChatStore((s) => s.voiceInterimText);
  const voiceFinalText = useChatStore((s) => s.voiceFinalText);

  const stopListening = useCallback(() => {
    if (vadRef.current) {
      vadRef.current.pause();
    }
    inSpeechRef.current = false;
    setIsListening(false);
    setIsSpeaking(false);
  }, []);

  const startListening = useCallback(async () => {
    if (!isSupported) return;

    setError(null);
    seqRef.current = 0;

    try {
      if (vadRef.current) {
        await vadRef.current.destroy();
        vadRef.current = null;
      }

      vadRef.current = await MicVAD.new({
        onSpeechStart: () => {
          setIsSpeaking(true);
          inSpeechRef.current = true;
          seqRef.current = 0;

          if (isStreaming) {
            sendMessage!({
              type: 'voice_stream_start',
              format: 'wav',
              language,
              timestamp: Date.now(),
            });
          }
        },

        onFrameProcessed: async (_probabilities, frame) => {
          if (!isStreaming || !inSpeechRef.current) return;

          // Accumulate audio frames during speech for the voice_stream_chunk
          // Send every ~300ms of audio (10 frames at ~30ms each)
          const frameSeq = seqRef.current++;
          const CHUNK_INTERVAL = 10; // send every 10 frames

          if (frameSeq % CHUNK_INTERVAL === 0) {
            try {
              const wavBuffer = utils.encodeWAV(frame);
              const base64 = arrayBufferToBase64(wavBuffer);

              sendMessage!({
                type: 'voice_stream_chunk',
                audio_data: base64,
                sequence: Math.floor(frameSeq / CHUNK_INTERVAL),
                timestamp: Date.now(),
              });
            } catch {
              // Ignore encoding errors for individual chunks
            }
          }
        },

        onSpeechEnd: async (audio: Float32Array) => {
          setIsSpeaking(false);
          inSpeechRef.current = false;

          if (isStreaming) {
            // Send the last chunk with accumulated audio if any
            try {
              const wavBuffer = utils.encodeWAV(audio);
              const base64 = arrayBufferToBase64(wavBuffer);

              sendMessage!({
                type: 'voice_stream_chunk',
                audio_data: base64,
                sequence: -1,
                timestamp: Date.now(),
              });
            } catch {
              // Ignore
            }

            // Signal end of voice stream — gateway returns voice_final
            sendMessage!({
              type: 'voice_stream_end',
              timestamp: Date.now(),
            });
            // onTranscription will be called in useEffect when voiceFinalText updates
            return;
          }

          // REST mode: send full audio for transcription
          const wavBuffer = utils.encodeWAV(audio);
          const wavBlob = new Blob([wavBuffer], { type: 'audio/wav' });

          try {
            const result = await transcribeAudio(wavBlob, language);
            if (result.error) {
              setError(result.error);
            } else if (result.data?.text) {
              onTranscription(result.data.text);
            }
          } catch (err) {
            setError(err instanceof Error ? err.message : '语音识别失败');
          }
        },

        onVADMisfire: () => {
          setIsSpeaking(false);
          inSpeechRef.current = false;

          if (isStreaming) {
            sendMessage!({
              type: 'voice_stream_end',
              timestamp: Date.now(),
            });
          }
        },

        startOnLoad: true,
      });

      setIsListening(true);
    } catch (err) {
      const message =
        err instanceof DOMException && err.name === 'NotAllowedError'
          ? '麦克风权限被拒绝'
          : err instanceof Error
            ? err.message
            : 'VAD 初始化失败';
      setError(message);
    }
  }, [isSupported, language, isStreaming, sendMessage, onTranscription]);

  // In streaming mode, watch for voice_final from the server
  useEffect(() => {
    if (isStreaming && voiceFinalText) {
      onTranscription(voiceFinalText);
      useChatStore.getState().setVoiceFinalText('');
    }
  }, [isStreaming, voiceFinalText, onTranscription]);

  useEffect(() => {
    return () => {
      if (vadRef.current) {
        vadRef.current.destroy();
        vadRef.current = null;
      }
    };
  }, []);

  return {
    isListening,
    isSpeaking,
    isSupported,
    interimText: isStreaming ? voiceInterimText : '',
    finalText: isStreaming ? voiceFinalText : '',
    error,
    startListening,
    stopListening,
  };
}

function arrayBufferToBase64(buffer: ArrayBuffer): string {
  const bytes = new Uint8Array(buffer);
  let binary = '';
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}
