<template>
  <ui-select
    v-bind="selectProps"
    :on-search-input="searchHandler"
    class="mm-auto-complete"
    filterable
    v-on="$listeners">
    <ui-icon
      slot="suffix-icon"
      name="search"
      size="12px">
    </ui-icon>
    <template
      slot="option"
      slot-scope="slotProps">
      <slot
        v-bind="slotProps"
        name="option"></slot>
    </template>
    <template>
      <slot></slot>
    </template>
  </ui-select>
</template>

<script>
import isFunction from "lodash/isFunction"
import debounce from "lodash/debounce"
import isNumber from "lodash/isNumber"
import input from "../../mixins/input"
import Select from "../select"
import Icon from "../icon"

import { t } from "../../locale"
import { isPromise, cancelPromise } from "../../utils/promise"
import config from "../../managers/config"

config.defaults({
  "AutoComplete.filterMethod": function() {
    return !isFunction(this.remoteMethod)
  },
  "AutoComplete.debounce": () => ({
    wait: 300,
    options: {},
  }),
})

export default {
  name: "mm-auto-complete",
  components: {
    "ui-select": Select,
    "ui-icon": Icon,
  },
  mixins: [input],
  inheritAttrs: false,
  props: {
    /* eslint-disable vue/require-prop-types */
    value: {
      ...Select.props.value,
      type: [String, Number],
    },
    /* eslint-disable vue/require-prop-types */
    placeholder: {
      ...Select.props.placeholder,
      default: t("ui.autoComplete.placeholder"),
    },
    remoteMethod: {
      type: Function,
    },
    filterMethod: {
      type: [Boolean, Function],
      default() {
        return config.get("AutoComplete.filterMethod").call(this)
      },
    },
    debounce: {
      type: [Object, Number],
      default() {
        return config.get("AutoComplete.debounce").call(this)
      },
    },
    loading: {
      type: Boolean,
      default: false,
    },
    options: {
      type: Array,
      default: () => [],
    },
  },

  data() {
    return {
      localLoading: this.loading,
      localOptions: this.options,
    }
  },
  computed: {
    selectProps() {
      return {
        ...this.$attrs,
        value: this.value,
        placeholder: this.placeholder,
        filterMethod: this.filterMethod,
        loading: this.localLoading,
        options: this.localOptions,
      }
    },
    remoteMethodHandler() {
      if (isFunction(this.remoteMethod)) {
        const { wait, options } = {
          ...(isNumber(debounce) ? { wait: this.debounce } : this.debounce),
          ...this.$options.props.debounce.default(),
        }

        return debounce(this.wrapRemoteMethod, wait, options)
      }
      return undefined
    },
  },
  watch: {
    loading(value) {
      this.localLoading = value
    },
    options(value) {
      this.localOptions = value
    },
  },
  beforeDestroy() {
    if (this.remoteMethodHandler) {
      this.remoteMethodHandler.cancel()
    }
  },

  methods: {
    searchHandler(event) {
      const searchValue = event.target.value
      if (this.remoteMethod && this.remoteMethodHandler) {
        this.remoteMethodHandler(searchValue)
      }
    },

    processLoading(loading = false) {
      this.localLoading = loading
    },

    wrapRemoteMethod(searchValue) {
      if (this.remotePromise) {
        this.remotePromise.cancel()
        this.remotePromise = null
      }

      const remote = this.remoteMethod(searchValue, this.processLoading)

      if (remote === false) {
        this.processLoading(false)
        return
      }

      if (!isPromise(remote)) {
        return
      }

      this.processLoading(true)
      this.remotePromise = cancelPromise(remote)
      this.remotePromise.promise
        .then(data => {
          if (Array.isArray(data)) {
            this.localOptions = data
          }
          this.processLoading(false)
        })
        .catch(error => {
          if (error instanceof Error && error.type === "cancel") {
            return
          }

          this.processLoading(false)
          return Promise.reject(error)
        })
    },
  },
}
</script>
