export function speechRecognitionConstructor(scope = globalThis) {
  return scope?.SpeechRecognition ?? scope?.webkitSpeechRecognition ?? null;
}

export function supportsVoiceInput(scope = globalThis) {
  return Boolean(speechRecognitionConstructor(scope));
}

export function voiceInputErrorKey(error) {
  if (error === "not-allowed" || error === "service-not-allowed") return "conversation.voicePermissionDenied";
  if (error === "audio-capture") return "conversation.voiceUnavailable";
  return "conversation.voiceFailed";
}

export function appendVoiceTranscript(current, transcript) {
  const base = String(current ?? "").trim();
  const next = String(transcript ?? "").trim();
  if (!next) return base;
  return base ? `${base} ${next}` : next;
}

export function mergeVoiceTranscript(current, transcript) {
  const base = String(current ?? "").trim().replace(/\s+/g, " ");
  const next = String(transcript ?? "").trim().replace(/\s+/g, " ");
  if (!base) return next;
  if (!next || base === next || base.endsWith(next)) return base;
  const baseWords = base.split(" ");
  const nextWords = next.split(" ");
  for (let size = Math.min(baseWords.length, nextWords.length); size > 0; size -= 1) {
    if (baseWords.slice(-size).join(" ") === nextWords.slice(0, size).join(" ")) {
      return [...baseWords, ...nextWords.slice(size)].join(" ");
    }
  }
  return `${base} ${next}`;
}

export function buildVoiceWaveBars(level, previous = [], count = 30) {
  const normalized = Math.max(0, Math.min(1, Number(level) || 0));
  const quiet = normalized === 0 ? 0.08 : normalized;
  const echo = quiet > 0.5 ? quiet * 0.96 : quiet;
  const bars = previous.slice(-(count - 2));
  bars.push(quiet, echo);
  while (bars.length < count) bars.unshift(quiet);
  return bars.map((bar, index) => {
    const pulse = normalized === 0 && index >= count - 2 ? 0.08 : Number(bar) || 0;
    return Math.max(0.08, Math.min(1, Math.round(pulse * 100) / 100));
  });
}

export function createVoiceRecognition({ scope = globalThis, onText, onInterimText, onTranscript, onDiagnostic, onError, onEnd } = {}) {
  const Recognition = speechRecognitionConstructor(scope);
  if (!Recognition) return { supported: false };

  const recognition = new Recognition();
  recognition.lang = "ko-KR";
  recognition.continuous = true;
  recognition.interimResults = true;
  let explicitStop = false;
  let terminalError = false;
  let completedCyclesText = "";
  let resultSegments = [];

  recognition.onstart = () => onDiagnostic?.("recognition_started");
  recognition.onaudiostart = () => onDiagnostic?.("audio_started");
  recognition.onspeechstart = () => onDiagnostic?.("speech_detected");

  recognition.onresult = (event) => {
    onDiagnostic?.("result_received");
    const results = Array.from(event.results ?? []);
    const resultIndex = Math.max(0, Number(event.resultIndex) || 0);
    resultSegments.length = results.length;
    for (let index = resultIndex; index < results.length; index += 1) {
      const result = results[index];
      resultSegments[index] = {
        text: (result[0] ?? result.item?.(0))?.transcript ?? "",
        final: Boolean(result.isFinal)
      };
    }
    const textFor = (items) => items
      .map((result) => (result[0] ?? result.item?.(0))?.transcript ?? "")
      .join(" ")
      .trim();
    const finalText = textFor(results.filter((result) => result.isFinal));
    if (finalText) onText?.(finalText);
    else {
      const interimText = textFor(results.filter((result) => !result.isFinal));
      if (interimText) onInterimText?.(interimText);
    }
    const cycleText = resultSegments.reduce(
      (text, segment) => mergeVoiceTranscript(text, segment?.text ?? ""),
      ""
    );
    const transcript = mergeVoiceTranscript(completedCyclesText, cycleText);
    if (transcript) onTranscript?.(transcript);
  };
  recognition.onerror = (event) => {
    const error = event.error || "voice_input_failed";
    onDiagnostic?.(`error:${error}`);
    if (error === "no-speech" || error === "aborted") return;
    terminalError = true;
    onError?.(error);
  };
  recognition.onend = () => {
    if (explicitStop || terminalError) {
      onEnd?.();
      return;
    }
    completedCyclesText = mergeVoiceTranscript(
      completedCyclesText,
      resultSegments.reduce(
        (text, segment) => mergeVoiceTranscript(text, segment?.text ?? ""),
        ""
      )
    );
    resultSegments = [];
    onDiagnostic?.("recognition_restarting");
    try {
      recognition.start();
    } catch {
      terminalError = true;
      onError?.("voice_input_failed");
      onEnd?.();
    }
  };

  return {
    supported: true,
    recognition,
    start: () => {
      explicitStop = false;
      terminalError = false;
      recognition.start();
    },
    stop: () => {
      explicitStop = true;
      recognition.stop();
    }
  };
}
