/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { isSlashCommand } from './ui/utils/commandUtils.js';
import { GeminiEventType, FatalInputError, promptIdContext, OutputFormat, JsonFormatter, StreamJsonFormatter, JsonStreamEventType, uiTelemetryService, debugLogger, coreEvents, CoreEvent, createWorkingStdio, recordToolCallInteractions, ToolErrorType, Scheduler, ROOT_SCHEDULER_ID, } from '@google/gemini-cli-core';
import readline from 'node:readline';
import stripAnsi from 'strip-ansi';
import { convertSessionToHistoryFormats } from './ui/hooks/useSessionBrowser.js';
import { handleSlashCommand } from './nonInteractiveCliCommands.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { handleAtCommand } from './ui/hooks/atCommandProcessor.js';
import { handleError, handleToolError, handleCancellationError, handleMaxTurnsExceededError, } from './utils/errors.js';
import { TextOutput } from './ui/utils/textOutput.js';
export async function runNonInteractive({ config, settings, input, prompt_id, resumedSessionData, }) {
    return promptIdContext.run(prompt_id, async () => {
        const consolePatcher = new ConsolePatcher({
            stderr: true,
            debugMode: config.getDebugMode(),
            onNewMessage: (msg) => {
                coreEvents.emitConsoleLog(msg.type, msg.content);
            },
        });
        const { stdout: workingStdout } = createWorkingStdio();
        const textOutput = new TextOutput(workingStdout);
        const handleUserFeedback = (payload) => {
            const prefix = payload.severity.toUpperCase();
            process.stderr.write(`[${prefix}] ${payload.message}\n`);
            if (payload.error && config.getDebugMode()) {
                const errorToLog = payload.error instanceof Error
                    ? payload.error.stack || payload.error.message
                    : String(payload.error);
                process.stderr.write(`${errorToLog}\n`);
            }
        };
        const startTime = Date.now();
        const streamFormatter = config.getOutputFormat() === OutputFormat.STREAM_JSON
            ? new StreamJsonFormatter()
            : null;
        const abortController = new AbortController();
        // Track cancellation state
        let isAborting = false;
        let cancelMessageTimer = null;
        // Setup stdin listener for Ctrl+C detection
        let stdinWasRaw = false;
        let rl = null;
        const setupStdinCancellation = () => {
            // Only setup if stdin is a TTY (user can interact)
            if (!process.stdin.isTTY) {
                return;
            }
            // Save original raw mode state
            stdinWasRaw = process.stdin.isRaw || false;
            // Enable raw mode to capture individual keypresses
            process.stdin.setRawMode(true);
            process.stdin.resume();
            // Setup readline to emit keypress events
            rl = readline.createInterface({
                input: process.stdin,
                escapeCodeTimeout: 0,
            });
            readline.emitKeypressEvents(process.stdin, rl);
            // Listen for Ctrl+C
            const keypressHandler = (str, key) => {
                // Detect Ctrl+C: either ctrl+c key combo or raw character code 3
                if ((key && key.ctrl && key.name === 'c') || str === '\u0003') {
                    // Only handle once
                    if (isAborting) {
                        return;
                    }
                    isAborting = true;
                    // Only show message if cancellation takes longer than 200ms
                    // This reduces verbosity for fast cancellations
                    cancelMessageTimer = setTimeout(() => {
                        process.stderr.write('\nCancelling...\n');
                    }, 200);
                    abortController.abort();
                    // Note: Don't exit here - let the abort flow through the system
                    // and trigger handleCancellationError() which will exit with proper code
                }
            };
            process.stdin.on('keypress', keypressHandler);
        };
        const cleanupStdinCancellation = () => {
            // Clear any pending cancel message timer
            if (cancelMessageTimer) {
                clearTimeout(cancelMessageTimer);
                cancelMessageTimer = null;
            }
            // Cleanup readline and stdin listeners
            if (rl) {
                rl.close();
                rl = null;
            }
            // Remove keypress listener
            process.stdin.removeAllListeners('keypress');
            // Restore stdin to original state
            if (process.stdin.isTTY) {
                process.stdin.setRawMode(stdinWasRaw);
                process.stdin.pause();
            }
        };
        let errorToHandle;
        try {
            consolePatcher.patch();
            if (config.getRawOutput() &&
                !config.getAcceptRawOutputRisk() &&
                config.getOutputFormat() === OutputFormat.TEXT) {
                process.stderr.write('[WARNING] --raw-output is enabled. Model output is not sanitized and may contain harmful ANSI sequences (e.g. for phishing or command injection). Use --accept-raw-output-risk to suppress this warning.\n');
            }
            // Setup stdin cancellation listener
            setupStdinCancellation();
            coreEvents.on(CoreEvent.UserFeedback, handleUserFeedback);
            coreEvents.drainBacklogs();
            // Handle EPIPE errors when the output is piped to a command that closes early.
            process.stdout.on('error', (err) => {
                if (err.code === 'EPIPE') {
                    // Exit gracefully if the pipe is closed.
                    process.exit(0);
                }
            });
            const geminiClient = config.getGeminiClient();
            const scheduler = new Scheduler({
                config,
                messageBus: config.getMessageBus(),
                getPreferredEditor: () => undefined,
                schedulerId: ROOT_SCHEDULER_ID,
            });
            // Initialize chat.  Resume if resume data is passed.
            if (resumedSessionData) {
                await geminiClient.resumeChat(convertSessionToHistoryFormats(resumedSessionData.conversation.messages).clientHistory, resumedSessionData);
            }
            // Emit init event for streaming JSON
            if (streamFormatter) {
                streamFormatter.emitEvent({
                    type: JsonStreamEventType.INIT,
                    timestamp: new Date().toISOString(),
                    session_id: config.getSessionId(),
                    model: config.getModel(),
                });
            }
            let query;
            if (isSlashCommand(input)) {
                const slashCommandResult = await handleSlashCommand(input, abortController, config, settings);
                // If a slash command is found and returns a prompt, use it.
                // Otherwise, slashCommandResult falls through to the default prompt
                // handling.
                if (slashCommandResult) {
                    query = slashCommandResult;
                }
            }
            if (!query) {
                const { processedQuery, error } = await handleAtCommand({
                    query: input,
                    config,
                    addItem: (_item, _timestamp) => 0,
                    onDebugMessage: () => { },
                    messageId: Date.now(),
                    signal: abortController.signal,
                });
                if (error || !processedQuery) {
                    // An error occurred during @include processing (e.g., file not found).
                    // The error message is already logged by handleAtCommand.
                    throw new FatalInputError(error || 'Exiting due to an error processing the @ command.');
                }
                query = processedQuery;
            }
            // Emit user message event for streaming JSON
            if (streamFormatter) {
                streamFormatter.emitEvent({
                    type: JsonStreamEventType.MESSAGE,
                    timestamp: new Date().toISOString(),
                    role: 'user',
                    content: input,
                });
            }
            let currentMessages = [{ role: 'user', parts: query }];
            let turnCount = 0;
            while (true) {
                turnCount++;
                if (config.getMaxSessionTurns() >= 0 &&
                    turnCount > config.getMaxSessionTurns()) {
                    handleMaxTurnsExceededError(config);
                }
                const toolCallRequests = [];
                const responseStream = geminiClient.sendMessageStream(currentMessages[0]?.parts || [], abortController.signal, prompt_id);
                let responseText = '';
                for await (const event of responseStream) {
                    if (abortController.signal.aborted) {
                        handleCancellationError(config);
                    }
                    if (event.type === GeminiEventType.Content) {
                        const isRaw = config.getRawOutput() || config.getAcceptRawOutputRisk();
                        const output = isRaw ? event.value : stripAnsi(event.value);
                        if (streamFormatter) {
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.MESSAGE,
                                timestamp: new Date().toISOString(),
                                role: 'assistant',
                                content: output,
                                delta: true,
                            });
                        }
                        else if (config.getOutputFormat() === OutputFormat.JSON) {
                            responseText += output;
                        }
                        else {
                            if (event.value) {
                                textOutput.write(output);
                            }
                        }
                    }
                    else if (event.type === GeminiEventType.ToolCallRequest) {
                        if (streamFormatter) {
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.TOOL_USE,
                                timestamp: new Date().toISOString(),
                                tool_name: event.value.name,
                                tool_id: event.value.callId,
                                parameters: event.value.args,
                            });
                        }
                        toolCallRequests.push(event.value);
                    }
                    else if (event.type === GeminiEventType.LoopDetected) {
                        if (streamFormatter) {
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.ERROR,
                                timestamp: new Date().toISOString(),
                                severity: 'warning',
                                message: 'Loop detected, stopping execution',
                            });
                        }
                    }
                    else if (event.type === GeminiEventType.MaxSessionTurns) {
                        if (streamFormatter) {
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.ERROR,
                                timestamp: new Date().toISOString(),
                                severity: 'error',
                                message: 'Maximum session turns exceeded',
                            });
                        }
                    }
                    else if (event.type === GeminiEventType.Error) {
                        throw event.value.error;
                    }
                    else if (event.type === GeminiEventType.AgentExecutionStopped) {
                        const stopMessage = `Agent execution stopped: ${event.value.systemMessage?.trim() || event.value.reason}`;
                        if (config.getOutputFormat() === OutputFormat.TEXT) {
                            process.stderr.write(`${stopMessage}\n`);
                        }
                        // Emit final result event for streaming JSON if needed
                        if (streamFormatter) {
                            const metrics = uiTelemetryService.getMetrics();
                            const durationMs = Date.now() - startTime;
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.RESULT,
                                timestamp: new Date().toISOString(),
                                status: 'success',
                                stats: streamFormatter.convertToStreamStats(metrics, durationMs),
                            });
                        }
                        return;
                    }
                    else if (event.type === GeminiEventType.AgentExecutionBlocked) {
                        const blockMessage = `Agent execution blocked: ${event.value.systemMessage?.trim() || event.value.reason}`;
                        if (config.getOutputFormat() === OutputFormat.TEXT) {
                            process.stderr.write(`[WARNING] ${blockMessage}\n`);
                        }
                    }
                }
                if (toolCallRequests.length > 0) {
                    textOutput.ensureTrailingNewline();
                    const completedToolCalls = await scheduler.schedule(toolCallRequests, abortController.signal);
                    const toolResponseParts = [];
                    for (const completedToolCall of completedToolCalls) {
                        const toolResponse = completedToolCall.response;
                        const requestInfo = completedToolCall.request;
                        if (streamFormatter) {
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.TOOL_RESULT,
                                timestamp: new Date().toISOString(),
                                tool_id: requestInfo.callId,
                                status: completedToolCall.status === 'error' ? 'error' : 'success',
                                output: typeof toolResponse.resultDisplay === 'string'
                                    ? toolResponse.resultDisplay
                                    : undefined,
                                error: toolResponse.error
                                    ? {
                                        type: toolResponse.errorType || 'TOOL_EXECUTION_ERROR',
                                        message: toolResponse.error.message,
                                    }
                                    : undefined,
                            });
                        }
                        if (toolResponse.error) {
                            handleToolError(requestInfo.name, toolResponse.error, config, toolResponse.errorType || 'TOOL_EXECUTION_ERROR', typeof toolResponse.resultDisplay === 'string'
                                ? toolResponse.resultDisplay
                                : undefined);
                        }
                        if (toolResponse.responseParts) {
                            toolResponseParts.push(...toolResponse.responseParts);
                        }
                    }
                    // Record tool calls with full metadata before sending responses to Gemini
                    try {
                        const currentModel = geminiClient.getCurrentSequenceModel() ?? config.getModel();
                        geminiClient
                            .getChat()
                            .recordCompletedToolCalls(currentModel, completedToolCalls);
                        await recordToolCallInteractions(config, completedToolCalls);
                    }
                    catch (error) {
                        debugLogger.error(`Error recording completed tool call information: ${error}`);
                    }
                    // Check if any tool requested to stop execution immediately
                    const stopExecutionTool = completedToolCalls.find((tc) => tc.response.errorType === ToolErrorType.STOP_EXECUTION);
                    if (stopExecutionTool && stopExecutionTool.response.error) {
                        const stopMessage = `Agent execution stopped: ${stopExecutionTool.response.error.message}`;
                        if (config.getOutputFormat() === OutputFormat.TEXT) {
                            process.stderr.write(`${stopMessage}\n`);
                        }
                        // Emit final result event for streaming JSON
                        if (streamFormatter) {
                            const metrics = uiTelemetryService.getMetrics();
                            const durationMs = Date.now() - startTime;
                            streamFormatter.emitEvent({
                                type: JsonStreamEventType.RESULT,
                                timestamp: new Date().toISOString(),
                                status: 'success',
                                stats: streamFormatter.convertToStreamStats(metrics, durationMs),
                            });
                        }
                        else if (config.getOutputFormat() === OutputFormat.JSON) {
                            const formatter = new JsonFormatter();
                            const stats = uiTelemetryService.getMetrics();
                            textOutput.write(formatter.format(config.getSessionId(), responseText, stats));
                        }
                        else {
                            textOutput.ensureTrailingNewline(); // Ensure a final newline
                        }
                        return;
                    }
                    currentMessages = [{ role: 'user', parts: toolResponseParts }];
                }
                else {
                    // Emit final result event for streaming JSON
                    if (streamFormatter) {
                        const metrics = uiTelemetryService.getMetrics();
                        const durationMs = Date.now() - startTime;
                        streamFormatter.emitEvent({
                            type: JsonStreamEventType.RESULT,
                            timestamp: new Date().toISOString(),
                            status: 'success',
                            stats: streamFormatter.convertToStreamStats(metrics, durationMs),
                        });
                    }
                    else if (config.getOutputFormat() === OutputFormat.JSON) {
                        const formatter = new JsonFormatter();
                        const stats = uiTelemetryService.getMetrics();
                        textOutput.write(formatter.format(config.getSessionId(), responseText, stats));
                    }
                    else {
                        textOutput.ensureTrailingNewline(); // Ensure a final newline
                    }
                    return;
                }
            }
        }
        catch (error) {
            errorToHandle = error;
        }
        finally {
            // Cleanup stdin cancellation before other cleanup
            cleanupStdinCancellation();
            consolePatcher.cleanup();
            coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
        }
        if (errorToHandle) {
            handleError(errorToHandle, config);
        }
    });
}
//# sourceMappingURL=nonInteractiveCli.js.map