import { useEffect, useCallback, useState } from "react";

type MessageHandler = (data: any) => void;

let globalWs: WebSocket | null = null;
let globalHandlers = new Set<MessageHandler>();
let globalConnected = false;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
let reconnectAttempt = 0;

function getWsUrl(): string {
    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
    return `${protocol}//${window.location.host}/ws`;
}

function connect(): void {
    if (globalWs?.readyState === WebSocket.OPEN || globalWs?.readyState === WebSocket.CONNECTING) return;

    const ws = new WebSocket(getWsUrl());

    ws.onopen = () => {
        globalConnected = true;
        reconnectAttempt = 0;
        globalHandlers.forEach(h => h({ type: "__WS_CONNECTED" }));
    };

    ws.onmessage = (event) => {
        try {
            const data = JSON.parse(event.data);
            globalHandlers.forEach(h => h(data));
        } catch {
            // ignore parse errors
        }
    };

    ws.onclose = () => {
        globalConnected = false;
        globalWs = null;
        globalHandlers.forEach(h => h({ type: "__WS_DISCONNECTED" }));

        const delay = Math.min(1000 * 2 ** reconnectAttempt, 30000);
        reconnectAttempt++;
        reconnectTimer = setTimeout(connect, delay);
    };

    ws.onerror = () => {
        ws.close();
    };

    globalWs = ws;
}

export function useWebSocket() {
    const [connected, setConnected] = useState(globalConnected);

    useEffect(() => {
        if (!globalWs) {
            connect();
        }

        const handler: MessageHandler = (data) => {
            if (data.type === "__WS_CONNECTED") setConnected(true);
            if (data.type === "__WS_DISCONNECTED") setConnected(false);
        };
        globalHandlers.add(handler);

        return () => {
            globalHandlers.delete(handler);
        };
    }, []);

    const send = useCallback((data: unknown) => {
        if (globalWs?.readyState === WebSocket.OPEN) {
            globalWs.send(JSON.stringify(data));
        }
    }, []);

    const subscribe = useCallback((handler: MessageHandler) => {
        globalHandlers.add(handler);
        return () => { globalHandlers.delete(handler); };
    }, []);

    return { connected, send, subscribe };
}
