/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
import { GeminiEventType as ServerGeminiEventType, getErrorMessage, isNodeError, MessageSenderType, logUserPrompt, GitService, UnauthorizedError, UserPromptEvent, DEFAULT_GEMINI_FLASH_MODEL, parseAndFormatApiError, getCodeAssistServer, UserTierId, EmojiFilter, DEFAULT_AGENT_ID, } from '@vybestack/llxprt-code-core';
import { FinishReason } from '@google/genai';
import { StreamingState, MessageType, ToolCallStatus, } from '../types.js';
import { isAtCommand } from '../utils/commandUtils.js';
import { useShellCommandProcessor } from './shellCommandProcessor.js';
import { handleAtCommand } from './atCommandProcessor.js';
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
import { useStateAndRef } from './useStateAndRef.js';
import { useLogger } from './useLogger.js';
import { promises as fs } from 'fs';
import path from 'path';
import { useReactToolScheduler, mapToDisplay as mapTrackedToolCallsToDisplay, } from './useReactToolScheduler.js';
import { useSessionStats } from '../contexts/SessionContext.js';
import { useKeypress } from './useKeypress.js';
const SYSTEM_NOTICE_EVENT = 'system_notice';
export function mergePartListUnions(list) {
    const resultParts = [];
    for (const item of list) {
        if (Array.isArray(item)) {
            // Each element in the array can be a string or Part
            for (const part of item) {
                if (typeof part === 'string') {
                    resultParts.push({ text: part });
                }
                else {
                    resultParts.push(part);
                }
            }
        }
        else if (typeof item === 'string') {
            resultParts.push({ text: item });
        }
        else {
            resultParts.push(item);
        }
    }
    return resultParts;
}
var StreamProcessingStatus;
(function (StreamProcessingStatus) {
    StreamProcessingStatus[StreamProcessingStatus["Completed"] = 0] = "Completed";
    StreamProcessingStatus[StreamProcessingStatus["UserCancelled"] = 1] = "UserCancelled";
    StreamProcessingStatus[StreamProcessingStatus["Error"] = 2] = "Error";
})(StreamProcessingStatus || (StreamProcessingStatus = {}));
function showCitations(settings, config) {
    // Try settings service first (consistent with core/turn.ts)
    try {
        const settingsService = config.getSettingsService();
        if (settingsService) {
            const enabled = settingsService.get('ui.showCitations');
            if (enabled !== undefined) {
                return enabled;
            }
        }
    }
    catch {
        // Fall through to other methods
    }
    // Fallback: check loaded settings for backwards compatibility
    const enabled = settings?.merged?.ui
        ?.showCitations;
    if (enabled !== undefined) {
        return enabled;
    }
    // Final fallback: check user tier
    const server = getCodeAssistServer(config);
    return (server && server.userTier !== UserTierId.FREE) ?? false;
}
/**
 * Manages the Gemini stream, including user input, command processing,
 * API interaction, and tool call lifecycle.
 */
