import test from "node:test";
import assert from "node:assert/strict";
import { appendVoiceTranscript, buildVoiceWaveBars, createVoiceRecognition, mergeVoiceTranscript, supportsVoiceInput, voiceInputErrorKey } from "./voice-input.js";

test("voice input maps permission failures to safe user-facing copy keys", () => {
  assert.equal(voiceInputErrorKey("not-allowed"), "conversation.voicePermissionDenied");
  assert.equal(voiceInputErrorKey("service-not-allowed"), "conversation.voicePermissionDenied");
  assert.equal(voiceInputErrorKey("audio-capture"), "conversation.voiceUnavailable");
  assert.equal(voiceInputErrorKey("network"), "conversation.voiceFailed");
});

test("voice input detects standard and webkit speech recognition support", () => {
  assert.equal(supportsVoiceInput({ SpeechRecognition: function Recognition() {} }), true);
  assert.equal(supportsVoiceInput({ webkitSpeechRecognition: function Recognition() {} }), true);
  assert.equal(supportsVoiceInput({}), false);
});

test("voice input reports safe recognition lifecycle diagnostics", () => {
  const calls = [];
  class Recognition {
    start() {}
    stop() {}
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onDiagnostic: (status) => calls.push(status)
  });

  voice.recognition.onstart();
  voice.recognition.onaudiostart();
  voice.recognition.onspeechstart();
  voice.recognition.onresult({ results: [{ 0: { transcript: "hello" }, isFinal: false }] });
  voice.recognition.onerror({ error: "network", message: "private browser detail" });

  assert.deepEqual(calls, ["recognition_started", "audio_started", "speech_detected", "result_received", "error:network"]);
  assert.equal(calls.some((item) => item.includes("private browser detail")), false);
});

test("voice input reports interim text when final text is not available", () => {
  const calls = [];
  class Recognition {
    start() {}
    stop() {}
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onText: (text) => calls.push(`text:${text}`),
    onInterimText: (text) => calls.push(`interim:${text}`)
  });

  voice.recognition.onresult({
    results: [
      { 0: { transcript: "draft one" }, isFinal: false },
      { 0: { transcript: "draft two" }, isFinal: false }
    ]
  });

  assert.deepEqual(calls, ["interim:draft one draft two"]);
});

test("voice input reads transcripts from item accessors", () => {
  const calls = [];
  class Recognition {
    start() {}
    stop() {}
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onText: (text) => calls.push(text)
  });

  voice.recognition.onresult({
    results: [
      { item: () => ({ transcript: "send this" }), isFinal: true }
    ]
  });

  assert.deepEqual(calls, ["send this"]);
});

test("voice input appends recognized text to the current draft", () => {
  assert.equal(appendVoiceTranscript("", "된고야?"), "된고야?");
  assert.equal(appendVoiceTranscript("확인해줘", "바로 보내"), "확인해줘 바로 보내");
  assert.equal(appendVoiceTranscript("확인해줘 ", " 바로 보내 "), "확인해줘 바로 보내");
  assert.equal(appendVoiceTranscript("확인해줘", ""), "확인해줘");
});

test("voice input merges overlapping recognition cycles without duplicate words", () => {
  assert.equal(mergeVoiceTranscript("please check this", "check this carefully"), "please check this carefully");
  assert.equal(mergeVoiceTranscript("same phrase", "same phrase"), "same phrase");
  assert.equal(mergeVoiceTranscript("first", "second"), "first second");
});

test("voice input accumulates final and interim results without replaying earlier segments", () => {
  const calls = [];
  class Recognition {
    start() {}
    stop() {}
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onTranscript: (text) => calls.push(text)
  });

  const first = { 0: { transcript: "first part" }, isFinal: true };
  voice.recognition.onresult({ resultIndex: 0, results: [first] });
  voice.recognition.onresult({
    resultIndex: 1,
    results: [first, { 0: { transcript: "second draft" }, isFinal: false }]
  });
  voice.recognition.onresult({
    resultIndex: 1,
    results: [first, { 0: { transcript: "second part" }, isFinal: true }]
  });

  assert.deepEqual(calls, ["first part", "first part second draft", "first part second part"]);
});

test("voice input collapses cumulative browser segments within one result event", () => {
  const calls = [];
  class Recognition {
    start() {}
    stop() {}
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onTranscript: (text) => calls.push(text)
  });

  voice.recognition.onresult({
    resultIndex: 0,
    results: [
      { 0: { transcript: "지금부터" }, isFinal: true },
      { 0: { transcript: "지금부터" }, isFinal: true },
      { 0: { transcript: "지금부터 프로젝트" }, isFinal: true },
      { 0: { transcript: "지금부터 프로젝트 녹화를 시작할 거야" }, isFinal: true }
    ]
  });

  assert.deepEqual(calls, ["지금부터 프로젝트 녹화를 시작할 거야"]);
});

test("voice input restarts after an unexpected browser end and finishes only after explicit stop", () => {
  const calls = [];
  class Recognition {
    start() { calls.push("start"); }
    stop() { calls.push("stop"); }
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onEnd: () => calls.push("end")
  });

  voice.start();
  voice.recognition.onend();
  voice.stop();
  voice.recognition.onend();

  assert.deepEqual(calls, ["start", "start", "stop", "end"]);
});

test("voice input maps live volume into stable waveform bars", () => {
  assert.deepEqual(buildVoiceWaveBars(0, [0.1, 0.2, 0.3], 3), [0.3, 0.08, 0.08]);
  assert.deepEqual(buildVoiceWaveBars(0.2, [0.1, 0.2], 4), [0.1, 0.2, 0.2, 0.2]);
  assert.deepEqual(buildVoiceWaveBars(1, [0.4], 3), [0.4, 1, 0.96]);
  assert.equal(buildVoiceWaveBars(0.3).length, 30);
});

test("voice input creates a Korean continuous recognizer with callbacks", () => {
  const calls = [];
  class Recognition {
    start() { calls.push("start"); }
    stop() { calls.push("stop"); }
  }

  const voice = createVoiceRecognition({
    scope: { SpeechRecognition: Recognition },
    onText: (text) => calls.push(`text:${text}`),
    onError: (error) => calls.push(`error:${error}`),
    onEnd: () => calls.push("end")
  });

  assert.equal(voice.supported, true);
  assert.equal(voice.recognition.lang, "ko-KR");
  assert.equal(voice.recognition.continuous, true);
  assert.equal(voice.recognition.interimResults, true);

  voice.recognition.onresult({
    results: [
      { 0: { transcript: "임시" }, isFinal: false },
      { 0: { transcript: "최종" }, isFinal: true }
    ]
  });
  voice.recognition.onerror({ error: "not-allowed" });
  voice.recognition.onend();
  voice.start();
  voice.stop();

  assert.deepEqual(calls, ["text:최종", "error:not-allowed", "end", "start", "stop"]);
});
