import { Utils } from '../utils/Utils.js';
import { EventType, EventTypeMap } from '../enums.js';
/** Manages event subscribers and dispatches entity/flush/transaction lifecycle events. */
export class EventManager {
  #listeners = {};
  #entities = new Map();
  #cache = new Map();
  #subscribers = new Set();
  constructor(subscribers) {
    for (const subscriber of subscribers) {
      this.registerSubscriber(subscriber);
    }
  }
  /** Registers an event subscriber and indexes its subscribed entities and event types. */
  registerSubscriber(subscriber) {
    if (this.#subscribers.has(subscriber)) {
      return;
    }
    this.#subscribers.add(subscriber);
    this.#entities.set(subscriber, this.getSubscribedEntities(subscriber));
    this.#cache.clear();
    Utils.keys(EventType)
      .filter(event => event in subscriber)
      .forEach(event => {
        this.#listeners[event] ??= new Set();
        this.#listeners[event].add(subscriber);
      });
  }
  /** Returns the set of all registered event subscribers. */
  getSubscribers() {
    return this.#subscribers;
  }
  dispatchEvent(event, args, meta) {
    const listeners = [];
    const entity = args.entity;
    // execute lifecycle hooks first
    meta ??= entity?.__meta;
    const hooks = meta?.hooks[event] || [];
    listeners.push(
      ...hooks.map(hook => {
        const prototypeHook = meta?.prototype[hook];
        const handler = typeof hook === 'function' ? hook : (entity[hook] ?? prototypeHook);
        return handler.bind(entity);
      }),
    );
    for (const listener of this.#listeners[event] ?? new Set()) {
      const entities = this.#entities.get(listener);
      if (entities.size === 0 || !entity || entities.has(entity.constructor.name)) {
        listeners.push(listener[event].bind(listener));
      }
    }
    if (event === EventType.onInit) {
      for (const listener of listeners) {
        void listener(args);
      }
      return;
    }
    return Utils.runSerial(listeners, listener => listener(args));
  }
  /** Checks whether there are any listeners (hooks or subscribers) for the given event type and entity. */
  hasListeners(event, meta) {
    const cacheKey = meta._id + EventTypeMap[event];
    if (this.#cache.has(cacheKey)) {
      return this.#cache.get(cacheKey);
    }
    const hasHooks = meta.hooks[event]?.length;
    if (hasHooks) {
      this.#cache.set(cacheKey, true);
      return true;
    }
    for (const listener of this.#listeners[event] ?? new Set()) {
      const entities = this.#entities.get(listener);
      if (entities.size === 0 || entities.has(meta.className)) {
        this.#cache.set(cacheKey, true);
        return true;
      }
    }
    this.#cache.set(cacheKey, false);
    return false;
  }
  /** Creates a new EventManager with the same set of subscribers. */
  clone() {
    return new EventManager(this.#subscribers);
  }
  getSubscribedEntities(listener) {
    if (!listener.getSubscribedEntities) {
      return new Set();
    }
    return new Set(listener.getSubscribedEntities().map(name => Utils.className(name)));
  }
}
