import { useCallback, useEffect, useRef, useState } from 'react';
import { transcribeOneShot } from '../voice/asrTransport';
import { mapMicError } from '../voice/audioAnalyser';
import { shouldFallbackToServerAsr, shouldPreferServerAsr } from '../voice/capabilities';
import { MicCapture } from '../voice/micCapture';
import { waitForMicRelease } from '../voice/micSession';
import { createLiveSpeechRecognition, isSpeechRecognitionSupported } from '../voice/speechRecognition';
import type { VoiceSessionPhase } from '../voice/types';

const MIN_RECORD_MS = 500;

export function useVoiceSession({
  active,
  onError,
}: {
  active: boolean;
  onError?: (message: string) => void;
}) {
  const [phase, setPhase] = useState<VoiceSessionPhase>('idle');
  const [text, setText] = useState('');
  const [analyser, setAnalyser] = useState<AnalyserNode | null>(null);
  const [liveRecognition, setLiveRecognition] = useState(
    isSpeechRecognitionSupported() && !shouldPreferServerAsr(),
  );

  const committedRef = useRef('');
  const interimRef = useRef('');
  const userEditedRef = useRef(false);
  const captureRef = useRef<MicCapture | null>(null);
  const recognitionRef = useRef<ReturnType<typeof createLiveSpeechRecognition> | null>(null);
  const startedAtRef = useRef(0);
  const sessionRef = useRef(0);
  const stopTimerRef = useRef<number | null>(null);

  const syncDisplayText = useCallback(() => {
    if (userEditedRef.current) return;
    setText(`${committedRef.current}${interimRef.current}`);
  }, []);

  const resetSession = useCallback(() => {
    committedRef.current = '';
    interimRef.current = '';
    userEditedRef.current = false;
    setText('');
    setAnalyser(null);
    setPhase('idle');
  }, []);

  const cleanup = useCallback(async () => {
    if (stopTimerRef.current != null) {
      window.clearTimeout(stopTimerRef.current);
      stopTimerRef.current = null;
    }
    recognitionRef.current?.abort();
    recognitionRef.current = null;
    const capture = captureRef.current;
    captureRef.current = null;
    if (capture) {
      await capture.cancel();
    }
    setAnalyser(null);
  }, []);

  const stopListening = useCallback(() => {
    const recognition = recognitionRef.current;
    if (recognition) {
      recognition.stop();
      recognitionRef.current = null;
      if (stopTimerRef.current != null) {
        window.clearTimeout(stopTimerRef.current);
      }
      stopTimerRef.current = window.setTimeout(() => {
        stopTimerRef.current = null;
        setPhase((current) => (current === 'transcribing' ? current : 'ready'));
      }, 220);
    }
    const capture = captureRef.current;
    captureRef.current = null;
    if (capture) {
      void capture.cancel();
    }
    setAnalyser(null);
    if (!recognition) {
      setPhase((current) => (current === 'transcribing' ? current : 'ready'));
    }
  }, []);

  const startServerAsrCapture = useCallback(
    async (sessionId: number) => {
      recognitionRef.current?.abort();
      recognitionRef.current = null;
      const capture = new MicCapture();
      captureRef.current = capture;
      await capture.start();
      if (sessionRef.current !== sessionId) {
        await capture.cancel();
        captureRef.current = null;
        return false;
      }
      setAnalyser(capture.levelAnalyser);
      setLiveRecognition(false);
      setPhase('listening');
      return true;
    },
    [],
  );

  const finishFallbackRecording = useCallback(async () => {
    const duration = Date.now() - startedAtRef.current;
    const capture = captureRef.current;
    captureRef.current = null;
    setAnalyser(null);

    if (duration < MIN_RECORD_MS) {
      if (capture) await capture.cancel();
      setPhase('ready');
      onError?.('说话时间太短');
      return;
    }

    setPhase('transcribing');
    try {
      if (!capture) throw new Error('未录制到音频');
      const blob = await capture.stop();
      if (!blob.size) throw new Error('未录制到音频');
      const transcript = await transcribeOneShot(blob);
      if (!transcript) {
        onError?.('未识别到语音，请重试');
      } else {
        committedRef.current = transcript;
        userEditedRef.current = false;
        setText(transcript);
      }
    } catch (err) {
      onError?.(err instanceof Error ? err.message : '识别失败');
    } finally {
      setPhase('ready');
    }
  }, [onError]);

  useEffect(() => {
    if (!active) {
      void cleanup().then(resetSession);
      return undefined;
    }

    const sessionId = sessionRef.current + 1;
    sessionRef.current = sessionId;
    resetSession();
    setPhase('requesting');

    const start = async () => {
      try {
        await waitForMicRelease();
        if (sessionRef.current !== sessionId) return;

        startedAtRef.current = Date.now();
        const useLiveSpeech = isSpeechRecognitionSupported() && !shouldPreferServerAsr();

        if (useLiveSpeech) {
          const recognition = createLiveSpeechRecognition({
            onInterim: (chunk) => {
              interimRef.current = chunk;
              syncDisplayText();
            },
            onFinal: (chunk) => {
              committedRef.current += chunk;
              interimRef.current = '';
              syncDisplayText();
            },
            onError: (message) => {
              if (shouldFallbackToServerAsr(message)) {
                void startServerAsrCapture(sessionId)
                  .then((ok) => {
                    if (!ok) return;
                    onError?.('实时识别不可用，已切换为录音识别，说完后点「完成识别」');
                  })
                  .catch((err) => onError?.(mapMicError(err)));
                return;
              }
              onError?.(message);
            },
          });
          if (!recognition) {
            if (MicCapture.isSupported()) {
              const ok = await startServerAsrCapture(sessionId);
              if (ok) return;
            }
            throw new Error('当前浏览器不支持实时语音识别');
          }
          recognitionRef.current = recognition;
          recognition.start();
          if (sessionRef.current !== sessionId) {
            recognition.abort();
            return;
          }
          setLiveRecognition(true);
          setAnalyser(null);
          setPhase('listening');
          return;
        }

        const capture = new MicCapture();
        captureRef.current = capture;
        await capture.start();
        if (sessionRef.current !== sessionId) {
          await capture.cancel();
          return;
        }
        setAnalyser(capture.levelAnalyser);
        setLiveRecognition(false);
        setPhase('listening');
      } catch (err) {
        if (sessionRef.current !== sessionId) return;
        await cleanup();
        setPhase('error');
        onError?.(mapMicError(err));
      }
    };

    void start();

    return () => {
      void cleanup();
    };
  }, [active, cleanup, onError, resetSession, startServerAsrCapture, syncDisplayText]);

  const updateText = useCallback((value: string) => {
    userEditedRef.current = true;
    committedRef.current = value;
    interimRef.current = '';
    setText(value);
  }, []);

  return {
    phase,
    text,
    analyser,
    liveRecognition,
    updateText,
    stopListening,
    finishFallbackRecording,
    resetSession,
  };
}
