import { EventEmitter } from "node:events";
import { log } from "../log.js";
import { MultiInputStream } from "../stream/multi_input_stream.js";
import { Future } from "../utils.js";
const TIMED_STRING_SYMBOL = Symbol.for("lk.TimedString");
function createTimedString(opts) {
  return {
    [TIMED_STRING_SYMBOL]: true,
    text: opts.text,
    startTime: opts.startTime,
    endTime: opts.endTime,
    confidence: opts.confidence,
    startTimeOffset: opts.startTimeOffset,
    speakerId: opts.speakerId ?? null
  };
}
function isTimedString(value) {
  return typeof value === "object" && value !== null && TIMED_STRING_SYMBOL in value && value[TIMED_STRING_SYMBOL] === true;
}
class AudioInput {
  multiStream = new MultiInputStream();
  get stream() {
    return this.multiStream.stream;
  }
  async close() {
    await this.multiStream.close();
  }
  onAttached() {
  }
  onDetached() {
  }
}
class AudioOutput extends EventEmitter {
  constructor(sampleRate, nextInChain, capabilities = { pause: false }) {
    super();
    this.sampleRate = sampleRate;
    this.nextInChain = nextInChain;
    this.capabilities = capabilities;
    if (this.nextInChain) {
      this.nextInChain.on(
        AudioOutput.EVENT_PLAYBACK_STARTED,
        (ev) => this.onPlaybackStarted(ev.createdAt)
      );
      this.nextInChain.on(
        AudioOutput.EVENT_PLAYBACK_FINISHED,
        (ev) => this.onPlaybackFinished(ev)
      );
    }
  }
  static EVENT_PLAYBACK_STARTED = "playbackStarted";
  static EVENT_PLAYBACK_FINISHED = "playbackFinished";
  playbackFinishedFuture = new Future();
  _capturing = false;
  playbackFinishedCount = 0;
  playbackSegmentsCount = 0;
  lastPlaybackEvent = {
    playbackPosition: 0,
    interrupted: false
  };
  logger = log();
  capabilities;
  /**
   * Whether this output and all outputs in the chain support pause/resume.
   */
  get canPause() {
    var _a;
    return this.capabilities.pause && (((_a = this.nextInChain) == null ? void 0 : _a.canPause) ?? true);
  }
  /**
   * Capture an audio frame for playback, frames can be pushed faster than real-time
   */
  async captureFrame(_frame) {
    if (!this._capturing) {
      this._capturing = true;
      this.playbackSegmentsCount++;
    }
  }
  /**
   * Wait for the past audio segments to finish playing out.
   *
   * @returns The event that was emitted when the audio finished playing out (only the last segment information)
   */
  async waitForPlayout() {
    const target = this.playbackSegmentsCount;
    while (this.playbackFinishedCount < target) {
      await this.playbackFinishedFuture.await;
      this.playbackFinishedFuture = new Future();
    }
    return this.lastPlaybackEvent;
  }
  /**
   * Playback segments captured but not yet finished. Used by chained outputs to detect and
   * reconcile a segment-count drift against the next output in the chain.
   * @internal
   */
  get pendingPlayoutSegments() {
    return this.playbackSegmentsCount - this.playbackFinishedCount;
  }
  /**
   * Called when playback actually starts (first frame is sent to output).
   * Developers building audio sinks should call this when the first frame is captured.
   */
  onPlaybackStarted(createdAt) {
    this.emit(AudioOutput.EVENT_PLAYBACK_STARTED, { createdAt });
  }
  /**
   * Developers building audio sinks must call this method when a playback/segment is finished.
   * Segments are segmented by calls to flush() or clearBuffer()
   */
  onPlaybackFinished(options) {
    if (this.playbackFinishedCount >= this.playbackSegmentsCount) {
      this.logger.warn("playback_finished called more times than playback segments were captured");
      return;
    }
    this.lastPlaybackEvent = options;
    this.playbackFinishedCount++;
    this.playbackFinishedFuture.resolve();
    this.emit(AudioOutput.EVENT_PLAYBACK_FINISHED, options);
  }
  flush() {
    this._capturing = false;
  }
  onAttached() {
    if (this.nextInChain) {
      this.nextInChain.onAttached();
    }
  }
  onDetached() {
    if (this.nextInChain) {
      this.nextInChain.onDetached();
    }
  }
  /**
   * Pause the audio playback
   */
  pause() {
    if (this.nextInChain) {
      this.nextInChain.pause();
    }
  }
  /**
   * Resume the audio playback
   */
  resume() {
    if (this.nextInChain) {
      this.nextInChain.resume();
    }
  }
}
class TextOutput {
  constructor(nextInChain) {
    this.nextInChain = nextInChain;
  }
  onAttached() {
    if (this.nextInChain) {
      this.nextInChain.onAttached();
    }
  }
  onDetached() {
    if (this.nextInChain) {
      this.nextInChain.onDetached();
    }
  }
}
class AgentInput {
  constructor(audioChanged) {
    this.audioChanged = audioChanged;
  }
  _audioStream = null;
  // enabled by default
  _audioEnabled = true;
  setAudioEnabled(enable) {
    if (enable === this._audioEnabled) {
      return;
    }
    this._audioEnabled = enable;
    if (!this._audioStream) {
      return;
    }
    if (enable) {
      this._audioStream.onAttached();
    } else {
      this._audioStream.onDetached();
    }
  }
  get audioEnabled() {
    return this._audioEnabled;
  }
  get audio() {
    return this._audioStream;
  }
  set audio(stream) {
    this._audioStream = stream;
    this.audioChanged();
  }
}
class AgentOutput {
  constructor(audioChanged, transcriptionChanged) {
    this.audioChanged = audioChanged;
    this.transcriptionChanged = transcriptionChanged;
  }
  _audioSink = null;
  _transcriptionSink = null;
  _audioEnabled = true;
  _transcriptionEnabled = true;
  setAudioEnabled(enabled) {
    if (enabled === this._audioEnabled) {
      return;
    }
    this._audioEnabled = enabled;
    if (!this._audioSink) {
      return;
    }
    if (enabled) {
      this._audioSink.onAttached();
    } else {
      this._audioSink.onDetached();
    }
  }
  setTranscriptionEnabled(enabled) {
    if (enabled === this._transcriptionEnabled) {
      return;
    }
    this._transcriptionEnabled = enabled;
    if (!this._transcriptionSink) {
      return;
    }
    if (enabled) {
      this._transcriptionSink.onAttached();
    } else {
      this._transcriptionSink.onDetached();
    }
  }
  get audioEnabled() {
    return this._audioEnabled;
  }
  get transcriptionEnabled() {
    return this._transcriptionEnabled;
  }
  get audio() {
    return this._audioSink;
  }
  set audio(sink) {
    if (sink === this._audioSink) {
      return;
    }
    if (this._audioSink) {
      this._audioSink.onDetached();
    }
    this._audioSink = sink;
    this.audioChanged();
    if (this._audioSink) {
      this._audioSink.onAttached();
    }
  }
  get transcription() {
    return this._transcriptionSink;
  }
  set transcription(sink) {
    if (sink === this._transcriptionSink) {
      return;
    }
    if (this._transcriptionSink) {
      this._transcriptionSink.onDetached();
    }
    this._transcriptionSink = sink;
    this.transcriptionChanged();
    if (this._transcriptionSink) {
      this._transcriptionSink.onAttached();
    }
  }
}
export {
  AgentInput,
  AgentOutput,
  AudioInput,
  AudioOutput,
  TIMED_STRING_SYMBOL,
  TextOutput,
  createTimedString,
  isTimedString
};
//# sourceMappingURL=io.js.map