/**
 * Minimal typed pub/sub shared by the native handle (`native.ts`) and the
 * mock (`mock.ts`). Deliberately mirrors the subset of
 * `expo-modules-core`'s `EventEmitter` API surface (`addListener`,
 * `removeAllListeners`) that consumers rely on, so `events.ts`'s helpers
 * work identically against either.
 *
 * `SimpleEventEmitter` itself has zero runtime dependency on
 * `expo`/`expo-modules-core`/`react-native` — only a type-only import of
 * `EventSubscription`, which is erased at compile time. That's what lets
 * `mock.ts` (and this file) be imported from Vitest/plain Node.
 */
import type { EventSubscription } from "expo-modules-core";

export type EventsMap = Record<string, (...args: any[]) => void>;

/** Structural shape both the real native module and `MapmapNavMock` satisfy. */
export interface MapmapNavEmitterLike<Events extends EventsMap> {
  addListener<K extends keyof Events>(eventName: K, listener: Events[K]): EventSubscription;
  removeAllListeners?(eventName: keyof Events): void;
}

export class SimpleEventEmitter<Events extends EventsMap> implements MapmapNavEmitterLike<Events> {
  private readonly listenersByEvent = new Map<keyof Events, Set<Events[keyof Events]>>();

  addListener<K extends keyof Events>(eventName: K, listener: Events[K]): EventSubscription {
    let listeners = this.listenersByEvent.get(eventName);
    if (!listeners) {
      listeners = new Set();
      this.listenersByEvent.set(eventName, listeners);
    }
    listeners.add(listener as Events[keyof Events]);

    let removed = false;
    return {
      remove: () => {
        if (removed) return;
        removed = true;
        listeners?.delete(listener as Events[keyof Events]);
      },
    };
  }

  removeAllListeners(eventName: keyof Events): void {
    this.listenersByEvent.get(eventName)?.clear();
  }

  listenerCount(eventName: keyof Events): number {
    return this.listenersByEvent.get(eventName)?.size ?? 0;
  }

  protected emit<K extends keyof Events>(eventName: K, ...args: Parameters<Events[K]>): void {
    const listeners = this.listenersByEvent.get(eventName);
    if (!listeners || listeners.size === 0) return;
    // Snapshot before iterating: a listener may unsubscribe itself (or
    // another listener) mid-emit.
    for (const listener of Array.from(listeners)) {
      (listener as (...args: Parameters<Events[K]>) => void)(...args);
    }
  }
}
