import { jsx as _jsx } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { render } from '../../../test-utils/render.js';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
import { Text } from 'ink';
import { BaseSettingsDialog, } from './BaseSettingsDialog.js';
import { KeypressProvider } from '../../contexts/KeypressContext.js';
import { SettingScope } from '../../../config/settings.js';
vi.mock('../../contexts/UIStateContext.js', () => ({
    useUIState: () => ({
        mainAreaWidth: 100,
    }),
}));
var TerminalKeys;
(function (TerminalKeys) {
    TerminalKeys["ENTER"] = "\r";
    TerminalKeys["TAB"] = "\t";
    TerminalKeys["UP_ARROW"] = "\u001B[A";
    TerminalKeys["DOWN_ARROW"] = "\u001B[B";
    TerminalKeys["LEFT_ARROW"] = "\u001B[D";
    TerminalKeys["RIGHT_ARROW"] = "\u001B[C";
    TerminalKeys["ESCAPE"] = "\u001B";
    TerminalKeys["BACKSPACE"] = "\b";
    TerminalKeys["CTRL_L"] = "\f";
})(TerminalKeys || (TerminalKeys = {}));
const createMockItems = (count = 4) => {
    const items = [
        {
            key: 'boolean-setting',
            label: 'Boolean Setting',
            description: 'A boolean setting for testing',
            displayValue: 'true',
            rawValue: true,
            type: 'boolean',
        },
        {
            key: 'string-setting',
            label: 'String Setting',
            description: 'A string setting for testing',
            displayValue: 'test-value',
            rawValue: 'test-value',
            type: 'string',
        },
        {
            key: 'number-setting',
            label: 'Number Setting',
            description: 'A number setting for testing',
            displayValue: '42',
            rawValue: 42,
            type: 'number',
        },
        {
            key: 'enum-setting',
            label: 'Enum Setting',
            description: 'An enum setting for testing',
            displayValue: 'option-a',
            rawValue: 'option-a',
            type: 'enum',
        },
    ];
    // If count is larger than our base mock items, generate dynamic ones
    if (count > items.length) {
        for (let i = items.length; i < count; i++) {
            items.push({
                key: `extra-setting-${i}`,
                label: `Extra Setting ${i}`,
                displayValue: `value-${i}`,
                type: 'string',
            });
        }
    }
    return items.slice(0, count);
};
describe('BaseSettingsDialog', () => {
    let mockOnItemToggle;
    let mockOnEditCommit;
    let mockOnItemClear;
    let mockOnClose;
    let mockOnScopeChange;
    beforeEach(() => {
        vi.clearAllMocks();
        mockOnItemToggle = vi.fn();
        mockOnEditCommit = vi.fn();
        mockOnItemClear = vi.fn();
        mockOnClose = vi.fn();
        mockOnScopeChange = vi.fn();
    });
    const renderDialog = async (props = {}) => {
        const defaultProps = {
            title: 'Test Settings',
            items: createMockItems(),
            selectedScope: SettingScope.User,
            maxItemsToShow: 8,
            onItemToggle: mockOnItemToggle,
            onEditCommit: mockOnEditCommit,
            onItemClear: mockOnItemClear,
            onClose: mockOnClose,
            ...props,
        };
        const result = render(_jsx(KeypressProvider, { children: _jsx(BaseSettingsDialog, { ...defaultProps }) }));
        await result.waitUntilReady();
        return result;
    };
    describe('rendering', () => {
        it('should render the dialog with title', async () => {
            const { lastFrame, unmount } = await renderDialog();
            expect(lastFrame()).toContain('Test Settings');
            unmount();
        });
        it('should render all items', async () => {
            const { lastFrame, unmount } = await renderDialog();
            const frame = lastFrame();
            expect(frame).toContain('Boolean Setting');
            expect(frame).toContain('String Setting');
            expect(frame).toContain('Number Setting');
            expect(frame).toContain('Enum Setting');
            unmount();
        });
        it('should render help text with Ctrl+L for reset', async () => {
            const { lastFrame, unmount } = await renderDialog();
            const frame = lastFrame();
            expect(frame).toContain('Use Enter to select');
            expect(frame).toContain('Ctrl+L to reset');
            expect(frame).toContain('Tab to change focus');
            expect(frame).toContain('Esc to close');
            unmount();
        });
        it('should render scope selector when showScopeSelector is true', async () => {
            const { lastFrame, unmount } = await renderDialog({
                showScopeSelector: true,
                onScopeChange: mockOnScopeChange,
            });
            expect(lastFrame()).toContain('Apply To');
            unmount();
        });
        it('should not render scope selector when showScopeSelector is false', async () => {
            const { lastFrame, unmount } = await renderDialog({
                showScopeSelector: false,
            });
            expect(lastFrame({ allowEmpty: true })).not.toContain('Apply To');
            unmount();
        });
        it('should render footer content when provided', async () => {
            const { lastFrame, unmount } = await renderDialog({
                footer: {
                    content: _jsx(Text, { children: "Custom Footer" }),
                    height: 1,
                },
            });
            expect(lastFrame()).toContain('Custom Footer');
            unmount();
        });
    });
    describe('keyboard navigation', () => {
        it('should close dialog on Escape', async () => {
            const { stdin, waitUntilReady, unmount } = await renderDialog();
            await act(async () => {
                stdin.write(TerminalKeys.ESCAPE);
            });
            // Escape key has a 50ms timeout in KeypressContext, so we need to wrap waitUntilReady in act
            await act(async () => {
                await waitUntilReady();
            });
            await waitFor(() => {
                expect(mockOnClose).toHaveBeenCalled();
            });
            unmount();
        });
        it('should navigate down with arrow key', async () => {
            const { lastFrame, stdin, waitUntilReady, unmount } = await renderDialog();
            // Initially first item is active (indicated by bullet point)
            const initialFrame = lastFrame();
            expect(initialFrame).toContain('Boolean Setting');
            // Press down arrow
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            // Navigation should move to next item
            await waitFor(() => {
                const frame = lastFrame();
                // The active indicator should now be on a different row
                expect(frame).toContain('String Setting');
            });
            unmount();
        });
        it('should navigate up with arrow key', async () => {
            const { stdin, waitUntilReady, unmount } = await renderDialog();
            // Press down then up
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            await act(async () => {
                stdin.write(TerminalKeys.UP_ARROW);
            });
            await waitUntilReady();
            // Should be back at first item
            await waitFor(() => {
                // First item should be active again
                expect(mockOnClose).not.toHaveBeenCalled();
            });
            unmount();
        });
        it('should wrap around when navigating past last item', async () => {
            const items = createMockItems(2); // Only 2 items
            const { stdin, waitUntilReady, unmount } = await renderDialog({ items });
            // Press down twice to go past the last item
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            // Should wrap to first item - verify no crash
            await waitFor(() => {
                expect(mockOnClose).not.toHaveBeenCalled();
            });
            unmount();
        });
        it('should wrap around when navigating before first item', async () => {
            const { stdin, waitUntilReady, unmount } = await renderDialog();
            // Press up at first item
            await act(async () => {
                stdin.write(TerminalKeys.UP_ARROW);
            });
            await waitUntilReady();
            // Should wrap to last item - verify no crash
            await waitFor(() => {
                expect(mockOnClose).not.toHaveBeenCalled();
            });
            unmount();
        });
        it('should switch focus with Tab when scope selector is shown', async () => {
            const { lastFrame, stdin, waitUntilReady, unmount } = await renderDialog({
                showScopeSelector: true,
                onScopeChange: mockOnScopeChange,
            });
            // Initially settings section is focused (indicated by >)
            expect(lastFrame()).toContain('> Test Settings');
            // Press Tab to switch to scope selector
            await act(async () => {
                stdin.write(TerminalKeys.TAB);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(lastFrame()).toContain('> Apply To');
            });
            unmount();
        });
    });
    describe('scrolling and resizing list (search filtering)', () => {
        it('should preserve focus on the active item if it remains in the filtered list', async () => {
            const items = createMockItems(5); // items 0 to 4
            const { rerender, stdin, lastFrame, waitUntilReady, unmount } = await renderDialog({
                items,
                maxItemsToShow: 5,
            });
            // Move focus down to item 2 ("Number Setting")
            // Separate acts needed so React state updates between keypresses
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            // Rerender with a filtered list where "Number Setting" is now at index 1
            const filteredItems = [items[0], items[2], items[4]];
            await act(async () => {
                rerender(_jsx(KeypressProvider, { children: _jsx(BaseSettingsDialog, { title: "Test Settings", items: filteredItems, selectedScope: SettingScope.User, maxItemsToShow: 5, onItemToggle: mockOnItemToggle, onEditCommit: mockOnEditCommit, onItemClear: mockOnItemClear, onClose: mockOnClose }) }));
            });
            await waitUntilReady();
            // Verify the dialog hasn't crashed and the items are displayed
            await waitFor(() => {
                const frame = lastFrame();
                expect(frame).toContain('Boolean Setting');
                expect(frame).toContain('Number Setting');
                expect(frame).toContain('Extra Setting 4');
                expect(frame).not.toContain('No matches found.');
            });
            // Press Enter. If focus was preserved, it should be on "Number Setting" (index 1).
            // Since it's a number, it enters edit mode (mockOnItemToggle is NOT called).
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnItemToggle).not.toHaveBeenCalled();
            });
            unmount();
        });
        it('should reset focus to the top if the active item is filtered out', async () => {
            const items = createMockItems(5);
            const { rerender, stdin, lastFrame, waitUntilReady, unmount } = await renderDialog({
                items,
                maxItemsToShow: 5,
            });
            // Move focus down to item 2 ("Number Setting")
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            // Rerender with a filtered list that EXCLUDES "Number Setting"
            const filteredItems = [items[0], items[1]];
            await act(async () => {
                rerender(_jsx(KeypressProvider, { children: _jsx(BaseSettingsDialog, { title: "Test Settings", items: filteredItems, selectedScope: SettingScope.User, maxItemsToShow: 5, onItemToggle: mockOnItemToggle, onEditCommit: mockOnEditCommit, onItemClear: mockOnItemClear, onClose: mockOnClose }) }));
            });
            await waitUntilReady();
            await waitFor(() => {
                const frame = lastFrame();
                expect(frame).toContain('Boolean Setting');
                expect(frame).toContain('String Setting');
            });
            // Press Enter. Since focus reset to index 0 ("Boolean Setting"), it should toggle.
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnItemToggle).toHaveBeenCalledWith('boolean-setting', expect.anything());
            });
            unmount();
        });
    });
    describe('item interactions', () => {
        it('should call onItemToggle for boolean items on Enter', async () => {
            const { stdin, waitUntilReady, unmount } = await renderDialog();
            // Press Enter on first item (boolean)
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnItemToggle).toHaveBeenCalledWith('boolean-setting', expect.objectContaining({ type: 'boolean' }));
            });
            unmount();
        });
        it('should call onItemToggle for enum items on Enter', async () => {
            const items = createMockItems(4);
            // Move enum to first position
            const enumItem = items.find((i) => i.type === 'enum');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [enumItem],
            });
            // Press Enter on enum item
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnItemToggle).toHaveBeenCalledWith('enum-setting', expect.objectContaining({ type: 'enum' }));
            });
            unmount();
        });
        it('should enter edit mode for string items on Enter', async () => {
            const items = createMockItems(4);
            const stringItem = items.find((i) => i.type === 'string');
            const { lastFrame, stdin, waitUntilReady, unmount } = await renderDialog({
                items: [stringItem],
            });
            // Press Enter to start editing
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Should show the edit buffer with cursor
            await waitFor(() => {
                const frame = lastFrame();
                // In edit mode, the value should be displayed (possibly with cursor)
                expect(frame).toContain('test-value');
            });
            unmount();
        });
        it('should enter edit mode for number items on Enter', async () => {
            const items = createMockItems(4);
            const numberItem = items.find((i) => i.type === 'number');
            const { lastFrame, stdin, waitUntilReady, unmount } = await renderDialog({
                items: [numberItem],
            });
            // Press Enter to start editing
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Should show the edit buffer
            await waitFor(() => {
                const frame = lastFrame();
                expect(frame).toContain('42');
            });
            unmount();
        });
        it('should call onItemClear on Ctrl+L', async () => {
            const { stdin, waitUntilReady, unmount } = await renderDialog();
            // Press Ctrl+L to reset
            await act(async () => {
                stdin.write(TerminalKeys.CTRL_L);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnItemClear).toHaveBeenCalledWith('boolean-setting', expect.objectContaining({ type: 'boolean' }));
            });
            unmount();
        });
    });
    describe('edit mode', () => {
        it('should prioritize editValue over rawValue stringification', async () => {
            const objectItem = {
                key: 'object-setting',
                label: 'Object Setting',
                description: 'A complex object setting',
                displayValue: '{"foo":"bar"}',
                type: 'object',
                rawValue: { foo: 'bar' },
                editValue: '{"foo":"bar"}',
            };
            const { stdin } = await renderDialog({
                items: [objectItem],
            });
            // Enter edit mode and immediately commit
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalledWith('object-setting', '{"foo":"bar"}', expect.objectContaining({ type: 'object' }));
            });
        });
        it('should commit edit on Enter', async () => {
            const items = createMockItems(4);
            const stringItem = items.find((i) => i.type === 'string');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [stringItem],
            });
            // Enter edit mode
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Type some characters
            await act(async () => {
                stdin.write('x');
            });
            await waitUntilReady();
            // Commit with Enter
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalledWith('string-setting', 'test-valuex', expect.objectContaining({ type: 'string' }));
            });
            unmount();
        });
        it('should commit edit on Escape', async () => {
            const items = createMockItems(4);
            const stringItem = items.find((i) => i.type === 'string');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [stringItem],
            });
            // Enter edit mode
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Commit with Escape
            await act(async () => {
                stdin.write(TerminalKeys.ESCAPE);
            });
            // Escape key has a 50ms timeout in KeypressContext, so we need to wrap waitUntilReady in act
            await act(async () => {
                await waitUntilReady();
            });
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalled();
            });
            unmount();
        });
        it('should commit edit and navigate on Down arrow', async () => {
            const items = createMockItems(4);
            const stringItem = items.find((i) => i.type === 'string');
            const numberItem = items.find((i) => i.type === 'number');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [stringItem, numberItem],
            });
            // Enter edit mode
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Press Down to commit and navigate
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalled();
            });
            unmount();
        });
        it('should commit edit and navigate on Up arrow', async () => {
            const items = createMockItems(4);
            const stringItem = items.find((i) => i.type === 'string');
            const numberItem = items.find((i) => i.type === 'number');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [stringItem, numberItem],
            });
            // Navigate to second item
            await act(async () => {
                stdin.write(TerminalKeys.DOWN_ARROW);
            });
            await waitUntilReady();
            // Enter edit mode
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Press Up to commit and navigate
            await act(async () => {
                stdin.write(TerminalKeys.UP_ARROW);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalled();
            });
            unmount();
        });
        it('should allow number input for number fields', async () => {
            const items = createMockItems(4);
            const numberItem = items.find((i) => i.type === 'number');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [numberItem],
            });
            // Enter edit mode
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Type numbers one at a time
            await act(async () => {
                stdin.write('1');
            });
            await waitUntilReady();
            await act(async () => {
                stdin.write('2');
            });
            await waitUntilReady();
            await act(async () => {
                stdin.write('3');
            });
            await waitUntilReady();
            // Commit
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalledWith('number-setting', '42123', expect.objectContaining({ type: 'number' }));
            });
            unmount();
        });
        it('should support quick number entry for number fields', async () => {
            const items = createMockItems(4);
            const numberItem = items.find((i) => i.type === 'number');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [numberItem],
            });
            // Type a number directly (without Enter first)
            await act(async () => {
                stdin.write('5');
            });
            await waitUntilReady();
            // Should start editing with that number
            await waitFor(async () => {
                // Commit to verify
                await act(async () => {
                    stdin.write(TerminalKeys.ENTER);
                });
                await waitUntilReady();
            });
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalledWith('number-setting', '5', expect.objectContaining({ type: 'number' }));
            });
            unmount();
        });
        it('should allow j and k characters to be typed in string edit fields without triggering navigation', async () => {
            const items = createMockItems(4);
            const stringItem = items.find((i) => i.type === 'string');
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                items: [stringItem],
            });
            // Enter edit mode
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // Type 'j' - should appear in field, NOT trigger navigation
            await act(async () => {
                stdin.write('j');
            });
            await waitUntilReady();
            // Type 'k' - should appear in field, NOT trigger navigation
            await act(async () => {
                stdin.write('k');
            });
            await waitUntilReady();
            // Commit with Enter
            await act(async () => {
                stdin.write(TerminalKeys.ENTER);
            });
            await waitUntilReady();
            // j and k should be typed into the field
            await waitFor(() => {
                expect(mockOnEditCommit).toHaveBeenCalledWith('string-setting', 'test-valuejk', // entered value + j and k
                expect.objectContaining({ type: 'string' }));
            });
            unmount();
        });
    });
    describe('custom key handling', () => {
        it('should call onKeyPress and respect its return value', async () => {
            const customKeyHandler = vi.fn().mockReturnValue(true);
            const { stdin, waitUntilReady, unmount } = await renderDialog({
                onKeyPress: customKeyHandler,
            });
            // Press a key
            await act(async () => {
                stdin.write('r');
            });
            await waitUntilReady();
            await waitFor(() => {
                expect(customKeyHandler).toHaveBeenCalled();
            });
            // Since handler returned true, default behavior should be blocked
            expect(mockOnClose).not.toHaveBeenCalled();
            unmount();
        });
    });
    describe('focus management', () => {
        it('should keep focus on settings when scope selector is hidden', async () => {
            const { lastFrame, stdin, waitUntilReady, unmount } = await renderDialog({
                showScopeSelector: false,
            });
            // Press Tab - should not crash and focus should stay on settings
            await act(async () => {
                stdin.write(TerminalKeys.TAB);
            });
            await waitUntilReady();
            await waitFor(() => {
                // Should still show settings as focused
                expect(lastFrame()).toContain('> Test Settings');
            });
            unmount();
        });
    });
    describe('responsiveness', () => {
        it('should show the scope selector when availableHeight is sufficient (25)', async () => {
            const { lastFrame, unmount } = await renderDialog({
                availableHeight: 25,
                showScopeSelector: true,
            });
            const frame = lastFrame();
            expect(frame).toContain('Apply To');
            unmount();
        });
        it('should hide the scope selector when availableHeight is small (24) to show more items', async () => {
            const { lastFrame, unmount } = await renderDialog({
                availableHeight: 24,
                showScopeSelector: true,
            });
            const frame = lastFrame();
            expect(frame).not.toContain('Apply To');
            unmount();
        });
        it('should reduce the number of visible items based on height', async () => {
            // At height 25, it should show 2 items (math: (25-4 - (10+5))/3 = 2)
            const { lastFrame, unmount } = await renderDialog({
                availableHeight: 25,
                items: createMockItems(10),
            });
            const frame = lastFrame();
            // Items 0 and 1 should be there
            expect(frame).toContain('Boolean Setting');
            expect(frame).toContain('String Setting');
            // Item 2 should NOT be there
            expect(frame).not.toContain('Number Setting');
            unmount();
        });
        it('should show scroll indicators when list is truncated by height', async () => {
            const { lastFrame, unmount } = await renderDialog({
                availableHeight: 25,
                items: createMockItems(10),
            });
            const frame = lastFrame();
            // Shows both scroll indicators when the list is truncated by height
            expect(frame).toContain('▼');
            expect(frame).toContain('▲');
            unmount();
        });
    });
});
//# sourceMappingURL=BaseSettingsDialog.test.js.map