/**
 * Checkpoint serialization for the current WARP state
 *
 * Provides full state serialization including ORSet internals (entries + tombstones).
 * This is the AUTHORITATIVE checkpoint format for current state.
 *
 * Key differences from StateSerializer:
 * - StateSerializer serializes the VISIBLE PROJECTION (for hashing)
 * - CheckpointSerializer serializes the FULL INTERNAL STATE (for resume)
 *
 * @module CheckpointSerializer
 * @see WARP Spec Section 10 (Checkpoints)
 */
import defaultCodec from "../../utils/defaultCodec.js";
import ORSet from "../../crdt/ORSet.js";
import VersionVector from "../../crdt/VersionVector.js";
import { decodeDot } from "../../crdt/Dot.js";
import WarpState from "./WarpState.js";
import SchemaUnsupportedError from "../../errors/SchemaUnsupportedError.js";
import WarpError from "../../errors/WarpError.js";
// ============================================================================
// Full State Serialization (for Checkpoints)
// ============================================================================
/**
 * Serializes full state including ORSet internals (entries + tombstones).
 * This is the AUTHORITATIVE checkpoint format.
 */
export function serializeFullState(state, { codec } = {}) {
    const c = codec ?? defaultCodec;
    const nodeAliveObj = state.nodeAlive.serialize();
    const edgeAliveObj = state.edgeAlive.serialize();
    const propArray = serializePropsArray(WarpState.allPropEntriesFromState(state));
    const observedFrontierObj = VersionVector.serialize(state.observedFrontier);
    const edgeBirthArray = serializeEdgeBirthArray(state.edgeBirthEvent);
    return c.encode({
        version: 'full-v5',
        nodeAlive: nodeAliveObj,
        edgeAlive: edgeAliveObj,
        prop: propArray,
        observedFrontier: observedFrontierObj,
        edgeBirthEvent: edgeBirthArray,
    });
}
function serializePropsArray(propEntries) {
    const propArray = []; // nosemgrep: ts-no-unknown-outside-adapters -- 0025B
    for (const [key, register] of propEntries) {
        propArray.push([key, serializeLWWRegister(register)]);
    }
    propArray.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    return propArray;
}
function serializeEdgeBirthArray(edgeBirthEvent) {
    const result = [];
    if (edgeBirthEvent !== undefined && edgeBirthEvent !== null) {
        for (const [key, eventId] of edgeBirthEvent) {
            result.push([key, { lamport: eventId.lamport, writerId: eventId.writerId, patchSha: eventId.patchSha, opIndex: eventId.opIndex }]);
        }
        result.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    }
    return result;
}
/**
 * Deserializes full state. Used for resume.
 */
export function deserializeFullState(buffer, { codec: codecOpt } = {}) {
    const codec = codecOpt ?? defaultCodec;
    if (buffer === null || buffer === undefined) {
        throw new WarpError('Checkpoint state buffer is missing', 'E_CHECKPOINT_STATE_BUFFER_MISSING');
    }
    const obj = codec.decode(buffer);
    if (obj === null || obj === undefined) {
        throw new WarpError('Checkpoint state payload is missing', 'E_CHECKPOINT_STATE_PAYLOAD_MISSING');
    }
    if (obj.version !== undefined && obj.version !== 'full-v5') {
        throw new SchemaUnsupportedError(`Unsupported full state version: expected 'full-v5', got '${JSON.stringify(obj.version)}'`, // nosemgrep: ts-no-json-stringify-in-core -- 0025B
        { context: { version: obj.version } });
    }
    return new WarpState({
        nodeAlive: ORSet.deserialize(obj.nodeAlive ?? {}),
        edgeAlive: ORSet.deserialize(obj.edgeAlive ?? {}),
        prop: deserializeProps(obj.prop ?? []),
        observedFrontier: VersionVector.from(obj.observedFrontier ?? {}),
        edgeBirthEvent: deserializeEdgeBirthEvent(obj),
    });
}
export function serializeCheckpointStateEnvelope(state, { codec } = {}) {
    const c = codec ?? defaultCodec;
    return {
        nodeAlive: c.encode(state.nodeAlive.serialize()),
        edgeAlive: c.encode(state.edgeAlive.serialize()),
        prop: c.encode(serializePropsArray(WarpState.allPropEntriesFromState(state))),
        observedFrontier: c.encode(VersionVector.serialize(state.observedFrontier)),
        edgeBirthEvent: c.encode(serializeEdgeBirthArray(state.edgeBirthEvent)),
    };
}
export function deserializeCheckpointStateEnvelope(buffers, { codec } = {}) {
    const c = codec ?? defaultCodec;
    const emptyORSet = { entries: [], tombstones: [] };
    return new WarpState({
        nodeAlive: ORSet.deserialize(decodeEnvelopeBlob(c, buffers.nodeAlive, emptyORSet)),
        edgeAlive: ORSet.deserialize(decodeEnvelopeBlob(c, buffers.edgeAlive, emptyORSet)),
        prop: deserializeProps(decodeEnvelopeBlob(c, buffers.prop, [])),
        observedFrontier: VersionVector.from(decodeEnvelopeBlob(c, buffers.observedFrontier, {})),
        edgeBirthEvent: deserializeEdgeBirthEvent({
            edgeBirthEvent: decodeEnvelopeBlob(c, buffers.edgeBirthEvent, []),
        }),
    });
}
function decodeEnvelopeBlob(codec, buffer, fallback) {
    if (buffer.byteLength === 0) {
        return fallback;
    }
    return codec.decode(buffer);
}
// ============================================================================
// AppliedVV Computation and Serialization
// ============================================================================
/**
 * Computes appliedVV by scanning all dots in state.
 * Walks `nodeAlive` and `edgeAlive` via the ORSet's entryDotsIter.
 * Returns Map<writerId, maxCounter>.
 *
 * CRITICAL: This scans ALL entry dots, including tombstoned ones.
 * The appliedVV represents what operations have been applied, not what is visible.
 */
