'use strict';

import $ from 'jquery';
import { Motion } from './foundation.util.motion';
import { Plugin } from './foundation.core.plugin';
import { RegExpEscape } from './foundation.core.utils';
import { Triggers } from './foundation.util.triggers';

/**
 * Toggler module.
 * @module foundation.toggler
 * @requires foundation.util.motion
 * @requires foundation.util.triggers
 */

class Toggler extends Plugin {
  /**
   * Creates a new instance of Toggler.
   * @class
   * @name Toggler
   * @fires Toggler#init
   * @param {Object} element - jQuery object to add the trigger to.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options = $.extend({}, Toggler.defaults, element.data(), options);
    this.className = '';
    this.className = 'Toggler'; // ie9 back compat

    // Triggers init is idempotent, just need to make sure it is initialized
    Triggers.init($);

    this._init();
    this._events();
  }

  /**
   * Initializes the Toggler plugin by parsing the toggle class from data-toggler, or animation classes from data-animate.
   * @function
   * @private
   */
  _init() {
    // Collect triggers to set ARIA attributes to
    var id = this.$element[0].id,
      $triggers = $(`[data-open~="${id}"], [data-close~="${id}"], [data-toggle~="${id}"]`);

    var input;
    // Parse animation classes if they were set
    if (this.options.animate) {
      input = this.options.animate.split(' ');

      this.animationIn = input[0];
      this.animationOut = input[1] || null;

      // - aria-expanded: according to the element visibility.
      $triggers.attr('aria-expanded', !this.$element.is(':hidden'));
    }
    // Otherwise, parse toggle class
    else {
      input = this.options.toggler;
      if (typeof input !== 'string' || !input.length) {
        throw new Error(`The 'toogler' option containing the target class is required, got "${input}"`);
      }
      // Allow for a . at the beginning of the string
      this.className = input[0] === '.' ? input.slice(1) : input;

      // - aria-expanded: according to the elements class set.
      $triggers.attr('aria-expanded', this.$element.hasClass(this.className));
    }

    // - aria-controls: adding the element id to it if not already in it.
    $triggers.each((index, trigger) => {
      const $trigger = $(trigger);
      const controls = $trigger.attr('aria-controls') || '';

      const containsId = new RegExp(`\\b${RegExpEscape(id)}\\b`).test(controls);
      if (!containsId) $trigger.attr('aria-controls', controls ? `${controls} ${id}` : id);
    });
  }

  /**
   * Initializes events for the toggle trigger.
   * @function
   * @private
   */
  _events() {
    this.$element.off('toggle.zf.trigger').on('toggle.zf.trigger', this.toggle.bind(this));
  }

  /**
   * Toggles the target class on the target element. An event is fired from the original trigger depending on if the resultant state was "on" or "off".
   * @function
   * @fires Toggler#on
   * @fires Toggler#off
   */
  toggle() {
    this[ this.options.animate ? '_toggleAnimate' : '_toggleClass']();
  }

  _toggleClass() {
    this.$element.toggleClass(this.className);

    var isOn = this.$element.hasClass(this.className);
    if (isOn) {
      /**
       * Fires if the target element has the class after a toggle.
       * @event Toggler#on
       */
      this.$element.trigger('on.zf.toggler');
    }
    else {
      /**
       * Fires if the target element does not have the class after a toggle.
       * @event Toggler#off
       */
      this.$element.trigger('off.zf.toggler');
    }

    this._updateARIA(isOn);
    this.$element.find('[data-mutate]').trigger('mutateme.zf.trigger');
  }

  _toggleAnimate() {
    var _this = this;

    if (this.$element.is(':hidden')) {
      Motion.animateIn(this.$element, this.animationIn, function() {
        _this._updateARIA(true);
        this.trigger('on.zf.toggler');
        this.find('[data-mutate]').trigger('mutateme.zf.trigger');
      });
    }
    else {
      Motion.animateOut(this.$element, this.animationOut, function() {
        _this._updateARIA(false);
        this.trigger('off.zf.toggler');
        this.find('[data-mutate]').trigger('mutateme.zf.trigger');
      });
    }
  }

  _updateARIA(isOn) {
    var id = this.$element[0].id;
    $(`[data-open="${id}"], [data-close="${id}"], [data-toggle="${id}"]`)
      .attr({
        'aria-expanded': isOn ? true : false
      });
  }

  /**
   * Destroys the instance of Toggler on the element.
   * @function
   */
  _destroy() {
    this.$element.off('.zf.toggler');
  }
}

Toggler.defaults = {
  /**
   * Class of the element to toggle. It can be provided with or without "."
   * @option
   * @type {string}
   */
  toggler: undefined,
  /**
   * Tells the plugin if the element should animated when toggled.
   * @option
   * @type {boolean}
   * @default false
   */
  animate: false
};

export {Toggler};
