<template>
  <div v-bind="$attrs" class="relative" :dusk="dusk" ref="searchInputContainer">
    <div
      ref="input"
      @click.stop="open"
      @keydown.space.prevent="open"
      @keydown.down.prevent="open"
      @keydown.up.prevent="open"
      :class="{
        'ring dark:border-gray-500 dark:ring-gray-700': show,
        'form-input-border-error': error,
        'bg-gray-50 dark:bg-gray-700': disabled || readOnly,
      }"
      class="relative flex items-center form-control form-input form-control-bordered form-select pr-6"
      :tabindex="show ? -1 : 0"
      :aria-expanded="show === true ? 'true' : 'false'"
      :dusk="`${dusk}-selected`"
    >
      <IconArrow
        v-if="shouldShowDropdownArrow && !disabled"
        class="pointer-events-none form-select-arrow text-gray-700"
      />

      <slot name="default">
        <div class="text-gray-400 dark:text-gray-400">
          {{ __('Click to choose') }}
        </div>
      </slot>
    </div>

    <button
      type="button"
      @click="clear"
      v-if="!shouldShowDropdownArrow && !disabled"
      tabindex="-1"
      class="absolute p-2 inline-block right-[4px]"
      style="top: 6px"
      :dusk="`${dusk}-clear-button`"
    >
      <svg
        class="block fill-current icon h-2 w-2"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="278.046 126.846 235.908 235.908"
      >
        <path
          d="M506.784 134.017c-9.56-9.56-25.06-9.56-34.62 0L396 210.18l-76.164-76.164c-9.56-9.56-25.06-9.56-34.62 0-9.56 9.56-9.56 25.06 0 34.62L361.38 244.8l-76.164 76.165c-9.56 9.56-9.56 25.06 0 34.62 9.56 9.56 25.06 9.56 34.62 0L396 279.42l76.164 76.165c9.56 9.56 25.06 9.56 34.62 0 9.56-9.56 9.56-25.06 0-34.62L430.62 244.8l76.164-76.163c9.56-9.56 9.56-25.06 0-34.62z"
        />
      </svg>
    </button>
  </div>

  <teleport to="body">
    <div
      v-if="show"
      ref="dropdown"
      class="rounded-lg px-0 bg-white dark:bg-gray-900 shadow border border-gray-200 dark:border-gray-700 absolute top-0 left-0 my-1 overflow-hidden"
      :style="{ width: inputWidth + 'px', zIndex: 2000 }"
      :dusk="`${dusk}-dropdown`"
    >
      <!-- Search Input -->
      <input
        :disabled="disabled || readOnly"
        v-model="searchValue"
        ref="search"
        @keydown.enter.prevent="chooseSelected"
        @keydown.down.prevent="move(1)"
        @keydown.up.prevent="move(-1)"
        class="h-10 outline-none w-full px-3 text-sm leading-normal bg-white dark:bg-gray-700 rounded-t border-b border-gray-200 dark:border-gray-800"
        tabindex="-1"
        type="search"
        :placeholder="__('Search')"
        spellcheck="false"
      />

      <!-- Search Results -->
      <div
        ref="container"
        class="relative overflow-y-scroll text-sm"
        tabindex="-1"
        style="max-height: 155px"
        :dusk="`${dusk}-results`"
      >
        <div
          v-for="(option, index) in data"
          :dusk="`${dusk}-result-${index}`"
          :key="getTrackedByKey(option)"
          :ref="index === selectedOptionIndex ? 'selected' : 'unselected'"
          @click.stop="choose(option)"
          class="px-3 py-1.5 cursor-pointer z-[50]"
          :class="{
            'border-t border-gray-100 dark:border-gray-700': index !== 0,
            [`search-input-item-${index}`]: true,
            'hover:bg-gray-100 dark:hover:bg-gray-800':
              index !== selectedOptionIndex,
            'bg-primary-500 text-white dark:text-gray-900':
              index === selectedOptionIndex,
          }"
        >
          <slot
            name="option"
            :option="option"
            :selected="index === selectedOptionIndex"
          />
        </div>
      </div>
    </div>

    <Backdrop @click="close" :show="show" :style="{ zIndex: 1999 }" />
  </teleport>
</template>

<script>
import debounce from 'lodash/debounce'
import findIndex from 'lodash/findIndex'
import get from 'lodash/get'
import { createPopper } from '@popperjs/core'
import { mapProps } from '@/mixins'

