<script lang="ts">
	import type { Snippet } from 'svelte';
	import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements';
	import Spinner from './Spinner.svelte';
	import { defaultIconProps, type IconDef } from './util';

	interface Props extends Omit<HTMLButtonAttributes, 'class'> {
		icon?: IconDef;
		animate?: boolean;
		loading?: boolean;
		class?: ClassValue | null | undefined;
		ref?: HTMLButtonElement | null;
		children: Snippet;
		onclick?: MouseEventHandler<HTMLButtonElement>;
	}

	let {
		type = 'button',
		icon,
		animate = true,
		loading,
		disabled,
		class: classValue,
		ref = $bindable<HTMLButtonElement | null>(null),
		children,
		onclick,
		...others
	}: Props = $props();

	type SVGInHTML = HTMLElement & SVGElement;
	let iconElement = $state<SVGInHTML | null>(null);
	$effect(() => {
		if (icon && ref) {
			iconElement = ref.querySelector('svg') as SVGInHTML | null;
		}
	});

	const handleButtonClick: MouseEventHandler<HTMLButtonElement> = (event) => {
		if (animate && !loading && !disabled) {
			animateLoop();
			animateRipple(event);
		}

		if (loading || disabled) return;

		onclick?.(event);
	};

	const triggerAnimation = (className: string) => {
		if (icon && iconElement) {
			iconElement.classList.remove(className);

			// make sure we have DOM reflow
			requestAnimationFrame(() => {
				iconElement?.classList.add(className);
			});
		}
	};

	export const animateLoop = () => triggerAnimation('animate');
	export const animateBounce = () => triggerAnimation('bounce');
	export const animateRipple: MouseEventHandler<HTMLButtonElement> = (event) => {
		const button = event.currentTarget;
		const circle = document.createElement('span');
		const diameter = Math.max(button.clientWidth, button.clientHeight);
		const radius = diameter / 2;

		const rect = button.getBoundingClientRect();
		const x = event.clientX - rect.left - radius;
		const y = event.clientY - rect.top - radius;

		circle.style.width = circle.style.height = `${diameter}px`;
		circle.style.left = `${x}px`;
		circle.style.top = `${y}px`;
		circle.classList.add('ripple');

		const ripples = button.getElementsByClassName('ripple');
		for (let i = 0; i < ripples.length; i++) {
			ripples[i].remove();
		}

		button.appendChild(circle);
	};
</script>

<button
	{type}
	bind:this={ref}
	class={[
		'button group relative flex items-center gap-3 overflow-hidden rounded-sm px-5',
		'text-sui-background py-3 font-medium transition-colors',
		!loading && !disabled
			? ' bg-sui-primary hover:bg-sui-secondary cursor-pointer'
			: 'cursor-not-allowed opacity-55',
		classValue
	]}
	onclick={handleButtonClick}
	{...others}
>
	{@render children()}

	{#if icon && !loading}
		<icon.component {...icon.props || defaultIconProps} />
	{/if}

	{#if loading}
		<Spinner class="-mr-1 ml-0.5" size="1.3rem" />
	{/if}
</button>

<style>
	@keyframes loop {
		0% {
			transform: translateX(0);
			opacity: 100%;
		}
		40% {
			opacity: 100%;
		}
		50% {
			transform: translateX(3rem);
			opacity: 0%;
		}
		51% {
			transform: translateX(-2rem);
			opacity: 0%;
		}
		100% {
			transform: translateX(0);
		}
	}

	@keyframes bounce {
		0% {
			transform: translateX(0);
		}
		25% {
			transform: translateX(-0.25rem);
		}
		75% {
			transform: translateX(0.25rem);
		}
		100% {
			transform: translateX(0);
		}
	}

	@keyframes ripple {
		to {
			transform: scale(4);
			opacity: 0;
		}
	}

	:global(button svg.animate) {
		animation-name: loop;
		animation-duration: 0.5s;
	}

	:global(button svg.bounce) {
		animation-name: bounce;
		animation-duration: 180ms;
		animation-timing-function: ease-in-out;
		animation-iteration-count: 3;
	}

	:global(span.ripple) {
		position: absolute;
		border-radius: 50%;
		background-color: rgba(255, 255, 255, 0.3);
		animation: ripple 0.5s linear;
		transform: scale(0);
		pointer-events: none;
	}
</style>