export function computeAppliedVV(state) {
    const vv = VersionVector.empty();
    function scanORSet(orset) {
        for (const encodedDot of orset.entryDotsIter()) {
            const dot = decodeDot(encodedDot);
            const current = vv.get(dot.writerId) ?? 0;
            if (dot.counter > current) {
                vv.set(dot.writerId, dot.counter);
            }
        }
    }
    scanORSet(state.nodeAlive);
    scanORSet(state.edgeAlive);
    return vv;
}
/**
 * Serializes appliedVV to CBOR format.
 */
export function serializeAppliedVV(vv, { codec } = {}) {
    const c = codec ?? defaultCodec;
    const obj = VersionVector.serialize(vv);
    return c.encode(obj);
}
/**
 * Deserializes appliedVV from CBOR format.
 */
export function deserializeAppliedVV(buffer, { codec } = {}) {
    const c = codec ?? defaultCodec;
    const obj = c.decode(buffer);
    return VersionVector.from(obj);
}
// ============================================================================
// Helper Functions
// ============================================================================
function deserializeProps(propArray) {
    const prop = new Map();
    if (!Array.isArray(propArray)) {
        return prop;
    }
    for (const [key, registerObj] of propArray) {
        const register = deserializeLWWRegister(registerObj);
        if (register !== null) {
            prop.set(key, register);
        }
    }
    return prop;
}
function deserializeEdgeBirthEvent(obj) {
    const edgeBirthEvent = new Map();
    const birthData = obj.edgeBirthEvent ?? obj.edgeBirthLamport;
    if (!Array.isArray(birthData)) {
        return edgeBirthEvent;
    }
    for (const [key, val] of birthData) {
        edgeBirthEvent.set(key, deserializeSingleBirthEvent(val));
    }
    return edgeBirthEvent;
}
function deserializeSingleBirthEvent(val) {
    if (typeof val === 'number') {
        return { lamport: val, writerId: '', patchSha: '0000', opIndex: 0 };
    }
    const ev = val;
    return { lamport: ev.lamport, writerId: ev.writerId, patchSha: ev.patchSha, opIndex: ev.opIndex };
}
function serializeLWWRegister(register) {
    if (register === null || register === undefined) {
        return null;
    }
    return {
        eventId: {
            lamport: register.eventId.lamport,
            opIndex: register.eventId.opIndex,
            patchSha: register.eventId.patchSha,
            writerId: register.eventId.writerId,
        },
        value: register.value,
    };
}
function deserializeLWWRegister(obj) {
    if (obj === null || obj === undefined) {
        return null;
    }
    return {
        eventId: {
            lamport: obj.eventId.lamport,
            writerId: obj.eventId.writerId,
            patchSha: obj.eventId.patchSha,
            opIndex: obj.eventId.opIndex,
        },
        // Codec boundary: deserialized value is typed as PropValue
        value: obj.value,
    };
}
