<script lang="ts" generics="TData">
	import {
		type ColumnDef,
		type ColumnFiltersState,
		type PaginationState,
		type SortingState,
		type VisibilityState,
		getCoreRowModel,
		getFilteredRowModel,
		getPaginationRowModel,
		getSortedRowModel
	} from '@tanstack/table-core';
	import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table';
	import * as Table from '$lib/components/ui/table';
	import DataTableToolbar from './data-table-toolbar.svelte';
	import DataTablePagination from './data-table-pagination.svelte';
	import { goto } from '$app/navigation';
	import { cn } from '$lib/utils';
	import { untrack } from 'svelte';

	interface Props {
		data: TData[];
		columns: ColumnDef<TData>[];
		searchPlaceholder?: string;
		mapOptions?: string[];
		getRowHref?: (row: TData) => string;
		emptyMessage?: string;
		// Bindable state so the parent route can persist it across navigations
		// via SvelteKit's snapshot API.
		pagination?: PaginationState;
		sorting?: SortingState;
		columnFilters?: ColumnFiltersState;
		columnVisibility?: VisibilityState;
		globalFilter?: string;
	}

	let {
		data,
		columns,
		searchPlaceholder,
		mapOptions = [],
		getRowHref,
		emptyMessage = 'No results.',
		pagination = $bindable<PaginationState>({ pageIndex: 0, pageSize: 25 }),
		sorting = $bindable<SortingState>([]),
		columnFilters = $bindable<ColumnFiltersState>([]),
		columnVisibility = $bindable<VisibilityState>({}),
		globalFilter = $bindable<string>('')
	}: Props = $props();

	// Reset to first page whenever filters change so users don't end up on an
	// empty page after narrowing the result set.
	$effect(() => {
		void globalFilter;
		void columnFilters;
		untrack(() => {
			if (pagination.pageIndex !== 0) {
				pagination = { ...pagination, pageIndex: 0 };
			}
		});
	});

	const table = createSvelteTable<TData>({
		get data() {
			return data;
		},
		get columns() {
			return columns;
		},
		state: {
			get pagination() {
				return pagination;
			},
			get sorting() {
				return sorting;
			},
			get columnFilters() {
				return columnFilters;
			},
			get columnVisibility() {
				return columnVisibility;
			},
			get globalFilter() {
				return globalFilter;
			}
		},
		globalFilterFn: 'includesString',
		getCoreRowModel: getCoreRowModel(),
		getSortedRowModel: getSortedRowModel(),
		getFilteredRowModel: getFilteredRowModel(),
		getPaginationRowModel: getPaginationRowModel(),
		onPaginationChange: (u) => {
			pagination = typeof u === 'function' ? u(pagination) : u;
		},
		onSortingChange: (u) => {
			sorting = typeof u === 'function' ? u(sorting) : u;
		},
		onColumnFiltersChange: (u) => {
			columnFilters = typeof u === 'function' ? u(columnFilters) : u;
		},
		onColumnVisibilityChange: (u) => {
			columnVisibility = typeof u === 'function' ? u(columnVisibility) : u;
		},
		onGlobalFilterChange: (u) => {
			globalFilter = typeof u === 'function' ? u(globalFilter) : u;
		}
	});

	function handleRowClick(row: TData, e: MouseEvent | KeyboardEvent) {
		if (!getRowHref) return;
		const href = getRowHref(row);
		if (e instanceof MouseEvent && (e.metaKey || e.ctrlKey || e.button === 1)) {
			window.open(href, '_blank', 'noopener,noreferrer');
			return;
		}
		goto(href);
	}
</script>

<div>
	<DataTableToolbar {table} {searchPlaceholder} {mapOptions} />

	<div class="rounded-md border">
		<Table.Root>
			<Table.Header>
				{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
					<Table.Row class="bg-muted/40 hover:bg-muted/40">
						{#each headerGroup.headers as header (header.id)}
							<Table.Head class={cn('h-9 text-xs', header.column.columnDef.meta?.headClass)}>
								{#if !header.isPlaceholder}
									<FlexRender
										content={header.column.columnDef.header}
										context={header.getContext()}
									/>
								{/if}
							</Table.Head>
						{/each}
					</Table.Row>
				{/each}
			</Table.Header>
			<Table.Body>
				{#each table.getRowModel().rows as row (row.id)}
					<Table.Row
						class={cn(getRowHref && 'cursor-pointer')}
						onclick={(e: MouseEvent) => handleRowClick(row.original, e)}
						onkeydown={(e: KeyboardEvent) => {
							if (e.key === 'Enter' || e.key === ' ') {
								e.preventDefault();
								handleRowClick(row.original, e);
							}
						}}
						tabindex={getRowHref ? 0 : undefined}
						role={getRowHref ? 'link' : undefined}
					>
						{#each row.getVisibleCells() as cell (cell.id)}
							<Table.Cell class={cn('py-2 align-middle', cell.column.columnDef.meta?.cellClass)}>
								<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
							</Table.Cell>
						{/each}
					</Table.Row>
				{:else}
					<Table.Row>
						<Table.Cell
							colspan={columns.length}
							class="h-24 text-center text-sm text-muted-foreground"
						>
							{emptyMessage}
						</Table.Cell>
					</Table.Row>
				{/each}
			</Table.Body>
		</Table.Root>
	</div>

	<DataTablePagination {table} />
</div>
