<script setup lang="ts">
defineOptions({
    inheritAttrs: false,
});

const props = defineProps<{
    options: Record<string, string>;
    freeform?: boolean;
}>();

const modelValue = defineModel<string | null>({ required: true });

const filter = ref((modelValue.value !== null ? props.options[modelValue.value] : null) ?? modelValue.value);
const shown = ref(false);
const highlighted = ref(-1);

const visibleOptions = computed((): Record<string, string> => {
    return Object.fromEntries(Object.entries(props.options).filter(([option, display]) => {
        return !filter.value ||
            option.toLowerCase().includes(filter.value.toLowerCase()) ||
            display.toLowerCase().includes(filter.value.toLowerCase());
    }));
});

const highlightedOption = computed(() => {
    return Object.keys(visibleOptions.value)[highlighted.value];
});

watch(modelValue, () => {
    filter.value = (modelValue.value !== null ? props.options[modelValue.value] : null) ?? modelValue.value;
    highlighted.value = -1;
});

watch(filter, () => {
    if (props.freeform) {
        modelValue.value = filter.value;
    }
});

const select = (option: string) => {
    modelValue.value = option;
    hide();
    highlighted.value = -1;
};
const show = () => {
    shown.value = true;
};
const hide = () => {
    setTimeout(() => {
        shown.value = false;
    }, 100);
};
const filterKeydown = (event: KeyboardEvent) => {
    if (!shown.value) {
        show();
    }

    switch (event.key) {
        case 'ArrowUp':
            highlighted.value--;
            event.preventDefault();
            event.stopPropagation();
            break;
        case 'ArrowDown':
            highlighted.value++;
            event.preventDefault();
            event.stopPropagation();
            break;
        case 'Enter':
            if (highlightedOption.value) {
                select(highlightedOption.value);
                event.preventDefault();
                event.stopPropagation();
            }
            break;
        default:
            break;
    }
};
</script>

<template>
    <div class="select flex-grow-1">
        <input
            v-model="filter"
            type="text"
            class="form-control"
            autocomplete="off"
            v-bind="$attrs"
            @focus="show"
            @blur="hide"
            @keydown="filterKeydown"
        >
        <div v-show="shown" class="list-group shadow">
            <button
                v-for="(display, option) in visibleOptions"
                :key="option"
                type="button"
                :class="['list-group-item', 'list-group-item-action', highlightedOption === option ? 'active' : '']"
                @click.prevent="select(option)"
            >
                {{ display }}
            </button>
        </div>
    </div>
</template>

<style lang="scss" scoped>
.select {
    position: relative;
    .list-group {
        position: absolute;
        top: 100%;
        max-height: 300px;
        width: min(300px, 100%);
        overflow-y: auto;
        z-index: 999;
    }
}
</style>
