<template>
  <div ref="dropdown" class="smart-select dropdown">
    <div ref="dropdownToggle" class="input-group" role="button">
      <input
        ref="search"
        v-model="inputFilter"
        type="text"
        class="form-control form-control-sm"
        :placeholder="selectedInputDescription === false ? nullDescription : selectedInputDescription"
        :class="inputClass"
        autocomplete="nope"
        @keyup="filterResults"
        @focus="focusInput"
        @blur="blurInput"
        @keyup.enter="select(activeIndex)"
        @keyup.down="activeIndex = activeIndex === allOptions.length-1 ? allOptions.length-1 : activeIndex+1"
        @keyup.up="activeIndex = activeIndex === -1 ? activeIndex : activeIndex-1">
      <div class="input-group-text" @click="$refs.search.focus();">
        <i class="fa fa-caret-down" />
      </div>
    </div>
    <ul class="dropdown-menu" :class="hasFocus ? 'show' : ''" :style="dropdownPosition" @scroll="handleScroll">
      <li class="dropdown-item" :class="{active: activeIndex === -1}" @mouseover="isMouseOver=true;activeIndex=-1" @mouseleave="isMouseOver=false;activeIndex=null">
        {{ nullDescription }}
      </li>
      <li
        v-for="(option, index) in allOptions"
        :key="index"
        class="dropdown-item"
        :class="{active: index === activeIndex}"
        @mouseover="isMouseOver=true;activeIndex=index"
        @mouseleave="isMouseOver=false;activeIndex=index">
        <slot name="option" :option="option">
          {{ option.node !== undefined ? (option.node[descriptionIndex].node_description !== undefined ? option.node[descriptionIndex].node_description : option.node[descriptionIndex]) : option[descriptionIndex] }}
        </slot>
      </li>
    </ul>
  </div>
</template>

<script>
import { useDefinitions } from '@/components/hooks/definitions'
import { Utils } from './lib/utils.js'
import { useQuery, useResult } from '@vue/apollo-composable'
import { watch, ref, reactive, onMounted, onUnmounted } from 'vue'
import debounce from 'lodash/debounce'

