import $ from 'domtastic';
import Capitalize from './capitalize';

export default class ToggleComponent {
  constructor(element, globalOptions) {
    this.$component = $(element);
    this.globalOptions = globalOptions;
    this.instanceId = false;
    this.focusRecoverElement = null;
  }

  _onInit() {}

  _onDestroy() {}

  _onOpen() {
    this.$component.addClass(this.options.openClass);
  }

  _onClose() {
    this.$component.removeClass(this.options.openClass);
  }

  _isOpen() {
    return this.$component.hasClass(this.options.openClass);
  }

  _isClickOutside(target) {
    const isInside = $.contains(this.$component[0], target);
    const isParent = this.$component[0] === target;

    return !isInside && !isParent;
  }

  init(customOptions) {
    // Default options
    this.options = {
      openClass: 'as--open',
      dataAttrBase: 'data-sui-CHANGE_THIS_PART',
      dataAttrSuffixToggle: '',
      dataAttrSuffixOpen: '-open',
      dataAttrSuffixClose: '-close',
      dataAttrSuffixConfirm: '-confirm',
      dataAttrClicksEnabled: true,
      htmlToBodyEnabled: true,
      focusRecoverEnabled: false,
      keysEnabled: true,
      confirmOnEnterKey: false,
      closeOnEscapeKey: true,
      clickOutsideEnabled: false,
      onInit() {},
      onOpen() {},
      onConfirm() {},
      onClose() {},
      onEnterKey() {},
      onEscapeKey() {},
      onDestroy() {},
    };

    // Merge global options
    if (this.globalOptions) {
      this.options = $.extend({}, this.options, this.globalOptions);
    }

    // Merge custom options
    if (customOptions) {
      this.options = $.extend({}, this.options, customOptions);
    }

    // Destroy component if already init
    if (this.instanceId) {
      this.destroy();
    }

    /* eslint-disable-next-line prefer-destructuring */
    this.instanceId = (window.crypto || window.msCrypto).getRandomValues(new Uint32Array(1))[0];

    // Move html at the end of the body tag
    if (this.options.htmlToBodyEnabled) {
      document.body.appendChild(this.$component[0]);
    }

    // Enabled all clicks on elements with data- attributes
    if (this.options.dataAttrClicksEnabled) {
      this.bindDataClicks();
    }

    this.bindEvents();

    this._onInit();
    this.options.onInit(this.$component); // Callback

    return this;
  }

  toggle(clickedElement) {
    if (this._isOpen()) {
      this.close(clickedElement);
    } else {
      this.open(clickedElement);
    }

    return this;
  }

  open(clickedElement) {
    if (this._isOpen()) {
      return this;
    }

    this._onOpen(clickedElement);
    this.options.onOpen(this.$component, clickedElement); // Callback

    // Store current element with focus
    this.focusRecoverElement = document.activeElement;

    // Remove focus on activeElement
    if (document.activeElement) {
      document.activeElement.blur();
    }

    if (this.options.keysEnabled) {
      this.bindKeys();
    }

    if (this.options.clickOutsideEnabled) {
      this.bindClickOutside();
    }

    return this;
  }

  close(clickedElement) {
    if (!this._isOpen()) {
      return this;
    }

    this._onClose(clickedElement);
    this.options.onClose(this.$component, clickedElement); // Callback

    // Restore element with focus (before open)
    if (this.options.focusRecoverEnabled && this.focusRecoverElement) {
      this.focusRecoverElement.focus();
    }

    // Remove eventListeners
    this.unbindClickOutside();
    this.unbindKeys();

    return this;
  }

  confirm(clickedElement) {
    this.options.onConfirm(this.$component, clickedElement); // Callback
    this.close(clickedElement);

    return this;
  }

  destroy() {
    // Remove eventListeners
    this.unbindEvents();
    this.unbindDataClicks();
    this.unbindClickOutside();
    this.unbindKeys();

    this._onDestroy();
    this.options.onDestroy(this.$component); // Callback

    if (this.options.htmlToBodyEnabled) {
      this.$component.remove();
    }

    this.instanceId = false;

    return this;
  }

  bindDataClicks() {
    const instance = this;
    const componentId = this.$component.attr('id');

    // Clicks inside component
    ['close', 'confirm'].forEach((type) => {
      this.$component.on(`click.${type}`, `[${this.options.dataAttrBase}${this.options[`dataAttrSuffix${Capitalize(type)}`]}]`, function () {
        instance[type](this);
      });
    });

    if (!componentId) {
      return;
    }

    // Clicks outside component
    ['toggle', 'open', 'close', 'confirm'].forEach((type) => {
      $(document).on(`click.data-sui${this.instanceId}.${type}`, `[${this.options.dataAttrBase}${this.options[`dataAttrSuffix${Capitalize(type)}`]}="${componentId}"]`, function (e) {
        e.preventDefault();
        instance[type](this);
      });
    });
  }

  unbindDataClicks() {
    $(document).off(`click.data-sui${this.instanceId}`);
  }

  bindEvents() {
    ['toggle', 'open', 'close', 'confirm', 'destroy'].forEach((type) => {
      this.$component.on(type, () => {
        this[type]();
      });
    });
  }

  unbindEvents() {
    this.$component.off('click toggle open close confirm destroy');
  }

  bindKeys() {
    $(document).on(`keydown.sui${this.instanceId}`, (event) => {
      this._keysMethods(event);
    });
  }

  unbindKeys() {
    $(document).off(`keydown.sui${this.instanceId}`);
  }

  bindClickOutside() {
    const instance = this;

    setTimeout(() => {
      $(document).on(`click.outside-sui${this.instanceId}`, (e) => {
        if (instance._isClickOutside(e.target)) {
          instance.close();
        }
      });
    });
  }

  unbindClickOutside() {
    $(document).off(`click.outside-sui${this.instanceId}`);
  }

  _keysMethods(event) {
    // Escape key
    if (event.which === 27) {
      this.options.onEscapeKey(this.$component, event); // Callback

      if (this.options.closeOnEscapeKey) {
        this.close();
      }
    }

    // Enter key
    if (event.which === 13 && this.options.enterKeyToConfirmEnabled) {
      event.preventDefault();

      this.options.onEnterKey(this.$component, event); // Callback

      if (this.options.confirmOnEnterKey) {
        this.confirm();
      }
    }

    // Tab key
    if (event.which === 9) {
      this._focusInComponent(event);
    }
  }

  _focusInComponent(event) {
    if (!document.activeElement || $.contains(this.$component[0], document.activeElement)) {
      return;
    }

    event.preventDefault();

    const $focusElementsInComponent = $('button:not([tabindex="-1"]), [href]:not([tabindex="-1"]), input:not([tabindex="-1"]), select:not([tabindex="-1"]), textarea:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])', this.$component);

    if (!$focusElementsInComponent.length) {
      return;
    }

    $focusElementsInComponent[0].focus();
  }
}
