"use strict";

exports.__esModule = true;
require("core-js/modules/es.error.cause.js");
var _element = require("../../../helpers/dom/element");
var _function = require("../../../helpers/function");
var _feature = require("../../../helpers/feature");
var _browser = require("../../../helpers/browser");
var _mixed = require("../../../helpers/mixed");
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
/**
 * @class Event
 */
var _wtSettings = /*#__PURE__*/new WeakMap();
var _domBindings = /*#__PURE__*/new WeakMap();
var _wtTable = /*#__PURE__*/new WeakMap();
var _selectionManager = /*#__PURE__*/new WeakMap();
var _parent = /*#__PURE__*/new WeakMap();
var _eventManager = /*#__PURE__*/new WeakMap();
var _facadeGetter = /*#__PURE__*/new WeakMap();
var _selectedCellBeforeTouchEnd = /*#__PURE__*/new WeakMap();
var _dblClickTimeout = /*#__PURE__*/new WeakMap();
var _dblClickOrigin = /*#__PURE__*/new WeakMap();
class Event {
  /**
   * @param {FacadeGetter} facadeGetter Gets an instance facade.
   * @param {DomBindings} domBindings Bindings into dom.
   * @param {Settings} wtSettings The walkontable settings.
   * @param {EventManager} eventManager The walkontable event manager.
   * @param {Table} wtTable The table.
   * @param {SelectionManager} selectionManager Selections.
   * @param {Event} [parent=null] The main Event instance.
   */
  constructor(facadeGetter, domBindings, wtSettings, eventManager, wtTable, selectionManager) {
    let parent = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
    _classPrivateFieldInitSpec(this, _wtSettings, {
      writable: true,
      value: void 0
    });
    _classPrivateFieldInitSpec(this, _domBindings, {
      writable: true,
      value: void 0
    });
    _classPrivateFieldInitSpec(this, _wtTable, {
      writable: true,
      value: void 0
    });
    _classPrivateFieldInitSpec(this, _selectionManager, {
      writable: true,
      value: void 0
    });
    _classPrivateFieldInitSpec(this, _parent, {
      writable: true,
      value: void 0
    });
    /**
     * Instance of {@link EventManager}.
     *
     * @type {EventManager}
     */
    _classPrivateFieldInitSpec(this, _eventManager, {
      writable: true,
      value: void 0
    });
    /**
     * Should be use only for passing face called external origin methods, like registered event listeners.
     * It provides backward compatibility by getting instance facade.
     *
     * @todo Consider about removing this from Event class, because it make relationship into facade (implicit circular
     *   dependency).
     * @todo Con. Maybe passing listener caller as an ioc from faced resolves this issue. To rethink later.
     *
     * @type {FacadeGetter}
     */
    _classPrivateFieldInitSpec(this, _facadeGetter, {
      writable: true,
      value: void 0
    });
    /**
     * @type {boolean}
     */
    _classPrivateFieldInitSpec(this, _selectedCellBeforeTouchEnd, {
      writable: true,
      value: void 0
    });
    /**
     * @type {number[]}
     */
    _classPrivateFieldInitSpec(this, _dblClickTimeout, {
      writable: true,
      value: [null, null]
    });
    /**
     * @type {number[]}
     */
    _classPrivateFieldInitSpec(this, _dblClickOrigin, {
      writable: true,
      value: [null, null]
    });
    _classPrivateFieldSet(this, _wtSettings, wtSettings);
    _classPrivateFieldSet(this, _domBindings, domBindings);
    _classPrivateFieldSet(this, _wtTable, wtTable);
    _classPrivateFieldSet(this, _selectionManager, selectionManager);
    _classPrivateFieldSet(this, _parent, parent);
    _classPrivateFieldSet(this, _eventManager, eventManager);
    _classPrivateFieldSet(this, _facadeGetter, facadeGetter);
    this.registerEvents();
  }

