import { useCallback, useEffect, useRef, useState } from "react";

interface BrowserSpeechRecognitionAlternative {
  transcript: string;
}

interface BrowserSpeechRecognitionResult {
  isFinal: boolean;
  length: number;
  [index: number]: BrowserSpeechRecognitionAlternative;
}

interface BrowserSpeechRecognitionResultList {
  length: number;
  [index: number]: BrowserSpeechRecognitionResult;
}

interface BrowserSpeechRecognitionEvent extends Event {
  resultIndex: number;
  results: BrowserSpeechRecognitionResultList;
}

interface BrowserSpeechRecognitionErrorEvent extends Event {
  error: string;
}

interface BrowserSpeechRecognition extends EventTarget {
  continuous: boolean;
  interimResults: boolean;
  lang: string;
  maxAlternatives: number;
  onend: ((event: Event) => void) | null;
  onerror: ((event: BrowserSpeechRecognitionErrorEvent) => void) | null;
  onresult: ((event: BrowserSpeechRecognitionEvent) => void) | null;
  onstart: ((event: Event) => void) | null;
  abort: () => void;
  start: () => void;
  stop: () => void;
}

interface BrowserSpeechRecognitionConstructor {
  new (): BrowserSpeechRecognition;
}

declare global {
  interface Window {
    SpeechRecognition?: BrowserSpeechRecognitionConstructor;
    webkitSpeechRecognition?: BrowserSpeechRecognitionConstructor;
  }
}

export interface SpeechRecognitionMessages {
  applied: string;
  errors: Partial<Record<string, string>>;
  listening: string;
  startFailed: string;
  unsupported: string;
}

export interface UseSpeechRecognitionInputOptions {
  lang?: string;
  mergeTranscript?: (baseValue: string, transcript: string) => string;
  messages?: Partial<SpeechRecognitionMessages>;
  onValueChange: (nextValue: string) => void;
  value: string;
}

export interface UseSpeechRecognitionInputResult {
  abortListening: () => void;
  clearStatus: () => void;
  error: string | null;
  feedback: string | null;
  isListening: boolean;
  isSupported: boolean;
  startListening: () => void;
  stopListening: () => void;
  toggleListening: () => void;
}

const defaultSpeechRecognitionErrorMessages: Record<string, string> = {
  "audio-capture": "사용 가능한 마이크를 찾지 못했어요. 기기 연결 상태를 확인해주세요.",
  "network": "음성 인식 연결이 불안정해요. 네트워크 상태를 확인한 뒤 다시 시도해주세요.",
  "no-speech": "음성이 들리지 않았어요. 조금 더 가까이에서 다시 말씀해주세요.",
  "not-allowed": "마이크 권한이 필요해요. 브라우저 설정에서 마이크 접근을 허용해주세요.",
  "service-not-allowed": "마이크 권한이 필요해요. 브라우저 설정에서 마이크 접근을 허용해주세요.",
};

const defaultSpeechRecognitionMessages: SpeechRecognitionMessages = {
  applied: "음성 입력이 반영됐어요. 필요하면 바로 수정할 수 있어요.",
  errors: defaultSpeechRecognitionErrorMessages,
  listening: "듣는 중이에요. 말씀을 마치면 자동으로 입력돼요.",
  startFailed: "음성 입력을 시작하지 못했어요. 잠시 후 다시 시도해주세요.",
  unsupported: "현재 브라우저에서는 음성 입력을 지원하지 않아요. 외부 브라우저에서 다시 시도해주세요.",
};

function getSpeechRecognitionConstructor(): BrowserSpeechRecognitionConstructor | null {
  if (typeof window === "undefined") {
    return null;
  }

  return window.SpeechRecognition ?? window.webkitSpeechRecognition ?? null;
}

function resolveSpeechRecognitionMessages(messages?: Partial<SpeechRecognitionMessages>): SpeechRecognitionMessages {
  return {
    ...defaultSpeechRecognitionMessages,
    ...messages,
    errors: {
      ...defaultSpeechRecognitionErrorMessages,
      ...(messages?.errors ?? {}),
    },
  };
}

