import CheckpointTailOpticSource from "./CheckpointTailOpticSource.js";
import WarpError from "../../errors/WarpError.js";
export default class CoordinateCheckpointTailOpticSource extends CheckpointTailOpticSource {
    graphName;
    _persistence;
    _codec;
    _blobStorage;
    _commitMessageCodec;
    _source;
    _checkpointSha;
    _frontier;
    constructor(options) {
        super();
        assertSource(options.source);
        assertFrontier(options.frontier);
        this.graphName = options.source.graphName;
        this._persistence = options.source._persistence;
        this._codec = options.source._codec;
        this._blobStorage = options.source._blobStorage;
        this._commitMessageCodec = options.source._commitMessageCodec;
        this._source = options.source;
        assertNonEmpty(options.checkpointSha, 'checkpointSha');
        this._checkpointSha = options.checkpointSha;
        this._frontier = copyFrontier(options.frontier);
        Object.freeze(this);
    }
    discoverWriters() {
        return Promise.resolve([...this._frontier.keys()].sort());
    }
    _readCheckpointSha() {
        return Promise.resolve(this._checkpointSha);
    }
    async _loadPatchChainFromSha(tipSha, stopAtSha = null) {
        return await this._source._loadPatchChainFromSha(tipSha, stopAtSha);
    }
    async _loadWriterPatches(writerId, stopAtSha = null) {
        const coordinateTipSha = this._frontier.get(writerId);
        if (coordinateTipSha === undefined || coordinateTipSha === stopAtSha) {
            return [];
        }
        return await this._source._loadPatchChainFromSha(coordinateTipSha, stopAtSha);
    }
    async _validatePatchAgainstCheckpoint(writerId, incomingSha, checkpoint) {
        await this._source._validatePatchAgainstCheckpoint(writerId, incomingSha, checkpoint);
    }
}
function copyFrontier(frontier) {
    const copy = new Map();
    for (const [writerId, patchSha] of [...frontier.entries()].sort(([left], [right]) => left.localeCompare(right))) {
        assertNonEmpty(writerId, 'writerId');
        assertNonEmpty(patchSha, 'patchSha');
        copy.set(writerId, patchSha);
    }
    return copy;
}
function assertSource(source) {
    if (!hasSourceIdentity(source) || !hasSourcePorts(source) || !hasSourceMethods(source)) {
        throw new WarpError('Coordinate checkpoint-tail optic source requires a checkpoint-tail source', 'E_COORDINATE_CHECKPOINT_TAIL_OPTIC_SOURCE', { context: { field: 'source' } });
    }
}
function hasSourceIdentity(source) {
    return typeof source.graphName === 'string' && source.graphName.trim().length > 0;
}
function hasSourcePorts(source) {
    return hasPersistencePort(source._persistence)
        && hasCodecPort(source._codec)
        && hasOptionalBlobStoragePort(source._blobStorage)
        && hasCommitMessageCodecPort(source._commitMessageCodec);
}
function hasSourceMethods(source) {
    const methodChecks = [
        typeof source.discoverWriters === 'function',
        typeof source._readCheckpointSha === 'function',
        typeof source._loadPatchChainFromSha === 'function',
        typeof source._loadWriterPatches === 'function',
        typeof source._validatePatchAgainstCheckpoint === 'function',
    ];
    return methodChecks.every((methodExists) => methodExists);
}
function hasPersistencePort(persistence) {
    const methodChecks = [
        typeof persistence.showNode === 'function',
        typeof persistence.getNodeInfo === 'function',
        typeof persistence.readBlob === 'function',
        typeof persistence.readTreeOids === 'function',
        typeof persistence.readRef === 'function',
    ];
    return methodChecks.every((methodExists) => methodExists);
}
function hasCodecPort(codec) {
    const methodChecks = [
        typeof codec.encode === 'function',
        typeof codec.decode === 'function',
    ];
    return methodChecks.every((methodExists) => methodExists);
}
function hasOptionalBlobStoragePort(blobStorage) {
    if (blobStorage === null) {
        return true;
    }
    const methodChecks = [
        typeof blobStorage.retrieve === 'function',
        typeof blobStorage.retrieveStream === 'function',
    ];
    return methodChecks.every((methodExists) => methodExists);
}
function hasCommitMessageCodecPort(commitMessageCodec) {
    const methodChecks = [
        typeof commitMessageCodec.decodeCheckpoint === 'function',
        typeof commitMessageCodec.decodePatch === 'function',
        typeof commitMessageCodec.detectKind === 'function',
    ];
    return methodChecks.every((methodExists) => methodExists);
}
function assertFrontier(frontier) {
    if (!(frontier instanceof Map)) {
        throw new WarpError('Coordinate checkpoint-tail optic source requires a frontier Map', 'E_COORDINATE_CHECKPOINT_TAIL_OPTIC_SOURCE', { context: { field: 'frontier' } });
    }
}
function assertNonEmpty(value, field) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        throw new WarpError('Coordinate checkpoint-tail optic source requires non-empty identity fields', 'E_COORDINATE_CHECKPOINT_TAIL_OPTIC_SOURCE', { context: { field } });
    }
}
