/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 *
 *
 * This test suite covers:
 * - Initial rendering and display state
 * - Keyboard navigation (arrows, vim keys, Tab)
 * - Settings toggling (Enter, Space)
 * - Focus section switching between settings and scope selector
 * - Scope selection and settings persistence across scopes
 * - Restart-required vs immediate settings behavior
 * - Complex user interaction workflows
 * - Error handling and edge cases
 * - Display values for inherited and overridden settings
 *
 */

import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { SettingsDialog } from './SettingsDialog.js';
import { SettingScope } from '../../config/settings.js';
import { createMockSettings } from '../../test-utils/settings.js';
import { makeFakeConfig } from '@google/gemini-cli-core';
import { act } from 'react';
import { TEST_ONLY } from '../../utils/settingsUtils.js';
import {
  getSettingsSchema,
  type SettingDefinition,
  type SettingsSchemaType,
} from '../../config/settingsSchema.js';
import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js';

enum TerminalKeys {
  ENTER = '\u000D',
  TAB = '\t',
  UP_ARROW = '\u001B[A',
  DOWN_ARROW = '\u001B[B',
  LEFT_ARROW = '\u001B[D',
  RIGHT_ARROW = '\u001B[C',
  ESCAPE = '\u001B',
  BACKSPACE = '\u0008',
  CTRL_P = '\u0010',
  CTRL_N = '\u000E',
}

vi.mock('../../config/settingsSchema.js', async (importOriginal) => {
  const original =
    await importOriginal<typeof import('../../config/settingsSchema.js')>();
  return {
    ...original,
    getSettingsSchema: vi.fn(original.getSettingsSchema),
  };
});

// Shared test schemas
enum StringEnum {
  FOO = 'foo',
  BAR = 'bar',
  BAZ = 'baz',
}

const ENUM_SETTING: SettingDefinition = {
  type: 'enum',
  label: 'Theme',
  options: [
    {
      label: 'Foo',
      value: StringEnum.FOO,
    },
    {
      label: 'Bar',
      value: StringEnum.BAR,
    },
    {
      label: 'Baz',
      value: StringEnum.BAZ,
    },
  ],
  category: 'UI',
  requiresRestart: false,
  default: StringEnum.BAR,
  description: 'The color theme for the UI.',
  showInDialog: true,
};

// Minimal general schema for KeypressProvider
const MINIMAL_GENERAL_SCHEMA = {
  general: {
    showInDialog: false,
    properties: {
      debugKeystrokeLogging: {
        type: 'boolean',
        label: 'Debug Keystroke Logging',
        category: 'General',
        requiresRestart: false,
        default: false,
        showInDialog: false,
      },
    },
  },
};

const ENUM_FAKE_SCHEMA: SettingsSchemaType = {
  ...MINIMAL_GENERAL_SCHEMA,
  ui: {
    showInDialog: false,
    properties: {
      theme: {
        ...ENUM_SETTING,
      },
    },
  },
} as unknown as SettingsSchemaType;

const ARRAY_FAKE_SCHEMA: SettingsSchemaType = {
  ...MINIMAL_GENERAL_SCHEMA,
  context: {
    type: 'object',
    label: 'Context',
    category: 'Context',
    requiresRestart: false,
    default: {},
    description: 'Context settings.',
    showInDialog: false,
    properties: {
      fileFiltering: {
        type: 'object',
        label: 'File Filtering',
        category: 'Context',
        requiresRestart: false,
        default: {},
        description: 'File filtering settings.',
        showInDialog: false,
        properties: {
          customIgnoreFilePaths: {
            type: 'array',
            label: 'Custom Ignore File Paths',
            category: 'Context',
            requiresRestart: false,
            default: [] as string[],
            description: 'Additional ignore file paths.',
            showInDialog: true,
            items: { type: 'string' },
          },
        },
      },
    },
  },
  security: {
    type: 'object',
    label: 'Security',
    category: 'Security',
    requiresRestart: false,
    default: {},
    description: 'Security settings.',
    showInDialog: false,
    properties: {
      allowedExtensions: {
        type: 'array',
        label: 'Extension Source Regex Allowlist',
        category: 'Security',
        requiresRestart: false,
        default: [] as string[],
        description: 'Allowed extension source regex patterns.',
        showInDialog: true,
        items: { type: 'string' },
      },
    },
  },
} as unknown as SettingsSchemaType;

