"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IccTopicXApi = void 0;
const icc_api_1 = require("../icc-api");
const models = require("../icc-api/model/models");
const AuthenticationProvider_1 = require("./auth/AuthenticationProvider");
const SecureDelegation_1 = require("../icc-api/model/SecureDelegation");
const EntityShareRequest_1 = require("../icc-api/model/requests/EntityShareRequest");
var AccessLevelEnum = SecureDelegation_1.SecureDelegation.AccessLevelEnum;
var RequestedPermissionEnum = EntityShareRequest_1.EntityShareRequest.RequestedPermissionEnum;
const utils_1 = require("./utils");
const Connection_1 = require("../icc-api/model/Connection");
const SecretIdUseOption_1 = require("./crypto/SecretIdUseOption");
// noinspection JSUnusedGlobalSymbols
class IccTopicXApi extends icc_api_1.IccTopicApi {
    constructor(host, headers, crypto, dataOwnerApi, authApi, autofillAuthor, authenticationProvider = new AuthenticationProvider_1.NoAuthenticationProvider(), encryptedKeys = ['description'], fetchImpl = typeof window !== 'undefined'
        ? window.fetch
        : typeof self !== 'undefined'
            ? self.fetch
            : fetch) {
        super(host, headers, authenticationProvider, fetchImpl);
        this.crypto = crypto;
        this.dataOwnerApi = dataOwnerApi;
        this.authApi = authApi;
        this.autofillAuthor = autofillAuthor;
        this.encryptedFields = (0, utils_1.parseEncryptedFields)(encryptedKeys, 'Topic.');
    }
    get headers() {
        return super.headers.then((h) => this.crypto.accessControlKeysHeaders.addAccessControlKeysHeaders(h, utils_1.EntityWithDelegationTypeName.Topic));
    }
    /**
     * Creates a new instance of topic with initialised encryption metadata (not in the database).
     * @param user the current user.
     * @param patient the patient this topic refers to.
     * @param c initialised data for the topic. Metadata such as id, creation data, etc. will be automatically initialised, but you can specify
     * other kinds of data or overwrite generated metadata with this. You can't specify encryption metadata.
     * @param options optional parameters:
     * - additionalDelegates: delegates which will have access to the entity in addition to the current data owner and delegates from the
     * auto-delegations. Must be an object which associates each data owner id with the access level to give to that data owner. May overlap with
     * auto-delegations, in such case the access level specified here will be used.
     * - sfkOption: specifies which sfk of the owning entity to use.
     * - ignoreAutoDelegations: if true the data won't be shared with the autodelegations of the user, but only with additional delegates
     * - alternateRootDelegation: by default a new entity is created with a root delegation from self to self. In keyless mode this is not possible,
     * and instead the root delegation will be from self to another. You have to specify which delegate will be part of the root delegation.
     * @return a new instance of topic.
     */
    newInstance(user_1, patient_1) {
        return __awaiter(this, arguments, void 0, function* (user, patient, c = {}, // TODO: Why this isn't Topic?
        options = {}) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
            if (!patient && options.sfkOption)
                throw new Error('preferredSfk can only be specified if patient is specified.');
            const topic = Object.assign(Object.assign({}, (c !== null && c !== void 0 ? c : {})), { id: (_a = c === null || c === void 0 ? void 0 : c.id) !== null && _a !== void 0 ? _a : this.crypto.primitives.randomUuid(), _type: 'org.taktik.icure.entities.Topic', created: (_b = c === null || c === void 0 ? void 0 : c.created) !== null && _b !== void 0 ? _b : new Date().getTime(), modified: (_c = c === null || c === void 0 ? void 0 : c.modified) !== null && _c !== void 0 ? _c : new Date().getTime(), responsible: (_d = c === null || c === void 0 ? void 0 : c.responsible) !== null && _d !== void 0 ? _d : (this.autofillAuthor ? this.dataOwnerApi.getDataOwnerIdOf(user) : undefined), author: (_e = c === null || c === void 0 ? void 0 : c.author) !== null && _e !== void 0 ? _e : (this.autofillAuthor ? user.id : undefined), codes: (_f = c === null || c === void 0 ? void 0 : c.codes) !== null && _f !== void 0 ? _f : [], tags: (_g = c === null || c === void 0 ? void 0 : c.tags) !== null && _g !== void 0 ? _g : [] });
            const extraDelegations = Object.assign(Object.assign({}, (options.ignoreAutoDelegations == true ? {} : Object.fromEntries(((_j = (_h = user.autoDelegations) === null || _h === void 0 ? void 0 : _h.all) !== null && _j !== void 0 ? _j : []).map((d) => [d, AccessLevelEnum.WRITE])))), ((_k = options === null || options === void 0 ? void 0 : options.additionalDelegates) !== null && _k !== void 0 ? _k : {}));
            const ownerId = this.dataOwnerApi.getDataOwnerIdOf(user);
            if (ownerId !== (yield this.dataOwnerApi.getCurrentDataOwnerId()))
                throw new Error('Can only initialise entities as current data owner.');
            const sfk = patient
                ? yield this.crypto.xapi.resolveSecretIdUseOptions({ entity: patient, type: utils_1.EntityWithDelegationTypeName.Patient }, (_l = options.sfkOption) !== null && _l !== void 0 ? _l : SecretIdUseOption_1.SecretIdUseOption.UseAnySharedWithParent)
                : undefined;
            if (patient && !sfk)
                throw new Error(`Couldn't find any sfk of parent patient ${patient.id}`);
            return new models.Topic(yield this.crypto.xapi
                .entityWithInitialisedEncryptedMetadata(topic, utils_1.EntityWithDelegationTypeName.Topic, patient === null || patient === void 0 ? void 0 : patient.id, sfk, true, extraDelegations, options.alternateRootDelegation)
                .then((x) => x.updatedEntity));
        });
    }
    decrypt(topics) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.crypto.xapi.tryDecryptEntities(topics, utils_1.EntityWithDelegationTypeName.Topic, (x) => new models.Topic(x))).map(({ entity }) => entity);
        });
    }
    encrypt(topics) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.crypto.xapi.tryEncryptEntities(topics, utils_1.EntityWithDelegationTypeName.Topic, this.encryptedFields, true, false, (x) => new models.Topic(x));
        });
    }
    /**
     * Create and share a new topic with other participants.
     * @param body the topic to create.
     * @return the created topic.
     */
    createTopic(body) {
        const _super = Object.create(null, {
            createTopic: { get: () => super.createTopic }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const encryptedTopics = yield this.encrypt([body]);
            const createdTopic = yield _super.createTopic.call(this, encryptedTopics[0]);
            const decryptedTopics = yield this.decrypt([createdTopic]);
            return decryptedTopics[0];
        });
    }
    /**
     * @param topic a topic
     * @return the id of the patient that the topic refers to, retrieved from the encrypted metadata. Normally there should only be one element
     * in the returned array, but in case of entity merges there could be multiple values.
     */
    decryptPatientIdOf(topic) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.crypto.xapi.owningEntityIdsOf({ entity: topic, type: utils_1.EntityWithDelegationTypeName.Topic }, undefined);
        });
    }
    /**
     * @return if the logged data owner has write access to the content of the given topic
     */
    hasWriteAccess(topic) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.crypto.xapi.hasWriteAccess({ entity: topic, type: utils_1.EntityWithDelegationTypeName.Topic });
        });
    }
    /**
     * Share an existing topic with other data owners, allowing them to access the non-encrypted data of the topic and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param delegateId the id of the data owner which will be granted access to the topic.
     * @param topic the topic to share.
     * @param options optional parameters to customize the sharing behaviour:
     * - shareSecretIds: specifies which secret ids of the entity should be shared. If not provided all secret ids available to the current user will be shared
     * - shareEncryptionKey: specifies if the encryption key of the access log should be shared with the delegate, giving access to all encrypted
     * content of the entity, excluding other encrypted metadata (defaults to {@link ShareMetadataBehaviour.IF_AVAILABLE}).
     * - sharePatientId: specifies if the id of the patient that this topic refers to should be shared with the delegate (defaults to
     * {@link ShareMetadataBehaviour.IF_AVAILABLE}).
     * - requestedPermissions: the requested permissions for the delegate, defaults to {@link RequestedPermissionEnum.MAX_WRITE}.
     * @return the updated entity
     */
    shareWith(delegateId_1, topic_1) {
        return __awaiter(this, arguments, void 0, function* (delegateId, topic, options = {}) {
            return this.shareWithMany(topic, { [delegateId]: options });
        });
    }
    /**
     * Share an existing topic with other data owners, allowing them to access the non-encrypted data of the topic and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param topic the topic to share.
     * @param delegates associates the id of data owners which will be granted access to the entity, to the following sharing options:
     * - shareSecretIds: specifies which secret ids of the entity should be shared. If not provided all secret ids available to the current user will be shared
     * - shareEncryptionKey: specifies if the encryption key of the access log should be shared with the delegate, giving access to all encrypted
     * content of the entity, excluding other encrypted metadata (defaults to {@link ShareMetadataBehaviour.IF_AVAILABLE}). Note that by default a
     * topic does not have encrypted content.
     * - sharePatientId: specifies if the id of the patient that this topic refers to should be shared with the delegate (defaults to
     * {@link ShareMetadataBehaviour.IF_AVAILABLE}).
     * - requestedPermissions: the requested permissions for the delegate, defaults to {@link RequestedPermissionEnum.MAX_WRITE}.
     * @return the updated entity
     */
    shareWithMany(topic, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.tryShareWithMany(topic, delegates)).updatedEntityOrThrow;
        });
    }
    /**
     * Share an existing topic with other data owners, allowing them to access the non-encrypted data of the topic and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param topic the topic to share.
     * @param delegates associates the id of data owners which will be granted access to the entity, to the following sharing options:
     * - shareSecretIds: specifies which secret ids of the entity should be shared. If not provided all secret ids available to the current user will be shared
     * - shareEncryptionKey: specifies if the encryption key of the access log should be shared with the delegate, giving access to all encrypted
     * content of the entity, excluding other encrypted metadata (defaults to {@link ShareMetadataBehaviour.IF_AVAILABLE}). Note that by default a
     * topic does not have encrypted content.
     * - sharePatientId: specifies if the id of the patient that this topic refers to should be shared with the delegate (defaults to
     * {@link ShareMetadataBehaviour.IF_AVAILABLE}).
     * - requestedPermissions: the requested permissions for the delegate, defaults to {@link RequestedPermissionEnum.MAX_WRITE}.
     * @return a promise which will contain the result of the operation: the updated entity if the operation was successful or details of the error if
     * the operation failed.
     */
    tryShareWithMany(topic, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            // All entities should have an encryption key.
            const entityWithEncryptionKey = yield this.crypto.xapi.ensureEncryptionKeysInitialised(topic, utils_1.EntityWithDelegationTypeName.Topic);
            const updatedEntity = entityWithEncryptionKey ? yield this.modifyTopic(entityWithEncryptionKey) : topic;
            return this.crypto.xapi
                .simpleShareOrUpdateEncryptedEntityMetadata({
                entity: updatedEntity,
                type: utils_1.EntityWithDelegationTypeName.Topic,
            }, Object.fromEntries(Object.entries(delegates).map(([delegateId, options]) => [
                delegateId,
                {
                    requestedPermissions: options.requestedPermissions,
                    shareEncryptionKeys: options.shareEncryptionKey,
                    shareOwningEntityIds: options.sharePatientId,
                    shareSecretIds: options.shareSecretIds,
                },
            ])), (x) => this.bulkShareTopics(x))
                .then((r) => r.mapSuccessAsync((e) => this.decrypt([e]).then((es) => es[0])));
        });
    }
    getDataOwnersWithAccessTo(entity) {
        return this.crypto.delegationsDeAnonymization.getDataOwnersWithAccessTo({ entity, type: utils_1.EntityWithDelegationTypeName.Topic });
    }
    getEncryptionKeysOf(entity) {
        return this.crypto.xapi.encryptionKeysOf({ entity, type: utils_1.EntityWithDelegationTypeName.Topic }, undefined);
    }
    filterTopicsBy(body_1, startDocumentId_1, limit_1) {
        const _super = Object.create(null, {
            filterTopicsBy: { get: () => super.filterTopicsBy }
        });
        return __awaiter(this, arguments, void 0, function* (body, startDocumentId, limit, collectTiming = false) {
            var _a;
            const page = yield _super.filterTopicsBy.call(this, body, startDocumentId, limit, collectTiming);
            const decryptedTopics = yield this.decrypt((_a = page.rows) !== null && _a !== void 0 ? _a : []);
            return Object.assign(Object.assign({}, page), { rows: decryptedTopics });
        });
    }
    /**
     * Obtain the topic with the given id and decrypt it.
     * @param id the id of the topic to retrieve.
     * @return the decrypted topic.
     */
    getTopic(id) {
        const _super = Object.create(null, {
            getTopic: { get: () => super.getTopic }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const topic = yield _super.getTopic.call(this, id);
            const decryptedTopics = yield this.decrypt([topic]);
            return decryptedTopics[0];
        });
    }
    /**
     * Obtain the topics with the given ids and decrypt them.
     * @param body the ids of the topics to retrieve.
     * @return the decrypted topics.
     */
    getTopics(body) {
        const _super = Object.create(null, {
            getTopics: { get: () => super.getTopics }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const topics = yield _super.getTopics.call(this, body);
            return yield this.decrypt(topics);
        });
    }
    /**
     * Update and encrypt the given topic.
     * @param body the topic to update.
     * @return the updated decrypted topic.
     */
    modifyTopic(body) {
        const _super = Object.create(null, {
            modifyTopic: { get: () => super.modifyTopic }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const encryptedTopics = yield this.encrypt([body]);
            const modifiedTopic = yield _super.modifyTopic.call(this, encryptedTopics[0]);
            const decryptedTopics = yield this.decrypt([modifiedTopic]);
            return decryptedTopics[0];
        });
    }
    /**
     * @internal this method is for internal use only and may be changed without notice
     * @param body data owner id of the data owner to which the participant should be added and the topic role of the participant.
     * @param topicId Id of the topic to which the participant should be added.
     */
    addParticipant(body, topicId) {
        return __awaiter(this, void 0, void 0, function* () {
            throw new Error('Cannot add participant to topic without sharing it first. You should use addParticipantWithTopic instead.');
        });
    }
    /**
     * This method is similar to {@link addParticipant} but it also shares the topic with the data owner before adding the participant.
     * @param body data owner id of the data owner to which the participant should be added and the topic role of the participant.
     * @param topic the topic to share and to which the participant should be added.
     * @return the updated topic.
     */
    addParticipantWithTopic(body, topic) {
        const _super = Object.create(null, {
            addParticipant: { get: () => super.addParticipant }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const updatedTopic = yield this.shareWith(body.dataOwnerId, topic, {
                requestedPermissions: RequestedPermissionEnum.FULL_WRITE,
            });
            return (yield this.decrypt([yield _super.addParticipant.call(this, body, updatedTopic.id)]))[0];
        });
    }
    /**
     * Remove the participant with the given data owner id from the topic with the given id.
     * @param body data owner id of the participant to remove.
     * @param topicId Id of the topic from which the participant should be removed.
     * @return the updated decrypted topic.
     */
    removeParticipant(body, topicId) {
        const _super = Object.create(null, {
            removeParticipant: { get: () => super.removeParticipant }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const updatedTopic = yield _super.removeParticipant.call(this, body, topicId);
            return (yield this.decrypt([updatedTopic]))[0];
        });
    }
    subscribeToTopicEvents(eventTypes_1, filter_1, eventFired_1) {
        return __awaiter(this, arguments, void 0, function* (eventTypes, filter, eventFired, options = {}) {
            return yield (0, utils_1.subscribeToEntityEvents)(this.host, this.authApi, utils_1.EntityWithDelegationTypeName.Topic, eventTypes, filter, eventFired, options, (encrypted) => __awaiter(this, void 0, void 0, function* () { return (yield this.decrypt([encrypted]))[0]; })).then((rs) => new Connection_1.ConnectionImpl(rs));
        });
    }
    createDelegationDeAnonymizationMetadata(entity, delegates) {
        return this.crypto.delegationsDeAnonymization.createOrUpdateDeAnonymizationInfo({ entity, type: utils_1.EntityWithDelegationTypeName.Topic }, delegates);
    }
}
exports.IccTopicXApi = IccTopicXApi;
//# sourceMappingURL=icc-topic-x-api.js.map