	<script lang="ts" generics="TData, TValue">
	import { onMount } from 'svelte';
	import { type ColumnDef, getCoreRowModel } from '@tanstack/table-core';
	import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table/index.js';
	import * as Table from '$lib/components/ui/table/index.js';

	type InfiniteDataTableProps<TData, TValue> = {
		columns: ColumnDef<TData, TValue>[];
		data: TData[];
		loading: boolean;
		hasMore: boolean;
		loadMore: () => void;
		emptyMessage?: string;
		selectedIds?: number[];
		onSelectedIdsChange?: (ids: number[]) => void;
		onRowClick?: (row: TData) => void;
		onRowDoubleClick?: (row: TData) => void;
	};

	let {
		data,
		columns,
		loading,
		hasMore,
		loadMore,
		emptyMessage = 'No hay resultados.',
		selectedIds = [],
		onSelectedIdsChange,
		onRowClick,
		onRowDoubleClick,
	}: InfiniteDataTableProps<TData, TValue> = $props();

	const table = createSvelteTable({
		get data() {
			return data;
		},
		columns,
		getCoreRowModel: getCoreRowModel(),
		getRowId: (row: any) => row.id?.toString(),
		state: {
			get rowSelection() {
				const selection: Record<string, boolean> = {};
				selectedIds.forEach((id) => {
					selection[id.toString()] = true;
				});
				return selection;
			}
		},
		onStateChange: (updater: any) => {
			if (!onSelectedIdsChange) return;

			const currentState = table.getState();
			const nextState = typeof updater === 'function' ? updater(currentState) : updater;
			const rowSelection = nextState?.rowSelection;

			if (!rowSelection) {
				onSelectedIdsChange([]);
				return;
			}

			const nextSelectedIds = Object.entries(rowSelection)
				.filter(([, selected]) => Boolean(selected))
				.map(([id]) => Number(id))
				.filter((id) => Number.isFinite(id));

			onSelectedIdsChange(nextSelectedIds);
		},
		enableRowSelection: true,
		enableMultiRowSelection: true
	});

	let scrollContainer = $state<HTMLDivElement>();
	let loadingTrigger = $state<HTMLDivElement>();

	function findRowActionTrigger(tr: HTMLTableRowElement): HTMLElement | null {
		const lastTd = tr.querySelector('td:last-of-type');
		if (!lastTd) return null;
		return lastTd.querySelector<HTMLElement>(
			'[data-slot="dropdown-menu-trigger"], button[aria-haspopup="menu"], button[aria-haspopup="dialog"]'
		);
	}

	function handleCatalogTableKeydown(event: KeyboardEvent) {
		if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
		const target = event.target as HTMLElement | null;
		if (!target || !scrollContainer?.contains(target)) return;
		if (
			target.tagName === 'INPUT' ||
			target.tagName === 'TEXTAREA' ||
			target.tagName === 'SELECT' ||
			target.isContentEditable
		) {
			return;
		}
		const row = target.closest<HTMLTableRowElement>('tr[data-slot="table-row"]');
		if (!row || row.closest('[role="dialog"]')) return;
		const lastCell = row.querySelector('td:last-of-type');
		if (!lastCell || !lastCell.contains(target)) return;

		const tbody = row.closest('tbody');
		if (!tbody || !scrollContainer.contains(tbody)) return;
		const rows = Array.from(
			tbody.querySelectorAll<HTMLTableRowElement>('tr[data-slot="table-row"]')
		);
		const idx = rows.indexOf(row);
		if (idx < 0) return;
		const delta = event.key === 'ArrowDown' ? 1 : -1;
		const nextIdx = idx + delta;
		if (nextIdx < 0 || nextIdx >= rows.length) return;
		const nextTrigger = findRowActionTrigger(rows[nextIdx]);
		if (!nextTrigger) return;
		event.preventDefault();
		event.stopPropagation();
		nextTrigger.focus();
		rows[nextIdx].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
	}

	onMount(() => {
		const observer = new IntersectionObserver(
			(entries) => {
				const [entry] = entries;
				if (entry.isIntersecting && hasMore && !loading) {
					loadMore();
				}
			},
			{
				root: scrollContainer,
				threshold: 0.1
			}
		);

		if (loadingTrigger) {
			observer.observe(loadingTrigger);
		}

		return () => {
			observer.disconnect();
		};
	});
