/**
 * Custom usePresence hook based on @convex-dev/presence/react.
 *
 * Fix: The upstream hook disconnects on `visibilitychange` (document.hidden),
 * which marks the user offline when the Electron window is minimized.
 * This version keeps heartbeats running when hidden and only sends an
 * immediate heartbeat + restarts the interval when becoming visible again
 * (in case the browser had throttled timers).
 */
import { useEffect, useMemo, useRef, useState } from "react";
import { useQuery, useMutation, useConvex } from "convex/react";
import useSingleFlight from "./useSingleFlight.js";

export default function usePresence(presence, roomId, userId, interval = 10000, convexUrl) {
    const hasMounted = useRef(false);
    const convex = useConvex();
    const baseUrl = convexUrl ?? convex.url;

    const [sessionId, setSessionId] = useState(() => crypto.randomUUID());
    const [sessionToken, setSessionToken] = useState(null);
    const sessionTokenRef = useRef(null);
    const [roomToken, setRoomToken] = useState(null);
    const roomTokenRef = useRef(null);
    const intervalRef = useRef(null);

    const heartbeat = useSingleFlight(useMutation(presence.heartbeat));
    const disconnect = useSingleFlight(useMutation(presence.disconnect));

    useEffect(() => {
        // Reset session state when roomId or userId changes.
        if (intervalRef.current) {
            clearInterval(intervalRef.current);
            intervalRef.current = null;
        }
        if (sessionTokenRef.current) {
            void disconnect({ sessionToken: sessionTokenRef.current });
        }
        setSessionId(crypto.randomUUID());
        setSessionToken(null);
        setRoomToken(null);
    }, [roomId, userId, disconnect]);

    useEffect(() => {
        sessionTokenRef.current = sessionToken;
        roomTokenRef.current = roomToken;
    }, [sessionToken, roomToken]);

    useEffect(() => {
        const sendHeartbeat = async () => {
            const result = await heartbeat({ roomId, userId, sessionId, interval });
            setRoomToken(result.roomToken);
            setSessionToken(result.sessionToken);
        };

        // Send initial heartbeat
        void sendHeartbeat();

        // Clear any existing interval before setting a new one
        if (intervalRef.current) {
            clearInterval(intervalRef.current);
        }
        intervalRef.current = setInterval(sendHeartbeat, interval);

        // Handle page unload.
        const handleUnload = () => {
            if (sessionTokenRef.current) {
                const blob = new Blob([
                    JSON.stringify({
                        path: "presence:disconnect",
                        args: { sessionToken: sessionTokenRef.current },
                    }),
                ], { type: "application/json" });
                navigator.sendBeacon(`${baseUrl}/api/mutation`, blob);
            }
        };
        window.addEventListener("beforeunload", handleUnload);

        // Handle visibility changes.
        // Desktop/web: Do NOT disconnect when hidden. Electron timers keep
        // running when minimized, so heartbeats continue normally. Only send
        // an immediate heartbeat when becoming visible again to recover
        // quickly in case the browser had throttled the interval.
        //
        // Capacitor (Android): When minimized and NOT in a voice call,
        // disconnect presence so the user appears offline. When in a voice
        // call, keep heartbeats running (user stays online).
        const isCapacitor = !!window.Capacitor?.isNativePlatform?.();

        const handleVisibility = async () => {
            if (document.hidden) {
                // Only disconnect on Capacitor when not in a voice call
                if (isCapacitor && !window.__inVoiceCall) {
                    if (intervalRef.current) {
                        clearInterval(intervalRef.current);
                        intervalRef.current = null;
                    }
                    if (sessionTokenRef.current) {
                        const blob = new Blob([
                            JSON.stringify({
                                path: "presence:disconnect",
                                args: { sessionToken: sessionTokenRef.current },
                            }),
                        ], { type: "application/json" });
                        navigator.sendBeacon(`${baseUrl}/api/mutation`, blob);
                    }
                }
            } else {
                // Becoming visible: send immediate heartbeat + restart interval
                void sendHeartbeat();
                if (intervalRef.current) {
                    clearInterval(intervalRef.current);
                }
                intervalRef.current = setInterval(sendHeartbeat, interval);
            }
        };
        const wrappedHandleVisibility = () => {
            handleVisibility().catch(console.error);
        };
        document.addEventListener("visibilitychange", wrappedHandleVisibility);

        // Cleanup.
        return () => {
            if (intervalRef.current) {
                clearInterval(intervalRef.current);
            }
            document.removeEventListener("visibilitychange", wrappedHandleVisibility);
            window.removeEventListener("beforeunload", handleUnload);
            // Don't disconnect on first render in strict mode.
            if (hasMounted.current) {
                if (sessionTokenRef.current) {
                    void disconnect({ sessionToken: sessionTokenRef.current });
                }
            }
        };
    }, [heartbeat, disconnect, roomId, userId, baseUrl, interval, sessionId]);

    useEffect(() => {
        hasMounted.current = true;
    }, []);

    const state = useQuery(presence.list, roomToken ? { roomToken } : "skip");
    return useMemo(() => state?.slice().sort((a, b) => {
        if (a.userId === userId) return -1;
        if (b.userId === userId) return 1;
        return 0;
    }), [state, userId]);
}
