"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var avatar_session_exports = {};
__export(avatar_session_exports, {
  AvatarSession: () => AvatarSession
});
module.exports = __toCommonJS(avatar_session_exports);
var import_rtc_node = require("@livekit/rtc-node");
var import_livekit_server_sdk = require("livekit-server-sdk");
var import_node_events = require("node:events");
var import_job = require("../../job.cjs");
var import_log = require("../../log.cjs");
var import_utils = require("../../utils.cjs");
var import_events = require("../events.cjs");
class AvatarSession extends import_node_events.EventEmitter {
  #logger = (0, import_log.log)();
  #agentSession;
  #room;
  #waitAvatarJoinAbort;
  #waitAvatarJoinPromise;
  get avatarIdentity() {
    return "unknown";
  }
  get provider() {
    return "unknown";
  }
  /**
   * Start the avatar session.
   *
   * Subclasses should override this method and call `super.start(agentSession, room)` at the
   * top of their implementation. Subclasses may widen the return type (e.g. returning a
   * session id), matching the `# type: ignore[override]` escape hatch used in Python.
   */
  async start(agentSession, room) {
    const jobCtx = (0, import_job.getJobContext)(false);
    if (jobCtx !== void 0) {
      jobCtx.addShutdownCallback(() => this.aclose());
    } else {
      this.#logger.debug(
        "AvatarSession started outside a job context; call aclose() manually to release resources when the job shuts down"
      );
    }
    const audioOutput = agentSession.output.audio;
    if (agentSession._started && audioOutput !== null) {
      this.#logger.warn(
        { audioOutput: audioOutput.constructor.name },
        "AvatarSession.start() was called after AgentSession.start(); the existing audio output may be replaced by the avatar. Please start the avatar session before AgentSession.start() to avoid this."
      );
    }
    this.#agentSession = agentSession;
    this.#room = room;
    this.#agentSession.on(
      import_events.AgentSessionEventTypes.ConversationItemAdded,
      this.#onConversationItemAdded
    );
    if (room.isConnected) {
      this.#startWaitAvatarJoin();
    } else {
      room.on(import_rtc_node.RoomEvent.ConnectionStateChanged, this.#onConnectionStateChanged);
    }
    return void 0;
  }
  /**
   * Wait until the avatar participant has joined the room and published its video track.
   *
   * @param timeout - Timeout in milliseconds. Pass `null` to wait indefinitely.
   */
  async waitForJoin({ timeout = 3e4 } = {}) {
    if (!this.#waitAvatarJoinPromise) return;
    if (timeout === null) {
      await this.#waitAvatarJoinPromise;
      return;
    }
    let timer;
    await Promise.race([
      this.#waitAvatarJoinPromise,
      new Promise((_, reject) => {
        timer = setTimeout(
          () => reject(new Error("timed out waiting for avatar participant")),
          timeout
        );
      })
    ]).finally(() => clearTimeout(timer));
  }
  /**
   * Release any resources owned by this avatar session. Default implementation is a no-op;
   * subclasses can override to perform cleanup.
   */
  async aclose() {
    var _a, _b, _c;
    const roomName = (_a = this.#room) == null ? void 0 : _a.name;
    if (((_b = this.#room) == null ? void 0 : _b.isConnected) && roomName !== void 0) {
      const jobCtx = (0, import_job.getJobContext)(false);
      if (jobCtx !== void 0) {
        try {
          const client = new import_livekit_server_sdk.RoomServiceClient(
            jobCtx.info.url,
            jobCtx.info.apiKey,
            jobCtx.info.apiSecret
          );
          await client.removeParticipant(roomName, this.avatarIdentity);
        } catch (error) {
          this.#logger.warn(
            { error, identity: this.avatarIdentity },
            "failed to remove avatar participant"
          );
        }
      }
    }
    if (this.#agentSession) {
      this.#agentSession.off(
        import_events.AgentSessionEventTypes.ConversationItemAdded,
        this.#onConversationItemAdded
      );
      this.#agentSession = void 0;
    }
    if (this.#room) {
      this.#room.off(import_rtc_node.RoomEvent.ConnectionStateChanged, this.#onConnectionStateChanged);
      this.#room = void 0;
    }
    (_c = this.#waitAvatarJoinAbort) == null ? void 0 : _c.abort();
    if (this.#waitAvatarJoinPromise) {
      await this.#waitAvatarJoinPromise;
      this.#waitAvatarJoinPromise = void 0;
    }
    this.#waitAvatarJoinAbort = void 0;
  }
  #startWaitAvatarJoin() {
    if (this.#waitAvatarJoinPromise) return;
    if (this.avatarIdentity === "unknown") {
      this.#logger.warn("cannot wait for avatar join; avatar identity is unknown");
      return;
    }
    const abortController = new AbortController();
    this.#waitAvatarJoinAbort = abortController;
    this.#waitAvatarJoinPromise = this.#waitAvatarJoin(abortController.signal).catch((error) => {
      if (!abortController.signal.aborted) {
        this.#logger.warn({ error: String(error) }, "failed while waiting for avatar participant");
      }
    });
  }
  async #waitAvatarJoin(signal) {
    const room = this.#room;
    if (!room) return;
    const startedAt = Date.now();
    await (0, import_utils.waitForParticipant)({
      room,
      identity: this.avatarIdentity,
      includeLocal: true,
      signal
    });
    await (0, import_utils.waitForTrackPublication)({
      room,
      identity: this.avatarIdentity,
      kind: import_rtc_node.TrackKind.KIND_VIDEO,
      includeLocal: true,
      signal
    });
    const joinedAt = Date.now();
    this.#emitMetrics({
      type: "avatar_metrics",
      timestamp: joinedAt,
      sessionStartedAt: startedAt,
      avatarJoinedAt: joinedAt,
      metadata: { modelProvider: this.provider }
    });
  }
  #onConversationItemAdded = (ev) => {
    const { item } = ev;
    if (item.type !== "message" || item.role !== "assistant") return;
    const { playbackLatency } = item.metrics;
    if (playbackLatency === void 0) return;
    this.#emitMetrics({
      type: "avatar_metrics",
      timestamp: ev.createdAt,
      playbackLatencyMs: playbackLatency * 1e3,
      metadata: { modelProvider: this.provider }
    });
  };
  #onConnectionStateChanged = () => {
    var _a;
    if ((_a = this.#room) == null ? void 0 : _a.isConnected) {
      this.#startWaitAvatarJoin();
    }
  };
  #emitMetrics(metrics) {
    var _a;
    this.emit("metrics_collected", metrics);
    (_a = this.#agentSession) == null ? void 0 : _a.emit(
      import_events.AgentSessionEventTypes.MetricsCollected,
      (0, import_events.createMetricsCollectedEvent)({ metrics })
    );
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  AvatarSession
});
//# sourceMappingURL=avatar_session.cjs.map