import QueryError from "../../errors/QueryError.js";
import { GIT_WARP_RECEIPT_ENVELOPE_BOUNDARY_VERSION, GIT_WARP_RECEIPT_ENVELOPE_FACT_KIND, } from "../../continuum/GitWarpReceiptEnvelopeBoundary.js";
import ObserverEmission from "./ObserverEmission.js";
import ObserverPlan from "./ObserverPlan.js";
/** Observer reading envelope tying source plan, emitted payload, and witness refs. */
export default class ObserverReadingEnvelope {
    plan;
    payload;
    stateHash;
    witnessRef;
    shellRef;
    pluralityRef;
    receiptAnchors;
    budget;
    residualBasis;
    constructor(fields) {
        const checkedFields = requireFields(fields);
        const refs = requireEnvelopeRefs(checkedFields);
        this.plan = requirePlan(checkedFields.plan);
        this.payload = requirePayload(checkedFields.payload);
        this.stateHash = refs.stateHash;
        this.witnessRef = refs.witnessRef;
        this.shellRef = refs.shellRef;
        this.pluralityRef = refs.pluralityRef;
        this.receiptAnchors = freezeReceiptAnchors(checkedFields.receiptAnchors ?? []);
        this.budget = freezeBudget(this.payload);
        this.residualBasis = freezeResidualBasis(this.plan, this.payload);
        Object.freeze(this);
    }
    get source() {
        return this.plan.source;
    }
    hasResidual() {
        return this.residualBasis.length > 0;
    }
    hasPlurality() {
        return this.pluralityRef !== null;
    }
    hasReceiptAnchors() {
        return this.receiptAnchors.length > 0;
    }
}
function requireFields(fields) {
    if (fields !== null && typeof fields === 'object') {
        return fields;
    }
    throw new QueryError('observer reading envelope requires object fields', {
        code: 'E_OBSERVER_READING_ENVELOPE_FIELDS',
    });
}
function requirePlan(plan) {
    if (plan instanceof ObserverPlan) {
        return plan;
    }
    throw new QueryError('observer reading envelope requires an ObserverPlan', {
        code: 'E_OBSERVER_READING_ENVELOPE_PLAN',
    });
}
function requirePayload(payload) {
    if (payload instanceof ObserverEmission) {
        return payload;
    }
    throw new QueryError('observer reading envelope requires an ObserverEmission payload', {
        code: 'E_OBSERVER_READING_ENVELOPE_PAYLOAD',
    });
}
function requireEnvelopeRefs(fields) {
    return {
        stateHash: requireOptionalRef(fields.stateHash ?? null, 'stateHash'),
        witnessRef: requireOptionalRef(fields.witnessRef ?? null, 'witnessRef'),
        shellRef: requireOptionalRef(fields.shellRef ?? null, 'shellRef'),
        pluralityRef: requireOptionalRef(fields.pluralityRef ?? null, 'pluralityRef'),
    };
}
function requireOptionalRef(value, field) {
    if (value === null) {
        return null;
    }
    if (typeof value === 'string' && value.length > 0) {
        return value;
    }
    throw new QueryError('observer reading envelope refs must be non-empty when provided', {
        code: 'E_OBSERVER_READING_ENVELOPE_REF',
        context: { field },
    });
}
function freezeBudget(payload) {
    return Object.freeze({
        nodeCount: payload.nodeCount,
        edgeCount: payload.edgeCount,
        propertyKeyCount: payload.propertyKeys.length,
        matchedBasisCount: payload.matchedBasis.length,
    });
}
function freezeResidualBasis(plan, payload) {
    const matched = new Set(payload.matchedBasis);
    return Object.freeze(plan.basis.distinctions.filter((distinction) => !matched.has(distinction)));
}
function freezeReceiptAnchors(anchors) {
    if (!Array.isArray(anchors)) {
        throw new QueryError('observer reading envelope receipt anchors must be an array', {
            code: 'E_OBSERVER_READING_ENVELOPE_RECEIPTS',
        });
    }
    return Object.freeze(anchors.map(requireReceiptAnchor));
}
function requireReceiptAnchor(anchor) {
    requireReceiptAnchorBoundary(anchor);
    requireReceiptAnchorFields(anchor);
    return freezeReceiptAnchor(anchor);
}
function requireReceiptAnchorBoundary(anchor) {
    if (anchor.boundaryVersion === GIT_WARP_RECEIPT_ENVELOPE_BOUNDARY_VERSION
        && anchor.substrateFactKind === GIT_WARP_RECEIPT_ENVELOPE_FACT_KIND) {
        return;
    }
    throw new QueryError('observer reading envelope receipt anchor has an unsupported boundary', {
        code: 'E_OBSERVER_READING_ENVELOPE_RECEIPT_BOUNDARY',
    });
}
function requireReceiptAnchorFields(anchor) {
    requireNonEmptyString(anchor.patchSha, 'patchSha');
    requireNonEmptyString(anchor.writer, 'writer');
    requireNonNegativeInteger(anchor.lamport, 'lamport');
    requireNonNegativeInteger(anchor.outcomeCount, 'outcomeCount');
    requireNonNegativeInteger(anchor.appliedCount, 'appliedCount');
    requireNonNegativeInteger(anchor.supersededCount, 'supersededCount');
    requireNonNegativeInteger(anchor.redundantCount, 'redundantCount');
    if (typeof anchor.hasExplanatoryReasons !== 'boolean') {
        throw new QueryError('observer reading envelope receipt anchor reason flag must be boolean', {
            code: 'E_OBSERVER_READING_ENVELOPE_RECEIPT_FLAG',
            context: { field: 'hasExplanatoryReasons' },
        });
    }
}
function freezeReceiptAnchor(anchor) {
    return Object.freeze({
        boundaryVersion: anchor.boundaryVersion,
        substrateFactKind: anchor.substrateFactKind,
        patchSha: anchor.patchSha,
        writer: anchor.writer,
        lamport: anchor.lamport,
        outcomeCount: anchor.outcomeCount,
        appliedCount: anchor.appliedCount,
        supersededCount: anchor.supersededCount,
        redundantCount: anchor.redundantCount,
        hasExplanatoryReasons: anchor.hasExplanatoryReasons,
    });
}
function requireNonEmptyString(value, field) {
    if (typeof value !== 'string' || value.length === 0) {
        throw new QueryError('observer reading envelope receipt anchor fields must be non-empty strings', {
            code: 'E_OBSERVER_READING_ENVELOPE_RECEIPT_STRING',
            context: { field },
        });
    }
}
function requireNonNegativeInteger(value, field) {
    if (!Number.isInteger(value) || value < 0) {
        throw new QueryError('observer reading envelope receipt anchor counts must be non-negative integers', {
            code: 'E_OBSERVER_READING_ENVELOPE_RECEIPT_COUNT',
            context: { field },
        });
    }
}
