// ---------------------------------------------------------------------------
// Closure failure codes
// ---------------------------------------------------------------------------
/**
 * Typed failure codes for closure resolution. Each code maps to a specific
 * dependency class from the closure RFC.
 */
export var ClosureFailureCode;
(function (ClosureFailureCode) {
    /** Class 1: The ProtocolsConfigure for the protocol could not be found. */
    ClosureFailureCode["ProtocolMetadataMissing"] = "ClosureProtocolMetadataMissing";
    /** Class 2: The initialWrite for a non-initial RecordsWrite is missing. */
    ClosureFailureCode["InitialWriteMissing"] = "ClosureInitialWriteMissing";
    /** Class 2: A parent record in the parentId chain is missing. */
    ClosureFailureCode["ParentChainMissing"] = "ClosureParentChainMissing";
    /** Class 2: A context ancestor record is missing. */
    ClosureFailureCode["ContextChainMissing"] = "ClosureContextChainMissing";
    /** Class 3: A permission grant referenced by a grant invocation is missing. */
    ClosureFailureCode["GrantMissing"] = "ClosureGrantMissing";
    /** Class 3: The grant exists but is not yet active at the message's timestamp. */
    ClosureFailureCode["GrantNotYetActive"] = "ClosureGrantNotYetActive";
    /** Class 3: The grant exists but has expired at the message's timestamp. */
    ClosureFailureCode["GrantExpired"] = "ClosureGrantExpired";
    /** Class 3: A revocation record that affects a referenced grant is missing. */
    ClosureFailureCode["GrantRevocationMissing"] = "ClosureGrantRevocationMissing";
    /** Class 4: A squash floor or visibility-floor record is missing. */
    ClosureFailureCode["VisibilityFloorMissing"] = "ClosureVisibilityFloorMissing";
    /** Class 5: An encryption/key-delivery dependency is missing. */
    ClosureFailureCode["EncryptionDependencyMissing"] = "ClosureEncryptionDependencyMissing";
    /** Class 6: A cross-protocol $ref dependency is missing. */
    ClosureFailureCode["CrossProtocolReferenceMissing"] = "ClosureCrossProtocolReferenceMissing";
    /** A dependency exists but the syncing principal is not authorized to fetch it. */
    ClosureFailureCode["DependencyForbidden"] = "ClosureDependencyForbidden";
    /** Traversal depth exceeded the configured maximum (default 32). */
    ClosureFailureCode["DepthExceeded"] = "ClosureDepthExceeded";
})(ClosureFailureCode || (ClosureFailureCode = {}));
const TERMINAL_CLOSURE_FAILURE_CODES = new Set([
    ClosureFailureCode.DependencyForbidden,
    ClosureFailureCode.GrantExpired,
    ClosureFailureCode.GrantNotYetActive,
    ClosureFailureCode.GrantRevocationMissing,
]);
/** Returns true when repair cannot make the closure failure complete. */
export function isTerminalClosureFailureCode(failureCode) {
    return TERMINAL_CLOSURE_FAILURE_CODES.has(failureCode);
}
/**
 * Create a fresh evaluation context for a batch of closure evaluations.
 */
export function createClosureContext(tenantDid, maxDepth, options) {
    return {
        tenantDid,
        protocolCache: new Map(),
        grantCache: new Map(),
        satisfiedDeps: new Set(),
        missingDeps: new Set(),
        maxDepth: maxDepth !== null && maxDepth !== void 0 ? maxDepth : 32,
        isDelegateSession: options === null || options === void 0 ? void 0 : options.isDelegateSession,
    };
}
/**
 * Invalidate cached entries that may be affected by a newly processed message.
 * Called after `processRawMessage` succeeds to ensure subsequent closure
 * evaluations see the updated state.
 *
 * - `ProtocolsConfigure` → clears protocolCache for that protocol URI
 * - Permissions grant write → clears grantCache for that recordId
 * - Permissions revocation → clears grantCache for the parent grantId
 * - Any Records message → clears satisfiedDeps/missingDeps for that recordId
 *   (the message may have changed what's locally present)
 */
