import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import React, { useState, useEffect } from 'react';
import { render, Box, Text } from 'ink';
import Spinner from 'ink-spinner';
import { AppWrapper } from './ui/App.js';
import { loadCliConfig, parseArguments } from './config/config.js';
import { readStdin } from './utils/readStdin.js';
import { basename } from 'node:path';
import v8 from 'node:v8';
import os from 'node:os';
import dns from 'node:dns';
import { spawn } from 'node:child_process';
import { start_sandbox } from './utils/sandbox.js';
import { loadSettings, SettingScope } from './config/settings.js';
import { themeManager } from './ui/themes/theme-manager.js';
import { getStartupWarnings } from './utils/startupWarnings.js';
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { runNonInteractive } from './nonInteractiveCli.js';
import { loadExtensions } from './config/extension.js';
import { cleanupCheckpoints, registerCleanup, runExitCleanup, } from './utils/cleanup.js';
import { getCliVersion } from './utils/version.js';
import { sessionId, logUserPrompt, AuthType, getOauthClient, uiTelemetryService, } from '@google/gemini-cli-core';
import { validateAuthMethod } from './config/auth.js';
import { setMaxSizedBoxDebugging } from './ui/components/shared/MaxSizedBox.js';
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
import { detectAndEnableKittyProtocol } from './ui/utils/kittyProtocolDetector.js';
import { checkForUpdates } from './ui/utils/updateCheck.js';
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
import { appEvents, AppEvent } from './utils/events.js';
import { SettingsContext } from './ui/contexts/SettingsContext.js';
import { writeFileSync } from 'node:fs';
export function validateDnsResolutionOrder(order) {
    const defaultValue = 'ipv4first';
    if (order === undefined) {
        return defaultValue;
    }
    if (order === 'ipv4first' || order === 'verbatim') {
        return order;
    }
    // We don't want to throw here, just warn and use the default.
    console.warn(`Invalid value for dnsResolutionOrder in settings: "${order}". Using default "${defaultValue}".`);
    return defaultValue;
}
function getNodeMemoryArgs(config) {
    const totalMemoryMB = os.totalmem() / (1024 * 1024);
    const heapStats = v8.getHeapStatistics();
    const currentMaxOldSpaceSizeMb = Math.floor(heapStats.heap_size_limit / 1024 / 1024);
    // Set target to 50% of total memory
    const targetMaxOldSpaceSizeInMB = Math.floor(totalMemoryMB * 0.5);
    if (config.getDebugMode()) {
        console.debug(`Current heap size ${currentMaxOldSpaceSizeMb.toFixed(2)} MB`);
    }
    if (process.env['GEMINI_CLI_NO_RELAUNCH']) {
        return [];
    }
    if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
        if (config.getDebugMode()) {
            console.debug(`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`);
        }
        return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`];
    }
    return [];
}
async function relaunchWithAdditionalArgs(additionalArgs) {
    const nodeArgs = [...additionalArgs, ...process.argv.slice(1)];
    const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
    const child = spawn(process.execPath, nodeArgs, {
        stdio: 'inherit',
        env: newEnv,
    });
    await new Promise((resolve) => child.on('close', resolve));
    process.exit(0);
}
const InitializingComponent = ({ initialTotal }) => {
    const [total, setTotal] = useState(initialTotal);
    const [connected, setConnected] = useState(0);
    useEffect(() => {
        const onStart = ({ count }) => setTotal(count);
        const onChange = () => {
            setConnected((val) => val + 1);
        };
        appEvents.on('mcp-servers-discovery-start', onStart);
        appEvents.on('mcp-server-connected', onChange);
        appEvents.on('mcp-server-error', onChange);
        return () => {
            appEvents.off('mcp-servers-discovery-start', onStart);
            appEvents.off('mcp-server-connected', onChange);
            appEvents.off('mcp-server-error', onChange);
        };
    }, []);
    const message = `Connecting to MCP servers... (${connected}/${total})`;
    return (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Spinner, {}), " ", message] }) }));
};
import { runZedIntegration } from './zed-integration/zedIntegration.js';
export function setupUnhandledRejectionHandler() {
    let unhandledRejectionOccurred = false;
    process.on('unhandledRejection', (reason, _promise) => {
        const errorMessage = `=========================================
This is an unexpected error. Please file a bug report using the /bug tool.
CRITICAL: Unhandled Promise Rejection!
=========================================
Reason: ${reason}${reason instanceof Error && reason.stack
            ? `
Stack trace:
${reason.stack}`
            : ''}`;
        appEvents.emit(AppEvent.LogError, errorMessage);
        if (!unhandledRejectionOccurred) {
            unhandledRejectionOccurred = true;
            appEvents.emit(AppEvent.OpenDebugConsole);
        }
    });
}
export async function startInteractiveUI(config, settings, startupWarnings, workspaceRoot = process.cwd()) {
    const version = await getCliVersion();
    // Detect and enable Kitty keyboard protocol once at startup
    await detectAndEnableKittyProtocol();
    setWindowTitle(basename(workspaceRoot), settings);
    const instance = render(_jsx(React.StrictMode, { children: _jsx(SettingsContext.Provider, { value: settings, children: _jsx(AppWrapper, { config: config, settings: settings, startupWarnings: startupWarnings, version: version }) }) }), { exitOnCtrlC: false, isScreenReaderEnabled: config.getScreenReader() });
    checkForUpdates()
        .then((info) => {
        handleAutoUpdate(info, settings, config.getProjectRoot());
    })
        .catch((err) => {
        // Silently ignore update check errors.
        if (config.getDebugMode()) {
            console.error('Update check failed:', err);
        }
    });
    registerCleanup(() => instance.unmount());
}
export async function main() {
    setupUnhandledRejectionHandler();
    const settings = loadSettings();
    await cleanupCheckpoints();
    const argv = await parseArguments(settings.merged);
    const extensions = loadExtensions();
    const config = await loadCliConfig(settings.merged, extensions, sessionId, argv);
    if (argv.sessionSummary) {
        registerCleanup(() => {
            const metrics = uiTelemetryService.getMetrics();
            writeFileSync(argv.sessionSummary, JSON.stringify({ sessionMetrics: metrics }, null, 2));
        });
    }
    const consolePatcher = new ConsolePatcher({
        stderr: true,
        debugMode: config.getDebugMode(),
    });
    consolePatcher.patch();
    registerCleanup(consolePatcher.cleanup);
    dns.setDefaultResultOrder(validateDnsResolutionOrder(settings.merged.advanced?.dnsResolutionOrder));
    if (argv.promptInteractive && !process.stdin.isTTY) {
        console.error('Error: The --prompt-interactive flag is not supported when piping input from stdin.');
        process.exit(1);
    }
    if (config.getListExtensions()) {
        console.log('Installed extensions:');
        for (const extension of extensions) {
            console.log(`- ${extension.config.name}`);
        }
        process.exit(0);
    }
    // Set a default auth type if one isn't set.
    if (!settings.merged.security?.auth?.selectedType) {
        if (process.env['CLOUD_SHELL'] === 'true') {
            settings.setValue(SettingScope.User, 'selectedAuthType', AuthType.CLOUD_SHELL);
        }
    }
    setMaxSizedBoxDebugging(config.getDebugMode());
    const mcpServers = config.getMcpServers();
    const mcpServersCount = mcpServers ? Object.keys(mcpServers).length : 0;
    let spinnerInstance;
    if (config.isInteractive() && mcpServersCount > 0) {
        spinnerInstance = render(_jsx(InitializingComponent, { initialTotal: mcpServersCount }));
    }
    await config.initialize();
    if (spinnerInstance) {
        // Small UX detail to show the completion message for a bit before unmounting.
        await new Promise((f) => setTimeout(f, 100));
        spinnerInstance.clear();
        spinnerInstance.unmount();
    }
    // Load custom themes from settings
    themeManager.loadCustomThemes(settings.merged.ui?.customThemes);
    if (settings.merged.ui?.theme) {
        if (!themeManager.setActiveTheme(settings.merged.ui?.theme)) {
            // If the theme is not found during initial load, log a warning and continue.
            // The useThemeCommand hook in App.tsx will handle opening the dialog.
            console.warn(`Warning: Theme "${settings.merged.ui?.theme}" not found.`);
        }
    }
    // hop into sandbox if we are outside and sandboxing is enabled
    if (!process.env['SANDBOX']) {
        const memoryArgs = settings.merged.advanced?.autoConfigureMemory
            ? getNodeMemoryArgs(config)
            : [];
        const sandboxConfig = config.getSandbox();
        if (sandboxConfig) {
            if (settings.merged.security?.auth?.selectedType &&
                !settings.merged.security?.auth?.useExternal) {
                // Validate authentication here because the sandbox will interfere with the Oauth2 web redirect.
                try {
                    const err = validateAuthMethod(settings.merged.security.auth.selectedType);
                    if (err) {
                        throw new Error(err);
                    }
                    await config.refreshAuth(settings.merged.security.auth.selectedType);
                }
                catch (err) {
                    console.error('Error authenticating:', err);
                    process.exit(1);
                }
            }
            let stdinData = '';
            if (!process.stdin.isTTY) {
                stdinData = await readStdin();
            }
            // This function is a copy of the one from sandbox.ts
            // It is moved here to decouple sandbox.ts from the CLI's argument structure.
            const injectStdinIntoArgs = (args, stdinData) => {
                const finalArgs = [...args];
                if (stdinData) {
                    const promptIndex = finalArgs.findIndex((arg) => arg === '--prompt' || arg === '-p');
                    if (promptIndex > -1 && finalArgs.length > promptIndex + 1) {
                        // If there's a prompt argument, prepend stdin to it
                        finalArgs[promptIndex + 1] =
                            `${stdinData}\n\n${finalArgs[promptIndex + 1]}`;
                    }
                    else {
                        // If there's no prompt argument, add stdin as the prompt
                        finalArgs.push('--prompt', stdinData);
                    }
                }
                return finalArgs;
            };
            const sandboxArgs = injectStdinIntoArgs(process.argv, stdinData);
            await start_sandbox(sandboxConfig, memoryArgs, config, sandboxArgs);
            process.exit(0);
        }
        else {
            // Not in a sandbox and not entering one, so relaunch with additional
            // arguments to control memory usage if needed.
            if (memoryArgs.length > 0) {
                await relaunchWithAdditionalArgs(memoryArgs);
                process.exit(0);
            }
        }
    }
    if (settings.merged.security?.auth?.selectedType ===
        AuthType.LOGIN_WITH_GOOGLE &&
        config.isBrowserLaunchSuppressed()) {
        // Do oauth before app renders to make copying the link possible.
        await getOauthClient(settings.merged.security.auth.selectedType, config);
    }
    if (config.getExperimentalZedIntegration()) {
        return runZedIntegration(config, settings, extensions, argv);
    }
    let input = config.getQuestion();
    const startupWarnings = [
        ...(await getStartupWarnings()),
        ...(await getUserStartupWarnings()),
    ];
    // Render UI, passing necessary config values. Check that there is no command line question.
    if (config.isInteractive()) {
        await startInteractiveUI(config, settings, startupWarnings);
        return;
    }
    // If not a TTY, read from stdin
    // This is for cases where the user pipes input directly into the command
    if (!process.stdin.isTTY) {
        const stdinData = await readStdin();
        if (stdinData) {
            input = `${stdinData}\n\n${input}`;
        }
    }
    if (!input) {
        console.error(`No input provided via stdin. Input can be provided by piping data into gemini or using the --prompt option.`);
        process.exit(1);
    }
    const prompt_id = Math.random().toString(16).slice(2);
    logUserPrompt(config, {
        'event.name': 'user_prompt',
        'event.timestamp': new Date().toISOString(),
        prompt: input,
        prompt_id,
        auth_type: config.getContentGeneratorConfig()?.authType,
        prompt_length: input.length,
    });
    const nonInteractiveConfig = await validateNonInteractiveAuth(settings.merged.security?.auth?.selectedType, settings.merged.security?.auth?.useExternal, config, settings);
    if (config.getDebugMode()) {
        console.log('Session ID: %s', sessionId);
    }
    await runNonInteractive(nonInteractiveConfig, input, prompt_id);
    // Call cleanup before process.exit, which causes cleanup to not run
    await runExitCleanup();
    process.exit(0);
}
function setWindowTitle(title, settings) {
    if (!settings.merged.ui?.hideWindowTitle) {
        const windowTitle = (process.env['CLI_TITLE'] || `Gemini - ${title}`).replace(
        // eslint-disable-next-line no-control-regex
        /[\x00-\x1F\x7F]/g, '');
        process.stdout.write(`\x1b]2;${windowTitle}\x07`);
        process.on('exit', () => {
            process.stdout.write(`\x1b]2;\x07`);
        });
    }
}
//# sourceMappingURL=gemini.js.map