import CheckpointStorePort from "../../ports/CheckpointStorePort.js";
import WarpError from "../../domain/errors/WarpError.js";
import ORSet from "../../domain/crdt/ORSet.js";
import VersionVector from "../../domain/crdt/VersionVector.js";
import { createEmptyState } from "../../domain/services/JoinReducer.js";
import WarpState from "../../domain/services/state/WarpState.js";
import { ProvenanceIndex } from "../../domain/services/provenance/ProvenanceIndex.js";
import { readPayloadBlob, writePayloadBlob } from "./CasPayloadPointer.js";
/**
 * CBOR-backed implementation of CheckpointStorePort.
 *
 * Owns the codec and raw blob persistence. Domain services call
 * writeCheckpoint(record) with domain objects; the adapter internally
 * encodes each artifact and writes blobs.
 */
export class CborCheckpointStoreAdapter extends CheckpointStorePort {
    _codec;
    _blobPort;
    _blobStorage;
    constructor({ codec, blobPort, blobStorage }) {
        super();
        if (codec === null || codec === undefined) {
            throw new WarpError('CborCheckpointStoreAdapter requires a codec', 'E_INVALID_DEPENDENCY');
        }
        if (blobPort === null || blobPort === undefined) {
            throw new WarpError('CborCheckpointStoreAdapter requires a blobPort', 'E_INVALID_DEPENDENCY');
        }
        this._codec = codec;
        this._blobPort = blobPort;
        this._blobStorage = blobStorage ?? null;
    }
    async writeCheckpoint(record) {
        const stateBytes = this._encodeFullState(record.state);
        const frontierBytes = this._encodeFrontier(record.frontier);
        const appliedVVBytes = this._encodeAppliedVV(record.appliedVV);
        let provenanceBytes = null;
        if (record.provenanceIndex !== null && record.provenanceIndex !== undefined) {
            provenanceBytes = record.provenanceIndex.serialize({ codec: this._codec });
        }
        const writes = [
            writePayloadBlob({
                blobPort: this._blobPort,
                blobStorage: this._blobStorage,
                bytes: stateBytes,
                options: {
                    slug: 'checkpoint-state',
                    mime: 'application/cbor',
                    size: stateBytes.length,
                },
            }),
            writePayloadBlob({
                blobPort: this._blobPort,
                blobStorage: this._blobStorage,
                bytes: frontierBytes,
                options: {
                    slug: 'checkpoint-frontier',
                    mime: 'application/cbor',
                    size: frontierBytes.length,
                },
            }),
            writePayloadBlob({
                blobPort: this._blobPort,
                blobStorage: this._blobStorage,
                bytes: appliedVVBytes,
                options: {
                    slug: 'checkpoint-applied-vv',
                    mime: 'application/cbor',
                    size: appliedVVBytes.length,
                },
            }),
        ];
        if (provenanceBytes !== null) {
            writes.push(writePayloadBlob({
                blobPort: this._blobPort,
                blobStorage: this._blobStorage,
                bytes: provenanceBytes,
                options: {
                    slug: 'checkpoint-provenance-index',
                    mime: 'application/cbor',
                    size: provenanceBytes.length,
                },
            }));
        }
        const oids = await Promise.all(writes);
        return {
            stateBlobOid: oids[0],
            frontierBlobOid: oids[1],
            appliedVVBlobOid: oids[2],
            provenanceIndexBlobOid: oids.length > 3 ? oids[3] : null,
        };
    }
    async readCheckpoint(treeOids) {
        const stateOid = treeOids['state.cbor'];
        const frontierOid = treeOids['frontier.cbor'];
        const appliedVVOid = treeOids['appliedVV.cbor'];
        const provenanceOid = treeOids['provenanceIndex.cbor'];
        if (stateOid === undefined) {
            throw new WarpError('Checkpoint missing state.cbor', 'E_MISSING_ARTIFACT');
        }
        if (frontierOid === undefined) {
            throw new WarpError('Checkpoint missing frontier.cbor', 'E_MISSING_ARTIFACT');
        }
        const reads = [
            readPayloadBlob({ blobPort: this._blobPort, blobStorage: this._blobStorage, oid: stateOid }),
            readPayloadBlob({ blobPort: this._blobPort, blobStorage: this._blobStorage, oid: frontierOid }),
        ];
        if (appliedVVOid !== undefined) {
            reads.push(readPayloadBlob({ blobPort: this._blobPort, blobStorage: this._blobStorage, oid: appliedVVOid }));
        }
        if (provenanceOid !== undefined) {
            reads.push(readPayloadBlob({ blobPort: this._blobPort, blobStorage: this._blobStorage, oid: provenanceOid }));
        }
        const buffers = await Promise.all(reads);
        let idx = 0;
        const state = this._decodeFullState(buffers[idx++]);
        const frontier = this._decodeFrontier(buffers[idx++]);
        let appliedVV = null;
        if (appliedVVOid !== undefined) {
            appliedVV = this._decodeAppliedVV(buffers[idx++]);
        }
        let provenanceIndex = null;
        if (provenanceOid !== undefined) {
            provenanceIndex = ProvenanceIndex.deserialize(buffers[idx++], { codec: this._codec });
        }
        let indexShardOids = null;
        const shardEntries = Object.entries(treeOids).filter(([p]) => p.startsWith('index/'));
        if (shardEntries.length > 0) {
            indexShardOids = Object.fromEntries(shardEntries.map(([p, o]) => [p.slice('index/'.length), o]));
        }
        return {
            state,
            frontier,
            appliedVV,
            stateHash: '',
            schema: 2,
            ...(provenanceIndex !== null ? { provenanceIndex } : {}),
            indexShardOids,
        };
    }
    // ── Encode Helpers ──────────────────────────────────────────────────
    _encodeFullState(state) {
        return this._codec.encode({
            version: 'full-v5',
            nodeAlive: state.nodeAlive.serialize(),
            edgeAlive: state.edgeAlive.serialize(),
            prop: _serializePropsArray(state.allPropEntries()),
            observedFrontier: VersionVector.serialize(state.observedFrontier),
            edgeBirthEvent: _serializeEdgeBirthArray(state.edgeBirthEvent),
        });
    }
    _encodeFrontier(frontier) {
        const obj = {};
        for (const key of Array.from(frontier.keys()).sort()) {
            obj[key] = frontier.get(key);
        }
        return this._codec.encode(obj);
    }
    _encodeAppliedVV(vv) {
        return this._codec.encode(VersionVector.serialize(vv));
    }
    // ── Decode Helpers ──────────────────────────────────────────────────
    _decodeFullState(buffer) {
        if (buffer === null || buffer === undefined) {
            return createEmptyState();
        }
        const obj = this._codec.decode(buffer);
        if (obj === null || obj === undefined) {
            return createEmptyState();
        }
        if (obj.version !== undefined && obj.version !== 'full-v5') {
            throw new WarpError(`Unsupported full state version: expected 'full-v5', got '${JSON.stringify(obj.version)}'`, 'E_UNSUPPORTED_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),
        });
    }
    _decodeFrontier(buffer) {
        const obj = this._codec.decode(buffer);
        const frontier = new Map();
        for (const [k, v] of Object.entries(obj)) {
            frontier.set(k, v);
        }
        return frontier;
    }
    _decodeAppliedVV(buffer) {
        const obj = this._codec.decode(buffer);
        return VersionVector.from(obj);
    }
}
// ── Private Helpers ───────────────────────────────────────────────────
function _serializePropsArray(propEntries) {
    const arr = [];
    for (const [key, register] of propEntries) {
        arr.push([key, _serializeLWWRegister(register)]);
    }
    arr.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    return arr;
}
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;
}
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 result = new Map();
    const birthData = obj.edgeBirthEvent ?? obj.edgeBirthLamport;
    if (!Array.isArray(birthData)) {
        return result;
    }
    for (const [key, val] of birthData) {
        if (typeof val === 'number') {
            result.set(key, { lamport: val, writerId: '', patchSha: '0000', opIndex: 0 });
        }
        else {
            const ev = val;
            result.set(key, {
                lamport: ev.lamport ?? 0,
                writerId: ev.writerId ?? '',
                patchSha: ev.patchSha ?? '0000',
                opIndex: ev.opIndex ?? 0,
            });
        }
    }
    return result;
}
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,
        },
        value: obj.value,
    };
}
