<script lang="ts">
    import { onMount, onDestroy, getContext } from "svelte"
    import type { FormEventHandler, HTMLSelectAttributes } from "svelte/elements"
    import type { Snippet } from "svelte"
    import { FORM_CONTEXT, type FormContext, type ValidatableField } from "../lib/formContext"

    let {
        value = $bindable(""),
        options = [] as { value: string; label: string }[],
        name = "",
        required = false,
        id = "",
        label = "",
        hideLabel = true,
        variant = "default",
        error = $bindable(""),
        disabled = false,
        messages = {},
        validator,
        onchange = undefined as FormEventHandler<HTMLSelectElement> | undefined,
        children,
        ...rest
    }: {
        value?: string
        options?: { value: string; label: string }[]
        name?: string
        required?: boolean
        id?: string
        label?: string
        hideLabel?: boolean
        variant?: "default" | "integrated"
        error?: string
        disabled?: boolean
        messages?: Partial<Record<keyof ValidityState, string>>
        onchange?: FormEventHandler<HTMLSelectElement>
        children?: Snippet
        validator?: (value: string) => Promise<string | null | undefined> | string | null | undefined
    } & HTMLSelectAttributes = $props()

    const fallbackId = `select-${Math.random().toString(36).substr(2, 9)}`
    const selectId = $derived(id || fallbackId)
    const errorId = $derived(`${selectId}-error`)

    let selectElement = $state<HTMLSelectElement>()
    const formContext = getContext<FormContext>(FORM_CONTEXT)

    export const validate = async () => {
        if (!selectElement) return true

        selectElement.setCustomValidity("")

        if (selectElement.checkValidity()) {
            if (validator) {
                const customError = await validator(value)
                if (customError) {
                    error = customError
                    return false
                }
            }
            error = ""
            return true
        }

        const validity = selectElement.validity
        let errorMessage = selectElement.validationMessage

        if (validity.valueMissing && messages.valueMissing) errorMessage = messages.valueMissing
        else if (validity.customError && messages.customError) errorMessage = messages.customError

        error = errorMessage
        return false
    }

    const reset = () => {
        error = ""
    }

    const focus = () => {
        selectElement?.focus()
    }

    const handleChange: FormEventHandler<HTMLSelectElement> = (e) => {
        onchange?.(e)
        validate()
    }

    const field: ValidatableField = { validate, reset, focus }

    onMount(() => {
        if (formContext) {
            formContext.register(field)
        }
    })

    onDestroy(() => {
        if (formContext) {
            formContext.unregister(field)
        }
    })
</script>

<div class="w-full">
    {#if variant === "integrated"}
        <div
            class="relative w-full border rounded-lg px-3 py-2 h-16.5 transition-colors group focus-within:ring-1 focus-within:ring-blue-600 {disabled
                ? 'bg-gray-50'
                : 'bg-white'} {error
                ? 'border-red-500'
                : 'border-gray-300 hover:border-blue-600 focus-within:border-blue-600'}"
        >
            {#if label}
                <label
                    for={selectId}
                    class="block text-xs font-bold uppercase mb-1 {error ? 'text-red-500' : 'text-gray-500'}"
                >
                    {label}
                    {#if required}<span class="text-red-600">*</span>{/if}
                </label>
            {/if}
            <select
                bind:this={selectElement}
                id={selectId}
                {name}
                bind:value
                {required}
                {disabled}
                aria-invalid={!!error}
                aria-describedby={error ? errorId : undefined}
                class="w-full bg-transparent text-base text-gray-900 focus:outline-none appearance-none cursor-pointer"
                onchange={handleChange}
                {...rest}
            >
                {#if options.length > 0}
                    {#each options as option}
                        <option value={option.value}>{option.label}</option>
                    {/each}
                {:else if children}
                    {@render children()}
                {/if}
            </select>
            <div class="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none">
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    class="h-5 w-5 text-gray-400 group-hover:text-blue-600"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke="currentColor"
                >
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
                </svg>
            </div>
        </div>
    {:else}
        {#if label}
            <label
                for={selectId}
                class="block text-base mb-1 {hideLabel ? 'sr-only' : ''} {error ? 'text-red-600' : 'text-gray-900'}"
            >
                {label}
                {#if required}<span class="text-red-600">*</span>{/if}
            </label>
        {/if}

        <div class="relative w-full">
            <select
                bind:this={selectElement}
                id={selectId}
                {name}
                bind:value
                {required}
                {disabled}
                aria-invalid={!!error}
                aria-describedby={error ? errorId : undefined}
                class="w-full border rounded-lg px-4 py-3 bg-white text-base appearance-none cursor-pointer transition-colors focus:outline-none focus:ring-1 focus:ring-blue-600 {error
                    ? 'border-red-500 focus:border-red-500'
                    : 'border-gray-300 hover:border-blue-600 focus:border-blue-600'} {disabled
                    ? 'bg-gray-50'
                    : 'bg-white'}"
                onchange={handleChange}
                {...rest}
            >
                {#if options.length > 0}
                    {#each options as option}
                        <option value={option.value}>{option.label}</option>
                    {/each}
                {:else if children}
                    {@render children()}
                {/if}
            </select>
            <div class="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none">
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    class="h-5 w-5 {error ? 'text-red-500' : 'text-gray-400'} group-hover:text-blue-600"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke="currentColor"
                >
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
                </svg>
            </div>
        </div>
    {/if}

    <div class="min-h-5.5 mt-1">
        {#if error}
            <p id={errorId} class="text-sm text-red-600">{error}</p>
        {/if}
    </div>
</div>
