/**
 * A protocol-scoped API returned by {@link Enbox.using}.
 *
 * `TypedEnbox` is the **primary developer interface** for interacting with
 * protocol-backed records. It auto-injects the protocol URI, protocolPath,
 * and schema into every operation, and provides compile-time path
 * autocompletion plus typed data payloads via the schema map.
 *
 * All record-returning methods wrap the underlying `Record` instances in
 * {@link TypedRecord} so that type information flows through reads, queries,
 * updates, and subscriptions without manual casts.
 *
 * @example
 * ```ts
 * const social = enbox.using(SocialProtocol);
 *
 * // Install the protocol
 * await social.configure();
 *
 * // Create — path and data type are checked at compile time
 * const { record } = await social.records.create('thread', {
 *   data: { title: 'Hello World', body: '...' },
 * });
 * // record is TypedRecord<ThreadData>
 *
 * const data = await record.data.json(); // ThreadData — no cast needed
 *
 * // Query — protocol and protocolPath are auto-injected
 * const { records } = await social.records.query('thread');
 * // records is TypedRecord<ThreadData>[]
 *
 * // Subscribe — real-time changes via TypedLiveQuery
 * const { liveQuery } = await social.records.subscribe('thread/reply');
 * liveQuery.on('create', (record) => {
 *   // record is TypedRecord<ReplyData>
 * });
 * ```
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { TypedLiveQuery } from './typed-live-query.js';
import { TypedRecord } from './typed-record.js';
// ---------------------------------------------------------------------------
// TypedEnbox class
// ---------------------------------------------------------------------------
/**
 * A protocol-scoped API that auto-injects `protocol`, `protocolPath`, and
 * `schema` into every DWN operation.
 *
 * All record-returning methods wrap results in {@link TypedRecord} so that
 * the data type `T` (resolved from the schema map) flows end-to-end — from
 * write through read, query, update, and subscribe — without manual casts.
 *
 * Obtain an instance via `enbox.using(typedProtocol)`.
 *
 * @example
 * ```ts
 * const social = enbox.using(SocialProtocol);
 *
 * await social.configure();
 *
 * const { record } = await social.records.create('friend', {
 *   data: { did: 'did:example:alice', alias: 'Alice' },
 * });
 * const data = await record.data.json(); // FriendData — no cast
 *
 * const { records } = await social.records.query('friend', {
 *   filter: { tags: { did: 'did:example:alice' } },
 * });
 * for (const r of records) {
 *   const d = await r.data.json(); // FriendData
 * }
 * ```
 */
