import { describe, it, expect, afterEach, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';

import Pagination from './Pagination.svelte';

afterEach(() => {
	cleanup();
});

const makeHref = (p: number) => `/documents?page=${p}`;

describe('Pagination', () => {
	it('renders the page-of-total label for the current page', async () => {
		render(Pagination, { page: 2, totalPages: 10, makeHref });

		const label = page.getByTestId('pagination-page-label');
		await expect.element(label).toHaveTextContent(/3/); // page is 0-indexed, label is 1-indexed
		await expect.element(label).toHaveTextContent(/10/);
	});

	it('mobile page label is aria-hidden (desktop buttons carry the aria-current role)', async () => {
		render(Pagination, { page: 0, totalPages: 3, makeHref });

		const label = page.getByTestId('pagination-page-label');
		await expect.element(label).toHaveAttribute('aria-hidden', 'true');
	});

	describe('page number buttons', () => {
		it('renders page number buttons when totalPages > 1', async () => {
			render(Pagination, { page: 4, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			// active page button — the current page (5, 1-indexed)
			const activeBtn = nav.getByTestId('pagination-page-5');
			await expect.element(activeBtn).toBeInTheDocument();
		});

		it('does not render page number buttons when totalPages <= 1', async () => {
			render(Pagination, { page: 0, totalPages: 1, makeHref });

			// entire nav is hidden
			const nav = page.getByRole('navigation');
			await expect.element(nav).not.toBeInTheDocument();
		});

		it('marks the active page button with aria-current="page"', async () => {
			render(Pagination, { page: 4, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const activeBtn = nav.getByTestId('pagination-page-5');
			await expect.element(activeBtn).toHaveAttribute('aria-current', 'page');
		});

		it('active page button has brand-navy background', async () => {
			render(Pagination, { page: 4, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const activeBtn = nav.getByTestId('pagination-page-5');
			await expect.element(activeBtn).toHaveClass(/bg-brand-navy/);
		});

		it('active page button has 44px touch target', async () => {
			render(Pagination, { page: 4, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const activeBtn = nav.getByTestId('pagination-page-5');
			await expect.element(activeBtn).toHaveClass(/min-h-\[44px\]/);
			await expect.element(activeBtn).toHaveClass(/min-w-\[44px\]/);
		});

		it('inactive page buttons link to their target page via makeHref', async () => {
			const spy = vi.fn(makeHref);
			render(Pagination, { page: 4, totalPages: 12, makeHref: spy });

			const nav = page.getByRole('navigation');
			// page button for page 1 (0-indexed: 0) should link to /documents?page=0
			const firstPageBtn = nav.getByTestId('pagination-page-1');
			await expect.element(firstPageBtn).toHaveAttribute('href', '/documents?page=0');
		});

		it('renders first and last page buttons always visible', async () => {
			render(Pagination, { page: 5, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			await expect.element(nav.getByTestId('pagination-page-1')).toBeInTheDocument();
			await expect.element(nav.getByTestId('pagination-page-12')).toBeInTheDocument();
		});

		it('renders ellipsis span between first page and window when gap exists', async () => {
			// page 6 (0-indexed: 5) — window is 5,6,7 — gap between 1 and 5
			render(Pagination, { page: 5, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const ellipses = nav.getByTestId('pagination-ellipsis-left');
			await expect.element(ellipses).toBeInTheDocument();
		});

		it('renders ellipsis span between window and last page when gap exists', async () => {
			// page 1 (0-indexed: 0) — window is 1,2 — gap between 2 and 12
			render(Pagination, { page: 0, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const ellipsis = nav.getByTestId('pagination-ellipsis-right');
			await expect.element(ellipsis).toBeInTheDocument();
		});

		it('does not render left ellipsis when window is adjacent to first page', async () => {
			// page 1 (0-indexed: 0) — window starts at 1, adjacent to first page
			render(Pagination, { page: 0, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const leftEllipsis = nav.getByTestId('pagination-ellipsis-left');
			await expect.element(leftEllipsis).not.toBeInTheDocument();
		});

		it('does not render right ellipsis when window is adjacent to last page', async () => {
			// last page (0-indexed: 11) — window ends at 12, adjacent to last page
			render(Pagination, { page: 11, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const rightEllipsis = nav.getByTestId('pagination-ellipsis-right');
			await expect.element(rightEllipsis).not.toBeInTheDocument();
		});

		it('page buttons container has hidden class on mobile (sm: prefix)', async () => {
			// The page buttons container must be hidden below sm: breakpoint
			render(Pagination, { page: 4, totalPages: 12, makeHref });

			const nav = page.getByRole('navigation');
			const pageButtons = nav.getByTestId('pagination-pages');
			await expect.element(pageButtons).toHaveClass(/hidden/);
			await expect.element(pageButtons).toHaveClass(/sm:flex/);
		});

		it('renders both pages without ellipsis when totalPages is 2', async () => {
			render(Pagination, { page: 0, totalPages: 2, makeHref });

			const nav = page.getByRole('navigation');
			await expect.element(nav.getByTestId('pagination-page-1')).toBeInTheDocument();
			await expect.element(nav.getByTestId('pagination-page-2')).toBeInTheDocument();
			await expect.element(nav.getByTestId('pagination-ellipsis-left')).not.toBeInTheDocument();
			await expect.element(nav.getByTestId('pagination-ellipsis-right')).not.toBeInTheDocument();
		});
	});

	it('mobile page label is aria-hidden so screen readers skip it on wide screens', async () => {
		render(Pagination, { page: 2, totalPages: 10, makeHref });

		const label = page.getByTestId('pagination-page-label');
		await expect.element(label).toHaveAttribute('aria-hidden', 'true');
	});

	it('sr-only span always provides aria-current="page" for screen readers at all breakpoints', async () => {
		render(Pagination, { page: 2, totalPages: 10, makeHref });

		const nav = page.getByRole('navigation');
		const srLabel = nav.getByTestId('pagination-current-page-sr');
		await expect.element(srLabel).toBeInTheDocument();
		await expect.element(srLabel).toHaveAttribute('aria-current', 'page');
	});

	it('renders prev as a link pointing at page - 1 when not on first page', async () => {
		render(Pagination, { page: 4, totalPages: 10, makeHref });

		const prev = page.getByTestId('pagination-prev');
		await expect.element(prev).toHaveAttribute('href', '/documents?page=3');
	});

	it('renders disabled prev as an aria-hidden non-link so screen readers skip it', async () => {
		render(Pagination, { page: 0, totalPages: 3, makeHref });

		const prev = page.getByTestId('pagination-prev');
		// Not a link — no href, no role=link
		await expect.element(prev).not.toHaveAttribute('href');
		// Hidden from assistive tech — AT shouldn't read "Previous, link, disabled"
		await expect.element(prev).toHaveAttribute('aria-hidden', 'true');
	});

	it('renders next as a link pointing at page + 1 when not on last page', async () => {
		render(Pagination, { page: 0, totalPages: 3, makeHref });

		const next = page.getByTestId('pagination-next');
		await expect.element(next).toHaveAttribute('href', '/documents?page=1');
	});

	it('renders disabled next as an aria-hidden non-link on the last page', async () => {
		render(Pagination, { page: 2, totalPages: 3, makeHref });

		const next = page.getByTestId('pagination-next');
		await expect.element(next).not.toHaveAttribute('href');
		await expect.element(next).toHaveAttribute('aria-hidden', 'true');
	});

	it('calls makeHref with p-1 and p+1', async () => {
		const spy = vi.fn(makeHref);
		render(Pagination, { page: 3, totalPages: 10, makeHref: spy });

		const calls = spy.mock.calls.map((c) => c[0]).sort((a, b) => a - b);
		expect(calls).toContain(2);
		expect(calls).toContain(4);
	});

	it('renders decorative chevron inside aria-hidden span so screen readers skip it', async () => {
		render(Pagination, { page: 1, totalPages: 3, makeHref });

		const prev = page.getByTestId('pagination-prev');
		await expect
			.element(prev.getByText('«', { exact: true }))
			.toHaveAttribute('aria-hidden', 'true');
	});

	it('prev and next have min 44px touch targets', async () => {
		render(Pagination, { page: 1, totalPages: 3, makeHref });

		const prev = page.getByTestId('pagination-prev');
		await expect.element(prev).toHaveClass(/min-h-\[44px\]/);
		await expect.element(prev).toHaveClass(/min-w-\[44px\]/);
	});
});
