import type { IUnifiedEventBus, EventFilter } from '../../interfaces/events/unified-event-bus';
import type { RunnerEvent, RunnerEventType } from '../../interfaces/events/runner-events';
import type { SessionEvent, SessionEventType } from '../../interfaces/events/session-events';

export class UnifiedEventBus implements IUnifiedEventBus {
  private runnerListeners = new Map<RunnerEventType, Set<(event: RunnerEvent) => void>>();
  private sessionListeners = new Map<SessionEventType, Set<(event: SessionEvent) => void>>();
  private anyEventListeners = new Set<(event: RunnerEvent | SessionEvent) => void>();
  private eventHistory: (RunnerEvent | SessionEvent)[] = [];
  private readonly maxHistorySize = 1000;

  emitRunnerEvent(event: RunnerEvent): void {
    // Add to history
    this.addToHistory(event);
    
    // Emit to specific listeners
    const listeners = this.runnerListeners.get(event.type);
    if (listeners) {
      listeners.forEach(callback => {
        try {
          callback(event);
        } catch (error) {
          console.error('Error in runner event listener:', error);
        }
      });
    }
    
    // Emit to universal listeners
    this.anyEventListeners.forEach(callback => {
      try {
        callback(event);
      } catch (error) {
        console.error('Error in universal event listener:', error);
      }
    });
  }

  onRunnerEvent(eventType: RunnerEventType, callback: (event: RunnerEvent) => void): () => void {
    if (!this.runnerListeners.has(eventType)) {
      this.runnerListeners.set(eventType, new Set());
    }
    
    this.runnerListeners.get(eventType)!.add(callback);
    
    // Return unsubscribe function
    return () => {
      const listeners = this.runnerListeners.get(eventType);
      if (listeners) {
        listeners.delete(callback);
        if (listeners.size === 0) {
          this.runnerListeners.delete(eventType);
        }
      }
    };
  }

  emitSessionEvent(event: SessionEvent): void {
    // Add to history
    this.addToHistory(event);
    
    // Emit to specific listeners
    const listeners = this.sessionListeners.get(event.type);
    if (listeners) {
      listeners.forEach(callback => {
        try {
          callback(event);
        } catch (error) {
          console.error('Error in session event listener:', error);
        }
      });
    }
    
    // Emit to universal listeners
    this.anyEventListeners.forEach(callback => {
      try {
        callback(event);
      } catch (error) {
        console.error('Error in universal event listener:', error);
      }
    });
  }

  onSessionEvent(eventType: SessionEventType, callback: (event: SessionEvent) => void): () => void {
    if (!this.sessionListeners.has(eventType)) {
      this.sessionListeners.set(eventType, new Set());
    }
    
    this.sessionListeners.get(eventType)!.add(callback);
    
    // Return unsubscribe function
    return () => {
      const listeners = this.sessionListeners.get(eventType);
      if (listeners) {
        listeners.delete(callback);
        if (listeners.size === 0) {
          this.sessionListeners.delete(eventType);
        }
      }
    };
  }

  onAnyEvent(callback: (event: RunnerEvent | SessionEvent) => void): () => void {
    this.anyEventListeners.add(callback);
    
    // Return unsubscribe function
    return () => {
      this.anyEventListeners.delete(callback);
    };
  }

  getEventHistory(filter?: EventFilter): (RunnerEvent | SessionEvent)[] {
    let filtered = [...this.eventHistory];
    
    if (filter) {
      if (filter.eventTypes) {
        filtered = filtered.filter(event => filter.eventTypes!.includes(event.type as any));
      }
      
      if (filter.sessionLabel) {
        filtered = filtered.filter(event => 
          'sessionLabel' in event && event.sessionLabel === filter.sessionLabel
        );
      }
      
      if (filter.timeRange) {
        filtered = filtered.filter(event => {
          const eventTime = new Date(event.timestamp);
          return eventTime >= filter.timeRange!.start && eventTime <= filter.timeRange!.end;
        });
      }
      
      if (filter.maxEvents) {
        filtered = filtered.slice(-filter.maxEvents);
      }
    }
    
    return filtered;
  }

  clearEventHistory(): void {
    this.eventHistory = [];
  }

  hasListeners(eventType?: RunnerEventType | SessionEventType): boolean {
    if (!eventType) {
      return this.runnerListeners.size > 0 || 
             this.sessionListeners.size > 0 || 
             this.anyEventListeners.size > 0;
    }
    
    return (this.runnerListeners.get(eventType as RunnerEventType)?.size || 0) > 0 ||
           (this.sessionListeners.get(eventType as SessionEventType)?.size || 0) > 0;
  }

  getListenerCount(eventType?: RunnerEventType | SessionEventType): number {
    if (!eventType) {
      let total = this.anyEventListeners.size;
      this.runnerListeners.forEach(listeners => total += listeners.size);
      this.sessionListeners.forEach(listeners => total += listeners.size);
      return total;
    }
    
    const runnerCount = this.runnerListeners.get(eventType as RunnerEventType)?.size || 0;
    const sessionCount = this.sessionListeners.get(eventType as SessionEventType)?.size || 0;
    return runnerCount + sessionCount;
  }

  destroy(): void {
    this.runnerListeners.clear();
    this.sessionListeners.clear();
    this.anyEventListeners.clear();
    this.eventHistory = [];
  }

  private addToHistory(event: RunnerEvent | SessionEvent): void {
    this.eventHistory.push(event);
    
    // Trim history if it gets too large
    if (this.eventHistory.length > this.maxHistorySize) {
      this.eventHistory = this.eventHistory.slice(-this.maxHistorySize);
    }
  }
}
