/**
 * Generic virtualized list component for ink-terminal.
 *
 * Wraps the useVirtualScroll hook into a declarative component API.
 * Renders only items in viewport + overscan, using spacer Boxes to maintain
 * scroll height. Works with any ScrollBox.
 *
 * Usage:
 * ```tsx
 * <ScrollBox ref={scrollRef} height={40}>
 *   <VirtualList
 *     items={messages}
 *     scrollRef={scrollRef}
 *     columns={process.stdout.columns ?? 80}
 *     itemKey={(item) => item.id}
 *     renderItem={(item, index) => <MessageRow message={item} />}
 *   />
 * </ScrollBox>
 * ```
 */

import React, { type RefObject, useMemo } from 'react'
import type { ScrollBoxHandle } from '../components/ScrollBox.js'
import type { DOMElement } from '../dom.js'
import {
	useVirtualScroll,
	type VirtualScrollResult,
} from '../hooks/use-virtual-scroll.js'
import Box from './Box.js'

type VirtualListProps<T> = {
	/** Array of items to render. */
	items: readonly T[]
	/** Ref to the parent ScrollBox handle. */
	scrollRef: RefObject<ScrollBoxHandle | null>
	/** Terminal column count. Triggers height cache scaling on change. */
	columns: number
	/** Extract a unique key from each item. Must be stable across renders. */
	itemKey: (item: T, index: number) => string
	/** Render function for each visible item. */
	renderItem: (item: T, index: number) => React.ReactNode
	/**
	 * Optional ref for accessing the VirtualScrollResult imperatively.
	 * Useful for scrollToIndex, getItemTop, etc.
	 */
	resultRef?: React.MutableRefObject<VirtualScrollResult | null>
}

/**
 * A generic virtualized list that renders only visible items inside a ScrollBox.
 *
 * This component manages:
 * - Mounting only items in viewport + overscan (80 rows above/below)
 * - Height measurement and caching via Yoga layout
 * - Top/bottom spacers to maintain scroll position
 * - Slide cap (25 items/commit) for smooth fast scrolling
 * - Column-change scaling for terminal resize
 */
export function VirtualList<T>({
	items,
	scrollRef,
	columns,
	itemKey,
	renderItem,
	resultRef,
}: VirtualListProps<T>): React.ReactNode {
	// Build stable key array from items. useMemo so useVirtualScroll's
	// itemKeys identity only changes when items actually change.
	const itemKeys = useMemo(
		() => items.map((item, i) => itemKey(item, i)),
		[items, itemKey],
	)

	const result = useVirtualScroll(scrollRef, itemKeys, columns)
	const { range, topSpacer, bottomSpacer, measureRef, spacerRef } = result

	// Expose result for imperative access.
	if (resultRef) {
		resultRef.current = result
	}

	const [startIdx, endIdx] = range

	// Render visible items with measurement refs.
	const renderedItems: React.ReactNode[] = []
	for (let i = startIdx; i < endIdx; i++) {
		const item = items[i]!
		const key = itemKeys[i]!
		renderedItems.push(
			<Box
				key={key}
				ref={measureRef(key) as unknown as React.Ref<DOMElement>}
				flexDirection="column"
			>
				{renderItem(item, i)}
			</Box>,
		)
	}

	return (
		<>
			<Box
				ref={spacerRef as React.Ref<DOMElement>}
				height={Math.max(0, Math.round(topSpacer))}
			/>
			{renderedItems}
			<Box height={Math.max(0, Math.round(bottomSpacer))} />
		</>
	)
}
