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

	export const button = tv({
		base: 'inline-flex cursor-pointer select-none items-center justify-center overflow-hidden rounded no-underline transition-[background-color,border-color,color] duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] hover:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50',
		variants: {
			variant: {
				default:
					'border border-border bg-surface text-fg hover:bg-surface-hover focus-visible:outline-ring',
				primary:
					'border border-primary-border bg-primary text-primary-fg hover:bg-primary-hover focus-visible:outline-primary',
				ghost: 'bg-transparent text-fg-muted hover:bg-surface-muted focus-visible:outline-ring',
				flat: 'bg-gray-50 text-fg hover:bg-gray-100 focus-visible:outline-ring dark:bg-gray-900 dark:hover:bg-gray-700',
				danger:
					'border border-danger-border bg-danger text-danger-fg hover:bg-danger-hover focus-visible:outline-danger',
				success:
					'border border-success-border bg-success text-success-fg hover:bg-success-hover focus-visible:outline-success'
			},
			size: {
				sm: 'min-h-8 gap-1.5 px-2 py-1.5 text-sm',
				md: 'min-h-9 gap-2 px-4 py-2 text-sm',
				lg: 'min-h-10 gap-2.5 px-6 py-2 text-base'
			}
		},
		compoundVariants: [
			// raised variants draw inset depth via a ::before layer.
			{
				variant: 'default',
				class:
					"relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-raised-depth)] before:transition-all before:duration-150 hover:before:shadow-[inset_0_-2px_0_0_var(--color-raised-depth-hover)] active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-raised-depth)] disabled:active:translate-y-0"
			},
			{
				variant: ['primary', 'danger', 'success'],
				class:
					"relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-raised-accent-depth)] before:transition-all before:duration-150 active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-raised-accent-depth-active)] disabled:active:translate-y-0"
			}
		],
		defaultVariants: {
			variant: 'default',
			size: 'md'
		}
	});

	export type ButtonVariants = VariantProps<typeof button>;
</script>

<script lang="ts">
	import { resolve } from '$app/paths';
	import type { Snippet } from 'svelte';
	import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
	import Spinner from './Spinner.svelte';

	interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, 'class' | 'children'> {
		variant?: ButtonVariants['variant'];
		size?: ButtonVariants['size'];
		href?: string;
		type?: 'button' | 'submit' | 'reset';
		disabled?: boolean;
		class?: string;
		loading?: boolean;
		spinnerClass?: string;
		children?: Snippet;
	}

	let {
		variant = 'default',
		size = 'md',
		href,
		type = 'button',
		disabled = false,
		class: className,
		loading = false,
		spinnerClass,
		children,
		...rest
	}: Props = $props();

	const classes = $derived(button({ variant, size, class: className }));
	const spinnerSize = $derived(spinnerClass ?? (size === 'lg' ? 'size-5' : 'size-4'));
</script>

{#if href}
	<a
		href={resolve(href as '/')}
		class={classes}
		aria-disabled={disabled || loading ? 'true' : undefined}
		tabindex={disabled ? -1 : undefined}
		{...rest}
	>
		{#if loading}
			<Spinner class={spinnerSize} />
		{:else if children}
			{@render children()}
		{/if}
	</a>
{:else}
	<button {type} disabled={disabled || loading} class={classes} aria-busy={loading} {...rest}>
		{#if loading}
			<Spinner class={spinnerSize} />
		{:else if children}
			{@render children()}
		{/if}
	</button>
{/if}