export default {
  emits: ['clear', 'input', 'shown', 'closed', 'selected'],

  inheritAttrs: false,

  props: {
    dusk: {
      type: String,
      required: true,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    readOnly: {
      type: Boolean,
      default: false,
    },
    value: {},
    data: {},
    trackBy: {},
    error: {
      type: Boolean,
      default: false,
    },
    boundary: {},
    debounce: {
      type: Number,
      default: 500,
    },
    clearable: {
      type: Boolean,
      default: true,
    },
    ...mapProps(['mode']),
  },

  data: () => ({
    debouncer: null,
    show: false,
    searchValue: '',
    selectedOptionIndex: 0,
    popper: null,
    inputWidth: null,
  }),

  watch: {
    searchValue(search) {
      this.selectedOptionIndex = 0
      if (this.$refs.container) {
        this.$refs.container.scrollTop = 0
      } else {
        this.$nextTick(() => {
          this.$refs.container.scrollTop = 0
        })
      }

      this.debouncer(() => {
        this.$emit('input', search)
      })
    },

    show(show) {
      if (show) {
        let selected = findIndex(this.data, [
          this.trackBy,
          get(this.value, this.trackBy),
        ])
        if (selected !== -1) this.selectedOptionIndex = selected
        this.inputWidth = this.$refs.input.offsetWidth

        Nova.$emit('disable-focus-trap')

        this.$nextTick(() => {
          this.popper = createPopper(this.$refs.input, this.$refs.dropdown, {
            placement: 'bottom-start',
            onFirstUpdate: state => {
              this.$refs.container.scrollTop = this.$refs.container.scrollHeight
              this.updateScrollPosition()
              this.$refs.search.focus()
            },
          })
        })
      } else {
        this.$refs.search.blur()
        if (this.popper) this.popper.destroy()

        Nova.$emit('enable-focus-trap')
      }
    },
  },

  created() {
    this.debouncer = debounce(callback => callback(), this.debounce)
  },

  mounted() {
    document.addEventListener('keydown', this.handleEscape)
  },

  beforeUnmount() {
    document.removeEventListener('keydown', this.handleEscape)
  },

  methods: {
    handleEscape(e) {
      // 'tab' or 'escape'
      if (this.show && (e.keyCode == 9 || e.keyCode == 27)) {
        setTimeout(() => this.close(), 50)
      }
    },

    getTrackedByKey(option) {
      return get(option, this.trackBy)
    },

    open() {
      if (!this.disabled && !this.readOnly) {
        this.show = true
        this.searchValue = ''
        this.$emit('shown')
      }
    },

    close() {
      this.show = false
      this.$emit('closed')
    },

    clear() {
      if (!this.disabled) {
        this.selectedOptionIndex = null
        this.$emit('clear', null)
      }
    },

    move(offset) {
      let newIndex = this.selectedOptionIndex + offset

      if (newIndex >= 0 && newIndex < this.data.length) {
        this.selectedOptionIndex = newIndex
        this.updateScrollPosition()
      }
    },

    updateScrollPosition() {
      this.$nextTick(() => {
        if (this.$refs.selected && this.$refs.selected[0]) {
          if (
            this.$refs.selected[0].offsetTop >
            this.$refs.container.scrollTop +
              this.$refs.container.clientHeight -
              this.$refs.selected[0].clientHeight
          ) {
            this.$refs.container.scrollTop =
              this.$refs.selected[0].offsetTop +
              this.$refs.selected[0].clientHeight -
              this.$refs.container.clientHeight
          }

          if (
            this.$refs.selected[0].offsetTop < this.$refs.container.scrollTop
          ) {
            this.$refs.container.scrollTop = this.$refs.selected[0].offsetTop
          }
        }
      })
    },

    chooseSelected(event) {
      if (event.isComposing || event.keyCode === 229) return

      if (this.data[this.selectedOptionIndex] !== undefined) {
        this.$emit('selected', this.data[this.selectedOptionIndex])
        this.$refs.input.focus()
        this.$nextTick(() => this.close())
      }
    },

    choose(option) {
      this.selectedOptionIndex = findIndex(this.data, [
        this.trackBy,
        get(option, this.trackBy),
      ])
      this.$emit('selected', option)
      this.$refs.input.blur()
      this.$nextTick(() => this.close())
    },
  },

  computed: {
    shouldShowDropdownArrow() {
      return this.value == '' || this.value == null || !this.clearable
    },
  },
}
</script>
