import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import React, { useMemo, useCallback } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import { useSelectionList } from '../../hooks/useSelectionList.js';
import { TextInput } from './TextInput.js';
import { useKeypress } from '../../hooks/useKeypress.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
/**
 * A generic searchable list component with keyboard navigation.
 */
export function SearchableList({ title, items, onSelect, onClose, searchPlaceholder = 'Search...', renderItem, header, footer, maxItemsToShow = 10, useSearch, onSearch, resetSelectionOnItemsChange = false, }) {
    const { filteredItems, searchBuffer, maxLabelWidth } = useSearch({
        items,
        onSearch,
    });
    const selectionItems = useMemo(() => filteredItems.map((item) => ({
        key: item.key,
        value: item,
    })), [filteredItems]);
    const handleSelectValue = useCallback((item) => {
        onSelect(item);
    }, [onSelect]);
    const { activeIndex, setActiveIndex } = useSelectionList({
        items: selectionItems,
        onSelect: handleSelectValue,
        isFocused: true,
        showNumbers: false,
        wrapAround: true,
        priority: true,
    });
    const [scrollOffsetState, setScrollOffsetState] = React.useState(0);
    // Compute effective scroll offset during render to avoid visual flicker
    let scrollOffset = scrollOffsetState;
    if (activeIndex < scrollOffset) {
        scrollOffset = activeIndex;
    }
    else if (activeIndex >= scrollOffset + maxItemsToShow) {
        scrollOffset = activeIndex - maxItemsToShow + 1;
    }
    const maxScroll = Math.max(0, filteredItems.length - maxItemsToShow);
    if (scrollOffset > maxScroll) {
        scrollOffset = maxScroll;
    }
    // Update state to match derived value if it changed
    if (scrollOffsetState !== scrollOffset) {
        setScrollOffsetState(scrollOffset);
    }
    // Reset selection to top when items change if requested
    const prevItemsRef = React.useRef(filteredItems);
    React.useLayoutEffect(() => {
        if (resetSelectionOnItemsChange && filteredItems !== prevItemsRef.current) {
            setActiveIndex(0);
            setScrollOffsetState(0);
        }
        prevItemsRef.current = filteredItems;
    }, [filteredItems, setActiveIndex, resetSelectionOnItemsChange]);
    // Handle global Escape key to close the list
    useKeypress((key) => {
        if (keyMatchers[Command.ESCAPE](key)) {
            onClose();
            return true;
        }
        return false;
    }, { isActive: true });
    const visibleItems = filteredItems.slice(scrollOffset, scrollOffset + maxItemsToShow);
    const defaultRenderItem = (item, isActive, labelWidth) => (_jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsx(Box, { minWidth: 2, flexShrink: 0, children: _jsx(Text, { color: isActive ? theme.status.success : theme.text.secondary, children: isActive ? '●' : '' }) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, minWidth: 0, children: [_jsx(Text, { color: isActive ? theme.status.success : theme.text.primary, children: item.label.padEnd(labelWidth) }), item.description && (_jsx(Text, { color: theme.text.secondary, wrap: "truncate-end", children: item.description }))] })] }));
    return (_jsxs(Box, { flexDirection: "column", width: "100%", height: "100%", paddingX: 1, children: [title && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.text.primary, children: title }) })), searchBuffer && (_jsx(Box, { borderStyle: "round", borderColor: theme.border.default, paddingX: 1, marginBottom: 1, children: _jsx(TextInput, { buffer: searchBuffer, placeholder: searchPlaceholder, focus: true }) })), header && _jsx(Box, { marginBottom: 1, children: header }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: filteredItems.length === 0 ? (_jsx(Box, { marginX: 2, children: _jsx(Text, { color: theme.text.secondary, children: "No items found." }) })) : (_jsxs(_Fragment, { children: [filteredItems.length > maxItemsToShow && (_jsx(Box, { marginX: 1, children: _jsx(Text, { color: theme.text.secondary, children: "\u25B2" }) })), visibleItems.map((item, index) => {
                            const isSelected = activeIndex === scrollOffset + index;
                            return (_jsx(Box, { marginBottom: 1, marginX: 1, children: renderItem
                                    ? renderItem(item, isSelected, maxLabelWidth)
                                    : defaultRenderItem(item, isSelected, maxLabelWidth) }, item.key));
                        }), filteredItems.length > maxItemsToShow && (_jsx(Box, { marginX: 1, children: _jsx(Text, { color: theme.text.secondary, children: "\u25BC" }) }))] })) }), footer && (_jsx(Box, { marginTop: 1, children: footer({
                    startIndex: scrollOffset,
                    endIndex: scrollOffset + visibleItems.length,
                    totalVisible: filteredItems.length,
                }) }))] }));
}
//# sourceMappingURL=SearchableList.js.map