text: 'use strict';
  
  import $ from 'jquery';
  import { onLoad } from './foundation.core.utils';
  import { Keyboard } from './foundation.util.keyboard';
  import { MediaQuery } from './foundation.util.mediaQuery';
  import { Motion } from './foundation.util.motion';
  import { Plugin } from './foundation.core.plugin';
  import { Triggers } from './foundation.util.triggers';
  import { Touch } from './foundation.util.touch'
  
  /**
   * Reveal module.
   * @module foundation.reveal
   * @requires foundation.util.keyboard
   * @requires foundation.util.triggers
   * @requires foundation.util.mediaQuery
   * @requires foundation.util.motion if using animations
   */
  
  class Reveal extends Plugin {
    /**
     * Creates a new instance of Reveal.
     * @class
     * @name Reveal
     * @param {jQuery} element - jQuery object to use for the modal.
     * @param {Object} options - optional parameters.
     */
    _setup(element, options) {
      this.$element = element;
      this.options = $.extend({}, Reveal.defaults, this.$element.data(), options);
      this.className = 'Reveal'; // ie9 back compat
      this._init();
  
      // Triggers init is idempotent, just need to make sure it is initialized
      Triggers.init($);
  
      Keyboard.register('Reveal', {
        'ESCAPE': 'close',
      });
    }
  
    /**
     * Initializes the modal by adding the overlay and close buttons, (if selected).
     * @private
     */
    _init() {
      MediaQuery._init();
      this.id = this.$element.attr('id');
      this.isActive = false;
      this.cached = {mq: MediaQuery.current};
  
      this.$anchor = $(`[data-open="${this.id}"]`).length ? $(`[data-open="${this.id}"]`) : $(`[data-toggle="${this.id}"]`);
      this.$anchor.attr({
        'aria-controls': this.id,
        'aria-haspopup': true,
        'tabindex': 0
      });
  
      if (this.options.fullScreen || this.$element.hasClass('full')) {
        this.options.fullScreen = true;
        this.options.overlay = false;
      }
      if (this.options.overlay && !this.$overlay) {
        this.$overlay = this._makeOverlay(this.id);
      }
  
      this.$element.attr({
          'role': 'dialog',
          'aria-hidden': true,
          'data-yeti-box': this.id,
          'data-resize': this.id
      });
  
      if(this.$overlay) {
        this.$element.detach().appendTo(this.$overlay);
      } else {
        this.$element.detach().appendTo($(this.options.appendTo));
        this.$element.addClass('without-overlay');
      }
      this._events();
      if (this.options.deepLink && window.location.hash === ( `#${this.id}`)) {
        this.onLoadListener = onLoad($(window), () => this.open());
      }
    }
  
    /**
     * Creates an overlay div to display behind the modal.
     * @private
     */
    _makeOverlay() {
      var additionalOverlayClasses = '';
  
      if (this.options.additionalOverlayClasses) {
        additionalOverlayClasses = ' ' + this.options.additionalOverlayClasses;
      }
  
      return $('<div></div>')
        .addClass('reveal-overlay' + additionalOverlayClasses)
        .appendTo(this.options.appendTo);
    }
  
    /**
     * Updates position of modal
     * TODO:  Figure out if we actually need to cache these values or if it doesn't matter
     * @private
     */
    _updatePosition() {
      var width = this.$element.outerWidth();
      var outerWidth = $(window).width();
      var height = this.$element.outerHeight();
      var outerHeight = $(window).height();
      var left, top = null;
      if (this.options.hOffset === 'auto') {
        left = parseInt((outerWidth - width) / 2, 10);
      } else {
        left = parseInt(this.options.hOffset, 10);
      }
      if (this.options.vOffset === 'auto') {
        if (height > outerHeight) {
          top = parseInt(Math.min(100, outerHeight / 10), 10);
        } else {
          top = parseInt((outerHeight - height) / 4, 10);
        }
      } else if (this.options.vOffset !== null) {
        top = parseInt(this.options.vOffset, 10);
      }
  
      if (top !== null) {
        this.$element.css({top: top + 'px'});
      }
  
      // only worry about left if we don't have an overlay or we have a horizontal offset,
      // otherwise we're perfectly in the middle
      if (!this.$overlay || (this.options.hOffset !== 'auto')) {
        this.$element.css({left: left + 'px'});
        this.$element.css({margin: '0px'});
      }
  
    }
  
    /**
     * Adds event handlers for the modal.
     * @private
     */
    _events() {
      var _this = this;
  
      this.$element.on({
        'open.zf.trigger': this.open.bind(this),
        'close.zf.trigger': (event, $element) => {
          if ((event.target === _this.$element[0]) ||
              ($(event.target).parents('[data-closable]')[0] === $element)) { // only close reveal when it's explicitly called
            return this.close.apply(this);
          }
        },
        'toggle.zf.trigger': this.toggle.bind(this),
        'resizeme.zf.trigger': function() {
          _this._updatePosition();
        }
      });
  
      if (this.options.closeOnClick && this.options.overlay) {
        this.$overlay.off('.zf.reveal').on('click.zf.reveal', function(e) {
          if (e.target === _this.$element[0] ||
            $.contains(_this.$element[0], e.target) ||
              !$.contains(document, e.target)) {
                return;
          }
          _this.close();
        });
      }
      if (this.options.deepLink) {
        $(window).on(`hashchange.zf.reveal:${this.id}`, this._handleState.bind(this));
      }
    }
  
    /**
     * Handles modal methods on back/forward button clicks or any other event that triggers hashchange.
     * @private
     */
    _handleState(e) {
      if(window.location.hash === ( '#' + this.id) && !this.isActive){ this.open(); }
      else{ this.close(); }
    }
  
    /**
    * Disables the scroll when Reveal is shown to prevent the background from shifting
    * @param {number} scrollTop - Scroll to visually apply, window current scroll by default
    */
    _disableScroll(scrollTop) {
      scrollTop = scrollTop || $(window).scrollTop();
      if ($(document).height() > $(window).height()) {
        $("html")
          .css("top", -scrollTop);
      }
    }
  
    /**
    * Reenables the scroll when Reveal closes
    * @param {number} scrollTop - Scroll to restore, html "top" property by default (as set by `_disableScroll`)
    */
    _enableScroll(scrollTop) {
      scrollTop = scrollTop || parseInt($("html").css("top"));
      if ($(document).height() > $(window).height()) {
        $("html")
          .css("top", "");
        $(window).scrollTop(-scrollTop);
      }
    }
  
  
    /**
     * Opens the modal controlled by `this.$anchor`, and closes all others by default.
     * @function
     * @fires Reveal#closeme
     * @fires Reveal#open
     */
    open() {
      // either update or replace browser history
      const hash = `#${this.id}`;
      if (this.options.deepLink && window.location.hash !== hash) {
  
        if (window.history.pushState) {
          if (this.options.updateHistory) {
            window.history.pushState({}, '', hash);
          } else {
            window.history.replaceState({}, '', hash);
          }
        } else {
          window.location.hash = hash;
        }
      }
  
      // Remember anchor that opened it to set focus back later, have general anchors as fallback
      this.$activeAnchor = $(document.activeElement).is(this.$anchor) ? $(document.activeElement) : this.$anchor;
  
      this.isActive = true;
  
      // Make elements invisible, but remove display: none so we can get size and positioning
      this.$element
          .css({ 'visibility': 'hidden' })
          .show()
          .scrollTop(0);
      if (this.options.overlay) {
        this.$overlay.css({'visibility': 'hidden'}).show();
      }
  
      this._updatePosition();
  
      this.$element
        .hide()
        .css({ 'visibility': '' });
  
      if(this.$overlay) {
        this.$overlay.css({'visibility': ''}).hide();
        if(this.$element.hasClass('fast')) {
          this.$overlay.addClass('fast');
        } else if (this.$element.hasClass('slow')) {
          this.$overlay.addClass('slow');
        }
      }
  
  
      if (!this.options.multipleOpened) {
        /**
         * Fires immediately before the modal opens.
         * Closes any other modals that are currently open
         * @event Reveal#closeme
         */
        this.$element.trigger('closeme.zf.reveal', this.id);
      }
  
      this._disableScroll();
  
      var _this = this;
  
      // Motion UI method of reveal
      if (this.options.animationIn) {
        function afterAnimation(){
          _this.$element
            .attr({
              'aria-hidden': false,
              'tabindex': -1
            })
            .focus();
          _this._addGlobalClasses();
          Keyboard.trapFocus(_this.$element);
        }
        if (this.options.overlay) {
          Motion.animateIn(this.$overlay, 'fade-in');
        }
        Motion.animateIn(this.$element, this.options.animationIn, () => {
          if(this.$element) { // protect against object having been removed
            this.focusableElements = Keyboard.findFocusable(this.$element);
            afterAnimation();
          }
        });
      }
      // jQuery method of reveal
      else {
        if (this.options.overlay) {
          this.$overlay.show(0);
        }
        this.$element.show(this.options.showDelay);
      }
  
      // handle accessibility
      this.$element
        .attr({
          'aria-hidden': false,
          'tabindex': -1
        })
        .focus();
      Keyboard.trapFocus(this.$element);
  
      this._addGlobalClasses();
  
      this._addGlobalListeners();
  
      /**
       * Fires when the modal has successfully opened.
       * @event Reveal#open
       */
      this.$element.trigger('open.zf.reveal');
    }
  
    /**
     * Adds classes and listeners on document required by open modals.
     *
     * The following classes are added and updated:
     * - `.is-reveal-open` - Prevents the scroll on document
     * - `.zf-has-scroll`  - Displays a disabled scrollbar on document if required like if the
     *                       scroll was not disabled. This prevent a "shift" of the page content due
     *                       the scrollbar disappearing when the modal opens.
     *
     * @private
     */
    _addGlobalClasses() {
      const updateScrollbarClass = () => {
        $('html').toggleClass('zf-has-scroll', !!($(document).height() > $(window).height()));
      };
  
      this.$element.on('resizeme.zf.trigger.revealScrollbarListener', () => updateScrollbarClass());
      updateScrollbarClass();
      $('html').addClass('is-reveal-open');
    }
  
    /**
     * Removes classes and listeners on document that were required by open modals.
     * @private
     */
    _removeGlobalClasses() {
      this.$element.off('resizeme.zf.trigger.revealScrollbarListener');
      $('html').removeClass('is-reveal-open');
      $('html').removeClass('zf-has-scroll');
    }
  
    /**
     * Adds extra event handlers for the body and window if necessary.
     * @private
     */
    _addGlobalListeners() {
      var _this = this;
      if(!this.$element) { return; } // If we're in the middle of cleanup, don't freak out
      this.focusableElements = Keyboard.findFocusable(this.$element);
  
      if (!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen) {
        $('body').on('click.zf.reveal', function(e) {
          if (e.target === _this.$element[0] ||
            $.contains(_this.$element[0], e.target) ||
              !$.contains(document, e.target)) { return; }
          _this.close();
        });
      }
  
      if (this.options.closeOnEsc) {
        $(window).on('keydown.zf.reveal', function(e) {
          Keyboard.handleKey(e, 'Reveal', {
            close: function() {
              if (_this.options.closeOnEsc) {
                _this.close();
              }
            }
          });
        });
      }
    }
  
    /**
     * Closes the modal.
     * @function
     * @fires Reveal#closed
     */
    close() {
      if (!this.isActive || !this.$element.is(':visible')) {
        return false;
      }
      var _this = this;
  
      // Motion UI method of hiding
      if (this.options.animationOut) {
        if (this.options.overlay) {
          Motion.animateOut(this.$overlay, 'fade-out');
        }
  
        Motion.animateOut(this.$element, this.options.animationOut, finishUp);
      }
      // jQuery method of hiding
      else {
        this.$element.hide(this.options.hideDelay);
  
        if (this.options.overlay) {
          this.$overlay.hide(0, finishUp);
        }
        else {
          finishUp();
        }
      }
  
      // Conditionals to remove extra event listeners added on open
      if (this.options.closeOnEsc) {
        $(window).off('keydown.zf.reveal');
      }
  
      if (!this.options.overlay && this.options.closeOnClick) {
        $('body').off('click.zf.reveal');
      }
  
      this.$element.off('keydown.zf.reveal');
  
      function finishUp() {
  
        // Get the current top before the modal is closed and restore the scroll after.
        // TODO: use component properties instead of HTML properties
        // See https://github.com/zurb/foundation-sites/pull/10786
        var scrollTop = parseInt($("html").css("top"));
  
        if ($('.reveal:visible').length  === 0) {
          _this._removeGlobalClasses(); // also remove .is-reveal-open from the html element when there is no opened reveal
        }
  
        Keyboard.releaseFocus(_this.$element);
  
        _this.$element.attr('aria-hidden', true);
  
        _this._enableScroll(scrollTop);
  
        /**
        * Fires when the modal is done closing.
        * @event Reveal#closed
        */
        _this.$element.trigger('closed.zf.reveal');
      }
  
      /**
      * Resets the modal content
      * This prevents a running video to keep going in the background
      */
      if (this.options.resetOnClose) {
        this.$element.html(this.$element.html());
      }
  
      this.isActive = false;
      // If deepLink and we did not switched to an other modal...
      if (_this.options.deepLink && window.location.hash === `#${this.id}`) {
        // Remove the history hash
        if (window.history.replaceState) {
          const urlWithoutHash = window.location.pathname + window.location.search;
          if (this.options.updateHistory) {
            window.history.pushState({}, '', urlWithoutHash); // remove the hash
          } else {
            window.history.replaceState('', document.title, urlWithoutHash);
          }
        } else {
          window.location.hash = '';
        }
      }
  
      this.$activeAnchor.focus();
    }
  
    /**
     * Toggles the open/closed state of a modal.
     * @function
     */
    toggle() {
      if (this.isActive) {
        this.close();
      } else {
        this.open();
      }
    };
  
    /**
     * Destroys an instance of a modal.
     * @function
     */
    _destroy() {
      if (this.options.overlay) {
        this.$element.appendTo($(this.options.appendTo)); // move $element outside of $overlay to prevent error unregisterPlugin()
        this.$overlay.hide().off().remove();
      }
      this.$element.hide().off();
      this.$anchor.off('.zf');
      $(window).off(`.zf.reveal:${this.id}`)
      if (this.onLoadListener) $(window).off(this.onLoadListener);
  
      if ($('.reveal:visible').length  === 0) {
        this._removeGlobalClasses(); // also remove .is-reveal-open from the html element when there is no opened reveal
      }
    };
  }
  
  Reveal.defaults = {
    /**
     * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
     * @option
     * @type {string}
     * @default ''
     */
    animationIn: '',
    /**
     * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
     * @option
     * @type {string}
     * @default ''
     */
    animationOut: '',
    /**
     * Time, in ms, to delay the opening of a modal after a click if no animation used.
     * @option
     * @type {number}
     * @default 0
     */
    showDelay: 0,
    /**
     * Time, in ms, to delay the closing of a modal after a click if no animation used.
     * @option
     * @type {number}
     * @default 0
     */
    hideDelay: 0,
    /**
     * Allows a click on the body/overlay to close the modal.
     * @option
     * @type {boolean}
     * @default true
     */
    closeOnClick: true,
    /**
     * Allows the modal to close if the user presses the `ESCAPE` key.
     * @option
     * @type {boolean}
     * @default true
     */
    closeOnEsc: true,
    /**
     * If true, allows multiple modals to be displayed at once.
     * @option
     * @type {boolean}
     * @default false
     */
    multipleOpened: false,
    /**
     * Distance, in pixels, the modal should push down from the top of the screen.
     * @option
     * @type {number|string}
     * @default auto
     */
    vOffset: 'auto',
    /**
     * Distance, in pixels, the modal should push in from the side of the screen.
     * @option
     * @type {number|string}
     * @default auto
     */
    hOffset: 'auto',
    /**
     * Allows the modal to be fullscreen, completely blocking out the rest of the view. JS checks for this as well.
     * @option
     * @type {boolean}
     * @default false
     */
    fullScreen: false,
    /**
     * Allows the modal to generate an overlay div, which will cover the view when modal opens.
     * @option
     * @type {boolean}
     * @default true
     */
    overlay: true,
    /**
     * Allows the modal to remove and reinject markup on close. Should be true if using video elements w/o using provider's api, otherwise, videos will continue to play in the background.
     * @option
     * @type {boolean}
     * @default false
     */
    resetOnClose: false,
    /**
     * Link the location hash to the modal.
     * Set the location hash when the modal is opened/closed, and open/close the modal when the location changes.
     * @option
     * @type {boolean}
     * @default false
     */
    deepLink: false,
    /**
     * If `deepLink` is enabled, update the browser history with the open modal
     * @option
     * @default false
     */
    updateHistory: false,
      /**
     * Allows the modal to append to custom div.
     * @option
     * @type {string}
     * @default "body"
     */
    appendTo: "body",
    /**
     * Allows adding additional class names to the reveal overlay.
     * @option
     * @type {string}
     * @default ''
     */
    additionalOverlayClasses: ''
  };
  
  export {Reveal};
  