/**
 * @archivo src/js/event-target.js
 * /
importar * como Eventos desde './utils/events.js';
importar ventana desde 'global/window';

/**
 * `EventTarget` es una clase que puede tener la misma API que DOM `EventTarget`. Él
 * agrega funciones abreviadas que envuelven funciones largas. Por ejemplo:
 * la función `on` es un envoltorio alrededor de `addEventListener`.
 *
 * @ver [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
 * @clase EventObjetivo
 * /
const EventTarget = function() {};

/**
 * Un evento DOM personalizado.
 *
 * @typedef {Objeto} Objetivo de evento ~ Evento
 * @ver [Propiedades]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
 * /

/**
 * Todos los oyentes de eventos deben seguir el siguiente formato.
 *
 * @callback EventObjetivo ~ EventListener
 * @este {objetivo de evento}
 *
 * @param {EventTarget~Evento} evento
 * el evento que activó esta función
 *
 * @param {Objeto} [hash]
 * hash de datos enviados durante el evento
 * /

/**
 * Un objeto que contiene nombres de eventos como claves y booleanos como valores.
 *
 * > NOTA: Si el nombre de un evento se establece en un valor verdadero aquí {@link EventTarget#trigger}
 * tendrá funcionalidad extra. Consulte esa función para obtener más información.
 *
 * @property EventTarget.prototype.allowedEvents_
 * @privado
 * /
EventTarget.prototype.allowedEvents_ = {};

/**
 * Agrega un `escucha de eventos` a una instancia de un `EventTarget`. Un `escucha de eventos` es un
 * función que se llamará cuando se active un evento con un nombre determinado.
 *
 * @param {cadena|cadena[]} tipo
 * Un nombre de evento o una matriz de nombres de eventos.
 *
 * @param {EventTarget~EventListener} fn
 * La función para llamar con `EventTarget`s
 * /
EventTarget.prototype.on = función (tipo, fn) {
  // Eliminar el alias addEventListener antes de llamar a Events.on
  // para que no entremos en un bucle de tipo infinito
  const ael = this.addEventListener;

  this.addEventListener = () => {};
  Eventos.on(esto, tipo, fn);
  this.addEventListener = ael;
};

/**
 * Un alias de {@link EventTarget#on}. Permite que `EventTarget` imite
 * la API DOM estándar.
 *
 * @función
 * @ver {@link EventTarget#on}
 * /
EventTarget.prototype.addEventListener = EventTarget.prototype.on;

/**
 * Elimina un `escucha de eventos` para un evento específico de una instancia de `EventTarget`.
 * Esto hace que el `event listener` ya no sea llamado cuando el
 * Sucede el evento nombrado.
 *
 * @param {cadena|cadena[]} tipo
 * Un nombre de evento o una matriz de nombres de eventos.
 *
 * @param {EventTarget~EventListener} fn
 * La función de eliminar.
 * /
EventTarget.prototype.off = función (tipo, fn) {
  Eventos.off(esto, tipo, fn);
};

/**
 * Un alias de {@link EventTarget#off}. Permite que `EventTarget` imite
 * la API DOM estándar.
 *
 * @función
 * @ver {@link EventTarget#off}
 * /
EventTarget.prototype.removeEventListener = EventTarget.prototype.off;

/**
 * Esta función agregará un "escucha de eventos" que se activa solo una vez. Después de la
 * primer activador se eliminará. Esto es como agregar un `escucha de eventos`
 * con {@link EventTarget#on} que llama {@link EventTarget#off} sobre sí mismo.
 *
 * @param {cadena|cadena[]} tipo
 * Un nombre de evento o una matriz de nombres de eventos.
 *
 * @param {EventTarget~EventListener} fn
 * La función que se llamará una vez para cada nombre de evento.
 * /
EventTarget.prototype.one = función (tipo, fn) {
  // Eliminar el alias de addEventListener Events.on
  // para que no entremos en un bucle de tipo infinito
  const ael = this.addEventListener;

  this.addEventListener = () => {};
  Eventos.uno(esto, tipo, fn);
  this.addEventListener = ael;
};

EventTarget.prototype.any = función (tipo, fn) {
  // Eliminar el alias de addEventListener Events.on
  // para que no entremos en un bucle de tipo infinito
  const ael = this.addEventListener;

  this.addEventListener = () => {};
  Eventos.cualquiera(esto, tipo, fn);
  this.addEventListener = ael;
};

/**
 * Esta función hace que suceda un evento. Esto hará que cualquier 'escucha de eventos'
 * que están esperando ese evento, para ser llamados. Si no hay 'escuchadores de eventos'
 * para un evento entonces no pasará nada.
 *
 * Si el nombre del `Evento` que se activa está en `EventTarget.allowedEvents_`.
 * Trigger también llamará a la función `on` + `uppercaseEventName`.
 *
 * Ejemplo:
 * 'clic' está en `EventTarget.allowedEvents_`, por lo tanto, el disparador intentará llamar
 * `onClick` si existe.
 *
 * @param {string|EventTarget~Event|Object} evento
 * El nombre del evento, un 'Evento' o un objeto con una clave de tipo establecida en
 * un nombre de evento.
 * /
EventTarget.prototype.trigger = función (evento) {
  const tipo = evento.tipo || evento;

  // desaprobación
  // En una versión futura, deberíamos tener como destino predeterminado `this`
  // similar a cómo por defecto el objetivo es `elem` en
  // `Eventos.trigger`. En este momento, el "objetivo" predeterminado será
  // `document` debido a la llamada `Event.fixEvent`.
  if (tipo de evento === 'cadena') {
    evento = {tipo};
  }
  evento = Eventos.fixEvent(evento);

  if (this.allowedEvents_[tipo] && este['en' + tipo]) {
    este['en' + tipo](evento);
  }

  Eventos.trigger(este, evento);
};

/**
 * Un alias de {@link EventTarget#trigger}. Permite que `EventTarget` imite
 * la API DOM estándar.
 *
 * @función
 * @ver {@link EventTarget#trigger}
 * /
EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;

dejar EVENT_MAP;

EventTarget.prototype.queueTrigger = función (evento) {
  // solo configure EVENT_MAP si se usará
  si (!EVENTO_MAPA) {
    EVENT_MAP = nuevo Mapa();
  }

  const tipo = evento.tipo || evento;
  let map = EVENT_MAP.get(this);

  si (!mapa) {
    mapa = nuevo Mapa();
    EVENT_MAP.set(esto, mapa);
  }

  const oldTimeout = map.get(tipo);

  map.delete(tipo);
  ventana.clearTimeout(oldTimeout);

  const tiempo de espera = ventana.setTimeout(() => {
    map.delete(tipo);
    // si eliminamos todos los tiempos de espera para el objetivo actual, eliminamos su mapa
    if (mapa.tamaño === 0) {
      mapa = nulo;
      EVENT_MAP.delete(esto);
    }

    this.trigger(evento);
  }, 0);

  map.set(tipo, tiempo de espera);
};

exportar EventTarget por defecto;