(function($) {
    'use strict';
  
    let _defaults = {
      classes: '',
      dropdownOptions: {}
    };
  
    /**
     * @class
     *
     */
    class FormSelect extends Component {
      /**
       * Construct FormSelect instance
       * @constructor
       * @param {Element} el
       * @param {Object} options
       */
      constructor(el, options) {
        super(FormSelect, el, options);
  
        // Don't init if browser default version
        if (this.$el.hasClass('browser-default')) {
          return;
        }
  
        this.el.M_FormSelect = this;
  
        /**
         * Options for the select
         * @member FormSelect#options
         */
        this.options = $.extend({}, FormSelect.defaults, options);
  
        this.isMultiple = this.$el.prop('multiple');
  
        // Setup
        this.el.tabIndex = -1;
        this._keysSelected = {};
        this._valueDict = {}; // Maps key to original and generated option element.
        this._setupDropdown();
  
        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_FormSelect;
      }
  
      /**
       * Teardown component
       */
      destroy() {
        this._removeEventHandlers();
        this._removeDropdown();
        this.el.M_FormSelect = undefined;
      }
  
      /**
       * Setup Event Handlers
       */
      _setupEventHandlers() {
        this._handleSelectChangeBound = this._handleSelectChange.bind(this);
        this._handleOptionClickBound = this._handleOptionClick.bind(this);
        this._handleInputClickBound = this._handleInputClick.bind(this);
  
        $(this.dropdownOptions)
          .find('li:not(.optgroup)')
          .each((el) => {
            el.addEventListener('click', this._handleOptionClickBound);
          });
        this.el.addEventListener('change', this._handleSelectChangeBound);
        this.input.addEventListener('click', this._handleInputClickBound);
      }
  
      /**
       * Remove Event Handlers
       */
      _removeEventHandlers() {
        $(this.dropdownOptions)
          .find('li:not(.optgroup)')
          .each((el) => {
            el.removeEventListener('click', this._handleOptionClickBound);
          });
        this.el.removeEventListener('change', this._handleSelectChangeBound);
        this.input.removeEventListener('click', this._handleInputClickBound);
      }
  
      /**
       * Handle Select Change
       * @param {Event} e
       */
      _handleSelectChange(e) {
        this._setValueToInput();
      }
  
      /**
       * Handle Option Click
       * @param {Event} e
       */
      _handleOptionClick(e) {
        e.preventDefault();
        let optionEl = $(e.target).closest('li')[0];
        this._selectOption(optionEl);
        e.stopPropagation();
      }
  
      _selectOption(optionEl) {
        let key = optionEl.id;
        if (!$(optionEl).hasClass('disabled') && !$(optionEl).hasClass('optgroup') && key.length) {
          let selected = true;
  
          if (this.isMultiple) {
            // Deselect placeholder option if still selected.
            let placeholderOption = $(this.dropdownOptions).find('li.disabled.selected');
            if (placeholderOption.length) {
              placeholderOption.removeClass('selected');
              placeholderOption.find('input[type="checkbox"]').prop('checked', false);
              this._toggleEntryFromArray(placeholderOption[0].id);
            }
            selected = this._toggleEntryFromArray(key);
          } else {
            $(this.dropdownOptions)
              .find('li')
              .removeClass('selected');
            $(optionEl).toggleClass('selected', selected);
            this._keysSelected = {};
            this._keysSelected[optionEl.id] = true;
          }
  
          // Set selected on original select option
          // Only trigger if selected state changed
          let prevSelected = $(this._valueDict[key].el).prop('selected');
          if (prevSelected !== selected) {
            $(this._valueDict[key].el).prop('selected', selected);
            this.$el.trigger('change');
          }
        }
  
        if (!this.isMultiple) {
          this.dropdown.close();
        }
      }
  
      /**
       * Handle Input Click
       */
      _handleInputClick() {
        if (this.dropdown && this.dropdown.isOpen) {
          this._setValueToInput();
          this._setSelectedStates();
        }
      }
  
      /**
       * Setup dropdown
       */
      _setupDropdown() {
        this.wrapper = document.createElement('div');
        $(this.wrapper).addClass('select-wrapper ' + this.options.classes);
        this.$el.before($(this.wrapper));
        // Move actual select element into overflow hidden wrapper
        let $hideSelect = $('<div class="hide-select"></div>');
        $(this.wrapper).append($hideSelect);
        $hideSelect[0].appendChild(this.el);
  
        if (this.el.disabled) {
          this.wrapper.classList.add('disabled');
        }
  
        // Create dropdown
        this.$selectOptions = this.$el.children('option, optgroup');
        this.dropdownOptions = document.createElement('ul');
        this.dropdownOptions.id = `select-options-${M.guid()}`;
        $(this.dropdownOptions).addClass(
          'dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : '')
        );
  
        // Create dropdown structure.
        if (this.$selectOptions.length) {
          this.$selectOptions.each((el) => {
            if ($(el).is('option')) {
              // Direct descendant option.
              let optionEl;
              if (this.isMultiple) {
                optionEl = this._appendOptionWithIcon(this.$el, el, 'multiple');
              } else {
                optionEl = this._appendOptionWithIcon(this.$el, el);
              }
  
              this._addOptionToValueDict(el, optionEl);
            } else if ($(el).is('optgroup')) {
              // Optgroup.
              let selectOptions = $(el).children('option');
              $(this.dropdownOptions).append(
                $('<li class="optgroup"><span>' + el.getAttribute('label') + '</span></li>')[0]
              );
  
              selectOptions.each((el) => {
                let optionEl = this._appendOptionWithIcon(this.$el, el, 'optgroup-option');
                this._addOptionToValueDict(el, optionEl);
              });
            }
          });
        }
  
        $(this.wrapper).append(this.dropdownOptions);
  
        // Add input dropdown
        this.input = document.createElement('input');
        $(this.input).addClass('select-dropdown dropdown-trigger');
        this.input.setAttribute('type', 'text');
        this.input.setAttribute('readonly', 'true');
        this.input.setAttribute('data-target', this.dropdownOptions.id);
        if (this.el.disabled) {
          $(this.input).prop('disabled', 'true');
        }
  
        $(this.wrapper).prepend(this.input);
        this._setValueToInput();
  
        // Add caret
        let dropdownIcon = $(
          '<svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'
        );
        $(this.wrapper).prepend(dropdownIcon[0]);
  
        // Initialize dropdown
        if (!this.el.disabled) {
          let dropdownOptions = $.extend({}, this.options.dropdownOptions);
          let userOnOpenEnd = dropdownOptions.onOpenEnd;
  
          // Add callback for centering selected option when dropdown content is scrollable
          dropdownOptions.onOpenEnd = (el) => {
            let selectedOption = $(this.dropdownOptions)
              .find('.selected')
              .first();
  
            if (selectedOption.length) {
              // Focus selected option in dropdown
              M.keyDown = true;
              this.dropdown.focusedIndex = selectedOption.index();
              this.dropdown._focusFocusedItem();
              M.keyDown = false;
  
              // Handle scrolling to selected option
              if (this.dropdown.isScrollable) {
                let scrollOffset =
                  selectedOption[0].getBoundingClientRect().top -
                  this.dropdownOptions.getBoundingClientRect().top; // scroll to selected option
                scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown
                this.dropdownOptions.scrollTop = scrollOffset;
              }
            }
  
            // Handle user declared onOpenEnd if needed
            if (userOnOpenEnd && typeof userOnOpenEnd === 'function') {
              userOnOpenEnd.call(this.dropdown, this.el);
            }
          };
  
          // Prevent dropdown from closeing too early
          dropdownOptions.closeOnClick = false;
  
          this.dropdown = M.Dropdown.init(this.input, dropdownOptions);
        }
  
        // Add initial selections
        this._setSelectedStates();
      }
  
      /**
       * Add option to value dict
       * @param {Element} el  original option element
       * @param {Element} optionEl  generated option element
       */
      _addOptionToValueDict(el, optionEl) {
        let index = Object.keys(this._valueDict).length;
        let key = this.dropdownOptions.id + index;
        let obj = {};
        optionEl.id = key;
  
        obj.el = el;
        obj.optionEl = optionEl;
        this._valueDict[key] = obj;
      }
  
      /**
       * Remove dropdown
       */
      _removeDropdown() {
        $(this.wrapper)
          .find('.caret')
          .remove();
        $(this.input).remove();
        $(this.dropdownOptions).remove();
        $(this.wrapper).before(this.$el);
        $(this.wrapper).remove();
      }
  
      /**
       * Setup dropdown
       * @param {Element} select  select element
       * @param {Element} option  option element from select
       * @param {String} type
       * @return {Element}  option element added
       */
      _appendOptionWithIcon(select, option, type) {
        // Add disabled attr if disabled
        let disabledClass = option.disabled ? 'disabled ' : '';
        let optgroupClass = type === 'optgroup-option' ? 'optgroup-option ' : '';
        let multipleCheckbox = this.isMultiple
          ? `<label><input type="checkbox"${disabledClass}"/><span>${option.innerHTML}</span></label>`
          : option.innerHTML;
        let liEl = $('<li></li>');
        let spanEl = $('<span></span>');
        spanEl.html(multipleCheckbox);
        liEl.addClass(`${disabledClass} ${optgroupClass}`);
        liEl.append(spanEl);
  
        // add icons
        let iconUrl = option.getAttribute('data-icon');
        if (!!iconUrl) {
          let imgEl = $(`<img alt="" src="${iconUrl}">`);
          liEl.prepend(imgEl);
        }
  
        // Check for multiple type.
        $(this.dropdownOptions).append(liEl[0]);
        return liEl[0];
      }
  
      /**
       * Toggle entry from option
       * @param {String} key  Option key
       * @return {Boolean}  if entry was added or removed
       */
      _toggleEntryFromArray(key) {
        let notAdded = !this._keysSelected.hasOwnProperty(key);
        let $optionLi = $(this._valueDict[key].optionEl);
  
        if (notAdded) {
          this._keysSelected[key] = true;
        } else {
          delete this._keysSelected[key];
        }
  
        $optionLi.toggleClass('selected', notAdded);
  
        // Set checkbox checked value
        $optionLi.find('input[type="checkbox"]').prop('checked', notAdded);
  
        // use notAdded instead of true (to detect if the option is selected or not)
        $optionLi.prop('selected', notAdded);
  
        return notAdded;
      }
  
      /**
       * Set text value to input
       */
      _setValueToInput() {
        let values = [];
        let options = this.$el.find('option');
  
        options.each((el) => {
          if ($(el).prop('selected')) {
            let text = $(el).text();
            values.push(text);
          }
        });
  
        if (!values.length) {
          let firstDisabled = this.$el.find('option:disabled').eq(0);
          if (firstDisabled.length && firstDisabled[0].value === '') {
            values.push(firstDisabled.text());
          }
        }
  
        this.input.value = values.join(', ');
      }
  
      /**
       * Set selected state of dropdown to match actual select element
       */
      _setSelectedStates() {
        this._keysSelected = {};
  
        for (let key in this._valueDict) {
          let option = this._valueDict[key];
          let optionIsSelected = $(option.el).prop('selected');
          $(option.optionEl)
            .find('input[type="checkbox"]')
            .prop('checked', optionIsSelected);
          if (optionIsSelected) {
            this._activateOption($(this.dropdownOptions), $(option.optionEl));
            this._keysSelected[key] = true;
          } else {
            $(option.optionEl).removeClass('selected');
          }
        }
      }
  
      /**
       * Make option as selected and scroll to selected position
       * @param {jQuery} collection  Select options jQuery element
       * @param {Element} newOption  element of the new option
       */
      _activateOption(collection, newOption) {
        if (newOption) {
          if (!this.isMultiple) {
            collection.find('li.selected').removeClass('selected');
          }
          let option = $(newOption);
          option.addClass('selected');
        }
      }
  
      /**
       * Get Selected Values
       * @return {Array}  Array of selected values
       */
      getSelectedValues() {
        let selectedValues = [];
        for (let key in this._keysSelected) {
          selectedValues.push(this._valueDict[key].el.value);
        }
        return selectedValues;
      }
    }
  
    M.FormSelect = FormSelect;
  
    if (M.jQueryLoaded) {
      M.initializeJqueryWrapper(FormSelect, 'formSelect', 'M_FormSelect');
    }
  })(cash);