import { normalizeMarkerId } from "../utils";
import { activeInlineEditMatchesField, reduceActiveBridgeInlineEditForRefreshEvent, } from "./bridgeFieldPatch";
export function createBridgeInlineEditSessionCoordinator({ stateRef, onStateChange, }) {
    let nextTokenId = 0;
    const coordinatorId = typeof globalThis.crypto?.randomUUID === "function"
        ? globalThis.crypto.randomUUID()
        : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
    let currentToken = null;
    // The session most recently torn down by clear(). A mode change (e.g.
    // leaving suggestions mode) clears the session while the bridge's final
    // fieldValueChanged/inlineEditEnded commit is still in flight; that commit
    // must still route by the session's modeAtStart or a value typed under
    // suggestions mode is recorded as a direct edit. Consumed by end(),
    // superseded by the next beginSession.
    let recentlyClearedState = null;
    const pendingByKey = new Map();
    const publish = (state) => {
        stateRef.current = state;
        onStateChange?.(state);
    };
    const isCurrent = (token) => currentToken?.id === token.id;
    const invalidateCurrent = () => {
        if (currentToken) {
            const pending = pendingByKey.get(currentToken.key);
            if (pending?.token.id === currentToken.id) {
                pendingByKey.delete(currentToken.key);
            }
        }
        currentToken = null;
    };
    const clear = () => {
        if (stateRef.current)
            recentlyClearedState = stateRef.current;
        invalidateCurrent();
        publish(null);
    };
    const completeBegin = (token, authoritativeIdentity) => {
        if (!isCurrent(token) || !stateRef.current)
            return false;
        publish({
            ...stateRef.current,
            ...authoritativeIdentity,
            sessionId: token.sessionId,
        });
        return true;
    };
    const failBegin = (token) => {
        if (!isCurrent(token))
            return false;
        clear();
        return true;
    };
    const beginSession = (identity, operation) => {
        const key = getInlineEditSessionKey(identity);
        const pending = pendingByKey.get(key);
        if (pending && getInlineEditSessionKey(stateRef.current) === key) {
            return pending.promise;
        }
        if (!pending &&
            stateRef.current?.sessionId &&
            getInlineEditSessionKey(stateRef.current) === key) {
            return Promise.resolve(true);
        }
        const id = ++nextTokenId;
        const token = {
            id,
            key,
            sessionId: `inline-${coordinatorId}-${id}`,
        };
        currentToken = token;
        recentlyClearedState = null;
        publish({ ...identity, sessionId: token.sessionId });
        const promise = (async () => {
            try {
                const started = await operation({
                    token,
                    sessionId: token.sessionId,
                    isCurrent: () => isCurrent(token),
                });
                if (started) {
                    completeBegin(token);
                }
                else {
                    failBegin(token);
                }
                return started;
            }
            catch (error) {
                failBegin(token);
                throw error;
            }
            finally {
                const latest = pendingByKey.get(key);
                if (latest?.token.id === token.id)
                    pendingByKey.delete(key);
            }
        })();
        pendingByKey.set(key, { token, promise });
        return promise;
    };
    const matches = (identity) => inlineEditSessionMatches(stateRef.current, identity);
    const reject = (identity) => {
        if (!matches(identity))
            return false;
        clear();
        return true;
    };
    const recordValue = (identity, value, modeAtStart) => {
        if (!stateRef.current) {
            // Re-establishing a session for a field whose session was just cleared
            // (mode-change teardown) must keep the original modeAtStart — the
            // caller's fallback is the editor's CURRENT mode, which the teardown
            // already flipped.
            const sessionModeAtStart = getRecentlyClearedState(identity)?.modeAtStart ?? modeAtStart;
            if (sessionModeAtStart) {
                publish({ ...identity, modeAtStart: sessionModeAtStart, value });
                return true;
            }
        }
        if (!matches(identity) || !stateRef.current)
            return false;
        publish({
            ...stateRef.current,
            item: identity.item ?? stateRef.current.item,
            fieldId: identity.fieldId || stateRef.current.fieldId,
            elementKey: identity.elementKey || stateRef.current.elementKey,
            sessionId: identity.sessionId ?? stateRef.current.sessionId,
            value,
        });
        return true;
    };
    const getRecentlyClearedState = (identity) => recentlyClearedState &&
        inlineEditSessionMatches(recentlyClearedState, identity)
        ? recentlyClearedState
        : null;
    const end = (identity) => {
        const matched = matches(identity);
        let endedState = null;
        if (matched) {
            endedState = stateRef.current;
            // clear() retains the ended session as recentlyClearedState so commits
            // still in flight for it can resolve their original mode.
            clear();
        }
        else {
            endedState = getRecentlyClearedState(identity);
        }
        return { matched, endedState };
    };
    const reconcileForRefreshEvent = (event) => {
        const current = stateRef.current;
        const reconciled = reduceActiveBridgeInlineEditForRefreshEvent(current, event);
        if (reconciled === current)
            return current;
        if (!reconciled)
            invalidateCurrent();
        publish(reconciled);
        return reconciled;
    };
    return {
        beginSession,
        clear,
        completeBegin,
        end,
        failBegin,
        getRecentlyClearedState,
        getState: () => stateRef.current,
        isCurrent,
        matches,
        reconcileForRefreshEvent,
        recordValue,
        reject,
    };
}
export function inlineEditSessionMatches(active, identity) {
    if (!active)
        return false;
    if (active.sessionId && identity.sessionId) {
        return active.sessionId === identity.sessionId;
    }
    return (active.elementKey === identity.elementKey ||
        activeInlineEditMatchesField(active, identity));
}
function getInlineEditSessionKey(identity) {
    if (!identity)
        return "";
    return `${identity.elementKey}:${normalizeMarkerId(identity.fieldId)}`;
}
//# sourceMappingURL=bridgeInlineEditSession.js.map