import QueryError from "../../errors/QueryError.js";
import { CURRENT_CHECKPOINT_SCHEMA } from "../state/checkpointHelpers.js";
export const CHECKPOINT_BASIS_MANIFEST_SCHEMA = 1;
const CHECKPOINT_BASIS_ROOT_FAMILIES = Object.freeze([
    'node-liveness',
    'node-property',
    'outgoing-adjacency',
    'incoming-adjacency',
    'edge-fact',
]);
export default class CheckpointBasisManifest {
    manifestSchema = CHECKPOINT_BASIS_MANIFEST_SCHEMA;
    schema;
    graphName;
    checkpointSha;
    _frontier;
    _appliedVersionVector;
    basisIdentity;
    semanticReadingIdentity;
    livenessRoots;
    propertyRoots;
    outgoingAdjacencyRoots;
    incomingAdjacencyRoots;
    edgeFactRoots;
    provenancePosture;
    contentAnchorPosture;
    shardGeometry;
    chunking;
    completeness;
    retainedPayloadRefs;
    retainedPayloadByteHashes;
    commitmentFamily;
    commitmentRoot;
    proofFamily;
    proofRefs;
    openedAperture;
    verificationPosture;
    constructor(options) {
        const schema = validateManifestOptions(options);
        const roots = manifestRootMaps(options);
        const nullableRefs = nullableManifestRefs(options);
        this.schema = schema;
        this.graphName = options.graphName;
        this.checkpointSha = options.checkpointSha;
        this._frontier = copyFrontier(options.frontier);
        this._appliedVersionVector = copyVersionVector(options.appliedVersionVector);
        this.basisIdentity = validateIdentity(options.basisIdentity, 'basisIdentity');
        this.semanticReadingIdentity = validateIdentity(options.semanticReadingIdentity, 'semanticReadingIdentity');
        this.livenessRoots = roots.livenessRoots;
        this.propertyRoots = roots.propertyRoots;
        this.outgoingAdjacencyRoots = roots.outgoingAdjacencyRoots;
        this.incomingAdjacencyRoots = roots.incomingAdjacencyRoots;
        this.edgeFactRoots = roots.edgeFactRoots;
        this.provenancePosture = options.provenancePosture;
        this.contentAnchorPosture = options.contentAnchorPosture;
        this.shardGeometry = options.shardGeometry;
        this.chunking = options.chunking;
        this.completeness = options.completeness;
        this.retainedPayloadRefs = freezeStringList(options.retainedPayloadRefs ?? [], 'retainedPayloadRefs');
        this.retainedPayloadByteHashes = freezeStringList(options.retainedPayloadByteHashes ?? [], 'retainedPayloadByteHashes');
        this.commitmentFamily = nullableRefs.commitmentFamily;
        this.commitmentRoot = nullableRefs.commitmentRoot;
        this.proofFamily = nullableRefs.proofFamily;
        this.openedAperture = nullableRefs.openedAperture;
        this.proofRefs = freezeStringList(options.proofRefs ?? [], 'proofRefs');
        this.verificationPosture = verificationPostureOrDefault(options.verificationPosture ?? null);
        validateManifestState(this);
        Object.freeze(this);
    }
    get frontier() {
        return copyFrontier(this._frontier);
    }
    get appliedVersionVector() {
        return copyVersionVector(this._appliedVersionVector);
    }
    rootMaps() {
        return Object.freeze([
            this.livenessRoots,
            this.propertyRoots,
            this.outgoingAdjacencyRoots,
            this.incomingAdjacencyRoots,
            this.edgeFactRoots,
        ]);
    }
}
function validateManifestOptions(options) {
    const schema = options.schema ?? CURRENT_CHECKPOINT_SCHEMA;
    validateSupportedSchema(schema);
    validateNonEmptyString(options.graphName, 'graphName');
    validateNonEmptyString(options.checkpointSha, 'checkpointSha');
    validateFrontier(options.frontier);
    validateAppliedVersionVector(options.appliedVersionVector);
    return schema;
}
function manifestRootMaps(options) {
    return {
        livenessRoots: requireRoots(options.livenessRoots, 'livenessRoots', 'node-liveness'),
        propertyRoots: requireRoots(options.propertyRoots, 'propertyRoots', 'node-property'),
        outgoingAdjacencyRoots: requireRoots(options.outgoingAdjacencyRoots, 'outgoingAdjacencyRoots', 'outgoing-adjacency'),
        incomingAdjacencyRoots: requireRoots(options.incomingAdjacencyRoots, 'incomingAdjacencyRoots', 'incoming-adjacency'),
        edgeFactRoots: requireRoots(options.edgeFactRoots, 'edgeFactRoots', 'edge-fact'),
    };
}
function nullableManifestRefs(options) {
    return {
        commitmentFamily: validateNullableIdentity(options.commitmentFamily ?? null, 'commitmentFamily'),
        commitmentRoot: validateNullableIdentity(options.commitmentRoot ?? null, 'commitmentRoot'),
        proofFamily: validateNullableIdentity(options.proofFamily ?? null, 'proofFamily'),
        openedAperture: validateNullableIdentity(options.openedAperture ?? null, 'openedAperture'),
    };
}
function validateManifestState(manifest) {
    validateSupportPosture(manifest.provenancePosture, 'provenancePosture');
    validateSupportPosture(manifest.contentAnchorPosture, 'contentAnchorPosture');
    validateSupportPosture(manifest.verificationPosture, 'verificationPosture');
    validateCompleteness(manifest.completeness);
    validateShardGeometry(manifest);
    validateReadingIdentitySeparation(manifest);
}
export class CheckpointBasisShardRootMap {
    family;
    _roots;
    constructor(options) {
        validateRootFamily(options.family);
        validateRoots(options.roots, options.family);
        this.family = options.family;
        this._roots = copyRoots(options.roots);
        Object.freeze(this);
    }
    get size() {
        return this._roots.size;
    }
    get(path) {
        return this._roots.get(path);
    }
    paths() {
        return Object.freeze([...this._roots.keys()].sort());
    }
    entries() {
        const entries = [];
        for (const [path, oid] of [...this._roots.entries()].sort(compareMapEntries)) {
            entries.push(Object.freeze([path, oid]));
        }
        return Object.freeze(entries);
    }
}
export class CheckpointBasisShardGeometry {
    layoutFamily;
    payloadLayout;
    shardKeyStrategy;
    shardCount;
    constructor(options) {
        this.layoutFamily = validateIdentity(options.layoutFamily, 'layoutFamily');
        this.payloadLayout = validateIdentity(options.payloadLayout, 'payloadLayout');
        this.shardKeyStrategy = validateIdentity(options.shardKeyStrategy, 'shardKeyStrategy');
        this.shardCount = validatePositiveInteger(options.shardCount, 'shardCount');
        Object.freeze(this);
    }
}
export class CheckpointBasisChunking {
    maxFactsPerShard;
    chunkCount;
    constructor(options) {
        this.maxFactsPerShard = validatePositiveInteger(options.maxFactsPerShard, 'maxFactsPerShard');
        this.chunkCount = validatePositiveInteger(options.chunkCount, 'chunkCount');
        Object.freeze(this);
    }
}
export class CheckpointBasisCompleteness {
    kind;
    reason;
    constructor(options) {
        validateCompletenessKind(options.kind);
        this.kind = options.kind;
        this.reason = options.reason === undefined || options.reason === null
            ? null
            : validateIdentity(options.reason, 'completeness.reason');
        Object.freeze(this);
    }
    static complete() {
        return new CheckpointBasisCompleteness({ kind: 'complete' });
    }
    static partial(reason) {
        return new CheckpointBasisCompleteness({ kind: 'partial', reason });
    }
    static obstructed(reason) {
        return new CheckpointBasisCompleteness({ kind: 'obstructed', reason });
    }
}
export class CheckpointBasisSupportPosture {
    kind;
    ref;
    reason;
    constructor(options) {
        validatePostureKind(options.kind);
        this.kind = options.kind;
        this.ref = options.ref === undefined || options.ref === null
            ? null
            : validateIdentity(options.ref, 'posture.ref');
        this.reason = options.reason === undefined || options.reason === null
            ? null
            : validateIdentity(options.reason, 'posture.reason');
        validatePostureShape(this);
        Object.freeze(this);
    }
    static present(ref) {
        return new CheckpointBasisSupportPosture({ kind: 'present', ref });
    }
    static unavailable(reason) {
        return new CheckpointBasisSupportPosture({ kind: 'unavailable', reason });
    }
    static obstructed(reason) {
        return new CheckpointBasisSupportPosture({ kind: 'obstructed', reason });
    }
}
function verificationPostureOrDefault(posture) {
    return posture ?? CheckpointBasisSupportPosture.present('verified');
}
function requireRoots(roots, field, family) {
    if (roots === undefined) {
        throwManifestError(field, 'missing-required-root');
    }
    if (!(roots instanceof CheckpointBasisShardRootMap)) {
        throwManifestError(field, 'invalid-root-map');
    }
    if (roots.family !== family) {
        throwManifestError(field, 'wrong-root-family');
    }
    return roots;
}
function validateSupportedSchema(schema) {
    if (schema !== CURRENT_CHECKPOINT_SCHEMA) {
        throwManifestError('schema', 'unsupported-schema');
    }
}
function validateRootFamily(family) {
    if (!CHECKPOINT_BASIS_ROOT_FAMILIES.includes(family)) {
        throwManifestError('family', 'unsupported-root-family');
    }
}
function validateCompletenessKind(kind) {
    if (kind !== 'complete' && kind !== 'partial' && kind !== 'obstructed') {
        throwManifestError('completeness.kind', 'unsupported-completeness');
    }
}
function validatePostureKind(kind) {
    if (kind !== 'present' && kind !== 'unavailable' && kind !== 'obstructed') {
        throwManifestError('posture.kind', 'unsupported-posture');
    }
}
function validateFrontier(frontier) {
    if (!(frontier instanceof Map) || frontier.size === 0) {
        throwManifestError('frontier', 'invalid-frontier');
    }
    for (const [writerId, patchSha] of frontier) {
        validateNonEmptyString(writerId, 'frontier.writerId');
        validateNonEmptyString(patchSha, 'frontier.patchSha');
    }
}
function validateAppliedVersionVector(versionVector) {
    if (!(versionVector instanceof Map)) {
        throwManifestError('appliedVersionVector', 'invalid-version-vector');
    }
    for (const [writerId, counter] of versionVector) {
        validateNonEmptyString(writerId, 'appliedVersionVector.writerId');
        validateNonNegativeInteger(counter, 'appliedVersionVector.counter');
    }
}
function validateRoots(roots, family) {
    if (!(roots instanceof Map)) {
        throwManifestError(`${family}.roots`, 'invalid-roots');
    }
    for (const [path, oid] of roots) {
        validateNonEmptyString(path, `${family}.path`);
        validateNonEmptyString(oid, `${family}.oid`);
    }
}
function validateSupportPosture(posture, field) {
    if (!(posture instanceof CheckpointBasisSupportPosture)) {
        throwManifestError(field, 'invalid-posture');
    }
}
function validateCompleteness(completeness) {
    if (!(completeness instanceof CheckpointBasisCompleteness)) {
        throwManifestError('completeness', 'invalid-completeness');
    }
    if (completeness.kind !== 'complete' && completeness.reason === null) {
        throwManifestError('completeness.reason', 'missing-completeness-reason');
    }
}
function validateShardGeometry(manifest) {
    validateShardGeometryObjects(manifest);
    const observed = new Set();
    for (const rootMap of manifest.rootMaps()) {
        for (const path of rootMap.paths()) {
            observed.add(`${rootMap.family}:${path}`);
        }
    }
    if (observed.size > manifest.shardGeometry.shardCount) {
        throwManifestError('shardGeometry.shardCount', 'incomplete-shard-geometry');
    }
    if (manifest.chunking.chunkCount > manifest.shardGeometry.shardCount) {
        throwManifestError('chunking.chunkCount', 'incomplete-chunking-geometry');
    }
}
function validateReadingIdentitySeparation(manifest) {
    const byteIdentityValues = initialByteIdentityValues(manifest);
    addRootIdentityValues(byteIdentityValues, manifest);
    addRetainedIdentityValues(byteIdentityValues, manifest);
    addCommitmentIdentityValues(byteIdentityValues, manifest);
    addProofIdentityValues(byteIdentityValues, manifest);
    if (byteIdentityValues.has(manifest.semanticReadingIdentity)) {
        throwManifestError('semanticReadingIdentity', 'semantic-identity-collides-with-byte-identity');
    }
}
function validateShardGeometryObjects(manifest) {
    if (!(manifest.shardGeometry instanceof CheckpointBasisShardGeometry)) {
        throwManifestError('shardGeometry', 'invalid-shard-geometry');
    }
    if (!(manifest.chunking instanceof CheckpointBasisChunking)) {
        throwManifestError('chunking', 'invalid-chunking');
    }
}
function initialByteIdentityValues(manifest) {
    return new Set([manifest.checkpointSha, manifest.basisIdentity]);
}
function addRootIdentityValues(values, manifest) {
    for (const rootMap of manifest.rootMaps()) {
        for (const [, oid] of rootMap.entries()) {
            values.add(oid);
        }
    }
}
function addRetainedIdentityValues(values, manifest) {
    for (const ref of manifest.retainedPayloadRefs) {
        values.add(ref);
    }
    for (const hash of manifest.retainedPayloadByteHashes) {
        values.add(hash);
    }
}
function addCommitmentIdentityValues(values, manifest) {
    if (manifest.commitmentRoot !== null) {
        values.add(manifest.commitmentRoot);
    }
}
function addProofIdentityValues(values, manifest) {
    for (const ref of manifest.proofRefs) {
        values.add(ref);
    }
}
function validatePostureShape(posture) {
    if (posture.kind === 'present' && posture.ref === null) {
        throwManifestError('posture.ref', 'missing-present-posture-ref');
    }
    if (posture.kind !== 'present' && posture.reason === null) {
        throwManifestError('posture.reason', 'missing-obstruction-reason');
    }
}
function copyFrontier(frontier) {
    return new Map([...frontier.entries()].sort(compareMapEntries));
}
function copyVersionVector(versionVector) {
    return new Map([...versionVector.entries()].sort(compareMapEntries));
}
function copyRoots(roots) {
    return new Map([...roots.entries()].sort(compareMapEntries));
}
function compareMapEntries(left, right) {
    return left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0;
}
function freezeStringList(values, field) {
    const copied = [];
    for (const value of values) {
        copied.push(validateIdentity(value, field));
    }
    return Object.freeze(copied);
}
function validateNullableIdentity(value, field) {
    return value === null ? null : validateIdentity(value, field);
}
function validateIdentity(value, field) {
    validateNonEmptyString(value, field);
    return value;
}
function validateNonEmptyString(value, field) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        throwManifestError(field, 'empty-string');
    }
}
function validatePositiveInteger(value, field) {
    if (!Number.isInteger(value) || value <= 0) {
        throwManifestError(field, 'invalid-positive-integer');
    }
    return value;
}
function validateNonNegativeInteger(value, field) {
    if (!Number.isInteger(value) || value < 0) {
        throwManifestError(field, 'invalid-non-negative-integer');
    }
}
function throwManifestError(field, reason) {
    throw new QueryError('Checkpoint basis manifest is invalid.', {
        code: 'E_CHECKPOINT_BASIS_MANIFEST',
        context: { field, reason },
    });
}
