"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var import_rtc_node = require("@livekit/rtc-node");
var import_vitest = require("vitest");
var logModule = __toESM(require("../../log.cjs"), 1);
var import_io = require("../io.cjs");
var import_synchronizer = require("./synchronizer.cjs");
(0, import_vitest.describe)("SpeakingRateData", () => {
  (0, import_vitest.describe)("constructor", () => {
    (0, import_vitest.it)("should initialize with empty arrays", () => {
      const data = new import_synchronizer.SpeakingRateData();
      (0, import_vitest.expect)(data.timestamps).toEqual([]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([]);
      (0, import_vitest.expect)(data.pushedDuration).toBe(0);
    });
  });
  (0, import_vitest.describe)("addByRate", () => {
    (0, import_vitest.it)("should add a single rate entry", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 5);
      (0, import_vitest.expect)(data.timestamps).toEqual([1]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([5]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([5]);
      (0, import_vitest.expect)(data.pushedDuration).toBe(1);
    });
    (0, import_vitest.it)("should accumulate integrals across multiple entries", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 4);
      data.addByRate(2, 6);
      data.addByRate(3.5, 2);
      (0, import_vitest.expect)(data.timestamps).toEqual([1, 2, 3.5]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([4, 6, 2]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([4, 10, 13]);
      (0, import_vitest.expect)(data.pushedDuration).toBe(3.5);
    });
    (0, import_vitest.it)("should handle zero rate", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 0);
      (0, import_vitest.expect)(data.timestamps).toEqual([1]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([0]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([0]);
    });
  });
  (0, import_vitest.describe)("addByAnnotation", () => {
    (0, import_vitest.it)("should buffer text without startTime", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByAnnotation("hello", void 0, void 0);
      (0, import_vitest.expect)(data.timestamps).toEqual([]);
      (0, import_vitest.expect)(data.pushedDuration).toBe(0);
    });
    (0, import_vitest.it)("should add entry when startTime is provided", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByAnnotation("hello", void 0, void 0);
      data.addByAnnotation("world", 1, void 0);
      (0, import_vitest.expect)(data.timestamps).toEqual([1]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([5]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([5]);
    });
    (0, import_vitest.it)("should handle startTime and endTime together", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByAnnotation("hello ", 0, 0.5);
      data.addByAnnotation("world", 0.5, 1);
      (0, import_vitest.expect)(data.timestamps.length).toBeGreaterThanOrEqual(2);
      (0, import_vitest.expect)(data.pushedDuration).toBe(1);
    });
    (0, import_vitest.it)("should calculate rate based on buffered text length", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByAnnotation("ab", void 0, void 0);
      data.addByAnnotation("cde", void 0, void 0);
      data.addByAnnotation("", 2, void 0);
      (0, import_vitest.expect)(data.timestamps).toEqual([2]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([2.5]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([5]);
    });
    (0, import_vitest.it)("should handle zero time delta gracefully", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByAnnotation("hello", 0, void 0);
      (0, import_vitest.expect)(data.timestamps).toEqual([0]);
      (0, import_vitest.expect)(data.speakingRate).toEqual([0]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([0]);
    });
  });
  (0, import_vitest.describe)("accumulateTo", () => {
    (0, import_vitest.it)("should return 0 for empty data", () => {
      const data = new import_synchronizer.SpeakingRateData();
      (0, import_vitest.expect)(data.accumulateTo(1)).toBe(0);
    });
    (0, import_vitest.it)("should return 0 for timestamp before first entry", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 5);
      (0, import_vitest.expect)(data.accumulateTo(0.5)).toBe(0);
    });
    (0, import_vitest.it)("should return exact integral at timestamp", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 4);
      data.addByRate(2, 6);
      (0, import_vitest.expect)(data.accumulateTo(1)).toBe(4);
      (0, import_vitest.expect)(data.accumulateTo(2)).toBe(10);
    });
    (0, import_vitest.it)("should interpolate between timestamps", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 4);
      data.addByRate(2, 6);
      (0, import_vitest.expect)(data.accumulateTo(1.5)).toBe(7);
    });
    (0, import_vitest.it)("should extrapolate beyond last timestamp", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 4);
      data.addByRate(2, 6);
      (0, import_vitest.expect)(data.accumulateTo(3)).toBe(16);
    });
    (0, import_vitest.it)("should not exceed next integral when interpolating", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 100);
      data.addByRate(2, 1);
      (0, import_vitest.expect)(data.accumulateTo(1.5)).toBe(100.5);
    });
  });
  (0, import_vitest.describe)("pushedDuration", () => {
    (0, import_vitest.it)("should return 0 when empty", () => {
      const data = new import_synchronizer.SpeakingRateData();
      (0, import_vitest.expect)(data.pushedDuration).toBe(0);
    });
    (0, import_vitest.it)("should return last timestamp", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(1, 5);
      data.addByRate(2.5, 3);
      data.addByRate(4, 7);
      (0, import_vitest.expect)(data.pushedDuration).toBe(4);
    });
  });
  (0, import_vitest.describe)("integration scenarios", () => {
    (0, import_vitest.it)("should handle typical TTS word timing scenario", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByAnnotation("Hello ", 0, 0.3);
      data.addByAnnotation("world", 0.3, 0.6);
      (0, import_vitest.expect)(data.pushedDuration).toBe(0.6);
      const mid1 = data.accumulateTo(0.15);
      (0, import_vitest.expect)(mid1).toBeGreaterThan(0);
      (0, import_vitest.expect)(mid1).toBeLessThan(6);
      const mid2 = data.accumulateTo(0.45);
      (0, import_vitest.expect)(mid2).toBeGreaterThan(6);
    });
    (0, import_vitest.it)("should handle mixed rate and annotation data", () => {
      const data = new import_synchronizer.SpeakingRateData();
      data.addByRate(0.5, 4);
      data.addByAnnotation("test", void 0, void 0);
      data.addByAnnotation("", 1, void 0);
      (0, import_vitest.expect)(data.timestamps).toEqual([0.5, 1]);
      (0, import_vitest.expect)(data.speakIntegrals).toEqual([2, 6]);
    });
  });
});
class MockAudioOutput extends import_io.AudioOutput {
  constructor() {
    super(8e3);
  }
  async captureFrame(frame) {
    await super.captureFrame(frame);
  }
  clearBuffer() {
  }
}
class MockTextOutput extends import_io.TextOutput {
  captured = [];
  async captureText(text) {
    this.captured.push(text);
  }
  flush() {
  }
}
(0, import_vitest.describe)("TranscriptionSynchronizer attachment warnings", () => {
  const textDetachedWarning = "TranscriptSynchronizer text output was detached while audio output is still active; transcription sync is disabled. This usually means session.output.transcription was replaced after AgentSession.start().";
  const audioDetachedWarning = "TranscriptSynchronizer audio output was detached while text output is still active; transcription sync is disabled. This usually means session.output.audio was replaced after AgentSession.start().";
  (0, import_vitest.afterEach)(() => {
    import_vitest.vi.restoreAllMocks();
  });
  (0, import_vitest.it)("warns once per enabled cycle when text output is detached while audio is still attached", async () => {
    const warn = import_vitest.vi.fn();
    import_vitest.vi.spyOn(logModule, "log").mockReturnValue({
      warn,
      debug: import_vitest.vi.fn(),
      info: import_vitest.vi.fn(),
      error: import_vitest.vi.fn()
    });
    const synchronizer = new import_synchronizer.TranscriptionSynchronizer(new MockAudioOutput(), new MockTextOutput());
    const frame = new import_rtc_node.AudioFrame(new Int16Array(160), 8e3, 1, 160);
    synchronizer.textOutput.onDetached();
    await synchronizer.audioOutput.captureFrame(frame);
    await synchronizer.audioOutput.captureFrame(frame);
    (0, import_vitest.expect)(warn.mock.calls.filter((c) => c[0] === textDetachedWarning)).toHaveLength(1);
    synchronizer.textOutput.onAttached();
    synchronizer.textOutput.onDetached();
    await synchronizer.audioOutput.captureFrame(frame);
    (0, import_vitest.expect)(warn.mock.calls.filter((c) => c[0] === textDetachedWarning)).toHaveLength(2);
    await synchronizer.close();
  });
  (0, import_vitest.it)("warns once per enabled cycle when audio output is detached while text is still attached", async () => {
    const warn = import_vitest.vi.fn();
    import_vitest.vi.spyOn(logModule, "log").mockReturnValue({
      warn,
      debug: import_vitest.vi.fn(),
      info: import_vitest.vi.fn(),
      error: import_vitest.vi.fn()
    });
    const synchronizer = new import_synchronizer.TranscriptionSynchronizer(new MockAudioOutput(), new MockTextOutput());
    synchronizer.audioOutput.onDetached();
    await synchronizer.textOutput.captureText("hello");
    await synchronizer.textOutput.captureText("again");
    (0, import_vitest.expect)(warn.mock.calls.filter((c) => c[0] === audioDetachedWarning)).toHaveLength(1);
    synchronizer.audioOutput.onAttached();
    synchronizer.audioOutput.onDetached();
    await synchronizer.textOutput.captureText("third");
    (0, import_vitest.expect)(warn.mock.calls.filter((c) => c[0] === audioDetachedWarning)).toHaveLength(2);
    await synchronizer.close();
  });
});
class DroppingAudioOutput extends import_io.AudioOutput {
  constructor() {
    super(8e3);
  }
  async captureFrame(_frame) {
  }
  clearBuffer() {
  }
}
(0, import_vitest.describe)("TranscriptionSynchronizer playback-counter drift on a dropped frame", () => {
  (0, import_vitest.it)("does not strand waitForPlayout() when the downstream sink drops a captured frame", async () => {
    const synchronizer = new import_synchronizer.TranscriptionSynchronizer(
      new DroppingAudioOutput(),
      new MockTextOutput()
    );
    const frame = new import_rtc_node.AudioFrame(new Int16Array(160), 8e3, 1, 160);
    await synchronizer.audioOutput.captureFrame(frame);
    const result = await Promise.race([
      synchronizer.audioOutput.waitForPlayout().then(() => "resolved"),
      new Promise((resolve) => setTimeout(() => resolve("timeout"), 1e3))
    ]);
    (0, import_vitest.expect)(result).toBe("resolved");
    await synchronizer.close();
  });
  (0, import_vitest.it)("routes the synthetic finish through the synchronizer like a real playback finish", async () => {
    const synchronizer = new import_synchronizer.TranscriptionSynchronizer(
      new DroppingAudioOutput(),
      new MockTextOutput()
    );
    const frame = new import_rtc_node.AudioFrame(new Int16Array(160), 8e3, 1, 160);
    const implBefore = synchronizer._impl;
    await synchronizer.audioOutput.captureFrame(frame);
    const ev = await synchronizer.audioOutput.waitForPlayout();
    (0, import_vitest.expect)(ev.interrupted).toBe(true);
    (0, import_vitest.expect)(typeof ev.synchronizedTranscript).toBe("string");
    await synchronizer.barrier();
    (0, import_vitest.expect)(synchronizer._impl).not.toBe(implBefore);
    await synchronizer.close();
  });
});
//# sourceMappingURL=synchronizer.test.cjs.map