/**
 * SSE (Server-Sent Events) Handler
 *
 * Streams agent events to HTTP clients using SSE protocol.
 */

import { EventType, type AgentEvent, type EventHandler, eventToJSON } from './types.js';

/**
 * SSE event format
 */
export interface SSEEvent {
  type: EventType;
  data: Record<string, unknown>;
  timestamp: string;
  agentName?: string;
}

/**
 * Format an event as SSE wire protocol
 */
export function formatSSE(event: SSEEvent): string {
  const json = JSON.stringify({
    ...event.data,
    timestamp: event.timestamp,
    agent_name: event.agentName,
  });
  return `event: ${event.type}\ndata: ${json}\n\n`;
}

/**
 * Format a keep-alive ping
 */
export function formatPing(): string {
  return ': ping\n\n';
}

/**
 * SSE Handler
 *
 * Queues agent events and provides async iteration for streaming.
 * Thread-safe via array-based queue with promise signaling.
 */
export class SSEHandler implements EventHandler {
  private queue: SSEEvent[] = [];
  private closed = false;
  private agentName?: string;
  private resolvers: Array<(value: void) => void> = [];

  constructor(agentName?: string) {
    this.agentName = agentName;
  }

  /**
   * Handle an agent event (EventHandler interface)
   */
  handle(event: AgentEvent): void {
    if (this.closed) return;

    const sseEvent: SSEEvent = {
      type: event.type,
      data: event.data,
      timestamp: event.timestamp.toISOString(),
      agentName: event.agentName ?? this.agentName,
    };

    this.queue.push(sseEvent);
    this.notifyWaiters();
  }

  /**
   * Emit an SSE event directly
   */
  emit(eventType: EventType, data: Record<string, unknown>): void {
    if (this.closed) return;

    const sseEvent: SSEEvent = {
      type: eventType,
      data,
      timestamp: new Date().toISOString(),
      agentName: this.agentName,
    };

    this.queue.push(sseEvent);
    this.notifyWaiters();
  }

  /**
   * Close the handler
   */
  close(): void {
    this.closed = true;
    this.notifyWaiters();
  }

  /**
   * Check if handler is closed
   */
  isClosed(): boolean {
    return this.closed;
  }

  /**
   * Notify any waiting consumers
   */
  private notifyWaiters(): void {
    const resolvers = this.resolvers;
    this.resolvers = [];
    for (const resolve of resolvers) {
      resolve();
    }
  }

  /**
   * Wait for new events
   */
  private waitForEvents(): Promise<void> {
    return new Promise((resolve) => {
      if (this.queue.length > 0 || this.closed) {
        resolve();
      } else {
        this.resolvers.push(resolve);
      }
    });
  }

  /**
   * Async iterator for SSE-formatted strings
   */
  async *[Symbol.asyncIterator](): AsyncIterator<string> {
    const PING_INTERVAL = 30000; // 30 seconds
    let lastPing = Date.now();

    while (!this.closed || this.queue.length > 0) {
      // Wait for events with timeout for ping
      const timeoutPromise = new Promise<void>((resolve) => {
        setTimeout(resolve, PING_INTERVAL);
      });

      await Promise.race([this.waitForEvents(), timeoutPromise]);

      // Drain the queue
      while (this.queue.length > 0) {
        const event = this.queue.shift()!;
        yield formatSSE(event);
      }

      // Send ping if needed
      if (Date.now() - lastPing >= PING_INTERVAL) {
        yield formatPing();
        lastPing = Date.now();
      }
    }
  }

  /**
   * Get all queued events (for debugging)
   */
  getQueuedEvents(): SSEEvent[] {
    return [...this.queue];
  }
}

/**
 * Create SSE stream from handler
 */
export async function* createSSEStream(handler: SSEHandler): AsyncGenerator<string> {
  yield* handler;
}

/**
 * SSE Bridge Handler
 *
 * Bridges AgentEventEmitter to SSEHandler.
 */
export class SSEBridgeHandler implements EventHandler {
  private sseHandler: SSEHandler;

  constructor(sseHandler: SSEHandler) {
    this.sseHandler = sseHandler;
  }

  handle(event: AgentEvent): void {
    this.sseHandler.handle(event);
  }
}

/**
 * Create an SSE handler for streaming
 */
export function createSSEHandler(agentName?: string): SSEHandler {
  return new SSEHandler(agentName);
}