const TOOLS_SHELL_FAKE_SCHEMA: SettingsSchemaType = {
  ...MINIMAL_GENERAL_SCHEMA,
  tools: {
    type: 'object',
    label: 'Tools',
    category: 'Tools',
    requiresRestart: false,
    default: {},
    description: 'Tool settings.',
    showInDialog: false,
    properties: {
      shell: {
        type: 'object',
        label: 'Shell',
        category: 'Tools',
        requiresRestart: false,
        default: {},
        description: 'Shell tool settings.',
        showInDialog: false,
        properties: {
          showColor: {
            type: 'boolean',
            label: 'Show Color',
            category: 'Tools',
            requiresRestart: false,
            default: false,
            description: 'Show color in shell output.',
            showInDialog: true,
          },
          enableInteractiveShell: {
            type: 'boolean',
            label: 'Enable Interactive Shell',
            category: 'Tools',
            requiresRestart: true,
            default: true,
            description: 'Enable interactive shell mode.',
            showInDialog: true,
          },
          pager: {
            type: 'string',
            label: 'Pager',
            category: 'Tools',
            requiresRestart: false,
            default: 'cat',
            description: 'The pager command to use for shell output.',
            showInDialog: true,
          },
        },
      },
    },
  },
} as unknown as SettingsSchemaType;

// Helper function to render SettingsDialog with standard wrapper
const renderDialog = async (
  settings: ReturnType<typeof createMockSettings>,
  onSelect: ReturnType<typeof vi.fn>,
  options?: {
    onRestartRequest?: ReturnType<typeof vi.fn>;
    availableTerminalHeight?: number;
  },
) =>
  renderWithProviders(
    <SettingsDialog
      onSelect={onSelect}
      onRestartRequest={options?.onRestartRequest}
      availableTerminalHeight={options?.availableTerminalHeight}
    />,
    {
      settings,
      config: makeFakeConfig(),
      uiState: { terminalBackgroundColor: undefined },
    },
  );

