<!--
    Component: Input
    design-system input. Zero border-radius, Space Grotesk, precise states.
-->
<script lang="ts">
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import XIcon from '@lucide/svelte/icons/x';
import type { Snippet } from 'svelte';
import { cubicOut } from 'svelte/easing';
import type { HTMLInputAttributes } from 'svelte/elements';
import { scale } from 'svelte/transition';
import type {
    InputSize,
    InputVariant,
} from './types';

interface Props extends Omit<HTMLInputAttributes, 'size'> {
    variant?: InputVariant;
    size?: InputSize;
    /** Marks the input as invalid — red border + ring, red helper text. */
    error?: boolean;
    /** Helper / error message rendered below the input. */
    helperText?: string;
    /** Show an animated × button when the input has a value. */
    showClearButton?: boolean;
    /** Called when the clear button is clicked. */
    onclear?: () => void;
    /**
     * Snippet for the left icon slot.
     * Receives `size` as an argument for convenient icon sizing.
     * @example {#snippet leftIcon(size)}<SearchIcon size={inputIconSize[size]} />{/snippet}
     */
    leftIcon?: Snippet<[InputSize]>;
    /**
     * Snippet for the right icon slot (rendered after the clear button).
     * Receives `size` as an argument.
     */
    rightIcon?: Snippet<[InputSize]>;
    fullWidth?: boolean;
    value?: string | number | readonly string[];
    class?: string;
}

let {
    variant = 'default',
    size = 'md',
    error = false,
    helperText,
    showClearButton = false,
    onclear,
    leftIcon,
    rightIcon,
    fullWidth = false,
    value = $bindable(''),
    class: className,
    ...rest
}: Props = $props();

// ── Size config ──────────────────────────────────────────────────────────────
const sizeConfig: Record<InputSize, { input: string; text: string; height: string; clearIcon: number }> = {
    sm: { input: 'px-3 py-1.5', text: 'text-sm', height: 'h-8', clearIcon: 12 },
    md: { input: 'px-4 py-2', text: 'text-base', height: 'h-10', clearIcon: 14 },
    lg: { input: 'px-4 py-3', text: 'text-lg', height: 'h-12', clearIcon: 16 },
    xl: { input: 'px-4 py-3', text: 'text-xl', height: 'h-14', clearIcon: 18 },
};

// ── Variant config ───────────────────────────────────────────────────────────
const variantConfig: Record<InputVariant, { base: string; focus: string; error: string }> = {
    default: {
        base: 'bg-paper dark:bg-paper border border-black/5 dark:border-white/10',
        focus: 'focus:border-brand focus:ring-1 focus:ring-brand/20',
        error: 'border-brand ring-1 ring-brand/20',
    },
    underline: {
        base: 'bg-transparent border-0 border-b border-neutral-300 dark:border-neutral-700',
        focus: 'focus:border-brand',
        error: 'border-brand',
    },
    filled: {
        base: 'bg-surface dark:bg-paper border border-transparent',
        focus: 'focus:border-brand focus:ring-1 focus:ring-brand/20',
        error: 'border-brand ring-1 ring-brand/20',
    },
};

const hasValue = $derived(value !== undefined && value !== '');
const showClear = $derived(showClearButton && hasValue && !!onclear);
const hasRightSlot = $derived(!!rightIcon || showClearButton);
const cfg = $derived(sizeConfig[size]);
const styles = $derived(variantConfig[variant]);

const inputClasses = $derived(cn(
    "font-['Space_Grotesk'] rounded-none outline-none transition-all duration-200",
    'text-neutral-900 dark:text-neutral-100',
    'placeholder:text-neutral-400 dark:placeholder:text-neutral-600',
    'disabled:opacity-50 disabled:cursor-not-allowed',
    cfg.input,
    cfg.text,
    cfg.height,
    styles.base,
    error ? styles.error : styles.focus,
    !!leftIcon && 'pl-10',
    hasRightSlot && 'pr-10',
    fullWidth && 'w-full',
    className,
));
</script>

<div class={cn('flex flex-col gap-1', fullWidth && 'w-full')}>
    <div class={cn('relative group', fullWidth && 'w-full')}>
        <!-- Left icon slot -->
        {#if leftIcon}
            <div class="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400 dark:text-neutral-600 pointer-events-none z-10 flex items-center">
                {@render leftIcon(size)}
            </div>
        {/if}

        <!-- Input -->
        <input class={inputClasses} bind:value {...rest} />

        <!-- Right slot: clear button + rightIcon -->
        {#if hasRightSlot}
            <div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 z-10">
                {#if showClear}
                    <button
                        type="button"
                        tabindex={-1}
                        onclick={onclear}
                        class="text-neutral-400 hover:text-brand transition-colors p-0.5 flex items-center"
                        in:scale={{ duration: 150, start: 0.8, easing: cubicOut }}
                        out:scale={{ duration: 100, start: 0.8, easing: cubicOut }}
                    >
                        <XIcon size={cfg.clearIcon} />
                    </button>
                {/if}

                {#if rightIcon}
                    <div class="text-neutral-400 dark:text-neutral-600 flex items-center">
                        {@render rightIcon(size)}
                    </div>
                {/if}
            </div>
        {/if}
    </div>

    <!-- Helper / error text -->
    {#if helperText}
        <span
            class={cn(
                "text-[0.625rem] font-['Space_Mono'] tracking-wide px-1",
                error ? 'text-brand ' : 'text-neutral-500 dark:text-neutral-400',
            )}
        >
            {helperText}
        </span>
    {/if}
</div>
