<template>
    <div class="control">
        <input type="hidden" :name="name" :value="selectedValue"
            @change="$emit('change', $event)"/>
        <input type="text" ref="input" class="input is-fullwidth" :class="inputClass"
            v-show="!button || !selected"
            v-model="inputValue"
            :placeholder="placeholder"
            @keydown.capture="onKeyDown"
            @keyup="onKeyUp($event); $emit('keyup', $event)"
            @keydown="$emit('keydown', $event)"
            @keypress="$emit('keypress', $event)"
            @focus="onInputFocus" @blur="onBlur" />
        <a v-if="selected && button"
                class="button is-normal is-fullwidth has-text-left is-inline-block overflow-hidden"
                @click="select(-1, false, true)">
            <span class="icon is-small ml-1">
                <i class="fa fa-pen"></i>
            </span>
            <span class="is-inline-block" v-if="selected">
                <slot name="button" :index="selectedIndex" :item="selected"
                    :value-field="valueField" :labelField="labelField">
                {{ selectedLabel }}
                </slot>
            </span>
        </a>
        <div :class="dropdownClass">
            <div class="dropdown-menu is-fullwidth">
                <div class="dropdown-content" style="overflow: hidden">
                    <span v-for="(item, index) in items" :key="item.id"
                        :data-autocomplete-index="index"
                        @click="select(index, false, false)"
                        :class="['dropdown-item', (index == this.cursor) ? 'is-active':'']"
                        tabindex="-1">
                        <slot name="item" :index="index" :item="item" :value-field="valueField"
                            :labelField="labelField">
                        {{ getValue(item, labelField) || item }}
                        </slot>
                    </span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
// import debounce from 'lodash/debounce'
import Model from '../model'


export default {
    emit: ['change', 'keypress', 'keydown', 'keyup', 'select', 'unselect',
           'update:modelValue'],

    props: {
        //! Search URL (where `${query}` is replaced by search term)
        url: String,
        //! Extra GET url parameters
        urlParams: Object,
        //! Items' model
        model: Function,
        //! Input tag class
        inputClass: Array,
        //! input text placeholder
        placeholder: Object,
        //! input form field name
        name: String,
        //! Field on items to use as label
        labelField: String,
        //! Field on selected item to get selectedValue from, if any
        valueField: {type: String, default: null},
        count: {type: Number, count: 10},
        //! If true, show button when value has been selected
        button: Boolean,
        //! If true, value must come from a selection
        mustExist: {type: Boolean, default: false},
        //! Minimum input size before fetching
        minFetchLength: {type: Number, default: 3},
        modelValue: {default: ''},
    },

    data() {
        return {
            inputValue: this.modelValue || '',
            query: '',
            items: [],
            selectedIndex: -1,
            cursor: -1,
            promise: null,
        }
    },

    watch: {
        modelValue(value) {
            this.inputValue = value
        },

        inputValue(value, old) {
            if(value != old && value != this.modelValue) {
                this.$emit('update:modelValue', value)
                this.$emit('change', {target: this.$refs.input})
            }
            if(this.selectedLabel != value)
                this.selectedIndex = -1
        },
    },

    computed: {
        fullUrl() {
            if(!this.urlParams)
               return this.url

            const url = new URL(this.url, window.location.origin)
            const params = new URLSearchParams(url.searchParams)

            for(var key in this.urlParams)
               params.set(key, this.urlParams[key])
            const join = this.url.indexOf("?") >= 0 ? "&" : "?"
            url.search = params.toString()
            return url.href
        },

        isFetching() { return !!this.promise },

        selected() {
            let index = this.selectedIndex
            if(index<0)
                return null
            index = Math.min(index, this.items.length-1)
            return this.items[index]
        },

        selectedValue() {
            let value = this.itemValue(this.selected)
            if(!value && !this.mustExist)
                value = this.inputValue
            return value
        },

        selectedLabel() {
            return this.itemLabel(this.selected)
        },

        dropdownClass() {
            var active = this.cursor > -1 && this.items.length;
            if(active && this.items.length == 1 &&
                    this.itemValue(this.items[0]) == this.inputValue)
                active = false
            return ['dropdown is-fullwidth', active ? 'is-active':'']
        },
    },

    methods: {
        reset() {
            this.inputValue = ""
            this.selectedIndex = -1
            this.items = []
        },

        // TODO: move to utils/data
        getValue(data, path=null) {
            if(!data)
                return null
            if(!path)
                return data

            const paths = path.split('.')
            for(const key of paths) {
                if(key in data)
                    data = data[key]
                else return null;
            }
            return data
        },

        itemValue(item) {
            return this.valueField ? this.getValue(item, this.valueField) : item;
        },

        itemLabel(item) {
            return this.labelField ? this.getValue(item, this.labelField) : item;
        },

        hide() {
            this.cursor = -1;
            this.selectedIndex = -1;
        },

        move(index=-1, relative=false) {
            if(relative)
                index += this.cursor
            this.cursor = Math.max(-1, Math.min(index, this.items.length-1))
        },

        select(index=-1, relative=false, active=null) {
            if(relative)
                index += this.selectedIndex
            else if(index == this.selectedIndex)
                return

            this.selectedIndex = Math.max(-1, Math.min(index, this.items.length-1))
            if(index >= 0) {
                this.inputValue = this.selectedLabel
                this.$refs.input.focus()
            }
            if(this.selectedIndex < 0)
                this.$emit('unselect')
            else
                this.$emit('select', index, this.selected, this.selectedValue)

            if(active!==null)
                active && this.move(0) || this.move(-1)
        },

        onInputFocus() {
            this.cursor < 0 && this.move(0)
        },

        onBlur(event) {
            if(!this.items.length)
                return

            var index = event.relatedTarget && Math.floor(event.relatedTarget.dataset.autocompleteIndex);
            if(index !== undefined && index !== null)
                this.select(index, false, false)
            this.cursor = -1;
        },

        onKeyDown(event) {
            if(event.ctrlKey || event.altKey || event.metaKey)
                return
            switch(event.keyCode) {
                case 13: this.select(this.cursor, false, false)
                         break
                case 27: this.hide(); this.select()
                         break
                case 38: this.move(-1, true)
                         break
                case 40: this.move(1, true)
                         break
                default: return
            }
            event.preventDefault()
            event.stopPropagation()
        },

        onKeyUp(event) {
            if(event.ctrlKey || event.altKey || event.metaKey)
                return

            const value = event.target.value
            if(value === this.query)
                return

            this.inputValue = value;
            if(!value)
                return this.selected && this.select(-1)
            if(!this.minFetchLength || value.length >= this.minFetchLength)
                this.fetch(value)
        },

        fetch(query) {
            if(!query || this.promise)
                return

            this.query = query
            var url = this.fullUrl.replace('${query}', query).replace('%24%7Bquery%7D', query)
            var promise = this.model ? this.model.fetch(url, {many:true})
                                     : fetch(url, Model.getOptions()).then(d => d.json())

            promise = promise.then(items => {
                if(items.results)
                    items = items.results
                this.items = items.filter((i) => i) || []
                this.promise = null;
                this.move(0)
                return items
            }, data => {this.promise = null; Promise.reject(data)})
            this.promise = promise
            return promise
        },
    },

    mounted() {
        const form = this.$el.closest('form')
        form && form.addEventListener('reset', () => {
            this.inputValue = this.value;
            this.select(-1)
        })
    }
}

</script>
