/**
 * Protocol-aware repository factory.
 *
 * `repository()` takes a `TypedEnbox` instance and returns a Proxy-backed
 * object whose shape mirrors the protocol's `structure` tree with
 * ergonomic CRUD methods on each node.
 *
 * - **Collections** (default): `create`, `query`, `get`, `delete`, `subscribe`
 * - **Singletons** (`$recordLimit: { max: 1 }`): `set`, `get`, `delete`
 * - **Nested types**: first argument is `parentContextId`
 * - **`configure()`**: idempotent protocol installation
 *
 * @example
 * ```ts
 * const social = repository(enbox.using(SocialGraphProtocol));
 * await social.configure();
 *
 * // Root collection
 * const { record } = await social.friend.create({
 *   data: { did: 'did:example:alice' },
 * });
 *
 * // Nested under group
 * const members = await social.group.member.query(groupContextId);
 * ```
 *
 * ## Deliberate `as never` pattern
 *
 * The CRUD methods below pass their `options: Record<string, unknown>`
 * parameter to `typed.records.{create, query, subscribe, update}` with
 * `as never`. This is the type-system bridge between the type-erased
 * Proxy (which doesn't carry the per-protocol generic) and the strictly-
 * typed record methods (which take `TypedRecordCreateParams<...>` etc.).
 *
 * `never` is the universal bottom subtype, so any strict generic is
 * satisfied. The structural runtime shape of `options` is validated by
 * the DWN SDK's grant-processing layer downstream — the cast is a
 * narrow, intentional escape hatch at the proxy boundary.
 *
 * SonarCloud's `typescript:S4325` flags these casts as redundant
 * because its rule doesn't model generic-parameter constraints. We
 * suppress S4325 at the file level (see `sonar-project.properties`)
 * because every CRUD method uses the same pattern and per-line
 * `// NOSONAR` would dwarf the actual code.
 *
 * @module
 */
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());
    });
};
/**
 * Checks whether a protocol rule set at a given path is a singleton
 * (has `$recordLimit: { max: 1 }`).
 */
function isSingletonPath(definition, path) {
    const [first, ...rest] = path.split('/');
    // Top-level lookup uses the declared `{ [key: string]: ProtocolRuleSet }`
    // index signature directly — no top-level cast needed.
    let node = definition.structure[first];
    for (const seg of rest) {
        if (!node || typeof node !== 'object') {
            return false;
        }
        node = node[seg];
    }
    if (!node || typeof node !== 'object') {
        return false;
    }
    const limit = node['$recordLimit'];
    return limit !== undefined
        && typeof limit === 'object'
        && limit !== null
        && limit['max'] === 1;
}
/**
 * Returns the child type keys (non-`$`-prefixed) of a rule set node
 * reached by the given path.
 */
function getChildKeys(definition, path) {
    // Top-level: walk into the structure's declared index-signature value.
    // The structure is `{ [key: string]: ProtocolRuleSet }`; navigating
    // beneath the first segment uses `ProtocolRuleSet`'s own index
    // signature, which contains both rule-set children and `$`-prefixed
    // metadata — we filter out the metadata at the leaf below.
    const segments = path.split('/');
    let node = definition.structure;
    for (const seg of segments) {
        if (!node || typeof node !== 'object') {
            return [];
        }
        node = node[seg];
    }
    if (!node || typeof node !== 'object') {
        return [];
    }
    return Object.keys(node).filter((k) => !k.startsWith('$'));
}
function querySingletonRecord(typed, path, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const { records } = yield typed.records.query(path, options);
        return records[0];
    });
}
function updateSingletonRecord(record, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const { status, record: updatedRecord } = yield record.update(Object.assign({ data: options.data }, (options.tags === undefined ? {} : { tags: options.tags })));
        return { status, record: updatedRecord };
    });
}
function createSingletonRecord(typed, path, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const result = yield typed.records.create(path, options);
        return result;
    });
}
function setSingletonRecord(typed, path, createOptions, queryOptions) {
    return __awaiter(this, void 0, void 0, function* () {
        const existingRecord = yield querySingletonRecord(typed, path, queryOptions);
        if (existingRecord !== undefined) {
            return updateSingletonRecord(existingRecord, createOptions);
        }
        const createResult = yield createSingletonRecord(typed, path, createOptions);
        return { status: createResult.status, record: createResult.record };
    });
}
// ---------------------------------------------------------------------------
// CRUD method builders
// ---------------------------------------------------------------------------
/**
 * Build collection CRUD methods for a root-level path.
 */
function buildRootCollectionMethods(typed, path) {
    return {
        create(options) {
            return __awaiter(this, void 0, void 0, function* () {
                const { status, record } = yield typed.records.create(path, options);
                return { status, record };
            });
        },
        query(options) {
            return __awaiter(this, void 0, void 0, function* () {
                const { status, records, cursor } = yield typed.records.query(path, options);
                return { status, records, cursor };
            });
        },
        get(recordId) {
            return __awaiter(this, void 0, void 0, function* () {
                const { record } = yield typed.records.read(path, {
                    filter: { recordId },
                });
                return record; // undefined when the read fails (e.g. 404)
            });
        },
        delete(recordId) {
            return __awaiter(this, void 0, void 0, function* () {
                return typed.records.delete(path, { recordId });
            });
        },
        subscribe(options) {
            return __awaiter(this, void 0, void 0, function* () {
                const { liveQuery } = yield typed.records.subscribe(path, options);
                return liveQuery;
            });
        },
    };
}
/**
 * Build singleton CRUD methods for a root-level path.
 *
 * `set()` preserves singleton upsert semantics over DWN `$recordLimit` scopes:
 * update the current projected singleton when it exists, otherwise create one.
 */