describe('SettingsDialog', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.spyOn(
      terminalCapabilityManager,
      'isKittyProtocolEnabled',
    ).mockReturnValue(true);
  });

  afterEach(() => {
    TEST_ONLY.clearFlattenedSchema();
    vi.clearAllMocks();
    vi.resetAllMocks();
  });

  describe('Initial Rendering', () => {
    it('should render the settings dialog with default state', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      const output = lastFrame();
      expect(output).toContain('Settings');
      expect(output).toContain('Apply To');
      // Use regex for more flexible help text matching
      expect(output).toMatch(/Enter.*select.*Esc.*close/);
      unmount();
    });

    it('should accept availableTerminalHeight prop without errors', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect, {
        availableTerminalHeight: 20,
      });

      const output = lastFrame();
      // Should still render properly with the height prop
      expect(output).toContain('Settings');
      // Use regex for more flexible help text matching
      expect(output).toMatch(/Enter.*select.*Esc.*close/);
      unmount();
    });

    it('should render settings list with visual indicators', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const renderResult = await renderDialog(settings, onSelect);

      await expect(renderResult).toMatchSvgSnapshot();
      renderResult.unmount();
    });

    it('should use almost full height of the window but no more when the window height is 25 rows', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      // Render with a fixed height of 25 rows
      const { lastFrame, unmount } = await renderDialog(settings, onSelect, {
        availableTerminalHeight: 25,
      });

      // Wait for the dialog to render
      await waitFor(() => {
        const output = lastFrame();
        expect(output).toBeDefined();
        const lines = output.trim().split('\n');

        expect(lines.length).toBeGreaterThanOrEqual(24);
        expect(lines.length).toBeLessThanOrEqual(25);
      });
      unmount();
    });
  });

  describe('Setting Descriptions', () => {
    it('should render descriptions for settings that have them', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      const output = lastFrame();
      // 'general.vimMode' has description 'Enable Vim keybindings' in settingsSchema.ts
      expect(output).toContain('Vim Mode');
      expect(output).toContain('Enable Vim keybindings');
      // 'general.enableAutoUpdate' has description 'Enable automatic updates.'
      expect(output).toContain('Enable Auto Update');
      expect(output).toContain('Enable automatic updates.');
      unmount();
    });
  });

  describe('Settings Navigation', () => {
    it.each([
      {
        name: 'arrow keys',
        down: TerminalKeys.DOWN_ARROW,
        up: TerminalKeys.UP_ARROW,
      },
      {
        name: 'emacs keys (Ctrl+P/N)',
        down: TerminalKeys.CTRL_N,
        up: TerminalKeys.CTRL_P,
      },
    ])('should navigate with $name', async ({ down, up }) => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, lastFrame, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      const initialFrame = lastFrame();
      expect(initialFrame).toContain('Vim Mode');

      // Navigate down
      await act(async () => {
        stdin.write(down);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('Enable Auto Update');
      });

      // Navigate up
      await act(async () => {
        stdin.write(up);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('Vim Mode');
      });

      unmount();
    });

    it('should allow j and k characters to be typed in search without triggering navigation', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();
      const { lastFrame, stdin, waitUntilReady, unmount } = await renderDialog(
        settings,
        onSelect,
      );

      // Enter 'j' and 'k' in search
      await act(async () => stdin.write('j'));
      await waitUntilReady();
      await act(async () => stdin.write('k'));
      await waitUntilReady();

      await waitFor(() => {
        const frame = lastFrame();
        // The search box should contain 'jk'
        expect(frame).toContain('jk');
        // Since 'jk' doesn't match any setting labels, it should say "No matches found."
        expect(frame).toContain('No matches found.');
      });
      unmount();
    });

    it('wraps around when at the top of the list', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, lastFrame, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Try to go up from first item
      await act(async () => {
        stdin.write(TerminalKeys.UP_ARROW);
      });
      await waitUntilReady();

      await waitFor(() => {
        // Should wrap to last setting (without relying on exact bullet character)
        expect(lastFrame()).toContain('Hook Notifications');
      });

      unmount();
    });
  });

  describe('Settings Toggling', () => {
    it('should toggle setting with Enter key', async () => {
      const settings = createMockSettings();
      const setValueSpy = vi.spyOn(settings, 'setValue');
      const onSelect = vi.fn();

      const { stdin, unmount, lastFrame } = await renderDialog(
        settings,
        onSelect,
      );

      // Wait for initial render and verify we're on Vim Mode (first setting)
      await waitFor(() => {
        expect(lastFrame()).toContain('Vim Mode');
      });

      // Toggle the setting (Vim Mode is the first setting now)
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string);
      });

      // Wait for setValue to be called
      await waitFor(() => {
        expect(setValueSpy).toHaveBeenCalled();
      });

      expect(setValueSpy).toHaveBeenCalledWith(
        SettingScope.User,
        'general.vimMode',
        true,
      );

      unmount();
    });

    describe('enum values', () => {
      it.each([
        {
          name: 'toggles to next value',
          initialValue: undefined,
          expectedValue: StringEnum.BAZ,
        },
        {
          name: 'loops back to first value when at end',
          initialValue: StringEnum.BAZ,
          expectedValue: StringEnum.FOO,
        },
      ])('$name', async ({ initialValue, expectedValue }) => {
        vi.mocked(getSettingsSchema).mockReturnValue(ENUM_FAKE_SCHEMA);

        const settings = createMockSettings();
        if (initialValue !== undefined) {
          settings.setValue(SettingScope.User, 'ui.theme', initialValue);
        }
        const setValueSpy = vi.spyOn(settings, 'setValue');

        const onSelect = vi.fn();

        const { stdin, unmount, waitUntilReady } = await renderDialog(
          settings,
          onSelect,
        );

        await act(async () => {
          stdin.write(TerminalKeys.DOWN_ARROW as string);
        });
        await waitUntilReady();

        await act(async () => {
          stdin.write(TerminalKeys.ENTER as string);
        });
        await waitUntilReady();

        await waitFor(() => {
          expect(setValueSpy).toHaveBeenCalledWith(
            SettingScope.User,
            'ui.theme',
            expectedValue,
          );
        });

        unmount();
      });
    });

    it('should handle vim mode setting specially', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Navigate to vim mode setting and toggle it
      // This would require knowing the exact position, so we'll just test that the mock is called
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter key
      });
      await waitUntilReady();

      // The mock should potentially be called if vim mode was toggled
      unmount();
    });
  });

  describe('Scope Selection', () => {
    it('should switch between scopes', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Switch to scope focus
      await act(async () => {
        stdin.write(TerminalKeys.TAB); // Tab key
        // Select different scope (numbers 1-3 typically available)
        stdin.write('2'); // Select second scope option
      });
      await waitUntilReady();

      unmount();
    });

    it('should reset to settings focus when scope is selected', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // Wait for initial render
      await waitFor(() => {
        expect(lastFrame()).toContain('Vim Mode');
      });

      // The UI should show the settings section is active and scope section is inactive
      expect(lastFrame()).toContain('Vim Mode'); // Settings section active
      expect(lastFrame()).toContain('Apply To'); // Scope section (don't rely on exact spacing)

      // This test validates the initial state - scope selection behavior
      // is complex due to keypress handling, so we focus on state validation

      unmount();
    });
  });

  describe('Restart Prompt', () => {
    it('should show restart prompt for restart-required settings', async () => {
      const settings = createMockSettings();
      const onRestartRequest = vi.fn();

      const { unmount } = await renderDialog(settings, vi.fn(), {
        onRestartRequest,
      });

      // This test would need to trigger a restart-required setting change
      // The exact steps depend on which settings require restart

      unmount();
    });

    it('should handle restart request when r is pressed', async () => {
      const settings = createMockSettings();
      const onRestartRequest = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        vi.fn(),
        {
          onRestartRequest,
        },
      );

      // Press 'r' key (this would only work if restart prompt is showing)
      await act(async () => {
        stdin.write('r');
      });
      await waitUntilReady();

      // If restart prompt was showing, onRestartRequest should be called
      unmount();
    });
  });

  describe('Escape Key Behavior', () => {
    it('should call onSelect with undefined when Escape is pressed', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // Wait for initial render
      await waitFor(() => {
        expect(lastFrame()).toContain('Vim Mode');
      });

      // Verify the dialog is rendered properly
      expect(lastFrame()).toContain('Settings');
      expect(lastFrame()).toContain('Apply To');

      // This test validates rendering - escape key behavior depends on complex
      // keypress handling that's difficult to test reliably in this environment

      unmount();
    });
  });

  describe('Settings Persistence', () => {
    it('should persist settings across scope changes', async () => {
      const settings = createMockSettings({ vimMode: true });
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Switch to scope selector and change scope
      await act(async () => {
        stdin.write(TerminalKeys.TAB as string); // Tab
        stdin.write('2'); // Select workspace scope
      });
      await waitUntilReady();

      // Settings should be reloaded for new scope
      unmount();
    });

    it('should show different values for different scopes', async () => {
      const settings = createMockSettings({
        user: {
          settings: { vimMode: true },
          originalSettings: { vimMode: true },
          path: '',
        },
        system: {
          settings: { vimMode: false },
          originalSettings: { vimMode: false },
          path: '',
        },
        workspace: {
          settings: { autoUpdate: false },
          originalSettings: { autoUpdate: false },
          path: '',
        },
      });
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // Should show user scope values initially
      const output = lastFrame();
      expect(output).toContain('Settings');
      unmount();
    });
  });

  describe('Complex State Management', () => {
    it('should track modified settings correctly', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Toggle a setting, then toggle another setting
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW as string); // Down
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter
      });
      await waitUntilReady();

      // Should track multiple modified settings
      unmount();
    });

    it('should handle scrolling when there are many settings', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Navigate down many times to test scrolling
      await act(async () => {
        for (let i = 0; i < 10; i++) {
          stdin.write(TerminalKeys.DOWN_ARROW as string); // Down arrow
        }
      });
      await waitUntilReady();

      unmount();
    });
  });

  describe('Specific Settings Behavior', () => {
    it('should show correct display values for settings with different states', async () => {
      const settings = createMockSettings({
        user: {
          settings: { vimMode: true, hideTips: false },
          originalSettings: { vimMode: true, hideTips: false },
          path: '',
        },
        system: {
          settings: { hideWindowTitle: true },
          originalSettings: { hideWindowTitle: true },
          path: '',
        },
        workspace: {
          settings: { ideMode: false },
          originalSettings: { ideMode: false },
          path: '',
        },
      });
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      const output = lastFrame();
      // Should contain settings labels
      expect(output).toContain('Settings');
      unmount();
    });

    it('should handle immediate settings save for non-restart-required settings', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Toggle a non-restart-required setting (like hideTips)
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter - toggle current setting
      });
      await waitUntilReady();

      // Should save immediately without showing restart prompt
      unmount();
    });

    it('should show restart prompt for restart-required settings', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // This test would need to navigate to a specific restart-required setting
      // Since we can't easily target specific settings, we test the general behavior

      // Should not show restart prompt initially
      await waitFor(() => {
        expect(lastFrame()).not.toContain(
          'Changes that require a restart have been modified',
        );
      });

      unmount();
    });

    it('should clear restart prompt when switching scopes', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { unmount } = await renderDialog(settings, onSelect);

      // Restart prompt should be cleared when switching scopes
      unmount();
    });
  });

  describe('Settings Display Values', () => {
    it('should show correct values for inherited settings', async () => {
      const settings = createMockSettings({
        system: {
          settings: { vimMode: true, hideWindowTitle: false },
          originalSettings: { vimMode: true, hideWindowTitle: false },
          path: '',
        },
      });
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      const output = lastFrame();
      // Settings should show inherited values
      expect(output).toContain('Settings');
      unmount();
    });

    it('should show override indicator for overridden settings', async () => {
      const settings = createMockSettings({
        user: {
          settings: { vimMode: false },
          originalSettings: { vimMode: false },
          path: '',
        },
        system: {
          settings: { vimMode: true },
          originalSettings: { vimMode: true },
          path: '',
        },
      });
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      const output = lastFrame();
      // Should show settings with override indicators
      expect(output).toContain('Settings');
      unmount();
    });
  });

  describe('Race Condition Regression Tests', () => {
    it.each([
      {
        name: 'not reset sibling settings when toggling a nested setting multiple times',
        toggleCount: 5,
        shellSettings: {
          showColor: false,
          enableInteractiveShell: true,
        },
        expectedSiblings: {
          enableInteractiveShell: true,
        },
      },
      {
        name: 'preserve multiple sibling settings in nested objects during rapid toggles',
        toggleCount: 3,
        shellSettings: {
          showColor: false,
          enableInteractiveShell: true,
          pager: 'less',
        },
        expectedSiblings: {
          enableInteractiveShell: true,
          pager: 'less',
        },
      },
    ])('should $name', async ({ toggleCount, shellSettings }) => {
      vi.mocked(getSettingsSchema).mockReturnValue(TOOLS_SHELL_FAKE_SCHEMA);

      const settings = createMockSettings({
        tools: {
          shell: shellSettings,
        },
      });
      const setValueSpy = vi.spyOn(settings, 'setValue');

      const onSelect = vi.fn();

      const { stdin, unmount } = await renderDialog(settings, onSelect);

      for (let i = 0; i < toggleCount; i++) {
        act(() => {
          stdin.write(TerminalKeys.ENTER as string);
        });
      }

      await waitFor(() => {
        expect(setValueSpy).toHaveBeenCalled();
      });

      // With the store pattern, setValue is called atomically per key.
      // Sibling preservation is handled by LoadedSettings internally.
      const calls = setValueSpy.mock.calls;
      expect(calls.length).toBeGreaterThan(0);
      calls.forEach((call) => {
        // Each call should target only 'tools.shell.showColor'
        expect(call[1]).toBe('tools.shell.showColor');
      });

      unmount();
    });
  });

  describe('Keyboard Shortcuts Edge Cases', () => {
    it('should handle rapid key presses gracefully', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Rapid navigation
      await act(async () => {
        for (let i = 0; i < 5; i++) {
          stdin.write(TerminalKeys.DOWN_ARROW as string);
          stdin.write(TerminalKeys.UP_ARROW as string);
        }
      });
      await waitUntilReady();

      // Should not crash
      unmount();
    });

    it.each([
      { key: 'Ctrl+C', code: '\u0003' },
      { key: 'Ctrl+L', code: '\u000C' },
    ])(
      'should handle $key to reset current setting to default',
      async ({ code }) => {
        const settings = createMockSettings({ vimMode: true });
        const onSelect = vi.fn();

        const { stdin, unmount, waitUntilReady } = await renderDialog(
          settings,
          onSelect,
        );

        await act(async () => {
          stdin.write(code);
        });
        await waitUntilReady();

        // Should reset the current setting to its default value
        unmount();
      },
    );

    it('should handle navigation when only one setting exists', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Try to navigate when potentially at bounds
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW as string);
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.UP_ARROW as string);
      });
      await waitUntilReady();

      unmount();
    });

    it('should properly handle Tab navigation between sections', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // Wait for initial render
      await waitFor(() => {
        expect(lastFrame()).toContain('Vim Mode');
      });

      // Verify initial state: settings section active, scope section inactive
      expect(lastFrame()).toContain('Vim Mode'); // Settings section active
      expect(lastFrame()).toContain('Apply To'); // Scope section (don't rely on exact spacing)

      // This test validates the rendered UI structure for tab navigation
      // Actual tab behavior testing is complex due to keypress handling

      unmount();
    });
  });

  describe('Error Recovery', () => {
    it('should handle malformed settings gracefully', async () => {
      // Create settings with potentially problematic values
      const settings = createMockSettings({
        user: {
          settings: { vimMode: null as unknown as boolean },
          originalSettings: { vimMode: null as unknown as boolean },
          path: '',
        },
      });
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // Should still render without crashing
      expect(lastFrame()).toContain('Settings');
      unmount();
    });

    it('should handle missing setting definitions gracefully', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      // Should not crash even if some settings are missing definitions
      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      expect(lastFrame()).toContain('Settings');
      unmount();
    });
  });

  describe('Complex User Interactions', () => {
    it('should handle complete user workflow: navigate, toggle, change scope, exit', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, unmount } = await renderDialog(settings, onSelect);

      // Wait for initial render
      await waitFor(() => {
        expect(lastFrame()).toContain('Vim Mode');
      });

      // Verify the complete UI is rendered with all necessary sections
      expect(lastFrame()).toContain('Settings'); // Title
      expect(lastFrame()).toContain('Vim Mode'); // Active setting
      expect(lastFrame()).toContain('Apply To'); // Scope section
      expect(lastFrame()).toContain('User Settings'); // Scope options (no numbers when settings focused)
      // Use regex for more flexible help text matching
      expect(lastFrame()).toMatch(/Enter.*select.*Tab.*focus.*Esc.*close/);

      // This test validates the complete UI structure is available for user workflow
      // Individual interactions are tested in focused unit tests

      unmount();
    });

    it('should allow changing multiple settings without losing pending changes', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Toggle multiple settings
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW as string); // Down
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW as string); // Down
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Enter
      });
      await waitUntilReady();

      // The test verifies that all changes are preserved and the dialog still works
      // This tests the fix for the bug where changing one setting would reset all pending changes
      unmount();
    });

    it('should maintain state consistency during complex interactions', async () => {
      const settings = createMockSettings({ vimMode: true });
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Multiple scope changes
      await act(async () => {
        stdin.write(TerminalKeys.TAB as string); // Tab to scope
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write('2'); // Workspace
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.TAB as string); // Tab to settings
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write(TerminalKeys.TAB as string); // Tab to scope
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write('1'); // User
      });
      await waitUntilReady();

      // Should maintain consistent state
      unmount();
    });

    it('should handle restart workflow correctly', async () => {
      const settings = createMockSettings();
      const onRestartRequest = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        vi.fn(),
        {
          onRestartRequest,
        },
      );

      // This would test the restart workflow if we could trigger it
      await act(async () => {
        stdin.write('r'); // Try restart key
      });
      await waitUntilReady();

      // Without restart prompt showing, this should have no effect
      expect(onRestartRequest).not.toHaveBeenCalled();

      unmount();
    });
  });

  describe('Restart and Search Conflict Regression', () => {
    it('should prioritize restart request over search text box when showRestartPrompt is true', async () => {
      vi.mocked(getSettingsSchema).mockReturnValue(TOOLS_SHELL_FAKE_SCHEMA);
      const settings = createMockSettings();
      const onRestartRequest = vi.fn();

      const { stdin, lastFrame, unmount, waitUntilReady } = await renderDialog(
        settings,
        vi.fn(),
        {
          onRestartRequest,
        },
      );

      // Wait for initial render
      await waitFor(() => expect(lastFrame()).toContain('Show Color'));

      // Navigate to "Enable Interactive Shell" (second item in TOOLS_SHELL_FAKE_SCHEMA)
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW);
      });
      await waitUntilReady();

      // Wait for navigation to complete
      await waitFor(() =>
        expect(lastFrame()).toContain('● Enable Interactive Shell'),
      );

      // Toggle it to trigger restart required
      await act(async () => {
        stdin.write(TerminalKeys.ENTER);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain(
          'Changes that require a restart have been modified',
        );
      });

      // Press 'r' - it should call onRestartRequest, NOT be handled by search
      await act(async () => {
        stdin.write('r');
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(onRestartRequest).toHaveBeenCalled();
      });

      unmount();
    });

    it('should hide search box when showRestartPrompt is true', async () => {
      vi.mocked(getSettingsSchema).mockReturnValue(TOOLS_SHELL_FAKE_SCHEMA);
      const settings = createMockSettings();

      const { stdin, lastFrame, unmount, waitUntilReady } = await renderDialog(
        settings,
        vi.fn(),
      );

      // Search box should be visible initially (searchPlaceholder)
      expect(lastFrame()).toContain('Search to filter');

      // Navigate to "Enable Interactive Shell" and toggle it
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW);
      });
      await waitUntilReady();

      await waitFor(() =>
        expect(lastFrame()).toContain('● Enable Interactive Shell'),
      );

      await act(async () => {
        stdin.write(TerminalKeys.ENTER);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain(
          'Changes that require a restart have been modified',
        );
      });

      // Search box should now be hidden
      expect(lastFrame()).not.toContain('Search to filter');

      unmount();
    });
  });

  describe('String Settings Editing', () => {
    it('should allow editing and committing a string setting', async () => {
      const settings = createMockSettings({
        'general.sessionCleanup.maxAge': 'initial',
      });
      const onSelect = vi.fn();

      const { stdin, unmount, waitUntilReady } = await renderWithProviders(
        <SettingsDialog onSelect={onSelect} />,
        { settings, config: makeFakeConfig() },
      );

      // Search for 'chat history' to filter the list
      await act(async () => {
        stdin.write('chat history');
      });
      await waitUntilReady();

      // Press Down Arrow to focus the list
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW);
      });
      await waitUntilReady();

      // Press Enter to start editing, type new value, and commit
      await act(async () => {
        stdin.write('\r'); // Start editing
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write('new value');
      });
      await waitUntilReady();
      await act(async () => {
        stdin.write('\r'); // Commit
      });
      await waitUntilReady();

      // Simulate the settings file being updated on disk
      await act(async () => {
        settings.setValue(
          SettingScope.User,
          'general.sessionCleanup.maxAge',
          'new value',
        );
      });
      await waitUntilReady();

      // Press Escape to exit
      await act(async () => {
        stdin.write('\u001B');
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(onSelect).toHaveBeenCalledWith(undefined, 'User');
      });

      unmount();
    });
  });

  describe('Array Settings Editing', () => {
    const typeInput = async (
      stdin: { write: (data: string) => void },
      input: string,
    ) => {
      for (const ch of input) {
        await act(async () => {
          stdin.write(ch);
        });
      }
    };

    it('should parse comma-separated input as string arrays', async () => {
      vi.mocked(getSettingsSchema).mockReturnValue(ARRAY_FAKE_SCHEMA);
      const settings = createMockSettings();
      const setValueSpy = vi.spyOn(settings, 'setValue');

      const { stdin, unmount } = await renderDialog(settings, vi.fn());

      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Start editing first array setting
      });
      await typeInput(stdin, 'first/path, second/path,third/path');
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Commit
      });

      await waitFor(() => {
        expect(setValueSpy).toHaveBeenCalledWith(
          SettingScope.User,
          'context.fileFiltering.customIgnoreFilePaths',
          ['first/path', 'second/path', 'third/path'],
        );
      });

      unmount();
    });

    it('should parse JSON array input for allowedExtensions', async () => {
      vi.mocked(getSettingsSchema).mockReturnValue(ARRAY_FAKE_SCHEMA);
      const settings = createMockSettings();
      const setValueSpy = vi.spyOn(settings, 'setValue');

      const { stdin, unmount } = await renderDialog(settings, vi.fn());

      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW as string); // Move to second array setting
      });
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Start editing
      });
      await typeInput(stdin, '["^github\\\\.com/.*$", "^gitlab\\\\.com/.*$"]');
      await act(async () => {
        stdin.write(TerminalKeys.ENTER as string); // Commit
      });

      await waitFor(() => {
        expect(setValueSpy).toHaveBeenCalledWith(
          SettingScope.User,
          'security.allowedExtensions',
          ['^github\\.com/.*$', '^gitlab\\.com/.*$'],
        );
      });

      unmount();
    });
  });

  describe('Search Functionality', () => {
    it('should display text entered in search', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Wait for initial render and verify that search is not active
      await waitFor(() => {
        expect(lastFrame()).not.toContain('> Search:');
      });
      expect(lastFrame()).toContain('Search to filter');

      // Press '/' to enter search mode
      await act(async () => {
        stdin.write('/');
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('/');
        expect(lastFrame()).not.toContain('Search to filter');
      });

      unmount();
    });

    it('should show search query and filter settings as user types', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      await act(async () => {
        stdin.write('yolo');
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('yolo');
        expect(lastFrame()).toContain('Disable YOLO Mode');
      });

      unmount();
    });

    it('should exit search settings when Escape is pressed', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      await act(async () => {
        stdin.write('vim');
      });
      await waitUntilReady();
      await waitFor(() => {
        expect(lastFrame()).toContain('vim');
      });

      // Press Escape
      await act(async () => {
        stdin.write(TerminalKeys.ESCAPE);
      });
      await waitUntilReady();

      await waitFor(() => {
        // onSelect is called with (settingName, scope).
        // undefined settingName means "close dialog"
        expect(onSelect).toHaveBeenCalledWith(undefined, expect.anything());
      });

      unmount();
    });

    it('should handle backspace to modify search query', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      await act(async () => {
        stdin.write('vimm');
      });
      await waitUntilReady();
      await waitFor(() => {
        expect(lastFrame()).toContain('vimm');
      });

      // Press backspace
      await act(async () => {
        stdin.write(TerminalKeys.BACKSPACE);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('vim');
        expect(lastFrame()).toContain('Vim Mode');
        expect(lastFrame()).not.toContain('Hook Notifications');
      });

      unmount();
    });

    it('should display nothing when search yields no results', async () => {
      const settings = createMockSettings();
      const onSelect = vi.fn();

      const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
        settings,
        onSelect,
      );

      // Type a search query that won't match any settings
      await act(async () => {
        stdin.write('nonexistentsetting');
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('nonexistentsetting');
        expect(lastFrame()).not.toContain('Vim Mode'); // Should not contain any settings
        expect(lastFrame()).not.toContain('Enable Auto Update'); // Should not contain any settings
      });

      unmount();
    });
  });

  describe('Snapshot Tests', () => {
    /**
     * Snapshot tests for SettingsDialog component using ink-testing-library.
     * These tests capture the visual output of the component in various states.
     * The snapshots help ensure UI consistency and catch unintended visual changes.
     */

    it.each([
      {
        name: 'default state',
        userSettings: {},
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
      {
        name: 'various boolean settings enabled',
        userSettings: {
          general: {
            vimMode: true,
            enableAutoUpdate: false,
            debugKeystrokeLogging: true,
          },
          ui: {
            hideWindowTitle: true,
            hideTips: true,
            showMemoryUsage: true,
            showLineNumbers: true,
            showCitations: true,
            accessibility: {
              enableLoadingPhrases: false,
              screenReader: true,
            },
          },
          ide: {
            enabled: true,
          },
          context: {
            loadMemoryFromIncludeDirectories: true,
            fileFiltering: {
              respectGitIgnore: true,
              respectGeminiIgnore: true,
              enableRecursiveFileSearch: true,
              enableFuzzySearch: true,
            },
          },
          tools: {
            enableInteractiveShell: true,
            useRipgrep: true,
          },
          security: {
            folderTrust: {
              enabled: true,
            },
          },
        },
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
      {
        name: 'mixed boolean and number settings',
        userSettings: {
          general: {
            vimMode: false,
            enableAutoUpdate: false,
          },
          ui: {
            showMemoryUsage: true,
            hideWindowTitle: false,
          },
          tools: {
            truncateToolOutputThreshold: 50000,
          },
          context: {
            discoveryMaxDirs: 500,
          },
          model: {
            maxSessionTurns: 100,
            skipNextSpeakerCheck: false,
          },
        },
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
      {
        name: 'focused on scope selector',
        userSettings: {},
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: async (
          stdin: { write: (data: string) => void },
          waitUntilReady: () => Promise<void>,
        ) => {
          await act(async () => {
            stdin.write('\t');
          });
          await waitUntilReady();
        },
      },
      {
        name: 'accessibility settings enabled',
        userSettings: {
          ui: {
            accessibility: {
              enableLoadingPhrases: false,
              screenReader: true,
            },
            showMemoryUsage: true,
            showLineNumbers: true,
          },
          general: {
            vimMode: true,
          },
        },
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
      {
        name: 'file filtering settings configured',
        userSettings: {
          context: {
            fileFiltering: {
              respectGitIgnore: false,
              respectGeminiIgnore: true,
              enableRecursiveFileSearch: false,
              enableFuzzySearch: false,
            },
            loadMemoryFromIncludeDirectories: true,
            discoveryMaxDirs: 100,
          },
        },
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
      {
        name: 'tools and security settings',
        userSettings: {
          tools: {
            enableInteractiveShell: true,
            useRipgrep: true,
            truncateToolOutputThreshold: 25000,
          },
          security: {
            folderTrust: {
              enabled: true,
            },
          },
          model: {
            maxSessionTurns: 50,
            skipNextSpeakerCheck: true,
          },
        },
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
      {
        name: 'all boolean settings disabled',
        userSettings: {
          general: {
            vimMode: false,
            enableAutoUpdate: true,
            debugKeystrokeLogging: false,
          },
          ui: {
            hideWindowTitle: false,
            hideTips: false,
            showMemoryUsage: false,
            showLineNumbers: false,
            showCitations: false,
            accessibility: {
              enableLoadingPhrases: true,
              screenReader: false,
            },
          },
          ide: {
            enabled: false,
          },
          context: {
            loadMemoryFromIncludeDirectories: false,
            fileFiltering: {
              respectGitIgnore: false,
              respectGeminiIgnore: false,
              enableRecursiveFileSearch: false,
              enableFuzzySearch: true,
            },
          },
          tools: {
            enableInteractiveShell: false,
            useRipgrep: false,
          },
          security: {
            folderTrust: {
              enabled: false,
            },
          },
        },
        systemSettings: {},
        workspaceSettings: {},
        stdinActions: undefined,
      },
    ])(
      'should render $name correctly',
      async ({
        userSettings,
        systemSettings,
        workspaceSettings,
        stdinActions,
      }) => {
        const settings = createMockSettings({
          user: {
            settings: userSettings,
            originalSettings: userSettings,
            path: '',
          },
          system: {
            settings: systemSettings,
            originalSettings: systemSettings,
            path: '',
          },
          workspace: {
            settings: workspaceSettings,
            originalSettings: workspaceSettings,
            path: '',
          },
        });
        const onSelect = vi.fn();

        const renderResult = await renderDialog(settings, onSelect);
        await renderResult.waitUntilReady();

        if (stdinActions) {
          await stdinActions(renderResult.stdin, renderResult.waitUntilReady);
        }

        await expect(renderResult).toMatchSvgSnapshot();
        renderResult.unmount();
      },
    );
  });
});
