import QueryError from "../../errors/QueryError.js";
export default class CheckpointTailWitnessScan {
    _source;
    _maxTailPatches;
    constructor(options) {
        this._source = options.source;
        this._maxTailPatches = options.maxTailPatches;
        Object.freeze(this);
    }
    async collect(options) {
        const scan = createTailWitnessScanDraft();
        for (const writerId of await this._sortedWriterIds()) {
            await this._scanWriterTail({
                basis: options.basis,
                includeEntry: options.includeEntry,
                scan,
                writerId,
            });
        }
        return {
            entries: Object.freeze(scan.entries),
            witnesses: Object.freeze(scan.witnesses),
        };
    }
    async _sortedWriterIds() {
        return Object.freeze([...(await this._source.discoverWriters())].sort());
    }
    async _scanWriterTail(options) {
        const writerTail = await this._loadWriterTail(options.basis, options.writerId);
        this._accountTailBudget(options.scan, writerTail.length);
        await this._validateWriterTail(options.writerId, writerTail, options.basis);
        collectIncludedTailEntries({
            entries: writerTail,
            includeEntry: options.includeEntry,
            scan: options.scan,
        });
    }
    async _loadWriterTail(basis, writerId) {
        const stopAtSha = basis.frontier.get(writerId) ?? null;
        return await this._source._loadWriterPatches(writerId, stopAtSha);
    }
    _accountTailBudget(scan, patchCount) {
        scan.scanned += patchCount;
        if (scan.scanned > this._maxTailPatches) {
            throwTailBudgetExceeded(this._source.graphName, {
                budgetLimit: this._maxTailPatches,
                budgetObserved: scan.scanned,
            });
        }
    }
    async _validateWriterTail(writerId, writerTail, basis) {
        if (writerTail.length === 0) {
            return;
        }
        const lastEntry = writerTail[writerTail.length - 1];
        if (lastEntry === undefined) {
            return;
        }
        await this._source._validatePatchAgainstCheckpoint(writerId, lastEntry.sha, { schema: basis.schema, frontier: basis.frontier });
    }
}
function createTailWitnessScanDraft() {
    return {
        entries: [],
        witnesses: [],
        scanned: 0,
    };
}
function collectIncludedTailEntries(options) {
    for (const entry of options.entries) {
        if (options.includeEntry(entry)) {
            options.scan.entries.push(entry);
            options.scan.witnesses.push({
                sha: entry.sha,
                writerId: entry.patch.writer,
                lamport: entry.patch.lamport,
            });
        }
    }
}
function throwTailBudgetExceeded(graphName, budget) {
    throw new QueryError('Checkpoint-tail optic read exceeded its tail scan budget.', {
        code: 'E_OPTIC_TAIL_BUDGET_EXCEEDED',
        context: {
            graphName,
            maxTailPatches: budget.budgetLimit,
            budgetKind: 'maxTailPatches',
            budgetLimit: budget.budgetLimit,
            budgetObserved: budget.budgetObserved,
            budgetUnit: 'patch',
        },
    });
}
