<script module lang="ts">
	import { tv, type VariantProps } from "tailwind-variants";

	export const inputField = tv({
		base: "flex items-center rounded-sm border bg-background-default transition-colors",
		variants: {
			// same 32/36/40 heights as Button's sm/md/lg, so an input and a button of the same
			// size line up in a row. line-height + py + border stays under each min-h, so the
			// field sits exactly on that height whatever it holds. horizontal padding runs one
			// step tighter than Button's — a button centres its label and needs the room, a
			// field's text is left-aligned against the border.
			size: {
				sm: "min-h-8 gap-1.5 px-1.5 py-1 typography-paragraph-small",
				md: "min-h-9 gap-2 px-2 py-1 typography-paragraph-regular",
				lg: "min-h-10 gap-2.5 px-3 py-1 typography-paragraph-large"
			},
			error: {
				false:
					"border-border-default focus-within:border-border-strong focus-within:ring-1 focus-within:ring-border-strong",
				true: "border-border-danger bg-background-danger-subtle text-foreground-danger focus-within:border-border-focus-danger focus-within:ring-1 focus-within:ring-border-focus-danger"
			},
			disabled: {
				false: "",
				true: "cursor-not-allowed bg-background-inset text-foreground-disabled"
			}
		},
		defaultVariants: {
			size: "md",
			error: false,
			disabled: false
		}
	});

	export type InputFieldVariants = VariantProps<typeof inputField>;
</script>

<script lang="ts">
	import type { Component } from "svelte";
	import type { HTMLInputAttributes, SvelteHTMLElements } from "svelte/elements";
	import Spinner from "./Spinner.svelte";

	// `size` shadows the native character-width attribute, which nothing here uses and which
	// no design would reach for over a width class
	interface Props extends Omit<HTMLInputAttributes, "class" | "value" | "size"> {
		value?: string;
		size?: InputFieldVariants["size"];
		error?: boolean;
		disabled?: boolean;
		loading?: boolean;
		iconLeft?: Component<SvelteHTMLElements["svg"]>;
		iconRight?: Component<SvelteHTMLElements["svg"]>;
		/** static, non-editable trailing text shown inside the field, e.g. a domain suffix */
		suffix?: string;
		/** the input itself, for callers that have to focus or measure it */
		element?: HTMLInputElement;
		class?: string;
	}

	let {
		value = $bindable(""),
		size = "md",
		error = false,
		disabled = false,
		loading = false,
		iconLeft,
		iconRight,
		suffix,
		element = $bindable(),
		class: className,
		...rest
	}: Props = $props();

	const classes = $derived(inputField({ size, error, disabled, class: className }));
	// matches Button: the icon only steps up on lg
	const iconSize = $derived(size === "lg" ? "size-5" : "size-4");
</script>

<div class={classes}>
	{#if iconLeft}
		{@const IconLeft = iconLeft}
		<IconLeft class="{iconSize} shrink-0 text-foreground-subtle" aria-hidden="true" />
	{/if}
	<input
		bind:this={element}
		bind:value
		{disabled}
		aria-invalid={error}
		aria-busy={loading}
		class="mt-px min-w-0 flex-1 bg-transparent outline-none placeholder:text-foreground-placeholder disabled:cursor-not-allowed"
		{...rest}
	/>
	{#if suffix}
		<span class="shrink-0 text-foreground-muted select-none">{suffix}</span>
	{/if}
	{#if loading}
		<Spinner class="{iconSize} shrink-0" />
	{:else if iconRight}
		{@const IconRight = iconRight}
		<IconRight class="{iconSize} shrink-0 text-foreground-subtle" aria-hidden="true" />
	{/if}
</div>
