"use strict";

exports.__esModule = true;
exports.getListenersCounter = getListenersCounter;
require("core-js/modules/es.array.push.js");
var _event = require("./helpers/dom/event");
/**
 * Counter which tracks unregistered listeners (useful for detecting memory leaks).
 *
 * @type {number}
 */
let listenersCounter = 0;

/**
 * Event DOM manager for internal use in Handsontable.
 *
 * @class EventManager
 * @util
 */
class EventManager {
  /**
   * @param {object} [context=null] An object to which event listeners will be stored.
   * @private
   */
  constructor() {
    let context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
    this.context = context || this;

    // TODO it modify external object. Rethink that.
    if (!this.context.eventListeners) {
      this.context.eventListeners = []; // TODO perf It would be more performant if every instance of EventManager tracked its own listeners only
    }
  }

  /**
   * Register specified listener (`eventName`) to the element.
   *
   * @param {Element} element Target element.
   * @param {string} eventName Event name.
   * @param {Function} callback Function which will be called after event occur.
   * @param {AddEventListenerOptions|boolean} [options] Listener options if object or useCapture if boolean.
   * @returns {Function} Returns function which you can easily call to remove that event.
   */
  addEventListener(element, eventName, callback) {
    let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
    /**
     * @param {Event} event The event object.
     */
    function callbackProxy(event) {
      callback.call(this, extendEvent(event));
    }
    this.context.eventListeners.push({
      element,
      event: eventName,
      callback,
      callbackProxy,
      options,
      eventManager: this
    });
    element.addEventListener(eventName, callbackProxy, options);
    listenersCounter += 1;
    return () => {
      this.removeEventListener(element, eventName, callback);
    };
  }

  /**
   * Remove the event listener previously registered.
   *
   * @param {Element} element Target element.
   * @param {string} eventName Event name.
   * @param {Function} callback Function to remove from the event target. It must be the same as during registration listener.
   * @param {boolean} [onlyOwnEvents] Whether whould remove only events registered using this instance of EventManager.
   */
  removeEventListener(element, eventName, callback) {
    let onlyOwnEvents = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
    let len = this.context.eventListeners.length;
    let tmpEvent;
    while (len) {
      len -= 1;
      tmpEvent = this.context.eventListeners[len];
      if (tmpEvent.event === eventName && tmpEvent.element === element) {
        if (callback && callback !== tmpEvent.callback) {
          /* eslint-disable no-continue */
          continue;
        }
        // TODO rethink that, main bulk is that it needs multi instances to handle same context, but with a different scopes.
        // TODO I suppose much more efficient way will be comparing string with scope id, or any similar approach.
        if (onlyOwnEvents && tmpEvent.eventManager !== this) {
          continue;
        }
        this.context.eventListeners.splice(len, 1);
        tmpEvent.element.removeEventListener(tmpEvent.event, tmpEvent.callbackProxy, tmpEvent.options);
        listenersCounter -= 1;
      }
    }
  }

  /**
   * Clear all previously registered events.
   *
   * @private
   * @since 0.15.0-beta3
   * @param {boolean} [onlyOwnEvents] Whether whould remove only events registered using this instance of EventManager.
   */
  clearEvents() {
    let onlyOwnEvents = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    if (!this.context) {
      return;
    }
    let len = this.context.eventListeners.length;
    while (len) {
      len -= 1;
      const event = this.context.eventListeners[len];
      if (onlyOwnEvents && event.eventManager !== this) {
        continue;
      }
      this.context.eventListeners.splice(len, 1);
      event.element.removeEventListener(event.event, event.callbackProxy, event.options);
      listenersCounter -= 1;
    }
  }

  /**
   * Clear all previously registered events.
   */
  clear() {
    this.clearEvents();
  }

  /**
   * Destroy instance of EventManager, clearing all events of the context.
   */
  destroy() {
    this.clearEvents();
    this.context = null;
  }

  /**
   * Destroy instance of EventManager, clearing only the own events.
   */
  destroyWithOwnEventsOnly() {
    this.clearEvents(true);
    this.context = null;
  }

  /**
   * Trigger event at the specified target element.
   *
   * @param {Element} element Target element.
   * @param {string} eventName Event name.
   */
  fireEvent(element, eventName) {
    let rootDocument = element.document;
    let rootWindow = element;
    if (!rootDocument) {
      rootDocument = element.ownerDocument ? element.ownerDocument : element;
      rootWindow = rootDocument.defaultView;
    }
    const options = {
      bubbles: true,
      cancelable: eventName !== 'mousemove',
      view: rootWindow,
      detail: 0,
      screenX: 0,
      screenY: 0,
      clientX: 1,
      clientY: 1,
      ctrlKey: false,
      altKey: false,
      shiftKey: false,
      metaKey: false,
      button: 0,
      relatedTarget: undefined
    };
    let event;
    if (rootDocument.createEvent) {
      event = rootDocument.createEvent('MouseEvents');
      event.initMouseEvent(eventName, options.bubbles, options.cancelable, options.view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget || rootDocument.body.parentNode);
    } else {
      event = rootDocument.createEventObject();
    }
    if (element.dispatchEvent) {
      element.dispatchEvent(event);
    } else {
      element.fireEvent(`on${eventName}`, event);
    }
  }
}

/**
 * @private
 * @param {Event} event The event object.
 * @returns {Event}
 */
function extendEvent(event) {
  const nativeStopImmediatePropagation = event.stopImmediatePropagation;
  event.stopImmediatePropagation = function () {
    nativeStopImmediatePropagation.apply(this);
    (0, _event.stopImmediatePropagation)(this);
  };
  return event;
}
var _default = exports.default = EventManager;
/**
 * @returns {number}
 */
function getListenersCounter() {
  return listenersCounter;
}