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, { useState, useEffect, useCallback, useRef } 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, stripUnsafeCharacters, cpIndexToOffset, } from '../../utils/textUtils.js';
import { useKeypress } from '../../hooks/useKeypress.js';
import { keyMatchers, Command } from '../../keyMatchers.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, footerContent, }) {
    // Internal state
    const [activeIndex, setActiveIndex] = useState(0);
    const [scrollOffset, setScrollOffset] = useState(0);
    const [focusSection, setFocusSection] = useState('settings');
    const [editingKey, setEditingKey] = useState(null);
    const [editBuffer, setEditBuffer] = useState('');
    const [editCursorPos, setEditCursorPos] = useState(0);
    const [cursorVisible, setCursorVisible] = useState(true);
    const prevItemsRef = useRef(items);
    // Preserve focus when items change (e.g., search filter)
    useEffect(() => {
        const prevItems = prevItemsRef.current;
        if (prevItems !== items) {
            const prevActiveItem = prevItems[activeIndex];
            if (prevActiveItem) {
                const newIndex = items.findIndex((i) => i.key === prevActiveItem.key);
                if (newIndex !== -1) {
                    // Item still exists in the filtered list, keep focus on it
                    setActiveIndex(newIndex);
                    // Adjust scroll offset to ensure the item is visible
                    let newScroll = scrollOffset;
                    if (newIndex < scrollOffset)
                        newScroll = newIndex;
                    else if (newIndex >= scrollOffset + maxItemsToShow)
                        newScroll = newIndex - maxItemsToShow + 1;
                    const maxScroll = Math.max(0, items.length - maxItemsToShow);
                    setScrollOffset(Math.min(newScroll, maxScroll));
                }
                else {
                    // Item was filtered out, reset to the top
                    setActiveIndex(0);
                    setScrollOffset(0);
                }
            }
            else {
                setActiveIndex(0);
                setScrollOffset(0);
            }
            prevItemsRef.current = items;
        }
    }, [items, activeIndex, scrollOffset, maxItemsToShow]);
    // Cursor blink effect
    useEffect(() => {
        if (!editingKey)
            return;
        setCursorVisible(true);
        const interval = setInterval(() => {
            setCursorVisible((v) => !v);
        }, 500);
        return () => clearInterval(interval);
    }, [editingKey]);
    // Ensure focus stays on settings when scope selection is hidden
    useEffect(() => {
        if (!showScopeSelector && focusSection === 'scope') {
            setFocusSection('settings');
        }
    }, [showScopeSelector, focusSection]);
    // Scope selector items
    const scopeItems = getScopeItems().map((item) => ({
        ...item,
        key: item.value,
    }));
    // Calculate visible items based on scroll offset
    const visibleItems = items.slice(scrollOffset, scrollOffset + maxItemsToShow);
    // Show scroll indicators if there are more items than can be displayed
    const showScrollUp = items.length > maxItemsToShow;
    const showScrollDown = items.length > maxItemsToShow;
    // Get current item
    const currentItem = items[activeIndex];
    // Start editing a field
    const startEditing = useCallback((key, initialValue) => {
        setEditingKey(key);
        setEditBuffer(initialValue);
        setEditCursorPos(cpLen(initialValue));
        setCursorVisible(true);
    }, []);
    // Commit edit and exit edit mode
    const commitEdit = useCallback(() => {
        if (editingKey && currentItem) {
            onEditCommit(editingKey, editBuffer, currentItem);
        }
        setEditingKey(null);
        setEditBuffer('');
        setEditCursorPos(0);
    }, [editingKey, editBuffer, currentItem, onEditCommit]);
    // Handle scope highlight (for RadioButtonSelect)
    const handleScopeHighlight = useCallback((scope) => {
        onScopeChange?.(scope);
    }, [onScopeChange]);
    // Handle scope select (for RadioButtonSelect)
    const handleScopeSelect = useCallback((scope) => {
        onScopeChange?.(scope);
    }, [onScopeChange]);
    // Keyboard handling
    useKeypress((key) => {
        // Let parent handle custom keys first
        if (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)) {
                setEditCursorPos((p) => Math.max(0, p - 1));
                return;
            }
            if (keyMatchers[Command.MOVE_RIGHT](key)) {
                setEditCursorPos((p) => Math.min(cpLen(editBuffer), p + 1));
                return;
            }
            if (keyMatchers[Command.HOME](key)) {
                setEditCursorPos(0);
                return;
            }
            if (keyMatchers[Command.END](key)) {
                setEditCursorPos(cpLen(editBuffer));
                return;
            }
            // Backspace
            if (keyMatchers[Command.DELETE_CHAR_LEFT](key)) {
                if (editCursorPos > 0) {
                    setEditBuffer((b) => {
                        const before = cpSlice(b, 0, editCursorPos - 1);
                        const after = cpSlice(b, editCursorPos);
                        return before + after;
                    });
                    setEditCursorPos((p) => p - 1);
                }
                return;
            }
            // Delete
            if (keyMatchers[Command.DELETE_CHAR_RIGHT](key)) {
                if (editCursorPos < cpLen(editBuffer)) {
                    setEditBuffer((b) => {
                        const before = cpSlice(b, 0, editCursorPos);
                        const after = cpSlice(b, editCursorPos + 1);
                        return before + after;
                    });
                }
                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
            if (keyMatchers[Command.DIALOG_NAVIGATION_UP](key)) {
                commitEdit();
                const newIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
                setActiveIndex(newIndex);
                if (newIndex === items.length - 1) {
                    setScrollOffset(Math.max(0, items.length - maxItemsToShow));
                }
                else if (newIndex < scrollOffset) {
                    setScrollOffset(newIndex);
                }
                return;
            }
            if (keyMatchers[Command.DIALOG_NAVIGATION_DOWN](key)) {
                commitEdit();
                const newIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0;
                setActiveIndex(newIndex);
                if (newIndex === 0) {
                    setScrollOffset(0);
                }
                else if (newIndex >= scrollOffset + maxItemsToShow) {
                    setScrollOffset(newIndex - maxItemsToShow + 1);
                }
                return;
            }
            // Character input
            let ch = key.sequence;
            let isValidChar = false;
            if (type === 'number') {
                isValidChar = /[0-9\-+.]/.test(ch);
            }
            else {
                isValidChar = ch.length === 1 && ch.charCodeAt(0) >= 32;
                // Sanitize string input to prevent unsafe characters
                ch = stripUnsafeCharacters(ch);
            }
            if (isValidChar && ch.length > 0) {
                setEditBuffer((b) => {
                    const before = cpSlice(b, 0, editCursorPos);
                    const after = cpSlice(b, editCursorPos);
                    return before + ch + after;
                });
                setEditCursorPos((p) => p + 1);
            }
            return;
        }
        // Not in edit mode - handle navigation and actions
        if (focusSection === 'settings') {
            // Up/Down navigation with wrap-around
            if (keyMatchers[Command.DIALOG_NAVIGATION_UP](key)) {
                const newIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
                setActiveIndex(newIndex);
                if (newIndex === items.length - 1) {
                    setScrollOffset(Math.max(0, items.length - maxItemsToShow));
                }
                else if (newIndex < scrollOffset) {
                    setScrollOffset(newIndex);
                }
                return true;
            }
            if (keyMatchers[Command.DIALOG_NAVIGATION_DOWN](key)) {
                const newIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0;
                setActiveIndex(newIndex);
                if (newIndex === 0) {
                    setScrollOffset(0);
                }
                else if (newIndex >= scrollOffset + maxItemsToShow) {
                    setScrollOffset(newIndex - maxItemsToShow + 1);
                }
                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' && showScopeSelector) {
            setFocusSection((s) => (s === 'settings' ? 'scope' : 'settings'));
            return;
        }
        // Escape - close dialog
        if (keyMatchers[Command.ESCAPE](key)) {
            onClose();
            return;
        }
        return;
    }, {
        isActive: true,
        priority: focusSection === 'settings' && !editingKey,
    });
    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: focusSection === 'settings' && !editingKey, wrap: "truncate", children: [focusSection === 'settings' ? '> ' : '  ', title, ' '] }) }), searchEnabled && searchBuffer && (_jsx(Box, { borderStyle: "round", borderColor: editingKey
                        ? theme.border.default
                        : focusSection === 'settings'
                            ? theme.border.focused
                            : theme.border.default, paddingX: 1, height: 3, marginTop: 1, children: _jsx(TextInput, { focus: focusSection === '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 + scrollOffset;
                            const isActive = focusSection === '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", children: [_jsx(Box, { minWidth: 2, flexShrink: 0, children: _jsx(Text, { color: isActive ? theme.status.success : 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.status.success : 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.status.success
                                                                : 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 }), showScopeSelector && (_jsxs(Box, { marginX: 1, flexDirection: "column", children: [_jsxs(Text, { bold: focusSection === 'scope', wrap: "truncate", children: [focusSection === 'scope' ? '> ' : '  ', "Apply To"] }), _jsx(RadioButtonSelect, { items: scopeItems, initialIndex: scopeItems.findIndex((item) => item.value === selectedScope), onSelect: handleScopeSelect, onHighlight: handleScopeHighlight, isFocused: focusSection === 'scope', showNumbers: focusSection === 'scope', priority: focusSection === 'scope' })] })), _jsx(Box, { height: 1 }), _jsx(Box, { marginX: 1, children: _jsxs(Text, { color: theme.text.secondary, children: ["(Use Enter to select, Ctrl+L to reset", showScopeSelector ? ', Tab to change focus' : '', ", Esc to close)"] }) }), footerContent && _jsx(Box, { marginX: 1, children: footerContent })] }) }));
}
//# sourceMappingURL=BaseSettingsDialog.js.map