text: (function($) {
    'use strict';
  
    let _defaults = {
      data: {}, // Autocomplete data set
      limit: Infinity, // Limit of results the autocomplete shows
      onAutocomplete: null, // Callback for when autocompleted
      minLength: 1, // Min characters before autocomplete starts
      sortFunction: function(a, b, inputString) {
        // Sort function for sorting autocomplete results
        return a.indexOf(inputString) - b.indexOf(inputString);
      },
      useCustomData: false
    };
  
    /**
     * @class
     *
     */
    class Autocomplete extends Component {
      /**
       * Construct Autocomplete instance
       * @constructor
       * @param {Element} el
       * @param {Object} options
       */
      constructor(el, options) {
        super(Autocomplete, el, options);
  
        this.el.M_Autocomplete = this;
  
        /**
         * Options for the autocomplete
         * @member Autocomplete#options
         * @prop {Number} duration
         * @prop {Number} dist
         * @prop {number} shift
         * @prop {number} padding
         * @prop {Boolean} fullWidth
         * @prop {Boolean} indicators
         * @prop {Boolean} noWrap
         * @prop {Function} onCycleTo
         */
        this.options = $.extend({}, Autocomplete.defaults, options);
  
        // Setup
        this.isOpen = false;
        this.count = 0;
        this.activeIndex = -1;
        this.oldVal;
        this.$inputField = this.$el.closest('.input-field');
        this.$active = $();
        this._mousedown = false;
        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_Autocomplete;
      }
  
      /**
       * Teardown component
       */
      destroy() {
        this._removeEventHandlers();
        this._removeDropdown();
        this.el.M_Autocomplete = undefined;
      }
  
      /**
       * Setup Event Handlers
       */
      _setupEventHandlers() {
        this._handleInputBlurBound = this._handleInputBlur.bind(this);
        this._handleInputKeyupAndFocusBound = this._handleInputKeyupAndFocus.bind(this);
        this._handleInputKeydownBound = this._handleInputKeydown.bind(this);
        this._handleInputClickBound = this._handleInputClick.bind(this);
        this._handleContainerMousedownAndTouchstartBound = this._handleContainerMousedownAndTouchstart.bind(
          this
        );
        this._handleContainerMouseupAndTouchendBound = this._handleContainerMouseupAndTouchend.bind(
          this
        );
  
        this.el.addEventListener('blur', this._handleInputBlurBound);
        this.el.addEventListener('keyup', this._handleInputKeyupAndFocusBound);
        this.el.addEventListener('focus', this._handleInputKeyupAndFocusBound);
        this.el.addEventListener('keydown', this._handleInputKeydownBound);
        this.el.addEventListener('click', this._handleInputClickBound);
        this.container.addEventListener(
          'mousedown',
          this._handleContainerMousedownAndTouchstartBound
        );
        this.container.addEventListener('mouseup', this._handleContainerMouseupAndTouchendBound);
  
        if (typeof window.ontouchstart !== 'undefined') {
          this.container.addEventListener(
            'touchstart',
            this._handleContainerMousedownAndTouchstartBound
          );
          this.container.addEventListener('touchend', this._handleContainerMouseupAndTouchendBound);
        }
      }
  
      /**
       * Remove Event Handlers
       */
      _removeEventHandlers() {
        this.el.removeEventListener('blur', this._handleInputBlurBound);
        this.el.removeEventListener('keyup', this._handleInputKeyupAndFocusBound);
        this.el.removeEventListener('focus', this._handleInputKeyupAndFocusBound);
        this.el.removeEventListener('keydown', this._handleInputKeydownBound);
        this.el.removeEventListener('click', this._handleInputClickBound);
        this.container.removeEventListener(
          'mousedown',
          this._handleContainerMousedownAndTouchstartBound
        );
        this.container.removeEventListener('mouseup', this._handleContainerMouseupAndTouchendBound);
  
        if (typeof window.ontouchstart !== 'undefined') {
          this.container.removeEventListener(
            'touchstart',
            this._handleContainerMousedownAndTouchstartBound
          );
          this.container.removeEventListener(
            'touchend',
            this._handleContainerMouseupAndTouchendBound
          );
        }
      }
  
      /**
       * Setup dropdown
       */
      _setupDropdown() {
        this.container = document.createElement('ul');
        this.container.id = `autocomplete-options-${M.guid()}`;
        $(this.container).addClass('autocomplete-content dropdown-content');
        this.$inputField.append(this.container);
        this.el.setAttribute('data-target', this.container.id);
  
        this.dropdown = M.Dropdown.init(this.el, {
          autoFocus: false,
          closeOnClick: false,
          coverTrigger: false,
          constrainWidth: false,
          onItemClick: (itemEl) => {
            this.selectOption($(itemEl));
          }
        });
  
        // Sketchy removal of dropdown click handler
        this.el.removeEventListener('click', this.dropdown._handleClickBound);
      }
  
      /**
       * Remove dropdown
       */
      _removeDropdown() {
        this.container.parentNode.removeChild(this.container);
      }
  
      /**
       * Handle Input Blur
       */
      _handleInputBlur() {
        if (!this._mousedown) {
          this.close();
          this._resetAutocomplete();
        }
      }
  
      /**
       * Handle Input Keyup and Focus
       * @param {Event} e
       */
      _handleInputKeyupAndFocus(e) {
        if (e.type === 'keyup') {
          Autocomplete._keydown = false;
        }
  
        this.count = 0;
        let val = this.el.value.toLowerCase();
  
        // Don't capture enter or arrow key usage.
        if (e.keyCode === 13 || e.keyCode === 38 || e.keyCode === 40) {
          return;
        }
  
        // Check if the input isn't empty
        // Check if focus triggered by tab
        if (this.oldVal !== val && (M.tabPressed || e.type !== 'focus')) {
          this.open();
        }
  
        // Update oldVal
        this.oldVal = val;
      }
  
      /**
       * Handle Input Keydown
       * @param {Event} e
       */
      _handleInputKeydown(e) {
        Autocomplete._keydown = true;
  
        // Arrow keys and enter key usage
        let keyCode = e.keyCode,
          liElement,
          numItems = $(this.container).children('li').length;
  
        // select element on Enter
        if (keyCode === M.keys.ENTER && this.activeIndex >= 0) {
          liElement = $(this.container)
            .children('li')
            .eq(this.activeIndex);
          if (liElement.length) {
            this.selectOption(liElement);
            e.preventDefault();
          }
          return;
        }
  
        // Capture up and down key
        if (keyCode === M.keys.ARROW_UP || keyCode === M.keys.ARROW_DOWN) {
          e.preventDefault();
  
          if (keyCode === M.keys.ARROW_UP && this.activeIndex > 0) {
            this.activeIndex--;
          }
  
          if (keyCode === M.keys.ARROW_DOWN && this.activeIndex < numItems - 1) {
            this.activeIndex++;
          }
  
          this.$active.removeClass('active');
          if (this.activeIndex >= 0) {
            this.$active = $(this.container)
              .children('li')
              .eq(this.activeIndex);
            this.$active.addClass('active');
          }
        }
      }
  
      /**
       * Handle Input Click
       * @param {Event} e
       */
      _handleInputClick(e) {
        this.open();
      }
  
      /**
       * Handle Container Mousedown and Touchstart
       * @param {Event} e
       */
      _handleContainerMousedownAndTouchstart(e) {
        this._mousedown = true;
      }
  
      /**
       * Handle Container Mouseup and Touchend
       * @param {Event} e
       */
      _handleContainerMouseupAndTouchend(e) {
        this._mousedown = false;
      }
  
      /**
       * Highlight partial match
       */
      _highlight(string, $el) {
        let img = $el.find('img');
        let data = $el.find('span').attr('data');
        let matchStart = $el
            .text()
            .toLowerCase()
            .indexOf('' + string.toLowerCase() + ''),
          matchEnd = matchStart + string.length - 1,
          beforeMatch = $el.text().slice(0, matchStart),
          matchText = $el.text().slice(matchStart, matchEnd + 1),
          afterMatch = $el.text().slice(matchEnd + 1);
        $el.html(
          `<span data="${data}">${beforeMatch}<span class='highlight'>${matchText}</span>${afterMatch}</span>`
        );
        if (img.length) {
          $el.prepend(img);
        }
      }
  
      /**
       * Reset current element position
       */
      _resetCurrentElement() {
        this.activeIndex = -1;
        this.$active.removeClass('active');
      }
  
      /**
       * Reset autocomplete elements
       */
      _resetAutocomplete() {
        $(this.container).empty();
        this._resetCurrentElement();
        this.oldVal = null;
        this.isOpen = false;
        this._mousedown = false;
      }
  
      /**
       * Select autocomplete option
       * @param {Element} el  Autocomplete option list item element
       */
      selectOption(el) {
        let text = el.text().trim();
        this.el.value = text;
        let event = { value: text };
        if (this.options.useCustomData) {
          let data = el.find('span').attr('data');
          let customData = JSON.parse(data);
          event['data'] = customData;
        }
        this.$el.trigger('change');
        this._resetAutocomplete();
        this.close();
        // Handle onAutocomplete callback.
        if (typeof this.options.onAutocomplete === 'function') {
          this.options.onAutocomplete.call(this, event);
        }
      }
  
      /**
       * Render dropdown content
       * @param {Object} data  data set
       * @param {String} val  current input value
       */
      _renderDropdown(data, val) {
        this._resetAutocomplete();
  
        let matchingData = [];
  
        // Gather all matching data
        for (let key in data) {
          if (data.hasOwnProperty(key) && key.toLowerCase().indexOf(val) !== -1) {
            let entry = {
              data: data[key],
              key: key
            };
            matchingData.push(entry);
  
            this.count++;
          }
        }
  
        // Sort
        if (this.options.sortFunction) {
          let sortFunctionBound = (a, b) => {
            return this.options.sortFunction(
              a.key.toLowerCase(),
              b.key.toLowerCase(),
              val.toLowerCase()
            );
          };
          matchingData.sort(sortFunctionBound);
        }
  
        // Limit
        matchingData = matchingData.slice(0, this.options.limit);
  
        // Render
        for (let i = 0; i < matchingData.length; i++) {
          let entry = matchingData[i];
          let $autocompleteOption = $('<li></li>');
          if (!!entry.data && !this.options.useCustomData) {
            $autocompleteOption.append(
              `<img src="${entry.data}" class="right circle"><span>${entry.key}</span>`
            );
          } else if (this.options.useCustomData) {
            let stringData = JSON.stringify(entry.data);
            $autocompleteOption.append(`<span data="${stringData}">${entry.key}</span>`);
          } else {
            $autocompleteOption.append('<span>' + entry.key + '</span>');
          }
  
          $(this.container).append($autocompleteOption);
          this._highlight(val, $autocompleteOption);
        }
      }
  
      /**
       * Open Autocomplete Dropdown
       */
      open() {
        let val = this.el.value.toLowerCase();
  
        this._resetAutocomplete();
  
        if (val.length >= this.options.minLength) {
          this.isOpen = true;
          this._renderDropdown(this.options.data, val);
        }
  
        // Open dropdown
        if (!this.dropdown.isOpen) {
          this.dropdown.open();
        } else {
          // Recalculate dropdown when its already open
          this.dropdown.recalculateDimensions();
        }
      }
  
      /**
       * Close Autocomplete Dropdown
       */
      close() {
        this.dropdown.close();
      }
  
      /**
       * Update Data
       * @param {Object} data
       */
      updateData(data) {
        let val = this.el.value.toLowerCase();
        this.options.data = data;
  
        if (this.isOpen) {
          this._renderDropdown(data, val);
        }
      }
    }
  
    /**
     * @static
     * @memberof Autocomplete
     */
    Autocomplete._keydown = false;
  
    M.Autocomplete = Autocomplete;
  
    if (M.jQueryLoaded) {
      M.initializeJqueryWrapper(Autocomplete, 'autocomplete', 'M_Autocomplete');
    }
  })(cash);
  