import { jsx as _jsx } from "react/jsx-runtime";
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { useEffect, useLayoutEffect, useMemo, useRef, } from "react";
import { useStore } from "zustand";
import { ContextInternal } from "@foxglove/studio-base/components/MessagePipeline";
import MessageOrderTracker from "@foxglove/studio-base/components/MessagePipeline/MessageOrderTracker";
import { pauseFrameForPromises } from "@foxglove/studio-base/components/MessagePipeline/pauseFrameForPromise";
import { createMessagePipelineStore, defaultPlayerState } from "@foxglove/studio-base/components/MessagePipeline/store";
import { useConnections } from "./ConnectionsProvider.js";
const selectRenderDone = (state) => state.renderDone;
export function MessagePipelineProvider(props) {
    const connections = useConnections();
    const promisesToWaitForRef = useRef([]);
    const store = useMemo(() => {
        return createMessagePipelineStore({ promisesToWaitForRef, initialPlayer: undefined });
    }, []);
    // Tell listener the layout has completed
    const renderDone = useStore(store, selectRenderDone);
    useLayoutEffect(() => {
        renderDone?.();
    }, [renderDone]);
    useEffect(() => {
        const { listener, cleanupListener } = createMessageListener({
            promisesToWaitForRef,
            store,
        });
        connections.forEach(connection => {
            connection.setListener(listener);
        });
        return () => {
            cleanupListener();
            connections.forEach(connection => {
                connection.close();
            });
            store.getState().dispatch({
                type: "update-player-state",
                playerState: defaultPlayerState(),
                renderDone: undefined,
            });
        };
    }, [connections, store]);
    return _jsx(ContextInternal.Provider, { value: store, children: props.children });
}
function createMessageListener(args) {
    const { promisesToWaitForRef, store } = args;
    const updateState = store.getState().dispatch;
    const messageOrderTracker = new MessageOrderTracker();
    let closed = false;
    let resolveFn;
    const listener = async (listenerPlayerState) => {
        if (closed) {
            return;
        }
        if (resolveFn) {
            console.error("New playerState was emitted before last playerState was rendered.");
            return;
        }
        // check for any out-of-order or out-of-sync messages
        const problems = messageOrderTracker.update(listenerPlayerState);
        const newPlayerState = concatProblems(listenerPlayerState, problems);
        const promise = new Promise((resolve) => {
            resolveFn = () => {
                resolveFn = undefined;
                resolve();
            };
        });
        // Track when we start the state update. This will pair when layout effect calls renderDone.
        const start = Date.now();
        // Render done is invoked by a layout effect once the component has rendered.
        // After the component renders, we kick off an animation frame to give panels one
        // animation frame to invoke pause.
        let called = false;
        function renderDone() {
            if (called) {
                return;
            }
            called = true;
            // Compute how much time remains before this frame is done
            const delta = Date.now() - start;
            const frameTime = Math.max(0, 1000 / 60.0 - delta);
            // Panels have the remaining frame time to invoke pause
            setTimeout(async () => {
                if (closed) {
                    return;
                }
                const promisesToWaitFor = promisesToWaitForRef.current;
                if (promisesToWaitFor.length > 0) {
                    promisesToWaitForRef.current = [];
                    await pauseFrameForPromises(promisesToWaitFor);
                }
                if (!resolveFn) {
                    return;
                }
                resolveFn();
            }, frameTime);
        }
        updateState({
            type: "update-player-state",
            playerState: newPlayerState,
            renderDone,
        });
        await promise;
    };
    return {
        listener,
        cleanupListener() {
            closed = true;
            resolveFn = undefined;
        },
    };
}
function concatProblems(origState, problems) {
    if (problems.length === 0) {
        return origState;
    }
    return {
        ...origState,
        problems: problems.concat(origState.problems ?? []),
    };
}
//# sourceMappingURL=MessagePipelineProvider.js.map