import ContinuumEvidencePosture from "../../continuum/ContinuumEvidencePosture.js";
import QueryError from "../../errors/QueryError.js";
import OpticAperturePosture from "./OpticAperturePosture.js";
import OpticBasisPosture from "./OpticBasisPosture.js";
import OpticCoordinatePosture from "./OpticCoordinatePosture.js";
import OpticReadTarget from "./OpticReadTarget.js";
import OpticSupportRule from "./OpticSupportRule.js";
const NODE_OPTIC_KIND = 'node';
const NODE_PROPERTY_OPTIC_KIND = 'node-property';
const NEIGHBORHOOD_OPTIC_KIND = 'neighborhood';
const TRAVERSAL_OPTIC_KIND = 'traversal';
export default class Optic {
    target;
    coordinatePosture;
    aperturePosture;
    basisPosture;
    supportRule;
    evidencePosture;
    constructor(fields) {
        this.target = requireTarget(fields.target);
        this.coordinatePosture = normalizeCoordinatePosture(fields.coordinatePosture);
        this.aperturePosture = normalizeAperturePosture(fields.aperturePosture);
        this.basisPosture = normalizeBasisPosture(fields.basisPosture);
        this.supportRule = normalizeSupportRule(fields.supportRule);
        this.evidencePosture = requireEvidencePosture(fields.evidencePosture);
        requireSupportMatchesTarget(this.target.opticKind, this.supportRule);
        Object.freeze(this);
    }
    static node(fields) {
        return new Optic({
            ...fields,
            target: OpticReadTarget.node(fields.nodeId),
            supportRule: OpticSupportRule.exactEntity(),
        });
    }
    static nodeProperty(fields) {
        return new Optic({
            ...fields,
            target: OpticReadTarget.nodeProperty(fields.nodeId, fields.propertyKey),
            supportRule: OpticSupportRule.exactEntity(),
        });
    }
    static neighborhood(fields) {
        return new Optic({
            ...fields,
            target: OpticReadTarget.neighborhood(fields.nodeId),
            supportRule: OpticSupportRule.neighborhood(),
        });
    }
    static traversal(fields) {
        return new Optic({
            ...fields,
            target: OpticReadTarget.traversal(fields.startNodeId),
        });
    }
    nodeProperty(propertyKey) {
        return this.withTarget(OpticReadTarget.nodeProperty(this.nodeId(), propertyKey), OpticSupportRule.exactEntity());
    }
    neighborhood() {
        return this.withTarget(OpticReadTarget.neighborhood(this.nodeId()), OpticSupportRule.neighborhood());
    }
    traversal(supportRule) {
        return this.withTarget(OpticReadTarget.traversal(this.nodeId()), supportRule);
    }
    withSupportRule(supportRule) {
        return this.withTarget(this.target, supportRule);
    }
    withTarget(target, supportRule) {
        return new Optic({
            target,
            coordinatePosture: this.coordinatePosture,
            aperturePosture: this.aperturePosture,
            basisPosture: this.basisPosture,
            supportRule,
            evidencePosture: this.evidencePosture,
        });
    }
    nodeId() {
        return this.target.toContextValue().nodeId;
    }
    propertyKey() {
        const targetContext = this.target.toContextValue();
        if ('propertyKey' in targetContext) {
            return targetContext.propertyKey;
        }
        throw new QueryError('Optic target does not include a property key.', {
            code: 'E_OPTIC_SCHEMA',
            context: { opticKind: this.target.opticKind, field: 'propertyKey' },
        });
    }
    toContextValue() {
        return Object.freeze({
            opticKind: this.target.opticKind,
            target: this.target.toContextValue(),
            coordinatePosture: this.coordinatePosture.toString(),
            aperturePosture: this.aperturePosture.toString(),
            basisPosture: this.basisPosture.toString(),
            supportRule: this.supportRule.toString(),
            evidencePosture: this.evidencePosture.toString(),
        });
    }
}
function requireTarget(target) {
    if (!(target instanceof OpticReadTarget)) {
        throw new QueryError('Optic requires a runtime-backed read target.', {
            code: 'E_OPTIC_SCHEMA',
            context: { field: 'target' },
        });
    }
    return target;
}
function requireEvidencePosture(posture) {
    if (!(posture instanceof ContinuumEvidencePosture)) {
        throw new QueryError('Optic requires a Continuum evidence posture.', {
            code: 'E_OPTIC_SCHEMA',
            context: { field: 'evidencePosture' },
        });
    }
    return posture;
}
function normalizeCoordinatePosture(value) {
    if (value instanceof OpticCoordinatePosture) {
        return value;
    }
    return new OpticCoordinatePosture(value);
}
function normalizeAperturePosture(value) {
    if (value instanceof OpticAperturePosture) {
        return value;
    }
    return new OpticAperturePosture(value);
}
function normalizeBasisPosture(value) {
    if (value === undefined) {
        throw new QueryError('Optic requires a basis posture.', {
            code: 'E_OPTIC_SCHEMA',
            context: { field: 'basisPosture' },
        });
    }
    if (value instanceof OpticBasisPosture) {
        return value;
    }
    return new OpticBasisPosture(value);
}
function normalizeSupportRule(value) {
    if (value instanceof OpticSupportRule) {
        return value;
    }
    return new OpticSupportRule(value);
}
function requireSupportMatchesTarget(opticKind, supportRule) {
    if (supportRuleMatchesTarget(opticKind, supportRule)) {
        return;
    }
    throwSupportMismatch(opticKind, supportRule);
}
function supportRuleMatchesTarget(opticKind, supportRule) {
    if (isExactEntityOpticKind(opticKind)) {
        return supportRule.isExactEntity();
    }
    if (opticKind === NEIGHBORHOOD_OPTIC_KIND) {
        return supportRule.isNeighborhood();
    }
    if (opticKind === TRAVERSAL_OPTIC_KIND) {
        return isTraversalSupportRule(supportRule);
    }
    return false;
}
function isExactEntityOpticKind(opticKind) {
    return opticKind === NODE_OPTIC_KIND || opticKind === NODE_PROPERTY_OPTIC_KIND;
}
function isTraversalSupportRule(supportRule) {
    if (supportRule.isTraversalWindow()) {
        return true;
    }
    return supportRule.refusesGlobalDiscovery();
}
function throwSupportMismatch(opticKind, supportRule) {
    throw new QueryError('Optic support rule does not match target kind.', {
        code: 'E_OPTIC_SCHEMA',
        context: { opticKind, supportRule: supportRule.toString() },
    });
}