export class TypedEnbox {
    /**
     * @internal Create a new `TypedEnbox` instance. Use `enbox.using(protocol)` instead.
     * @param dwn - The underlying DWN API instance.
     * @param protocol - The typed protocol containing the definition and schema map.
     */
    constructor(dwn, protocol) {
        /** @internal */
        this._configured = false;
        /** @internal */
        this._ensureReadyPromise = null;
        this._dwn = dwn;
        this._definition = protocol.definition;
        this._validPaths = collectPaths(this._definition.structure);
        this._hasEncryptedTypes = Object.values(this._definition.types)
            .some((type) => type.encryptionRequired === true);
    }
    /**
     * The protocol URI string (e.g. `'https://example.com/social'`).
     *
     * This is the globally unique identifier for the protocol and is
     * auto-injected into every record operation.
     */
    get protocol() {
        return this._definition.protocol;
    }
    /**
     * The raw protocol definition object.
     *
     * Contains the full `protocol`, `types`, and `structure` that define
     * the protocol's schema and permission rules.
     */
    get definition() {
        return this._definition;
    }
    /**
     * Configures (installs) this protocol on the local DWN.
     *
     * If the protocol is already installed with an identical definition,
     * this is a no-op and returns the existing protocol with status `200`.
     * If the definition has changed (e.g. new types, modified structure),
     * the protocol is re-configured with the updated definition and returns
     * status `202`.
     *
     * **Must be called before any record operations.** Methods like
     * `records.create()`, `records.query()`, etc. will throw if the protocol
     * has not been configured.
     *
     * @param options - Optional configuration overrides.
     * @param options.encryption - Whether to enable auto-encryption for the
     *   protocol. If omitted, follows the protocol definition defaults.
     * @returns The DWN response status and the installed protocol object.
     *
     * @example
     * ```ts
     * const proto = enbox.using(NotebookProtocol);
     *
     * const { status, protocol } = await proto.configure();
     * console.log(status.code); // 202 (first install) or 200 (already installed)
     *
     * // Now you can use records.create(), records.query(), etc.
     * ```
     */
    configure(options) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            // Query for an existing installation of this protocol.
            const { protocols } = yield this._dwn.protocols.query({
                filter: { protocol: this._definition.protocol },
            });
            // If already installed with the same definition, return it as-is.
            if (protocols.length > 0) {
                const existing = protocols[0];
                if (definitionsEqual(existing.definition, this._definition)) {
                    this._configured = true;
                    return { status: { code: 200, detail: 'OK' }, protocol: existing };
                }
            }
            // Not installed or definition has changed — configure the new version.
            // In delegate mode, the wallet is responsible for installing encrypted
            // protocols during the connect flow. The delegate cannot derive the
            // owner's encryption keys, so configuring here would install the
            // protocol WITHOUT $encryption keys — breaking all encrypted writes.
            if (this._dwn.isDelegate && this._hasEncryptedTypes) {
                throw new Error(`TypedEnbox: Protocol '${this._definition.protocol}' requires ` +
                    `encryption but is not installed or has changed. In delegate mode, ` +
                    `encrypted protocols must be installed by the wallet during the ` +
                    `connect flow. Ensure the sync engine has completed its initial ` +
                    `sync before performing record operations.`);
            }
            const needsEncryption = !this._dwn.isDelegate && this._hasEncryptedTypes;
            const result = yield this._dwn.protocols.configure({
                definition: this._definition,
                encryption: (_a = options === null || options === void 0 ? void 0 : options.encryption) !== null && _a !== void 0 ? _a : (needsEncryption || undefined),
            });
            if (result.status.code === 202) {
                this._configured = true;
            }
            return result;
        });
    }
    /**
     * Whether the protocol has been configured (installed) on the local DWN.
     *
     * Returns `true` after a successful call to {@link TypedEnbox.configure | configure()}.
     * Record operations will throw if this is `false`.
     */
    get isConfigured() {
        return this._configured;
    }
    /**
     * Subscribe to **all** record changes across the entire protocol,
     * regardless of protocolPath.
     *
     * Unlike `records.subscribe(path)` which scopes to a single path
     * (e.g. `'notebook'`), this method catches creates, updates, and
     * deletes at every level of the protocol hierarchy — ideal for a
     * "refresh everything" strategy when any record changes.
     *
     * Returns a {@link LiveQuery} (untyped, since events span multiple
     * paths/types) or `undefined` if the subscription could not be created.
     * Call `liveQuery.close()` to stop listening.
     *
     * @example
     * ```ts
     * const typed = enbox.using(NotebookProtocol);
     * const liveQuery = await typed.subscribe();
     *
     * liveQuery?.on('change', () => {
     *   // A record somewhere in the protocol changed — re-query as needed.
     * });
     * ```
     */
    subscribe(request) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._autoConfigureOnce();
            const { liveQuery } = yield this._dwn.records.subscribe({
                from: request === null || request === void 0 ? void 0 : request.from,
                filter: { protocol: this._definition.protocol },
            });
            return liveQuery;
        });
    }
    /**
     * Validates that the path is recognized.
     * Throws a descriptive error if the path is not a valid protocol path.
     */
    _assertValidPath(path) {
        if (!this._validPaths.has(path)) {
            throw new Error(`TypedEnbox: invalid protocol path '${path}'. ` +
                `Valid paths are: ${[...this._validPaths].join(', ')}.`);
        }
    }
    /**
     * Ensures the protocol is configured before performing record operations.
     *
     * On first call, queries for an existing protocol installation:
     * - If found with an identical definition → marks as configured.
     * - If found with a different definition → marks as configured but warns
     *   that the local definition differs from the installed one.
     * - If not found → installs the protocol via `protocols.configure()`.
     *
     * Concurrent calls are deduplicated via a shared Promise so the network
     * call only happens once.
     */
    _ensureReady(path) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._configured) {
                this._assertValidPath(path);
                return;
            }
            if (this._ensureReadyPromise === null) {
                this._ensureReadyPromise = this._autoConfigureOnce();
            }
            yield this._ensureReadyPromise;
            this._assertValidPath(path);
        });
    }
    /**
     * Performs the one-time auto-configuration check. Called at most once;
     * subsequent calls reuse the same Promise via `_ensureReadyPromise`.
     */
    _autoConfigureOnce() {
        return __awaiter(this, void 0, void 0, function* () {
            const { protocols } = yield this._dwn.protocols.query({
                filter: { protocol: this._definition.protocol },
            });
            if (protocols.length > 0) {
                const existing = protocols[0];
                if (definitionsEqual(existing.definition, this._definition)) {
                    this._configured = true;
                    return;
                }
                // Installed but definitions differ — allow operations but warn.
                console.warn(`TypedEnbox: installed protocol '${this._definition.protocol}' differs from the provided definition. ` +
                    'Call configure() to update it.');
                this._configured = true;
                return;
            }
            // Not installed locally — configure it now.
            //
            // For delegates: the wallet already installed the protocol with derived
            // `$encryption` keys on the owner's remote DWN. We fetch that remote
            // definition and install it locally so the delegate can encrypt records
            // using the public keys from `$encryption`. This avoids the delegate
            // needing the owner's private X25519 key — only the already-public
            // derived keys are used for ProtocolPath encryption.
            //
            // For owners: derive encryption keys locally via the KMS.
            if (this._dwn.isDelegate) {
                const installed = yield this._autoConfigureDelegateProtocol();
                if (installed) {
                    return;
                }
                // The remote definition could not be fetched. If this protocol has
                // any types with `encryptionRequired: true`, we MUST fail — silently
                // installing without `$encryption` keys would allow plaintext writes
                // that the owner's remote DWN would later reject, or worse, persist
                // unencrypted sensitive data.
                if (this._hasEncryptedTypes) {
                    throw new Error(`TypedEnbox: delegate cannot install protocol '${this._definition.protocol}' ` +
                        'because it contains types with encryptionRequired but the owner\'s ' +
                        'remote protocol definition (with $encryption keys) could not be fetched. ' +
                        'Ensure the owner has installed the protocol on their DWN and that the ' +
                        'delegate has network access to the owner\'s DWN endpoints.');
                }
                // No encrypted types — safe to install the app-provided definition
                // as-is (no encryption keys needed).
                const result = yield this._dwn.protocols.configure({
                    definition: this._definition,
                });
                if (result.status.code === 202) {
                    this._configured = true;
                }
                return;
            }
            // Owner path: derive encryption keys locally when any type needs them.
            const needsEncryption = Object.values(this._definition.types)
                .some((type) => (type === null || type === void 0 ? void 0 : type.encryptionRequired) === true);
            const result = yield this._dwn.protocols.configure({
                definition: this._definition,
                encryption: needsEncryption || undefined,
            });
            if (result.status.code === 202) {
                this._configured = true;
            }
        });
    }
    /**
     * For delegates: fetch the owner's protocol definition (with `$encryption`
     * keys) from the remote DWN and install it locally.
     *
     * Returns `true` if the remote definition was successfully installed.
     */
    _autoConfigureDelegateProtocol() {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const { protocols: remoteProtocols } = yield this._dwn.protocols.query({
                    from: this._dwn.connectedDid,
                    filter: { protocol: this._definition.protocol },
                });
                if (remoteProtocols.length === 0) {
                    return false;
                }
                // The remote definition includes `$encryption` keys injected by the
                // owner's agent during their `protocols.configure({ encryption: true })`
                // call. Install it as-is on the delegate's local DWN so that:
                //   1. `encryptionRequired` is enforced locally (matching the remote)
                //   2. `$encryption` public keys are available for record encryption
                const remoteDefinition = remoteProtocols[0].definition;
                const result = yield this._dwn.protocols.configure({
                    definition: remoteDefinition,
                });
                if (result.status.code === 202) {
                    this._configured = true;
                    return true;
                }
                return false;
            }
            catch (_a) {
                // Network error or grant issue — fall back to local-only install.
                return false;
            }
        });
    }
    /**
     * Protocol-scoped record operations.
     *
     * Every method auto-injects the `protocol`, `protocolPath`, and `schema`
     * from the protocol definition — you never need to specify them manually.
     * Path parameters provide **compile-time autocompletion** via
     * `ProtocolPaths<D>`, and data types are resolved from the schema map.
     *
     * All methods return {@link TypedRecord} or {@link TypedLiveQuery} instances
     * that carry the resolved data type from the schema map, providing
     * end-to-end type safety.
     *
     * Available methods:
     * - {@link TypedEnbox.records.create | create(path, request)} — Create a new record
     * - {@link TypedEnbox.records.query | query(path, request?)} — Query records with filters
     * - {@link TypedEnbox.records.read | read(path, request)} — Read a single record
     * - {@link TypedEnbox.records.delete | delete(path, request)} — Delete a record by ID
     * - {@link TypedEnbox.records.subscribe | subscribe(path, request?)} — Real-time subscription
     */
    get records() {
        if (this._records !== undefined) {
            return this._records;
        }
        const cached = {
            /**
             * Create a new record at the given protocol path.
             *
             * The `protocol`, `protocolPath`, and `schema` are auto-injected from
             * the protocol definition. The `data` field is type-checked against
             * the schema map for the given path.
             *
             * @param path - The protocol path (e.g. `'notebook'`, `'notebook/page'`).
             *   Provides compile-time autocompletion for valid paths.
             * @param request - Create options including the typed `data` payload
             *   and optional fields like `parentContextId`, `tags`, `recipient`.
             * @returns A {@link TypedCreateResponse} containing the DWN response
             *   `status` and the created {@link TypedRecord}.
             *
             * @example
             * ```ts
             * const { status, record } = await proto.records.create('notebook', {
             *   data: { name: 'My Notebook' },
             * });
             *
             * // Create a child page under the notebook's context
             * const { record: page } = await proto.records.create('notebook/page', {
             *   data: { title: 'First Page', body: '' },
             *   parentContextId: record.contextId,
             * });
             * ```
             */
            create: (path, request) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                const normalizedPath = normalizePath(path);
                yield this._ensureReady(normalizedPath);
                const typeName = lastSegment(normalizedPath);
                const typeEntry = this._definition.types[typeName];
                // Auto-enable encryption when the type requires it.
                // Delegates CAN encrypt because the $encryption public keys in the
                // synced protocol definition are sufficient for ProtocolPath
                // encryption — no private key access is needed for writes.
                const autoEncryption = (typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.encryptionRequired) === true
                    ? true : undefined;
                const { status, record } = yield this._dwn.records.write(Object.assign(Object.assign({ data: request.data, store: request.store, encryption: (_a = request.encryption) !== null && _a !== void 0 ? _a : autoEncryption, parentContextId: request.parentContextId, published: request.published, datePublished: request.datePublished, recipient: request.recipient, protocolRole: request.protocolRole, tags: request.tags, protocol: this._definition.protocol, protocolPath: normalizedPath }, ((typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.schema) === undefined ? {} : { schema: typeEntry.schema })), { dataFormat: (_b = request.dataFormat) !== null && _b !== void 0 ? _b : (_c = typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.dataFormats) === null || _c === void 0 ? void 0 : _c[0] }));
                return {
                    status,
                    record: record ? new TypedRecord(record) : undefined,
                };
            }),
            /**
             * Query records at the given protocol path.
             *
             * Returns all matching records as an array of typed records, with
             * an optional pagination cursor for fetching additional pages.
             *
             * @param path - The protocol path to query (e.g. `'notebook'`,
             *   `'notebook/page'`).
             * @param request - Optional filter, sort, and pagination options.
             *   Omit entirely to return all records at the path.
             * @returns A {@link TypedQueryResponse} containing `status`, `records`
             *   (as {@link TypedRecord | TypedRecord<T>[]}), and an optional
             *   `cursor` for pagination.
             *
             * @example
             * ```ts
             * // Query all notebooks
             * const { records } = await proto.records.query('notebook');
             *
             * // Query pages under a specific notebook
             * const { records: pages } = await proto.records.query('notebook/page', {
             *   filter: { parentId: notebook.contextId },
             * });
             *
             * for (const page of pages) {
             *   const data = await page.data.json(); // PageData
             * }
             *
             * // Paginated query
             * const { records: batch, cursor } = await proto.records.query('notebook', {
             *   pagination: { limit: 10 },
             *   dateSort: DateSort.CreatedDescending,
             * });
             * ```
             */
            query: (path, request) => __awaiter(this, void 0, void 0, function* () {
                var _a;
                const normalizedPath = normalizePath(path);
                yield this._ensureReady(normalizedPath);
                const typeName = lastSegment(normalizedPath);
                const typeEntry = this._definition.types[typeName];
                const queryFilter = mapParentContextId(request === null || request === void 0 ? void 0 : request.filter);
                // Auto-enable decryption when the type requires it. Delegates use
                // delivered ProtocolPath keys from the connect flow; the agent layer
                // resolves the correct key decrypter automatically.
                const autoEncryption = (typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.encryptionRequired) === true
                    ? true : undefined;
                const { status, records, cursor } = yield this._dwn.records.query({
                    from: request === null || request === void 0 ? void 0 : request.from,
                    encryption: (_a = request === null || request === void 0 ? void 0 : request.encryption) !== null && _a !== void 0 ? _a : autoEncryption,
                    filter: Object.assign(Object.assign(Object.assign({}, queryFilter), { protocol: this._definition.protocol, protocolPath: normalizedPath }), ((typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.schema) === undefined ? {} : { schema: typeEntry.schema })),
                    dateSort: request === null || request === void 0 ? void 0 : request.dateSort,
                    pagination: request === null || request === void 0 ? void 0 : request.pagination,
                    protocolRole: request === null || request === void 0 ? void 0 : request.protocolRole,
                });
                return {
                    status,
                    records: records.map((r) => new TypedRecord(r)),
                    cursor,
                };
            }),
            /**
             * Read a single record at the given protocol path.
             *
             * Unlike `query()`, which returns an array, `read()` returns exactly
             * one record. Use `filter.recordId` to target a specific record.
             *
             * @param path - The protocol path to read from.
             * @param request - Read options including a `filter` to identify the
             *   record. See {@link TypedReadRequest} for details.
             * @returns A {@link TypedReadResponse} containing `status` and the
             *   matching {@link TypedRecord}.
             *
             * @example
             * ```ts
             * const { record } = await proto.records.read('notebook', {
             *   filter: { recordId: notebookId },
             * });
             *
             * const data = await record.data.json(); // NotebookData
             * console.log(data.name);
             * ```
             */
            read: (path, request) => __awaiter(this, void 0, void 0, function* () {
                var _a;
                const normalizedPath = normalizePath(path);
                yield this._ensureReady(normalizedPath);
                const typeName = lastSegment(normalizedPath);
                const typeEntry = this._definition.types[typeName];
                const readFilter = mapParentContextId(request.filter);
                // Auto-enable decryption when the type requires it. See query()
                // comment for delegate decryption details.
                const autoEncryption = (typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.encryptionRequired) === true
                    ? true : undefined;
                const { status, record } = yield this._dwn.records.read({
                    from: request.from,
                    encryption: (_a = request.encryption) !== null && _a !== void 0 ? _a : autoEncryption,
                    protocol: this._definition.protocol,
                    filter: Object.assign(Object.assign(Object.assign({}, readFilter), { protocol: this._definition.protocol, protocolPath: normalizedPath }), ((typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.schema) === undefined ? {} : { schema: typeEntry.schema })),
                });
                return {
                    status,
                    record: record ? new TypedRecord(record) : undefined,
                };
            }),
            /**
             * Delete a record at the given protocol path.
             *
             * The path is used for protocol validation and permission scoping,
             * while the actual record is identified by `recordId`.
             *
             * @param path - The protocol path (used for permission scoping and
             *   path validation).
             * @param request - Delete options. `recordId` is required; `from` is
             *   optional for remote deletes.
             * @returns The DWN response status.
             *
             * @example
             * ```ts
             * const { status } = await proto.records.delete('notebook', {
             *   recordId: notebook.id,
             * });
             *
             * if (status.code === 202) {
             *   console.log('Notebook deleted');
             * }
             * ```
             */
            delete: (_path, request) => __awaiter(this, void 0, void 0, function* () {
                yield this._ensureReady(normalizePath(_path));
                return this._dwn.records.delete({
                    from: request.from,
                    protocol: this._definition.protocol,
                    recordId: request.recordId,
                });
            }),
            /**
             * Subscribe to real-time changes at the given protocol path.
             *
             * Returns a {@link TypedLiveQuery} that atomically provides an initial
             * snapshot and a real-time stream of deduplicated change events, with
             * all records typed as `TypedRecord<T>`.
             *
             * @param path - The protocol path to subscribe to.
             * @param request - Optional filter and role. Use `filter.parentId`
             *   to scope the subscription to children of a specific parent.
             * @returns A {@link TypedSubscribeResponse} containing `status` and
             *   a {@link TypedLiveQuery} for receiving events.
             *
             * @example
             * ```ts
             * const { liveQuery } = await proto.records.subscribe('notebook/page', {
             *   filter: { parentId: notebook.contextId },
             * });
             *
             * liveQuery.on('create', (record) => {
             *   // record is TypedRecord<PageData>
             *   console.log('New page created');
             * });
             *
             * liveQuery.on('update', (record) => {
             *   const data = await record.data.json(); // PageData
             * });
             *
             * liveQuery.on('delete', (record) => {
             *   console.log('Page deleted:', record.id);
             * });
             *
             * // Stop listening
             * liveQuery.close();
             * ```
             */
            subscribe: (path, request) => __awaiter(this, void 0, void 0, function* () {
                const normalizedPath = normalizePath(path);
                yield this._ensureReady(normalizedPath);
                const typeName = lastSegment(normalizedPath);
                const typeEntry = this._definition.types[typeName];
                const subFilter = mapParentContextId(request === null || request === void 0 ? void 0 : request.filter);
                const { status, liveQuery } = yield this._dwn.records.subscribe({
                    from: request === null || request === void 0 ? void 0 : request.from,
                    filter: Object.assign(Object.assign(Object.assign({}, subFilter), { protocol: this._definition.protocol, protocolPath: normalizedPath }), ((typeEntry === null || typeEntry === void 0 ? void 0 : typeEntry.schema) === undefined ? {} : { schema: typeEntry.schema })),
                    protocolRole: request === null || request === void 0 ? void 0 : request.protocolRole,
                });
                return {
                    status,
                    liveQuery: liveQuery ? new TypedLiveQuery(liveQuery) : undefined,
                };
            }),
        };
        this._records = cached;
        return cached;
    }
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Maps the `parentContextId` alias to the underlying `parentId` field
 * expected by the DWN SDK. If both are provided, `parentId` takes precedence.
 * Returns a new object (or `undefined` if the input was `undefined`).
 */
