/**
 * MaterializeController — CRDT state replay from patches.
 *
 * Three materialization pipelines:
 * 1. materialize() — full or checkpoint-incremental, live frontier
 * 2. materializeCoordinate() — explicit frontier snapshot
 * 3. materializeAt() — specific checkpoint SHA
 *
 * Dependencies are constructor-injected. No host bag.
 * Side effects (notification, GC, auto-checkpoint) are the caller's job.
 */
import { reducePatches as reduceJoinedPatches, createEmptyState } from "../JoinReducer.js";
import { ProvenanceIndex } from "../provenance/ProvenanceIndex.js";
import { computeStateHash } from "../state/StateSerializer.js";
import { normalizeFrontierInput, normalizeExplicitCeiling, buildAdjacency, } from "./MaterializeHelpers.js";
import { reduceSessionBackedState, } from "./MaterializeSessionBridge.js";
import MaterializeLiveStrategy from "./MaterializeLiveStrategy.js";
import MaterializeCoordinateStrategy from "./MaterializeCoordinateStrategy.js";
import MaterializeCeilingStrategy from "./MaterializeCeilingStrategy.js";
import MaterializeCheckpointStrategy from "./MaterializeCheckpointStrategy.js";
import MaterializePatchStreamReducer from "./MaterializePatchStreamReducer.js";
import { summarizeMaterializePatches } from "./MaterializePatchSummary.js";
import AdjacencyMap from "../../capabilities/AdjacencyMap.js";
function toReducerInput(patches) {
    return patches;
}
function reduceWithReceipts(patches, base) {
    const r = reduceJoinedPatches(toReducerInput(patches), base, { receipts: true });
    return { state: r.state, receipts: r.receipts };
}
function reduceWithDiff(patches, base) {
    const r = reduceJoinedPatches(toReducerInput(patches), base, { trackDiff: true });
    return { state: r.state, diff: r.diff };
}
function reducePlain(patches, base) {
    return { state: reduceJoinedPatches(toReducerInput(patches), base) };
}
function reduceMaterializePatches(patches, base, opts) {
    if (opts.receipts) {
        return reduceWithReceipts(patches, base);
    }
    if (opts.wantDiff) {
        return reduceWithDiff(patches, base);
    }
    return reducePlain(patches, base);
}
// ── Provenance ──────────────────────────────────────────────────────
function buildProvenance(patches, base) {
    const index = base ? base.clone() : new ProvenanceIndex();
    for (const { patch, sha } of patches) {
        index.addPatch(sha, patch.reads, patch.writes);
    }
    return index;
}
// ── State hash ──────────────────────────────────────────────────────
async function computeHash(deps, state) {
    return await computeStateHash(state, { crypto: deps.crypto, codec: deps.codec });
}
// ── Controller ──────────────────────────────────────────────────────
export default class MaterializeController {
    _deps;
    _liveStrategy;
    _coordinateStrategy;
    _ceilingStrategy;
    _checkpointStrategy;
    constructor(deps) {
        this._deps = deps;
        const runtime = this._createStrategyRuntime();
        this._liveStrategy = new MaterializeLiveStrategy(runtime);
        this._coordinateStrategy = new MaterializeCoordinateStrategy(runtime);
        this._ceilingStrategy = new MaterializeCeilingStrategy(runtime, this._coordinateStrategy);
        this._checkpointStrategy = new MaterializeCheckpointStrategy(runtime);
    }
    /** Full materialization — live frontier, optional ceiling. */
    async materialize(opts = {}) {
        const ceiling = normalizeExplicitCeiling(opts.ceiling);
        if (ceiling !== null) {
            return await this._ceilingStrategy.materialize({ ceiling, receipts: opts.receipts === true });
        }
        return await this._liveStrategy.materialize({
            receipts: opts.receipts === true,
            wantDiff: opts.wantDiff === true,
        });
    }
    /** Coordinate materialization — explicit frontier. */
    async materializeCoordinate(opts) {
        const frontier = normalizeFrontierInput(opts.frontier);
        const ceiling = normalizeExplicitCeiling(opts.ceiling);
        return await this._coordinateStrategy.materialize({ frontier, ceiling, receipts: opts.receipts === true });
    }
    /** Checkpoint materialization — replay from a specific checkpoint SHA. */
    async materializeAt(checkpointSha) {
        return await this._checkpointStrategy.materializeAt(checkpointSha);
    }
    _assertLoadPersistence(persistence) {
        void persistence;
    }
    _loadPersistence() {
        const { persistence } = this._deps;
        this._assertLoadPersistence(persistence);
        return persistence;
    }
    // ── Result building ───────────────────────────────────────────────
    async _emptyResult(ceiling, frontier) {
        return await this._wrapState(createEmptyState(), ceiling ?? null, frontier ?? null);
    }
    async _wrapState(state, ceiling, frontier) {
        const stateHash = await computeHash(this._deps, state);
        const adjacency = buildAdjacency(state);
        await this._publishSnapshot({
            state,
            stateHash,
            degraded: false,
            ceiling,
            frontier,
        });
        return {
            state,
            stateHash,
            adjacency: new AdjacencyMap({ outgoing: adjacency.outgoing, incoming: adjacency.incoming }),
            patchCount: 0,
            maxObservedLamport: 0,
            provenanceIndex: new ProvenanceIndex(),
            provenanceDegraded: false,
            frontier,
            ceiling,
        };
    }
    async _buildResult(params) {
        const stateHash = await computeHash(this._deps, params.reduced.state);
        const adjacency = params.reduced.adjacency ?? buildAdjacency(params.reduced.state);
        await this._publishSnapshot({
            state: params.reduced.state,
            stateHash,
            degraded: params.degraded,
            ceiling: params.ceiling,
            frontier: params.frontier,
        });
        return {
            state: params.reduced.state,
            stateHash,
            adjacency: new AdjacencyMap({ outgoing: adjacency.outgoing, incoming: adjacency.incoming }),
            receipts: params.reduced.receipts,
            diff: params.reduced.diff,
            patchCount: params.summary.patchCount,
            maxObservedLamport: params.summary.maxObservedLamport,
            provenanceIndex: params.summary.provenance,
            provenanceDegraded: params.degraded,
            frontier: params.frontier,
            ceiling: params.ceiling,
        };
    }
    async _reducePatches(patches, base, opts) {
        const { openStateSession } = this._deps;
        if (openStateSession === undefined) {
            return reduceMaterializePatches(patches, base, opts);
        }
        const sessionArgs = {
            openStateSession,
            patches,
            receipts: opts.receipts,
            wantDiff: opts.wantDiff,
            ...(base === undefined ? {} : { baseState: base }),
        };
        return await reduceSessionBackedState(sessionArgs);
    }
    async _reducePatchStream(stream, base, opts, provenanceBase) {
        if (this._deps.openStateSession === undefined) {
            return await MaterializePatchStreamReducer.reduce({
                source: stream,
                base,
                options: opts,
                ...(provenanceBase === undefined ? {} : { provenanceBase }),
            });
        }
        const patches = [];
        for await (const entry of stream) {
            patches.push(entry);
        }
        const reduced = await this._reducePatches(patches, base, opts);
        return {
            reduced,
            summary: summarizeMaterializePatches(patches, provenanceBase),
        };
    }
    _createStrategyRuntime() {
        return {
            deps: this._deps,
            emptyResult: async (ceiling, frontier) => await this._emptyResult(ceiling, frontier),
            wrapState: async (state, ceiling, frontier) => await this._wrapState(state, ceiling, frontier),
            reducePatches: async (patches, base, opts) => await this._reducePatches(patches, base, opts),
            reducePatchStream: async (stream, base, opts, provenanceBase) => await this._reducePatchStream(stream, base, opts, provenanceBase),
            buildResult: async (params) => await this._buildResult(params),
            buildProvenance: (patches, base) => buildProvenance(patches, base),
            loadPersistence: () => this._loadPersistence(),
        };
    }
    async _publishSnapshot(args) {
        const stateCache = this._deps.getStateCache?.() ?? null;
        if (stateCache === null || args.frontier === null) {
            return;
        }
        await stateCache.put({
            snapshotId: `snapshot:${args.stateHash}`,
            coordinate: {
                frontier: args.frontier,
                ceiling: args.ceiling,
            },
            retention: 'evictable',
            provenancePosture: args.degraded ? 'degraded' : 'full',
            stateHash: args.stateHash,
            payloadRef: `snapshot:${args.stateHash}`,
            createdAt: 'materialize-controller',
            state: args.state,
        });
    }
}
