<template>
  <VcPopover
    :click-outside-handler="closeDropdown"
    :open="open"
    :append-to="appendTarget"
    :disabled="appendDisabled"
    trigger="manual"
    :max-width="maxWidth"
    popover-width="100%"
    arrow-position="top-right"
    class="vc-dropdown-wrapper"
  >
    <template #reference>
      <VcSelectbox
        ref="reference"
        v-model="searchQuery"
        :prefix-icon="prefixIcon"
        :prefix-icon-color="prefixIconColor"
        :selected-value="selectedElement"
        :open="open"
        :readonly="readonly"
        :placeholder="placeholder"
        :info-text="infoText"
        :info-tooltip-width="infoTooltipWidth"
        v-bind="$attrs"
        @onclick="onClick"
        @handleNavigate="handleNavigate"
        @onDropdownClose="closeDropdown"
        @onblur="onBlur"
        @onchange="onChange"
        @onclear="onClear"
        @onfocus="onFocus"
        @onArrowDown="onArrowDown"
        @onArrowUp="onArrowUp"
        @onEnter="onEnter"
      >
        <template #suffix-content>
          <slot name="suffix-content" />
        </template>
      </VcSelectbox>
    </template>
    <div
      ref="dropdown"
      class="vc-dropdown"
      :class="popoverClass"
      :data-test-name="dataTestName"
    >
      <div
        ref="optionList"
        class="vc-dropdown-option-list"
        @scroll="loadMore"
      >
        <template v-if="$slots.default">
          <slot />
        </template>
        <template v-else>
          <div
            v-if="localOptions.length"
            ref="options"
          >
            <VcDropdownOption
              v-for="(option, index) in localOptions"
              :key="optionKey(option, index, config)"
              :value="optionValue(option, config)"
              :active="optionValue(option,config) === modelValue || index === keyup_selected"
              :label="optionLabel(option, config)"
              :icon="optionIcon(option, config)"
              :disabled="optionDisabled(option, config)"
              :icon-color-hex="optionIconColorHex(option, config)"
              :class="optionClass(option, config)"
              :style="optionStyle(option, config)"
              @click="onDropdownOptionClick(option)"
            >
              <template
                v-if="$slots.option"
                #default
              >
                <slot
                  name="option"
                  :option="option"
                />
              </template>
              <template #content-suffix>
                <slot
                  name="content-suffix"
                  :option="option"
                />
              </template>
            </VcDropdownOption>
          </div>
          <div v-else-if="!localOptions.length && !loading">
            <VcDropdownOption
              disabled
              :label="noDataMessage"
              value=""
            />
          </div>
        </template>
      </div>
    </div>
  </VcPopover>
</template>

<script lang="ts">
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { defineComponent, PropType } from 'vue'
import VcDropdownOption from '@/components/VcDropdown/VcDropdownOption.vue'
import VcSelectbox from '@/components/VcSelectbox/VcSelectbox.vue'
import VcPopover from '@/components/VcPopover/VcPopover.vue'
import { OptionType, OptionValueType } from '@/types/OptionAndConfig.types'
import dropdownMixin from '@/mixins/dropdown.mixin'
import optionMethods from '@/mixins/optionMethods.mixin'
import clickOutside from '@/directives/click-outside'
import { JSONPath } from 'jsonpath-plus'
import appendTo from '@/mixins/common/appendTo.mixin'
import { VcInputColor } from '@/components/VcInput/VcInput.types'