  /**
   * Adds listeners for mouse and touch events.
   *
   * @private
   */
  registerEvents() {
    _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).holder, 'contextmenu', event => this.onContextMenu(event));
    _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).TABLE, 'mouseover', event => this.onMouseOver(event));
    _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).TABLE, 'mouseout', event => this.onMouseOut(event));
    const initTouchEvents = () => {
      _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).holder, 'touchstart', event => this.onTouchStart(event));
      _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).holder, 'touchend', event => this.onTouchEnd(event));
      if (!this.momentumScrolling) {
        this.momentumScrolling = {};
      }
      _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).holder, 'scroll', () => {
        clearTimeout(this.momentumScrolling._timeout);
        if (!this.momentumScrolling.ongoing) {
          _classPrivateFieldGet(this, _wtSettings).getSetting('onBeforeTouchScroll');
        }
        this.momentumScrolling.ongoing = true;
        this.momentumScrolling._timeout = setTimeout(() => {
          if (!this.touchApplied) {
            this.momentumScrolling.ongoing = false;
            _classPrivateFieldGet(this, _wtSettings).getSetting('onAfterMomentumScroll');
          }
        }, 200);
      });
    };
    const initMouseEvents = () => {
      _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).holder, 'mouseup', event => this.onMouseUp(event));
      _classPrivateFieldGet(this, _eventManager).addEventListener(_classPrivateFieldGet(this, _wtTable).holder, 'mousedown', event => this.onMouseDown(event));
    };
    if ((0, _browser.isMobileBrowser)()) {
      initTouchEvents();
    } else {
      // PC like devices which support both methods (touchscreen and ability to plug-in mouse).
      if ((0, _feature.isTouchSupported)()) {
        initTouchEvents();
      }
      initMouseEvents();
    }
  }

  /**
   * Checks if an element is already selected.
   *
   * @private
   * @param {Element} touchTarget An element to check.
   * @returns {boolean}
   */
  selectedCellWasTouched(touchTarget) {
    const cellUnderFinger = this.parentCell(touchTarget);
    const coordsOfCellUnderFinger = cellUnderFinger.coords;
    if (_classPrivateFieldGet(this, _selectedCellBeforeTouchEnd) && coordsOfCellUnderFinger) {
      const [rowTouched, rowSelected] = [coordsOfCellUnderFinger.row, _classPrivateFieldGet(this, _selectedCellBeforeTouchEnd).from.row];
      const [colTouched, colSelected] = [coordsOfCellUnderFinger.col, _classPrivateFieldGet(this, _selectedCellBeforeTouchEnd).from.col];
      return rowTouched === rowSelected && colTouched === colSelected;
    }
    return false;
  }

  /**
   * Gets closest TD or TH element.
   *
   * @private
   * @param {Element} elem An element from the traversing starts.
   * @returns {object} Contains coordinates and reference to TD or TH if it exists. Otherwise it's empty object.
   */
  parentCell(elem) {
    const cell = {};
    const TABLE = _classPrivateFieldGet(this, _wtTable).TABLE;
    const TD = (0, _element.closestDown)(elem, ['TD', 'TH'], TABLE);
    if (TD) {
      cell.coords = _classPrivateFieldGet(this, _wtTable).getCoords(TD);
      cell.TD = TD;
    } else if ((0, _element.hasClass)(elem, 'wtBorder') && (0, _element.hasClass)(elem, 'current')) {
      cell.coords = _classPrivateFieldGet(this, _selectionManager).getFocusSelection().cellRange.highlight;
      cell.TD = _classPrivateFieldGet(this, _wtTable).getCell(cell.coords);
    } else if ((0, _element.hasClass)(elem, 'wtBorder') && (0, _element.hasClass)(elem, 'area')) {
      if (_classPrivateFieldGet(this, _selectionManager).getAreaSelection().cellRange) {
        cell.coords = _classPrivateFieldGet(this, _selectionManager).getAreaSelection().cellRange.to;
        cell.TD = _classPrivateFieldGet(this, _wtTable).getCell(cell.coords);
      }
    }
    return cell;
  }

  /**
   * OnMouseDown callback.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onMouseDown(event) {
    const activeElement = _classPrivateFieldGet(this, _domBindings).rootDocument.activeElement;
    const getParentNode = (0, _function.partial)(_element.getParent, event.target);
    const realTarget = event.target;

    // ignore non-TD focusable elements from mouse down processing
    // (https://github.com/handsontable/handsontable/issues/3555)
    if (!['TD', 'TH'].includes(activeElement.nodeName) && (realTarget === activeElement || getParentNode(0) === activeElement || getParentNode(1) === activeElement)) {
      return;
    }
    const cell = this.parentCell(realTarget);
    if ((0, _element.hasClass)(realTarget, 'corner')) {
      _classPrivateFieldGet(this, _wtSettings).getSetting('onCellCornerMouseDown', event, realTarget);
    } else if (cell.TD && _classPrivateFieldGet(this, _wtSettings).has('onCellMouseDown')) {
      this.callListener('onCellMouseDown', event, cell.coords, cell.TD);
    }

    // doubleclick reacts only for left mouse button or from touch events
    if ((event.button === 0 || this.touchApplied) && cell.TD) {
      _classPrivateFieldGet(this, _dblClickOrigin)[0] = cell.TD;
      clearTimeout(_classPrivateFieldGet(this, _dblClickTimeout)[0]);
      _classPrivateFieldGet(this, _dblClickTimeout)[0] = setTimeout(() => {
        _classPrivateFieldGet(this, _dblClickOrigin)[0] = null;
      }, 1000);
    }
  }

  /**
   * OnContextMenu callback.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onContextMenu(event) {
    if (_classPrivateFieldGet(this, _wtSettings).has('onCellContextMenu')) {
      const cell = this.parentCell(event.target);
      if (cell.TD) {
        this.callListener('onCellContextMenu', event, cell.coords, cell.TD);
      }
    }
  }

  /**
   * OnMouseOver callback.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onMouseOver(event) {
    if (!_classPrivateFieldGet(this, _wtSettings).has('onCellMouseOver')) {
      return;
    }
    const table = _classPrivateFieldGet(this, _wtTable).TABLE;
    const td = (0, _element.closestDown)(event.target, ['TD', 'TH'], table);
    const parent = _classPrivateFieldGet(this, _parent) || this;
    if (td && td !== parent.lastMouseOver && (0, _element.isChildOf)(td, table)) {
      parent.lastMouseOver = td;
      this.callListener('onCellMouseOver', event, _classPrivateFieldGet(this, _wtTable).getCoords(td), td);
    }
  }

  /**
   * OnMouseOut callback.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onMouseOut(event) {
    if (!_classPrivateFieldGet(this, _wtSettings).has('onCellMouseOut')) {
      return;
    }
    const table = _classPrivateFieldGet(this, _wtTable).TABLE;
    const lastTD = (0, _element.closestDown)(event.target, ['TD', 'TH'], table);
    const nextTD = (0, _element.closestDown)(event.relatedTarget, ['TD', 'TH'], table);
    const parent = _classPrivateFieldGet(this, _parent) || this;
    if (lastTD && lastTD !== nextTD && (0, _element.isChildOf)(lastTD, table)) {
      this.callListener('onCellMouseOut', event, _classPrivateFieldGet(this, _wtTable).getCoords(lastTD), lastTD);
      if (nextTD === null) {
        parent.lastMouseOver = null;
      }
    }
  }

  /**
   * OnMouseUp callback.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onMouseUp(event) {
    const cell = this.parentCell(event.target);
    if (cell.TD && _classPrivateFieldGet(this, _wtSettings).has('onCellMouseUp')) {
      this.callListener('onCellMouseUp', event, cell.coords, cell.TD);
    }

    // if not left mouse button, and the origin event is not comes from touch
    if (event.button !== 0 && !this.touchApplied) {
      return;
    }
    if (cell.TD === _classPrivateFieldGet(this, _dblClickOrigin)[0] && cell.TD === _classPrivateFieldGet(this, _dblClickOrigin)[1]) {
      if ((0, _element.hasClass)(event.target, 'corner')) {
        this.callListener('onCellCornerDblClick', event, cell.coords, cell.TD);
      } else {
        this.callListener('onCellDblClick', event, cell.coords, cell.TD);
      }
      _classPrivateFieldGet(this, _dblClickOrigin)[0] = null;
      _classPrivateFieldGet(this, _dblClickOrigin)[1] = null;
    } else if (cell.TD === _classPrivateFieldGet(this, _dblClickOrigin)[0]) {
      _classPrivateFieldGet(this, _dblClickOrigin)[1] = cell.TD;
      clearTimeout(_classPrivateFieldGet(this, _dblClickTimeout)[1]);
      _classPrivateFieldGet(this, _dblClickTimeout)[1] = setTimeout(() => {
        _classPrivateFieldGet(this, _dblClickOrigin)[1] = null;
      }, 500);
    }
  }

  /**
   * OnTouchStart callback. Simulates mousedown event.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onTouchStart(event) {
    _classPrivateFieldSet(this, _selectedCellBeforeTouchEnd, _classPrivateFieldGet(this, _selectionManager).getFocusSelection().cellRange);
    this.touchApplied = true;
    this.onMouseDown(event);
  }

  /**
   * OnTouchEnd callback. Simulates mouseup event.
   *
   * @private
   * @param {MouseEvent} event The mouse event object.
   */
  onTouchEnd(event) {
    var _this$parentCell;
    const target = event.target;
    const parentCellCoords = (_this$parentCell = this.parentCell(target)) === null || _this$parentCell === void 0 ? void 0 : _this$parentCell.coords;
    const isCellsRange = (0, _mixed.isDefined)(parentCellCoords) && parentCellCoords.row >= 0 && parentCellCoords.col >= 0;
    const isEventCancelable = event.cancelable && isCellsRange && _classPrivateFieldGet(this, _wtSettings).getSetting('isDataViewInstance');

    // To prevent accidental redirects or other actions that the interactive elements (e.q "A" link) do
    // while the cell is highlighted, all touch events that are triggered on different cells are
    // "preventDefault"'ed. The user can interact with the element (e.q. click on the link that opens
    // a new page) only when the same cell was previously selected (see related PR #7980).
    if (isEventCancelable) {
      const interactiveElements = ['A', 'BUTTON', 'INPUT'];

      // For browsers that use the WebKit as an engine (excluding Safari), there is a bug. The prevent
      // default has to be called all the time. Otherwise, the second tap won't be triggered (probably
      // caused by the native ~300ms delay - https://webkit.org/blog/5610/more-responsive-tapping-on-ios/).
      // To make the interactive elements work, the event target element has to be check. If the element
      // matches the allow-list, the event is not prevented.
      if ((0, _browser.isIOS)() && ((0, _browser.isChromeWebKit)() || (0, _browser.isFirefoxWebKit)()) && this.selectedCellWasTouched(target) && !interactiveElements.includes(target.tagName)) {
        event.preventDefault();
      } else if (!this.selectedCellWasTouched(target)) {
        // For other browsers, prevent default is fired only for the first tap and only when the previous
        // highlighted cell was different.
        event.preventDefault();
      }
    }
    this.onMouseUp(event);
    this.touchApplied = false;
  }

  /**
   * Call listener with backward compatibility.
   *
   * @private
   * @param {string} name Name of listener.
   * @param {MouseEvent} event The event object.
   * @param {CellCoords} coords Coordinates.
   * @param {HTMLElement} target Event target.
   */
  callListener(name, event, coords, target) {
    const listener = _classPrivateFieldGet(this, _wtSettings).getSettingPure(name);
    if (listener) {
      listener(event, coords, target, _classPrivateFieldGet(this, _facadeGetter).call(this));
    }
  }

  /**
   * Clears double-click timeouts and destroys the internal eventManager instance.
   */
  destroy() {
    clearTimeout(_classPrivateFieldGet(this, _dblClickTimeout)[0]);
    clearTimeout(_classPrivateFieldGet(this, _dblClickTimeout)[1]);
    _classPrivateFieldGet(this, _eventManager).destroy();
  }
}
var _default = Event;
exports.default = _default;