"use strict";
var import_protocol = require("@livekit/protocol");
var import_rtc_node = require("@livekit/rtc-node");
var import_vitest = require("vitest");
var import_exceptions = require("../../_exceptions.cjs");
var import_types = require("../../types.cjs");
var import_base = require("./base.cjs");
var import_detector = require("./detector.cjs");
var import_languages = require("./languages.cjs");
var import_transports = require("./transports.cjs");
const { ClientMessage } = import_protocol.AgentInference;
class FakeWS {
  sent = [];
  readyState = 1;
  // OPEN
  closeCbs = [];
  send(data) {
    if (this.readyState !== 1) throw new Error("ws closed");
    this.sent.push(ClientMessage.fromBinary(data));
  }
  close() {
    this.readyState = 3;
    for (const cb of this.closeCbs) cb();
  }
  on(event, cb) {
    if (event === "close") this.closeCbs.push(cb);
  }
}
class FakeDetector extends import_base.BaseStreamingTurnDetector {
  get model() {
    return "turn-detector-v1";
  }
  stream() {
    throw new Error("unused");
  }
}
function makeStream(opts) {
  const fakeWs = new FakeWS();
  const script = [...opts.connectScript ?? []];
  const turnOpts = {
    sampleRate: 16e3,
    thresholds: new import_languages.ThresholdOptions("turn-detector-v1")
  };
  const detector = new FakeDetector(turnOpts);
  const cloudOpts = {
    baseUrl: "",
    apiKey: "x",
    apiSecret: "x",
    connOptions: {
      ...import_types.DEFAULT_API_CONNECT_OPTIONS,
      maxRetry: opts.maxRetry ?? 3,
      retryIntervalMs: opts.retryIntervalMs ?? 0
    }
  };
  const connect = async () => {
    if (script.length > 0) {
      const r = script.shift();
      if (r instanceof Error) throw r;
    }
    fakeWs.readyState = 1;
    return fakeWs;
  };
  const transport = new import_transports.CloudTransport({ detector, opts: turnOpts, cloudOpts, connect });
  const stream = new import_detector.TurnDetectorStreamImpl({
    detector,
    opts: turnOpts,
    cloudOpts,
    model: "turn-detector-v1",
    transport
  });
  return { stream, fakeWs, transport };
}
async function tick() {
  await new Promise((r) => setImmediate(r));
}
async function waitUntilConnected(transport, ticks = 50) {
  for (let i = 0; i < ticks; i++) {
    if (transport.transportReady()) return;
    await tick();
  }
  throw new Error("transport did not connect within timeout");
}
async function drainSendQueue(_transport, ticks = 50) {
  for (let i = 0; i < ticks; i++) {
    await tick();
  }
}
async function waitForCond(predicate, ticks = 50) {
  for (let i = 0; i < ticks; i++) {
    if (predicate()) return;
    await tick();
  }
}
function pcmFrame(samples = 320) {
  return new import_rtc_node.AudioFrame(new Int16Array(samples), 16e3, 1, samples);
}
(0, import_vitest.describe)("CloudStreamRetry", () => {
  (0, import_vitest.it)("num retries resets after a successful connect", async () => {
    const { stream, transport } = makeStream({
      connectScript: [new import_exceptions.APIConnectionError({ message: "transient" }), null],
      maxRetry: 3,
      retryIntervalMs: 0
    });
    try {
      await waitUntilConnected(transport);
      (0, import_vitest.expect)(transport.connectCalls).toBe(2);
      (0, import_vitest.expect)(transport.numRetries).toBe(0);
    } finally {
      await stream.aclose();
    }
  });
});
(0, import_vitest.describe)("CloudToLocalFallback", () => {
  (0, import_vitest.it)("releases the shared audio reader lock on fallback (regression)", async () => {
    const { stream, transport } = makeStream({ connectScript: [null] });
    try {
      await waitUntilConnected(transport);
      stream.pushAudio(pcmFrame());
      await tick();
      const fut = stream.predict();
      stream.cancelInference({ timedOut: true });
      await fut.await;
      await waitForCond(() => stream.model === "turn-detector-v1-mini");
      (0, import_vitest.expect)(stream.isFallback).toBe(true);
      for (let i = 0; i < 10; i++) await tick();
      (0, import_vitest.expect)(stream.warnedLocalFailure).toBe(false);
    } finally {
      await stream.aclose();
    }
  });
});
(0, import_vitest.describe)("CloudStreamSendOrdering", () => {
  (0, import_vitest.it)("inferenceStart precedes inputAudio (FIFO)", async () => {
    const { stream, fakeWs, transport } = makeStream({ connectScript: [null] });
    try {
      await waitUntilConnected(transport);
      stream.predict();
      stream.pushAudio(pcmFrame());
      await drainSendQueue(transport);
      const kinds = fakeWs.sent.map((m) => m.message.case);
      const startIdx = kinds.indexOf("inferenceStart");
      const audioIdx = kinds.indexOf("inputAudio");
      (0, import_vitest.expect)(startIdx).toBeGreaterThanOrEqual(0);
      (0, import_vitest.expect)(audioIdx).toBeGreaterThanOrEqual(0);
      (0, import_vitest.expect)(startIdx).toBeLessThan(audioIdx);
    } finally {
      await stream.aclose();
    }
  });
  (0, import_vitest.it)("consecutive inferenceStarts are serialized in call order", async () => {
    const { stream, fakeWs, transport } = makeStream({ connectScript: [null] });
    try {
      await waitUntilConnected(transport);
      stream.predict();
      const firstId = stream._requestId;
      stream.predict();
      const secondId = stream._requestId;
      await drainSendQueue(transport);
      const startIds = [];
      for (const m of fakeWs.sent) {
        if (m.message.case === "inferenceStart") {
          startIds.push(m.message.value.requestId);
        }
      }
      (0, import_vitest.expect)(startIds).toEqual([firstId, secondId]);
    } finally {
      await stream.aclose();
    }
  });
});
//# sourceMappingURL=transports.test.cjs.map