/**
 * Typed event emitter for the Ion workflow engine.
 *
 * Provides a singleton event bus for workflow lifecycle events.
 * Supports both global and run-scoped subscriptions.
 */

export type WorkflowEventType =
  | 'workflow_started'
  | 'workflow_completed'
  | 'workflow_failed'
  | 'workflow_cancelled'
  | 'node_started'
  | 'node_completed'
  | 'node_failed'
  | 'node_skipped'
  | 'loop_iteration_started'
  | 'loop_iteration_completed'
  | 'approval_pending';

export interface WorkflowEventBase {
  /** Discriminator for the event type. */
  type: WorkflowEventType;
  /** The workflow run id. */
  runId: string;
  /** The node id (when applicable). */
  nodeId?: string;
  /** The workflow name. */
  workflowName?: string;
  /** Error message (for failure events). */
  error?: string;
  /** Human-readable step name. */
  stepName?: string;
  /** Arbitrary metadata attached to the event. */
  metadata?: Record<string, unknown>;
}

/** Specific shapes for strongly-typed event handling. */
export interface WorkflowStartedEvent extends WorkflowEventBase {
  type: 'workflow_started';
  workflowName: string;
}

export interface WorkflowCompletedEvent extends WorkflowEventBase {
  type: 'workflow_completed';
  workflowName: string;
}

export interface WorkflowFailedEvent extends WorkflowEventBase {
  type: 'workflow_failed';
  error: string;
}

export interface WorkflowCancelledEvent extends WorkflowEventBase {
  type: 'workflow_cancelled';
}

export interface NodeStartedEvent extends WorkflowEventBase {
  type: 'node_started';
  nodeId: string;
  stepName?: string;
}

export interface NodeCompletedEvent extends WorkflowEventBase {
  type: 'node_completed';
  nodeId: string;
  stepName?: string;
}

export interface NodeFailedEvent extends WorkflowEventBase {
  type: 'node_failed';
  nodeId: string;
  error: string;
}

export interface NodeSkippedEvent extends WorkflowEventBase {
  type: 'node_skipped';
  nodeId: string;
  stepName?: string;
}

export interface LoopIterationStartedEvent extends WorkflowEventBase {
  type: 'loop_iteration_started';
  nodeId: string;
  metadata?: { iteration: number };
}

export interface LoopIterationCompletedEvent extends WorkflowEventBase {
  type: 'loop_iteration_completed';
  nodeId: string;
  metadata?: { iteration: number };
}

export interface ApprovalPendingEvent extends WorkflowEventBase {
  type: 'approval_pending';
  nodeId: string;
  metadata?: { approver?: string; reason?: string };
}

export type WorkflowEvent =
  | WorkflowStartedEvent
  | WorkflowCompletedEvent
  | WorkflowFailedEvent
  | WorkflowCancelledEvent
  | NodeStartedEvent
  | NodeCompletedEvent
  | NodeFailedEvent
  | NodeSkippedEvent
  | LoopIterationStartedEvent
  | LoopIterationCompletedEvent
  | ApprovalPendingEvent;

export type WorkflowEventHandler = (event: WorkflowEvent) => void;

export class WorkflowEventEmitter {
  private listeners: Set<WorkflowEventHandler> = new Set();
  private runListeners: Map<string, Set<WorkflowEventHandler>> = new Map();

  /** Subscribe to all workflow events. */
  subscribe(handler: WorkflowEventHandler): () => void {
    this.listeners.add(handler);
    return () => {
      this.listeners.delete(handler);
    };
  }

  /** Unsubscribe a handler from all events. */
  unsubscribe(handler: WorkflowEventHandler): void {
    this.listeners.delete(handler);
  }

  /** Emit an event to all subscribers (global + run-scoped). */
  emit(event: WorkflowEvent): void {
    for (const handler of this.listeners) {
      try {
        handler(event);
      } catch {
        // Swallow handler errors to prevent cascading failures.
      }
    }

    // Also notify run-scoped listeners.
    const runHandlers = this.runListeners.get(event.runId);
    if (runHandlers) {
      for (const handler of runHandlers) {
        try {
          handler(event);
        } catch {
          // Swallow handler errors to prevent cascading failures.
        }
      }
    }
  }

  /** Remove all global listeners and run-scoped listeners. */
  clear(): void {
    this.listeners.clear();
    this.runListeners.clear();
  }

  // -- Run-scoped subscriptions ------------------------------------------------

  /** Register a run-scoped event listener. */
  registerRun(runId: string, handler: WorkflowEventHandler): () => void {
    let handlers = this.runListeners.get(runId);
    if (!handlers) {
      handlers = new Set();
      this.runListeners.set(runId, handlers);
    }
    handlers.add(handler);

    return () => {
      handlers!.delete(handler);
      if (handlers!.size === 0) {
        this.runListeners.delete(runId);
      }
    };
  }

  /** Unregister all listeners for a specific run. */
  unregisterRun(runId: string): void {
    this.runListeners.delete(runId);
  }
}

let instance: WorkflowEventEmitter | undefined;

/** Get the singleton WorkflowEventEmitter instance. */
export function getWorkflowEventEmitter(): WorkflowEventEmitter {
  if (!instance) {
    instance = new WorkflowEventEmitter();
  }
  return instance;
}