<script lang="ts">
import * as m from '$lib/paraglide/messages.js';

interface Props {
	/** 0-indexed current page. */
	page: number;
	/** Total number of pages. `0` or `1` hides the control as trivially there's nothing to navigate. */
	totalPages: number;
	/** Given a 0-indexed page number, returns the href the link should point at. */
	makeHref: (page: number) => string;
	/** Optional override for the outer `<nav>`'s aria-label. */
	ariaLabel?: string;
}

const { page, totalPages, makeHref, ariaLabel }: Props = $props();

const hasPrev = $derived(page > 0);
const hasNext = $derived(page < totalPages - 1);
const controlBase =
	'inline-flex min-h-[44px] min-w-[44px] items-center justify-center gap-1.5 rounded-sm border border-line bg-white px-4 py-2 font-sans text-sm font-bold text-ink';
const linkBase = `${controlBase} transition-colors hover:bg-surface focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:ring-offset-2 focus-visible:outline-none`;
const disabledBase = `${controlBase} cursor-not-allowed opacity-40`;
const activePageBase =
	'inline-flex min-h-[44px] min-w-[44px] items-center justify-center rounded-sm border border-brand-navy bg-brand-navy px-4 py-2 font-sans text-sm font-bold text-white';

/**
 * Builds the sliding window of 1-indexed page numbers to render as buttons.
 * Always shows: first, last, current, one neighbor each side.
 * null entries represent ellipsis gaps.
 */
const pageWindow = $derived.by(() => {
	const first = 1;
	const last = totalPages;
	const current = page + 1; // convert to 1-indexed

	const windowStart = Math.max(first, current - 1);
	const windowEnd = Math.min(last, current + 1);

	const result: (number | null)[] = [];

	result.push(first);

	if (windowStart > first + 2) {
		result.push(null); // left ellipsis
	} else if (windowStart === first + 2) {
		result.push(first + 1); // bridge: one page gap, show directly instead of ellipsis
	}

	for (let p = Math.max(windowStart, first + 1); p <= Math.min(windowEnd, last - 1); p++) {
		result.push(p);
	}

	if (windowEnd < last - 2) {
		result.push(null); // right ellipsis
	} else if (windowEnd === last - 2) {
		result.push(last - 1); // bridge: one page gap, show directly instead of ellipsis
	}

	if (last > first) {
		result.push(last);
	}

	return result;
});
</script>

{#if totalPages > 1}
	<nav
		aria-label={ariaLabel ?? m.pagination_nav_label()}
		class="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-between"
	>
		<!--
			At the bounds we render a <span aria-hidden="true"> instead of an
			<a aria-disabled>. aria-disabled on a link is the documented pattern
			but screen readers still announce "Previous, link, disabled" — which
			is confusing on a pagination control where the disabled state is
			purely visual. Hiding the element from the AT tree entirely is the
			cleaner semantic.
		-->
		{#if hasPrev}
			<a
				data-testid="pagination-prev"
				aria-label={m.pagination_prev()}
				href={makeHref(page - 1)}
				class={linkBase}
			>
				<span aria-hidden="true">«</span>
				{m.pagination_prev()}
			</a>
		{:else}
			<span data-testid="pagination-prev" aria-hidden="true" class={disabledBase}>
				<span aria-hidden="true">«</span>
				{m.pagination_prev()}
			</span>
		{/if}

		<!-- Mobile: "Seite X von Y" label (hidden on sm: and above) -->
		<!-- aria-hidden: decorative visual label; AT uses the sr-only span below for aria-current -->
		<span
			data-testid="pagination-page-label"
			aria-hidden="true"
			class="font-sans text-sm text-ink-2 sm:hidden"
		>
			{m.pagination_page_of({ page: page + 1, total: totalPages })}
		</span>
		<!-- Always in the AT tree: announces current page regardless of breakpoint.
		     On mobile, the desktop button container is display:none so this is the only AT anchor.
		     On desktop, the active page button also carries aria-current — both announce the same info. -->
		<span data-testid="pagination-current-page-sr" aria-current="page" class="sr-only">
			{m.pagination_page_of({ page: page + 1, total: totalPages })}
		</span>

		<!-- Desktop: numbered page buttons (hidden below sm:) -->
		<div data-testid="pagination-pages" class="hidden items-center gap-1 sm:flex">
			{#each pageWindow as entry, i (entry === null ? 'ellipsis-' + i : entry)}
				{#if entry === null}
					{#if i === 1}
						<span
							data-testid="pagination-ellipsis-left"
							aria-hidden="true"
							class="px-2 text-sm text-ink-2">…</span
						>
					{:else}
						<span
							data-testid="pagination-ellipsis-right"
							aria-hidden="true"
							class="px-2 text-sm text-ink-2">…</span
						>
					{/if}
				{:else if entry === page + 1}
					<span
						data-testid="pagination-page-{entry}"
						aria-current="page"
						aria-label={m.pagination_page_button({ page: entry })}
						class={activePageBase}
					>
						{entry}
					</span>
				{:else}
					<a
						data-testid="pagination-page-{entry}"
						aria-label={m.pagination_page_button({ page: entry })}
						href={makeHref(entry - 1)}
						class={linkBase}
					>
						{entry}
					</a>
				{/if}
			{/each}
		</div>

		{#if hasNext}
			<a
				data-testid="pagination-next"
				aria-label={m.pagination_next()}
				href={makeHref(page + 1)}
				class={linkBase}
			>
				{m.pagination_next()}
				<span aria-hidden="true">»</span>
			</a>
		{:else}
			<span data-testid="pagination-next" aria-hidden="true" class={disabledBase}>
				{m.pagination_next()}
				<span aria-hidden="true">»</span>
			</span>
		{/if}
	</nav>
{/if}
