"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.createEvent = createEvent;
exports.fireEvent = fireEvent;

var _config = require("./config");

var _helpers = require("./helpers");

var _eventMap = require("./event-map");

function fireEvent(element, event) {
  return (0, _config.getConfig)().eventWrapper(() => {
    if (!event) {
      throw new Error(`Unable to fire an event - please provide an event object.`);
    }

    if (!element) {
      throw new Error(`Unable to fire a "${event.type}" event - please provide a DOM element.`);
    }

    return element.dispatchEvent(event);
  });
}

function createEvent(eventName, node, init, {
  EventType = 'Event',
  defaultInit = {}
} = {}) {
  if (!node) {
    throw new Error(`Unable to fire a "${eventName}" event - please provide a DOM element.`);
  }

  const eventInit = { ...defaultInit,
    ...init
  };
  const {
    target: {
      value,
      files,
      ...targetProperties
    } = {}
  } = eventInit;

  if (value !== undefined) {
    setNativeValue(node, value);
  }

  if (files !== undefined) {
    // input.files is a read-only property so this is not allowed:
    // input.files = [file]
    // so we have to use this workaround to set the property
    Object.defineProperty(node, 'files', {
      configurable: true,
      enumerable: true,
      writable: true,
      value: files
    });
  }

  Object.assign(node, targetProperties);
  const window = (0, _helpers.getWindowFromNode)(node);
  const EventConstructor = window[EventType] || window.Event;
  let event;
  /* istanbul ignore else  */

  if (typeof EventConstructor === 'function') {
    event = new EventConstructor(eventName, eventInit);
  } else {
    // IE11 polyfill from https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
    event = window.document.createEvent(EventType);
    const {
      bubbles,
      cancelable,
      detail,
      ...otherInit
    } = eventInit;
    event.initEvent(eventName, bubbles, cancelable, detail);
    Object.keys(otherInit).forEach(eventKey => {
      event[eventKey] = otherInit[eventKey];
    });
  } // DataTransfer is not supported in jsdom: https://github.com/jsdom/jsdom/issues/1568


  const dataTransferProperties = ['dataTransfer', 'clipboardData'];
  dataTransferProperties.forEach(dataTransferKey => {
    const dataTransferValue = eventInit[dataTransferKey];

    if (typeof dataTransferValue === 'object') {
      /* istanbul ignore if  */
      if (typeof window.DataTransfer === 'function') {
        Object.defineProperty(event, dataTransferKey, {
          value: Object.getOwnPropertyNames(dataTransferValue).reduce((acc, propName) => {
            Object.defineProperty(acc, propName, {
              value: dataTransferValue[propName]
            });
            return acc;
          }, new window.DataTransfer())
        });
      } else {
        Object.defineProperty(event, dataTransferKey, {
          value: dataTransferValue
        });
      }
    }
  });
  return event;
}

Object.keys(_eventMap.eventMap).forEach(key => {
  const {
    EventType,
    defaultInit
  } = _eventMap.eventMap[key];
  const eventName = key.toLowerCase();

  createEvent[key] = (node, init) => createEvent(eventName, node, init, {
    EventType,
    defaultInit
  });

  fireEvent[key] = (node, init) => fireEvent(node, createEvent[key](node, init));
}); // function written after some investigation here:
// https://github.com/facebook/react/issues/10135#issuecomment-401496776

function setNativeValue(element, value) {
  const {
    set: valueSetter
  } = Object.getOwnPropertyDescriptor(element, 'value') || {};
  const prototype = Object.getPrototypeOf(element);
  const {
    set: prototypeValueSetter
  } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};

  if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
    prototypeValueSetter.call(element, value);
  } else {
    /* istanbul ignore if */
    // eslint-disable-next-line no-lonely-if -- Can't be ignored by istanbul otherwise
    if (valueSetter) {
      valueSetter.call(element, value);
    } else {
      throw new Error('The given element does not have a value setter');
    }
  }
}

Object.keys(_eventMap.eventAliasMap).forEach(aliasKey => {
  const key = _eventMap.eventAliasMap[aliasKey];

  fireEvent[aliasKey] = (...args) => fireEvent[key](...args);
});
/* eslint complexity:["error", 9] */