‘use strict’;

!function($) {

/**

* Dropdown module.
* @module foundation.dropdown
* @requires foundation.util.keyboard
* @requires foundation.util.box
* @requires foundation.util.triggers
*/

class Dropdown {

/**
 * Creates a new instance of a dropdown.
 * @class
 * @param {jQuery} element - jQuery object to make into a dropdown.
 *        Object should be of the dropdown panel, rather than its anchor.
 * @param {Object} options - Overrides to the default plugin settings.
 */
constructor(element, options) {
  this.$element = element;
  this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
  this._init();

  Foundation.registerPlugin(this, 'Dropdown');
  Foundation.Keyboard.register('Dropdown', {
    'ENTER': 'open',
    'SPACE': 'open',
    'ESCAPE': 'close'
  });
}

/**
 * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
 * @function
 * @private
 */
_init() {
  var $id = this.$element.attr('id');

  this.$anchor = $(`[data-toggle="${$id}"]`).length ? $(`[data-toggle="${$id}"]`) : $(`[data-open="${$id}"]`);
  this.$anchor.attr({
    'aria-controls': $id,
    'data-is-focus': false,
    'data-yeti-box': $id,
    'aria-haspopup': true,
    'aria-expanded': false

  });

  if(this.options.parentClass){
    this.$parent = this.$element.parents('.' + this.options.parentClass);
  }else{
    this.$parent = null;
  }
  this.options.positionClass = this.getPositionClass();
  this.counter = 4;
  this.usedPositions = [];
  this.$element.attr({
    'aria-hidden': 'true',
    'data-yeti-box': $id,
    'data-resize': $id,
    'aria-labelledby': this.$anchor[0].id || Foundation.GetYoDigits(6, 'dd-anchor')
  });
  this._events();
}

/**
 * Helper function to determine current orientation of dropdown pane.
 * @function
 * @returns {String} position - string value of a position class.
 */
getPositionClass() {
  var verticalPosition = this.$element[0].className.match(/(top|left|right|bottom)/g);
      verticalPosition = verticalPosition ? verticalPosition[0] : '';
  var horizontalPosition = /float-(\S+)/.exec(this.$anchor[0].className);
      horizontalPosition = horizontalPosition ? horizontalPosition[1] : '';
  var position = horizontalPosition ? horizontalPosition + ' ' + verticalPosition : verticalPosition;

  return position;
}

/**
 * Adjusts the dropdown panes orientation by adding/removing positioning classes.
 * @function
 * @private
 * @param {String} position - position class to remove.
 */
_reposition(position) {
  this.usedPositions.push(position ? position : 'bottom');
  //default, try switching to opposite side
  if(!position && (this.usedPositions.indexOf('top') < 0)){
    this.$element.addClass('top');
  }else if(position === 'top' && (this.usedPositions.indexOf('bottom') < 0)){
    this.$element.removeClass(position);
  }else if(position === 'left' && (this.usedPositions.indexOf('right') < 0)){
    this.$element.removeClass(position)
        .addClass('right');
  }else if(position === 'right' && (this.usedPositions.indexOf('left') < 0)){
    this.$element.removeClass(position)
        .addClass('left');
  }

  //if default change didn't work, try bottom or left first
  else if(!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)){
    this.$element.addClass('left');
  }else if(position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)){
    this.$element.removeClass(position)
        .addClass('left');
  }else if(position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
    this.$element.removeClass(position);
  }else if(position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
    this.$element.removeClass(position);
  }
  //if nothing cleared, set to bottom
  else{
    this.$element.removeClass(position);
  }
  this.classChanged = true;
  this.counter--;
}

/**
 * Sets the position and orientation of the dropdown pane, checks for collisions.
 * Recursively calls itself if a collision is detected, with a new position class.
 * @function
 * @private
 */