function buildRootSingletonMethods(typed, path) {
    return {
        set(options) {
            return __awaiter(this, void 0, void 0, function* () {
                return setSingletonRecord(typed, path, options);
            });
        },
        get() {
            return __awaiter(this, void 0, void 0, function* () {
                const { records } = yield typed.records.query(path);
                return records.length > 0 ? records[0] : undefined;
            });
        },
        delete(recordId) {
            return __awaiter(this, void 0, void 0, function* () {
                return typed.records.delete(path, { recordId });
            });
        },
    };
}
/**
 * Build collection CRUD methods for a nested path.
 */
function buildNestedCollectionMethods(typed, path) {
    return {
        create(parentContextId, options) {
            return __awaiter(this, void 0, void 0, function* () {
                const { status, record } = yield typed.records.create(path, Object.assign(Object.assign({}, options), { parentContextId }));
                return { status, record };
            });
        },
        query(parentContextId, options) {
            return __awaiter(this, void 0, void 0, function* () {
                const { status, records, cursor } = yield typed.records.query(path, Object.assign(Object.assign({}, options), { filter: Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.filter), { contextId: parentContextId }) }));
                return { status, records, cursor };
            });
        },
        get(recordId) {
            return __awaiter(this, void 0, void 0, function* () {
                const { record } = yield typed.records.read(path, {
                    filter: { recordId },
                });
                return record; // undefined when the read fails (e.g. 404)
            });
        },
        delete(recordId) {
            return __awaiter(this, void 0, void 0, function* () {
                return typed.records.delete(path, { recordId });
            });
        },
        subscribe(parentContextId, options) {
            return __awaiter(this, void 0, void 0, function* () {
                const { liveQuery } = yield typed.records.subscribe(path, Object.assign(Object.assign({}, options), { filter: Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.filter), { contextId: parentContextId }) }));
                return liveQuery;
            });
        },
    };
}
/**
 * Build singleton CRUD methods for a nested path.
 *
 * Uses the same singleton upsert strategy as root singletons, scoped to the
 * parent context.
 */
function buildNestedSingletonMethods(typed, path) {
    return {
        set(parentContextId, options) {
            return __awaiter(this, void 0, void 0, function* () {
                const queryOptions = { filter: { contextId: parentContextId } };
                return setSingletonRecord(typed, path, Object.assign(Object.assign({}, options), { parentContextId }), queryOptions);
            });
        },
        get(parentContextId) {
            return __awaiter(this, void 0, void 0, function* () {
                const { records } = yield typed.records.query(path, {
                    filter: { contextId: parentContextId },
                });
                return records.length > 0 ? records[0] : undefined;
            });
        },
        delete(recordId) {
            return __awaiter(this, void 0, void 0, function* () {
                return typed.records.delete(path, { recordId });
            });
        },
    };
}
// ---------------------------------------------------------------------------
// Node builder
// ---------------------------------------------------------------------------
/**
 * Build a repository node for a given path, including CRUD methods
 * and Proxy-based child nodes.
 */
function buildNode(typed, definition, path, isNested) {
    const singleton = isSingletonPath(definition, path);
    // Build CRUD methods based on root/nested and singleton/collection
    let methods;
    if (isNested) {
        methods = singleton
            ? buildNestedSingletonMethods(typed, path)
            : buildNestedCollectionMethods(typed, path);
    }
    else {
        methods = singleton
            ? buildRootSingletonMethods(typed, path)
            : buildRootCollectionMethods(typed, path);
    }
    // Use a Proxy to lazily build child nodes
    const childKeys = getChildKeys(definition, path);
    const childCache = {};
    return new Proxy(methods, {
        get(target, prop) {
            if (typeof prop !== 'string') {
                return undefined;
            }
            // CRUD methods take priority
            if (prop in target) {
                return target[prop];
            }
            // Lazily build child nodes
            if (childKeys.includes(prop)) {
                if (!(prop in childCache)) {
                    childCache[prop] = buildNode(typed, definition, `${path}/${prop}`, true);
                }
                return childCache[prop];
            }
            return undefined;
        },
    });
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Creates a protocol-aware repository from a `TypedEnbox` instance.
 *
 * The returned object provides domain-specific CRUD methods that mirror
 * the protocol's structure tree:
 *
 * - Root types: `repo.friend.create()`, `repo.friend.query()`
 * - Nested types: `repo.group.member.create(groupCtxId, { data })`
 * - Singletons: `repo.profile.set()`, `repo.profile.get()`
 * - Protocol install: `repo.configure()`
 *
 * @param typed - A `TypedEnbox` instance from `enbox.using(protocol)`.
 * @returns A typed repository object.
 *
 * @example
 * ```ts
 * const social = repository(enbox.using(SocialGraphProtocol));
 * await social.configure();
 *
 * const rec = await social.friend.create({
 *   data: { did: 'did:example:alice' },
 * });
 * const friends = await social.friend.query();
 * ```
 */
export function repository(typed) {
    const definition = typed.definition;
    // Get root-level type keys from the structure
    const rootKeys = Object.keys(definition.structure).filter((k) => !k.startsWith('$'));
    const nodeCache = {};
    const proxy = new Proxy({}, {
        get(_target, prop) {
            if (typeof prop !== 'string') {
                return undefined;
            }
            // configure() method
            if (prop === 'configure') {
                return (options) => __awaiter(this, void 0, void 0, function* () {
                    const result = yield typed.configure(options);
                    return result;
                });
            }
            // Root-level type nodes
            if (rootKeys.includes(prop)) {
                if (!(prop in nodeCache)) {
                    nodeCache[prop] = buildNode(typed, definition, prop, false);
                }
                return nodeCache[prop];
            }
            return undefined;
        },
    });
    return proxy;
}
//# sourceMappingURL=repository.js.map