import { AudioFrame } from "@livekit/rtc-node";
import { ReadableStream } from "stream/web";
import { describe, expect, it, vi } from "vitest";
import { initializeLogger } from "../log.js";
import { performAudioForwarding, performTTSInference } from "./generation.js";
import { AudioOutput } from "./io.js";
function createSilentFrame(sampleRate = 24e3, channels = 1, durationMs = 20) {
  const samplesPerChannel = Math.floor(sampleRate * durationMs / 1e3);
  const data = new Int16Array(samplesPerChannel * channels);
  return new AudioFrame(data, sampleRate, channels, samplesPerChannel);
}
class MockAudioOutput extends AudioOutput {
  capturedFrames = [];
  constructor() {
    super(24e3);
  }
  async captureFrame(frame) {
    await super.captureFrame(frame);
    this.capturedFrames.push(frame);
    this.onPlaybackStarted(Date.now());
  }
  clearBuffer() {
  }
}
describe("TTS stream idle timeout", () => {
  initializeLogger({ pretty: false, level: "silent" });
  it("forwardAudio completes when TTS stream stalls after producing frames", async () => {
    const stalledStream = new ReadableStream({
      start(controller2) {
        controller2.enqueue(createSilentFrame());
        controller2.enqueue(createSilentFrame());
      }
    });
    const audioOutput = new MockAudioOutput();
    const controller = new AbortController();
    const [task, audioOut] = performAudioForwarding(stalledStream, audioOutput, controller);
    vi.useFakeTimers();
    const taskPromise = task.result;
    await vi.advanceTimersByTimeAsync(11e3);
    await taskPromise;
    vi.useRealTimers();
    expect(audioOutput.capturedFrames.length).toBe(2);
    expect(audioOut.firstFrameFut.done).toBe(true);
  }, 1e4);
  it("forwardAudio honours a custom idle timeout", async () => {
    const stalledStream = new ReadableStream({
      start(controller2) {
        controller2.enqueue(createSilentFrame());
      }
    });
    const audioOutput = new MockAudioOutput();
    const controller = new AbortController();
    const [task, audioOut] = performAudioForwarding(stalledStream, audioOutput, controller, 500);
    vi.useFakeTimers();
    const taskPromise = task.result;
    await vi.advanceTimersByTimeAsync(600);
    await taskPromise;
    vi.useRealTimers();
    expect(audioOutput.capturedFrames.length).toBe(1);
    expect(audioOut.firstFrameFut.done).toBe(true);
  });
  it("forwardAudio completes normally when TTS stream closes properly", async () => {
    const normalStream = new ReadableStream({
      start(controller2) {
        controller2.enqueue(createSilentFrame());
        controller2.enqueue(createSilentFrame());
        controller2.enqueue(createSilentFrame());
        controller2.close();
      }
    });
    const audioOutput = new MockAudioOutput();
    const controller = new AbortController();
    const [task, audioOut] = performAudioForwarding(normalStream, audioOutput, controller);
    await task.result;
    expect(audioOutput.capturedFrames.length).toBe(3);
    expect(audioOut.firstFrameFut.done).toBe(true);
  });
  it("ignores PLAYBACK_STARTED from another segment before its own first frame", async () => {
    const stalledStream = new ReadableStream({ start() {
    } });
    const audioOutput = new MockAudioOutput();
    const controller = new AbortController();
    const [task, audioOut] = performAudioForwarding(stalledStream, audioOutput, controller, 500);
    audioOut.firstFrameFut.await.catch(() => {
    });
    vi.useFakeTimers();
    audioOutput.onPlaybackStarted(Date.now());
    expect(audioOut.firstFrameFut.done).toBe(false);
    const taskPromise = task.result;
    await vi.advanceTimersByTimeAsync(600);
    await taskPromise;
    vi.useRealTimers();
    expect(audioOutput.capturedFrames.length).toBe(0);
    expect(audioOut.firstFrameFut.rejected).toBe(true);
  });
  it("resamples a rate-mismatched frame even after a stray PLAYBACK_STARTED", async () => {
    const stream = new ReadableStream({
      start(controller2) {
        controller2.enqueue(createSilentFrame(16e3));
        controller2.enqueue(createSilentFrame(16e3));
        controller2.close();
      }
    });
    const audioOutput = new MockAudioOutput();
    const controller = new AbortController();
    const [task, audioOut] = performAudioForwarding(stream, audioOutput, controller);
    audioOutput.onPlaybackStarted(Date.now());
    await task.result;
    expect(audioOut.firstFrameFut.done).toBe(true);
    expect(audioOutput.capturedFrames.length).toBeGreaterThan(0);
    for (const f of audioOutput.capturedFrames) {
      expect(f.sampleRate).toBe(24e3);
    }
  });
  it("performTTSInference completes when TTS node returns stalled stream", async () => {
    const stalledTtsStream = new ReadableStream({
      start(controller2) {
        controller2.enqueue(createSilentFrame());
      }
    });
    const ttsNode = async () => stalledTtsStream;
    const textInput = new ReadableStream({
      start(controller2) {
        controller2.enqueue("Hello world");
        controller2.close();
      }
    });
    const controller = new AbortController();
    const [task, genData] = performTTSInference(ttsNode, textInput, {}, controller);
    vi.useFakeTimers();
    const taskPromise = task.result;
    await vi.advanceTimersByTimeAsync(11e3);
    await taskPromise;
    vi.useRealTimers();
    expect(genData.ttfb).toBeDefined();
  }, 1e4);
  it("performTTSInference honours a custom read idle timeout", async () => {
    const stalledTtsStream = new ReadableStream({
      start(controller2) {
        controller2.enqueue(createSilentFrame());
      }
    });
    const ttsNode = async () => stalledTtsStream;
    const textInput = new ReadableStream({
      start(controller2) {
        controller2.enqueue("Hello world");
        controller2.close();
      }
    });
    const controller = new AbortController();
    const [task, genData] = performTTSInference(
      ttsNode,
      textInput,
      {},
      controller,
      void 0,
      void 0,
      500
    );
    vi.useFakeTimers();
    const taskPromise = task.result;
    await vi.advanceTimersByTimeAsync(600);
    await taskPromise;
    vi.useRealTimers();
    expect(genData.ttfb).toBeDefined();
  }, 1e4);
});
//# sourceMappingURL=generation_tts_timeout.test.js.map