export const useGeminiStream = (geminiClient, history, addItem, config, settings, onDebugMessage, handleSlashCommand, shellModeActive, getPreferredEditor, onAuthError, performMemoryRefresh, onEditorClose, onCancelSubmit, onTodoPause, onEditorOpen = () => { }) => {
    const [initError, setInitError] = useState(null);
    const abortControllerRef = useRef(null);
    const turnCancelledRef = useRef(false);
    const [isResponding, setIsResponding] = useState(false);
    const [thought, setThought] = useState(null);
    const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] = useStateAndRef(null);
    const processedMemoryToolsRef = useRef(new Set());
    const queuedSubmissionsRef = useRef([]);
    const submitQueryRef = useRef(null);
    const { startNewPrompt, getPromptCount } = useSessionStats();
    const storage = config.storage;
    // @plan:PLAN-20251202-THINKING-UI.P08
    // @requirement:REQ-THINK-UI-001
    const thinkingBlocksRef = useRef([]);
    // Initialize emoji filter
    const emojiFilter = useMemo(() => {
        const emojiFilterMode = typeof config.getEphemeralSetting === 'function'
            ? config.getEphemeralSetting('emojifilter') ||
                'auto'
            : 'auto';
        return emojiFilterMode !== 'allowed'
            ? new EmojiFilter({ mode: emojiFilterMode })
            : undefined;
    }, [config]);
    const sanitizeContent = useCallback((text) => {
        if (!emojiFilter) {
            return {
                text,
                feedback: undefined,
                blocked: false,
            };
        }
        const result = emojiFilter.filterText(text);
        if (result.blocked) {
            return {
                text: '',
                feedback: result.systemFeedback,
                blocked: true,
            };
        }
        const sanitized = typeof result.filtered === 'string' ? result.filtered : '';
        return {
            text: sanitized,
            feedback: result.systemFeedback,
            blocked: false,
        };
    }, [emojiFilter]);
    const flushPendingHistoryItem = useCallback((timestamp) => {
        const pending = pendingHistoryItemRef.current;
        if (!pending) {
            return;
        }
        if (pending.type === 'gemini' || pending.type === 'gemini_content') {
            const { text: sanitized, feedback, blocked, } = sanitizeContent(pending.text);
            if (blocked) {
                addItem({
                    type: MessageType.ERROR,
                    text: '[Error: Response blocked due to emoji detection]',
                }, timestamp);
                if (feedback) {
                    addItem({ type: MessageType.INFO, text: feedback }, timestamp);
                }
                setPendingHistoryItem(null);
                return;
            }
            // @plan:PLAN-20251202-THINKING-UI.P08
            // @requirement:REQ-THINK-UI-003
            // Always include thinkingBlocks for storage (display is controlled separately)
            const itemWithThinking = {
                ...pending,
                text: sanitized,
                ...(thinkingBlocksRef.current.length > 0
                    ? { thinkingBlocks: [...thinkingBlocksRef.current] }
                    : {}),
            };
            addItem(itemWithThinking, timestamp);
            if (feedback) {
                addItem({ type: MessageType.INFO, text: feedback }, timestamp);
            }
        }
        else {
            addItem(pending, timestamp);
        }
        setPendingHistoryItem(null);
    }, [addItem, pendingHistoryItemRef, sanitizeContent, setPendingHistoryItem]);
    const logger = useLogger(storage);
    const gitService = useMemo(() => {
        if (!config.getProjectRoot()) {
            return;
        }
        return new GitService(config.getProjectRoot(), storage);
    }, [config, storage]);
    const [toolCalls, scheduleToolCalls, markToolsAsSubmitted, cancelAllToolCalls,] = useReactToolScheduler(async (schedulerId, completedToolCallsFromScheduler, { isPrimary }) => {
        if (completedToolCallsFromScheduler.length === 0) {
            return;
        }
        if (isPrimary) {
            addItem(mapTrackedToolCallsToDisplay(completedToolCallsFromScheduler), Date.now());
            // Record tool calls with full metadata before sending responses.
            try {
                const currentModel = config.getGeminiClient().getCurrentSequenceModel() ??
                    config.getModel();
                config
                    .getGeminiClient()
                    .getChat()
                    .recordCompletedToolCalls(currentModel, completedToolCallsFromScheduler);
            }
            catch (error) {
                console.error(`Error recording completed tool call information: ${error}`);
            }
            // Handle tool response submission immediately when tools complete
            await handleCompletedTools(completedToolCallsFromScheduler);
            return;
        }
        const callIdsToMark = completedToolCallsFromScheduler.map((toolCall) => toolCall.request.callId);
        if (callIdsToMark.length > 0) {
            markToolsAsSubmitted(callIdsToMark);
        }
        addItem(mapTrackedToolCallsToDisplay(completedToolCallsFromScheduler), Date.now());
    }, config, setPendingHistoryItem, getPreferredEditor, onEditorClose, onEditorOpen);
    const pendingToolCallGroupDisplay = useMemo(() => toolCalls.length ? mapTrackedToolCallsToDisplay(toolCalls) : undefined, [toolCalls]);
    const loopDetectedRef = useRef(false);
    const onExec = useCallback(async (done) => {
        setIsResponding(true);
        await done;
        setIsResponding(false);
    }, []);
    const { handleShellCommand } = useShellCommandProcessor(addItem, setPendingHistoryItem, onExec, onDebugMessage, config, geminiClient);
    const streamingState = useMemo(() => {
        if (toolCalls.some((tc) => tc.status === 'awaiting_approval')) {
            return StreamingState.WaitingForConfirmation;
        }
        if (isResponding ||
            toolCalls.some((tc) => tc.status === 'executing' ||
                tc.status === 'scheduled' ||
                tc.status === 'validating' ||
                ((tc.status === 'success' ||
                    tc.status === 'error' ||
                    tc.status === 'cancelled') &&
                    !tc
                        .responseSubmittedToGemini))) {
            return StreamingState.Responding;
        }
        return StreamingState.Idle;
    }, [isResponding, toolCalls]);
    const cancelOngoingRequest = useCallback(() => {
        if (streamingState !== StreamingState.Responding) {
            return;
        }
        if (turnCancelledRef.current) {
            return;
        }
        turnCancelledRef.current = true;
        abortControllerRef.current?.abort();
        if (abortControllerRef.current) {
            // Synchronously clear the tool queue and mark active tools as cancelled in the UI.
            // This prevents race conditions where late-arriving cancellation events might be missed.
            cancelAllToolCalls();
        }
        if (pendingHistoryItemRef.current) {
            flushPendingHistoryItem(Date.now());
        }
        addItem({
            type: MessageType.INFO,
            text: 'Request cancelled.',
        }, Date.now());
        setPendingHistoryItem(null);
        onCancelSubmit();
        setIsResponding(false);
        queuedSubmissionsRef.current = [];
    }, [
        streamingState,
        addItem,
        setPendingHistoryItem,
        onCancelSubmit,
        pendingHistoryItemRef,
        flushPendingHistoryItem,
        cancelAllToolCalls,
    ]);
    const cancelOngoingRequestRef = useRef(cancelOngoingRequest);
    cancelOngoingRequestRef.current = cancelOngoingRequest;
    const handleEscapeKeypress = useCallback((key) => {
        const isEscape = key.name === 'escape' ||
            key.sequence === '\u001b[27u' ||
            key.sequence === '\u001b';
        if (!isEscape) {
            return;
        }
        cancelOngoingRequestRef.current();
    }, []);
    useKeypress(handleEscapeKeypress, { isActive: true });
    const scheduleNextQueuedSubmission = useCallback(() => {
        if (queuedSubmissionsRef.current.length === 0) {
            return;
        }
        const next = queuedSubmissionsRef.current.shift();
        if (!next) {
            return;
        }
        setTimeout(() => {
            void submitQueryRef.current?.(next.query, next.options, next.promptId);
        }, 0);
    }, []);
    const prepareQueryForGemini = useCallback(async (query, userMessageTimestamp, abortSignal, prompt_id) => {
        if (turnCancelledRef.current) {
            return { queryToSend: null, shouldProceed: false };
        }
        if (typeof query === 'string' && query.trim().length === 0) {
            return { queryToSend: null, shouldProceed: false };
        }
        let localQueryToSendToGemini = null;
        if (typeof query === 'string') {
            const trimmedQuery = query.trim();
            logUserPrompt(config, new UserPromptEvent(trimmedQuery.length, prompt_id, config.getContentGeneratorConfig()?.authType, trimmedQuery));
            onDebugMessage(`User query: '${trimmedQuery}'`);
            await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
            // Handle UI-only commands first
            const slashCommandResult = await handleSlashCommand(trimmedQuery);
            if (slashCommandResult) {
                switch (slashCommandResult.type) {
                    case 'schedule_tool': {
                        const { toolName, toolArgs } = slashCommandResult;
                        const toolCallRequest = {
                            callId: `${toolName}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
                            name: toolName,
                            args: toolArgs,
                            isClientInitiated: true,
                            prompt_id,
                            agentId: DEFAULT_AGENT_ID,
                        };
                        scheduleToolCalls([toolCallRequest], abortSignal);
                        return { queryToSend: null, shouldProceed: false };
                    }
                    case 'submit_prompt': {
                        localQueryToSendToGemini = slashCommandResult.content;
                        return {
                            queryToSend: localQueryToSendToGemini,
                            shouldProceed: true,
                        };
                    }
                    case 'handled': {
                        return { queryToSend: null, shouldProceed: false };
                    }
                    default: {
                        const unreachable = slashCommandResult;
                        throw new Error(`Unhandled slash command result type: ${unreachable}`);
                    }
                }
            }
            if (shellModeActive && handleShellCommand(trimmedQuery, abortSignal)) {
                return { queryToSend: null, shouldProceed: false };
            }
            // Handle @-commands (which might involve tool calls)
            if (isAtCommand(trimmedQuery)) {
                const atCommandResult = await handleAtCommand({
                    query: trimmedQuery,
                    config,
                    addItem,
                    onDebugMessage,
                    messageId: userMessageTimestamp,
                    signal: abortSignal,
                });
                // User message already displayed in submitQuery before this function was called
                if (!atCommandResult.shouldProceed) {
                    return { queryToSend: null, shouldProceed: false };
                }
                localQueryToSendToGemini = atCommandResult.processedQuery;
            }
            else {
                // Normal query for Gemini
                // User message already displayed in submitQuery before this function was called
                localQueryToSendToGemini = trimmedQuery;
            }
        }
        else {
            // It's a function response (PartListUnion that isn't a string)
            localQueryToSendToGemini = query;
        }
        if (localQueryToSendToGemini === null) {
            onDebugMessage('Query processing resulted in null, not sending to Gemini.');
            return { queryToSend: null, shouldProceed: false };
        }
        return { queryToSend: localQueryToSendToGemini, shouldProceed: true };
    }, [
        config,
        addItem,
        onDebugMessage,
        handleShellCommand,
        handleSlashCommand,
        logger,
        shellModeActive,
        scheduleToolCalls,
    ]);
    // --- Stream Event Handlers ---
    const handleContentEvent = useCallback((eventValue, currentGeminiMessageBuffer, userMessageTimestamp) => {
        if (turnCancelledRef.current) {
            // Prevents additional output after a user initiated cancel.
            return '';
        }
        const combined = currentGeminiMessageBuffer + eventValue;
        const { text: sanitizedCombined, feedback, blocked, } = sanitizeContent(combined);
        if (blocked) {
            addItem({
                type: MessageType.ERROR,
                text: '[Error: Response blocked due to emoji detection]',
            }, userMessageTimestamp);
            if (feedback) {
                addItem({ type: MessageType.INFO, text: feedback }, userMessageTimestamp);
            }
            return currentGeminiMessageBuffer;
        }
        if (feedback) {
            addItem({ type: MessageType.INFO, text: feedback }, userMessageTimestamp);
        }
        if (pendingHistoryItemRef.current?.type !== 'gemini' &&
            pendingHistoryItemRef.current?.type !== 'gemini_content') {
            if (pendingHistoryItemRef.current) {
                flushPendingHistoryItem(userMessageTimestamp);
            }
            // @plan:PLAN-20251202-THINKING-UI.P08
            // Include thinkingBlocks in pending item so they display during streaming
            setPendingHistoryItem({
                type: 'gemini',
                text: '',
                ...(thinkingBlocksRef.current.length > 0
                    ? { thinkingBlocks: [...thinkingBlocksRef.current] }
                    : {}),
            });
        }
        const splitPoint = findLastSafeSplitPoint(sanitizedCombined);
        if (splitPoint === sanitizedCombined.length) {
            // @plan:PLAN-20251202-THINKING-UI.P08
            // Preserve thinkingBlocks during streaming updates
            setPendingHistoryItem((item) => ({
                type: item?.type,
                text: sanitizedCombined,
                ...(thinkingBlocksRef.current.length > 0
                    ? { thinkingBlocks: [...thinkingBlocksRef.current] }
                    : {}),
            }));
            return sanitizedCombined;
        }
        const beforeText = sanitizedCombined.substring(0, splitPoint);
        const afterText = sanitizedCombined.substring(splitPoint);
        const pendingType = pendingHistoryItemRef.current?.type === 'gemini_content'
            ? 'gemini_content'
            : 'gemini';
        if (beforeText) {
            // @plan:PLAN-20251202-THINKING-UI.P08
            // @requirement:REQ-THINK-UI-003
            // Always include thinkingBlocks for storage (display is controlled separately)
            addItem({
                type: pendingType,
                text: beforeText,
                ...(thinkingBlocksRef.current.length > 0
                    ? { thinkingBlocks: [...thinkingBlocksRef.current] }
                    : {}),
            }, userMessageTimestamp);
        }
        // @plan:PLAN-20251202-THINKING-UI.P08
        // Preserve thinkingBlocks in continuation pending item
        setPendingHistoryItem({
            type: 'gemini_content',
            text: afterText,
            ...(thinkingBlocksRef.current.length > 0
                ? { thinkingBlocks: [...thinkingBlocksRef.current] }
                : {}),
        });
        return afterText;
    }, [
        addItem,
        pendingHistoryItemRef,
        sanitizeContent,
        setPendingHistoryItem,
        flushPendingHistoryItem,
    ]);
    const handleUserCancelledEvent = useCallback((userMessageTimestamp) => {
        if (turnCancelledRef.current) {
            return;
        }
        if (pendingHistoryItemRef.current) {
            if (pendingHistoryItemRef.current.type === 'tool_group') {
                const updatedTools = pendingHistoryItemRef.current.tools.map((tool) => tool.status === ToolCallStatus.Pending ||
                    tool.status === ToolCallStatus.Confirming ||
                    tool.status === ToolCallStatus.Executing
                    ? { ...tool, status: ToolCallStatus.Canceled }
                    : tool);
                const pendingItem = {
                    ...pendingHistoryItemRef.current,
                    tools: updatedTools,
                };
                addItem(pendingItem, userMessageTimestamp);
            }
            else {
                flushPendingHistoryItem(userMessageTimestamp);
            }
            setPendingHistoryItem(null);
        }
        addItem({ type: MessageType.INFO, text: 'User cancelled the request.' }, userMessageTimestamp);
        setIsResponding(false);
        queuedSubmissionsRef.current = [];
        setThought(null); // Reset thought when user cancels
    }, [
        addItem,
        pendingHistoryItemRef,
        setPendingHistoryItem,
        setThought,
        flushPendingHistoryItem,
    ]);
    const handleErrorEvent = useCallback((eventValue, userMessageTimestamp) => {
        if (pendingHistoryItemRef.current) {
            flushPendingHistoryItem(userMessageTimestamp);
            setPendingHistoryItem(null);
        }
        addItem({
            type: MessageType.ERROR,
            text: parseAndFormatApiError(eventValue.error, config.getContentGeneratorConfig()?.authType, undefined, config.getModel(), DEFAULT_GEMINI_FLASH_MODEL),
        }, userMessageTimestamp);
        setThought(null); // Reset thought when there's an error
    }, [
        addItem,
        pendingHistoryItemRef,
        setPendingHistoryItem,
        config,
        setThought,
        flushPendingHistoryItem,
    ]);
    const handleCitationEvent = useCallback((text, userMessageTimestamp) => {
        if (!showCitations(settings, config)) {
            return;
        }
        if (pendingHistoryItemRef.current) {
            flushPendingHistoryItem(userMessageTimestamp);
            setPendingHistoryItem(null);
        }
        addItem({ type: MessageType.INFO, text }, userMessageTimestamp);
    }, [
        addItem,
        pendingHistoryItemRef,
        setPendingHistoryItem,
        settings,
        config,
        flushPendingHistoryItem,
    ]);
    const handleFinishedEvent = useCallback((event, userMessageTimestamp) => {
        const finishReason = event.value.reason;
        const finishReasonMessages = {
            [FinishReason.FINISH_REASON_UNSPECIFIED]: undefined,
            [FinishReason.STOP]: undefined,
            [FinishReason.MAX_TOKENS]: 'Response truncated due to token limits.',
            [FinishReason.SAFETY]: 'Response stopped due to safety reasons.',
            [FinishReason.RECITATION]: 'Response stopped due to recitation policy.',
            [FinishReason.LANGUAGE]: 'Response stopped due to unsupported language.',
            [FinishReason.BLOCKLIST]: 'Response stopped due to forbidden terms.',
            [FinishReason.PROHIBITED_CONTENT]: 'Response stopped due to prohibited content.',
            [FinishReason.SPII]: 'Response stopped due to sensitive personally identifiable information.',
            [FinishReason.OTHER]: 'Response stopped for other reasons.',
            [FinishReason.MALFORMED_FUNCTION_CALL]: 'Response stopped due to malformed function call.',
            [FinishReason.IMAGE_SAFETY]: 'Response stopped due to image safety violations.',
            [FinishReason.UNEXPECTED_TOOL_CALL]: 'Response stopped due to unexpected tool call.',
        };
        const message = finishReasonMessages[finishReason];
        if (message) {
            addItem({
                type: 'info',
                text: `⚠️  ${message}`,
            }, userMessageTimestamp);
        }
    }, [addItem]);
    const handleChatCompressionEvent = useCallback((eventValue, userMessageTimestamp) => {
        if (pendingHistoryItemRef.current) {
            addItem(pendingHistoryItemRef.current, userMessageTimestamp);
            setPendingHistoryItem(null);
        }
        return addItem({
            type: 'info',
            text: `IMPORTANT: This conversation approached the input token limit for ${config.getModel()}. ` +
                `A compressed context will be sent for future messages (compressed from: ` +
                `${eventValue?.originalTokenCount ?? 'unknown'} to ` +
                `${eventValue?.newTokenCount ?? 'unknown'} tokens).`,
        }, Date.now());
    }, [addItem, config, pendingHistoryItemRef, setPendingHistoryItem]);
    const handleMaxSessionTurnsEvent = useCallback(() => addItem({
        type: 'info',
        text: `The session has reached the maximum number of turns: ${config.getMaxSessionTurns()}. ` +
            `Please update this limit in your setting.json file.`,
    }, Date.now()), [addItem, config]);
    const handleLoopDetectedEvent = useCallback(() => {
        addItem({
            type: 'info',
            text: `A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.`,
        }, Date.now());
    }, [addItem]);
    const processGeminiStreamEvents = useCallback(async (stream, userMessageTimestamp, signal) => {
        let geminiMessageBuffer = '';
        const toolCallRequests = [];
        for await (const event of stream) {
            if (event.type === SYSTEM_NOTICE_EVENT) {
                // SystemNotice events are internal model reminders, not for user display
                // They are consumed here but not added to visible history
                continue;
            }
            switch (event.type) {
                case ServerGeminiEventType.Thought:
                    // @plan:PLAN-20251202-THINKING-UI.P08
                    // @requirement:REQ-THINK-UI-001
                    setThought(event.value);
                    // Accumulate as ThinkingBlock for history
                    {
                        const thinkingBlock = {
                            type: 'thinking',
                            thought: [event.value.subject, event.value.description]
                                .filter(Boolean)
                                .join(': '),
                            sourceField: 'thought',
                        };
                        thinkingBlocksRef.current.push(thinkingBlock);
                    }
                    break;
                case ServerGeminiEventType.Content:
                    geminiMessageBuffer = handleContentEvent(event.value, geminiMessageBuffer, userMessageTimestamp);
                    break;
                case ServerGeminiEventType.ToolCallRequest:
                    toolCallRequests.push({
                        ...event.value,
                        agentId: event.value.agentId ?? DEFAULT_AGENT_ID,
                    });
                    break;
                case ServerGeminiEventType.UserCancelled:
                    handleUserCancelledEvent(userMessageTimestamp);
                    break;
                case ServerGeminiEventType.Error:
                    handleErrorEvent(event.value, userMessageTimestamp);
                    break;
                case ServerGeminiEventType.ChatCompressed:
                    handleChatCompressionEvent(event.value, userMessageTimestamp);
                    break;
                case ServerGeminiEventType.ToolCallConfirmation:
                case ServerGeminiEventType.ToolCallResponse:
                    // do nothing
                    break;
                case ServerGeminiEventType.MaxSessionTurns:
                    handleMaxSessionTurnsEvent();
                    break;
                case ServerGeminiEventType.Finished:
                    handleFinishedEvent(event, userMessageTimestamp);
                    break;
                case ServerGeminiEventType.LoopDetected:
                    // handle later because we want to move pending history to history
                    // before we add loop detected message to history
                    loopDetectedRef.current = true;
                    break;
                case ServerGeminiEventType.UsageMetadata:
                    // Handle usage metadata - for now just ignore
                    break;
                case ServerGeminiEventType.Citation:
                    handleCitationEvent(event.value, userMessageTimestamp);
                    break;
                case ServerGeminiEventType.Retry:
                    // Will add the missing logic later
                    break;
                default:
                    break;
            }
        }
        if (toolCallRequests.length > 0) {
            scheduleToolCalls(toolCallRequests, signal);
        }
        return StreamProcessingStatus.Completed;
    }, [
        handleContentEvent,
        handleUserCancelledEvent,
        handleErrorEvent,
        scheduleToolCalls,
        handleChatCompressionEvent,
        handleFinishedEvent,
        handleMaxSessionTurnsEvent,
        handleCitationEvent,
    ]);
    const submitQuery = useCallback(async (query, options, prompt_id) => {
        if ((streamingState === StreamingState.Responding ||
            streamingState === StreamingState.WaitingForConfirmation) &&
            !options?.isContinuation) {
            queuedSubmissionsRef.current.push({
                query,
                options,
                promptId: prompt_id,
            });
            return;
        }
        const userMessageTimestamp = Date.now();
        abortControllerRef.current = new AbortController();
        const abortSignal = abortControllerRef.current.signal;
        turnCancelledRef.current = false;
        if (!prompt_id) {
            prompt_id = config.getSessionId() + '########' + getPromptCount();
        }
        // Display user message IMMEDIATELY for string queries (not continuations)
        // This ensures the user sees their input right away, before any async processing
        if (typeof query === 'string' &&
            query.trim().length > 0 &&
            !options?.isContinuation) {
            const trimmedQuery = query.trim();
            addItem({ type: MessageType.USER, text: trimmedQuery }, userMessageTimestamp);
        }
        const { queryToSend, shouldProceed } = await prepareQueryForGemini(query, userMessageTimestamp, abortSignal, prompt_id);
        if (!shouldProceed || queryToSend === null) {
            scheduleNextQueuedSubmission();
            return;
        }
        if (!options?.isContinuation) {
            startNewPrompt();
            setThought(null); // Reset thought when starting a new prompt
            // @plan:PLAN-20251202-THINKING-UI.P08
            thinkingBlocksRef.current = [];
        }
        setIsResponding(true);
        setInitError(null);
        try {
            const stream = geminiClient.sendMessageStream(queryToSend, abortSignal, prompt_id);
            const processingStatus = await processGeminiStreamEvents(stream, userMessageTimestamp, abortSignal);
            if (processingStatus === StreamProcessingStatus.UserCancelled) {
                return;
            }
            if (pendingHistoryItemRef.current) {
                flushPendingHistoryItem(userMessageTimestamp);
                setPendingHistoryItem(null);
            }
            if (loopDetectedRef.current) {
                loopDetectedRef.current = false;
                handleLoopDetectedEvent();
            }
        }
        catch (error) {
            if (error instanceof UnauthorizedError) {
                onAuthError();
            }
            else if (!isNodeError(error) || error.name !== 'AbortError') {
                addItem({
                    type: MessageType.ERROR,
                    text: parseAndFormatApiError(getErrorMessage(error) || 'Unknown error', config.getContentGeneratorConfig()?.authType, undefined, config.getModel(), DEFAULT_GEMINI_FLASH_MODEL),
                }, userMessageTimestamp);
            }
        }
        finally {
            setIsResponding(false);
        }
    }, [
        streamingState,
        prepareQueryForGemini,
        processGeminiStreamEvents,
        pendingHistoryItemRef,
        addItem,
        setPendingHistoryItem,
        setInitError,
        geminiClient,
        onAuthError,
        config,
        startNewPrompt,
        getPromptCount,
        handleLoopDetectedEvent,
        flushPendingHistoryItem,
        scheduleNextQueuedSubmission,
    ]);
    useEffect(() => {
        submitQueryRef.current = submitQuery;
    }, [submitQuery]);
    useEffect(() => {
        if (streamingState === StreamingState.Idle) {
            scheduleNextQueuedSubmission();
        }
    }, [streamingState, scheduleNextQueuedSubmission]);
    const handleCompletedTools = useCallback(async (completedToolCallsFromScheduler) => {
        if (isResponding) {
            return;
        }
        const completedAndReadyToSubmitTools = completedToolCallsFromScheduler.filter((tc) => {
            const isTerminalState = tc.status === 'success' ||
                tc.status === 'error' ||
                tc.status === 'cancelled';
            if (isTerminalState) {
                const completedOrCancelledCall = tc;
                return (completedOrCancelledCall.response?.responseParts !== undefined);
            }
            return false;
        });
        const [primaryTools, externalTools] = completedAndReadyToSubmitTools.reduce((acc, toolCall) => {
            const agentId = toolCall.request.agentId ?? DEFAULT_AGENT_ID;
            if (agentId === DEFAULT_AGENT_ID) {
                acc[0].push(toolCall);
            }
            else {
                acc[1].push(toolCall);
            }
            return acc;
        }, [[], []]);
        if (externalTools.length > 0) {
            markToolsAsSubmitted(externalTools.map((toolCall) => toolCall.request.callId));
        }
        if (primaryTools.length === 0) {
            return;
        }
        const todoPauseTriggered = primaryTools.some((tc) => tc.request.name === 'todo_pause' && tc.status === 'success');
        if (todoPauseTriggered) {
            onTodoPause?.();
        }
        // Finalize any client-initiated tools as soon as they are done.
        const clientTools = primaryTools.filter((t) => t.request.isClientInitiated);
        if (clientTools.length > 0) {
            markToolsAsSubmitted(clientTools.map((t) => t.request.callId));
        }
        // Identify new, successful save_memory calls that we haven't processed yet.
        const newSuccessfulMemorySaves = primaryTools.filter((t) => t.request.name === 'save_memory' &&
            t.status === 'success' &&
            !processedMemoryToolsRef.current.has(t.request.callId));
        if (newSuccessfulMemorySaves.length > 0) {
            // Perform the refresh only if there are new ones.
            void performMemoryRefresh();
            // Mark them as processed so we don't do this again on the next render.
            newSuccessfulMemorySaves.forEach((t) => processedMemoryToolsRef.current.add(t.request.callId));
        }
        const geminiTools = primaryTools.filter((t) => !t.request.isClientInitiated);
        if (geminiTools.length === 0) {
            return;
        }
        // If all the tools were cancelled, don't submit a response to Gemini.
        const allToolsCancelled = geminiTools.every((tc) => tc.status === 'cancelled');
        if (allToolsCancelled) {
            if (geminiClient) {
                // We need to manually add the function responses to the history
                // so the model knows the tools were cancelled.
                const combinedParts = geminiTools.flatMap((toolCall) => toolCall.response.responseParts);
                geminiClient.addHistory({
                    role: 'user',
                    parts: combinedParts,
                });
            }
            const callIdsToMarkAsSubmitted = geminiTools.map((toolCall) => toolCall.request.callId);
            markToolsAsSubmitted(callIdsToMarkAsSubmitted);
            return;
        }
        const responsesToSend = geminiTools.flatMap((toolCall) => toolCall.response.responseParts);
        const callIdsToMarkAsSubmitted = geminiTools.map((toolCall) => toolCall.request.callId);
        const prompt_ids = geminiTools.map((toolCall) => toolCall.request.prompt_id);
        markToolsAsSubmitted(callIdsToMarkAsSubmitted);
        submitQuery(responsesToSend, {
            isContinuation: true,
        }, prompt_ids[0]);
    }, [
        isResponding,
        submitQuery,
        markToolsAsSubmitted,
        geminiClient,
        performMemoryRefresh,
        onTodoPause,
    ]);
    const pendingHistoryItems = useMemo(() => [pendingHistoryItem, pendingToolCallGroupDisplay].filter((i) => i !== undefined && i !== null), [pendingHistoryItem, pendingToolCallGroupDisplay]);
    useEffect(() => {
        const saveRestorableToolCalls = async () => {
            if (!config.getCheckpointingEnabled()) {
                return;
            }
            const restorableToolCalls = toolCalls.filter((toolCall) => (toolCall.request.name === 'replace' ||
                toolCall.request.name === 'write_file') &&
                toolCall.status === 'awaiting_approval');
            if (restorableToolCalls.length > 0) {
                const checkpointDir = storage.getProjectTempCheckpointsDir();
                if (!checkpointDir) {
                    return;
                }
                try {
                    await fs.mkdir(checkpointDir, { recursive: true });
                }
                catch (error) {
                    if (!isNodeError(error) || error.code !== 'EEXIST') {
                        onDebugMessage(`Failed to create checkpoint directory: ${getErrorMessage(error)}`);
                        return;
                    }
                }
                for (const toolCall of restorableToolCalls) {
                    const filePath = toolCall.request.args['file_path'];
                    if (!filePath) {
                        onDebugMessage(`Skipping restorable tool call due to missing file_path: ${toolCall.request.name}`);
                        continue;
                    }
                    try {
                        if (!gitService) {
                            onDebugMessage(`Checkpointing is enabled but Git service is not available. Failed to create snapshot for ${filePath}. Ensure Git is installed and working properly.`);
                            continue;
                        }
                        let commitHash;
                        try {
                            commitHash = await gitService.createFileSnapshot(`Snapshot for ${toolCall.request.name}`);
                        }
                        catch (error) {
                            onDebugMessage(`Failed to create new snapshot: ${getErrorMessage(error)}. Attempting to use current commit.`);
                        }
                        if (!commitHash) {
                            commitHash = await gitService.getCurrentCommitHash();
                        }
                        if (!commitHash) {
                            onDebugMessage(`Failed to create snapshot for ${filePath}. Checkpointing may not be working properly. Ensure Git is installed and the project directory is accessible.`);
                            continue;
                        }
                        const timestamp = new Date()
                            .toISOString()
                            .replace(/:/g, '-')
                            .replace(/\./g, '_');
                        const toolName = toolCall.request.name;
                        const fileName = path.basename(filePath);
                        const toolCallWithSnapshotFileName = `${timestamp}-${fileName}-${toolName}.json`;
                        const clientHistory = await geminiClient?.getHistory();
                        const toolCallWithSnapshotFilePath = path.join(checkpointDir, toolCallWithSnapshotFileName);
                        await fs.writeFile(toolCallWithSnapshotFilePath, JSON.stringify({
                            history,
                            clientHistory,
                            toolCall: {
                                name: toolCall.request.name,
                                args: toolCall.request.args,
                            },
                            commitHash,
                            filePath,
                        }, null, 2));
                    }
                    catch (error) {
                        onDebugMessage(`Failed to create checkpoint for ${filePath}: ${getErrorMessage(error)}. This may indicate a problem with Git or file system permissions.`);
                    }
                }
            }
        };
        saveRestorableToolCalls();
    }, [
        toolCalls,
        config,
        onDebugMessage,
        gitService,
        history,
        geminiClient,
        storage,
    ]);
    return {
        streamingState,
        submitQuery,
        initError,
        pendingHistoryItems,
        thought,
        cancelOngoingRequest,
    };
};
//# sourceMappingURL=useGeminiStream.js.map