/**
 * IActivitySink — pluggable consumer for ActivityFrame events.
 *
 * Registered in the service registry via ACTIVITY_SINK_KEY.
 * SystemLlm pushes frames to the sink after building them,
 * in parallel with the existing activityTo push path.
 *
 * See: ARCHITECTURE/ARCHITECTURE-OBSERVABILITY-PIPELINE-V2.md
 */

import type { ActivityFrame } from './ActivityFrame';

// ── Service Key ──────────────────────────────────────────────────────────────

/**
 * Symbol key for registering the activity sink in the service registry.
 * Usage: context.setService(ACTIVITY_SINK_KEY, sink)
 */
export const ACTIVITY_SINK_KEY = Symbol.for('matrix.activity.sink');

// ── Interface ────────────────────────────────────────────────────────────────

export interface IActivitySink {
  onFrame(frame: ActivityFrame): void;
  dispose?(): void;
}

// ── CompoundActivitySink ─────────────────────────────────────────────────────

/**
 * Fan-out sink that dispatches to multiple child sinks.
 * Catches and swallows errors from individual sinks so one bad consumer
 * never blocks the pipeline.
 */
export class CompoundActivitySink implements IActivitySink {
  private _sinks: IActivitySink[] = [];

  add(sink: IActivitySink): void {
    this._sinks.push(sink);
  }

  remove(sink: IActivitySink): void {
    const i = this._sinks.indexOf(sink);
    if (i >= 0) this._sinks.splice(i, 1);
  }

  onFrame(frame: ActivityFrame): void {
    for (const s of this._sinks) {
      try {
        s.onFrame(frame);
      } catch (_) {
        /* never throw — one bad sink must not block the pipeline */
      }
    }
  }

  dispose(): void {
    for (const s of this._sinks) {
      try { s.dispose?.(); } catch (_) { /* swallow */ }
    }
    this._sinks.length = 0;
  }
}
