import { ElementAppender } from '../../helpers/ui/element-appender';
import { findParent, isParent } from '../../utils/ui';
/**
 * It creates a proxy for the `ElementAppender` class and attaches it to the component class as a
 * property
 *
 * @param {ShowConfigFactoryType} showConfigFactory - A function that returns a ShowConfig object.
 * @param {GetTriggerType} [getTrigger] - The name of the property that will trigger the dropdown to
 * show/hide.
 * @param {string} [isDropdownShowName] - The name of the property that will be used to determine if
 * the dropdown is shown or not.
 * @returns {(target: ComponentInterface, key: string) => void} A function that takes a component class and a string.
 */
export function InjectElementAppender(showConfigFactory, getTrigger, isDropdownShowName) {
  return (componentClass, elementAppenderName) => {
    const { componentDidLoad, disconnectedCallback, connectedCallback } = componentClass;
    /* eslint-disable jsdoc/require-jsdoc */
    const setElementAppender = (host) => {
      const elementAppender = new ElementAppender(showConfigFactory, host);
      host[elementAppenderName] = new Proxy(elementAppender, {
        get(target, prop) {
          const value = target[prop];
          if (value instanceof Function) {
            return function (...args) {
              const result = value.apply(target, args);
              if (prop === 'show') {
                host[isDropdownShowName] = true;
              }
              else if (prop === 'hide') {
                host[isDropdownShowName] = false;
              }
              else if (prop === 'destroy') {
                host[elementAppenderName] = null;
                host[isDropdownShowName] = false;
              }
              return result;
            };
          }
          return value;
        },
      });
    };
    const registerElementAppenderListener = (host) => {
      host.elementAppenderListener = new ElementAppenderListener(host, elementAppenderName, getTrigger, isDropdownShowName, showConfigFactory(host));
      host.elementAppenderListener.register();
    };
    const unRegisterElementAppenderListener = (host) => {
      host.elementAppenderListener.unRegister();
    };
    componentClass.componentDidLoad = function () {
      setElementAppender(this);
      registerElementAppenderListener(this);
      return componentDidLoad === null || componentDidLoad === void 0 ? void 0 : componentDidLoad.call(this);
    };
    componentClass.connectedCallback = function () {
      if (this[elementAppenderName] === null) {
        setElementAppender(this);
        registerElementAppenderListener(this);
      }
      return connectedCallback === null || connectedCallback === void 0 ? void 0 : connectedCallback.call(this);
    };
    componentClass.disconnectedCallback = function () {
      unRegisterElementAppenderListener(this);
      this.elementAppenderListener = null;
      this[elementAppenderName].destroy();
      this[elementAppenderName] = null;
      return disconnectedCallback === null || disconnectedCallback === void 0 ? void 0 : disconnectedCallback.call(this);
    };
  };
}
/* It listens to events on the target and the appendable
element, and then it shows or hides the dropdown based on the trigger property */
class ElementAppenderListener {
  /**
   * It creates a new instance of the `ElementAppender` class, and then returns it
   *
   * @param {ComponentInterface} component - ComponentInterface - the component that will be used to
   * append the element
   * @param {string} elementAppenderName - The name of the property that will be used to access the
   * ElementAppender instance.
   * @param {GetTriggerType} getTrigger - The name of the property that will trigger the dropdown to
   * show.
   * @param {string} isDropdownShowName - The name of the property that will be used to determine if the
   * dropdown is shown or not.
   * @param {ElementAppenderShowConfig} showConfigFactory - ElementAppenderShowConfig - this is the
   * factory that creates the show config.
   */
  constructor(component, elementAppenderName, getTrigger, isDropdownShowName, showConfigFactory) {
    /* It's a function that listens to the click event on the dropdown. It checks if the trigger
    property is equal to 'click'. If it is, then it toggles the dropdown. */
    this.targetClickHandler = () => {
      if (this.shouldListenerWork('click')) {
        this.component[this.isDropdownShowName]
          ? this.component[this.elementAppenderName].hide()
          : this.component[this.elementAppenderName].show();
      }
    };
    /* It's a function that listens to the mouseenter event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it clears the timer. */
    this.targetMouseEnterHandler = () => {
      if (this.shouldListenerWork('hover')) {
        this.component[this.isDropdownShowName] === false &&
          this.component[this.elementAppenderName].show();
        this.clearDelayHideTimer();
      }
    };
    /* It's a function that listens to the mouseleave event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it creates a timer that will hide the dropdown after a
    certain amount of time. */
    this.targetMouseLeaveHandler = () => {
      if (this.shouldListenerWork('hover')) {
        this.createDelayHideTimer();
      }
    };
    /* It's a function that listens to the mouseenter event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it clears the timer. */
    this.elementMouseEnterHandler = () => {
      if (this.shouldListenerWork('hover')) {
        this.clearDelayHideTimer();
      }
    };
    /* It's a function that listens to the mouseleave event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it creates a timer that will hide the dropdown after a
    certain amount of time. */
    this.elementMouseLeaveHandler = () => {
      if (this.shouldListenerWork('hover')) {
        this.createDelayHideTimer();
      }
    };
    /* It's a function that listens to the global mousedown event. It checks if the dropdown is showing,
    and if the target of the event is not a child of the dropdown or the trigger. If it is, then it
    hides the dropdown. */
    this.globalMouseDownHandler = (e) => {
      const currentTarget = e.composedPath()[0];
      if (this.shouldListenerWork('custom') &&
        this.component[this.isDropdownShowName] &&
        !isParent(currentTarget, this.appendableElement) &&
        !isParent(currentTarget, this.target) &&
        findParent(currentTarget, 'vega-modal') === findParent(this.target, 'vega-modal')) {
        this.component[this.elementAppenderName].hide();
      }
    };
    /* A timer that will hide the dropdown after a certain amount of time. */
    this.createDelayHideTimer = () => {
      this.delayHideTimer = setTimeout(() => {
        if (this.component[this.elementAppenderName]) {
          this.component[this.elementAppenderName].hide();
        }
        clearTimeout(this.delayHideTimer);
      }, ElementAppenderListener.DELAY_HIDE_TIME);
    };
    /* A function that clears the timer. */
    this.clearDelayHideTimer = () => {
      this.delayHideTimer && clearTimeout(this.delayHideTimer);
    };
    /* It's a function that checks if the listener should work or not. */
    this.shouldListenerWork = (trigger) => {
      const currentTrigger = this.getTrigger(this.component);
      switch (trigger) {
        case 'custom':
          return currentTrigger !== 'none';
        case 'click':
          return currentTrigger === 'click';
        case 'hover':
          return currentTrigger === 'hover';
      }
    };
    this.component = component;
    this.target = showConfigFactory.target;
    this.appendableElement = showConfigFactory.appendableElement;
    this.getTrigger = getTrigger;
    this.isDropdownShowName = isDropdownShowName;
    this.elementAppenderName = elementAppenderName;
  }
  /**
   * It adds event listeners to the target and the appendable element
   */
  register() {
    var _a, _b, _c, _d, _e, _f;
    this.unRegister();
    (_a = this.target) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.targetClickHandler);
    (_b = this.target) === null || _b === void 0 ? void 0 : _b.addEventListener('mouseenter', this.targetMouseEnterHandler);
    (_c = this.target) === null || _c === void 0 ? void 0 : _c.addEventListener('mouseleave', this.targetMouseLeaveHandler);
    (_d = this.target) === null || _d === void 0 ? void 0 : _d.addEventListener('focusout', this.targetMouseLeaveHandler);
    (_e = this.appendableElement) === null || _e === void 0 ? void 0 : _e.addEventListener('mouseenter', this.elementMouseEnterHandler);
    (_f = this.appendableElement) === null || _f === void 0 ? void 0 : _f.addEventListener('mouseleave', this.elementMouseLeaveHandler);
    window.addEventListener('mousedown', this.globalMouseDownHandler);
  }
  /**
   * It removes all event listeners that were added in the `register()` function
   */
  unRegister() {
    var _a, _b, _c, _d, _e, _f;
    (_a = this.target) === null || _a === void 0 ? void 0 : _a.removeEventListener('click', this.targetClickHandler);
    (_b = this.target) === null || _b === void 0 ? void 0 : _b.removeEventListener('mouseenter', this.targetMouseEnterHandler);
    (_c = this.target) === null || _c === void 0 ? void 0 : _c.removeEventListener('mouseleave', this.targetMouseLeaveHandler);
    (_d = this.target) === null || _d === void 0 ? void 0 : _d.removeEventListener('focusout', this.targetMouseLeaveHandler);
    (_e = this.appendableElement) === null || _e === void 0 ? void 0 : _e.removeEventListener('mouseenter', this.elementMouseEnterHandler);
    (_f = this.appendableElement) === null || _f === void 0 ? void 0 : _f.removeEventListener('mouseleave', this.elementMouseLeaveHandler);
    window.removeEventListener('mousedown', this.globalMouseDownHandler);
  }
}
ElementAppenderListener.DELAY_HIDE_TIME = 100;
