import TreeEntryFound from "../../tree/TreeEntryFound.js";
import TreeEntryLimit from "../../tree/TreeEntryLimit.js";
import TreeEntryPath from "../../tree/TreeEntryPath.js";
import QueryError from "../../errors/QueryError.js";
import { isCurrentCheckpointSchema } from "../state/checkpointHelpers.js";
const FRONTIER_ENTRY_PATH = new TreeEntryPath('frontier.cbor');
const INDEX_SUBTREE_PATH = new TreeEntryPath('index');
const INDEX_SHARD_PREFIX = new TreeEntryPath('index/');
const INDEX_SHARD_EVIDENCE_LIMIT = new TreeEntryLimit(1);
export default class CheckpointTailBasisVerifier {
    _source;
    constructor(options) {
        this._source = options.source;
        Object.freeze(this);
    }
    async verify() {
        const checkpointSha = await this._readCheckpointSha();
        const checkpointMessage = await this._decodeCheckpointMessage(checkpointSha);
        this._verifyCheckpointSchema(checkpointMessage);
        await this._verifyCheckpointTree(checkpointMessage);
        return Object.freeze({ checkpointSha });
    }
    async _readCheckpointSha() {
        const checkpointSha = await this._source._readCheckpointSha();
        if (checkpointSha === null) {
            throwNoBoundedBasis(this._source.graphName, 'missing-checkpoint');
        }
        return checkpointSha;
    }
    async _decodeCheckpointMessage(checkpointSha) {
        const commitMessage = await this._source._persistence.showNode(checkpointSha);
        return this._source._commitMessageCodec.decodeCheckpoint(commitMessage);
    }
    _verifyCheckpointSchema(checkpointMessage) {
        if (!isCurrentCheckpointSchema(checkpointMessage.schema)) {
            throwNoBoundedBasis(this._source.graphName, 'checkpoint-without-index-tree');
        }
    }
    async _verifyCheckpointTree(checkpointMessage) {
        const treeEntryProbe = this._treeEntryProbePort();
        const frontierEntry = await treeEntryProbe.readTreeEntryOid(checkpointMessage.indexOid, FRONTIER_ENTRY_PATH);
        if (!(frontierEntry instanceof TreeEntryFound)) {
            throwNoBoundedBasis(this._source.graphName, 'checkpoint-missing-frontier');
        }
        const indexEntry = await treeEntryProbe.readTreeEntryOid(checkpointMessage.indexOid, INDEX_SUBTREE_PATH);
        if (indexEntry instanceof TreeEntryFound) {
            return;
        }
        const indexShardEvidence = await treeEntryProbe.readTreeEntryPrefix(checkpointMessage.indexOid, INDEX_SHARD_PREFIX, INDEX_SHARD_EVIDENCE_LIMIT);
        if (!indexShardEvidence.hasEntries()) {
            throwNoBoundedBasis(this._source.graphName, 'checkpoint-missing-index-shards');
        }
    }
    _treeEntryProbePort() {
        if (!hasTreeEntryProbePort(this._source._persistence)) {
            throwNoBoundedBasis(this._source.graphName, 'tree-entry-probe-unavailable');
        }
        return this._source._persistence;
    }
}
function hasTreeEntryProbePort(persistence) {
    return ('readTreeEntryOid' in persistence &&
        typeof persistence.readTreeEntryOid === 'function' &&
        'readTreeEntryPrefix' in persistence &&
        typeof persistence.readTreeEntryPrefix === 'function');
}
function throwNoBoundedBasis(graphName, reason) {
    throw new QueryError('No bounded checkpoint-tail optic basis is available.', {
        code: 'E_OPTIC_NO_BOUNDED_BASIS',
        context: { graphName, reason },
    });
}
