"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.IccPatientXApi = void 0;
const icc_api_1 = require("../icc-api");
const collection_utils_1 = require("./utils/collection-utils");
const models = require("../icc-api/model/models");
const models_1 = require("../icc-api/model/models");
const ModelHelper_1 = require("../icc-api/model/ModelHelper");
const person_util_1 = require("./utils/person-util");
const utils_1 = require("./utils");
const AuthenticationProvider_1 = require("./auth/AuthenticationProvider");
const EntityWithDelegationTypeName_1 = require("./utils/EntityWithDelegationTypeName");
const SecureDelegation_1 = require("../icc-api/model/SecureDelegation");
const EntityShareRequest_1 = require("../icc-api/model/requests/EntityShareRequest");
const ShareMetadataBehaviour_1 = require("./crypto/ShareMetadataBehaviour");
var AccessLevelEnum = SecureDelegation_1.SecureDelegation.AccessLevelEnum;
var RequestedPermissionEnum = EntityShareRequest_1.EntityShareRequest.RequestedPermissionEnum;
const Connection_1 = require("../icc-api/model/Connection");
// noinspection JSUnusedGlobalSymbols
class IccPatientXApi extends icc_api_1.IccPatientApi {
    get headers() {
        return super.headers.then((h) => this.crypto.accessControlKeysHeaders.addAccessControlKeysHeaders(h, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient));
    }
    constructor(host, headers, crypto, contactApi, formApi, helementApi, invoiceApi, documentApi, hcpartyApi, classificationApi, dataOwnerApi, calendarItemApi, userApi, authApi, autofillAuthor, encryptedKeys = ['note'], authenticationProvider = new AuthenticationProvider_1.NoAuthenticationProvider(), fetchImpl = typeof window !== 'undefined'
        ? window.fetch
        : typeof self !== 'undefined'
            ? self.fetch
            : fetch) {
        super(host, headers, authenticationProvider, fetchImpl);
        this.crypto = crypto;
        this.contactApi = contactApi;
        this.formApi = formApi;
        this.helementApi = helementApi;
        this.invoiceApi = invoiceApi;
        this.documentApi = documentApi;
        this.hcpartyApi = hcpartyApi;
        this.classificationApi = classificationApi;
        this.dataOwnerApi = dataOwnerApi;
        this.calendarItemApi = calendarItemApi;
        this.userApi = userApi;
        this.authApi = authApi;
        this.autofillAuthor = autofillAuthor;
        this.encryptedFields = (0, utils_1.parseEncryptedFields)(encryptedKeys, 'Patient.');
    }
    /**
     * Creates a new instance of patient with initialised encryption metadata (not in the database).
     * @param user the current user.
     * @param p initialised data for the patient. 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.
     * - 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 patient.
     */
    newInstance(user_1) {
        return __awaiter(this, arguments, void 0, function* (user, p = {}, options = {}) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
            const patient = Object.assign(Object.assign({}, (p !== null && p !== void 0 ? p : {})), { _type: 'org.taktik.icure.entities.Patient', id: (_a = p === null || p === void 0 ? void 0 : p.id) !== null && _a !== void 0 ? _a : this.crypto.primitives.randomUuid(), created: (_b = p === null || p === void 0 ? void 0 : p.created) !== null && _b !== void 0 ? _b : new Date().getTime(), modified: (_c = p === null || p === void 0 ? void 0 : p.modified) !== null && _c !== void 0 ? _c : new Date().getTime(), responsible: (_d = p === null || p === void 0 ? void 0 : p.responsible) !== null && _d !== void 0 ? _d : (this.autofillAuthor ? this.dataOwnerApi.getDataOwnerIdOf(user) : undefined), author: (_e = p === null || p === void 0 ? void 0 : p.author) !== null && _e !== void 0 ? _e : (this.autofillAuthor ? user.id : undefined), codes: (_f = p === null || p === void 0 ? void 0 : p.codes) !== null && _f !== void 0 ? _f : [], tags: (_g = p === null || p === void 0 ? void 0 : p.tags) !== null && _g !== void 0 ? _g : [] });
            const ownerId = this.dataOwnerApi.getDataOwnerIdOf(user);
            if (ownerId !== (yield this.dataOwnerApi.getCurrentDataOwnerId()))
                throw new Error('Can only initialise entities as current data owner.');
            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 : []), ...((_l = (_k = user.autoDelegations) === null || _k === void 0 ? void 0 : _k.medicalInformation) !== null && _l !== void 0 ? _l : [])].map((d) => [d, AccessLevelEnum.WRITE])))), ((_m = options === null || options === void 0 ? void 0 : options.additionalDelegates) !== null && _m !== void 0 ? _m : {}));
            const initialisationInfo = yield this.crypto.xapi.entityWithInitialisedEncryptedMetadata(patient, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient, undefined, undefined, true, extraDelegations, options.alternateRootDelegation);
            return new models.Patient(initialisationInfo.updatedEntity);
        });
    }
    /**
     * Ensures patient names are consistent by synchronizing names property with firstName/lastName/maidenName/alias fields.
     * @param patient the patient to process
     * @return the patient with completed names
     */
    completeNames(patient) {
        var _a;
        let finalPatient = patient;
        if (!!finalPatient.lastName && !(0, person_util_1.hasName)(finalPatient, models.PersonName.UseEnum.Official)) {
            finalPatient = (0, person_util_1.garnishPersonWithName)(finalPatient, models.PersonName.UseEnum.Official, finalPatient.lastName, finalPatient.firstName);
        }
        if (!!finalPatient.maidenName && !(0, person_util_1.hasName)(finalPatient, models.PersonName.UseEnum.Maiden)) {
            finalPatient = (0, person_util_1.garnishPersonWithName)(finalPatient, models.PersonName.UseEnum.Maiden, finalPatient.maidenName, finalPatient.firstName);
        }
        if (!!finalPatient.alias && !(0, person_util_1.hasName)(finalPatient, models.PersonName.UseEnum.Nickname)) {
            finalPatient = (0, person_util_1.garnishPersonWithName)(finalPatient, models.PersonName.UseEnum.Nickname, finalPatient.alias, finalPatient.firstName);
        }
        if (!finalPatient.lastName && !!(0, person_util_1.hasName)(finalPatient, models.PersonName.UseEnum.Official)) {
            const officialName = (0, person_util_1.findName)(finalPatient, models.PersonName.UseEnum.Official);
            finalPatient = Object.assign(Object.assign({}, finalPatient), { lastName: officialName.lastName, firstName: (_a = officialName.firstNames) === null || _a === void 0 ? void 0 : _a[0] });
        }
        if (!finalPatient.maidenName && !!(0, person_util_1.hasName)(finalPatient, models.PersonName.UseEnum.Maiden)) {
            finalPatient = Object.assign(Object.assign({}, finalPatient), { maidenName: (0, person_util_1.findName)(finalPatient, models.PersonName.UseEnum.Maiden).lastName });
        }
        if (!finalPatient.alias && !!(0, person_util_1.hasName)(finalPatient, models.PersonName.UseEnum.Nickname)) {
            finalPatient = Object.assign(Object.assign({}, finalPatient), { alias: (0, person_util_1.findName)(finalPatient, models.PersonName.UseEnum.Nickname).lastName });
        }
        return new models_1.Patient(finalPatient);
    }
    /**
     * @deprecated replace with {@link initConfidentialSecretId}
     */
    initConfidentialDelegation(patient, user) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.initConfidentialSecretId(patient, user);
        });
    }
    /**
     * Ensures that the current data owner has some confidential secret ids for the provided patient. If not creates them and updates the patient in the
     * database.
     * @param patient the patient for which you want to initialise the confidential secret id.
     * @param user the current user.
     * @return the updated patient or the original patient if no change was necessary.
     */
    initConfidentialSecretId(patient, user) {
        return __awaiter(this, void 0, void 0, function* () {
            const dataOwnerId = this.dataOwnerApi.getDataOwnerIdOf(user);
            if (dataOwnerId !== (yield this.dataOwnerApi.getCurrentDataOwnerId()))
                throw new Error('You can initialise confidential delegations only for the current data owner');
            let updatedPatient = patient;
            if (!patient.rev) {
                updatedPatient = yield this.createPatientWithUser(user, patient);
                if (!updatedPatient)
                    throw new Error('Could not create patient');
            }
            const initialised = yield this.crypto.xapi.initialiseConfidentialSecretId(updatedPatient, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient, (x) => this.bulkSharePatients(x));
            if (initialised) {
                return initialised;
            }
            else {
                return updatedPatient;
            }
        });
    }
    createPatient(body) {
        throw new Error('Cannot call a method that returns patients without providing a user for de/encryption');
    }
    /**
     * Creates a patient in the database with encryption.
     * @param user the current user
     * @param body the patient data to create
     * @return the created patient
     */
    createPatientWithUser(user, body) {
        return body
            ? this.encrypt(user, [(0, collection_utils_1.cloneDeep)(this.completeNames(body))])
                .then((pats) => super.createPatient(pats[0]))
                .then((patient) => __awaiter(this, void 0, void 0, function* () {
                /**
                 * This code is a workaround for the fact that the backend is adding empty delegations to the patient when it is created.
                 */
                const patientDelegations = patient.delegations;
                if (patientDelegations != undefined && Object.keys(patientDelegations).length > 0) {
                    const areDelegationsEmpty = Object.values(patientDelegations).every((delegation) => delegation.length === 0);
                    if (areDelegationsEmpty) {
                        return yield this.modifyPatientRaw(new models_1.Patient(Object.assign(Object.assign({}, patient), { delegations: {} })));
                    }
                }
                return patient;
            }))
                .then((p) => this.decrypt(user, [p]))
                .then((pats) => pats[0])
            : Promise.resolve(null);
    }
    filterBy(startKey, startDocumentId, limit, skip, sort, desc, body) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    filterByWithUser(user, filterChain, startKey, startDocumentId, limit, skip, sort, desc, collectTiming = false) {
        return super
            .filterPatientsBy(startKey, startDocumentId, limit, skip, sort, desc, filterChain, collectTiming)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    findByAccessLogUserAfterDate(userId, accessType, startDate, startKey, startDocumentId, limit) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Finds patients by access log user after a specific date and returns decrypted results.
     * @param user the current user
     * @param userId the user id to search for in access logs
     * @param accessType optional type of access
     * @param startDate optional start date timestamp
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @return paginated list of decrypted patients
     */
    findByAccessLogUserAfterDateWithUser(user, userId, accessType, startDate, startKey, startDocumentId, limit) {
        return super
            .findByAccessLogUserAfterDate(userId, accessType, startDate, startKey, startDocumentId, limit)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    findByAccessLogUserAfterDate_1(externalId) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Finds a patient by external id and returns the decrypted result.
     * @param user the current user
     * @param externalId the external id to search for
     * @return the decrypted patient
     */
    findByExternalIdWithUser(user, externalId) {
        return super
            .findByExternalId(externalId)
            .then((pats) => this.decrypt(user, [pats]))
            .then((x) => x[0]);
    }
    findByNameBirthSsinAuto(healthcarePartyId, filterValue, startKey, startDocumentId, limit, sortDirection) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Finds patients by name, birth date, or SSIN with automatic search and returns decrypted results.
     * @param user the current user
     * @param healthcarePartyId optional healthcare party id
     * @param filterValue optional search value
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @param sortDirection optional sort direction
     * @return paginated list of decrypted patients
     */
    findByNameBirthSsinAutoWithUser(user, healthcarePartyId, filterValue, startKey, startDocumentId, limit, sortDirection) {
        return super
            .findByNameBirthSsinAuto(healthcarePartyId, filterValue, startKey, startDocumentId, limit, sortDirection)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    fuzzySearch(firstName, lastName, dateOfBirth) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Performs a fuzzy search for patients and returns decrypted results.
     * @param user the current user
     * @param firstName optional first name to search for
     * @param lastName optional last name to search for
     * @param dateOfBirth optional date of birth timestamp
     * @return array of decrypted patients
     */
    fuzzySearchWithUser(user, firstName, lastName, dateOfBirth) {
        return super.fuzzySearch(firstName, lastName, dateOfBirth).then((pats) => this.decrypt(user, pats));
    }
    getPatient(patientId) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Gets a patient by id without decryption.
     * @param patientId the patient id
     * @return the encrypted patient
     */
    getPatientRaw(patientId) {
        return super.getPatient(patientId);
    }
    /**
     * Gets a patient by id with decryption.
     * @param user the current user
     * @param patientId the patient id
     * @return the decrypted patient
     */
    getPatientWithUser(user, patientId) {
        return super
            .getPatient(patientId)
            .then((p) => this.tryDecryptOrReturnOriginal([p]))
            .then((pats) => pats[0].entity);
    }
    /**
     * Gets a patient by id and indicates whether it was successfully decrypted.
     * @param user the current user
     * @param patientId the patient id
     * @return object containing the patient and a flag indicating if it was decrypted
     */
    getPotentiallyEncryptedPatientWithUser(user, patientId) {
        return super
            .getPatient(patientId)
            .then((p) => this.tryDecryptOrReturnOriginal([p]))
            .then((pats) => ({ patient: pats[0].entity, decrypted: pats[0].decrypted }));
    }
    getPatients(body) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Gets multiple patients by their ids with decryption.
     * @param user the current user
     * @param body list of patient ids
     * @return array of decrypted patients
     */
    getPatientsWithUser(user, body) {
        return super.getPatients(body).then((pats) => this.decrypt(user, pats));
    }
    listDeletedPatients(startDate, endDate, desc, startKey, startDocumentId, limit) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists deleted patients with decryption.
     * @param user the current user
     * @param startDate optional start date timestamp
     * @param endDate optional end date timestamp
     * @param desc optional sort direction
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @return paginated list of decrypted deleted patients
     */
    listDeletedPatientsWithUser(user, startDate, endDate, desc, startKey, startDocumentId, limit) {
        return super
            .listDeletedPatients(startDate, endDate, desc, startDocumentId, startKey, limit)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    listDeletedPatients_2(firstName, lastName) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists deleted patients by name with decryption.
     * @param user the current user
     * @param firstName optional first name to filter by
     * @param lastName optional last name to filter by
     * @return array of decrypted deleted patients
     */
    listDeletedPatientsByNameWithUser(user, firstName, lastName) {
        return super.listDeletedPatientsByName(firstName, lastName).then((rows) => this.decrypt(user, rows, false));
    }
    listOfMergesAfter(date) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists merged patients after a specific date with decryption.
     * @param user the current user
     * @param date timestamp to start from
     * @return array of decrypted merged patients
     */
    listOfMergesAfterWithUser(user, date) {
        return super.listOfMergesAfter(date).then((pats) => this.decrypt(user, pats, false));
    }
    listOfPatientsModifiedAfter(date, startKey, startDocumentId, limit) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists patients modified after a specific date with decryption.
     * @param user the current user
     * @param date timestamp to start from
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @return paginated list of decrypted modified patients
     */
    listOfPatientsModifiedAfterWithUser(user, date, startKey, startDocumentId, limit) {
        return super
            .listOfPatientsModifiedAfter(date, startKey, startDocumentId, limit)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    listPatients(hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists patients with decryption.
     * @param user the current user
     * @param hcPartyId optional healthcare party id
     * @param sortField optional sort field
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @param sortDirection optional sort direction
     * @return paginated list of decrypted patients
     */
    listPatientsWithUser(user, hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection) {
        return super
            .listPatients(hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    listPatientsByHcParty(hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists patients by healthcare party with decryption.
     * @param user the current user
     * @param hcPartyId the healthcare party id
     * @param sortField optional sort field
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @param sortDirection optional sort direction
     * @return paginated list of decrypted patients
     */
    listPatientsByHcPartyWithUser(user, hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection) {
        return super
            .listPatientsByHcParty(hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    listPatientsOfHcParty(hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Lists patients of a healthcare party with decryption.
     * @param user the current user
     * @param hcPartyId the healthcare party id
     * @param sortField optional sort field
     * @param startKey optional start key for pagination
     * @param startDocumentId optional start document id for pagination
     * @param limit optional maximum number of results
     * @param sortDirection optional sort direction
     * @return paginated list of decrypted patients
     */
    listPatientsOfHcPartyWithUser(user, hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection) {
        return super
            .listPatientsOfHcParty(hcPartyId, sortField, startKey, startDocumentId, limit, sortDirection)
            .then((pl) => this.decrypt(user, pl.rows, false).then((dr) => Object.assign(pl, { rows: dr })));
    }
    mergeInto(toId, fromIds) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Merges patients and returns the decrypted result.
     * @param user the current user
     * @param toId the target patient id
     * @param fromIds the source patient ids to merge from
     * @return the decrypted merged patient
     */
    mergeIntoWithUser(user, toId, fromIds) {
        return super
            .mergeInto(toId, fromIds)
            .then((p) => this.decrypt(user, [p]))
            .then((pats) => pats[0]);
    }
    modifyPatient(body) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * @internal this method is for internal use only and may be changed without notice.
     */
    modifyPatientRaw(body) {
        return super.modifyPatient(body);
    }
    /**
     * Updates a patient with encryption.
     * @param user the current user
     * @param body the patient data to update
     * @return the updated decrypted patient
     */
    modifyPatientWithUser(user, body) {
        return body ? this.modifyPatientAs(this.dataOwnerApi.getDataOwnerIdOf(user), body) : Promise.resolve(null);
    }
    modifyPatientAs(dataOwner, body) {
        return this.encryptAs(dataOwner, [(0, collection_utils_1.cloneDeep)(this.completeNames(body))])
            .then((pats) => super.modifyPatient(pats[0]))
            .then((p) => this.decryptAs(dataOwner, [p]))
            .then((pats) => pats[0]);
    }
    modifyPatientReferral(patientId, referralId, start, end) {
        throw new Error('Cannot call a method that returns contacts without providing a user for de/encryption');
    }
    /**
     * Updates a patient referral and returns the decrypted result.
     * @param user the current user
     * @param patientId the patient id
     * @param referralId the referral id
     * @param start optional start timestamp
     * @param end optional end timestamp
     * @return the updated decrypted patient
     */
    modifyPatientReferralWithUser(user, patientId, referralId, start, end) {
        return super
            .modifyPatientReferral(patientId, referralId, start, end)
            .then((p) => this.decrypt(user, [p]))
            .then((pats) => pats[0]);
    }
    /**
     * Encrypts patients using the current user's encryption keys.
     * @param user the current user
     * @param pats the patients to encrypt
     * @return the encrypted patients
     */
    encrypt(user, pats) {
        const dataOwnerId = this.dataOwnerApi.getDataOwnerIdOf(user);
        return this.encryptAs(dataOwnerId, pats);
    }
    encryptAs(dataOwner, pats) {
        return this.crypto.xapi.tryEncryptEntities(pats, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient, this.encryptedFields, true, false, (x) => new models.Patient(x));
    }
    /**
     * Decrypts patients using the current user's encryption keys. If a patient can't be decrypted, returns it with encrypted data.
     * @param user the current user
     * @param patients the patients to decrypt
     * @param fillDelegations optional flag to fill delegations (defaults to true)
     * @return the decrypted patients
     */
    decrypt(user, patients, fillDelegations = true) {
        return this.decryptAs(this.dataOwnerApi.getDataOwnerIdOf(user), patients, fillDelegations);
    }
    decryptAs(dataOwner, patients, fillDelegations = true) {
        return this.tryDecryptOrReturnOriginal(patients).then((ps) => ps.map((p) => p.entity));
    }
    /**
     * Attempts to decrypt patients. If decryption fails, returns the original encrypted patients.
     * @param patients the patients to decrypt
     * @return array of objects containing the patient entity and a flag indicating if it was decrypted
     */
    tryDecryptOrReturnOriginal(patients) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.crypto.xapi.tryDecryptEntities(patients, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient, (x) => new models.Patient(x))).map((p) => {
                if (p.entity.picture && !(p.entity.picture instanceof ArrayBuffer)) {
                    return {
                        entity: new models.Patient(Object.assign(Object.assign({}, p.entity), { picture: (0, ModelHelper_1.b64_2ab)(p.entity.picture) })),
                        decrypted: p.decrypted,
                    };
                }
                else
                    return p;
            });
        });
    }
    /**
     * @deprecated replace with {@link shareAllDataOfPatient}
     */
    share(user_1, patId_1, ownerId_1, delegateIds_1, delegationTags_1) {
        return __awaiter(this, arguments, void 0, function* (user, patId, ownerId, delegateIds, delegationTags, usingPost = false) {
            return this.shareAllDataOfPatient(user, patId, ownerId, delegateIds, delegationTags, usingPost);
        });
    }
    /**
     * Shares a patient and all related data with specified delegates.
     * @param user the current user
     * @param patId the patient id
     * @param ownerId the owner healthcare party id
     * @param delegateIds the delegate ids to share with
     * @param delegationTags tags specifying what data to share (e.g., medicalInformation, financialInformation, all)
     * @param usingPost optional flag to use POST method instead of GET (defaults to false)
     * @return object containing the updated patient and sharing statuses for each entity type
     */
    shareAllDataOfPatient(user_1, patId_1, ownerId_1, delegateIds_1, delegationTags_1) {
        return __awaiter(this, arguments, void 0, function* (user, patId, ownerId, delegateIds, delegationTags, usingPost = false) {
            const allTags = [...new Set(Object.values(delegationTags).flat())];
            const status = {
                contacts: {
                    success: allTags.includes('medicalInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                forms: {
                    success: allTags.includes('medicalInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                healthElements: {
                    success: allTags.includes('medicalInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                invoices: {
                    success: allTags.includes('financialInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                documents: {
                    success: allTags.includes('medicalInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                classifications: {
                    success: allTags.includes('medicalInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                calendarItems: {
                    success: allTags.includes('medicalInformation') || allTags.includes('all') ? false : null,
                    error: null,
                    modified: 0,
                },
                patient: { success: false, error: null, modified: 0 },
            };
            const hcp = yield this.hcpartyApi.getHealthcareParty(ownerId);
            const parentId = hcp.parentId;
            let patient = yield (0, utils_1.retry)(() => this.getPatientWithUser(user, patId));
            const patientWithInitialisedEncryption = yield this.crypto.xapi.ensureEncryptionKeysInitialised(patient, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient);
            if (patientWithInitialisedEncryption) {
                patient = yield this.modifyPatientWithUser(user, patientWithInitialisedEncryption);
            }
            if (!patient) {
                status.patient = {
                    success: false,
                    error: new Error('Patient does not exist or cannot initialise encryption keys'),
                };
                return { patient: patient, statuses: status };
            }
            const delSfks = yield this.crypto.xapi.secretIdsOf({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, ownerId);
            const ecKeys = yield this.crypto.xapi.encryptionKeysOf({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, ownerId);
            if (delSfks.length) {
                const retrievedHealthElements = yield (0, utils_1.retry)(() => (usingPost
                    ? this.helementApi.findHealthElementsDelegationsStubsByHCPartyPatientForeignKeysUsingPost(ownerId, [...new Set(delSfks)])
                    : this.helementApi.findHealthElementsDelegationsStubsByHCPartyPatientForeignKeys(ownerId, [...new Set(delSfks)].join(','))).then((hes) => parentId
                    ? (usingPost
                        ? this.helementApi.findHealthElementsDelegationsStubsByHCPartyPatientForeignKeysUsingPost(parentId, [...new Set(delSfks)])
                        : this.helementApi.findHealthElementsDelegationsStubsByHCPartyPatientForeignKeys(parentId, [...new Set(delSfks)].join(','))).then((moreHes) => (0, collection_utils_1.uniqBy)(hes.concat(moreHes), 'id'))
                    : hes));
                const retrievedForms = yield (0, utils_1.retry)(() => (usingPost
                    ? this.formApi.findFormsDelegationsStubsByHCPartyPatientForeignKeysUsingPost(ownerId, [...new Set(delSfks)])
                    : this.formApi.findFormsDelegationsStubsByHCPartyPatientForeignKeys(ownerId, [...new Set(delSfks)].join(','))).then((frms) => parentId
                    ? (usingPost
                        ? this.formApi.findFormsDelegationsStubsByHCPartyPatientForeignKeysUsingPost(parentId, [...new Set(delSfks)])
                        : this.formApi.findFormsDelegationsStubsByHCPartyPatientForeignKeys(parentId, [...new Set(delSfks)].join(','))).then((moreFrms) => (0, collection_utils_1.uniqBy)(frms.concat(moreFrms), 'id'))
                    : frms));
                const retrievedContacts = yield (0, utils_1.retry)(() => (usingPost
                    ? this.contactApi.findByHCPartyPatientSecretFKeysUsingPost(ownerId, undefined, undefined, [...new Set(delSfks)])
                    : this.contactApi.findByHCPartyPatientSecretFKeys(ownerId, [...new Set(delSfks)].join(','))).then((ctcs) => parentId
                    ? (usingPost
                        ? this.contactApi.findByHCPartyPatientSecretFKeysUsingPost(parentId, undefined, undefined, [...new Set(delSfks)])
                        : this.contactApi.findByHCPartyPatientSecretFKeys(parentId, [...new Set(delSfks)].join(','))).then((moreCtcs) => (0, collection_utils_1.uniqBy)(ctcs.concat(moreCtcs), 'id'))
                    : ctcs));
                const retrievedInvoices = yield (0, utils_1.retry)(() => (usingPost
                    ? this.invoiceApi.findInvoicesDelegationsStubsByHCPartyPatientForeignKeysUsingPost(ownerId, [...new Set(delSfks)])
                    : this.invoiceApi.findInvoicesDelegationsStubsByHCPartyPatientForeignKeys(ownerId, [...new Set(delSfks)].join(','))).then((ivs) => parentId
                    ? this.invoiceApi
                        .findInvoicesDelegationsStubsByHCPartyPatientForeignKeys(parentId, [...new Set(delSfks)].join(','))
                        .then((moreIvs) => (0, collection_utils_1.uniqBy)(ivs.concat(moreIvs), 'id'))
                    : ivs));
                const retrievedClassifications = yield (0, utils_1.retry)(() => this.classificationApi
                    .findClassificationsByHCPartyPatientForeignKeys(ownerId, [...new Set(delSfks)].join(','))
                    .then((cls) => parentId
                    ? this.classificationApi
                        .findClassificationsByHCPartyPatientForeignKeys(parentId, [...new Set(delSfks)].join(','))
                        .then((moreCls) => (0, collection_utils_1.uniqBy)(cls.concat(moreCls), 'id'))
                    : cls));
                const retrievedCalendarItems = yield (0, utils_1.retry)(() => (usingPost
                    ? this.calendarItemApi.findByHCPartyPatientSecretFKeysArray(ownerId, [...new Set(delSfks)])
                    : this.calendarItemApi.findByHCPartyPatientSecretFKeys(ownerId, [...new Set(delSfks)].join(','))).then((cls) => parentId
                    ? (usingPost
                        ? this.calendarItemApi.findByHCPartyPatientSecretFKeysArray(parentId, [...new Set(delSfks)])
                        : this.calendarItemApi.findByHCPartyPatientSecretFKeys(parentId, [...new Set(delSfks)].join(','))).then((moreCls) => (0, collection_utils_1.uniqBy)(cls.concat(moreCls), 'id'))
                    : cls));
                const isMedicalInfoTags = (tags) => tags.includes('medicalInformation') || tags.includes('all');
                const isFinancialInfoTags = (tags) => tags.includes('financialInformation') || tags.includes('all');
                const doShareEntitiesAndUpdateStatus = (entities, entitiesType, status, tagsCondition, doShareMinimal) => __awaiter(this, void 0, void 0, function* () {
                    const delegatesToApply = delegateIds.filter((delegateId) => tagsCondition(delegationTags[delegateId]));
                    if (entities.length && delegatesToApply.length) {
                        const requests = [];
                        for (const entity of entities) {
                            const currentEntityRequests = {};
                            const secretIds = yield this.crypto.xapi.secretIdsOf({ entity, type: entitiesType }, undefined);
                            const encryptionKeys = yield this.crypto.xapi.encryptionKeysOf({ entity, type: entitiesType }, undefined);
                            const request = {
                                shareSecretIds: secretIds,
                                shareEncryptionKeys: encryptionKeys,
                                shareOwningEntityIds: [patient.id],
                                requestedPermissions: RequestedPermissionEnum.MAX_WRITE,
                            };
                            for (const delegateId of delegatesToApply) {
                                currentEntityRequests[delegateId] = request;
                            }
                            requests.push({ dataForDelegates: currentEntityRequests, entity });
                        }
                        yield this.crypto.xapi
                            .bulkShareOrUpdateEncryptedEntityMetadataNoEntities(entitiesType, requests, (x) => doShareMinimal(x))
                            .then((shareResult) => {
                            status.modified = new Set(shareResult.successfulUpdates.map((x) => x.entityId)).size;
                            status.success = shareResult.updateErrors.length === 0;
                            if (!status.success) {
                                const errorMsg = `Error while sharing (some) entities of type ${entitiesType} for patient ${patient.id} : ${JSON.stringify(shareResult.updateErrors)}`;
                                console.error(errorMsg);
                                status.error = new Error(errorMsg);
                            }
                        })
                            .catch((e) => {
                            status.success = false;
                            status.error = e;
                        });
                    }
                    else {
                        status.success = true;
                    }
                });
                yield doShareEntitiesAndUpdateStatus(retrievedHealthElements, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.HealthElement, status.healthElements, isMedicalInfoTags, (x) => this.helementApi.bulkShareHealthElementsMinimal(x));
                yield doShareEntitiesAndUpdateStatus(retrievedContacts, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Contact, status.contacts, isMedicalInfoTags, (x) => this.contactApi.bulkShareContactsMinimal(x));
                yield doShareEntitiesAndUpdateStatus(retrievedInvoices, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Invoice, status.invoices, isFinancialInfoTags, (x) => this.invoiceApi.bulkShareInvoicesMinimal(x));
                yield doShareEntitiesAndUpdateStatus(retrievedClassifications, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Classification, status.classifications, isMedicalInfoTags, (x) => this.classificationApi.bulkShareClassificationsMinimal(x));
                yield doShareEntitiesAndUpdateStatus(retrievedCalendarItems, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.CalendarItem, status.calendarItems, isMedicalInfoTags, (x) => this.calendarItemApi.bulkShareCalendarItemsMinimal(x));
                yield doShareEntitiesAndUpdateStatus(retrievedForms, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Form, status.forms, isMedicalInfoTags, (x) => this.formApi.bulkShareFormsMinimal(x));
            }
            const sharePatientDataRequest = {
                shareSecretIds: delSfks,
                shareEncryptionKeys: ecKeys,
                shareOwningEntityIds: [],
                requestedPermissions: RequestedPermissionEnum.MAX_WRITE,
            };
            const sharePatientRequest = {
                entity: patient,
                dataForDelegates: Object.fromEntries(delegateIds.map((delegateId) => [delegateId, sharePatientDataRequest])),
            };
            return yield this.crypto.xapi
                .bulkShareOrUpdateEncryptedEntityMetadata(EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient, [sharePatientRequest], (x) => this.bulkSharePatients(x))
                .then((shareResult) => {
                var _a;
                if (shareResult.updatedEntities.length && !shareResult.updateErrors.length) {
                    status.patient.success = true;
                    return { patient: shareResult.updatedEntities[0], statuses: status };
                }
                else {
                    const errorMsg = `Error while sharing patient with id ${patient.id} : ${JSON.stringify(shareResult.updateErrors)}`;
                    console.error(errorMsg);
                    status.patient.error = new Error(errorMsg);
                    status.patient.success = false;
                    return { patient: (_a = shareResult.updatedEntities[0]) !== null && _a !== void 0 ? _a : patient, statuses: status };
                }
            })
                .catch((e) => {
                status.patient.error = e;
                status.patient.success = false;
                return { patient, statuses: status };
            });
        });
    }
    /**
     * Exports a patient and all related medical data.
     * @param user the current user
     * @param patId the patient id
     * @param ownerId the owner healthcare party id
     * @return object containing the patient and all related entities (contacts, forms, health elements, invoices, classifications, calendar items, documents)
     */
    export(user, patId, ownerId) {
        return __awaiter(this, void 0, void 0, function* () {
            const parentId = yield this.dataOwnerApi.getCurrentDataOwnerHierarchyIds().then((h) => (h.length - 2 >= 0 ? h[h.length - 2] : h[0]));
            const patient = yield (0, utils_1.retry)(() => __awaiter(this, void 0, void 0, function* () {
                const retrieved = yield this.getPatientWithUser(user, patId);
                if (retrieved != undefined) {
                    const initialised = yield this.crypto.xapi.ensureEncryptionKeysInitialised(retrieved, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient);
                    if (!initialised) {
                        return retrieved;
                    }
                    else {
                        return yield this.modifyPatientWithUser(user, initialised);
                    }
                }
                else {
                    return null;
                }
            }));
            const delSfks = patient != null ? yield this.crypto.xapi.secretIdsOf({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, ownerId) : [];
            if (delSfks.length <= 0) {
                return {
                    id: patId,
                    patient,
                    contacts: [],
                    forms: [],
                    healthElements: [],
                    invoices: [],
                    classifications: [],
                    calItems: [],
                    documents: [],
                };
            }
            const contactIds = new Set([
                ...(yield (0, utils_1.retry)(() => this.contactApi.findContactIdsByDataOwnerPatientOpeningDate(ownerId, delSfks))),
                ...(yield (0, utils_1.retry)(() => this.contactApi.findContactIdsByDataOwnerPatientOpeningDate(parentId, delSfks))),
            ]);
            const formIds = new Set([
                ...(yield (0, utils_1.retry)(() => this.formApi.findFormIdsByDataOwnerPatientOpeningDate(ownerId, delSfks))),
                ...(yield (0, utils_1.retry)(() => this.formApi.findFormIdsByDataOwnerPatientOpeningDate(parentId, delSfks))),
            ]);
            const healthElementIds = new Set([
                ...(yield (0, utils_1.retry)(() => this.helementApi.findHealthElementIdsByDataOwnerPatientOpeningDate(ownerId, delSfks))),
                ...(yield (0, utils_1.retry)(() => this.helementApi.findHealthElementIdsByDataOwnerPatientOpeningDate(parentId, delSfks))),
            ]);
            const invoiceIds = new Set([
                ...(yield (0, utils_1.retry)(() => this.invoiceApi.findInvoiceIdsByDataOwnerPatientInvoiceDate(ownerId, delSfks))),
                ...(yield (0, utils_1.retry)(() => this.invoiceApi.findInvoiceIdsByDataOwnerPatientInvoiceDate(parentId, delSfks))),
            ]);
            const classificationIds = new Set([
                ...(yield (0, utils_1.retry)(() => this.classificationApi.findClassificationIdsByDataOwnerPatientCreated(ownerId, delSfks))),
                ...(yield (0, utils_1.retry)(() => this.classificationApi.findClassificationIdsByDataOwnerPatientCreated(parentId, delSfks))),
            ]);
            const calendarItemIds = new Set([
                ...(yield (0, utils_1.retry)(() => this.calendarItemApi.findCalendarItemIdsByDataOwnerPatientStartTime(ownerId, delSfks))),
                ...(yield (0, utils_1.retry)(() => this.calendarItemApi.findCalendarItemIdsByDataOwnerPatientStartTime(parentId, delSfks))),
            ]);
            function batchGet(items_1, get_1) {
                return __awaiter(this, arguments, void 0, function* (items, get, batchSize = 1000) {
                    const results = [];
                    for (let i = 0; i < items.length; i += batchSize) {
                        const batch = items.slice(i, i + batchSize);
                        const res = yield (0, utils_1.retry)(() => get(batch));
                        results.push(...res);
                    }
                    return results;
                });
            }
            const contacts = yield batchGet([...contactIds], (x) => this.contactApi.getContactsWithUser(user, { ids: x }));
            const forms = yield batchGet([...formIds], (x) => this.formApi.getForms({ ids: x }));
            const healthElements = yield batchGet([...healthElementIds], (x) => this.helementApi.getHealthElementsWithUser(user, { ids: x }));
            const invoices = yield batchGet([...invoiceIds], (x) => this.invoiceApi.getInvoices({ ids: x }));
            const classifications = yield batchGet([...classificationIds], (x) => this.classificationApi.getClassifications({ ids: x }));
            const calItems = yield batchGet([...calendarItemIds], (x) => this.calendarItemApi.getCalendarItemsWithIdsWithUser(user, { ids: x }));
            const documentIds = new Set();
            contacts.forEach((contact) => {
                var _a;
                (_a = contact.services) === null || _a === void 0 ? void 0 : _a.forEach((service) => {
                    var _a;
                    Object.values((_a = service.content) !== null && _a !== void 0 ? _a : {}).forEach((content) => {
                        if (!!content.documentId)
                            documentIds.add(content.documentId);
                    });
                });
            });
            const documents = yield batchGet([...documentIds], (x) => this.documentApi.getDocumentsWithUser(user, { ids: x }));
            return {
                id: patId,
                patient,
                calItems,
                classifications,
                contacts,
                documents,
                forms,
                healthElements,
                invoices,
            };
        });
    }
    /**
     * Validates a Belgian INAMI number using modulo 97 check.
     * @param inami the INAMI number to validate
     * @return true if the INAMI number is valid, false otherwise
     */
    checkInami(inami) {
        const num_inami = inami.replace(new RegExp('[^(0-9)]', 'g'), '');
        const checkDigit = num_inami.substring(6, 8);
        const numSansCheck = num_inami.substring(0, 6);
        let retour = false;
        //modulo du niss
        const modINAMI = parseInt(numSansCheck) % 97;
        //obtention du num de check 97 - le resultat du mod
        const checkDigit_2 = 97 - modINAMI;
        if (parseInt(checkDigit) == checkDigit_2) {
            retour = true;
        }
        return retour;
    }
    /**
     * Validates a Belgian social security identification number (SSIN/NISS) including support for bis and ter numbers.
     * @param ssin the SSIN to validate
     * @return true if the SSIN is valid, false otherwise
     */
    isValidSsin(ssin) {
        ssin = ssin.replace(new RegExp('[^(0-9)]', 'g'), '');
        let isValidNiss = false;
        const normalNumber = /^[0-9][0-9](([0][0-9])|([1][0-2]))(([0-2][0-9])|([3][0-1]))(([0-9]{2}[1-9])|([0-9][1-9][0-9])|([1-9][0-9]{2}))(([0-8][0-9])|([9][0-7]))$/.test(ssin);
        const bisNumber = /^[0-9][0-9](([2][0-9])|([3][0-2]))(([0-2][0-9])|([3][0-1]))[0-9]{3}(([0-8][0-9])|([9][0-7]))$/.test(ssin);
        const terNumber = /^[0-9][0-9](([4][0-9])|([5][0-2]))(([0-2][0-9])|([3][0-1]))[0-9]{3}(([0-8][0-9])|([9][0-7]))$/.test(ssin);
        if (normalNumber || bisNumber || terNumber) {
            isValidNiss =
                97 - (Number(ssin.substring(0, 9)) % 97) === Number(ssin.substring(9, 11))
                    ? true
                    : 97 - (Number('2' + ssin.substring(0, 9)) % 97) === Number(ssin.substring(9, 11));
        }
        return isValidNiss;
    }
    /**
     * Extracts the patient id from a child document, following merge chains if necessary.
     * @param childDocument the child document (invoice, calendar item, contact, or access log)
     * @param hcpId the healthcare party id
     * @param childDocumentType the type of the child document
     * @return the patient id after following any merge chains
     */
    getPatientIdOfChildDocumentForHcpAndHcpParents(childDocument, hcpId, childDocumentType) {
        const _super = Object.create(null, {
            getPatient: { get: () => super.getPatient }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const parentIdsArray = yield this.crypto.xapi.owningEntityIdsOf({ entity: childDocument, type: childDocumentType }, hcpId);
            const multipleParentIds = [...new Set(parentIdsArray)].length > 1;
            if (multipleParentIds) {
                throw 'Child document with id ' + childDocument.id + ' contains multiple parent ids in its CFKs for hcpId: ' + hcpId;
            }
            const parentId = parentIdsArray[0];
            if (!parentId) {
                throw 'Parent id is empty in CFK of child document with id ' + childDocument.id + ' for hcpId: ' + hcpId;
            }
            let patient = yield _super.getPatient.call(this, parentId);
            let mergeLevel = 0;
            const maxMergeLevel = 10;
            while (patient.mergeToPatientId) {
                mergeLevel++;
                if (mergeLevel === maxMergeLevel) {
                    throw 'Too many merged levels for parent (Patient) of child document ' + childDocument.id + ' ; hcpId: ' + hcpId;
                }
                patient = yield _super.getPatient.call(this, patient.mergeToPatientId);
            }
            return patient.id;
        });
    }
    /**
     * @return if the logged data owner has write access to the content of the given patient
     */
    hasWriteAccess(patient) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.crypto.xapi.hasWriteAccess({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient });
        });
    }
    /**
     * Share an existing patient with other data owners, allowing them to access the non-encrypted data of the patient 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 patient.
     * @param patient the patient to share.
     * @param shareSecretIds the secret ids of the Patient that the delegate will be given access to. Allows the delegate to search for data where the
     * shared Patient is the owning entity id.
     * @param options optional parameters to customize the sharing behaviour:
     * - 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
     * patient does not have encrypted content.
     * {@link ShareMetadataBehaviour.IF_AVAILABLE}).
     * - requestedPermissions: the requested permissions for the delegate, defaults to {@link RequestedPermissionEnum.MAX_WRITE}.
     * @return the updated entity
     */
    shareWith(delegateId_1, patient_1, shareSecretIds_1) {
        return __awaiter(this, arguments, void 0, function* (delegateId, patient, shareSecretIds, options = {}) {
            return this.shareWithMany(patient, { [delegateId]: Object.assign(Object.assign({}, options), { shareSecretIds: shareSecretIds }) });
        });
    }
    /**
     * Share an existing patient with other data owners, allowing them to access the non-encrypted data of the patient and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param patient the patient to share.
     * @param delegates associates the id of data owners which will be granted access to the entity, to the following sharing options:
     * - shareSecretIds the secret ids of the Patient that the delegate will be given access to. Allows the delegate to search for data where the
     * shared Patient is the owning entity id.
     * - 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
     * patient does not have encrypted content.
     * {@link ShareMetadataBehaviour.IF_AVAILABLE}).
     * - requestedPermissions: the requested permissions for the delegate, defaults to {@link RequestedPermissionEnum.MAX_WRITE}.
     * @return the updated entity
     */
    shareWithMany(patient, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.tryShareWithMany(patient, delegates)).updatedEntityOrThrow;
        });
    }
    /**
     * Share an existing patient with other data owners, allowing them to access the non-encrypted data of the patient and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param patient the patient to share.
     * @param delegates associates the id of data owners which will be granted access to the entity, to the following sharing options:
     * - shareSecretIds the secret ids of the Patient that the delegate will be given access to. Allows the delegate to search for data where the
     * shared Patient is the owning entity id.
     * - 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
     * patient does not have encrypted content.
     * {@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(patient, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            const self = yield this.dataOwnerApi.getCurrentDataOwnerId();
            // All entities should have an encryption key.
            const entityWithEncryptionKey = yield this.crypto.xapi.ensureEncryptionKeysInitialised(patient, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient);
            const updatedEntity = entityWithEncryptionKey ? yield this.modifyPatientAs(self, entityWithEncryptionKey) : patient;
            return this.crypto.xapi
                .simpleShareOrUpdateEncryptedEntityMetadata({
                entity: updatedEntity,
                type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient,
            }, Object.fromEntries(Object.entries(delegates).map(([delegateId, options]) => [
                delegateId,
                {
                    requestedPermissions: options.requestedPermissions,
                    shareEncryptionKeys: options.shareEncryptionKey,
                    shareOwningEntityIds: ShareMetadataBehaviour_1.ShareMetadataBehaviour.NEVER,
                    shareSecretIds: options.shareSecretIds,
                },
            ])), (x) => this.bulkSharePatients(x))
                .then((r) => r.mapSuccessAsync((e) => this.decryptAs(self, [e]).then((es) => es[0])));
        });
    }
    /**
     * @param patient a patient
     * @return all the decryptable secret ids of the patient, retrieved from the encrypted metadata. The result may be used to find entities where the
     * patient is the 'owning entity', or in the {@link shareWith} method in order to share it with other data owners.
     */
    decryptSecretIdsOf(patient) {
        return this.crypto.xapi.secretIdsOf({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, undefined);
    }
    /**
     * @param patient a patient
     * @return the confidential secret ids of the patient, retrieved from the encrypted metadata. The result may be used to find entities where the
     * patient is the 'owning entity', or in the {@link shareWith} method in order to share it with other data owners.
     */
    decryptConfidentialSecretIdsOf(patient) {
        return this.crypto.xapi.getConfidentialSecretIds({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, undefined);
    }
    /**
     * @param patient a patient
     * @return the non-confidential secret ids of the patient, retrieved from the encrypted metadata. The result may be used to find entities where the
     * patient is the 'owning entity', or in the {@link shareWith} method in order to share it with other data owners.
     */
    decryptNonConfidentialSecretIdsOf(patient) {
        return this.crypto.xapi.getSecretIdsSharedWithParents({ entity: patient, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient });
    }
    /**
     * Gets all data owners with access to the patient and their permission levels.
     * @param entity the patient
     * @return object containing permissions by data owner id and a flag indicating if there are unknown anonymous data owners
     */
    getDataOwnersWithAccessTo(entity) {
        return this.crypto.delegationsDeAnonymization.getDataOwnersWithAccessTo({ entity, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient });
    }
    /**
     * Gets all encryption keys of the patient that the current user can decrypt.
     * @param entity the patient
     * @return array of encryption keys
     */
    getEncryptionKeysOf(entity) {
        return this.crypto.xapi.encryptionKeysOf({ entity, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, undefined);
    }
    /**
     * Merge two patients into one. This method performs the following operations:
     * - The `from` patient will be soft-deleted, and it will point to the `into` patient. Only the `deletionDate` and `mergeToPatientId` fields of the
     *   patient will be changed (automatically by this method). Note that the value of {@link from} is only used to verify that the client is aware of
     *   the last version of the `from` patient: any changes to its content and/or metadata compared to what is actually stored in the database will be
     *   ignored.
     * - The metadata of the `into` patient will be automatically updated to contain also the metadata of the `from` patient and to keep track of the
     *   merge:
     *   - the `mergedIds` will be updated to contain the `from` patient id
     *   - all secret ids of the `from` patient will be added to the `into` patient
     *   - all data owners (including anonymous data owners) with access to the `from` patient will have the same access to the merged `into` patient
     *     (unless they already had greater access to the `into` patient, in which case they keep the greater access)
     * - The content of the `into` patient will be updated to match the content (name, address, note, ...) of the provided {@link mergedInto} parameter.
     *   Note that since the metadata is automatically updated by this method you must not change the metadata of the `mergedInto` patient
     *   (`delegations`, mergedInto`, ...): if there is any change between the metadata of the provided `mergedInto` patient and the stored patient this
     *   method will fail with an error.
     *
     * In case the revisions of {@link from} and/or {@link mergedInto} does not match the latest revisions for these patients in the database this
     * method will fail without soft-deleting the `from` patient and without updating the `into` patient with the merged content and metadata. You will
     * have to retrieve the updated versions of both patients before retrying the merge.
     *
     * Finally, note that this method only merges existing data, and does not perform any automatic sharing of the data. The secret ids and encryption
     * keys will not be shared with users that had access only to one of the entity, you will have to use the {@link shareWith} method after the merge
     * if you want to do so.
     * For example consider hcps A, B with access to P' and hcps A, C with access to P'', and we merge P'' into P'. After the merge:
     * - A has access to all secret ids of the merged patient and to the encryption key of the merged patient
     * - B has access to the encryption key of the merged patient (since it is the same as in P'), but only to the secret id which was originally from
     *   the unmerged P'
     * - C has no access to the encryption key of the merged patient, and has access only to the secret id which was originally from the unmerged P''
     *
     * @param from the original, unmodified `from` patient. Its content will be unchanged and its metadata will be automatically updated by this method
     * to reflect the merge.
     * @param mergedInto the `into` patient with updated content result of the merge with the `from` patient, as specified by your application logic.
     * The metadata of the `mergedInto` patient must not differ from the metadata of the stored version of the patient, since it will be automatically
     * updated by the method.
     * @return the updated `into` patient.
     */
    mergePatients(from, mergedInto) {
        const _super = Object.create(null, {
            baseMergePatients: { get: () => super.baseMergePatients }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const encryptedMerged = (yield this.encryptAs(yield this.dataOwnerApi.getCurrentDataOwnerId(), [mergedInto]))[0];
            const merged = yield _super.baseMergePatients.call(this, from.id, from.rev, encryptedMerged);
            return (yield this.tryDecryptOrReturnOriginal([merged]))[0].entity;
        });
    }
    /**
     * Subscribes to patient events (create, update, delete) and automatically decrypts them.
     * @param eventTypes the types of events to subscribe to
     * @param filter optional filter to apply to events
     * @param eventFired callback function to handle each event
     * @param options optional subscription options
     * @return connection object to manage the subscription
     */
    subscribeToPatientEvents(eventTypes_1, filter_1, eventFired_1) {
        return __awaiter(this, arguments, void 0, function* (eventTypes, filter, eventFired, options = {}) {
            const currentUser = yield this.userApi.getCurrentUser();
            return (0, utils_1.subscribeToEntityEvents)(this.host, this.authApi, EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient, eventTypes, filter, eventFired, options, (encrypted) => __awaiter(this, void 0, void 0, function* () { return (yield this.decrypt(currentUser, [encrypted]))[0]; })).then((rs) => new Connection_1.ConnectionImpl(rs));
        });
    }
    /**
     * Creates or updates de-anonymization metadata for patient delegations.
     * @param entity the patient
     * @param delegates array of delegate ids to create metadata for
     */
    createDelegationDeAnonymizationMetadata(entity, delegates) {
        return this.crypto.delegationsDeAnonymization.createOrUpdateDeAnonymizationInfo({ entity, type: EntityWithDelegationTypeName_1.EntityWithDelegationTypeName.Patient }, delegates);
    }
    /**
     * Initializes the exchange data towards a newly invited patient. This allows the doctor to share data with the
     * patient even if the patient has not yet initialized a keypair for himself.
     *
     * This method should be used only if the patient has not yet initialized a keypair for himself. If the patient has
     * already initialized a keypair this method does nothing and returns false. In this case the exchange data will be
     * automatically created the first time you share data with the patient, and your implementation of the crypto
     * strategies will be used to validate the public keys of the patient.
     *
     * Once exchange data is initialized you can use the {@link IccRecoveryXApi.createExchangeDataRecoveryInfo} to
     * generate a key that the patient will be able to use on his first login to immediately gain access to the exchange
     * data (through the {@link IccRecoveryXApi.recoverExchangeData} method).
     *
     * @param patientId the id of the newly invited patient.
     * @return true if exchange data was initialized, false if the patient already has a key pair and the exchange data
     * will be initialized in the standard way (automatically on the first time data is shared with the user).
     */
    forceInitialiseExchangeDataToNewlyInvitedPatient(patientId) {
        const _super = Object.create(null, {
            getPatient: { get: () => super.getPatient }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const patient = yield _super.getPatient.call(this, patientId);
            if (this.dataOwnerApi.getHexPublicKeysOf(patient).size)
                return false;
            yield this.crypto.exchangeData.getOrCreateEncryptionDataTo(patientId, {
                allowCreationWithoutDelegateKey: true,
            });
            return true;
        });
    }
}
exports.IccPatientXApi = IccPatientXApi;
//# sourceMappingURL=icc-patient-x-api.js.map