import CoordinateSelector from "../types/CoordinateSelector.js";
import LiveSelector from "../types/LiveSelector.js";
import StrandSelector from "../types/StrandSelector.js";
import WorldlineSelector from "../types/WorldlineSelector.js";
import LogicalTraversal from "./query/LogicalTraversal.js";
import QueryBuilder from "./query/QueryBuilder.js";
import QueryError from "../errors/QueryError.js";
import CoordinateCheckpointTailOpticSource from "./optic/CoordinateCheckpointTailOpticSource.js";
import OpticCoordinatePosture from "./optic/OpticCoordinatePosture.js";
import WorldlineOptic from "./optic/WorldlineOptic.js";
import CheckpointTailExactIdQueryReadModel, { exactIdOnlyQueryNodeId, } from "./query/CheckpointTailExactIdQueryReadModel.js";
function toSelector(source) {
    if (source instanceof WorldlineSelector) {
        return source.clone();
    }
    if (source === null || source === undefined) {
        return new LiveSelector();
    }
    return selectorFromSource(source);
}
function selectorFromSource(source) {
    const sourceKind = source.kind;
    if (sourceKind === 'live') {
        return new LiveSelector(source.ceiling);
    }
    if (sourceKind === 'coordinate') {
        return new CoordinateSelector(source.frontier, source.ceiling, source.checkpointSha);
    }
    if (sourceKind === 'strand') {
        return new StrandSelector(source.strandId, source.ceiling);
    }
    throw new QueryError('unrecognized worldline source kind', {
        code: 'E_WORLDLINE_SOURCE',
        context: { kind: sourceKind },
    });
}
function toWorldlineSource(source) {
    if (source instanceof LiveSelector) {
        return source.toDTO();
    }
    if (source instanceof CoordinateSelector) {
        return source.toDTO();
    }
    if (source instanceof StrandSelector) {
        return source.toDTO();
    }
    throw new QueryError('unsupported worldline selector kind', {
        code: 'E_WORLDLINE_SELECTOR',
        context: { kind: source.constructor.name },
    });
}
export default class ProjectionHandle {
    _graph;
    _source;
    _opticSource;
    _delegateObserverPromise;
    traverse;
    constructor({ graph, source, opticSource, }) {
        this._graph = graph;
        this._source = toSelector(source);
        this._opticSource = opticSource ?? null;
        this._delegateObserverPromise = null;
        this.traverse = new LogicalTraversal(this);
    }
    get source() {
        return toWorldlineSource(this._source);
    }
    async seek(options) {
        return await Promise.resolve(new ProjectionHandle({
            graph: this._graph,
            source: options?.source ?? this._source,
            opticSource: this._opticSource,
        }));
    }
    optic() {
        if (this._opticSource === null) {
            throw new QueryError('worldline optic requires a checkpoint-tail bounded basis source', {
                code: 'E_OPTIC_NO_BOUNDED_BASIS',
                context: { reason: 'missing-optic-source' },
            });
        }
        if (this._source instanceof LiveSelector) {
            return new WorldlineOptic({ source: this._opticSource });
        }
        if (this._source instanceof CoordinateSelector) {
            return this._coordinateOptic(this._opticSource);
        }
        throw new QueryError('checkpoint-tail optics support live and coordinate worldlines only', {
            code: 'E_OPTIC_NO_BOUNDED_BASIS',
            context: { selector: this._source.constructor.name },
        });
    }
    _coordinateOptic(source) {
        const coordinate = this._source;
        if (!(coordinate instanceof CoordinateSelector) || coordinate.checkpointSha === null) {
            throw new QueryError('coordinate optic requires a checkpoint-tail bounded basis source', {
                code: 'E_OPTIC_NO_BOUNDED_BASIS',
                context: { reason: 'coordinate-without-optic-basis' },
            });
        }
        return this._capturedCoordinateOptic(source, coordinate, coordinate.checkpointSha);
    }
    async _delegateObserver() {
        if (this._delegateObserverPromise === null) {
            this._delegateObserverPromise = this._graph.observer({ match: '*' }, { source: this.source });
        }
        return await this._delegateObserverPromise;
    }
    async hasNode(nodeId) {
        return await (await this._delegateObserver()).hasNode(nodeId);
    }
    async getNodes() {
        return await (await this._delegateObserver()).getNodes();
    }
    async getNodeProps(nodeId) {
        return await (await this._delegateObserver()).getNodeProps(nodeId);
    }
    async getEdges() {
        return await (await this._delegateObserver()).getEdges();
    }
    queryReadModelProvider() {
        return this;
    }
    async openQueryReadModel(request) {
        const bounded = await this._boundedExactReadModel(request);
        if (bounded !== null) {
            return bounded;
        }
        return await (await this._delegateObserver()).openQueryReadModel(request);
    }
    async _boundedExactReadModel(request) {
        const nodeId = exactIdOnlyQueryNodeId(request);
        if (nodeId === null || this._opticSource === null) {
            return null;
        }
        const optic = await this._exactReadOptic();
        return optic === null
            ? null
            : new CheckpointTailExactIdQueryReadModel({ nodeId, optic });
    }
    async _exactReadOptic() {
        const source = this._opticSource;
        if (source === null) {
            return null;
        }
        if (this._source instanceof LiveSelector) {
            return await this._liveExactReadOptic(source);
        }
        if (this._source instanceof CoordinateSelector) {
            return this._coordinateExactReadOptic(source);
        }
        return null;
    }
    async _liveExactReadOptic(source) {
        if (await source._readCheckpointSha() === null) {
            return null;
        }
        return new WorldlineOptic({ source });
    }
    _coordinateExactReadOptic(source) {
        const coordinate = this._source;
        if (!(coordinate instanceof CoordinateSelector) || coordinate.checkpointSha === null) {
            return null;
        }
        return this._capturedCoordinateOptic(source, coordinate, coordinate.checkpointSha);
    }
    _capturedCoordinateOptic(source, coordinate, checkpointSha) {
        return new WorldlineOptic({
            source: new CoordinateCheckpointTailOpticSource({
                source,
                checkpointSha,
                frontier: coordinate.frontier,
            }),
            coordinatePosture: OpticCoordinatePosture.capturedCoordinate(),
        });
    }
    query() {
        return new QueryBuilder(this.queryReadModelProvider());
    }
    async observer(nameOrConfig, config) {
        if (typeof nameOrConfig === 'string') {
            return await this._graph.observer(nameOrConfig, config, { source: this.source });
        }
        return await this._graph.observer(nameOrConfig, { source: this.source });
    }
}
