(function($, anim) {
    'use strict';

    let _defaults = {
        alignment: 'left',
        autoFocus: true,
        constrainWidth: true,
        container: null,
        coverTrigger: true,
        closeOnClick: true,
        hover: false,
        inDuration: 150,
        outDuration: 250,
        onOpenStart: null,
        onOpenEnd: null,
        onCloseStart: null,
        onCloseEnd: null,
        onItemClick: null
    };

    /**
     * @class
     */
    class Dropdown extends Component {
        constructor(el, options) {
            super(Dropdown, el, options);

            this.el.M_Dropdown = this;
            Dropdown._dropdowns.push(this);

            this.id = M.getIdFromTrigger(el);
            this.dropdownEl = document.getElementById(this.id);
            this.$dropdownEl = $(this.dropdownEl);

            /**
             * Options for the dropdown
             * @member Dropdown#options
             * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to
             * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard
             * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button
             * @prop {Element} container - Container element to attach dropdown to (optional)
             * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger
             * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item
             * @prop {Boolean} [hover=false] - Open dropdown on hover
             * @prop {Number} [inDuration=150] - Duration of open animation in ms
             * @prop {Number} [outDuration=250] - Duration of close animation in ms
             * @prop {Function} onOpenStart - Function called when dropdown starts opening
             * @prop {Function} onOpenEnd - Function called when dropdown finishes opening
             * @prop {Function} onCloseStart - Function called when dropdown starts closing
             * @prop {Function} onCloseEnd - Function called when dropdown finishes closing
             */
            this.options = $.extend({}, Dropdown.defaults, options);

            /**
             * Describes open/close state of dropdown
             * @type {Boolean}
             */
            this.isOpen = false;

            /**
             * Describes if dropdown content is scrollable
             * @type {Boolean}
             */
            this.isScrollable = false;

            /**
             * Describes if touch moving on dropdown content
             * @type {Boolean}
             */
            this.isTouchMoving = false;

            this.focusedIndex = -1;
            this.filterQuery = [];

            // Move dropdown-content after dropdown-trigger
            if (!!this.options.container) {
                $(this.options.container).append(this.dropdownEl);
            } else {
                this.$el.after(this.dropdownEl);
            }

            this._makeDropdownFocusable();
            this._resetFilterQueryBound = this._resetFilterQuery.bind(this);
            this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
            this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this);
            this._handleDropdownClickBound = this._handleDropdownClick.bind(this);
            this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this);
            this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this);
            this._setupEventHandlers();
        }

        static get defaults() {
            return _defaults;
        }

        static init(els, options) {
            return super.init(this, els, options);
        }

        /**
         * Get Instance
         */
        static getInstance(el) {
            let domElem = !!el.jquery ? el[0] : el;
            return domElem.M_Dropdown;
        }

        /**
         * Teardown component
         */
        destroy() {
            this._resetDropdownStyles();
            this._removeEventHandlers();
            Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1);
            this.el.M_Dropdown = undefined;
        }

        /**
         * Setup Event Handlers
         */
        _setupEventHandlers() {
            // Trigger keydown handler
            this.el.addEventListener('keydown', this._handleTriggerKeydownBound);

            // Item click handler
            this.dropdownEl.addEventListener('click', this._handleDropdownClickBound);

            // Hover event handlers
            if (this.options.hover) {
                this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
                this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
                this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
                this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
                this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound);

                // Click event handlers
            } else {
                this._handleClickBound = this._handleClick.bind(this);
                this.el.addEventListener('click', this._handleClickBound);
            }
        }

        /**
         * Remove Event Handlers
         */
        _removeEventHandlers() {
            this.el.removeEventListener('keydown', this._handleTriggerKeydownBound);
            this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound);

            if (this.options.hover) {
                this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
                this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
                this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound);
            } else {
                this.el.removeEventListener('click', this._handleClickBound);
            }
        }

        _setupTemporaryEventHandlers() {
            // Use capture phase event handler to prevent click
            document.body.addEventListener('click', this._handleDocumentClickBound, true);
            // document.body.addEventListener('touchend', this._handleDocumentClickBound);
            document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound);
            this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound);
        }

        _removeTemporaryEventHandlers() {
            // Use capture phase event handler to prevent click
            document.body.removeEventListener('click', this._handleDocumentClickBound, true);
            // document.body.removeEventListener('touchend', this._handleDocumentClickBound);
            document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound);
            this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound);
        }

        _handleClick(e) {
            e.preventDefault();
            this.open();
        }

        _handleMouseEnter() {
            this.open();
        }

        _handleMouseLeave(e) {
            let toEl = e.toElement || e.relatedTarget;
            let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length;
            let leaveToActiveDropdownTrigger = false;

            let $closestTrigger = $(toEl).closest('.dropdown-trigger');
            if (
                $closestTrigger.length &&
                !!$closestTrigger[0].M_Dropdown &&
                $closestTrigger[0].M_Dropdown.isOpen
            ) {
                leaveToActiveDropdownTrigger = true;
            }

            // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
            if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
                this.close();
            }
        }

        _handleDocumentClick(e) {
            let $target = $(e.target);
            if (
                this.options.closeOnClick &&
                $target.closest('.dropdown-content').length &&
                !this.isTouchMoving
            ) {
                // isTouchMoving to check if scrolling on mobile.
                setTimeout(() => {
                    this.close();
                }, 0);
            } else if (
                $target.closest('.dropdown-trigger').length ||
                !$target.closest('.dropdown-content').length
            ) {
                setTimeout(() => {
                    this.close();
                }, 0);
            }
            this.isTouchMoving = false;
        }

        _handleTriggerKeydown(e) {
            // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown
            if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) {
                e.preventDefault();
                this.open();
            }
        }

        /**
         * Handle Document Touchmove
         * @param {Event} e
         */
        _handleDocumentTouchmove(e) {
            let $target = $(e.target);
            if ($target.closest('.dropdown-content').length) {
                this.isTouchMoving = true;
            }
        }

        /**
         * Handle Dropdown Click
         * @param {Event} e
         */
        _handleDropdownClick(e) {
            // onItemClick callback
            if (typeof this.options.onItemClick === 'function') {
                let itemEl = $(e.target).closest('li')[0];
                this.options.onItemClick.call(this, itemEl);
            }
        }

        /**
         * Handle Dropdown Keydown
         * @param {Event} e
         */
        _handleDropdownKeydown(e) {
            if (e.which === M.keys.TAB) {
                e.preventDefault();
                this.close();

                // Navigate down dropdown list
            } else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) {
                e.preventDefault();
                let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1;
                let newFocusedIndex = this.focusedIndex;
                let foundNewIndex = false;
                do {
                    newFocusedIndex = newFocusedIndex + direction;

                    if (!!this.dropdownEl.children[newFocusedIndex] &&
                        this.dropdownEl.children[newFocusedIndex].tabIndex !== -1
                    ) {
                        foundNewIndex = true;
                        break;
                    }
                } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);

                if (foundNewIndex) {
                    this.focusedIndex = newFocusedIndex;
                    this._focusFocusedItem();
                }

                // ENTER selects choice on focused item
            } else if (e.which === M.keys.ENTER && this.isOpen) {
                // Search for <a> and <button>
                let focusedElement = this.dropdownEl.children[this.focusedIndex];
                let $activatableElement = $(focusedElement)
                    .find('a, button')
                    .first();

                // Click a or button tag if exists, otherwise click li tag
                if (!!$activatableElement.length) {
                    $activatableElement[0].click();
                } else if (!!focusedElement) {
                    focusedElement.click();
                }

                // Close dropdown on ESC
            } else if (e.which === M.keys.ESC && this.isOpen) {
                e.preventDefault();
                this.close();
            }

            // CASE WHEN USER TYPE LETTERS
            let letter = String.fromCharCode(e.which).toLowerCase(),
                nonLetters = [9, 13, 27, 38, 40];
            if (letter && nonLetters.indexOf(e.which) === -1) {
                this.filterQuery.push(letter);

                let string = this.filterQuery.join(''),
                    newOptionEl = $(this.dropdownEl)
                    .find('li')
                    .filter((el) => {
                        return (
                            $(el)
                            .text()
                            .toLowerCase()
                            .indexOf(string) === 0
                        );
                    })[0];

                if (newOptionEl) {
                    this.focusedIndex = $(newOptionEl).index();
                    this._focusFocusedItem();
                }
            }

            this.filterTimeout = setTimeout(this._resetFilterQueryBound, 1000);
        }

        /**
         * Setup dropdown
         */
        _resetFilterQuery() {
            this.filterQuery = [];
        }

        _resetDropdownStyles() {
            this.$dropdownEl.css({
                display: '',
                width: '',
                height: '',
                left: '',
                top: '',
                'transform-origin': '',
                transform: '',
                opacity: ''
            });
        }

        _makeDropdownFocusable() {
            // Needed for arrow key navigation
            this.dropdownEl.tabIndex = 0;

            // Only set tabindex if it hasn't been set by user
            $(this.dropdownEl)
                .children()
                .each(function(el) {
                    if (!el.getAttribute('tabindex')) {
                        el.setAttribute('tabindex', 0);
                    }
                });
        }

        _focusFocusedItem() {
            if (
                this.focusedIndex >= 0 &&
                this.focusedIndex < this.dropdownEl.children.length &&
                this.options.autoFocus
            ) {
                this.dropdownEl.children[this.focusedIndex].focus();
            }
        }

        _getDropdownPosition() {
            let offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
            let triggerBRect = this.el.getBoundingClientRect();
            let dropdownBRect = this.dropdownEl.getBoundingClientRect();

            let idealHeight = dropdownBRect.height;
            let idealWidth = dropdownBRect.width;
            let idealXPos = triggerBRect.left - dropdownBRect.left;
            let idealYPos = triggerBRect.top - dropdownBRect.top;

            let dropdownBounds = {
                left: idealXPos,
                top: idealYPos,
                height: idealHeight,
                width: idealWidth
            };

            // Countainer here will be closest ancestor with overflow: hidden
            let closestOverflowParent = !!this.dropdownEl.offsetParent ?
                this.dropdownEl.offsetParent :
                this.dropdownEl.parentNode;

            let alignments = M.checkPossibleAlignments(
                this.el,
                closestOverflowParent,
                dropdownBounds,
                this.options.coverTrigger ? 0 : triggerBRect.height
            );

            let verticalAlignment = 'top';
            let horizontalAlignment = this.options.alignment;
            idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;

            // Reset isScrollable
            this.isScrollable = false;

            if (!alignments.top) {
                if (alignments.bottom) {
                    verticalAlignment = 'bottom';
                } else {
                    this.isScrollable = true;

                    // Determine which side has most space and cutoff at correct height
                    if (alignments.spaceOnTop > alignments.spaceOnBottom) {
                        verticalAlignment = 'bottom';
                        idealHeight += alignments.spaceOnTop;
                        idealYPos -= alignments.spaceOnTop;
                    } else {
                        idealHeight += alignments.spaceOnBottom;
                    }
                }
            }

            // If preferred horizontal alignment is possible
            if (!alignments[horizontalAlignment]) {
                let oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
                if (alignments[oppositeAlignment]) {
                    horizontalAlignment = oppositeAlignment;
                } else {
                    // Determine which side has most space and cutoff at correct height
                    if (alignments.spaceOnLeft > alignments.spaceOnRight) {
                        horizontalAlignment = 'right';
                        idealWidth += alignments.spaceOnLeft;
                        idealXPos -= alignments.spaceOnLeft;
                    } else {
                        horizontalAlignment = 'left';
                        idealWidth += alignments.spaceOnRight;
                    }
                }
            }

            if (verticalAlignment === 'bottom') {
                idealYPos =
                    idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
            }
            if (horizontalAlignment === 'right') {
                idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
            }
            return {
                x: idealXPos,
                y: idealYPos,
                verticalAlignment: verticalAlignment,
                horizontalAlignment: horizontalAlignment,
                height: idealHeight,
                width: idealWidth
            };
        }

        /**
         * Animate in dropdown
         */
        _animateIn() {
            anim.remove(this.dropdownEl);
            anim({
                targets: this.dropdownEl,
                opacity: {
                    value: [0, 1],
                    easing: 'easeOutQuad'
                },
                scaleX: [0.3, 1],
                scaleY: [0.3, 1],
                duration: this.options.inDuration,
                easing: 'easeOutQuint',
                complete: (anim) => {
                    if (this.options.autoFocus) {
                        this.dropdownEl.focus();
                    }

                    // onOpenEnd callback
                    if (typeof this.options.onOpenEnd === 'function') {
                        this.options.onOpenEnd.call(this, this.el);
                    }
                }
            });
        }

        /**
         * Animate out dropdown
         */
        _animateOut() {
            anim.remove(this.dropdownEl);
            anim({
                targets: this.dropdownEl,
                opacity: {
                    value: 0,
                    easing: 'easeOutQuint'
                },
                scaleX: 0.3,
                scaleY: 0.3,
                duration: this.options.outDuration,
                easing: 'easeOutQuint',
                complete: (anim) => {
                    this._resetDropdownStyles();

                    // onCloseEnd callback
                    if (typeof this.options.onCloseEnd === 'function') {
                        this.options.onCloseEnd.call(this, this.el);
                    }
                }
            });
        }

        /**
         * Place dropdown
         */
        _placeDropdown() {
            // Set width before calculating positionInfo
            let idealWidth = this.options.constrainWidth ?
                this.el.getBoundingClientRect().width :
                this.dropdownEl.getBoundingClientRect().width;
            this.dropdownEl.style.width = idealWidth + 'px';

            let positionInfo = this._getDropdownPosition();
            this.dropdownEl.style.left = positionInfo.x + 'px';
            this.dropdownEl.style.top = positionInfo.y + 'px';
            this.dropdownEl.style.height = positionInfo.height + 'px';
            this.dropdownEl.style.width = positionInfo.width + 'px';
            this.dropdownEl.style.transformOrigin = `${
          positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
        } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
        }

        /**
         * Open Dropdown
         */
        open() {
            if (this.isOpen) {
                return;
            }
            this.isOpen = true;

            // onOpenStart callback
            if (typeof this.options.onOpenStart === 'function') {
                this.options.onOpenStart.call(this, this.el);
            }

            // Reset styles
            this._resetDropdownStyles();
            this.dropdownEl.style.display = 'block';

            this._placeDropdown();
            this._animateIn();
            this._setupTemporaryEventHandlers();
        }

        /**
         * Close Dropdown
         */
        close() {
            if (!this.isOpen) {
                return;
            }
            this.isOpen = false;
            this.focusedIndex = -1;

            // onCloseStart callback
            if (typeof this.options.onCloseStart === 'function') {
                this.options.onCloseStart.call(this, this.el);
            }

            this._animateOut();
            this._removeTemporaryEventHandlers();

            if (this.options.autoFocus) {
                this.el.focus();
            }
        }

        /**
         * Recalculate dimensions
         */
        recalculateDimensions() {
            if (this.isOpen) {
                this.$dropdownEl.css({
                    width: '',
                    height: '',
                    left: '',
                    top: '',
                    'transform-origin': ''
                });
                this._placeDropdown();
            }
        }
    }

    /**
     * @static
     * @memberof Dropdown
     */
    Dropdown._dropdowns = [];

    M.Dropdown = Dropdown;

    if (M.jQueryLoaded) {
        M.initializeJqueryWrapper(Dropdown, 'dropdown', 'M_Dropdown');
    }
})(cash, M.anime);