function mapParentContextId(filter) {
    if (!filter) {
        return undefined;
    }
    const _a = filter, { parentContextId } = _a, rest = __rest(_a, ["parentContextId"]);
    if (parentContextId !== undefined && rest.parentId === undefined) {
        rest.parentId = parentContextId;
    }
    return rest;
}
/**
 * Compares two protocol definitions for **logical** equality using
 * deterministic JSON serialization with `$encryption` blocks stripped.
 *
 * When a protocol is installed with `encryption: true`, the agent injects
 * `$encryption` blocks into the `structure` containing derived public keys.
 * These blocks are operational metadata — not part of the developer-authored
 * definition — so they must be ignored during equality checks. Otherwise,
 * the installed definition (with `$encryption`) would always differ from
 * the source definition (without `$encryption`), causing false drift
 * detection and spurious reconfigure warnings.
 *
 * Keys are sorted recursively so that semantically identical definitions
 * with different key ordering are treated as equal.
 */
export function definitionsEqual(a, b) {
    return stableStringify(stripEncryptionBlocks(a)) === stableStringify(stripEncryptionBlocks(b));
}
/**
 * Recursively removes `$encryption` keys from an object tree.
 * Returns a new object — the original is not mutated.
 */
function stripEncryptionBlocks(value) {
    if (value === null || value === undefined || typeof value !== 'object') {
        return value;
    }
    if (Array.isArray(value)) {
        return value.map((item) => stripEncryptionBlocks(item));
    }
    const result = {};
    for (const [key, val] of Object.entries(value)) {
        if (key === '$encryption') {
            continue;
        }
        result[key] = stripEncryptionBlocks(val);
    }
    return result;
}
/**
 * Strips leading and trailing slashes from a path.
 *
 * `'friend/'` → `'friend'`, `'/group/member/'` → `'group/member'`.
 */
