import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import React, { useMemo, useState, useCallback } from 'react';
import { Box, Text } from 'ink';
import chalk from 'chalk';
import { theme } from '../../semantic-colors.js';
import { getScopeItems } from '../../../utils/dialogScopeUtils.js';
import { RadioButtonSelect } from './RadioButtonSelect.js';
import { TextInput } from './TextInput.js';
import { cpSlice, cpLen, cpIndexToOffset } from '../../utils/textUtils.js';
import { useKeypress } from '../../hooks/useKeypress.js';
import { Command } from '../../key/keyMatchers.js';
import { useSettingsNavigation } from '../../hooks/useSettingsNavigation.js';
import { useInlineEditBuffer } from '../../hooks/useInlineEditBuffer.js';
import { formatCommand } from '../../key/keybindingUtils.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
/**
 * A base settings dialog component that handles rendering, layout, and keyboard navigation.
 * Parent components handle business logic (saving, filtering, etc.) via callbacks.
 */
export function BaseSettingsDialog({ title, borderColor, searchEnabled = true, searchPlaceholder = 'Search to filter', searchBuffer, items, showScopeSelector = true, selectedScope, onScopeChange, maxItemsToShow, maxLabelWidth, onItemToggle, onEditCommit, onItemClear, onClose, onKeyPress, keyMatchers: customKeyMatchers, availableHeight, footer, }) {
    const globalKeyMatchers = useKeyMatchers();
    const keyMatchers = customKeyMatchers ?? globalKeyMatchers;
    // Calculate effective max items and scope visibility based on terminal height
    const { effectiveMaxItemsToShow, finalShowScopeSelector } = useMemo(() => {
        const initialShowScope = showScopeSelector;
        const initialMaxItems = maxItemsToShow;
        if (!availableHeight) {
            return {
                effectiveMaxItemsToShow: initialMaxItems,
                finalShowScopeSelector: initialShowScope,
            };
        }
        // Layout constants based on BaseSettingsDialog structure:
        const DIALOG_PADDING = 4;
        const SETTINGS_TITLE_HEIGHT = 1;
        // Account for the unconditional spacer below search/title section
        const SEARCH_SECTION_HEIGHT = searchEnabled ? 5 : 1;
        const SCROLL_ARROWS_HEIGHT = 2;
        const ITEMS_SPACING_AFTER = 1;
        const SCOPE_SECTION_HEIGHT = 5;
        const HELP_TEXT_HEIGHT = 1;
        const FOOTER_CONTENT_HEIGHT = footer?.height ?? 0;
        const ITEM_HEIGHT = 3;
        const currentAvailableHeight = availableHeight - DIALOG_PADDING;
        const baseFixedHeight = SETTINGS_TITLE_HEIGHT +
            SEARCH_SECTION_HEIGHT +
            SCROLL_ARROWS_HEIGHT +
            ITEMS_SPACING_AFTER +
            HELP_TEXT_HEIGHT +
            FOOTER_CONTENT_HEIGHT;
        // Calculate max items with scope selector
        const heightWithScope = baseFixedHeight + SCOPE_SECTION_HEIGHT;
        const availableForItemsWithScope = currentAvailableHeight - heightWithScope;
        const maxItemsWithScope = Math.max(1, Math.floor(availableForItemsWithScope / ITEM_HEIGHT));
        // Calculate max items without scope selector
        const availableForItemsWithoutScope = currentAvailableHeight - baseFixedHeight;
        const maxItemsWithoutScope = Math.max(1, Math.floor(availableForItemsWithoutScope / ITEM_HEIGHT));
        // In small terminals, hide scope selector if it would allow more items to show
        let shouldShowScope = initialShowScope;
        let maxItems = initialShowScope ? maxItemsWithScope : maxItemsWithoutScope;
        if (initialShowScope && availableHeight < 25) {
            // Hide scope selector if it gains us more than 1 extra item
            if (maxItemsWithoutScope > maxItemsWithScope + 1) {
                shouldShowScope = false;
                maxItems = maxItemsWithoutScope;
            }
        }
        return {
            effectiveMaxItemsToShow: Math.min(maxItems, items.length),
            finalShowScopeSelector: shouldShowScope,
        };
    }, [
        availableHeight,
        maxItemsToShow,
        items.length,
        searchEnabled,
        showScopeSelector,
        footer,
    ]);
    // Internal state
    const { activeIndex, windowStart, moveUp, moveDown } = useSettingsNavigation({
        items,
        maxItemsToShow: effectiveMaxItemsToShow,
    });
    const { editState, editDispatch, startEditing, commitEdit, cursorVisible } = useInlineEditBuffer({
        onCommit: (key, value) => {
            const itemToCommit = items.find((i) => i.key === key);
            if (itemToCommit) {
                onEditCommit(key, value, itemToCommit);
            }
        },
    });
    const { editingKey, buffer: editBuffer, cursorPos: editCursorPos, } = editState;
    const [focusSection, setFocusSection] = useState('settings');
    const effectiveFocusSection = !finalShowScopeSelector && focusSection === 'scope'
        ? 'settings'
        : focusSection;
    // Scope selector items
    const scopeItems = getScopeItems().map((item) => ({
        ...item,
        key: item.value,
    }));
    // Calculate visible items based on scroll offset
    const visibleItems = items.slice(windowStart, windowStart + effectiveMaxItemsToShow);
    // Show scroll indicators if there are more items than can be displayed
    const showScrollUp = items.length > effectiveMaxItemsToShow;
    const showScrollDown = items.length > effectiveMaxItemsToShow;
    // Get current item
    const currentItem = items[activeIndex];
    // Handle scope changes (for RadioButtonSelect)
    const handleScopeChange = useCallback((scope) => {
        onScopeChange?.(scope);
    }, [onScopeChange]);
    // Keyboard handling
    useKeypress((key) => {
        // Let parent handle custom keys first (only if not editing)
        if (!editingKey && onKeyPress?.(key, currentItem)) {
            return;
        }
        // Edit mode handling
        if (editingKey) {
            const item = items.find((i) => i.key === editingKey);
            const type = item?.type ?? 'string';
            // Navigation within edit buffer
            if (keyMatchers[Command.MOVE_LEFT](key)) {
                editDispatch({ type: 'MOVE_LEFT' });
                return;
            }
            if (keyMatchers[Command.MOVE_RIGHT](key)) {
                editDispatch({ type: 'MOVE_RIGHT' });
                return;
            }
            if (keyMatchers[Command.HOME](key)) {
                editDispatch({ type: 'HOME' });
                return;
            }
            if (keyMatchers[Command.END](key)) {
                editDispatch({ type: 'END' });
                return;
            }
            // Backspace
            if (keyMatchers[Command.DELETE_CHAR_LEFT](key)) {
                editDispatch({ type: 'DELETE_LEFT' });
                return;
            }
            // Delete
            if (keyMatchers[Command.DELETE_CHAR_RIGHT](key)) {
                editDispatch({ type: 'DELETE_RIGHT' });
                return;
            }
            // Escape in edit mode - commit (consistent with SettingsDialog)
            if (keyMatchers[Command.ESCAPE](key)) {
                commitEdit();
                return;
            }
            // Enter in edit mode - commit
            if (keyMatchers[Command.RETURN](key)) {
                commitEdit();
                return;
            }
            // Up/Down in edit mode - commit and navigate.
            // Only trigger on non-insertable keys (arrow keys) so that typing
            // j/k characters into the edit buffer is not intercepted.
            if (keyMatchers[Command.DIALOG_NAVIGATION_UP](key) && !key.insertable) {
                commitEdit();
                moveUp();
                return;
            }
            if (keyMatchers[Command.DIALOG_NAVIGATION_DOWN](key) &&
                !key.insertable) {
                commitEdit();
                moveDown();
                return;
            }
            // Character input
            if (key.sequence) {
                editDispatch({
                    type: 'INSERT_CHAR',
                    char: key.sequence,
                    isNumberType: type === 'number',
                });
            }
            return;
        }
        // Not in edit mode - handle navigation and actions
        if (effectiveFocusSection === 'settings') {
            // Up/Down navigation with wrap-around
            if (keyMatchers[Command.DIALOG_NAVIGATION_UP](key)) {
                moveUp();
                return true;
            }
            if (keyMatchers[Command.DIALOG_NAVIGATION_DOWN](key)) {
                moveDown();
                return true;
            }
            // Enter - toggle or start edit
            if (keyMatchers[Command.RETURN](key) && currentItem) {
                if (currentItem.type === 'boolean' || currentItem.type === 'enum') {
                    onItemToggle(currentItem.key, currentItem);
                }
                else {
                    // Start editing for string/number/array/object
                    const rawVal = currentItem.rawValue;
                    const initialValue = currentItem.editValue ??
                        (rawVal !== undefined ? String(rawVal) : '');
                    startEditing(currentItem.key, initialValue);
                }
                return true;
            }
            // Ctrl+L - clear/reset to default (using only Ctrl+L to avoid Ctrl+C exit conflict)
            if (keyMatchers[Command.CLEAR_SCREEN](key) && currentItem) {
                onItemClear(currentItem.key, currentItem);
                return true;
            }
            // Number keys for quick edit on number fields
            if (currentItem?.type === 'number' && /^[0-9]$/.test(key.sequence)) {
                startEditing(currentItem.key, key.sequence);
                return true;
            }
        }
        // Tab - switch focus section
        if (key.name === 'tab' && finalShowScopeSelector) {
            setFocusSection((s) => (s === 'settings' ? 'scope' : 'settings'));
            return;
        }
        // Escape - close dialog
        if (keyMatchers[Command.ESCAPE](key)) {
            onClose();
            return;
        }
        return;
    }, {
        isActive: true,
        priority: effectiveFocusSection === 'settings',
    });
    return (_jsx(Box, { borderStyle: "round", borderColor: borderColor ?? theme.border.default, flexDirection: "row", padding: 1, width: "100%", height: "100%", children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Box, { marginX: 1, children: _jsxs(Text, { bold: effectiveFocusSection === 'settings' && !editingKey, wrap: "truncate", children: [effectiveFocusSection === 'settings' ? '> ' : '  ', title, ' '] }) }), searchEnabled && searchBuffer && (_jsx(Box, { borderStyle: "round", borderColor: editingKey
                        ? theme.border.default
                        : effectiveFocusSection === 'settings'
                            ? theme.ui.focus
                            : theme.border.default, paddingX: 1, height: 3, marginTop: 1, children: _jsx(TextInput, { focus: effectiveFocusSection === 'settings' && !editingKey, buffer: searchBuffer, placeholder: searchPlaceholder }) })), _jsx(Box, { height: 1 }), visibleItems.length === 0 ? (_jsx(Box, { marginX: 1, height: 1, flexDirection: "column", children: _jsx(Text, { color: theme.text.secondary, children: "No matches found." }) })) : (_jsxs(_Fragment, { children: [showScrollUp && (_jsx(Box, { marginX: 1, children: _jsx(Text, { color: theme.text.secondary, children: "\u25B2" }) })), visibleItems.map((item, idx) => {
                            const globalIndex = idx + windowStart;
                            const isActive = effectiveFocusSection === 'settings' &&
                                activeIndex === globalIndex;
                            // Compute display value with edit mode cursor
                            let displayValue;
                            if (editingKey === item.key) {
                                // Show edit buffer with cursor highlighting
                                if (cursorVisible && editCursorPos < cpLen(editBuffer)) {
                                    // Cursor is in the middle or at start of text
                                    const beforeCursor = cpSlice(editBuffer, 0, editCursorPos);
                                    const atCursor = cpSlice(editBuffer, editCursorPos, editCursorPos + 1);
                                    const afterCursor = cpSlice(editBuffer, editCursorPos + 1);
                                    displayValue =
                                        beforeCursor + chalk.inverse(atCursor) + afterCursor;
                                }
                                else if (editCursorPos >= cpLen(editBuffer)) {
                                    // Cursor is at the end - show inverted space
                                    displayValue =
                                        editBuffer + (cursorVisible ? chalk.inverse(' ') : ' ');
                                }
                                else {
                                    // Cursor not visible
                                    displayValue = editBuffer;
                                }
                            }
                            else {
                                displayValue = item.displayValue;
                            }
                            return (_jsxs(React.Fragment, { children: [_jsxs(Box, { marginX: 1, flexDirection: "row", alignItems: "flex-start", backgroundColor: isActive ? theme.background.focus : undefined, children: [_jsx(Box, { minWidth: 2, flexShrink: 0, children: _jsx(Text, { color: isActive ? theme.ui.focus : theme.text.secondary, children: isActive ? '●' : '' }) }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, minWidth: 0, alignItems: "flex-start", children: [_jsxs(Box, { flexDirection: "column", width: maxLabelWidth, minWidth: 0, children: [_jsxs(Text, { color: isActive ? theme.ui.focus : theme.text.primary, children: [item.label, item.scopeMessage && (_jsxs(Text, { color: theme.text.secondary, children: [' ', item.scopeMessage] }))] }), _jsx(Text, { color: theme.text.secondary, wrap: "truncate", children: item.description ?? '' })] }), _jsx(Box, { minWidth: 3 }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: isActive
                                                                ? theme.ui.focus
                                                                : item.isGreyedOut
                                                                    ? theme.text.secondary
                                                                    : theme.text.primary, terminalCursorFocus: editingKey === item.key && cursorVisible, terminalCursorPosition: cpIndexToOffset(editBuffer, editCursorPos), children: displayValue }) })] })] }), _jsx(Box, { height: 1 })] }, item.key));
                        }), showScrollDown && (_jsx(Box, { marginX: 1, children: _jsx(Text, { color: theme.text.secondary, children: "\u25BC" }) }))] })), _jsx(Box, { height: 1 }), finalShowScopeSelector && (_jsxs(Box, { marginX: 1, flexDirection: "column", children: [_jsxs(Text, { bold: effectiveFocusSection === 'scope', wrap: "truncate", children: [effectiveFocusSection === 'scope' ? '> ' : '  ', "Apply To"] }), _jsx(RadioButtonSelect, { items: scopeItems, initialIndex: scopeItems.findIndex((item) => item.value === selectedScope), onSelect: handleScopeChange, onHighlight: handleScopeChange, isFocused: effectiveFocusSection === 'scope', showNumbers: effectiveFocusSection === 'scope', priority: effectiveFocusSection === 'scope' })] })), _jsx(Box, { height: 1 }), _jsx(Box, { marginX: 1, children: _jsxs(Text, { color: theme.text.secondary, children: ["(Use Enter to select, ", formatCommand(Command.CLEAR_SCREEN), " to reset", finalShowScopeSelector ? ', Tab to change focus' : '', ", Esc to close)"] }) }), footer && _jsx(Box, { marginX: 1, children: footer.content })] }) }));
}
//# sourceMappingURL=BaseSettingsDialog.js.map