<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue';

const props = defineProps({
    modelValue: {
        type: [String, Number],
        default: '',
    },
    options: {
        type: Array,
        required: true,
    },
    label: {
        type: String,
        default: '',
    },
    id: {
        type: String,
        default: '',
    },
    placeholder: {
        type: String,
        default: 'Sélectionner…',
    },
    variant: {
        type: String,
        default: 'dark',
        validator: (value) => ['dark', 'light'].includes(value),
    },
    bordered: {
        type: Boolean,
        default: false,
    },
});

const emit = defineEmits(['update:modelValue', 'change']);

const root = ref(null);
const isOpen = ref(false);
const activeIndex = ref(-1);

const normalizedValue = computed(() => {
    const value = props.modelValue;

    return value === null || value === undefined ? '' : String(value);
});

const selectedLabel = computed(() => {
    const selected = props.options.find((option) => String(option.value) === normalizedValue.value);

    return selected?.label ?? props.placeholder;
});

const isDark = computed(() => props.variant === 'dark');

const wrapperClass = computed(() => (
    props.bordered
        ? (isDark.value ? 'rounded-xl border border-white/25 p-4' : 'rounded-xl border border-slate-200 p-4')
        : ''
));

const labelClass = computed(() => (
    isDark.value
        ? 'flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-indigo-200'
        : 'flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-slate-500'
));

const triggerClass = computed(() => (
    isDark.value
        ? 'border-white/20 bg-transparent text-white hover:border-white/30 focus:border-white/40 focus:ring-white/15'
        : 'border-slate-200 bg-white text-slate-800 hover:border-slate-300 focus:border-indigo-400 focus:ring-indigo-500/20'
));

const menuClass = computed(() => (
    isDark.value
        ? 'border-white/20 bg-slate-900/95 text-white shadow-xl shadow-black/30 backdrop-blur-md'
        : 'border-slate-200 bg-white text-slate-800 shadow-lg shadow-slate-200/60'
));

const chevronClass = computed(() => (
    isDark.value ? 'text-indigo-200' : 'text-slate-400'
));

const optionClass = (option, index) => {
    const isSelected = String(option.value) === normalizedValue.value;
    const isActive = index === activeIndex.value;

    if (isDark.value) {
        if (isSelected) {
            return 'bg-indigo-500/25 text-white';
        }

        if (isActive) {
            return 'bg-white/10 text-white';
        }

        return 'text-indigo-100 hover:bg-white/10 hover:text-white';
    }

    if (isSelected) {
        return 'bg-indigo-50 text-indigo-700';
    }

    if (isActive) {
        return 'bg-slate-50 text-slate-900';
    }

    return 'text-slate-700 hover:bg-slate-50';
};

const toggle = () => {
    isOpen.value = !isOpen.value;

    if (isOpen.value) {
        const selectedIndex = props.options.findIndex((option) => String(option.value) === normalizedValue.value);
        activeIndex.value = selectedIndex >= 0 ? selectedIndex : 0;
    }
};

const close = () => {
    isOpen.value = false;
    activeIndex.value = -1;
};

const selectOption = (option) => {
    emit('update:modelValue', option.value);
    emit('change', option.value);
    close();
};

const onClickOutside = (event) => {
    if (isOpen.value && root.value && !root.value.contains(event.target)) {
        close();
    }
};

const onKeydown = (event) => {
    if (!isOpen.value) {
        if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {
            event.preventDefault();
            isOpen.value = true;
            activeIndex.value = Math.max(
                0,
                props.options.findIndex((option) => String(option.value) === normalizedValue.value),
            );
        }

        return;
    }

    if (event.key === 'Escape') {
        event.preventDefault();
        close();
        return;
    }

    if (event.key === 'ArrowDown') {
        event.preventDefault();
        activeIndex.value = Math.min(activeIndex.value + 1, props.options.length - 1);
        return;
    }

    if (event.key === 'ArrowUp') {
        event.preventDefault();
        activeIndex.value = Math.max(activeIndex.value - 1, 0);
        return;
    }

    if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();

        if (props.options[activeIndex.value]) {
            selectOption(props.options[activeIndex.value]);
        }
    }
};

onMounted(() => {
    document.addEventListener('click', onClickOutside);
});

onUnmounted(() => {
    document.removeEventListener('click', onClickOutside);
});
</script>

<template>
    <div ref="root" :class="wrapperClass">
        <label
            v-if="label"
            :for="id"
            :class="labelClass"
        >
            <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                <path stroke-linecap="round" stroke-linejoin="round" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
            </svg>
            {{ label }}
        </label>

        <div class="relative" :class="label ? 'mt-2' : ''">
            <button
                :id="id"
                type="button"
                class="flex w-full items-center justify-between gap-3 rounded-lg border py-2.5 pl-3 pr-3 text-left text-sm font-medium transition focus:outline-none focus:ring-2"
                :class="triggerClass"
                :aria-expanded="isOpen"
                aria-haspopup="listbox"
                @click.stop="toggle"
                @keydown="onKeydown"
            >
                <span class="truncate">{{ selectedLabel }}</span>
                <svg
                    class="h-4 w-4 shrink-0 transition-transform duration-200"
                    :class="[chevronClass, isOpen ? 'rotate-180' : '']"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke="currentColor"
                    stroke-width="2"
                >
                    <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
                </svg>
            </button>

            <Transition
                enter-active-class="transition ease-out duration-150"
                enter-from-class="opacity-0 -translate-y-1 scale-[0.98]"
                enter-to-class="opacity-100 translate-y-0 scale-100"
                leave-active-class="transition ease-in duration-100"
                leave-from-class="opacity-100 translate-y-0 scale-100"
                leave-to-class="opacity-0 -translate-y-1 scale-[0.98]"
            >
                <ul
                    v-show="isOpen"
                    class="absolute z-[100] mt-2 max-h-60 w-full overflow-auto rounded-xl border p-1.5"
                    :class="menuClass"
                    role="listbox"
                    :aria-labelledby="id"
                >
                    <li
                        v-for="(option, index) in options"
                        :key="`${option.value}-${index}`"
                        role="option"
                        :aria-selected="String(option.value) === normalizedValue"
                        class="flex cursor-pointer items-center justify-between gap-2 rounded-lg px-3 py-2.5 text-sm transition"
                        :class="optionClass(option, index)"
                        @click.stop="selectOption(option)"
                        @mouseenter="activeIndex = index"
                    >
                        <span class="truncate">{{ option.label }}</span>
                        <svg
                            v-if="String(option.value) === normalizedValue"
                            class="h-4 w-4 shrink-0"
                            :class="isDark ? 'text-indigo-300' : 'text-indigo-500'"
                            fill="none"
                            viewBox="0 0 24 24"
                            stroke="currentColor"
                            stroke-width="2.5"
                        >
                            <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
                        </svg>
                    </li>
                </ul>
            </Transition>
        </div>
    </div>
</template>
