"use strict";
var import_rtc_node = require("@livekit/rtc-node");
var import_vitest = require("vitest");
var import_chat_context = require("../llm/chat_context.cjs");
var import_log = require("../log.cjs");
var import_audio_recognition = require("./audio_recognition.cjs");
var import_utils = require("./utils.cjs");
function createHooks() {
  return {
    onInterruption: import_vitest.vi.fn(),
    onStartOfSpeech: import_vitest.vi.fn(),
    onVADInferenceDone: import_vitest.vi.fn(),
    onEndOfSpeech: import_vitest.vi.fn(),
    onInterimTranscript: import_vitest.vi.fn(),
    onFinalTranscript: import_vitest.vi.fn(),
    onEndOfTurn: import_vitest.vi.fn(async () => true),
    onPreemptiveGeneration: import_vitest.vi.fn(),
    onAgentBackchannelOpportunity: import_vitest.vi.fn(),
    retrieveChatCtx: () => import_chat_context.ChatContext.empty()
  };
}
function markerFrame(byte, samples = 160, sampleRate = 16e3) {
  const data = new Int16Array(samples).fill(byte);
  return new import_rtc_node.AudioFrame(data, sampleRate, 1, samples);
}
function isAllZero(frame) {
  for (const v of frame.data) {
    if (v !== 0) return false;
  }
  return true;
}
async function flushTasks() {
  await new Promise((resolve) => setTimeout(resolve, 0));
}
async function waitFor(check, timeoutMs = 1e3) {
  const startedAt = Date.now();
  while (!check()) {
    if (Date.now() - startedAt > timeoutMs) {
      throw new Error("timed out waiting for condition");
    }
    await flushTasks();
  }
}
(0, import_vitest.describe)("createSilenceFrame helpers", () => {
  (0, import_log.initializeLogger)({ pretty: false, level: "silent" });
  (0, import_vitest.it)("produces a zeroed mono frame of the requested duration in ms", () => {
    const frame = (0, import_utils.createSilenceFrame)(500, 16e3);
    (0, import_vitest.expect)(frame.sampleRate).toBe(16e3);
    (0, import_vitest.expect)(frame.channels).toBe(1);
    (0, import_vitest.expect)(frame.samplesPerChannel).toBe(8e3);
    (0, import_vitest.expect)(frame.data.length).toBe(8e3);
    (0, import_vitest.expect)(isAllZero(frame)).toBe(true);
  });
  (0, import_vitest.it)("honours numChannels for multi-channel silence", () => {
    const frame = (0, import_utils.createSilenceFrame)(100, 24e3, 2);
    (0, import_vitest.expect)(frame.samplesPerChannel).toBe(2400);
    (0, import_vitest.expect)(frame.channels).toBe(2);
    (0, import_vitest.expect)(frame.data.length).toBe(4800);
    (0, import_vitest.expect)(isAllZero(frame)).toBe(true);
  });
  (0, import_vitest.it)("createSilenceFrameLike matches the source shape with zeroed samples", () => {
    const src = markerFrame(17, 160, 16e3);
    const silence = (0, import_utils.createSilenceFrameLike)(src);
    (0, import_vitest.expect)(silence.sampleRate).toBe(src.sampleRate);
    (0, import_vitest.expect)(silence.channels).toBe(src.channels);
    (0, import_vitest.expect)(silence.samplesPerChannel).toBe(src.samplesPerChannel);
    (0, import_vitest.expect)(silence.data.length).toBe(src.data.length);
    (0, import_vitest.expect)(isAllZero(silence)).toBe(true);
  });
});
(0, import_vitest.describe)("AudioRecognition substitutes silence on the STT path only", () => {
  (0, import_log.initializeLogger)({ pretty: false, level: "silent" });
  (0, import_vitest.it)("forwards the real frame to every consumer when no discard predicate is set", async () => {
    const sttFrames = [];
    const sttNode = async (audioStream) => {
      void (async () => {
        const reader = audioStream.getReader();
        try {
          while (true) {
            const { done, value } = await reader.read();
            if (done) return;
            sttFrames.push(value);
          }
        } catch {
        }
      })();
      return new ReadableStream({ start() {
      } });
    };
    const recognition = new import_audio_recognition.AudioRecognition({
      recognitionHooks: createHooks(),
      stt: sttNode,
      minEndpointingDelay: 0,
      maxEndpointingDelay: 0
    });
    const subscriberStream = recognition.subscribeAudioStream();
    const subscriberFrames = [];
    const subscriberDone = (async () => {
      const reader = subscriberStream.getReader();
      try {
        while (true) {
          const { done, value } = await reader.read();
          if (done) return;
          subscriberFrames.push(value);
        }
      } catch {
      }
    })();
    try {
      await recognition.start();
      recognition.setInputAudioStream(
        new ReadableStream({
          start(controller) {
            controller.enqueue(markerFrame(17));
            controller.enqueue(markerFrame(34));
            controller.close();
          }
        })
      );
      await waitFor(() => sttFrames.length === 2 && subscriberFrames.length === 2);
      (0, import_vitest.expect)(sttFrames.map((f) => f.data[0])).toEqual([17, 34]);
      (0, import_vitest.expect)(subscriberFrames.map((f) => f.data[0])).toEqual([17, 34]);
    } finally {
      await recognition.close();
      await subscriberDone;
    }
  });
  (0, import_vitest.it)("substitutes silence on the STT path while subscribers still see real frames", async () => {
    const DISCARD_MARKER = 34;
    const sttFrames = [];
    const sttNode = async (audioStream) => {
      void (async () => {
        const reader = audioStream.getReader();
        try {
          while (true) {
            const { done, value } = await reader.read();
            if (done) return;
            sttFrames.push(value);
          }
        } catch {
        }
      })();
      return new ReadableStream({ start() {
      } });
    };
    const recognition = new import_audio_recognition.AudioRecognition({
      recognitionHooks: createHooks(),
      stt: sttNode,
      shouldDiscardAudioForStt: (frame) => frame.data[0] === DISCARD_MARKER,
      minEndpointingDelay: 0,
      maxEndpointingDelay: 0
    });
    const subscriberStream = recognition.subscribeAudioStream();
    const subscriberFrames = [];
    const subscriberDone = (async () => {
      const reader = subscriberStream.getReader();
      try {
        while (true) {
          const { done, value } = await reader.read();
          if (done) return;
          subscriberFrames.push(value);
        }
      } catch {
      }
    })();
    try {
      await recognition.start();
      recognition.setInputAudioStream(
        new ReadableStream({
          start(controller) {
            controller.enqueue(markerFrame(17));
            controller.enqueue(markerFrame(DISCARD_MARKER));
            controller.enqueue(markerFrame(DISCARD_MARKER));
            controller.enqueue(markerFrame(68));
            controller.close();
          }
        })
      );
      await waitFor(() => sttFrames.length === 4 && subscriberFrames.length === 4);
      (0, import_vitest.expect)(sttFrames[0].data[0]).toBe(17);
      (0, import_vitest.expect)(isAllZero(sttFrames[1])).toBe(true);
      (0, import_vitest.expect)(isAllZero(sttFrames[2])).toBe(true);
      (0, import_vitest.expect)(sttFrames[3].data[0]).toBe(68);
      for (const frame of sttFrames) {
        (0, import_vitest.expect)(frame.sampleRate).toBe(16e3);
        (0, import_vitest.expect)(frame.channels).toBe(1);
        (0, import_vitest.expect)(frame.samplesPerChannel).toBe(160);
      }
      (0, import_vitest.expect)(subscriberFrames.map((f) => f.data[0])).toEqual([
        17,
        DISCARD_MARKER,
        DISCARD_MARKER,
        68
      ]);
    } finally {
      await recognition.close();
      await subscriberDone;
    }
  });
});
//# sourceMappingURL=audio_recognition_push_audio.test.cjs.map