<!--
    Component: VirtualList

    High-performance virtualized list for large datasets with:
    - Virtual scrolling (only renders visible items + overscan)
    - Keyboard navigation (ArrowUp/Down, Home, End)
    - Fixed or dynamic item heights
    - ARIA listbox/option pattern with single tab stop
-->
<script lang="ts" generics="T">
import { createVirtualizer } from '$shared/lib';
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import type { Snippet } from 'svelte';

interface Props {
    /**
     * Array of items to render in the virtual list.
     *
     * @template T - The type of items in the list
     */
    items: T[];
    /**
     * Height for each item, either as a fixed number
     * or a function that returns height per index.
     * @default 80
     */
    itemHeight?: number | ((index: number) => number);
    /**
     * Optional overscan value for the virtual list.
     * @default 5
     */
    overscan?: number;
    /**
     * Optional CSS class string for styling the container
     * (follows shadcn convention for className prop)
     */
    class?: string;
    /**
     * An optional callback that will be called for each new set of loaded items
     * @param items - Loaded items
     */
    onVisibleItemsChange?: (items: T[]) => void;
    /**
     * Snippet for rendering individual list items.
     *
     * The snippet receives an object containing:
     * - `item`: The item from the items array (type T)
     * - `index`: The current item's index in the array
     *
     * This pattern provides type safety and flexibility for
     * rendering different item types without prop drilling.
     *
     * @template T - The type of items in the list
     */
    children: Snippet<[{ item: T; index: number; isVisible: boolean; proximity: number }]>;
}

let { items, itemHeight = 80, overscan = 5, class: className, onVisibleItemsChange, children }:
    Props = $props();

const virtualizer = createVirtualizer(() => ({
    count: items.length,
    data: items,
    estimateSize: typeof itemHeight === 'function' ? itemHeight : () => itemHeight,
    overscan,
}));

$effect(() => {
    const visibleItems = virtualizer.items.map(item => items[item.index]);
    onVisibleItemsChange?.(visibleItems);
});
</script>

<div
    use:virtualizer.container
    class={cn(
        'relative overflow-auto rounded-md bg-background',
        'h-150 w-full',
        'scroll-smooth',
        className,
    )}
    onfocusin={(e => {
        // Prevent the browser from jumping the scroll when an inner element gets focus
        e.preventDefault();
    })}
>
    <div
        style:height="{virtualizer.totalSize}px"
        class="w-full pointer-events-none"
    >
    </div>

    {#each virtualizer.items as item (item.key)}
        <div
            use:virtualizer.measureElement
            data-index={item.index}
            class="absolute top-0 left-0 w-full"
            style:transform="translateY({item.start}px)"
        >
            {@render children({
    item: items[item.index],
    index: item.index,
    isVisible: item.isVisible,
    proximity: item.proximity,
})}
        </div>
    {/each}
</div>
