"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 __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var console_io_exports = {};
__export(console_io_exports, {
  AgentsConsole: () => AgentsConsole,
  TcpAudioInput: () => TcpAudioInput,
  TcpAudioOutput: () => TcpAudioOutput
});
module.exports = __toCommonJS(console_io_exports);
var import_protocol = require("@livekit/protocol");
var import_rtc_node = require("@livekit/rtc-node");
var path = __toESM(require("node:path"), 1);
var import_log = require("../log.cjs");
var import_stream_channel = require("../stream/stream_channel.cjs");
var import_utils = require("../utils.cjs");
var import_io = require("./io.cjs");
const WIRE_SAMPLE_RATE = 48e3;
const AGENT_SAMPLE_RATE = 24e3;
function consoleFrameToRtc(frame) {
  const aligned = new ArrayBuffer(frame.data.byteLength);
  new Uint8Array(aligned).set(frame.data);
  return new import_rtc_node.AudioFrame(
    new Int16Array(aligned),
    frame.sampleRate,
    frame.numChannels,
    frame.samplesPerChannel
  );
}
function rtcFrameToConsole(frame) {
  return new import_protocol.AgentSession.AgentSessionMessage_ConsoleIO_AudioFrame({
    data: new Uint8Array(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength),
    sampleRate: frame.sampleRate,
    numChannels: frame.channels,
    samplesPerChannel: frame.samplesPerChannel
  });
}
class TcpAudioInput extends import_io.AudioInput {
  channel = (0, import_stream_channel.createStreamChannel)();
  resampler = new import_rtc_node.AudioResampler(WIRE_SAMPLE_RATE, AGENT_SAMPLE_RATE, 1);
  closed = false;
  constructor() {
    super();
    this.multiStream.addInputStream(this.channel.stream());
  }
  pushFrame(frame) {
    if (this.closed) return;
    for (const resampled of this.resampler.push(consoleFrameToRtc(frame))) {
      void this.channel.write(resampled);
    }
  }
  async close() {
    this.closed = true;
    await this.channel.close();
    await super.close();
  }
}
class TcpAudioOutput extends import_io.AudioOutput {
  transport;
  resampler = new import_rtc_node.AudioResampler(AGENT_SAMPLE_RATE, WIRE_SAMPLE_RATE, 1);
  pushedDurationMs = 0;
  captureStartedAt = 0;
  flushTask;
  playoutDone = new import_utils.Future();
  interrupted = new import_utils.Future();
  constructor(transport) {
    super(AGENT_SAMPLE_RATE, void 0, { pause: true });
    this.transport = transport;
  }
  async captureFrame(frame) {
    await super.captureFrame(frame);
    if (this.flushTask && !this.flushTask.done) {
      (0, import_log.log)().error("captureFrame called while previous flush is in progress");
      await this.flushTask.result;
    }
    if (this.pushedDurationMs === 0) {
      this.captureStartedAt = Date.now();
      this.onPlaybackStarted(Date.now());
    }
    this.pushedDurationMs += frame.samplesPerChannel / frame.sampleRate * 1e3;
    for (const resampled of this.resampler.push(frame)) {
      await this.transport.sendMessage(
        new import_protocol.AgentSession.AgentSessionMessage({
          message: { case: "audioOutput", value: rtcFrameToConsole(resampled) }
        })
      );
    }
  }
  flush() {
    super.flush();
    void this.transport.sendMessage(
      new import_protocol.AgentSession.AgentSessionMessage({
        message: {
          case: "audioPlaybackFlush",
          value: new import_protocol.AgentSession.AgentSessionMessage_ConsoleIO_AudioPlaybackFlush()
        }
      })
    );
    if (this.pushedDurationMs > 0) {
      if (this.flushTask && !this.flushTask.done) {
        (0, import_log.log)().error("flush called while previous flush is in progress");
        this.flushTask.cancel();
      }
      this.playoutDone = new import_utils.Future();
      this.interrupted = new import_utils.Future();
      this.flushTask = import_utils.Task.from(() => this.runPlayoutHandshake());
    }
  }
  clearBuffer() {
    void this.transport.sendMessage(
      new import_protocol.AgentSession.AgentSessionMessage({
        message: {
          case: "audioPlaybackClear",
          value: new import_protocol.AgentSession.AgentSessionMessage_ConsoleIO_AudioPlaybackClear()
        }
      })
    );
    if (this.pushedDurationMs > 0) {
      this.interrupted.resolve();
    }
  }
  /** Called by {@link SessionHost} when the broker reports playout finished. */
  notifyPlayoutFinished() {
    if (!this.playoutDone.done) {
      this.playoutDone.resolve();
    }
  }
  async runPlayoutHandshake() {
    try {
      await Promise.race([this.playoutDone.await, this.interrupted.await]);
    } catch {
      return;
    }
    const interrupted = this.interrupted.done && !this.playoutDone.done;
    let playedMs;
    if (interrupted) {
      const elapsed = Date.now() - this.captureStartedAt;
      playedMs = Math.min(Math.max(0, elapsed), this.pushedDurationMs);
    } else {
      playedMs = this.pushedDurationMs;
    }
    this.onPlaybackFinished({ playbackPosition: playedMs / 1e3, interrupted });
    this.pushedDurationMs = 0;
    this.interrupted = new import_utils.Future();
  }
}
class AgentsConsole {
  static _instance;
  static getInstance() {
    if (!AgentsConsole._instance) {
      AgentsConsole._instance = new AgentsConsole();
    }
    return AgentsConsole._instance;
  }
  /** @internal — reset the process-wide singleton (test only). */
  static _reset() {
    AgentsConsole._instance = void 0;
  }
  enabled = false;
  record = false;
  transport;
  audioInput;
  audioOutput;
  /**
   * Directory where console recordings (audio + session report) are written,
   * relative to the working directory. Mirrors python's `AgentsConsole`.
   */
  sessionDirectory;
  acquired = false;
  constructor() {
    const now = /* @__PURE__ */ new Date();
    const pad = (n) => String(n).padStart(2, "0");
    this.sessionDirectory = path.join(
      "console-recordings",
      `session-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
    );
  }
  get ioAcquired() {
    return this.acquired;
  }
  /** Wire the session's IO for console mode. */
  acquireIo(session) {
    if (this.acquired) {
      throw new Error("the console IO was already acquired by another session");
    }
    this.acquired = true;
    session.input.audio = this.audioInput ?? null;
    session.output.audio = this.audioOutput ?? null;
    session.output.transcription = null;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  AgentsConsole,
  TcpAudioInput,
  TcpAudioOutput
});
//# sourceMappingURL=console_io.cjs.map