import { typeCheckConfig } from '../mdb/util/index';
import EventHandler from '../mdb/dom/event-handler';
import Manipulator from '../mdb/dom/manipulator';
import BSDropdown from '../bootstrap/mdb-prefix/dropdown';
import { bindCallbackEventsIfNeeded } from '../autoinit/init';

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'dropdown';
const DATA_KEY = `mdb.${NAME}`;
const EVENT_KEY = `.${DATA_KEY}`;

const Default = {
  offset: [0, 2],
  boundary: 'clippingParents',
  reference: 'toggle',
  display: 'dynamic',
  popperConfig: null,
  dropdownAnimation: 'on',
};

const DefaultType = {
  offset: '(array|string|function)',
  boundary: '(string|element)',
  reference: '(string|element|object)',
  display: 'string',
  popperConfig: '(null|object|function)',
  dropdownAnimation: 'string',
};

const EVENT_HIDE = 'hide.bs.dropdown';
const EVENT_HIDDEN = 'hidden.bs.dropdown';
const EVENT_SHOW = 'show.bs.dropdown';
const EVENT_SHOWN = 'shown.bs.dropdown';

const EVENT_HIDE_MDB = `hide${EVENT_KEY}`;
const EVENT_HIDDEN_MDB = `hidden${EVENT_KEY}`;
const EVENT_SHOW_MDB = `show${EVENT_KEY}`;
const EVENT_SHOWN_MDB = `shown${EVENT_KEY}`;

const ANIMATION_CLASS = 'animation';
const ANIMATION_SHOW_CLASS = 'fade-in';
const ANIMATION_HIDE_CLASS = 'fade-out';

class Dropdown extends BSDropdown {
  constructor(element, data) {
    super(element, data);
    this._config = this._getConfig(data);
    this._menuStyle = '';
    this._popperPlacement = '';
    this._mdbPopperConfig = '';

    //* prevents dropdown close issue when system animation is turned off
    const isPrefersReducedMotionSet = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    if (this._config.dropdownAnimation === 'on' && !isPrefersReducedMotionSet) {
      this._init();
    }
    Manipulator.setDataAttribute(this._element, `${this.constructor.NAME}-initialized`, true);
    bindCallbackEventsIfNeeded(this.constructor);
  }

  dispose() {
    EventHandler.off(this._element, EVENT_SHOW);
    EventHandler.off(this._parent, EVENT_SHOWN);
    EventHandler.off(this._parent, EVENT_HIDE);
    EventHandler.off(this._parent, EVENT_HIDDEN);

    Manipulator.removeDataAttribute(this._element, `${this.constructor.NAME}-initialized`);

    super.dispose();
  }

  // Getters
  static get NAME() {
    return NAME;
  }

  // Private
  _init() {
    this._bindShowEvent();
    this._bindShownEvent();
    this._bindHideEvent();
    this._bindHiddenEvent();
  }

  _getConfig(options) {
    const config = {
      ...Default,
      ...Manipulator.getDataAttributes(this._element),
      ...options,
    };
    typeCheckConfig(NAME, config, DefaultType);
    return config;
  }

  _getOffset() {
    const { offset } = this._config;

    if (typeof offset === 'string') {
      return offset.split(',').map((val) => Number.parseInt(val, 10));
    }

    if (typeof offset === 'function') {
      return (popperData) => offset(popperData, this._element);
    }

    return offset;
  }

  _getPopperConfig() {
    const popperConfig = {
      placement: this._getPlacement(),
      modifiers: [
        {
          name: 'preventOverflow',
          options: {
            boundary: this._config.boundary,
          },
        },
        {
          name: 'offset',
          options: {
            offset: this._getOffset(),
          },
        },
      ],
    };

    // Disable Popper if we have a static display
    if (this._config.display === 'static') {
      Manipulator.setDataAttribute(this._menu, 'popper', 'static');
      popperConfig.modifiers = [
        {
          name: 'applyStyles',
          enabled: false,
        },
      ];
    }

    return {
      ...popperConfig,
      /* eslint no-extra-parens: "off" */
      ...(typeof this._config.popperConfig === 'function'
        ? this._config.popperConfig(popperConfig)
        : this._config.popperConfig),
    };
  }

  _bindShowEvent() {
    EventHandler.on(this._element, EVENT_SHOW, (e) => {
      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW_MDB, {
        relatedTarget: e.relatedTarget,
      });

      if (showEvent.defaultPrevented) {
        e.preventDefault();
        return;
      }

      this._dropdownAnimationStart('show');
    });
  }

  _bindShownEvent() {
    EventHandler.on(this._parent, EVENT_SHOWN, (e) => {
      const shownEvent = EventHandler.trigger(this._parent, EVENT_SHOWN_MDB, {
        relatedTarget: e.relatedTarget,
      });

      if (shownEvent.defaultPrevented) {
        e.preventDefault();
        return;
      }
    });
  }

  _bindHideEvent() {
    EventHandler.on(this._parent, EVENT_HIDE, (e) => {
      const hideEvent = EventHandler.trigger(this._parent, EVENT_HIDE_MDB, {
        relatedTarget: e.relatedTarget,
      });

      if (hideEvent.defaultPrevented) {
        e.preventDefault();
        return;
      }

      this._menuStyle = this._menu.style.cssText;
      this._popperPlacement = this._menu.getAttribute('data-popper-placement');
      this._mdbPopperConfig = this._menu.getAttribute('data-mdb-popper');
    });
  }

  _bindHiddenEvent() {
    EventHandler.on(this._parent, EVENT_HIDDEN, (e) => {
      const hiddenEvent = EventHandler.trigger(this._parent, EVENT_HIDDEN_MDB, {
        relatedTarget: e.relatedTarget,
      });

      if (hiddenEvent.defaultPrevented) {
        e.preventDefault();
        return;
      }

      if (this._config.display !== 'static' && this._menuStyle !== '') {
        this._menu.style.cssText = this._menuStyle;
      }

      this._menu.setAttribute('data-popper-placement', this._popperPlacement);
      this._menu.setAttribute('data-mdb-popper', this._mdbPopperConfig);

      this._dropdownAnimationStart('hide');
    });
  }

  _dropdownAnimationStart(action) {
    switch (action) {
      case 'show':
        this._menu.classList.add(ANIMATION_CLASS, ANIMATION_SHOW_CLASS);
        this._menu.classList.remove(ANIMATION_HIDE_CLASS);
        break;
      default:
        // hide
        this._menu.classList.add(ANIMATION_CLASS, ANIMATION_HIDE_CLASS);
        this._menu.classList.remove(ANIMATION_SHOW_CLASS);
        break;
    }

    this._bindAnimationEnd();
  }

  _bindAnimationEnd() {
    EventHandler.one(this._menu, 'animationend', () => {
      this._menu.classList.remove(ANIMATION_CLASS, ANIMATION_HIDE_CLASS, ANIMATION_SHOW_CLASS);
    });
  }
}

export default Dropdown;