export default defineComponent({
  name: 'VcDropdown',
  components: {
    VcDropdownOption,
    VcPopover,
    VcSelectbox
  },
  directives: {
    'click-outside': clickOutside
  },
  mixins: [optionMethods, dropdownMixin, appendTo],
  props: {
    modelValue: {
      type: [String, Number] as PropType<OptionValueType>,
      default: null
    },
    readonly: {
      type: Boolean,
      default: false
    },
    prefixIcon: {
      type: String,
      default: ''
    },
    prefixIconColor: {
      type: String as PropType<VcInputColor>,
      default: ''
    },
    options: {
      type: Array,
      default: () => []
    },
    lazyLoadConfig: {
      type: Object,
      default: () => ({})
    },
    iconColorHex: {
      type: String,
      default: ''
    },
    popoverClass: {
      type: [String, Array, Object],
      default: ''
    },
    infoText: {
      type: String,
      default: ''
    },
    infoTooltipWidth: {
      type: String,
      default: '200'
    }
  },
  emits: ['update:modelValue', 'option-select', 'clear'],
  data () {
    return {
      localOptions: this.options as OptionType[]
    }
  },
  computed: {
    isLazyLoadEnabled (): boolean {
      return !!(
        (this.lazyLoadConfig?.getDataFunc && typeof this.lazyLoadConfig.getDataFunc === 'function') &&
        this.lazyLoadConfig?.getPaginationTotalPath
      )
    },
    keyupSelectedOption (): OptionType | undefined {
      return this.localOptions[this.keyup_selected]
    }
  },
  watch: {
    options: {
      deep: true,
      handler (val) {
        if (val.length) {
          this.localOptions = val
        }
        this.checkSelectedElement(this.modelValue)
      }
    },
    open (val) {
      if (!val) {
        this.keyup_selected = -1
      } else if (val && this.modelValue) {
        this.$nextTick(() => {
          const filteredIndex = this.getValueIndex(this.modelValue as number)
          const optionsNode = this.$refs.options as HTMLElement
          if (optionsNode) {
            const activeOption = optionsNode.children[filteredIndex] as HTMLElement
            if (activeOption) {
              (this.$refs.optionList as HTMLElement).scrollTop = activeOption.offsetTop
            }
          }
        })
      }
    },
    modelValue: {
      immediate: true,
      handler: function (newV: OptionValueType) {
        this.checkSelectedElement(newV)
      }
    },
    async searchQuery (searchedValue): Promise<void> {
      if (searchedValue && !this.open) {
        this.open = true
      }
      this.localOptions = this.options as OptionType[]
      this.lazyLoad.page = 0
      await this.loadData()
      this.localOptions = this.localOptions.filter((option) => {
        const opt = this.optionSearchKeys(option, this.config)
        let include = false
        if (opt && opt.length) {
          opt.forEach(value => {
            if (String(value).toLowerCase().includes(String(searchedValue).toLowerCase())) {
              include = true
            }
          })
        }

        return include
      })
    },
    placeholder (val) {
      if (val) {
        this.selectedElement = val
      }
    }
  },
  async created () {
    if (this.isLazyLoadEnabled) {
      await this.loadData()
    } else {
      this.localOptions = this.options
    }
  },
  methods: {
    checkSelectedElement (value: OptionValueType) {
      const filteredIndex = this.getValueIndex(value)
      if (filteredIndex !== -1) {
        this.keyup_selected = filteredIndex
      }
      if (value != null && value !== '') {
        const filtered = this.localOptions.find(option => {
          const optionValue = this.optionValue(option, this.config)
          return optionValue === value
        })
        if (filtered) {
          this.selectedElement = this.optionLabel(filtered, this.config)
        }
      } else {
        this.onClear()
      }
    },
    async loadData (): Promise<void> {
      try {
        if (this.lazyLoadConfig.getDataFunc && typeof this.lazyLoadConfig.getDataFunc === 'function') {
          this.loading = true
          const result = await this.lazyLoadConfig.getDataFunc(this.lazyLoad.page++, this.searchQuery)
          this.localOptions = [...this.localOptions, ...JSONPath({ json: result, path: this.lazyLoadConfig.getDataPath })[0]]
          this.lazyLoad.total = JSONPath({ json: result, path: this.lazyLoadConfig.getPaginationTotalPath })[0]
          this.loading = false
        }
      } catch (e) {
        console.log(e)
      }
    },
    getValueIndex (val: OptionValueType): number {
      return this.localOptions.findIndex(option => {
        const optVal = this.optionValue(option, this.config)
        return optVal === val
      })
    },
    updateModel (option: OptionType) {
      const optionValue = this.optionValue(option, this.config)

      this.$emit('option-select', option)
      this.$emit('update:modelValue', optionValue)
    },
    onArrowDown (ev: Event): void {
      ev.preventDefault()
      if (this.keyup_selected <= this.localOptions.length - 1) {
        this.keyup_selected = +this.keyup_selected + 1
        this.fixScrolling()
      }
    },
    onArrowUp (ev: Event): void {
      ev.preventDefault()
      if (this.keyup_selected >= 0) {
        this.keyup_selected = +this.keyup_selected - 1
        if (this.keyupSelectedOption !== undefined) {
          this.updateModel(this.keyupSelectedOption)
        }
        this.fixScrolling()
      }
    },
    setSelectedElement (): void {
      const filtered = this.localOptions[this.keyup_selected]
      if (filtered) {
        this.selectedElement = this.optionLabel(filtered, this.config)
      }
    },
    onEnter (): void {
      this.setSelectedElement()
      if (this.keyupSelectedOption !== undefined) {
        this.updateModel(this.keyupSelectedOption)
      }
      this.searchQuery = ''
      this.closeDropdown()
    },
    loadMore (): void {
      if (!this.isLazyLoadEnabled) {
        return
      }

      if (this.LOAD_MORE_BLOCK) {
        const scrollTop = this.LOAD_MORE_BLOCK.scrollTop
        const scrollHeight = this.LOAD_MORE_BLOCK.scrollHeight
        const offsetHeight = this.LOAD_MORE_BLOCK.offsetHeight
        const heightDiff = scrollHeight - offsetHeight
        const loadMoreOptions = this.localOptions.length < this.lazyLoad.total
        if (scrollTop === heightDiff && loadMoreOptions) {
          this.loadData()
        }
      }
    },
    onDropdownOptionClick (option: OptionType): void {
      const onClickFuncKey = this.config.onClickFuncKey
      const onClickSkipKey = this.config.onClickSkipKey

      if (typeof option === 'object') {
        if (onClickSkipKey !== undefined) {
          const clickSkip = option[onClickSkipKey]
          if (clickSkip) {
            return
          }
        }

        if (onClickFuncKey !== undefined) {
          const onClickFunc = option[onClickFuncKey]

          if (typeof onClickFunc === 'function') {
            onClickFunc()

            return
          }
        }
      }
      this.selectedElement = this.optionLabel(option, this.config)
      this.searchQuery = ''
      this.updateModel(option)
      this.closeDropdown()
    },
    onClick (): void {
      this.open = !this.open
      if (this.open) {
        this.$nextTick(() => {
          this.LOAD_MORE_BLOCK = document.querySelector('.vc-dropdown-option-list') as HTMLElement
        })
      }
    },
    onClear (): void {
      this.searchQuery = ''
      this.selectedElement = ''
      this.keyup_selected = -1
      this.$emit('update:modelValue', '')
      this.$emit('clear')
    }
  }
})
</script>

<style lang="scss" scoped>
@import "VcDropdown";
</style>
