/**
 * ComparisonSelector — validated comparison selector hierarchy and
 * frontier normalization utilities.
 *
 * Each selector subclass implements `resolve()` with the resolution
 * logic for its kind (live, coordinate, strand, strand_base),
 * eliminating dispatch switches.
 *
 * @module domain/services/controllers/ComparisonSelector
 */
import QueryError from "../../errors/QueryError.js";
import createStrandCoordinator from "../strand/createStrandCoordinator.js";
import { buildStrandMetadata, finalizeSide, normalizeFrontierRecord, normalizeLamportCeiling, normalizeRequiredString, optionalCeiling, } from "./ComparisonSelectorSupport.js";
export { compareStrings, normalizeLamportCeiling, normalizeOptionalString, normalizeRequiredString, normalizeFrontierRecord, frontierRecordToMap, optionalCeiling, combineCeilings, buildCoordinateRequest, patchFrontierFromEntries, lamportFrontierFromEntries, uniqueSortedPatchShas, collectPatchEntriesForFrontier, buildStrandMetadata, computeStateHashForGraph, ResolvedComparisonSide, finalizeSide, } from "./ComparisonSelectorSupport.js";
export class NormalizedSelector {
    kind;
    ceiling;
    constructor(kind, ceiling) {
        this.kind = kind;
        this.ceiling = ceiling;
    }
}
export class LiveComparisonSelector extends NormalizedSelector {
    constructor(ceiling) { super('live', ceiling); }
    async resolve(context, scope, liveFrontier) {
        const frontier = liveFrontier ?? await context.coordinateReader.liveFrontier();
        const read = await context.coordinateReader.readLiveSide({
            frontier,
            ceiling: this.ceiling,
        });
        return await context.sideFinalizer.finalize(read, scope);
    }
}
export class CoordinateComparisonSelector extends NormalizedSelector {
    frontier;
    constructor(frontier, ceiling) {
        super('coordinate', ceiling);
        this.frontier = frontier;
    }
    async resolve(context, scope) {
        const read = await context.coordinateReader.readCoordinateSide({
            frontier: this.frontier,
            ceiling: this.ceiling,
        });
        return await context.sideFinalizer.finalize(read, scope);
    }
}
/**
 * Assertion narrowing ComparisonHost to the strand coordinator's
 * parameter type.
 *
 * ComparisonHost is the structural subset of WarpRuntime that
 * comparison needs. The strand coordinator's parameter type
 * (exported only by inference) wants a wider WarpRuntime surface.
 * At runtime WarpRuntime is passed (it satisfies both), so the
 * assertion narrows the type without a value-level cast.
 */
function assertStrandCoordinatorHost(graph) {
    void graph;
}
/**
 * Helper: obtain a strand coordinator for a ComparisonHost.
 */
function strandCoordinatorFor(graph) {
    assertStrandCoordinatorHost(graph);
    return createStrandCoordinator(graph);
}
export class StrandComparisonSelector extends NormalizedSelector {
    strandId;
    constructor(strandId, ceiling) {
        super('strand', ceiling);
        this.strandId = strandId;
    }
    async resolve(context, scope) {
        const graph = context.strandGraph;
        const strands = strandCoordinatorFor(graph);
        const descriptor = await strands.getOrThrow(this.strandId);
        const materialized = await graph._materializeStrandGraph(this.strandId, this.ceiling === null ? undefined : { ceiling: this.ceiling });
        const patchEntries = await strands.getPatchEntries(this.strandId, this.ceiling === null ? undefined : { ceiling: this.ceiling });
        return await finalizeSide(graph, {
            requested: { kind: 'strand', strandId: this.strandId, ...optionalCeiling(this.ceiling) },
            state: materialized.state, patchEntries, coordinateKind: 'strand', lamportCeiling: this.ceiling,
            strand: buildStrandMetadata(this.strandId, descriptor),
        }, scope);
    }
}
export class StrandBaseComparisonSelector extends NormalizedSelector {
    strandId;
    constructor(strandId, ceiling) {
        super('strand_base', ceiling);
        this.strandId = strandId;
    }
    async resolve(context, scope) {
        const read = await context.coordinateReader.readStrandBaseSide({
            strandId: this.strandId,
            ceiling: this.ceiling,
        });
        return await context.sideFinalizer.finalize(read, scope);
    }
}
// ── Selector normalization ───────────────────────────────────────────
export function normalizeSelector(selector, field) {
    if (selector === null || selector === undefined || typeof selector !== 'object') {
        throw new QueryError(`${field} must be a selector object`, {
            code: 'invalid_coordinate', context: { field },
        });
    }
    const { kind } = selector;
    if (kind === 'live') {
        return new LiveComparisonSelector(normalizeLamportCeiling(selector.ceiling, `${field}.ceiling`));
    }
    if (kind === 'coordinate') {
        return new CoordinateComparisonSelector(normalizeFrontierRecord(selector.frontier, `${field}.frontier`), normalizeLamportCeiling(selector.ceiling, `${field}.ceiling`));
    }
    if (kind === 'strand' || kind === 'strand_base') {
        const strandId = normalizeRequiredString(selector.strandId, `${field}.strandId`);
        const ceiling = normalizeLamportCeiling(selector.ceiling, `${field}.ceiling`);
        return kind === 'strand_base'
            ? new StrandBaseComparisonSelector(strandId, ceiling)
            : new StrandComparisonSelector(strandId, ceiling);
    }
    throw new QueryError(`${field}.kind is unsupported`, { code: 'invalid_coordinate', context: { field, kind } });
}
