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

	export const button = tv({
		base: [
			"inline-flex cursor-pointer items-center justify-center overflow-hidden rounded-l-(--btn-radius-l) rounded-r-(--btn-radius-r) no-underline select-none [--btn-radius-l:var(--btn-radius)] [--btn-radius-r:var(--btn-radius)] [--btn-radius:var(--radius-sm)]",
			"transition-colors duration-150 ease-in-out",
			"hover:no-underline",
			"focus-visible:outline-2 focus-visible:outline-offset-2",
			"disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50"
		],
		variants: {
			variant: {
				default: [
					"border border-border-default bg-background-default text-foreground-default",
					"hover:bg-background-subtle",
					"focus-visible:outline-border-focus"
				],
				primary: [
					"border border-border-success bg-background-success-emphasis text-foreground-on-emphasis",
					"hover:bg-background-success-emphasis-hover",
					"focus-visible:outline-background-success-emphasis"
				],
				ghost: [
					"border border-transparent bg-transparent text-foreground-muted",
					"hover:bg-background-inset",
					"focus-visible:outline-border-focus"
				],
				// danger / success / warning are outline buttons in Figma: default surface +
				// border, colored text only (bound to the fg-* tokens), no accent fill.
				danger: [
					"border border-border-default bg-background-default text-foreground-danger",
					"hover:bg-background-subtle",
					"focus-visible:outline-foreground-danger"
				],
				success: [
					"border border-border-default bg-background-default text-foreground-success",
					"hover:bg-background-subtle",
					"focus-visible:outline-foreground-success"
				],
				warning: [
					"border border-border-default bg-background-default text-foreground-warning",
					"hover:bg-background-subtle",
					"focus-visible:outline-foreground-warning"
				]
			},
			size: {
				sm: "min-h-8 gap-1.5 px-2 py-1.5 text-sm",
				md: "min-h-9 gap-2 px-3 py-1.5 text-sm",
				lg: "min-h-10 gap-2.5 px-4 py-2 text-base"
			},
			insetShadow: {
				true: "",
				false: ""
			}
		},
		compoundVariants: [
			// raised variants draw inset depth via a ::before layer.
			{
				variant: "default",
				insetShadow: true,
				class: [
					"relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-l-[max(calc(var(--btn-radius-l)-2px),0px)] before:rounded-r-[max(calc(var(--btn-radius-r)-2px),0px)] before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-subtle)] before:transition-all before:duration-150 before:content-['']",
					"hover:before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-strong-hover)]",
					"active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-shadow-inner-subtle)]",
					"disabled:active:translate-y-0"
				]
			},
			{
				variant: "primary",
				insetShadow: true,
				class: [
					"relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-l-[max(calc(var(--btn-radius-l)-2px),0px)] before:rounded-r-[max(calc(var(--btn-radius-r)-2px),0px)] before:shadow-[inset_0_-2px_0_0_var(--color-alpha-dark-25)] before:transition-all before:duration-150 before:content-['']",
					"active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-alpha-dark-50)]",
					"disabled:active:translate-y-0"
				]
			}
		],
		defaultVariants: {
			variant: "default",
			size: "md",
			insetShadow: false
		}
	});

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

<script lang="ts">
	import type { ResolvedPathname } from "$app/types";
	import type { Component, Snippet } from "svelte";
	import type {
		HTMLAnchorAttributes,
		HTMLButtonAttributes,
		SvelteHTMLElements
	} from "svelte/elements";
	import Spinner from "./Spinner.svelte";

	interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, "class" | "children"> {
		variant?: ButtonVariants["variant"];
		size?: ButtonVariants["size"];
		insetShadow?: boolean;
		href?: ResolvedPathname;
		type?: "button" | "submit" | "reset";
		disabled?: boolean;
		icon?: Component<SvelteHTMLElements["svg"]>;
		iconSide?: string;
		class?: string;
		loading?: boolean;
		spinnerClass?: string;
		children?: Snippet;
	}

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

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

{#snippet content()}
	{#if loading}
		<Spinner class={spinnerSize} />
	{:else}
		{#if Icon && iconSide == "left"}
			<Icon class="{iconSize} shrink-0" aria-hidden="true" />
		{/if}
		{#if children}
			{@render children()}
		{/if}
		{#if Icon && iconSide == "right"}
			<Icon class="{iconSize} shrink-0" aria-hidden="true" />
		{/if}
	{/if}
{/snippet}

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