import { describe, expect, it } from "vitest";
import {
  BaseStreamingTurnDetector,
  BaseStreamingTurnDetectorStream
} from "./base.js";
import { ThresholdOptions } from "./languages.js";
class FakeTransport {
  events = [];
  _stream;
  attach(stream) {
    this._stream = stream;
  }
  async run() {
    if (this._stream === void 0) {
      throw new Error("stream not bound");
    }
    await this._stream._drainAudioChannel();
  }
  runInference(requestId) {
    this.events.push(["run_inference", requestId]);
  }
  async pushFrame(_frame) {
  }
  async flush(_sentinel) {
  }
  detach() {
  }
}
class FakeDetector extends BaseStreamingTurnDetector {
  // Default to the local mini model so the timed-out-cancel test sees a
  // non-cloud model and skips the fallback.
  get model() {
    return "turn-detector-v1-mini";
  }
  stream() {
    throw new Error("unused in request-lifecycle tests");
  }
}
class FakeBackend extends BaseStreamingTurnDetectorStream {
  fakeTransport;
  constructor(opts) {
    const transport = new FakeTransport();
    super({ detector: new FakeDetector(opts), opts, transport });
    this.fakeTransport = transport;
  }
  get events() {
    return this.fakeTransport.events;
  }
  /** Mirror what a transport would do: hand the prediction to the stream. */
  simulatePrediction(requestId, probability) {
    this._resolvePrediction(requestId, probability);
  }
  // Exposed for assertions.
  get requestId() {
    return this._requestId;
  }
  get requestFut() {
    return this._requestFut;
  }
}
function makeOpts(thresholds = {}) {
  return {
    sampleRate: 16e3,
    thresholds: new ThresholdOptions("turn-detector-v1-mini", thresholds)
  };
}
function makeStream(thresholds = {}) {
  return new FakeBackend(makeOpts(thresholds));
}
const countRunInference = (events) => events.filter((e) => e[0] === "run_inference").length;
describe("AudioTurnDetectionRequests", () => {
  it("predict starts inference", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      expect(s.requestId).toBeDefined();
      expect(fut.done).toBe(false);
      expect(s.events).toEqual([["run_inference", s.requestId]]);
    } finally {
      await s.aclose();
    }
  });
  it("predict supersedes previous request", async () => {
    const s = makeStream();
    try {
      const oldFut = s.predict();
      const oldId = s.requestId;
      const newFut = s.predict();
      expect(newFut).not.toBe(oldFut);
      expect(s.requestId).not.toBe(oldId);
      expect(oldFut.done).toBe(true);
      expect((await oldFut.await).endOfTurnProbability).toBe(0);
      expect(countRunInference(s.events)).toBe(2);
    } finally {
      await s.aclose();
    }
  });
  it("cancelInference closes the request", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      s.cancelInference();
      expect(s.requestId).toBeUndefined();
      expect(fut.done).toBe(true);
      expect((await fut.await).endOfTurnProbability).toBe(0);
    } finally {
      await s.aclose();
    }
  });
  it("cancelInference when idle is a no-op", async () => {
    const s = makeStream();
    try {
      s.cancelInference();
      expect(s.events).toEqual([]);
    } finally {
      await s.aclose();
    }
  });
  it("late prediction after cancelInference is dropped", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      const cancelledId = s.requestId;
      expect(cancelledId).toBeDefined();
      s.cancelInference();
      s.simulatePrediction(cancelledId, 0.9);
      expect((await fut.await).endOfTurnProbability).toBe(0);
      const nextFut = s.predict();
      expect(nextFut).not.toBe(fut);
      expect(nextFut.done).toBe(false);
      expect(countRunInference(s.events)).toBe(2);
    } finally {
      await s.aclose();
    }
  });
  it("prediction completes the request", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      const requestId = s.requestId;
      expect(requestId).toBeDefined();
      s.simulatePrediction(requestId, 0.3);
      expect(fut.done).toBe(true);
      expect((await fut.await).endOfTurnProbability).toBe(0.3);
      expect(s.requestId).toBeUndefined();
    } finally {
      await s.aclose();
    }
  });
  it("flush closes the request", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      s.flush("turn committed");
      expect(s.requestId).toBeUndefined();
      expect((await fut.await).endOfTurnProbability).toBe(0);
    } finally {
      await s.aclose();
    }
  });
  it("flush does not overwrite a resolved prediction", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      const requestId = s.requestId;
      expect(requestId).toBeDefined();
      s.simulatePrediction(requestId, 0.7);
      s.flush("turn committed");
      expect((await fut.await).endOfTurnProbability).toBe(0.7);
      expect(s.requestId).toBeUndefined();
    } finally {
      await s.aclose();
    }
  });
  it("predict after endInput returns a resolved default", async () => {
    const s = makeStream();
    try {
      s.endInput();
      await new Promise((resolve) => setTimeout(resolve, 20));
      const fut = s.predict();
      expect(fut.done).toBe(true);
      expect((await fut.await).endOfTurnProbability).toBe(1);
      expect(s.events.some((e) => e[0] === "run_inference")).toBe(false);
    } finally {
      await s.aclose();
    }
  });
  it("aclose resolves a pending future", async () => {
    const s = makeStream();
    const fut = s.predict();
    await s.aclose();
    expect(fut.done).toBe(true);
    expect((await fut.await).endOfTurnProbability).toBe(0);
  });
  it("timed-out cancelInference does not fall back for the local model", async () => {
    const s = makeStream();
    try {
      const fut = s.predict();
      s.cancelInference({ timedOut: true });
      expect((await fut.await).endOfTurnProbability).toBe(0);
      expect(s.model).toBe("turn-detector-v1-mini");
    } finally {
      await s.aclose();
    }
  });
});
//# sourceMappingURL=base.test.js.map