import { isThenable, ACTION_REFRESH, ACTION_SERVER_ACTION, ACTION_NAVIGATE } from "../../../client/components/router-reducer/router-reducer-types";
import { reducer } from "../../../client/components/router-reducer/router-reducer";
import React, { startTransition } from "react";
export const ActionQueueContext = React.createContext(null);
function runRemainingActions(actionQueue, setState) {
    if (actionQueue.pending !== null) {
        actionQueue.pending = actionQueue.pending.next;
        if (actionQueue.pending !== null) {
            // eslint-disable-next-line @typescript-eslint/no-use-before-define
            runAction({
                actionQueue,
                action: actionQueue.pending,
                setState
            });
        }
    }
}
async function runAction(param) {
    let { actionQueue, action, setState } = param;
    const prevState = actionQueue.state;
    if (!prevState) {
        // This shouldn't happen as the state is initialized in the dispatcher if it's not set
        throw new Error("Invariant: Router state not initialized");
    }
    actionQueue.pending = action;
    actionQueue.last = action;
    const payload = action.payload;
    const actionResult = actionQueue.action(prevState, payload);
    function handleResult(nextState) {
        // if we discarded this action, the state should also be discarded
        if (action.discarded) {
            // if a refresh is needed, we only want to trigger it once the action queue is empty
            if (actionQueue.needsRefresh && actionQueue.pending === null) {
                actionQueue.needsRefresh = false;
                actionQueue.dispatch({
                    type: ACTION_REFRESH,
                    mutable: {},
                    origin: window.location.origin
                }, setState);
            }
            return;
        }
        actionQueue.state = nextState;
        if (actionQueue.devToolsInstance) {
            actionQueue.devToolsInstance.send(payload, nextState);
        }
        runRemainingActions(actionQueue, setState);
        action.resolve(nextState);
    }
    // if the action is a promise, set up a callback to resolve it
    if (isThenable(actionResult)) {
        actionResult.then(handleResult, (err)=>{
            runRemainingActions(actionQueue, setState);
            action.reject(err);
        });
    } else {
        handleResult(actionResult);
    }
}
function dispatchAction(actionQueue, payload, setState) {
    let resolvers;
    // Create the promise and assign the resolvers to the object.
    const deferredPromise = new Promise((resolve, reject)=>{
        resolvers = {
            resolve,
            reject
        };
    });
    const newAction = {
        payload,
        next: null,
        resolve: resolvers.resolve,
        reject: resolvers.reject
    };
    startTransition(()=>{
        // we immediately notify React of the pending promise -- the resolver is attached to the action node
        // and will be called when the associated action promise resolves
        setState(deferredPromise);
    });
    // Check if the queue is empty
    if (actionQueue.pending === null) {
        // The queue is empty, so add the action and start it immediately
        runAction({
            actionQueue,
            action: newAction,
            setState
        });
    } else if (payload.type === ACTION_NAVIGATE) {
        // Navigations take priority over any pending actions.
        // Mark the pending action as discarded (so the state is never applied) and start the navigation action immediately.
        actionQueue.pending.discarded = true;
        // if the pending action was a server action, mark the queue as needing a refresh once events are processed
        if (actionQueue.pending.payload.type === ACTION_SERVER_ACTION) {
            actionQueue.needsRefresh = true;
        }
        runAction({
            actionQueue,
            action: newAction,
            setState
        });
    } else {
        // The queue is not empty, so add the action to the end of the queue
        // It will be started by finishRunningAction after the previous action finishes
        if (actionQueue.last !== null) {
            actionQueue.last.next = newAction;
        }
        actionQueue.last = newAction;
    }
}
export function createMutableActionQueue() {
    const actionQueue = {
        state: null,
        dispatch: (payload, setState)=>dispatchAction(actionQueue, payload, setState),
        action: async (state, action)=>{
            if (state === null) {
                throw new Error("Invariant: Router state not initialized");
            }
            const result = reducer(state, action);
            return result;
        },
        pending: null,
        last: null
    };
    return actionQueue;
}

//# sourceMappingURL=action-queue.js.map