import { useState, useRef, useCallback, useEffect } from 'react';
import { parseSSELine } from '../utils/sseFormatParsers.js';
import { StreamingTimeoutError, StreamingServerError, StreamingNetworkError, normalizeError } from '../utils/streamingErrors.js';
import { shouldRetry, calculateRetryDelay } from '../utils/retryUtils.js';

/**
 * useStreamingSSE Hook
 * Handles Server-Sent Events (SSE) streaming for AI/LLM responses
 * Supports OpenAI, Anthropic (Claude), and Google Gemini formats
 */
/**
 * Hook for handling SSE streaming from AI/LLM APIs
 *
 * @example
 * const {
 *   accumulatedText,
 *   isStreaming,
 *   error,
 *   start,
 *   stop
 * } = useStreamingSSE({
 *   url: '/api/chat',
 *   format: 'openai',
 *   onToken: (token) => console.log('New token:', token),
 *   onComplete: (text) => console.log('Complete:', text),
 * });
 */
const useStreamingSSE = (options) => {
    const { url, method = 'POST', headers, body, format = 'openai', autoStart = true, timeout = 30000, retry, onToken, onComplete, onError, onStart, onAbort, } = options;
    // State
    const [accumulatedText, setAccumulatedText] = useState('');
    const [isStreaming, setIsStreaming] = useState(false);
    const [error, setError] = useState(null);
    const [isComplete, setIsComplete] = useState(false);
    const [retryAttempt, setRetryAttempt] = useState(0);
    // Refs for cleanup and buffering
    const abortControllerRef = useRef(null);
    const timeoutRef = useRef(null);
    const retryTimeoutRef = useRef(undefined);
    const bufferRef = useRef(''); // For incomplete SSE lines
    // Store callback props in refs to keep `start` stable
    const onTokenRef = useRef(onToken);
    const onCompleteRef = useRef(onComplete);
    const onErrorRef = useRef(onError);
    const onStartRef = useRef(onStart);
    const onAbortRef = useRef(onAbort);
    onTokenRef.current = onToken;
    onCompleteRef.current = onComplete;
    onErrorRef.current = onError;
    onStartRef.current = onStart;
    onAbortRef.current = onAbort;
    /**
     * Start streaming from the SSE endpoint
     */
    const start = useCallback(async () => {
        var _a, _b, _c, _d, _e, _f;
        // Abort any existing stream
        if (abortControllerRef.current) {
            abortControllerRef.current.abort();
        }
        // Create new AbortController
        const controller = new AbortController();
        abortControllerRef.current = controller;
        // Set timeout if configured
        if (timeout) {
            timeoutRef.current = setTimeout(() => {
                var _a;
                controller.abort();
                const timeoutError = new StreamingTimeoutError(timeout);
                setError(timeoutError);
                setIsStreaming(false);
                (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, timeoutError);
            }, timeout);
        }
        // Reset state
        setIsStreaming(true);
        setError(null);
        setIsComplete(false);
        setAccumulatedText('');
        bufferRef.current = '';
        (_a = onStartRef.current) === null || _a === void 0 ? void 0 : _a.call(onStartRef);
        try {
            // Make fetch request
            const response = await fetch(url, {
                method,
                headers: {
                    'Content-Type': 'application/json',
                    ...headers,
                },
                body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined,
                signal: controller.signal,
            });
            // Check for HTTP errors
            if (!response.ok) {
                throw new StreamingServerError(response.status, response.statusText, await response.text().catch(() => undefined));
            }
            // Get reader from response body stream
            const reader = (_b = response.body) === null || _b === void 0 ? void 0 : _b.getReader();
            if (!reader) {
                throw new StreamingNetworkError('No response body available');
            }
            const decoder = new TextDecoder();
            let fullText = '';
            // Read stream chunks
            while (true) {
                const { done, value } = await reader.read();
                if (done)
                    break;
                // Decode chunk
                const chunk = decoder.decode(value, { stream: true });
                bufferRef.current += chunk;
                // Parse complete lines (SSE lines end with \n or \n\n)
                const lines = bufferRef.current.split('\n');
                bufferRef.current = lines.pop() || ''; // Keep incomplete line in buffer
                for (const line of lines) {
                    if (!line.trim())
                        continue; // Skip empty lines
                    const token = parseSSELine(line, format);
                    if (token) {
                        fullText += token;
                        setAccumulatedText(fullText);
                        (_c = onTokenRef.current) === null || _c === void 0 ? void 0 : _c.call(onTokenRef, token, fullText);
                    }
                }
            }
            // Clear timeout
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
                timeoutRef.current = null;
            }
            // Streaming completed successfully
            setIsStreaming(false);
            setIsComplete(true);
            (_d = onCompleteRef.current) === null || _d === void 0 ? void 0 : _d.call(onCompleteRef, fullText);
        }
        catch (err) {
            // Ignore AbortErrors (user cancelled or component unmounted)
            if (err instanceof Error && err.name === 'AbortError') {
                setIsStreaming(false);
                (_e = onAbortRef.current) === null || _e === void 0 ? void 0 : _e.call(onAbortRef);
                return;
            }
            // Handle real errors
            const streamingError = normalizeError(err);
            setError(streamingError);
            setIsStreaming(false);
            (_f = onErrorRef.current) === null || _f === void 0 ? void 0 : _f.call(onErrorRef, streamingError);
            // Auto-retry if configured and error is retryable
            if (shouldRetry(streamingError, retryAttempt, retry)) {
                const delay = calculateRetryDelay(retryAttempt, retry);
                if (process.env.NODE_ENV !== 'production') {
                    console.log(`[useStreamingSSE] Retrying in ${delay}ms (attempt ${retryAttempt + 1}/${(retry === null || retry === void 0 ? void 0 : retry.maxAttempts) || 3})`);
                }
                retryTimeoutRef.current = setTimeout(() => {
                    setRetryAttempt((prev) => prev + 1);
                    start();
                }, delay);
            }
        }
    }, [
        url,
        method,
        headers,
        body,
        format,
        timeout,
        retry,
        retryAttempt,
    ]);
    /**
     * Stop streaming (abort)
     */
    const stop = useCallback(() => {
        var _a;
        if (abortControllerRef.current) {
            abortControllerRef.current.abort();
        }
        if (timeoutRef.current) {
            clearTimeout(timeoutRef.current);
            timeoutRef.current = null;
        }
        clearTimeout(retryTimeoutRef.current);
        setIsStreaming(false);
        (_a = onAbortRef.current) === null || _a === void 0 ? void 0 : _a.call(onAbortRef);
    }, []);
    /**
     * Retry streaming
     */
    const retryFn = useCallback(() => {
        setRetryAttempt(0);
        start();
    }, [start]);
    /**
     * Reset to initial state
     */
    const reset = useCallback(() => {
        stop();
        setAccumulatedText('');
        setError(null);
        setIsComplete(false);
        setRetryAttempt(0);
        bufferRef.current = '';
    }, [stop]);
    // Cleanup on unmount
    useEffect(() => {
        return () => {
            if (abortControllerRef.current) {
                abortControllerRef.current.abort();
            }
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
            }
            clearTimeout(retryTimeoutRef.current);
        };
    }, []);
    // Auto-start if enabled
    useEffect(() => {
        if (autoStart) {
            start();
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []); // Only run on mount
    return {
        accumulatedText,
        isStreaming,
        error,
        isComplete,
        retryAttempt,
        start,
        stop,
        retry: retryFn,
        reset,
    };
};

export { useStreamingSSE };
