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

    type InputValue = string | number | null | undefined

    let {
        placeholder = "",
        value = $bindable<InputValue>(),
        type = "text",
        name = "",
        required = false,
        id = "",
        label = "",
        hideLabel = true,
        variant = "default",
        error = $bindable(""),
        hint = "",
        readonly = false,
        disabled = false,
        rightIcon,
        messages = {},
        validator,
        oninput = undefined as FormEventHandler<HTMLInputElement> | undefined,
        onchange = undefined as FormEventHandler<HTMLInputElement> | undefined,
        onblur = undefined as FormEventHandler<HTMLInputElement> | undefined,
        ...rest
    }: {
        placeholder?: string
        value?: InputValue
        type?: string
        name?: string
        required?: boolean
        id?: string
        label?: string
        hideLabel?: boolean
        variant?: "default" | "integrated"
        error?: string
        hint?: string
        readonly?: boolean
        disabled?: boolean
        rightIcon?: Snippet
        messages?: Partial<Record<keyof ValidityState, string>>
        oninput?: FormEventHandler<HTMLInputElement>
        onchange?: FormEventHandler<HTMLInputElement>
        onblur?: FormEventHandler<HTMLInputElement>
        validator?: (value: string) => Promise<string | null | undefined> | string | null | undefined
    } & HTMLInputAttributes = $props()

    // Generate unique ID if not provided
    const fallbackId = `input-${Math.random().toString(36).substr(2, 9)}`
    const inputId = $derived(id || fallbackId)
    const errorId = $derived(`${inputId}-error`)

    let inputElement = $state<HTMLInputElement>()
    const formContext = getContext<FormContext>(FORM_CONTEXT)
    let isValidating = $state(false)
    let validationRunId = 0

    let showPassword = $state(false)
    const isPasswordType = $derived(type === "password")
    const effectiveType = $derived(isPasswordType && showPassword ? "text" : type)

    const togglePassword = () => {
        showPassword = !showPassword
        inputElement?.focus()
    }

    const getDefaultValue = (inputType: string) => {
        const normalizedType = inputType?.toLowerCase?.() || "text"
        if (normalizedType === "number" || normalizedType === "range") {
            return 0
        }
        return ""
    }

    $effect(() => {
        if (value === null || value === undefined) {
            value = getDefaultValue(type)
        }
    })

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

        // Reset custom validity to check native validity correctly
        inputElement.setCustomValidity("")

        const runId = ++validationRunId

        if (inputElement.checkValidity()) {
            if (validator) {
                const valueForValidation = value === null || value === undefined ? "" : String(value)
                const result = validator(valueForValidation)
                const isAsync = !!result && typeof (result as Promise<string | null | undefined>).then === "function"

                if (isAsync) {
                    isValidating = true
                    let customError: string | null | undefined

                    try {
                        customError = await result
                    } finally {
                        if (runId === validationRunId) {
                            isValidating = false
                        }
                    }

                    if (runId !== validationRunId) return false
                    if (customError) {
                        error = customError
                        return false
                    }
                } else {
                    const customError = result as string | null | undefined
                    if (customError) {
                        error = customError
                        return false
                    }
                }
            }
            error = ""
            return true
        }

        const validity = inputElement.validity
        let errorMessage = inputElement.validationMessage

        if (validity.valueMissing && messages.valueMissing) errorMessage = messages.valueMissing
        else if (validity.typeMismatch && messages.typeMismatch) errorMessage = messages.typeMismatch
        else if (validity.patternMismatch && messages.patternMismatch) errorMessage = messages.patternMismatch
        else if (validity.tooLong && messages.tooLong) errorMessage = messages.tooLong
        else if (validity.tooShort && messages.tooShort) errorMessage = messages.tooShort
        else if (validity.rangeUnderflow && messages.rangeUnderflow) errorMessage = messages.rangeUnderflow
        else if (validity.rangeOverflow && messages.rangeOverflow) errorMessage = messages.rangeOverflow
        else if (validity.stepMismatch && messages.stepMismatch) errorMessage = messages.stepMismatch
        else if (validity.badInput && messages.badInput) errorMessage = messages.badInput

        error = errorMessage
        return false
    }

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

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

    const handleBlur: FormEventHandler<HTMLInputElement> = (e) => {
        validate()
        onblur?.(e)
    }

    let debounceTimer: ReturnType<typeof setTimeout> | undefined

    const handleInput: FormEventHandler<HTMLInputElement> = (e) => {
        oninput?.(e)

        // Only validate on input if we already have an error to clear
        if (error) {
            // Immediate check for native validity
            inputElement!.setCustomValidity("")
            const isNativelyValid = inputElement!.checkValidity()

            if (!isNativelyValid) {
                // If natively invalid (e.g. required missing), validate immediately to show correct error
                validate()
            } else if (validator) {
                // If natively valid but we have a custom async validator, debounce it
                clearTimeout(debounceTimer)
                debounceTimer = setTimeout(() => {
                    validate()
                }, 300)
            } else {
                // If natively valid and no custom validator, validate immediately (clears error)
                validate()
            }
        }
    }

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

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

    onDestroy(() => {
        clearTimeout(debounceTimer)
        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 {readonly ||
            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={inputId}
                    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}
            <input
                bind:this={inputElement}
                id={inputId}
                type={effectiveType}
                {name}
                {placeholder}
                {required}
                {readonly}
                {disabled}
                bind:value
                aria-label={label || placeholder}
                aria-required={required}
                aria-invalid={!!error}
                aria-describedby={error ? errorId : undefined}
                class="w-full bg-transparent text-base text-gray-900 focus:outline-none placeholder:text-gray-400 {isPasswordType
                    ? 'pr-12'
                    : rightIcon || isValidating
                      ? 'pr-24'
                      : ''}"
                oninput={handleInput}
                onblur={handleBlur}
                {onchange}
                {...rest}
            />
            {#if isPasswordType}
                <button
                    type="button"
                    class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none transition-colors"
                    onclick={togglePassword}
                    aria-label={showPassword ? "Hide password" : "Show password"}
                    tabindex={-1}
                >
                    {#if showPassword}
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            class="w-5 h-5"
                            ><path
                                d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"
                            ></path><line x1="1" y1="1" x2="23" y2="23"></line></svg
                        >
                    {:else}
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            class="w-5 h-5"
                            ><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"
                            ></circle></svg
                        >
                    {/if}
                </button>
            {:else if isValidating || rightIcon}
                <div class="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none">
                    {#if isValidating}
                        <span class="inline-flex items-center gap-2 text-xs text-gray-500">
                            <span
                                class="inline-block h-3 w-3 rounded-full border border-gray-400 border-t-transparent animate-spin"
                            ></span>
                            Checking…
                        </span>
                    {:else if rightIcon}
                        {@render rightIcon()}
                    {/if}
                </div>
            {/if}
        </div>
    {:else}
        {#if label}
            <label
                for={inputId}
                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">
            <input
                bind:this={inputElement}
                id={inputId}
                type={effectiveType}
                {name}
                {placeholder}
                {required}
                {readonly}
                {disabled}
                bind:value
                aria-label={label || placeholder}
                aria-required={required}
                aria-invalid={!!error}
                aria-describedby={error ? errorId : undefined}
                class="w-full border rounded-lg px-4 py-3 bg-white text-base transition-colors focus:outline-none focus:ring-1 focus:ring-blue-600 placeholder:text-gray-400 {error
                    ? 'border-red-500 focus:border-red-500'
                    : 'border-gray-300 hover:border-blue-600 focus:border-blue-600'} {readonly || disabled
                    ? 'bg-gray-50'
                    : 'bg-white'} {isPasswordType ? 'pr-12' : rightIcon || isValidating ? 'pr-24' : ''}"
                oninput={handleInput}
                onblur={handleBlur}
                {onchange}
                {...rest}
            />
            {#if isPasswordType}
                <button
                    type="button"
                    class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none transition-colors"
                    onclick={togglePassword}
                    aria-label={showPassword ? "Hide password" : "Show password"}
                    tabindex={-1}
                >
                    {#if showPassword}
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            class="w-5 h-5"
                            ><path
                                d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"
                            ></path><line x1="1" y1="1" x2="23" y2="23"></line></svg
                        >
                    {:else}
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            class="w-5 h-5"
                            ><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"
                            ></circle></svg
                        >
                    {/if}
                </button>
            {:else if isValidating || rightIcon}
                <div class="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none">
                    {#if isValidating}
                        <span class="inline-flex items-center gap-2 text-xs text-gray-500">
                            <span
                                class="inline-block h-3 w-3 rounded-full border border-gray-400 border-t-transparent animate-spin"
                            ></span>
                            Checking…
                        </span>
                    {:else if rightIcon}
                        {@render rightIcon()}
                    {/if}
                </div>
            {/if}
        </div>
    {/if}

    {#if hint}
        <p class="mt-0.5 text-xs text-gray-500">{hint}</p>
    {/if}
    <div class="min-h-3 mt-0">
        {#if error}
            <p id={errorId} class="text-sm text-red-600">{error}</p>
        {/if}
    </div>
</div>
