import type { EventEnvelope, ServerEvent, ThreadId, ThreadTranscriptStats } from "@best-agent/protocol";

export type RuntimeEventListener = (event: EventEnvelope) => void;

export interface RuntimeEventRecorder {
  appendEvent(event: ServerEvent): EventEnvelope;
}

type TranscriptState = {
  deltaCount: number;
  latestCursor: number;
  latestSequence: number;
};

export class RuntimeEventBus {
  readonly #listeners = new Set<RuntimeEventListener>();
  readonly #transcriptStats = new Map<ThreadId, TranscriptState>();
  readonly #recorder?: RuntimeEventRecorder;
  #nextSequence = 1;

  constructor(recorder?: RuntimeEventRecorder) {
    this.#recorder = recorder;
  }

  subscribe(listener: RuntimeEventListener): () => void {
    this.#listeners.add(listener);
    return () => {
      this.#listeners.delete(listener);
    };
  }

  clear(): void {
    this.#listeners.clear();
  }

  emit(event: ServerEvent): EventEnvelope {
    const envelope =
      this.#recorder?.appendEvent(event) ??
      {
        event,
        sequence: this.#nextSequence++,
      };
    this.#nextSequence = Math.max(this.#nextSequence, envelope.sequence + 1);

    this.#recordTranscriptStats(envelope);
    for (const listener of this.#listeners) {
      listener(envelope);
    }
    return envelope;
  }

  emitTransient(event: ServerEvent): EventEnvelope {
    const envelope: EventEnvelope = {
      event,
      sequence: this.#nextSequence++,
    };
    this.#recordTranscriptStats(envelope);
    for (const listener of this.#listeners) {
      listener(envelope);
    }
    return envelope;
  }

  #recordTranscriptStats(envelope: EventEnvelope): void {
    const { event } = envelope;
    if (event.type === "thread.transcript.delta") {
      const state = this.#ensureTranscriptState(event.threadId);
      state.deltaCount += 1;
      state.latestCursor = event.cursor;
      state.latestSequence = envelope.sequence;
    }
  }

  getTranscriptStats(threadId: ThreadId): ThreadTranscriptStats {
    const state = this.#transcriptStats.get(threadId);
    return {
      deltaCount: state?.deltaCount ?? 0,
      latestCursor: state?.latestCursor ?? 0,
      latestSequence: state?.latestSequence ?? 0,
    };
  }

  #ensureTranscriptState(threadId: ThreadId): TranscriptState {
    let state = this.#transcriptStats.get(threadId);
    if (!state) {
      state = {
        deltaCount: 0,
        latestCursor: 0,
        latestSequence: 0,
      };
      this.#transcriptStats.set(threadId, state);
    }
    return state;
  }
}
