<!--
	Documents the purpose, API, and usage details of the selected component.
	Include key props, expected behavior, and any important notes for consumers.
-->

<script lang="ts" module>
	export type TabPage = {
		title: string;
		onMount?: () => void;
		onUnmount?: () => void;
		content: Snippet<[tab: TabPage]>;
	};
</script>

<script lang="ts">
	import { onMount, tick, type Snippet } from 'svelte';
	import type { ClassValue } from 'svelte/elements';
	import { fly, type FlyParams } from 'svelte/transition';

	interface Props {
		pages: TabPage[];
		/** Currently active tab index (default: 0) */
		activeIndex?: number;
		/** Callback fired when the active tab changes */
		onchange?: (event: { index: number; tab: TabPage }) => void;
		/** Applies layout padding to the tab header (default: false) */
		padHeader?: boolean;
		/** Applies layout padding to the content areas (default: false) */
		padContent?: 'padding' | 'margin' | 'none';
		/** Additional classes applied to the outer container */
		class?: ClassValue | null;
	}

	let {
		pages,
		activeIndex = 0,
		onchange,
		padHeader = false,
		padContent = 'none',
		class: classValue
	}: Props = $props();

	let primaryContainerEl: HTMLDivElement;
	let tabContainerEl: HTMLDivElement;
	let indicatorEl: HTMLDivElement;
	let prevIndex = $state(activeIndex);
	const activePage = $derived(pages[activeIndex]);
	const refs = $state<HTMLButtonElement[]>([]);

	const changePage = (index: number) => {
		if (activeIndex === index) return;
		pages[activeIndex].onUnmount?.(); // run cleanup
		prevIndex = activeIndex; // store previous index
		activeIndex = index; // update active index
		onchange?.({ index, tab: pages[index] }); // run on change
		updateIndicator(); // update selected tab indicator
		// run on mount after tick
		tick().then(() => {
			pages[index].onMount?.();
		});
	};

	const updateIndicator = () => {
		if (!indicatorEl || !refs[activeIndex]) return;
		const parentRect = tabContainerEl.getBoundingClientRect();
		const rect = refs[activeIndex].getBoundingClientRect();

		const width = rect.width;
		const mul = 0.75;

		indicatorEl.style.left = `${rect.left - parentRect.left + (width / 2) * (1 - mul)}px`;
		indicatorEl.style.width = `${rect.width * mul}px`;
	};

	const flyX = (
		node: HTMLElement,
		{ direction = 1, ...opts }: { direction: number } & FlyParams
	) => {
		return fly(node, {
			...opts,
			x: direction * 100, // fly left or right
			y: 0
		});
	};

	const lockHeight = () => {
		if (!primaryContainerEl) return;
		const height = primaryContainerEl.getBoundingClientRect().height;
		primaryContainerEl.style.height = `${height}px`;
		primaryContainerEl.style.overflow = 'hidden';
	};

	const unlockHeight = () => {
		if (!primaryContainerEl) return;
		primaryContainerEl.style.height = '';
		primaryContainerEl.style.overflow = '';
	};

	onMount(() => {
		updateIndicator();
		setTimeout(() => {
			indicatorEl.style.opacity = '1';
		}, 100);
	});
</script>

<div bind:this={primaryContainerEl} class={[classValue]}>
	<div
		bind:this={tabContainerEl}
		class={[
			'border-sui-text/15 relative mb-4 flex items-center gap-5 border-b-2',
			padHeader && 'px-layout'
		]}
	>
		{#each pages as page, i (page.title)}
			{@const active = activeIndex === i}
			<button
				type="button"
				bind:this={refs[i]}
				class={['-mb-[2px] cursor-pointer px-2']}
				onclick={() => {
					changePage(i);
				}}
			>
				<div
					class={[
						'hover:text-sui-text border-b-0 py-1.5 text-lg font-medium transition-colors duration-100',
						active ? 'border-sui-text-900 text-sui-text' : 'text-sui-text/75 border-transparent'
					]}
				>
					{page.title}
				</div>
			</button>
		{/each}

		<div
			bind:this={indicatorEl}
			class={[
				'border-sui-text-900 pointer-events-none absolute top-0 -bottom-[2px] w-16 border-b-2',
				'opacity-0 transition-[left,width,opacity]'
			]}
		></div>
	</div>

	{#key activeIndex}
		<div
			class={[padContent === 'padding' && 'px-layout', padContent === 'margin' && 'mx-layout']}
			in:flyX={{ direction: activeIndex > prevIndex ? 1 : -1, duration: 180, delay: 181 }}
			out:flyX={{ direction: activeIndex > prevIndex ? -1 : 1, duration: 180 }}
			onoutrostart={lockHeight}
			onintroend={unlockHeight}
		>
			{@render activePage.content(activePage)}
		</div>
	{/key}
</div>
