import { X25519 } from '@enbox/crypto';
import { HdKey, KeyDerivationScheme } from '../utils/hd-key.js';
/**
 * Parses a string that may be a cross-protocol reference in `alias:path` format.
 * Returns `undefined` if the string is a local (non-cross-protocol) reference.
 *
 * Examples:
 * - `"threads:thread"` → `{ alias: "threads", protocolPath: "thread" }`
 * - `"threads:thread/participant"` → `{ alias: "threads", protocolPath: "thread/participant" }`
 * - `"thread/comment"` → `undefined` (local reference, no alias)
 */
export function parseCrossProtocolRef(ref) {
    const colonIndex = ref.indexOf(':');
    if (colonIndex === -1) {
        return undefined;
    }
    const alias = ref.substring(0, colonIndex);
    const protocolPath = ref.substring(colonIndex + 1);
    return { alias, protocolPath };
}
/**
 * Returns `true` if the given string contains a `:` indicating a cross-protocol reference.
 */
export function isCrossProtocolRef(ref) {
    return ref.includes(':');
}
/**
 * Extracts the last segment of a protocolPath — i.e. the type name.
 * e.g. `'thread/comment'` → `'comment'`, `'note'` → `'note'`.
 */
export function getTypeName(protocolPath) {
    return protocolPath.split('/').slice(-1)[0];
}
/**
 * Gets the rule set at a given protocol path within a protocol definition's structure tree.
 * Returns `undefined` if the path does not exist.
 */
export function getRuleSetAtPath(protocolPath, structure) {
    const segments = protocolPath.split('/');
    let current;
    let currentLevel = structure;
    for (const segment of segments) {
        if (!Object.hasOwn(currentLevel, segment)) {
            return undefined;
        }
        current = currentLevel[segment];
        currentLevel = current;
    }
    return current;
}
/**
 * Class containing Protocol related utility methods.
 */
export class Protocols {
    // Implementation dispatches based on argument type.
    static async deriveAndInjectPublicEncryptionKeys(protocolDefinition, rootKeyIdOrKeyDeriver, privateJwk) {
        // clone before modify
        const clone = JSON.parse(JSON.stringify(protocolDefinition));
        if (typeof rootKeyIdOrKeyDeriver !== 'string') {
            // Callback-based path
            const keyDeriver = rootKeyIdOrKeyDeriver;
            const basePath = [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol];
            clone.$keyAgreement = {
                publicKeyJwk: await keyDeriver.derivePublicKey(basePath),
            };
            async function injectKeysViaCallback(ruleSet, parentPath) {
                for (const key in ruleSet) {
                    if (!key.startsWith('$')) {
                        const currentPath = [...parentPath, key];
                        const childRuleSet = ruleSet[key];
                        // Skip $ref nodes — they are governed by the referenced protocol's encryption keys.
                        // Still recurse into children, which belong to the composing protocol.
                        if (childRuleSet.$ref !== undefined) {
                            await injectKeysViaCallback(childRuleSet, currentPath);
                            continue;
                        }
                        const publicKeyJwk = await keyDeriver.derivePublicKey(currentPath);
                        childRuleSet.$keyAgreement = { publicKeyJwk };
                        await injectKeysViaCallback(childRuleSet, currentPath);
                    }
                }
            }
            await injectKeysViaCallback(clone.structure, basePath);
            return clone;
        }
        // Raw-key path
        const rootKeyId = rootKeyIdOrKeyDeriver;
        // a function that recursively creates and adds `$keyAgreement` property to every rule set
        async function addEncryptionProperty(ruleSet, parentKey) {
            for (const key in ruleSet) {
                // if we encounter a nested rule set (a property name that doesn't begin with '$'), recursively inject the `$keyAgreement` property
                if (!key.startsWith('$')) {
                    const derivedPrivateKey = await HdKey.derivePrivateKey(parentKey, [key]);
                    const childRuleSet = ruleSet[key];
                    // Skip $ref nodes — they are governed by the referenced protocol's encryption keys.
                    // Still recurse into children, which belong to the composing protocol.
                    if (childRuleSet.$ref !== undefined) {
                        await addEncryptionProperty(childRuleSet, derivedPrivateKey);
                        continue;
                    }
                    const publicKeyJwk = await X25519.getPublicKey({ key: derivedPrivateKey.derivedPrivateKey });
                    childRuleSet.$keyAgreement = { publicKeyJwk };
                    await addEncryptionProperty(childRuleSet, derivedPrivateKey);
                }
            }
        }
        // inject encryption property starting from each root level record type
        const rootKey = {
            derivationScheme: KeyDerivationScheme.ProtocolPath,
            derivedPrivateKey: privateJwk,
            rootKeyId
        };
        const protocolLevelDerivedKey = await HdKey.derivePrivateKey(rootKey, [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol]);
        clone.$keyAgreement = {
            publicKeyJwk: await X25519.getPublicKey({ key: protocolLevelDerivedKey.derivedPrivateKey }),
        };
        await addEncryptionProperty(clone.structure, protocolLevelDerivedKey);
        return clone;
    }
}
//# sourceMappingURL=protocols.js.map