/**
 * @archivo mixins/evented.js
 * @module evento
 * /
importar ventana desde 'global/window';
importar * como Dom desde '../utils/dom';
importar * como Eventos desde '../utils/events';
importar * como Fn desde '../utils/fn';
importar * como Obj desde '../utils/obj';
importar EventTarget desde '../event-target';
importar DomData desde '../utils/dom-data';
importar registro desde '../utils/log';

const objNombre = (obj) => {
  if (tipo de obj.nombre === 'función') {
    return obj.nombre();
  }

  if (tipo de obj.nombre === 'cadena') {
    return obj.nombre;
  }

  if (obj.nombre_) {
    return obj.nombre_;
  }

  if (obj.constructor && obj.constructor.nombre) {
    return obj.constructor.nombre;
  }

  tipo de retorno de obj;
};

/**
 * Devuelve si a un objeto se le ha aplicado o no la combinación de eventos.
 *
 * @param {Objeto} objeto
 * Un objeto para probar.
 *
 * @return {booleano}
 * Si el objeto parece estar o no evento.
 * /
const isEvented = (objeto) =>
  instancia de objeto de EventTarget ||
  !!objeto.eventBusEl_ &&
  ['encendido', 'uno', 'apagado', 'disparador'].cada(k => tipo de objeto[k] === 'función');

/**
 * Agrega una devolución de llamada para que se ejecute después de que se aplique la combinación de eventos.
 *
 * @param {Objeto} objeto
 * Un objeto para agregar
 * @param {Función} devolución de llamada
 * La devolución de llamada para ejecutar.
 * /
const addEventedCallback = (objetivo, devolución de llamada) => {
  si (es un evento (objetivo)) {
    llamar de vuelta();
  } else {
    if (!target.eventedCallbacks) {
      target.eventedCallbacks = [];
    }
    target.eventedCallbacks.push(devolución de llamada);
  }
};

/**
 * Si un valor es un tipo de evento válido: cadena o matriz no vacía.
 *
 * @privado
 * @param {cadena|Array} tipo
 * El valor de tipo a probar.
 *
 * @return {booleano}
 * Si el tipo es o no un tipo de evento válido.
 * /
const esValidEventType = (tipo) =>
  // La expresión regular aquí verifica que el `tipo` contiene al menos un no-
  // carácter de espacio en blanco.
  (tipo de tipo === 'cadena' && (/\S/).prueba(tipo)) ||
  (Array.isArray(tipo) && !!tipo.longitud);

/**
 * Valida un valor para determinar si es un objetivo de evento válido. Tira si no.
 *
 * @privado
 * @throws {Error}
 * Si el objetivo no parece ser un objetivo de evento válido.
 *
 * @param {Objeto} objetivo
 * El objeto a probar.
 *
 * @param {Objeto} obj
 * El objeto con eventos que estamos validando
 *
 * @param {cadena} fnName
 * El nombre de la función mixin con eventos que llamó a esto.
 * /
const validarTarget = (objetivo, obj, fnName) => {
  if (!objetivo || (!objetivo.nombreNodo && !isEvented(objetivo))) {
    throw new Error(`Objetivo no válido para ${objName(obj)}#${fnName}; debe ser un nodo DOM o un objeto con eventos.`);
  }
};

/**
 * Valida un valor para determinar si es un objetivo de evento válido. Tira si no.
 *
 * @privado
 * @throws {Error}
 * Si el tipo no parece ser un tipo de evento válido.
 *
 * @param {cadena|Array} tipo
 * El tipo a probar.
 *
 * @param {Objeto} obj
* El objeto con eventos que estamos validando
 *
 * @param {cadena} fnName
 * El nombre de la función mixin con eventos que llamó a esto.
 * /
const validarEventType = (tipo, obj, fnName) => {
  if (!isValidEventType(tipo)) {
    throw new Error(`Tipo de evento no válido para ${objName(obj)}#${fnName}; debe ser una cadena o matriz no vacía.`);
  }
};

/**
 * Valida un valor para determinar si es un oyente válido. Tira si no.
 *
 * @privado
 * @throws {Error}
 * Si el oyente no es una función.
 *
 * @param {Función} oyente
 * El oyente a prueba.
 *
 * @param {Objeto} obj
 * El objeto con eventos que estamos validando
 *
 * @param {cadena} fnName
 * El nombre de la función mixin con eventos que llamó a esto.
 * /
const validarEscucha = (escucha, obj, fnName) => {
  if (tipo de oyente! == 'función') {
    throw new Error(`Oyente no válido para ${objName(obj)}#${fnName}; debe ser una función.`);
  }
};

/**
 * Toma una serie de argumentos dados a `on()` o `one()`, los valida y
 * los normaliza en un objeto.
 *
 * @privado
 * @param {Objeto} propio
 * El objeto con evento en el que se llamó `on()` o `one()`. Esto
 * El objeto se vinculará como el valor `this` para el oyente.
 *
 * @param {Array} argumentos
 * Una matriz de argumentos pasados a `on()` o `one()`.
 *
 * @param {cadena} fnName
 * El nombre de la función mixin con eventos que llamó a esto.
 *
 * @return {Objeto}
 * Un objeto que contiene valores útiles para las llamadas `on()` o `one()`.
 * /
const normalizeListenArgs = (self, args, fnName) => {

  // Si el número de argumentos es inferior a 3, el destino siempre es el
  // objeto con eventos en sí mismo.
  const esTargetingSelf = argumentos.longitud < 3 || argumentos[0] === yo || args[0] === self.eventBusEl_;
  dejar objetivo;
  dejar escribir;
  deja que el oyente;

  if (esTargetingSelf) {
    objetivo = self.eventBusEl_;

    // Tratar los casos en los que obtuvimos 3 argumentos, pero todavía estamos escuchando
    // el propio objeto del evento.
    if (args.longitud > = 3) {
      argumentos.shift();
    }

    [tipo, oyente] = argumentos;
  } else {
    [objetivo, tipo, oyente] = argumentos;
  }

  validarTarget(objetivo, self, fnName);
  validarEventType(tipo, self, fnName);
  validarEscucha(oyente, self, fnName);

  oyente = Fn.bind(self, oyente);

  return {isTargetingSelf, target, type, listener};
};

/**
 * Agrega el oyente a los tipos de eventos en el objetivo, normalizando para
 * el tipo de objetivo.
 *
 * @privado
 * @param {Elemento|Objeto} destino
 * Un nodo DOM u objeto con eventos.
 *
 * Método @param {cadena}
 * El método de vinculación de eventos a usar (&quot;on&quot; o &quot;one&quot;).
 *
 * @param {cadena|Array} tipo
 * Uno o más tipos de eventos.
 *
 * @param {Función} oyente
 * Una función de oyente.
 * /
const listen = (objetivo, método, tipo, oyente) => {
  validarTarget(objetivo, objetivo, método);

  if (objetivo.nombreNodo) {
    Eventos[método](objetivo, tipo, oyente);
  } else {
    objetivo[método](tipo, oyente);
  }
};

/**
 * Contiene métodos que proporcionan capacidades de eventos a un objeto que se pasa
 * a {@link module:evented|evented}.
 *
 * @mixin EventedMixin
 * /
const EventedMixin = {

  /**
   * Agregue un oyente a un evento (o eventos) en este objeto u otro evento
   * objeto.
   *
   * @param {cadena|Array|Element|Object} targetOrType
   * Si se trata de una cadena o matriz, representa los tipos de eventos
   * que activará al oyente.
   *
   * En su lugar, se puede pasar aquí otro objeto con eventos, que
   * hacer que el oyente escuche eventos en _ese_ objeto.
   *
   * En cualquier caso, el valor `this` del oyente estará vinculado a
   * este objeto.
   *
   * @param {cadena|Array|Función} typeOrListener
   * Si el primer argumento era una cadena o una matriz, este debería ser el
   * Función de escucha. De lo contrario, esta es una cadena o matriz de eventos.
   * tipo(s).
   *
   * @param {Función} [oyente]
   * Si el primer argumento fue otro objeto con evento, este será
   * la función de oyente.
   * /
  en (... argumentos) {
    const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'on');

    escuchar (objetivo, 'encendido', tipo, oyente);

    // Si este objeto está escuchando a otro objeto con eventos.
    if (!isTargetingSelf) {

      // Si se desecha este objeto, elimine el oyente.
      const removeListenerOnDispose = () => this.off(objetivo, tipo, oyente);

      // Use el mismo ID de función que el oyente para que podamos eliminarlo más tarde.
      // usando la ID del oyente original.
      removeListenerOnDispose.guid = oyente.guid;

      // Agregue también un oyente al evento de disposición del objetivo. Esto asegura
      // que si el objetivo se desecha ANTES de este objeto, eliminamos el
      // escucha de eliminación que se acaba de agregar. De lo contrario, creamos una pérdida de memoria.
      const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);

      // Use el mismo ID de función que el oyente para que podamos eliminarlo más tarde
      // usando la ID del oyente original.
      removeRemoverOnTargetDispose.guid = oyente.guid;

      listen(this, 'on', 'dispose', removeListenerOnDispose);
      listen(objetivo, 'on', 'dispose', removeRemoverOnTargetDispose);
    }
  },

  /**
   * Agregue un oyente a un evento (o eventos) en este objeto u otro evento
   * objeto. El agente de escucha se llamará una vez por evento y, a continuación, se eliminará.
   *
   * @param {cadena|Array|Element|Object} targetOrType
   * Si se trata de una cadena o matriz, representa los tipos de eventos
   * que activará al oyente.
   *
   * En su lugar, se puede pasar aquí otro objeto con eventos, que
   * hacer que el oyente escuche eventos en _ese_ objeto.
   *
   * En cualquier caso, el valor `this` del oyente estará vinculado a
   * este objeto.
   *
   * @param {cadena|Array|Función} typeOrListener
   * Si el primer argumento era una cadena o una matriz, este debería ser el
   * Función de escucha. De lo contrario, esta es una cadena o matriz de eventos.
   * tipo(s).
   *
   * @param {Función} [oyente]
   * Si el primer argumento fue otro objeto con evento, este será
   * la función de oyente.
   * /
  uno (... argumentos) {
    const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'one');

    // Apuntando a este objeto con eventos.
    if (esTargetingSelf) {
      listen(objetivo, 'uno', tipo, oyente);

    // Apuntando a otro objeto con eventos.
    } else {
      // HACER: ¡Este envoltorio es incorrecto! solo debe
      // elimina el envoltorio del tipo de evento que lo llamó.
      // ¡En cambio, todos los oyentes se eliminan en el primer disparo!
      // ver https://github.com/videojs/video.js/issues/5962
      contenedor const = (...largos) => {
        this.off(objetivo, tipo, envoltorio);
        listener.apply(null, largs);
      };

      // Use el mismo ID de función que el oyente para que podamos eliminarlo más tarde
      // usando la ID del oyente original.
      contenedor.guid = oyente.guid;
      listen(objetivo, 'uno', tipo, envoltorio);
    }
  },

  /**
   * Agregue un oyente a un evento (o eventos) en este objeto u otro evento
   * objeto. El oyente solo será llamado una vez para el primer evento que se active
   * luego eliminado.
   *
   * @param {cadena|Array|Element|Object} targetOrType
   * Si se trata de una cadena o matriz, representa los tipos de eventos
   * que activará al oyente.
   *
   * En su lugar, se puede pasar aquí otro objeto con eventos, que
   * hacer que el oyente escuche eventos en _ese_ objeto.
   *
   * En cualquier caso, el valor `this` del oyente estará vinculado a
   * este objeto.
   *
   * @param {cadena|Array|Función} typeOrListener
   * Si el primer argumento era una cadena o una matriz, este debería ser el
   * Función de escucha. De lo contrario, esta es una cadena o matriz de eventos.
   * tipo(s).
   *
   * @param {Función} [oyente]
   * Si el primer argumento fue otro objeto con evento, este será
   * la función de oyente.
   * /
  cualquiera (...argumentos) {
    const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'any');

    // Apuntando a este objeto con eventos.
    if (esTargetingSelf) {
      listen(objetivo, 'cualquiera', tipo, oyente);

    // Apuntando a otro objeto con eventos.
    } else {
      contenedor const = (...largos) => {
        this.off(objetivo, tipo, envoltorio);
        listener.apply(null, largs);
      };

      // Use el mismo ID de función que el oyente para que podamos eliminarlo más tarde
      // usando la ID del oyente original.
      contenedor.guid = oyente.guid;
      listen(objetivo, 'cualquiera', tipo, envoltorio);
    }
  },

  /**
   * Elimina los oyentes de los eventos en un objeto con eventos.
   *
   * @param {cadena|Matriz|Elemento|Objeto} [targetOrType]
   * Si se trata de una cadena o matriz, representa los tipos de eventos.
   *
   * Aquí se puede pasar otro objeto con evento, en cuyo caso
   * TODOS LOS 3 argumentos son _requeridos_.
   *
   * @param {cadena|Array|Función} [tipoOrListener]
   * Si el primer argumento fue una cadena o una matriz, este puede ser el
   * Función de escucha. De lo contrario, esta es una cadena o matriz de eventos.
   * tipo(s).
   *
   * @param {Función} [oyente]
   * Si el primer argumento fue otro objeto con evento, este será
   * la función de oyente; de lo contrario, _todos_ los oyentes vinculados al
   * Se eliminarán los tipos de eventos.
   * /
  off(targetOrType, typeOrListener, listener) {

    // Apuntando a este objeto con eventos.
    if (!targetOrType || isValidEventType(targetOrType)) {
      Events.off(this.eventBusEl_, targetOrType, typeOrListener);

    // Apuntando a otro objeto con eventos.
    } else {
      const target = targetOrType;
      const tipo = typeOrListener;

      // ¡Falla rápido y de manera significativa!
      validarTarget(objetivo, esto, 'apagado');
      validarEventType(tipo, esto, 'apagado');
      validarEscucha(escucha, esto, 'apagado');

      // Asegúrese de que haya al menos un GUID, incluso si la función no se ha utilizado
      oyente = Fn.bind(esto, oyente);

      // Eliminar el detector de disposición en este objeto con eventos, que se proporcionó
      // el mismo GUID que el detector de eventos en on().
      this.off('dispose', oyente);

      if (objetivo.nombreNodo) {
        Events.off(objetivo, tipo, oyente);
        Events.off(objetivo, 'disponer', oyente);
      } más si (es un evento (objetivo)) {
        target.off(tipo, oyente);
        target.off('dispose', oyente);
      }
    }
  },

  /**
   * Activar un evento en este objeto con evento, lo que hace que se llame a sus oyentes.
   *
   * @param {cadena|Objeto} evento
   * Un tipo de evento o un objeto con una propiedad de tipo.
   *
   * @param {Objeto} [hash]
   * Un objeto adicional para pasar a los oyentes.
   *
   * @return {booleano}
   * Si se evitó o no el comportamiento predeterminado.
   * /
  disparador (evento, hash) {
    validarTarget(this.eventBusEl_, this, 'trigger');

    tipo const = evento && tipo de evento !== 'cadena' ? evento.tipo : evento;

    if (!isValidEventType(tipo)) {
      const error = `Tipo de evento no válido para ${objName(this)}#trigger; ` +
        'debe ser una cadena u objeto no vacío con una clave de tipo que tiene un valor no vacío.';

      si (evento) {
        (this.log || log).error(error);
      } else {
        lanzar un nuevo error (error);
      }
    }
    return Events.trigger(this.eventBusEl_, evento, hash);
  }
};

/**
 * Aplica {@link module:evented~EventedMixin|EventedMixin} a un objeto de destino.
 *
 * @param {Objeto} objetivo
 * El objeto al que agregar métodos de evento.
 *
 * @param {Objeto} [opciones={}]
 * Opciones para personalizar el comportamiento de mixin.
 *
 * @param {cadena} [opciones.eventBusKey]
 * De forma predeterminada, agrega un elemento DOM `eventBusEl_` al objeto de destino,
 * que se utiliza como bus de eventos. Si el objeto de destino ya tiene un
 * Elemento DOM que debe usarse, pase su clave aquí.
 *
 * @return {Objeto}
 * El objeto de destino.
 * /
función evented(objetivo, opciones = {}) {
  const {eventBusKey} = opciones;

  // Establecer o crear el eventoBusEl_.
  si (clave de bus de eventos) {
    if (!target[eventoBusClave].nodoNombre) {
      throw new Error(`La eventBusKey &quot;${eventBusKey}&quot; no hace referencia a un elemento.`);
    }
    target.eventBusEl_ = target[eventBusKey];
  } else {
    target.eventBusEl_ = Dom.createEl('span', {className: 'vjs-event-bus'});
  }

  Obj.assign(objetivo, EventedMixin);

  if (objetivo.eventedCallbacks) {
    target.eventedCallbacks.forEach((devolución de llamada) => {
      llamar de vuelta();
    });
  }

  // Cuando se elimina cualquier objeto con evento, elimina todos sus oyentes.
  target.on('dispose', () => {
    objetivo.off();
    [objetivo, objetivo.el_, objetivo.eventBusEl_].forEach(function(val) {
      si (valor && DomData.has(val)) {
        DomData.delete(val);
      }
    });
    ventana.setTimeout(() => {
      objetivo.eventBusEl_ = nulo;
    }, 0);
  });

  objetivo de retorno;
}

evento predeterminado de exportación;
exportar {isEvented};
exportar {addEventedCallback};