_setPosition() {
  if(this.$anchor.attr('aria-expanded') === 'false'){ return false; }
  var position = this.getPositionClass(),
      $eleDims = Foundation.Box.GetDimensions(this.$element),
      $anchorDims = Foundation.Box.GetDimensions(this.$anchor),
      _this = this,
      direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')),
      param = (direction === 'top') ? 'height' : 'width',
      offset = (param === 'height') ? this.options.vOffset : this.options.hOffset;

  if(($eleDims.width >= $eleDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.$element, this.$parent))){
    var newWidth = $eleDims.windowDims.width,
        parentHOffset = 0;
    if(this.$parent){
      var $parentDims = Foundation.Box.GetDimensions(this.$parent),
          parentHOffset = $parentDims.offset.left;
      if ($parentDims.width < newWidth){
        newWidth = $parentDims.width;
      }
    }

    this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, 'center bottom', this.options.vOffset, this.options.hOffset + parentHOffset, true)).css({
      'width': newWidth - (this.options.hOffset * 2),
      'height': 'auto'
    });
    this.classChanged = true;
    return false;
  }

  this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, position, this.options.vOffset, this.options.hOffset));

  while(!Foundation.Box.ImNotTouchingYou(this.$element, this.$parent, true) && this.counter){
    this._reposition(position);
    this._setPosition();
  }
}

/**
 * Adds event listeners to the element utilizing the triggers utility library.
 * @function
 * @private
 */
_events() {
  var _this = this;
  this.$element.on({
    'open.zf.trigger': this.open.bind(this),
    'close.zf.trigger': this.close.bind(this),
    'toggle.zf.trigger': this.toggle.bind(this),
    'resizeme.zf.trigger': this._setPosition.bind(this)
  });

  if(this.options.hover){
    this.$anchor.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
    .on('mouseenter.zf.dropdown', function(){
      var bodyData = $('body').data();
      if(typeof(bodyData.whatinput) === 'undefined' || bodyData.whatinput === 'mouse') {
        clearTimeout(_this.timeout);
        _this.timeout = setTimeout(function(){
          _this.open();
          _this.$anchor.data('hover', true);
        }, _this.options.hoverDelay);
      }
    }).on('mouseleave.zf.dropdown', function(){
      clearTimeout(_this.timeout);
      _this.timeout = setTimeout(function(){
        _this.close();
        _this.$anchor.data('hover', false);
      }, _this.options.hoverDelay);
    });
    if(this.options.hoverPane){
      this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
          .on('mouseenter.zf.dropdown', function(){
            clearTimeout(_this.timeout);
          }).on('mouseleave.zf.dropdown', function(){
            clearTimeout(_this.timeout);
            _this.timeout = setTimeout(function(){
              _this.close();
              _this.$anchor.data('hover', false);
            }, _this.options.hoverDelay);
          });
    }
  }
  this.$anchor.add(this.$element).on('keydown.zf.dropdown', function(e) {

    var $target = $(this),
      visibleFocusableElements = Foundation.Keyboard.findFocusable(_this.$element);

    Foundation.Keyboard.handleKey(e, 'Dropdown', {
      open: function() {
        if ($target.is(_this.$anchor)) {
          _this.open();
          _this.$element.attr('tabindex', -1).focus();
          e.preventDefault();
        }
      },
      close: function() {
        _this.close();
        _this.$anchor.focus();
      }
    });
  });
}

/**
 * Adds an event handler to the body to close any dropdowns on a click.
 * @function
 * @private
 */
_addBodyHandler() {
   var $body = $(document.body).not(this.$element),
       _this = this;
   $body.off('click.zf.dropdown')
        .on('click.zf.dropdown', function(e){
          if(_this.$anchor.is(e.target) || _this.$anchor.find(e.target).length) {
            return;
          }
          if(_this.$element.find(e.target).length) {
            return;
          }
          _this.close();
          $body.off('click.zf.dropdown');
        });
}

/**
 * Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
 * @function
 * @fires Dropdown#closeme
 * @fires Dropdown#show
 */
