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

	export const pagination = tv({
		base: "flex items-center gap-1"
	});

	export const paginationNav = tv({
		base: "flex size-8 items-center justify-center rounded text-foreground-muted transition-colors duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] hover:bg-background-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus disabled:pointer-events-none disabled:opacity-50"
	});

	export const paginationItem = tv({
		base: "flex size-8 items-center justify-center rounded border text-sm transition-colors duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus",
		variants: {
			active: {
				true: "border-border-strong bg-background-inset font-medium text-foreground-default",
				false: "border-border-default text-foreground-muted hover:bg-background-subtle"
			}
		},
		defaultVariants: {
			active: false
		}
	});

	export type PaginationItemVariants = VariantProps<typeof paginationItem>;

	type Item = number | "start-ellipsis" | "end-ellipsis";

	function range(start: number, end: number): number[] {
		return Array.from({ length: Math.max(end - start + 1, 0) }, (_, i) => start + i);
	}

	// Windowing per MUI's usePagination: always show the boundary pages
	// (first/last) plus a sibling window around the current page, collapsing
	// any gap into a single ellipsis.
	function getPageItems(current: number, count: number): Item[] {
		const siblingCount = 1;
		const boundaryCount = 1;

		const startPages = range(1, Math.min(boundaryCount, count));
		const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count);

		const siblingsStart = Math.max(
			Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),
			boundaryCount + 2
		);
		const siblingsEnd = Math.min(
			Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),
			endPages.length > 0 ? endPages[0] - 2 : count - 1
		);

		return [
			...startPages,
			...(siblingsStart > boundaryCount + 2
				? (["start-ellipsis"] as const)
				: boundaryCount + 1 < count - boundaryCount
					? [boundaryCount + 1]
					: []),
			...range(siblingsStart, siblingsEnd),
			...(siblingsEnd < count - boundaryCount - 1
				? (["end-ellipsis"] as const)
				: count - boundaryCount > boundaryCount
					? [count - boundaryCount]
					: []),
			...endPages
		];
	}
</script>

<script lang="ts">
	import ChevronLeft from "$icon/chevron-left";
	import ChevronRight from "$icon/chevron-right";

	interface Props {
		page: number;
		total: number;
		onchange?: (page: number) => void;
		class?: string;
	}

	let { page = $bindable(1), total, onchange, class: className }: Props = $props();

	const count = $derived(Math.max(total, 1));
	const items = $derived(getPageItems(Math.min(Math.max(page, 1), count), count));

	function goTo(target: number) {
		const next = Math.min(Math.max(target, 1), count);
		if (next === page) return;
		page = next;
		onchange?.(next);
	}
</script>

<nav aria-label="Pagination" class={pagination({ class: className })}>
	<button
		type="button"
		class={paginationNav()}
		disabled={page <= 1}
		aria-label="Previous page"
		onclick={() => goTo(page - 1)}
	>
		<ChevronLeft class="size-4" aria-hidden="true" />
	</button>

	{#each items as item, i (typeof item === "number" ? item : `${item}-${i}`)}
		{#if typeof item === "number"}
			<button
				type="button"
				class={paginationItem({ active: item === page })}
				aria-current={item === page ? "page" : undefined}
				aria-label={`Page ${item}`}
				onclick={() => goTo(item)}
			>
				{item}
			</button>
		{:else}
			<span
				class="flex size-8 items-center justify-center text-foreground-subtle"
				aria-hidden="true"
			>
				&hellip;
			</span>
		{/if}
	{/each}

	<button
		type="button"
		class={paginationNav()}
		disabled={page >= count}
		aria-label="Next page"
		onclick={() => goTo(page + 1)}
	>
		<ChevronRight class="size-4" aria-hidden="true" />
	</button>
</nav>
