import ORSet from "../../crdt/ORSet.js";
import VersionVector from "../../crdt/VersionVector.js";
import { Dot } from "../../crdt/Dot.js";
import WarpStateClass from "../state/WarpState.js";
import { ReducerSessionFrame, reducePatchesInSession, } from "../JoinReducerSession.js";
import { buildAdjacencyFromSession, } from "./MaterializeHelpers.js";
export async function reduceSessionBackedState(args) {
    const frame = await openReducerSessionFrame(args.openStateSession, args.baseState);
    try {
        if (args.receipts) {
            const result = await reducePatchesInSession(args.patches, frame, {
                receipts: true,
            });
            const adjacency = await buildAdjacencyFromSession(result.frame.session);
            return {
                state: await projectFrameToState(result.frame),
                adjacency,
                receipts: result.receipts,
            };
        }
        if (args.wantDiff) {
            const result = await reducePatchesInSession(args.patches, frame, {
                trackDiff: true,
            });
            const adjacency = await buildAdjacencyFromSession(result.frame.session);
            return {
                state: await projectFrameToState(result.frame),
                adjacency,
                diff: result.diff,
            };
        }
        const result = await reducePatchesInSession(args.patches, frame);
        const adjacency = await buildAdjacencyFromSession(result.session);
        return {
            state: await projectFrameToState(result),
            adjacency,
        };
    }
    finally {
        await frame.session.close();
    }
}
async function openReducerSessionFrame(openStateSession, baseState) {
    const session = await openStateSession({
        nodeAliveRootOid: null,
        edgeAliveRootOid: null,
    });
    if (baseState !== undefined) {
        await seedSessionWithORSet({
            session,
            kind: "node",
            source: baseState.nodeAlive,
        });
        await seedSessionWithORSet({
            session,
            kind: "edge",
            source: baseState.edgeAlive,
        });
    }
    return new ReducerSessionFrame({
        session,
        prop: new Map(baseState?.allPropEntries() ?? []),
        observedFrontier: baseState?.observedFrontier.clone() ?? VersionVector.empty(),
        edgeBirthEvent: new Map(baseState?.edgeBirthEvent ?? []),
    });
}
async function seedSessionWithORSet(args) {
    for (const [element, dots] of args.source.entriesIter()) {
        for (const encodedDot of dots) {
            const dot = Dot.decode(encodedDot);
            if (args.kind === "node") {
                await args.session.addNode(element, dot);
            }
            else {
                await args.session.addEdge(element, dot);
            }
        }
    }
    if (args.source.tombstones.size === 0) {
        return;
    }
    const tombstones = new Set(args.source.tombstones);
    if (args.kind === "node") {
        await args.session.removeNodes(tombstones);
    }
    else {
        await args.session.removeEdges(tombstones);
    }
}
async function projectFrameToState(frame) {
    return new WarpStateClass({
        nodeAlive: await projectORSet(frame.session.scanNodeElementStates()),
        edgeAlive: await projectORSet(frame.session.scanEdgeElementStates()),
        prop: new Map(frame.prop),
        observedFrontier: frame.observedFrontier.clone(),
        edgeBirthEvent: new Map(frame.edgeBirthEvent),
    });
}
async function projectORSet(states) {
    const entries = new Map();
    const tombstones = new Set();
    for await (const state of states) {
        entries.set(state.element, new Set([...state.dots, ...state.tombstonedDots]));
        for (const dot of state.tombstonedDots) {
            tombstones.add(dot);
        }
    }
    return new ORSet(entries, tombstones);
}
