import { ParticipantKind } from "@livekit/rtc-node";
import { describe, expect, it, vi } from "vitest";
import { ChatContext } from "../llm/chat_context.js";
import { initializeLogger } from "../log.js";
import { SpeechEventType } from "../stt/stt.js";
import {
  AudioRecognition
} from "./audio_recognition.js";
function makeHooks() {
  return {
    onInterruption: vi.fn(),
    onStartOfSpeech: vi.fn(),
    onVADInferenceDone: vi.fn(),
    onEndOfSpeech: vi.fn(),
    onInterimTranscript: vi.fn(),
    onFinalTranscript: vi.fn(),
    onPreemptiveGeneration: vi.fn(),
    onAgentBackchannelOpportunity: vi.fn(),
    onUserTurnExceeded: vi.fn(),
    retrieveChatCtx: () => ChatContext.empty(),
    onEndOfTurn: vi.fn(async () => true)
  };
}
function makeRecognition() {
  const opts = {
    recognitionHooks: makeHooks(),
    stt: void 0,
    vad: void 0,
    interruptionDetection: void 0,
    turnDetectionMode: "stt",
    minEndpointingDelay: 0,
    maxEndpointingDelay: 0,
    getLinkedParticipant: () => ({ sid: "p1", identity: "bob", kind: ParticipantKind.AGENT })
  };
  const recognition = new AudioRecognition(opts);
  return { recognition, internals: recognition };
}
describe("AudioRecognition STT end-of-speech VAD reset", () => {
  initializeLogger({ pretty: false, level: "silent" });
  it("flushes the active VAD stream without restarting VAD", async () => {
    var _a;
    const { recognition, internals } = makeRecognition();
    internals.speaking = true;
    internals.vadSpeechStarted = true;
    internals.vad = {};
    const flush = vi.fn();
    const resettableStream = { flush };
    internals.vadStream = resettableStream;
    const resetVad = vi.spyOn(recognition, "resetVad").mockImplementation(() => {
    });
    try {
      await internals.onSTTEvent({ type: SpeechEventType.END_OF_SPEECH });
      expect(flush).toHaveBeenCalledTimes(1);
      expect(resetVad).not.toHaveBeenCalled();
      expect(internals.vadStream).toBe(resettableStream);
    } finally {
      await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
      }));
    }
  });
  it("falls back to restarting VAD when there is no active stream", async () => {
    var _a;
    const { recognition, internals } = makeRecognition();
    internals.speaking = true;
    internals.vadSpeechStarted = true;
    internals.vad = {};
    internals.vadStream = void 0;
    const resetVad = vi.spyOn(recognition, "resetVad").mockImplementation(() => {
    });
    try {
      await internals.onSTTEvent({ type: SpeechEventType.END_OF_SPEECH });
      expect(resetVad).toHaveBeenCalledTimes(1);
    } finally {
      await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
      }));
    }
  });
  it("preserves the VAD-owned lastSpeakingTime on STT end-of-speech", async () => {
    var _a;
    const { recognition, internals } = makeRecognition();
    internals.vad = {};
    const vadSpeakingTime = 12345;
    internals.lastSpeakingTime = vadSpeakingTime;
    vi.spyOn(recognition, "runEOUDetection").mockImplementation(() => {
    });
    try {
      await internals.onSTTEvent({ type: SpeechEventType.END_OF_SPEECH });
      expect(internals.lastSpeakingTime).toBe(vadSpeakingTime);
    } finally {
      await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
      }));
    }
  });
  it("stamps lastSpeakingTime on STT end-of-speech when there is no VAD", async () => {
    var _a;
    const { recognition, internals } = makeRecognition();
    internals.vad = void 0;
    internals.lastSpeakingTime = void 0;
    vi.spyOn(recognition, "runEOUDetection").mockImplementation(() => {
    });
    try {
      await internals.onSTTEvent({ type: SpeechEventType.END_OF_SPEECH });
      expect(internals.lastSpeakingTime).toBeTypeOf("number");
    } finally {
      await ((_a = internals.bounceEOUTask) == null ? void 0 : _a.cancelAndWait().catch(() => {
      }));
    }
  });
});
//# sourceMappingURL=audio_recognition_vad_reset.test.js.map