import { createEmptyState, reducePatches } from "../JoinReducer.js";
import { isNonEmptyString } from "./strandShared.js";
export default class StrandMaterializer {
    _graph;
    /**
     * Create a materialization boundary over strand patch collection and replay.
     */
    constructor({ graph }) {
        this._graph = graph;
    }
    /**
     * Collect all base-observation patches from the pinned frontier writers.
     */
    async collectBasePatches(descriptor) {
        return await this._collectParentBasisPatches(descriptor, 'pinned');
    }
    /**
     * Collect parent-basis patches from live truth when the caller opts in.
     */
    async collectParentBasisPatches(descriptor, options) {
        return await this._collectParentBasisPatches(descriptor, options.parentBasis);
    }
    async _collectParentBasisPatches(descriptor, parentBasis) {
        const allPatches = [];
        const parentFrontier = await this._parentBasisFrontier(descriptor, parentBasis);
        for (const tipSha of this._sortedFrontierTipShas(parentFrontier)) {
            const writerPatches = await this._graph._loadPatchChainFromSha(tipSha);
            this._pushVisibleBasePatches(allPatches, writerPatches, descriptor.baseObservation.lamportCeiling ?? null);
        }
        return allPatches;
    }
    /**
     * Collect patches from the strand's own writable overlay chain.
     */
    async collectOverlayPatches(descriptor) {
        if (descriptor.overlay.headPatchSha === null || descriptor.overlay.headPatchSha === undefined) {
            return [];
        }
        return await this._graph._loadPatchChainFromSha(descriptor.overlay.headPatchSha);
    }
    /**
     * Collect patches from all braided read-only overlay chains.
     */
    async collectBraidedOverlayPatches(descriptor) {
        const allPatches = [];
        for (const headPatchSha of this._braidedOverlayHeadShas(descriptor)) {
            const overlayPatches = await this._graph._loadPatchChainFromSha(headPatchSha);
            allPatches.push(...overlayPatches);
        }
        return allPatches;
    }
    /**
     * Merge base, braid, and overlay patches into a deduplicated list, optionally bounded by ceiling.
     */
    async collectPatchEntries(descriptor, { ceiling, parentBasis = 'pinned' }) {
        const basePatches = await this.collectParentBasisPatches(descriptor, { parentBasis });
        const braidedOverlayPatches = await this.collectBraidedOverlayPatches(descriptor);
        const overlayPatches = await this.collectOverlayPatches(descriptor);
        const deduped = new Map();
        for (const entry of basePatches.concat(braidedOverlayPatches, overlayPatches)) {
            if (!deduped.has(entry.sha)) {
                deduped.set(entry.sha, entry);
            }
        }
        const allPatches = [...deduped.values()];
        if (ceiling === null) {
            return allPatches;
        }
        return allPatches.filter((entry) => (entry.patch.lamport ?? 0) <= ceiling);
    }
    /**
     * Replay all strand patches through the CRDT reducer to produce materialized state.
     */
    async materializeDescriptor(descriptor, { collectReceipts, ceiling, parentBasis = 'pinned', }) {
        const allPatches = await this.collectPatchEntries(descriptor, { ceiling, parentBasis });
        const { state, receipts } = this._reduceCollectedPatches(allPatches, collectReceipts);
        return { state, receipts, allPatches };
    }
    async _parentBasisFrontier(descriptor, parentBasis) {
        if (parentBasis === 'pinned' || this._graph.getFrontier === undefined) {
            return descriptor.baseObservation.frontier;
        }
        return Object.fromEntries(await this._graph.getFrontier());
    }
    _sortedFrontierTipShas(frontier) {
        return Object.entries(frontier)
            .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
            .map(([, tipSha]) => tipSha)
            .filter((tipSha) => isNonEmptyString(tipSha));
    }
    _pushVisibleBasePatches(target, writerPatches, lamportCeiling) {
        for (const entry of writerPatches) {
            if (lamportCeiling === null || (entry.patch.lamport ?? 0) <= lamportCeiling) {
                target.push(entry);
            }
        }
    }
    _braidedOverlayHeadShas(descriptor) {
        const braidedReadOverlays = Array.isArray(descriptor.braid?.readOverlays)
            ? descriptor.braid.readOverlays
            : [];
        return braidedReadOverlays
            .map((readOverlay) => readOverlay.headPatchSha)
            .filter((headPatchSha) => isNonEmptyString(headPatchSha));
    }
    _reduceCollectedPatches(allPatches, collectReceipts) {
        if (allPatches.length === 0) {
            return {
                state: createEmptyState(),
                receipts: [],
            };
        }
        if (collectReceipts) {
            return reducePatches(allPatches, undefined, { receipts: true });
        }
        return {
            state: reducePatches(allPatches),
            receipts: [],
        };
    }
}
