/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import yargs from 'yargs/yargs';
import { hideBin } from 'yargs/helpers';
import process from 'node:process';
import { mcpCommand } from '../commands/mcp.js';
import { extensionsCommand } from '../commands/extensions.js';
import { Config, loadServerHierarchicalMemory, setGeminiMdFilename as setServerGeminiMdFilename, getCurrentGeminiMdFilename, ApprovalMode, DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_FILE_FILTERING_OPTIONS, DEFAULT_MEMORY_FILE_FILTERING_OPTIONS, FileDiscoveryService, WRITE_FILE_TOOL_NAME, SHELL_TOOL_NAMES, SHELL_TOOL_NAME, resolveTelemetrySettings, FatalConfigError, getPty, EDIT_TOOL_NAME, debugLogger, } from '@google/gemini-cli-core';
import { getCliVersion } from '../utils/version.js';
import { loadSandboxConfig } from './sandboxConfig.js';
import { resolvePath } from '../utils/resolvePath.js';
import { appEvents } from '../utils/events.js';
import { isWorkspaceTrusted } from './trustedFolders.js';
import { createPolicyEngineConfig } from './policy.js';
import { ExtensionManager } from './extension-manager.js';
import { requestConsentNonInteractive } from './extensions/consent.js';
import { promptForSetting } from './extensions/extensionSettings.js';
export async function parseArguments(settings) {
    const rawArgv = hideBin(process.argv);
    const yargsInstance = yargs(rawArgv)
        .locale('en')
        .scriptName('gemini')
        .usage('Usage: gemini [options] [command]\n\nSixth CLI - Launch an interactive CLI, use -p/--prompt for non-interactive mode')
        .option('debug', {
        alias: 'd',
        type: 'boolean',
        description: 'Run in debug mode?',
        default: false,
    })
        .command('$0 [query..]', 'Launch Sixth CLI', (yargsInstance) => yargsInstance
        .positional('query', {
        description: 'Positional prompt. Defaults to one-shot; use -i/--prompt-interactive for interactive.',
    })
        .option('model', {
        alias: 'm',
        type: 'string',
        nargs: 1,
        description: `Model`,
    })
        .option('prompt', {
        alias: 'p',
        type: 'string',
        nargs: 1,
        description: 'Prompt. Appended to input on stdin (if any).',
    })
        .option('prompt-interactive', {
        alias: 'i',
        type: 'string',
        nargs: 1,
        description: 'Execute the provided prompt and continue in interactive mode',
    })
        .option('sandbox', {
        alias: 's',
        type: 'boolean',
        description: 'Run in sandbox?',
    })
        .option('yolo', {
        alias: 'y',
        type: 'boolean',
        description: 'Automatically accept all actions (aka YOLO mode, see https://www.youtube.com/watch?v=xvFZjo5PgG0 for more details)?',
        default: false,
    })
        .option('approval-mode', {
        type: 'string',
        nargs: 1,
        choices: ['default', 'auto_edit', 'yolo'],
        description: 'Set the approval mode: default (prompt for approval), auto_edit (auto-approve edit tools), yolo (auto-approve all tools)',
    })
        .option('experimental-acp', {
        type: 'boolean',
        description: 'Starts the agent in ACP mode',
    })
        .option('allowed-mcp-server-names', {
        type: 'array',
        string: true,
        nargs: 1,
        description: 'Allowed MCP server names',
        coerce: (mcpServerNames) => 
        // Handle comma-separated values
        mcpServerNames.flatMap((mcpServerName) => mcpServerName.split(',').map((m) => m.trim())),
    })
        .option('allowed-tools', {
        type: 'array',
        string: true,
        nargs: 1,
        description: 'Tools that are allowed to run without confirmation',
        coerce: (tools) => 
        // Handle comma-separated values
        tools.flatMap((tool) => tool.split(',').map((t) => t.trim())),
    })
        .option('extensions', {
        alias: 'e',
        type: 'array',
        string: true,
        nargs: 1,
        description: 'A list of extensions to use. If not provided, all extensions are used.',
        coerce: (extensions) => 
        // Handle comma-separated values
        extensions.flatMap((extension) => extension.split(',').map((e) => e.trim())),
    })
        .option('list-extensions', {
        alias: 'l',
        type: 'boolean',
        description: 'List all available extensions and exit.',
    })
        .option('include-directories', {
        type: 'array',
        string: true,
        nargs: 1,
        description: 'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
        coerce: (dirs) => 
        // Handle comma-separated values
        dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())),
    })
        .option('screen-reader', {
        type: 'boolean',
        description: 'Enable screen reader mode for accessibility.',
    })
        .option('output-format', {
        alias: 'o',
        type: 'string',
        nargs: 1,
        description: 'The format of the CLI output.',
        choices: ['text', 'json', 'stream-json'],
    })
        .option('fake-responses', {
        type: 'string',
        description: 'Path to a file with fake model responses for testing.',
        hidden: true,
    })
        .option('record-responses', {
        type: 'string',
        description: 'Path to a file to record model responses for testing.',
        hidden: true,
    })
        .deprecateOption('prompt', 'Use the positional prompt instead. This flag will be removed in a future version.')
        // Ensure validation flows through .fail() for clean UX
        .fail((msg, err, yargs) => {
        debugLogger.error(msg || err?.message || 'Unknown error');
        yargs.showHelp();
        process.exit(1);
    })
        .check((argv) => {
        // The 'query' positional can be a string (for one arg) or string[] (for multiple).
        // This guard safely checks if any positional argument was provided.
        const query = argv['query'];
        const hasPositionalQuery = Array.isArray(query)
            ? query.length > 0
            : !!query;
        if (argv['prompt'] && hasPositionalQuery) {
            return 'Cannot use both a positional prompt and the --prompt (-p) flag together';
        }
        if (argv['prompt'] && argv['promptInteractive']) {
            return 'Cannot use both --prompt (-p) and --prompt-interactive (-i) together';
        }
        if (argv.yolo && argv['approvalMode']) {
            return 'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.';
        }
        return true;
    }))
        // Register MCP subcommands
        .command(mcpCommand);
    if (settings?.experimental?.extensionManagement ?? true) {
        yargsInstance.command(extensionsCommand);
    }
    yargsInstance
        .version(await getCliVersion()) // This will enable the --version flag based on package.json
        .alias('v', 'version')
        .help()
        .alias('h', 'help')
        .strict()
        .demandCommand(0, 0); // Allow base command to run with no subcommands
    yargsInstance.wrap(yargsInstance.terminalWidth());
    const result = await yargsInstance.parse();
    // If yargs handled --help/--version it will have exited; nothing to do here.
    // Handle case where MCP subcommands are executed - they should exit the process
    // and not return to main CLI logic
    if (result._.length > 0 &&
        (result._[0] === 'mcp' || result._[0] === 'extensions')) {
        // MCP commands handle their own execution and process exit
        process.exit(0);
    }
    // Normalize query args: handle both quoted "@path file" and unquoted @path file
    const queryArg = result.query;
    const q = Array.isArray(queryArg)
        ? queryArg.join(' ')
        : queryArg;
    // Route positional args: explicit -i flag -> interactive; else -> one-shot (even for @commands)
    if (q && !result['prompt']) {
        const hasExplicitInteractive = result['promptInteractive'] === '' || !!result['promptInteractive'];
        if (hasExplicitInteractive) {
            result['promptInteractive'] = q;
        }
        else {
            result['prompt'] = q;
        }
    }
    // Keep CliArgs.query as a string for downstream typing
    result['query'] = q || undefined;
    // The import format is now only controlled by settings.memoryImportFormat
    // We no longer accept it as a CLI argument
    return result;
}
// This function is now a thin wrapper around the server's implementation.
// It's kept in the CLI for now as App.tsx directly calls it for memory refresh.
// TODO: Consider if App.tsx should get memory via a server call or if Config should refresh itself.
export async function loadHierarchicalGeminiMemory(currentWorkingDirectory, includeDirectoriesToReadGemini = [], debugMode, fileService, settings, extensionLoader, folderTrust, memoryImportFormat = 'tree', fileFilteringOptions) {
    // FIX: Use real, canonical paths for a reliable comparison to handle symlinks.
    const realCwd = fs.realpathSync(path.resolve(currentWorkingDirectory));
    const realHome = fs.realpathSync(path.resolve(homedir()));
    const isHomeDirectory = realCwd === realHome;
    // If it is the home directory, pass an empty string to the core memory
    // function to signal that it should skip the workspace search.
    const effectiveCwd = isHomeDirectory ? '' : currentWorkingDirectory;
    if (debugMode) {
        debugLogger.debug(`CLI: Delegating hierarchical memory load to server for CWD: ${currentWorkingDirectory} (memoryImportFormat: ${memoryImportFormat})`);
    }
    // Directly call the server function with the corrected path.
    return loadServerHierarchicalMemory(effectiveCwd, includeDirectoriesToReadGemini, debugMode, fileService, extensionLoader, folderTrust, memoryImportFormat, fileFilteringOptions, settings.context?.discoveryMaxDirs);
}
/**
 * Creates a filter function to determine if a tool should be excluded.
 *
 * In non-interactive mode, we want to disable tools that require user
 * interaction to prevent the CLI from hanging. This function creates a predicate
 * that returns `true` if a tool should be excluded.
 *
 * A tool is excluded if it's not in the `allowedToolsSet`. The shell tool
 * has a special case: it's not excluded if any of its subcommands
 * are in the `allowedTools` list.
 *
 * @param allowedTools A list of explicitly allowed tool names.
 * @param allowedToolsSet A set of explicitly allowed tool names for quick lookups.
 * @returns A function that takes a tool name and returns `true` if it should be excluded.
 */