</script>

<div class="flex h-full w-full flex-col overflow-hidden">
	<div
		class="catalog-table-scroll min-h-[320px] max-h-[calc(100svh-280px)]"
		bind:this={scrollContainer}
		onkeydown={handleCatalogTableKeydown}
	>
		<Table.Root>
			<Table.Header class="catalog-table-header">
				{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
					{@const headerList = headerGroup.headers}
					{@const lastHeaderColId = headerList[headerList.length - 1]?.column.id}
					<Table.Row inTabOrder={false}>
						{#each headerList as header (header.id)}
							{@const colId = header.column.id}
							<Table.Head
								class={[
									'catalog-table-head-cell',
									colId === 'select' && 'catalog-table-sticky-left z-40 min-w-[2.75rem]',
									colId === lastHeaderColId &&
										'catalog-table-sticky-right z-30'
								]
									.filter(Boolean)
									.join(' ')}
							>
								{#if !header.isPlaceholder}
									<FlexRender
										content={header.column.columnDef.header}
										context={header.getContext()}
									/>
								{/if}
							</Table.Head>
						{/each}
					</Table.Row>
				{/each}
			</Table.Header>
			<Table.Body>
				{#if table.getRowModel().rows.length}
					{#each table.getRowModel().rows as row (row.id)}
						{@const visibleCells = row.getVisibleCells()}
						{@const lastCellColId = visibleCells[visibleCells.length - 1]?.column.id}
						<Table.Row
							inTabOrder={false}
							data-state={row.getIsSelected() ? 'selected' : undefined}
							class={[
								row.getIsSelected() ? 'catalog-table-row-selected' : 'catalog-table-row',
								onRowClick && 'cursor-pointer'
							]
								.filter(Boolean)
								.join(' ')}
							onclick={() => onRowClick?.(row.original)}
						>
							{#each visibleCells as cell (cell.id)}
								{@const colId = cell.column.id}
								<Table.Cell
									class={[
										'whitespace-nowrap',
										colId === 'select' && 'catalog-table-sticky-left z-30 min-w-[2.75rem]',
										colId === lastCellColId &&
											'catalog-table-sticky-right z-10',
										row.getIsSelected()
											? 'catalog-table-sticky-row-selected'
											: 'catalog-table-sticky-row-hover'
									]
										.filter(Boolean)
										.join(' ')}
								>
									<FlexRender
										content={cell.column.columnDef.cell}
										context={cell.getContext()}
									/>
								</Table.Cell>
							{/each}
						</Table.Row>
					{/each}
				{:else}
					<Table.Row inTabOrder={false}>
						<Table.Cell colspan={columns.length} class="h-24 text-center text-sm text-muted-foreground">
							{emptyMessage}
						</Table.Cell>
					</Table.Row>
				{/if}

				{#if hasMore}
					<Table.Row inTabOrder={false}>
						<Table.Cell colspan={columns.length} class="h-24 text-center p-0">
							<div bind:this={loadingTrigger} class="flex h-full w-full items-center justify-center">
								{#if loading}
									<div class="flex items-center justify-center gap-3 rounded-full border bg-muted/30 px-6 py-2 shadow-sm">
										<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
										<span class="text-sm font-medium text-foreground">Cargando más registros...</span>
									</div>
								{:else}
									<div class="flex items-center gap-2 text-sm text-muted-foreground">
										<span class="h-px w-8 bg-border"></span>
										<span>Desplázate para cargar más</span>
										<span class="h-px w-8 bg-border"></span>
									</div>
								{/if}
							</div>
						</Table.Cell>
					</Table.Row>
				{/if}
			</Table.Body>
		</Table.Root>
	</div>
</div>