open() {
  // var _this = this;
  /**
   * Fires to close other open dropdowns, typically when dropdown is opening
   * @event Dropdown#closeme
   */
  this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
  this.$anchor.addClass('hover')
      .attr({'aria-expanded': true});
  // this.$element/*.show()*/;
  this._setPosition();
  this.$element.addClass('is-open')
      .attr({'aria-hidden': false});

  if(this.options.autoFocus){
    var $focusable = Foundation.Keyboard.findFocusable(this.$element);
    if($focusable.length){
      $focusable.eq(0).focus();
    }
  }

  if(this.options.closeOnClick){ this._addBodyHandler(); }

  if (this.options.trapFocus) {
    Foundation.Keyboard.trapFocus(this.$element);
  }

  /**
   * Fires once the dropdown is visible.
   * @event Dropdown#show
   */
  this.$element.trigger('show.zf.dropdown', [this.$element]);
}

/**
 * Closes the open dropdown pane.
 * @function
 * @fires Dropdown#hide
 */
close() {
  if(!this.$element.hasClass('is-open')){
    return false;
  }
  this.$element.removeClass('is-open')
      .attr({'aria-hidden': true});

  this.$anchor.removeClass('hover')
      .attr('aria-expanded', false);

  if(this.classChanged){
    var curPositionClass = this.getPositionClass();
    if(curPositionClass){
      this.$element.removeClass(curPositionClass);
    }
    this.$element.addClass(this.options.positionClass)
        /*.hide()*/.css({height: '', width: ''});
    this.classChanged = false;
    this.counter = 4;
    this.usedPositions.length = 0;
  }
  /**
   * Fires once the dropdown is no longer visible.
   * @event Dropdown#hide
   */
  this.$element.trigger('hide.zf.dropdown', [this.$element]);

  if (this.options.trapFocus) {
    Foundation.Keyboard.releaseFocus(this.$element);
  }
}

/**
 * Toggles the dropdown pane's visibility.
 * @function
 */
toggle() {
  if(this.$element.hasClass('is-open')){
    if(this.$anchor.data('hover')) return;
    this.close();
  }else{
    this.open();
  }
}

/**
 * Destroys the dropdown.
 * @function
 */
destroy() {
  this.$element.off('.zf.trigger').hide();
  this.$anchor.off('.zf.dropdown');

  Foundation.unregisterPlugin(this);
}

}

Dropdown.defaults = {

/**
 * Class that designates bounding container of Dropdown (default: window)
 * @option
 * @type {?string}
 * @default null
 */
parentClass: null,
/**
 * Amount of time to delay opening a submenu on hover event.
 * @option
 * @type {number}
 * @default 250
 */
hoverDelay: 250,
/**
 * Allow submenus to open on hover events
 * @option
 * @type {boolean}
 * @default false
 */
hover: false,
/**
 * Don't close dropdown when hovering over dropdown pane
 * @option
 * @type {boolean}
 * @default false
 */
hoverPane: false,
/**
 * Number of pixels between the dropdown pane and the triggering element on open.
 * @option
 * @type {number}
 * @default 1
 */
vOffset: 1,
/**
 * Number of pixels between the dropdown pane and the triggering element on open.
 * @option
 * @type {number}
 * @default 1
 */
hOffset: 1,
/**
 * Class applied to adjust open position. JS will test and fill this in.
 * @option
 * @type {string}
 * @default ''
 */
positionClass: '',
/**
 * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
 * @option
 * @type {boolean}
 * @default false
 */
trapFocus: false,
/**
 * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
 * @option
 * @type {boolean}
 * @default false
 */
autoFocus: false,
/**
 * Allows a click on the body to close the dropdown.
 * @option
 * @type {boolean}
 * @default false
 */
closeOnClick: false

}

// Window exports Foundation.plugin(Dropdown, ‘Dropdown’);

}(jQuery);