function createToolExclusionFilter(allowedTools, allowedToolsSet) {
    return (tool) => {
        if (tool === SHELL_TOOL_NAME) {
            // If any of the allowed tools is ShellTool (even with subcommands), don't exclude it.
            return !allowedTools.some((allowed) => SHELL_TOOL_NAMES.some((shellName) => allowed.startsWith(shellName)));
        }
        return !allowedToolsSet.has(tool);
    };
}
export function isDebugMode(argv) {
    return (argv.debug ||
        [process.env['DEBUG'], process.env['DEBUG_MODE']].some((v) => v === 'true' || v === '1'));
}
export async function loadCliConfig(settings, sessionId, argv, cwd = process.cwd()) {
    const debugMode = isDebugMode(argv);
    if (argv.sandbox) {
        process.env['GEMINI_SANDBOX'] = 'true';
    }
    const memoryImportFormat = settings.context?.importFormat || 'tree';
    const ideMode = settings.ide?.enabled ?? false;
    const folderTrust = settings.security?.folderTrust?.enabled ?? false;
    const trustedFolder = isWorkspaceTrusted(settings)?.isTrusted ?? true;
    // Set the context filename in the server's memoryTool module BEFORE loading memory
    // TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
    // directly to the Config constructor in core, and have core handle setGeminiMdFilename.
    // However, loadHierarchicalGeminiMemory is called *before* createServerConfig.
    if (settings.context?.fileName) {
        setServerGeminiMdFilename(settings.context.fileName);
    }
    else {
        // Reset to default if not provided in settings.
        setServerGeminiMdFilename(getCurrentGeminiMdFilename());
    }
    const fileService = new FileDiscoveryService(cwd);
    const memoryFileFiltering = {
        ...DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
        ...settings.context?.fileFiltering,
    };
    const fileFiltering = {
        ...DEFAULT_FILE_FILTERING_OPTIONS,
        ...settings.context?.fileFiltering,
    };
    const includeDirectories = (settings.context?.includeDirectories || [])
        .map(resolvePath)
        .concat((argv.includeDirectories || []).map(resolvePath));
    const extensionManager = new ExtensionManager({
        settings,
        requestConsent: requestConsentNonInteractive,
        requestSetting: promptForSetting,
        workspaceDir: cwd,
        enabledExtensionOverrides: argv.extensions,
    });
    await extensionManager.loadExtensions();
    // Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
    const { memoryContent, fileCount, filePaths } = await loadHierarchicalGeminiMemory(cwd, settings.context?.loadMemoryFromIncludeDirectories
        ? includeDirectories
        : [], debugMode, fileService, settings, extensionManager, trustedFolder, memoryImportFormat, memoryFileFiltering);
    let mcpServers = mergeMcpServers(settings, extensionManager.getExtensions());
    const question = argv.promptInteractive || argv.prompt || '';
    // Determine approval mode with backward compatibility
    let approvalMode;
    if (argv.approvalMode) {
        // New --approval-mode flag takes precedence
        switch (argv.approvalMode) {
            case 'yolo':
                approvalMode = ApprovalMode.YOLO;
                break;
            case 'auto_edit':
                approvalMode = ApprovalMode.AUTO_EDIT;
                break;
            case 'default':
                approvalMode = ApprovalMode.DEFAULT;
                break;
            default:
                throw new Error(`Invalid approval mode: ${argv.approvalMode}. Valid values are: yolo, auto_edit, default`);
        }
    }
    else {
        // Fallback to legacy --yolo flag behavior
        approvalMode =
            argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT;
    }
    // Override approval mode if disableYoloMode is set.
    if (settings.security?.disableYoloMode) {
        if (approvalMode === ApprovalMode.YOLO) {
            debugLogger.error('YOLO mode is disabled by the "disableYolo" setting.');
            throw new FatalConfigError('Cannot start in YOLO mode when it is disabled by settings');
        }
        approvalMode = ApprovalMode.DEFAULT;
    }
    else if (approvalMode === ApprovalMode.YOLO) {
        debugLogger.warn('YOLO mode is enabled. All tool calls will be automatically approved.');
    }
    // Force approval mode to default if the folder is not trusted.
    if (!trustedFolder && approvalMode !== ApprovalMode.DEFAULT) {
        debugLogger.warn(`Approval mode overridden to "default" because the current folder is not trusted.`);
        approvalMode = ApprovalMode.DEFAULT;
    }
    let telemetrySettings;
    try {
        telemetrySettings = await resolveTelemetrySettings({
            env: process.env,
            settings: settings.telemetry,
        });
    }
    catch (err) {
        if (err instanceof FatalConfigError) {
            throw new FatalConfigError(`Invalid telemetry configuration: ${err.message}.`);
        }
        throw err;
    }
    const policyEngineConfig = await createPolicyEngineConfig(settings, approvalMode);
    // Debug: Log the merged policy configuration
    // Only log when message bus integration is enabled (when policies are active)
    const enableMessageBusIntegration = settings.tools?.enableMessageBusIntegration ?? false;
    if (enableMessageBusIntegration) {
        debugLogger.debug('=== Policy Engine Configuration ===');
        debugLogger.debug(`Default decision: ${policyEngineConfig.defaultDecision}`);
        debugLogger.debug(`Total rules: ${policyEngineConfig.rules?.length || 0}`);
        if (policyEngineConfig.rules && policyEngineConfig.rules.length > 0) {
            debugLogger.debug('Rules (sorted by priority):');
            policyEngineConfig.rules.forEach((rule, index) => {
                debugLogger.debug(`  [${index}] toolName: ${rule.toolName || '*'}, decision: ${rule.decision}, priority: ${rule.priority}, argsPattern: ${rule.argsPattern ? rule.argsPattern.source : 'none'}`);
            });
        }
        debugLogger.debug('===================================');
    }
    const allowedTools = argv.allowedTools || settings.tools?.allowed || [];
    const allowedToolsSet = new Set(allowedTools);
    // Interactive mode: explicit -i flag or (TTY + no args + no -p flag)
    const hasQuery = !!argv.query;
    const interactive = !!argv.promptInteractive ||
        (process.stdin.isTTY && !hasQuery && !argv.prompt);
    // In non-interactive mode, exclude tools that require a prompt.
    const extraExcludes = [];
    if (!interactive && !argv.experimentalAcp) {
        const defaultExcludes = [
            SHELL_TOOL_NAME,
            EDIT_TOOL_NAME,
            WRITE_FILE_TOOL_NAME,
        ];
        const autoEditExcludes = [SHELL_TOOL_NAME];
        const toolExclusionFilter = createToolExclusionFilter(allowedTools, allowedToolsSet);
        switch (approvalMode) {
            case ApprovalMode.DEFAULT:
                // In default non-interactive mode, all tools that require approval are excluded.
                extraExcludes.push(...defaultExcludes.filter(toolExclusionFilter));
                break;
            case ApprovalMode.AUTO_EDIT:
                // In auto-edit non-interactive mode, only tools that still require a prompt are excluded.
                extraExcludes.push(...autoEditExcludes.filter(toolExclusionFilter));
                break;
            case ApprovalMode.YOLO:
                // No extra excludes for YOLO mode.
                break;
            default:
                // This should never happen due to validation earlier, but satisfies the linter
                break;
        }
    }
    const excludeTools = mergeExcludeTools(settings, extensionManager.getExtensions(), extraExcludes.length > 0 ? extraExcludes : undefined);
    const blockedMcpServers = [];
    if (!argv.allowedMcpServerNames) {
        if (settings.mcp?.allowed) {
            mcpServers = allowedMcpServers(mcpServers, settings.mcp.allowed, blockedMcpServers);
        }
        if (settings.mcp?.excluded) {
            const excludedNames = new Set(settings.mcp.excluded.filter(Boolean));
            if (excludedNames.size > 0) {
                mcpServers = Object.fromEntries(Object.entries(mcpServers).filter(([key]) => !excludedNames.has(key)));
            }
        }
    }
    if (argv.allowedMcpServerNames) {
        mcpServers = allowedMcpServers(mcpServers, argv.allowedMcpServerNames, blockedMcpServers);
    }
    const useModelRouter = settings.experimental?.useModelRouter ?? true;
    const defaultModel = useModelRouter
        ? DEFAULT_GEMINI_MODEL_AUTO
        : DEFAULT_GEMINI_MODEL;
    const resolvedModel = argv.model ||
        process.env['GEMINI_MODEL'] ||
        settings.model?.name ||
        defaultModel;
    const sandboxConfig = await loadSandboxConfig(settings, argv);
    const screenReader = argv.screenReader !== undefined
        ? argv.screenReader
        : (settings.ui?.accessibility?.screenReader ?? false);
    const ptyInfo = await getPty();
    return new Config({
        sessionId,
        embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
        sandbox: sandboxConfig,
        targetDir: cwd,
        includeDirectories,
        loadMemoryFromIncludeDirectories: settings.context?.loadMemoryFromIncludeDirectories || false,
        debugMode,
        question,
        coreTools: settings.tools?.core || undefined,
        allowedTools: allowedTools.length > 0 ? allowedTools : undefined,
        policyEngineConfig,
        excludeTools,
        toolDiscoveryCommand: settings.tools?.discoveryCommand,
        toolCallCommand: settings.tools?.callCommand,
        mcpServerCommand: settings.mcp?.serverCommand,
        mcpServers,
        userMemory: memoryContent,
        geminiMdFileCount: fileCount,
        geminiMdFilePaths: filePaths,
        approvalMode,
        disableYoloMode: settings.security?.disableYoloMode,
        showMemoryUsage: settings.ui?.showMemoryUsage || false,
        accessibility: {
            ...settings.ui?.accessibility,
            screenReader,
        },
        telemetry: telemetrySettings,
        usageStatisticsEnabled: settings.privacy?.usageStatisticsEnabled ?? true,
        fileFiltering,
        checkpointing: settings.general?.checkpointing?.enabled,
        proxy: process.env['HTTPS_PROXY'] ||
            process.env['https_proxy'] ||
            process.env['HTTP_PROXY'] ||
            process.env['http_proxy'],
        cwd,
        fileDiscoveryService: fileService,
        bugCommand: settings.advanced?.bugCommand,
        model: resolvedModel,
        maxSessionTurns: settings.model?.maxSessionTurns ?? -1,
        experimentalZedIntegration: argv.experimentalAcp || false,
        listExtensions: argv.listExtensions || false,
        enabledExtensions: argv.extensions,
        extensionLoader: extensionManager,
        enableExtensionReloading: settings.experimental?.extensionReloading,
        blockedMcpServers,
        noBrowser: !!process.env['NO_BROWSER'],
        summarizeToolOutput: settings.model?.summarizeToolOutput,
        ideMode,
        compressionThreshold: settings.model?.compressionThreshold,
        folderTrust,
        interactive,
        trustedFolder,
        useRipgrep: settings.tools?.useRipgrep,
        enableInteractiveShell: settings.tools?.shell?.enableInteractiveShell ?? true,
        skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
        enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
        truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold,
        truncateToolOutputLines: settings.tools?.truncateToolOutputLines,
        enableToolOutputTruncation: settings.tools?.enableToolOutputTruncation,
        eventEmitter: appEvents,
        useSmartEdit: argv.useSmartEdit ?? settings.useSmartEdit,
        useWriteTodos: argv.useWriteTodos ?? settings.useWriteTodos,
        output: {
            format: (argv.outputFormat ?? settings.output?.format),
        },
        useModelRouter,
        enableMessageBusIntegration,
        codebaseInvestigatorSettings: settings.experimental?.codebaseInvestigatorSettings,
        fakeResponses: argv.fakeResponses,
        recordResponses: argv.recordResponses,
        retryFetchErrors: settings.general?.retryFetchErrors ?? false,
        ptyInfo: ptyInfo?.name,
        // TODO: loading of hooks based on workspace trust
        enableHooks: settings.tools?.enableHooks ?? false,
        hooks: settings.hooks || {},
    });
}
function allowedMcpServers(mcpServers, allowMCPServers, blockedMcpServers) {
    const allowedNames = new Set(allowMCPServers.filter(Boolean));
    if (allowedNames.size > 0) {
        mcpServers = Object.fromEntries(Object.entries(mcpServers).filter(([key, server]) => {
            const isAllowed = allowedNames.has(key);
            if (!isAllowed) {
                blockedMcpServers.push({
                    name: key,
                    extensionName: server.extension?.name || '',
                });
            }
            return isAllowed;
        }));
    }
    else {
        blockedMcpServers.push(...Object.entries(mcpServers).map(([key, server]) => ({
            name: key,
            extensionName: server.extension?.name || '',
        })));
        mcpServers = {};
    }
    return mcpServers;
}
function mergeMcpServers(settings, extensions) {
    const mcpServers = { ...(settings.mcpServers || {}) };
    for (const extension of extensions) {
        if (!extension.isActive) {
            continue;
        }
        Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
            if (mcpServers[key]) {
                debugLogger.warn(`Skipping extension MCP config for server with key "${key}" as it already exists.`);
                return;
            }
            mcpServers[key] = {
                ...server,
                extension,
            };
        });
    }
    return mcpServers;
}
function mergeExcludeTools(settings, extensions, extraExcludes) {
    const allExcludeTools = new Set([
        ...(settings.tools?.exclude || []),
        ...(extraExcludes || []),
    ]);
    for (const extension of extensions) {
        if (!extension.isActive) {
            continue;
        }
        for (const tool of extension.excludeTools || []) {
            allExcludeTools.add(tool);
        }
    }
    return [...allExcludeTools];
}
//# sourceMappingURL=config.js.map