"use strict";
/**
 * useStreamingResponse Hook - Real-time streaming AI responses
 * Features: Server-sent events, backpressure handling, response reconstruction
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.useStreamingResponse = void 0;
const react_1 = require("react");
// Default API URL (in production, this would come from environment)
const DEFAULT_API_URL = 'http://localhost:3000/api/stream';
const useStreamingResponse = ({ apiUrl = DEFAULT_API_URL, enableBackpressure = true, maxConcurrentStreams = 3, bufferSize = 1024, retryAttempts = 3, retryDelay = 1000, onChunkReceived, onStreamComplete, onStreamError, onConnectionStatusChange } = {}) => {
    const [streamingMessage, setStreamingMessage] = (0, react_1.useState)('');
    const [isStreaming, setIsStreaming] = (0, react_1.useState)(false);
    const [streamProgress, setStreamProgress] = (0, react_1.useState)(0);
    const [connectionStatus, setConnectionStatus] = (0, react_1.useState)('disconnected');
    const [activeStreams, setActiveStreams] = (0, react_1.useState)(0);
    const [latency, setLatency] = (0, react_1.useState)(0);
    const [tokensPerSecond, setTokensPerSecond] = (0, react_1.useState)(0);
    // Refs for managing streaming state
    const abortControllerRef = (0, react_1.useRef)(null);
    const eventSourceRef = (0, react_1.useRef)(null);
    const streamStartTimeRef = (0, react_1.useRef)(0);
    const tokensReceivedRef = (0, react_1.useRef)(0);
    const isPausedRef = (0, react_1.useRef)(false);
    const messageBufferRef = (0, react_1.useRef)([]);
    const streamStatsRef = (0, react_1.useRef)({
        totalStreams: 0,
        successfulStreams: 0,
        failedStreams: 0,
        avgLatency: 0,
        avgTokensPerSecond: 0,
        totalTokensStreamed: 0,
        totalBytesReceived: 0,
        uptime: Date.now()
    });
    // Update connection status
    const updateConnectionStatus = (0, react_1.useCallback)((status) => {
        setConnectionStatus(status);
        onConnectionStatusChange?.(status);
    }, [onConnectionStatusChange]);
    // Process streaming chunk
    const processChunk = (0, react_1.useCallback)((chunk) => {
        if (isPausedRef.current) {
            // Buffer chunks when paused
            if (messageBufferRef.current.length < bufferSize) {
                messageBufferRef.current.push(JSON.stringify(chunk));
            }
            return;
        }
        const choice = chunk.choices[0];
        if (choice?.delta?.content) {
            const content = choice.delta.content;
            setStreamingMessage(prev => {
                const newMessage = prev + content;
                // Update tokens per second calculation
                const elapsed = (Date.now() - streamStartTimeRef.current) / 1000;
                if (elapsed > 0) {
                    tokensReceivedRef.current += content.split(' ').length;
                    setTokensPerSecond(Math.round(tokensReceivedRef.current / elapsed));
                }
                return newMessage;
            });
            // Update progress (simplified estimation)
            if (chunk.usage?.completion_tokens && chunk.usage?.total_tokens) {
                const progress = chunk.usage.completion_tokens / chunk.usage.total_tokens;
                setStreamProgress(Math.min(progress, 1));
            }
        }
        onChunkReceived?.(chunk);
        // Handle completion
        if (choice?.finish_reason) {
            handleStreamCompletion(chunk);
        }
    }, [bufferSize, onChunkReceived]);
    // Handle stream completion
    const handleStreamCompletion = (0, react_1.useCallback)((finalChunk) => {
        const endTime = Date.now();
        const totalLatency = endTime - streamStartTimeRef.current;
        setIsStreaming(false);
        setStreamProgress(1);
        setLatency(totalLatency);
        setActiveStreams(prev => Math.max(0, prev - 1));
        updateConnectionStatus('connected');
        // Update stats
        streamStatsRef.current.successfulStreams++;
        streamStatsRef.current.totalTokensStreamed += tokensReceivedRef.current;
        // Calculate average latency
        const totalStreams = streamStatsRef.current.totalStreams;
        streamStatsRef.current.avgLatency = ((streamStatsRef.current.avgLatency * (totalStreams - 1) + totalLatency) / totalStreams);
        // Calculate average tokens per second
        const currentTPS = tokensReceivedRef.current / (totalLatency / 1000);
        streamStatsRef.current.avgTokensPerSecond = ((streamStatsRef.current.avgTokensPerSecond * (totalStreams - 1) + currentTPS) / totalStreams);
        onStreamComplete?.(streamingMessage, {
            latency: totalLatency,
            tokensPerSecond: currentTPS,
            totalTokens: tokensReceivedRef.current,
            finishReason: finalChunk.choices[0]?.finish_reason,
            usage: finalChunk.usage
        });
    }, [streamingMessage, onStreamComplete, updateConnectionStatus]);
    // Stream response using Server-Sent Events
    const streamResponse = (0, react_1.useCallback)(async (request) => {
        if (activeStreams >= maxConcurrentStreams) {
            throw new Error(`Maximum concurrent streams (${maxConcurrentStreams}) reached`);
        }
        // Reset state
        setStreamingMessage('');
        setStreamProgress(0);
        setIsStreaming(true);
        setActiveStreams(prev => prev + 1);
        tokensReceivedRef.current = 0;
        streamStartTimeRef.current = Date.now();
        // Update stats
        streamStatsRef.current.totalStreams++;
        updateConnectionStatus('connecting');
        try {
            // Create abort controller
            abortControllerRef.current = new AbortController();
            // Prepare request data
            const streamRequest = {
                ...request,
                stream: true,
                timestamp: Date.now()
            };
            // Create EventSource for streaming
            const eventSourceUrl = new URL(apiUrl);
            eventSourceUrl.searchParams.set('data', btoa(JSON.stringify(streamRequest)));
            const eventSource = new EventSource(eventSourceUrl.toString());
            eventSourceRef.current = eventSource;
            updateConnectionStatus('connected');
            return new Promise((resolve, reject) => {
                let fullMessage = '';
                let messageMetadata = {};
                eventSource.onopen = () => {
                    console.log('🌊 Stream connection opened');
                };
                eventSource.onmessage = (event) => {
                    try {
                        if (event.data === '[DONE]') {
                            eventSource.close();
                            // Create final message
                            const finalMessage = {
                                id: `msg-${Date.now()}`,
                                role: 'assistant',
                                content: fullMessage,
                                timestamp: new Date(),
                                metadata: {
                                    model: request.model,
                                    provider: request.provider,
                                    tokens: tokensReceivedRef.current,
                                    latency: Date.now() - streamStartTimeRef.current,
                                    ...messageMetadata
                                },
                                reactions: {},
                                isStreaming: false,
                                isEditing: false
                            };
                            resolve(finalMessage);
                            return;
                        }
                        const chunk = JSON.parse(event.data);
                        processChunk(chunk);
                        // Accumulate full message
                        const content = chunk.choices[0]?.delta?.content;
                        if (content) {
                            fullMessage += content;
                        }
                        // Store metadata from final chunk
                        if (chunk.usage) {
                            messageMetadata = {
                                ...messageMetadata,
                                cost: calculateCost(chunk.usage, request.model),
                                ...chunk.usage
                            };
                        }
                        streamStatsRef.current.totalBytesReceived += event.data.length;
                    }
                    catch (error) {
                        console.error('Failed to parse stream chunk:', error);
                    }
                };
                eventSource.onerror = (error) => {
                    console.error('🌊 Stream error:', error);
                    eventSource.close();
                    setIsStreaming(false);
                    setActiveStreams(prev => Math.max(0, prev - 1));
                    updateConnectionStatus('error');
                    streamStatsRef.current.failedStreams++;
                    const streamError = new Error('Stream connection failed');
                    onStreamError?.(streamError);
                    reject(streamError);
                };
                // Handle abort
                abortControllerRef.current?.signal.addEventListener('abort', () => {
                    eventSource.close();
                    reject(new Error('Stream aborted'));
                });
            });
        }
        catch (error) {
            setIsStreaming(false);
            setActiveStreams(prev => Math.max(0, prev - 1));
            updateConnectionStatus('error');
            streamStatsRef.current.failedStreams++;
            const streamError = error instanceof Error ? error : new Error('Stream failed');
            onStreamError?.(streamError);
            throw streamError;
        }
    }, [
        activeStreams,
        maxConcurrentStreams,
        apiUrl,
        processChunk,
        onStreamError,
        updateConnectionStatus
    ]);
    // Abort current stream
    const abortStream = (0, react_1.useCallback)(() => {
        abortControllerRef.current?.abort();
        eventSourceRef.current?.close();
        setIsStreaming(false);
        setActiveStreams(prev => Math.max(0, prev - 1));
        updateConnectionStatus('disconnected');
    }, [updateConnectionStatus]);
    // Pause stream (buffer chunks)
    const pauseStream = (0, react_1.useCallback)(() => {
        isPausedRef.current = true;
    }, []);
    // Resume stream (process buffered chunks)
    const resumeStream = (0, react_1.useCallback)(() => {
        isPausedRef.current = false;
        // Process buffered chunks
        while (messageBufferRef.current.length > 0) {
            const chunkData = messageBufferRef.current.shift();
            if (chunkData) {
                try {
                    const chunk = JSON.parse(chunkData);
                    processChunk(chunk);
                }
                catch (error) {
                    console.error('Failed to process buffered chunk:', error);
                }
            }
        }
    }, [processChunk]);
    // Get streaming statistics
    const getStreamStats = (0, react_1.useCallback)(() => {
        return {
            ...streamStatsRef.current,
            uptime: Date.now() - streamStatsRef.current.uptime
        };
    }, []);
    // Cleanup on unmount
    (0, react_1.useEffect)(() => {
        return () => {
            abortControllerRef.current?.abort();
            eventSourceRef.current?.close();
        };
    }, []);
    return {
        streamingMessage,
        isStreaming,
        streamProgress,
        connectionStatus,
        activeStreams,
        latency,
        tokensPerSecond,
        streamResponse,
        abortStream,
        pauseStream,
        resumeStream,
        getStreamStats
    };
};
exports.useStreamingResponse = useStreamingResponse;
// Helper function to calculate API costs
function calculateCost(usage, model) {
    // Simplified cost calculation (in production, use actual pricing)
    const baseCostPer1K = {
        'gpt-4': 0.03,
        'gpt-3.5-turbo': 0.002,
        'claude-3': 0.015,
        'default': 0.002
    };
    const rate = baseCostPer1K[model] || baseCostPer1K.default;
    const totalTokens = usage.prompt_tokens + usage.completion_tokens;
    return (totalTokens / 1000) * rate;
}
exports.default = exports.useStreamingResponse;
//# sourceMappingURL=useStreamingResponse.js.map