export function mergeSpeechRecognitionText(baseValue: string, transcript: string) {
  const trimmedBaseValue = baseValue.trim();
  const trimmedTranscript = transcript.trim();

  if (!trimmedBaseValue) {
    return trimmedTranscript;
  }

  if (!trimmedTranscript) {
    return trimmedBaseValue;
  }

  return `${trimmedBaseValue} ${trimmedTranscript}`;
}

export function getSpeechRecognitionErrorMessage(error: string, messages?: Partial<Record<string, string>>) {
  if (error === "aborted") {
    return null;
  }

  return messages?.[error] ?? defaultSpeechRecognitionErrorMessages[error] ?? defaultSpeechRecognitionMessages.startFailed;
}

export function useSpeechRecognitionInput({
  lang,
  mergeTranscript = mergeSpeechRecognitionText,
  messages,
  onValueChange,
  value,
}: UseSpeechRecognitionInputOptions): UseSpeechRecognitionInputResult {
  const recognitionRef = useRef<BrowserSpeechRecognition | null>(null);
  const baseValueRef = useRef(value);
  const valueRef = useRef(value);
  const onValueChangeRef = useRef(onValueChange);
  const [isListening, setIsListening] = useState(false);
  const [feedback, setFeedback] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const isSupported = Boolean(getSpeechRecognitionConstructor());
  const resolvedMessages = resolveSpeechRecognitionMessages(messages);

  useEffect(() => {
    valueRef.current = value;
    onValueChangeRef.current = onValueChange;
  }, [onValueChange, value]);

  const clearStatus = useCallback(() => {
    setError(null);
    setFeedback(null);
  }, []);

  const abortListening = useCallback(() => {
    recognitionRef.current?.abort();
    recognitionRef.current = null;
    setIsListening(false);
    setFeedback(null);
  }, []);

  const stopListening = useCallback(() => {
    recognitionRef.current?.stop();
    setFeedback(null);
  }, []);

  const startListening = useCallback(() => {
    if (recognitionRef.current) {
      recognitionRef.current.abort();
      recognitionRef.current = null;
    }

    const SpeechRecognition = getSpeechRecognitionConstructor();

    if (!SpeechRecognition) {
      setError(resolvedMessages.unsupported);
      setFeedback(null);
      setIsListening(false);
      return;
    }

    const recognition = new SpeechRecognition();
    recognition.lang = lang ?? (typeof navigator !== "undefined" && navigator.language ? navigator.language : "ko-KR");
    recognition.continuous = false;
    recognition.interimResults = true;
    recognition.maxAlternatives = 1;
    baseValueRef.current = valueRef.current;

    recognition.onstart = () => {
      setIsListening(true);
      setError(null);
      setFeedback(resolvedMessages.listening);
    };

    recognition.onresult = (event) => {
      let transcript = "";
      let hasFinalResult = false;

      for (let index = event.resultIndex; index < event.results.length; index += 1) {
        transcript += `${event.results[index][0].transcript} `;
        hasFinalResult = hasFinalResult || event.results[index].isFinal;
      }

      onValueChangeRef.current(mergeTranscript(baseValueRef.current, transcript));
      setError(null);
      setFeedback(hasFinalResult ? resolvedMessages.applied : resolvedMessages.listening);
    };

    recognition.onerror = (event) => {
      setIsListening(false);
      recognitionRef.current = null;
      setFeedback(null);
      setError(getSpeechRecognitionErrorMessage(event.error, resolvedMessages.errors));
    };

    recognition.onend = () => {
      setIsListening(false);
      recognitionRef.current = null;
    };

    recognitionRef.current = recognition;

    try {
      recognition.start();
    } catch {
      recognitionRef.current = null;
      setIsListening(false);
      setFeedback(null);
      setError(resolvedMessages.startFailed);
    }
  }, [lang, mergeTranscript, resolvedMessages]);

  const toggleListening = useCallback(() => {
    if (isListening) {
      stopListening();
      return;
    }

    startListening();
  }, [isListening, startListening, stopListening]);

  useEffect(
    () => () => {
      recognitionRef.current?.abort();
      recognitionRef.current = null;
    },
    [],
  );

  return {
    abortListening,
    clearStatus,
    error,
    feedback,
    isListening,
    isSupported,
    startListening,
    stopListening,
    toggleListening,
  };
}