export default {
  props: {
    inputClass: {
      type: [String, Object],
      default: null
    },
    modelValue: {
      type: [String, Object, Number],
      required: false,
      default: function () {
        return {}
      }
    },
    query: {
      type: String,
      required: false,
      default: null
    },
    extraFields: {
      type: Array,
      required: false,
      default: function () {
        return []
      }
    },
    filters: {
      type: Array,
      default: function () {
        return []
      }
    },
    options: {
      type: Array,
      required: false,
      default: function () {
        return []
      }
    },
    idIndex: {
      type: String,
      required: false,
      default: 'id'
    },
    descriptionIndex: {
      type: String,
      required: false,
      default: 'node_description'
    },
    nullDescription: {
      type: String,
      default: ' - '
    }
  },

  setup: function(props, { emit }){

    const definitions = useDefinitions()
    const inputFilter = ref(null)
    const inputQueryFilter = ref([])
    const isRemote = props.query ? true : false
    const filteredOptions = ref(props.options)
    const preloadSelected = []
    const hasFocus = ref(false)
    const activeIndex = ref(null)
    const selectedInputDescription = ref(false)
    const isMouseOver = ref(false)
    const dropdown = ref(null)
    const dropdownPosition = reactive({position: 'fixed', top: '0px', left: '0px', minWidth: '0px', maxHeight: '20em', overflowY: 'auto'})

    const select = (selectedIndex) => {
      // emit the new value and enforce the input to show selected values (clears search)
      let value = null

      if (selectedIndex === -1) {
        value = ''
        selectedInputDescription.value = ''
      } else {
        if (!isRemote && filteredOptions.value[selectedIndex] !== undefined) {
          value = filteredOptions.value[selectedIndex][props.idIndex]
          selectedInputDescription.value = filteredOptions.value[selectedIndex][props.descriptionIndex] + ''
        } else if (isRemote && remoteOptions.value.edges[selectedIndex] !== undefined) {
          value = remoteOptions.value.edges[selectedIndex].node
          selectedInputDescription.value = value[props.descriptionIndex].node_description ? value[props.descriptionIndex].node_description : value[props.descriptionIndex]
        }
      }
      emit('update:modelValue', value)
    }

    const { result, loading, fetchMore } = useQuery(
      function() {
        return isRemote ? Utils.api.list(definitions[props.query], props.extraFields) : null
      },
      () => {
        return {
          order: [],
          filters: [...props.filters, ...inputQueryFilter.value]
        }
      },
      {enabled: isRemote}
    )

    const remoteOptions = useResult(result, {edges: []}, data => data[props.query + '_set'])

    const loadMore = () => {
      fetchMore({
        variables: {
          after: remoteOptions.value.pageInfo.endCursor
        },
        updateQuery: (previousResult, { fetchMoreResult }) => {
          if (previousResult[props.query + '_set'].edges) {
            fetchMoreResult[props.query + '_set'].edges = [...previousResult[props.query + '_set'].edges, ...fetchMoreResult[props.query + '_set'].edges]
          }
          return fetchMoreResult
        }
      })
    }

    const filterResults = function(){
      const filters = []
      if(isRemote) {
        for(const columnName in definitions[props.query].fields){
          const column = definitions[props.query].fields[columnName]
          if (column.type == 'CharField') {
            filters.push({ field: columnName + '__icontains', qs: inputFilter.value })
          }
        }

        inputQueryFilter.value = [{ or_filters: filters }]
      } else{
        filteredOptions.value = props.options.filter(function(item){
          return item[props.descriptionIndex].toLowerCase().includes(inputFilter.value.toLowerCase())
        })
      }
    }

    const handleScroll = (e) => {
      if(remoteOptions.value.pageInfo && remoteOptions.value.pageInfo.hasNextPage ){
        if((e.target.offsetHeight + e.target.scrollTop) >= e.target.scrollHeight) {
          loadMore()
        }
      }
    }

    const updateDropdownPosition = function(){
      //todo: flip when no more space at the bottom
      dropdownPosition.top = dropdown.value.getBoundingClientRect().top+dropdown.value.clientHeight+'px'
      dropdownPosition.left = dropdown.value.getBoundingClientRect().left+'px'
      dropdownPosition.minWidth = dropdown.value.clientWidth+'px'
    }

    const handleDebouncedPosition = debounce(updateDropdownPosition, 10)

    const addEventListeners = () => {
      window.addEventListener('scroll', handleDebouncedPosition)
      window.addEventListener('resize', handleDebouncedPosition)
    }

    const removeEventListeners = () => {
      window.removeEventListener('scroll', handleDebouncedPosition)
      window.removeEventListener('resize', handleDebouncedPosition)
    }

    const focusInput = function(){
      addEventListeners()
      updateDropdownPosition()
      hasFocus.value = true
      selectedInputDescription.value = inputFilter.value
      inputFilter.value = ''
      activeIndex.value = -1
      filterResults()
    }

    const blurInput = function(){
      removeEventListeners()
      hasFocus.value = false

      if(isMouseOver.value) {
        select(activeIndex.value)
      }

      inputFilter.value = selectedInputDescription.value
      selectedInputDescription.value = false
    }

    const selectFromValue = () => {
      if(isRemote){
        inputFilter.value = props.modelValue && props.modelValue[props.descriptionIndex] ? props.modelValue[props.descriptionIndex] : null
      } else {
        props.options.forEach((item) => {
          if(item[props.idIndex] == props.modelValue){
            inputFilter.value = item[props.descriptionIndex]
          }
        })
      }
    }
    //initialise the value
    onMounted(() => {
      selectFromValue()

      onUnmounted(() => removeEventListeners)
    })

    watch(() => props.modelValue, selectFromValue)
    return {remoteOptions, handleScroll, loading, isMouseOver, selectedInputDescription, filterResults, inputFilter, preloadSelected, select, filteredOptions, hasFocus, activeIndex, blurInput, focusInput, dropdown, dropdownPosition}
  },
  emits: ['update:modelValue'],
  computed: {
    allOptions: {
      get: function () {
        return [...this.filteredOptions, ...this.remoteOptions.edges]
      }
    }
  }
}
</script>