export function invalidateClosureCache(context, message) {
    var _a;
    const desc = message.descriptor;
    // ProtocolsConfigure update → invalidate protocol cache.
    if (desc.interface === 'Protocols' && desc.method === 'Configure') {
        const protocol = desc.definition
            ? desc.definition.protocol
            : undefined;
        if (protocol) {
            context.protocolCache.delete(protocol);
        }
    }
    // Permissions protocol messages → invalidate grant/revocation cache.
    if (desc.protocol === 'https://identity.foundation/dwn/permissions') {
        const recordId = message.recordId;
        const protocolPath = desc.protocolPath;
        if (protocolPath === 'grant' && recordId) {
            // Grant write → invalidate the grant cache and dep keys.
            context.grantCache.delete(recordId);
            deleteDependencyCacheEntries(context.satisfiedDeps, `permissionGrant:grantId:${recordId}`);
            deleteDependencyCacheEntries(context.missingDeps, `permissionGrant:grantId:${recordId}`);
        }
        if (protocolPath === 'grant/revocation') {
            // Revocation write → invalidate revocation cache and both dep keys.
            const parentId = desc.parentId;
            if (parentId) {
                context.grantCache.delete(`revocation:${parentId}`);
                deleteDependencyCacheEntries(context.satisfiedDeps, `grantRevocation:grantId:${parentId}`);
                deleteDependencyCacheEntries(context.missingDeps, `grantRevocation:grantId:${parentId}`);
                // Also invalidate the grant itself since its revocation state changed.
                deleteDependencyCacheEntries(context.satisfiedDeps, `permissionGrant:grantId:${parentId}`);
                deleteDependencyCacheEntries(context.missingDeps, `permissionGrant:grantId:${parentId}`);
            }
        }
    }
    // Any Records message → invalidate dep keys that could be affected.
    // Handles multiple key formats:
    //   - plain recordId entries (class 2 parent/context/initialWrite)
    //   - composite protocol|recordId entries (class 6 crossProtocolParent)
    //   - filter-based entries keyed by protocol+protocolPath (class 6 role records)
    //   - contextKeyRecord entries keyed by source protocol from tags (class 5)
    // Uses boundary-aware matching for recordId to avoid substring collisions,
    // prefix matching for filter/contextKey entries, and tag-based source
    // protocol extraction for key-delivery records.
    if (desc.interface === 'Records') {
        const recordId = message.recordId;
        const protocol = desc.protocol;
        const protocolPath = desc.protocolPath;
        if (recordId) {
            // Invalidate dep keys that reference this exact recordId.
            // Check for recordId at a key boundary (preceded by ':' or '|')
            // to avoid substring false matches (e.g., 'thread-1' matching 'thread-10').
            const matchesRecordId = (key) => {
                const idx = key.indexOf(recordId);
                if (idx === -1) {
                    return false;
                }
                const charBefore = idx > 0 ? key[idx - 1] : ':';
                const charAfter = idx + recordId.length < key.length ? key[idx + recordId.length] : '|';
                const validBefore = charBefore === ':' || charBefore === '|';
                const validAfter = charAfter === '|' || charAfter === undefined;
                return validBefore && (idx + recordId.length === key.length || validAfter);
            };
            for (const key of context.satisfiedDeps) {
                if (matchesRecordId(key)) {
                    context.satisfiedDeps.delete(key);
                }
            }
            for (const key of context.missingDeps) {
                if (matchesRecordId(key)) {
                    context.missingDeps.delete(key);
                }
            }
        }
        // Invalidate filter-based role record deps that match this protocol + protocolPath.
        // Dep keys are "label:filter:protocol|protocolPath|author|context".
        // We match any key containing "filter:protocol|protocolPath".
        if (protocol && protocolPath) {
            const filterFragment = `filter:${protocol}|${protocolPath}`;
            for (const key of context.satisfiedDeps) {
                if (key.includes(filterFragment)) {
                    context.satisfiedDeps.delete(key);
                }
            }
            for (const key of context.missingDeps) {
                if (key.includes(filterFragment)) {
                    context.missingDeps.delete(key);
                }
            }
        }
        // Invalidate contextKeyRecord deps.
        // Context key records are in the key-delivery protocol, but their closure
        // cache keys use the SOURCE protocol (from tags.protocol), not the
        // key-delivery protocol URI. So when a key-delivery record arrives, we
        // must extract the source protocol from the record's tags.
        if (protocol === 'https://identity.foundation/protocols/key-delivery') {
            // Real key-delivery writes have tags: { protocol: sourceProtocol, contextId: ... }
            const tags = (_a = message.descriptor) === null || _a === void 0 ? void 0 : _a.tags;
            const sourceProtocol = tags === null || tags === void 0 ? void 0 : tags.protocol;
            if (sourceProtocol) {
                const ctxKeyFragment = `messageCid:${sourceProtocol}|`;
                for (const key of context.satisfiedDeps) {
                    if (key.includes(ctxKeyFragment)) {
                        context.satisfiedDeps.delete(key);
                    }
                }
                for (const key of context.missingDeps) {
                    if (key.includes(ctxKeyFragment)) {
                        context.missingDeps.delete(key);
                    }
                }
            }
        }
        else if (protocol) {
            const ctxKeyFragment = `messageCid:${protocol}|`;
            for (const key of context.satisfiedDeps) {
                if (key.includes(ctxKeyFragment)) {
                    context.satisfiedDeps.delete(key);
                }
            }
            for (const key of context.missingDeps) {
                if (key.includes(ctxKeyFragment)) {
                    context.missingDeps.delete(key);
                }
            }
        }
    }
}
function deleteDependencyCacheEntries(cache, dependencyKeySuffix) {
    for (const key of cache) {
        if (key.endsWith(dependencyKeySuffix)) {
            cache.delete(key);
        }
    }
}
//# sourceMappingURL=sync-closure-types.js.map