/**
 * DownloadEventBus
 *
 * Browser-native EventEmitter for asset preload progress tracking.
 * Replaces Socket.IO pattern from hoboken repository with client-side events.
 */

import { OFFLINE_CONFIG } from '../../config/offline.config';
import { logger } from '../logger';
import type {
  PreloadStartEvent,
  PreloadProgressEvent,
  PreloadCompleteEvent,
  PreloadErrorEvent,
  ProjectDownloadProgressEvent,
  ProjectDownloadCompleteEvent,
} from '../../types/offline';

type EventMap = {
  [OFFLINE_CONFIG.events.preloadStart]: PreloadStartEvent;
  [OFFLINE_CONFIG.events.preloadProgress]: PreloadProgressEvent;
  [OFFLINE_CONFIG.events.preloadComplete]: PreloadCompleteEvent;
  [OFFLINE_CONFIG.events.preloadError]: PreloadErrorEvent;
  [OFFLINE_CONFIG.events.projectDownloadProgress]: ProjectDownloadProgressEvent;
  [OFFLINE_CONFIG.events.projectDownloadComplete]: ProjectDownloadCompleteEvent;
  [OFFLINE_CONFIG.events.queueUpdate]: void;
};

type EventCallback<T> = (data: T) => void;

class DownloadEventBusClass {
  private listeners: Map<string, Set<EventCallback<unknown>>> = new Map();

  /**
   * Subscribe to an event
   */
  on<K extends keyof EventMap>(
    event: K,
    callback: EventCallback<EventMap[K]>,
  ): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    const listenerSet = this.listeners.get(event);
    if (listenerSet) {
      listenerSet.add(callback as EventCallback<unknown>);
    }

    // Return unsubscribe function
    return () => this.off(event, callback);
  }

  /**
   * Unsubscribe from an event
   */
  off<K extends keyof EventMap>(
    event: K,
    callback: EventCallback<EventMap[K]>,
  ): void {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      callbacks.delete(callback as EventCallback<unknown>);
    }
  }

  /**
   * Emit an event to all subscribers
   */
  emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      callbacks.forEach((callback) => {
        try {
          callback(data);
        } catch (error) {
          logger.error(
            `[DownloadEventBus] Error in ${event} handler:`,
            error instanceof Error ? error : { error },
          );
        }
      });
    }
  }

  /**
   * Subscribe to an event for one emission only
   */
  once<K extends keyof EventMap>(
    event: K,
    callback: EventCallback<EventMap[K]>,
  ): () => void {
    const wrappedCallback: EventCallback<EventMap[K]> = (data) => {
      this.off(event, wrappedCallback);
      callback(data);
    };
    return this.on(event, wrappedCallback);
  }

  /**
   * Remove all listeners for an event (or all events if no event specified)
   */
  removeAllListeners(event?: keyof EventMap): void {
    if (event) {
      this.listeners.delete(event);
    } else {
      this.listeners.clear();
    }
  }

  /**
   * Get listener count for an event
   */
  listenerCount(event: keyof EventMap): number {
    return this.listeners.get(event)?.size ?? 0;
  }

  // Convenience methods for common events

  /**
   * Emit preload start event
   */
  emitPreloadStart(data: PreloadStartEvent): void {
    this.emit(OFFLINE_CONFIG.events.preloadStart as keyof EventMap, data);
  }

  /**
   * Emit preload progress event
   */
  emitPreloadProgress(data: PreloadProgressEvent): void {
    this.emit(OFFLINE_CONFIG.events.preloadProgress as keyof EventMap, data);
  }

  /**
   * Emit preload complete event
   */
  emitPreloadComplete(data: PreloadCompleteEvent): void {
    this.emit(OFFLINE_CONFIG.events.preloadComplete as keyof EventMap, data);
  }

  /**
   * Emit preload error event
   */
  emitPreloadError(data: PreloadErrorEvent): void {
    this.emit(OFFLINE_CONFIG.events.preloadError as keyof EventMap, data);
  }

  /**
   * Emit project download progress event
   */
  emitProjectProgress(data: ProjectDownloadProgressEvent): void {
    this.emit(
      OFFLINE_CONFIG.events.projectDownloadProgress as keyof EventMap,
      data,
    );
  }

  /**
   * Emit project download complete event
   */
  emitProjectComplete(data: ProjectDownloadCompleteEvent): void {
    this.emit(
      OFFLINE_CONFIG.events.projectDownloadComplete as keyof EventMap,
      data,
    );
  }

  /**
   * Emit queue update event
   */
  emitQueueUpdate(): void {
    this.emit(
      OFFLINE_CONFIG.events.queueUpdate as keyof EventMap,
      undefined as never,
    );
  }
}

// Singleton instance
export const downloadEventBus = new DownloadEventBusClass();
