<template>
    <form
        ref="searchForm"
        class="tb-search-form"
        method="GET"
        @submit="handleFormSubmit($event)"
    >
        <fieldset
            class="tb-search-fieldset"
        >
            <label
                for="tb-search-input"
                class="sr-only"
                id="tb-search-label"
            >
                {{ searchInputLabel }}
            </label>
            <input
                ref="searchInput"
                aria-labelledby="tb-search-label"
                id="tb-search-input"
                type="search"
                name="search"
                v-model="searchTerm"
                class="tb-search-form__input"
                :class="{
                    'tb-search-form__input--suggestions': suggestions.length && isOpen,
                }"
                autocomplete="off"
                :placeholder="settings.placeholder"
                role="combobox"
                aria-autocomplete="list"
                aria-controls="search-suggestions"
                :aria-expanded="(!!(suggestions.length && isOpen)).toString()"
                :aria-activedescendant="isOpen && suggestions.length
                    ? `tb-search-suggestion-${arrowCounter}`
                    : null"
                @input="onChange"
                @keyup.down="onArrowDown"
                @keyup.up="onArrowUp"
                @keyup.enter="onEnter"
            >

            <ul
                v-if="suggestions.length && isOpen"
                class="tb-search-suggestions"
                id="search-suggestions"
                role="listbox"
            >
                <li
                    v-for="(suggestion, i) of suggestions"
                    :key="i"
                    :id="`tb-search-suggestion-${i}`"
                    class="tb-search-suggestions__item"
                    :class="{ 'is-active': i === arrowCounter }"
                    role="option"
                    :aria-selected="i === arrowCounter"
                    @click="setSearchTerm(suggestion)"
                >
                    {{ suggestion }}
                </li>
            </ul>
        </fieldset>

        <button
            ref="submitButton"
            class="c-button tb-search-form__submit-button"
            type="submit"
        >
            {{ submitButtonText }}
        </button>
    </form>
</template>
<script>
import debounce from 'lodash.debounce';

const baseUri = '/newland-search/suggest';

export default {
    props: {
        settings: { type: Object, required: true },
    },
    /**
     * isOpen handles the current state of the suggestions-popup
     * arrowCounter handles the current state of the user keyboard input (which position the user is at)
     */
    data: () => ({
        searchTerm: '',
        submitButtonText: '',
        searchInputLabel: '',
        suggestions: [],
        isOpen: false,
        arrowCounter: -1,
    }),
    async created() {
        this.searchTerm = this.settings.searchTerm || '';
        this.submitButtonText = this.settings.submitText || '';
        this.searchInputLabel = this.settings.searchInputLabel || 'Suchen';
    },
    mounted() {
        document.addEventListener('click', this.handleClickOutside);
    },
    methods: {
        async fetchSuggestions(searchTerm) {
            const data = {
                searchTerm: searchTerm,
                language: this.settings.language,
            };

            const response = await fetch(baseUri, {
                method: 'POST',
                cache: 'no-cache',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data),
            });

            return response.json(); // parses JSON response into native JavaScript objects
        },

        /**
         * Set the searchTerm and directly submit the form afterwards
         * triggered when clicking on a suggestion, or hitting enter
         * after navigation on the suggestion with the keyboard
         */
        setSearchTerm(searchTerm) {
            this.searchTerm = searchTerm;
            this.isOpen = false;
            this.arrowCounter = -1;

            // Workaround - searchTerm was not properly set without the small timeout
            setTimeout(() => {
                this.submitForm();
            }, 50);
        },

        onChange() {
            // If there are less than 2 letters in the input, do nothing
            if (this.$refs.searchInput.value.length < 2) {
                return;
            }

            this.getNewSuggestions();
        },

        getNewSuggestions: debounce(async function() {
            this.suggestions = await this.fetchSuggestions(this.searchTerm);
            this.isOpen = true;
            this.arrowCounter = -1;
        }, 200),

        onArrowDown() {
            if (this.isOpen
                && this.arrowCounter < this.suggestionsLength - 1
            ) {
                this.arrowCounter = this.arrowCounter + 1;
            }
        },
        onArrowUp() {
            if (this.isOpen && this.arrowCounter > 0) {
                this.arrowCounter = this.arrowCounter - 1;
            }
        },
        onEnter() {
            if (this.isOpen && this.arrowCounter !== -1) {
                this.setSearchTerm(this.suggestions[ this.arrowCounter ]);
            }
        },
        handleClickOutside(event) {
            if (!this.$el.contains(event.target)) {
                this.isOpen = false;
                this.arrowCounter = -1;
            }
        },

        submitForm() {
            this.$refs.searchForm.submit();
        },

        /**
         * Prevent the form from automatically sending when the user
         * is navigating the suggestions via keyboard, and selects via enter
         */
        handleFormSubmit(event) {
            if (this.isOpen && this.arrowCounter !== -1) {
                event.preventDefault();
            }
        },
    },
    computed: {
        suggestionsLength() {
            return this.suggestions.length;
        },
    },
};
</script>