function normalizePath(path) {
    // Strip leading and trailing '/' without regex quantifiers (avoids ReDoS scanners).
    let start = 0;
    while (start < path.length && path.codePointAt(start) === 47) {
        start++;
    } // 47 === '/'
    let end = path.length;
    while (end > start && path.codePointAt(end - 1) === 47) {
        end--;
    }
    return path.slice(start, end);
}
/**
 * Returns the last segment of a slash-delimited path.
 */
function lastSegment(path) {
    const parts = path.split('/');
    return parts[parts.length - 1];
}
/**
 * Recursively collects all valid protocol path strings from a structure object.
 *
 * Given `{ foo: { bar: { $actions: [...] } } }`, returns `Set(['foo', 'foo/bar'])`.
 * Keys starting with `$` are skipped.
 */
function collectPaths(structure, prefix = '') {
    const paths = new Set();
    for (const key of Object.keys(structure)) {
        if (key.startsWith('$')) {
            continue;
        }
        const fullPath = prefix ? `${prefix}/${key}` : key;
        paths.add(fullPath);
        const child = structure[key];
        if (child !== null && typeof child === 'object') {
            for (const nested of collectPaths(child, fullPath)) {
                paths.add(nested);
            }
        }
    }
    return paths;
}
/**
 * Deterministic JSON serialization with sorted keys.
 */
function stableStringify(value) {
    if (value === null || value === undefined || typeof value !== 'object') {
        return JSON.stringify(value);
    }
    if (Array.isArray(value)) {
        return '[' + value.map((item) => stableStringify(item)).join(',') + ']';
    }
    const keys = Object.keys(value).sort((a, b) => a.localeCompare(b));
    const pairs = keys.map((key) => JSON.stringify(key) + ':' + stableStringify(value[key]));
    return '{' + pairs.join(',') + '}';
}
//# sourceMappingURL=typed-enbox.js.map