import { useEffect, useRef } from "react";
import { checkConnectionAvailability, } from "../../services/editService";
import { createEditorSocketDiagnostics, getEditorSocketReadyStateLabel, } from "../socketDiagnostics";
export function useEditorWebSocket(params) {
    const { sessionId, onMessage, onOpen, onError, onConcurrentUserLimit, onSessionRevoked, connectSocket, requestQuota, sendClientInfo, setSocketDiagnostics, } = params;
    const socketInstanceRef = useRef(null);
    const attachedMessageHandlerRef = useRef(null);
    const reconnectTimeoutRef = useRef(null);
    const reconnectAttemptsRef = useRef(0);
    const reconnectCountRef = useRef(0);
    const isConcurrentUserLimitRef = useRef(false);
    // Stable refs for callbacks to avoid re-connecting on every render
    const onMessageRef = useRef(onMessage);
    const onOpenRef = useRef(onOpen);
    const onErrorRef = useRef(onError);
    const onConcurrentUserLimitRef = useRef(onConcurrentUserLimit);
    const onSessionRevokedRef = useRef(onSessionRevoked);
    const requestQuotaRef = useRef(requestQuota);
    const sendClientInfoRef = useRef(sendClientInfo);
    useEffect(() => {
        onMessageRef.current = onMessage;
    }, [onMessage]);
    useEffect(() => {
        onOpenRef.current = onOpen;
    }, [onOpen]);
    useEffect(() => {
        onErrorRef.current = onError;
    }, [onError]);
    useEffect(() => {
        onConcurrentUserLimitRef.current = onConcurrentUserLimit;
    }, [onConcurrentUserLimit]);
    useEffect(() => {
        onSessionRevokedRef.current = onSessionRevoked;
    }, [onSessionRevoked]);
    useEffect(() => {
        requestQuotaRef.current = requestQuota;
    }, [requestQuota]);
    useEffect(() => {
        sendClientInfoRef.current = sendClientInfo;
    }, [sendClientInfo]);
    useEffect(() => {
        setSocketDiagnostics?.(createEditorSocketDiagnostics(sessionId));
    }, [sessionId, setSocketDiagnostics]);
    useEffect(() => {
        let disposed = false;
        const shouldRecordSocketMessageActivity = (event) => {
            if (typeof event?.data !== "string") {
                return true;
            }
            try {
                const parsed = JSON.parse(event.data);
                if (!parsed) {
                    return true;
                }
                if (parsed.type !== "agent:run:delta") {
                    return true;
                }
                const deltaType = parsed.payload?.type;
                return String(deltaType || "").toLowerCase() !== "heartbeat";
            }
            catch {
                return true;
            }
        };
        const updateSocketDiagnostics = (patch, socket) => {
            if (!setSocketDiagnostics) {
                return;
            }
            setSocketDiagnostics((previous) => {
                const nextReadyState = socket?.readyState ??
                    (patch.readyState !== undefined ? patch.readyState : previous.readyState);
                return {
                    ...previous,
                    sessionId,
                    readyState: nextReadyState,
                    readyStateLabel: getEditorSocketReadyStateLabel(nextReadyState),
                    ...patch,
                };
            });
        };
        const markSocketEvent = (eventType, socket, patch) => {
            const now = new Date().toISOString();
            updateSocketDiagnostics({
                lastEventType: eventType,
                lastEventAt: now,
                ...patch,
            }, socket);
        };
        const scheduleReconnect = (socket) => {
            if (reconnectTimeoutRef.current) {
                return;
            }
            const attempts = reconnectAttemptsRef.current;
            const delay = Math.min(1000 * Math.pow(2, Math.min(attempts, 5)), 30000);
            reconnectCountRef.current += 1;
            markSocketEvent("reconnect-scheduled", socket, {
                reconnectCount: reconnectCountRef.current,
            });
            reconnectTimeoutRef.current = setTimeout(() => {
                reconnectTimeoutRef.current = null;
                reconnectAttemptsRef.current = attempts + 1;
                connect();
            }, delay);
        };
        const attach = (socket, handler, reusedExistingSocket) => {
            if (disposed) {
                return;
            }
            socket.addEventListener("message", handler);
            socketInstanceRef.current = socket;
            attachedMessageHandlerRef.current = handler;
            markSocketEvent(reusedExistingSocket ? "reused-socket" : "connect-attempt", socket);
        };
        const connect = async () => {
            // Check connection availability before attempting WebSocket connection
            try {
                const availability = await checkConnectionAvailability(sessionId);
                if (disposed)
                    return;
                if (!availability.allowed) {
                    // Connection not allowed - likely concurrent user limit
                    isConcurrentUserLimitRef.current = true;
                    if (availability.currentUsers !== undefined &&
                        availability.maxUsers !== undefined &&
                        availability.message) {
                        onConcurrentUserLimitRef.current?.({
                            currentUsers: availability.currentUsers,
                            maxUsers: availability.maxUsers,
                            message: availability.message,
                        });
                    }
                    // Don't attempt connection or retry for concurrent user limit
                    return;
                }
                // Reset the flag if connection is allowed
                isConcurrentUserLimitRef.current = false;
            }
            catch (error) {
                console.error("Error checking connection availability:", error);
                // Continue with connection attempt if check fails
            }
            if (disposed)
                return;
            let socket = globalThis.editorSocket;
            const existingSocketSessionId = socket?.__parheliaSessionId;
            const needsNewSocket = !socket ||
                existingSocketSessionId !== sessionId ||
                socket.readyState === WebSocket.CLOSING ||
                socket.readyState === WebSocket.CLOSED;
            const handleMessage = (event) => {
                if (shouldRecordSocketMessageActivity(event)) {
                    markSocketEvent("message", socketInstanceRef.current ?? socket, {
                        lastMessageAt: new Date().toISOString(),
                    });
                }
                onMessageRef.current?.(event);
            };
            if (needsNewSocket) {
                if (socket &&
                    existingSocketSessionId &&
                    existingSocketSessionId !== sessionId &&
                    socket.readyState !== WebSocket.CLOSED &&
                    socket.readyState !== WebSocket.CLOSING) {
                    socket.close();
                }
                socket = connectSocket(sessionId);
                markSocketEvent("connect-attempt", socket);
                socket.__parheliaSessionId =
                    sessionId;
                socket.addEventListener("open", () => {
                    if (disposed) {
                        return;
                    }
                    reconnectAttemptsRef.current = 0;
                    isConcurrentUserLimitRef.current = false;
                    markSocketEvent("open", socket, {
                        lastOpenAt: new Date().toISOString(),
                        lastCloseCode: null,
                        lastCloseReason: null,
                        lastErrorMessage: null,
                    });
                    onOpenRef.current?.();
                    sendClientInfoRef.current?.();
                    requestQuotaRef.current?.();
                });
                socket.addEventListener("close", (event) => {
                    if (disposed) {
                        return;
                    }
                    markSocketEvent("close", socket, {
                        lastCloseAt: new Date().toISOString(),
                        lastCloseCode: event.code,
                        lastCloseReason: event.reason || null,
                    });
                    // Don't retry if we hit concurrent user limit
                    if (isConcurrentUserLimitRef.current) {
                        return;
                    }
                    // For abnormal closes, re-check availability before reconnecting so
                    // concurrent-user-limit rejections do not get treated as transient failures.
                    const shouldCheckAvailability = event.code !== 1000;
                    const isSessionRevoked = event.code === 1008 || event.reason === "session-revoked";
                    if (isSessionRevoked) {
                        if (reconnectTimeoutRef.current) {
                            clearTimeout(reconnectTimeoutRef.current);
                            reconnectTimeoutRef.current = null;
                        }
                        onSessionRevokedRef.current?.();
                        return;
                    }
                    if (shouldCheckAvailability) {
                        checkConnectionAvailability(sessionId)
                            .then((availability) => {
                            if (!availability.allowed) {
                                isConcurrentUserLimitRef.current = true;
                                if (availability.currentUsers !== undefined &&
                                    availability.maxUsers !== undefined &&
                                    availability.message) {
                                    onConcurrentUserLimitRef.current?.({
                                        currentUsers: availability.currentUsers,
                                        maxUsers: availability.maxUsers,
                                        message: availability.message,
                                    });
                                }
                                return; // Don't retry
                            }
                            // Not a concurrent user limit issue, proceed with retry.
                            scheduleReconnect(socket);
                        })
                            .catch(() => {
                            // If the availability check fails, fall back to the normal retry path.
                            scheduleReconnect(socket);
                        });
                        return;
                    }
                });
                socket.addEventListener("error", (error) => {
                    if (disposed) {
                        return;
                    }
                    const errorMessage = error instanceof ErrorEvent && error.message
                        ? error.message
                        : null;
                    markSocketEvent("error", socket, {
                        lastErrorAt: new Date().toISOString(),
                        lastErrorMessage: errorMessage,
                    });
                    onErrorRef.current?.(error);
                });
                globalThis.editorSocket = socket;
            }
            if (socket) {
                attach(socket, handleMessage, !needsNewSocket);
                if (socket.readyState === WebSocket.OPEN) {
                    updateSocketDiagnostics({}, socket);
                    sendClientInfoRef.current?.();
                    requestQuotaRef.current?.();
                }
            }
        };
        // Call async connect function
        connect().catch((error) => {
            console.error("Error in WebSocket connection:", error);
            onErrorRef.current?.(new Event("error"));
        });
        return () => {
            disposed = true;
            if (reconnectTimeoutRef.current) {
                clearTimeout(reconnectTimeoutRef.current);
                reconnectTimeoutRef.current = null;
            }
            if (socketInstanceRef.current && attachedMessageHandlerRef.current) {
                socketInstanceRef.current.removeEventListener("message", attachedMessageHandlerRef.current);
                attachedMessageHandlerRef.current = null;
                socketInstanceRef.current = null;
            }
        };
    }, [sessionId, connectSocket]);
    return { socketRef: socketInstanceRef };
}
//# sourceMappingURL=useEditorWebSocket.js.map