import { jsx as _jsx } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { useState, useMemo, useCallback, useEffect } from 'react';
import { Text } from 'ink';
import { AsyncFzf } from 'fzf';
import {} from '../hooks/useKeypress.js';
import { theme } from '../semantic-colors.js';
import { SettingScope, } from '../../config/settings.js';
import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js';
import { getDialogSettingKeys, getDisplayValue, getSettingDefinition, getDialogRestartRequiredSettings, getEffectiveValue, isInSettingsScope, getEditValue, parseEditedValue, } from '../../utils/settingsUtils.js';
import { useSettingsStore, } from '../contexts/SettingsContext.js';
import { getCachedStringWidth } from '../utils/textUtils.js';
import { TOGGLE_TYPES, } from '../../config/settingsSchema.js';
import { debugLogger } from '@google/gemini-cli-core';
import { useSearchBuffer } from '../hooks/useSearchBuffer.js';
import { BaseSettingsDialog, } from './shared/BaseSettingsDialog.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
import { Command, KeyBinding } from '../key/keyBindings.js';
const MAX_ITEMS_TO_SHOW = 8;
const KEY_UP = new KeyBinding('up');
const KEY_CTRL_P = new KeyBinding('ctrl+p');
const KEY_DOWN = new KeyBinding('down');
const KEY_CTRL_N = new KeyBinding('ctrl+n');
// Create a snapshot of the initial per-scope state of Restart Required Settings
// This creates a nested map of the form
// restartRequiredSetting -> Map { scopeName -> value }
function getActiveRestartRequiredSettings(settings) {
    const snapshot = new Map();
    const scopes = [
        ['User', settings.user.settings],
        ['Workspace', settings.workspace.settings],
        ['System', settings.system.settings],
    ];
    for (const key of getDialogRestartRequiredSettings()) {
        const scopeMap = new Map();
        for (const [scopeName, scopeSettings] of scopes) {
            // Raw per-scope value (undefined if not in file)
            const value = isInSettingsScope(key, scopeSettings)
                ? getEffectiveValue(key, scopeSettings)
                : undefined;
            scopeMap.set(scopeName, JSON.stringify(value));
        }
        snapshot.set(key, scopeMap);
    }
    return snapshot;
}
export function SettingsDialog({ onSelect, onRestartRequest, availableTerminalHeight, }) {
    // Reactive settings from store (re-renders on any settings change)
    const { settings, setSetting } = useSettingsStore();
    const [selectedScope, setSelectedScope] = useState(SettingScope.User);
    // Snapshot restart-required values at mount time for diff tracking
    const [activeRestartRequiredSettings] = useState(() => getActiveRestartRequiredSettings(settings));
    // Search state
    const [searchQuery, setSearchQuery] = useState('');
    const [filteredKeys, setFilteredKeys] = useState(() => getDialogSettingKeys());
    const { fzfInstance, searchMap } = useMemo(() => {
        const keys = getDialogSettingKeys();
        const map = new Map();
        const searchItems = [];
        keys.forEach((key) => {
            const def = getSettingDefinition(key);
            if (def?.label) {
                searchItems.push(def.label);
                map.set(def.label.toLowerCase(), key);
            }
        });
        const fzf = new AsyncFzf(searchItems, {
            fuzzy: 'v2',
            casing: 'case-insensitive',
        });
        return { fzfInstance: fzf, searchMap: map };
    }, []);
    // Perform search
    useEffect(() => {
        let active = true;
        if (!searchQuery.trim() || !fzfInstance) {
            setFilteredKeys(getDialogSettingKeys());
            return;
        }
        const doSearch = async () => {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
            const results = await fzfInstance.find(searchQuery);
            if (!active)
                return;
            const matchedKeys = new Set();
            results.forEach((res) => {
                const key = searchMap.get(res.item.toLowerCase());
                if (key)
                    matchedKeys.add(key);
            });
            setFilteredKeys(Array.from(matchedKeys));
        };
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        doSearch();
        return () => {
            active = false;
        };
    }, [searchQuery, fzfInstance, searchMap]);
    // Track whether a restart is required to apply the changes in the Settings json file
    // This does not care for inheritance
    // It checks whether a proposed change from this UI to a settings.json file requires a restart to take effect in the app
    const pendingRestartRequiredSettings = useMemo(() => {
        const changed = new Set();
        const scopes = [
            ['User', settings.user.settings],
            ['Workspace', settings.workspace.settings],
            ['System', settings.system.settings],
        ];
        // Iterate through the nested map snapshot in activeRestartRequiredSettings, diff with current settings
        for (const [key, initialScopeMap] of activeRestartRequiredSettings) {
            for (const [scopeName, scopeSettings] of scopes) {
                const currentValue = isInSettingsScope(key, scopeSettings)
                    ? getEffectiveValue(key, scopeSettings)
                    : undefined;
                const initialJson = initialScopeMap.get(scopeName);
                if (JSON.stringify(currentValue) !== initialJson) {
                    changed.add(key);
                    break; // one scope changed is enough
                }
            }
        }
        return changed;
    }, [settings, activeRestartRequiredSettings]);
    const showRestartPrompt = pendingRestartRequiredSettings.size > 0;
    // Calculate max width for the left column (Label/Description) to keep values aligned or close
    const maxLabelOrDescriptionWidth = useMemo(() => {
        const allKeys = getDialogSettingKeys();
        let max = 0;
        for (const key of allKeys) {
            const def = getSettingDefinition(key);
            if (!def)
                continue;
            const scopeMessage = getScopeMessageForSetting(key, selectedScope, settings);
            const label = def.label || key;
            const labelFull = label + (scopeMessage ? ` ${scopeMessage}` : '');
            const lWidth = getCachedStringWidth(labelFull);
            const dWidth = def.description
                ? getCachedStringWidth(def.description)
                : 0;
            max = Math.max(max, lWidth, dWidth);
        }
        return max;
    }, [selectedScope, settings]);
    // Search input buffer
    const searchBuffer = useSearchBuffer({
        initialText: '',
        onChange: setSearchQuery,
    });
    // Generate items for BaseSettingsDialog
    const settingKeys = searchQuery ? filteredKeys : getDialogSettingKeys();
    const items = useMemo(() => {
        const scopeSettings = settings.forScope(selectedScope).settings;
        const mergedSettings = settings.merged;
        return settingKeys.map((key) => {
            const definition = getSettingDefinition(key);
            const type = definition?.type ?? 'string';
            // Get the display value (with * indicator if modified)
            const displayValue = getDisplayValue(key, scopeSettings, mergedSettings);
            // Get the scope message (e.g., "(Modified in Workspace)")
            const scopeMessage = getScopeMessageForSetting(key, selectedScope, settings);
            // Grey out values that defer to defaults
            const isGreyedOut = !isInSettingsScope(key, scopeSettings);
            // Some settings can be edited by an inline editor
            const rawValue = getEffectiveValue(key, scopeSettings);
            // The inline editor needs a string but non primitive settings like Arrays and Objects exist
            const editValue = getEditValue(type, rawValue);
            return {
                key,
                label: definition?.label || key,
                description: definition?.description,
                type,
                displayValue,
                isGreyedOut,
                scopeMessage,
                rawValue,
                editValue,
            };
        });
    }, [settingKeys, selectedScope, settings]);
    const handleScopeChange = useCallback((scope) => {
        setSelectedScope(scope);
    }, []);
    // Toggle handler for boolean/enum settings
    const handleItemToggle = useCallback((key, _item) => {
        const definition = getSettingDefinition(key);
        if (!TOGGLE_TYPES.has(definition?.type)) {
            return;
        }
        const scopeSettings = settings.forScope(selectedScope).settings;
        const currentValue = getEffectiveValue(key, scopeSettings);
        let newValue;
        if (definition?.type === 'boolean') {
            if (typeof currentValue !== 'boolean') {
                return;
            }
            newValue = !currentValue;
        }
        else if (definition?.type === 'enum' && definition.options) {
            const options = definition.options;
            if (options.length === 0) {
                return;
            }
            const currentIndex = options?.findIndex((opt) => opt.value === currentValue);
            if (currentIndex !== -1 && currentIndex < options.length - 1) {
                newValue = options[currentIndex + 1].value;
            }
            else {
                newValue = options[0].value; // loop back to start.
            }
        }
        else {
            return;
        }
        debugLogger.log(`[DEBUG SettingsDialog] Saving ${key} immediately with value:`, newValue);
        setSetting(selectedScope, key, newValue);
    }, [settings, selectedScope, setSetting]);
    // For inline editor
    const handleEditCommit = useCallback((key, newValue, _item) => {
        const definition = getSettingDefinition(key);
        const type = definition?.type ?? 'string';
        const parsed = parseEditedValue(type, newValue);
        if (parsed === null) {
            return;
        }
        setSetting(selectedScope, key, parsed);
    }, [selectedScope, setSetting]);
    // Clear/reset handler - removes the value from settings.json so it falls back to default
    const handleItemClear = useCallback((key, _item) => {
        setSetting(selectedScope, key, undefined);
    }, [selectedScope, setSetting]);
    const handleClose = useCallback(() => {
        onSelect(undefined, selectedScope);
    }, [onSelect, selectedScope]);
    const globalKeyMatchers = useKeyMatchers();
    const settingsKeyMatchers = useMemo(() => ({
        ...globalKeyMatchers,
        [Command.DIALOG_NAVIGATION_UP]: (key) => KEY_UP.matches(key) || KEY_CTRL_P.matches(key),
        [Command.DIALOG_NAVIGATION_DOWN]: (key) => KEY_DOWN.matches(key) || KEY_CTRL_N.matches(key),
    }), [globalKeyMatchers]);
    // Custom key handler for restart key
    const handleKeyPress = useCallback((key, _currentItem) => {
        // 'r' key for restart
        if (showRestartPrompt && key.sequence === 'r') {
            if (onRestartRequest)
                onRestartRequest();
            return true;
        }
        return false;
    }, [showRestartPrompt, onRestartRequest]);
    // Decisions on what features to enable
    const hasWorkspace = settings.workspace.path !== undefined;
    const showSearch = !showRestartPrompt;
    return (_jsx(BaseSettingsDialog, { title: "Settings", borderColor: showRestartPrompt ? theme.status.warning : undefined, searchEnabled: showSearch, searchBuffer: searchBuffer, items: items, showScopeSelector: hasWorkspace, selectedScope: selectedScope, onScopeChange: handleScopeChange, maxItemsToShow: MAX_ITEMS_TO_SHOW, availableHeight: availableTerminalHeight, maxLabelWidth: maxLabelOrDescriptionWidth, onItemToggle: handleItemToggle, onEditCommit: handleEditCommit, onItemClear: handleItemClear, onClose: handleClose, onKeyPress: handleKeyPress, keyMatchers: settingsKeyMatchers, footer: showRestartPrompt
            ? {
                content: (_jsx(Text, { color: theme.status.warning, children: "Changes that require a restart have been modified. Press r to exit and apply changes now." })),
                height: 1,
            }
            : undefined }));
}
//# sourceMappingURL=SettingsDialog.js.map