//@ts-check
/// <reference path="makeEventEmitter.d.ts"/>

/**
 * Returns a native browser event target that is properly typed.
 * Three major differences with a classical event target:
 *
 * 1. The emitter's methods are bound and can be passed to other objects
 * 2. The emitter has an `abort` property and a `signal` property that can be
 *    used to abort all listeners (you have to explicitely pass it though, it's
 *    not automatic)
 * 3. `dispatchEvent` has a different signature `(type, event)` rather than just
 *     `event`. This is because there is no way to enforce a string & details
 *     tuple on a CustomEvent using Typescript or JSDocs.
 * @template {CustomEventMap} EvtMap
 * @returns {CustomEventEmitter<EvtMap>}
 */
export const makeEventEmitter = () => {
  let abortController = new AbortController();
  const eventEmitter = new EventTarget();
  const addEventListener = eventEmitter.addEventListener.bind(eventEmitter);
  const removeEventListener =
    eventEmitter.removeEventListener.bind(eventEmitter);

  /**
   * Dispatches a custom event to all listeners of that event.
   * @type {CustomEventEmitter<EvtMap>["dispatchEvent"]}
   */
  const dispatchEvent = (type, detail) => {
    const event = new CustomEvent(type, { detail });
    eventEmitter.dispatchEvent(event);
  };

  /**
   * Aborts any eventListener, fetch, or other process that received the signal.
   * resets the abort controller and signal (they are new instances)
   * @param {any} reason
   */
  const abort = (reason) => {
    abortController.abort(reason);
    abortController = new AbortController();
  };

  return {
    dispatchEvent,
    addEventListener,
    removeEventListener,
    abort,
    get signal() {
      return abortController.signal;
    },
  };
};

export default makeEventEmitter