"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.IccInvoiceXApi = 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");
var AccessLevelEnum = SecureDelegation_1.SecureDelegation.AccessLevelEnum;
const utils_1 = require("./utils");
const SecretIdUseOption_1 = require("./crypto/SecretIdUseOption");
class IccInvoiceXApi extends icc_api_1.IccInvoiceApi {
    get headers() {
        return super.headers.then((h) => this.crypto.accessControlKeysHeaders.addAccessControlKeysHeaders(h, utils_1.EntityWithDelegationTypeName.Invoice));
    }
    constructor(host, headers, crypto, entityrefApi, dataOwnerApi, autofillAuthor, 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.entityrefApi = entityrefApi;
        this.dataOwnerApi = dataOwnerApi;
        this.autofillAuthor = autofillAuthor;
    }
    /**
     * Creates a new instance of invoice with initialised encryption metadata (not in the database).
     * @param user the current user.
     * @param patient the patient this invoice refers to.
     * @param inv initialised data for the invoice. 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 invoice.
     */
    newInstance(user_1, patient_1) {
        return __awaiter(this, arguments, void 0, function* (user, patient, inv = {}, options = {}) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
            const invoice = new models.Invoice(Object.assign(Object.assign({}, (inv !== null && inv !== void 0 ? inv : {})), { _type: 'org.taktik.icure.entities.Invoice', id: (_a = inv === null || inv === void 0 ? void 0 : inv.id) !== null && _a !== void 0 ? _a : this.crypto.primitives.randomUuid(), groupId: (_b = inv === null || inv === void 0 ? void 0 : inv.groupId) !== null && _b !== void 0 ? _b : this.crypto.primitives.randomUuid(), created: (_c = inv === null || inv === void 0 ? void 0 : inv.created) !== null && _c !== void 0 ? _c : new Date().getTime(), modified: (_d = inv === null || inv === void 0 ? void 0 : inv.modified) !== null && _d !== void 0 ? _d : new Date().getTime(), responsible: (_e = inv === null || inv === void 0 ? void 0 : inv.responsible) !== null && _e !== void 0 ? _e : (this.autofillAuthor ? this.dataOwnerApi.getDataOwnerIdOf(user) : undefined), author: (_f = inv === null || inv === void 0 ? void 0 : inv.author) !== null && _f !== void 0 ? _f : (this.autofillAuthor ? user.id : undefined), codes: (_g = inv === null || inv === void 0 ? void 0 : inv.codes) !== null && _g !== void 0 ? _g : [], tags: (_h = inv === null || inv === void 0 ? void 0 : inv.tags) !== null && _h !== void 0 ? _h : [], invoicingCodes: (_j = inv === null || inv === void 0 ? void 0 : inv.invoicingCodes) !== null && _j !== void 0 ? _j : [] }));
            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 = yield this.crypto.xapi.resolveSecretIdUseOptions({ entity: patient, type: utils_1.EntityWithDelegationTypeName.Patient }, (_k = options.sfkOption) !== null && _k !== void 0 ? _k : SecretIdUseOption_1.SecretIdUseOption.UseAnySharedWithParent);
            const extraDelegations = Object.assign(Object.assign({}, (options.ignoreAutoDelegations == true
                ? {}
                : Object.fromEntries([...((_m = (_l = user.autoDelegations) === null || _l === void 0 ? void 0 : _l.all) !== null && _m !== void 0 ? _m : []), ...((_p = (_o = user.autoDelegations) === null || _o === void 0 ? void 0 : _o.financialInformation) !== null && _p !== void 0 ? _p : [])].map((d) => [d, AccessLevelEnum.WRITE])))), ((_q = options === null || options === void 0 ? void 0 : options.additionalDelegates) !== null && _q !== void 0 ? _q : {}));
            return new models.Invoice(yield this.crypto.xapi
                .entityWithInitialisedEncryptedMetadata(invoice, utils_1.EntityWithDelegationTypeName.Invoice, patient.id, sfk, true, extraDelegations, options.alternateRootDelegation)
                .then((x) => x.updatedEntity));
        });
    }
    /**
     * Creates an invoice. If a prefix is provided, an auto-incrementing invoice reference is generated using the
     * entity reference API. If the invoice has an internshipNihii, the reference is adjusted accordingly.
     * @param invoice the invoice to create.
     * @param prefix optional prefix for generating an auto-incrementing invoice reference.
     * @return the created invoice.
     */
    createInvoice(invoice, prefix) {
        if (!prefix) {
            return super.createInvoice(invoice);
        }
        if (!invoice.id) {
            invoice.id = this.crypto.primitives.randomUuid();
        }
        return this.getNextInvoiceReference(prefix, this.entityrefApi)
            .then((reference) => this.createInvoiceReference(reference, invoice.id, prefix, this.entityrefApi))
            .then((entityReference) => {
            if (!entityReference.id) {
                throw new Error('Cannot create invoice');
            }
            if (invoice.internshipNihii) {
                invoice.invoiceReference = entityReference.id.substring(prefix.length).replace('0', '1');
            }
            else {
                invoice.invoiceReference = entityReference.id.substring(prefix.length);
            }
            return super.createInvoice(invoice);
        });
    }
    /**
     * Gets the next available invoice reference number for the given prefix.
     * @param prefix the prefix to look up the latest reference for.
     * @param entityrefApi the entity reference API to query.
     * @return the next sequence number (1-based).
     */
    getNextInvoiceReference(prefix, entityrefApi) {
        return entityrefApi.getLatest(prefix).then((entRef) => {
            if (!entRef || !entRef.id || !entRef.id.startsWith(prefix))
                return 1;
            const sequenceNumber = entRef.id.split(':').pop() || 0;
            return Number(sequenceNumber) + 1;
        });
    }
    /**
     * Creates an entity reference for an invoice with the given reference number. If the reference already exists
     * (conflict), retries with the next available number.
     * @param nextReference the reference number to use.
     * @param docId the document id to link to this reference.
     * @param prefix the prefix for the reference id.
     * @param entityrefApi the entity reference API to use.
     * @return the created entity reference.
     */
    createInvoiceReference(nextReference, docId, prefix, entityrefApi) {
        return entityrefApi
            .createEntityReference(new models.EntityReference({
            id: prefix + nextReference.toString().padStart(6, '0'),
            docId,
        }))
            .catch((err) => {
            console.log(err);
            return this.createInvoiceReference(nextReference + 1, docId, prefix, entityrefApi);
        });
    }
    /**
     * 1. Check whether there is a delegation with 'hcpartyId' or not.
     * 2. 'fetchHcParty[hcpartyId][1]': is encrypted AES exchange key by RSA public key of him.
     * 3. Obtain the AES exchange key, by decrypting the previous step value with hcparty private key
     *      3.1.  KeyPair should be fetch from cache (in jwk)
     *      3.2.  if it doesn't exist in the cache, it has to be loaded from Browser Local store, and then import it to WebCrypto
     * 4. Obtain the array of delegations which are delegated to his ID (hcpartyId) in this patient
     * 5. Decrypt and collect all keys (secretForeignKeys) within delegations of previous step (with obtained AES key of step 4)
     * 6. Do the REST call to get all invoices with (allSecretForeignKeysDelimitedByComa, hcpartyId)
     *
     * After these painful steps, you have the invoices for the patient.
     *
     * @deprecated use {@link findIdsBy} instead
     * @param hcpartyId
     * @param patient (Promise)
     * @param usingPost
     */
    findBy(hcpartyId_1, patient_1) {
        return __awaiter(this, arguments, void 0, function* (hcpartyId, patient, usingPost = false) {
            const extractedKeys = yield this.crypto.xapi.secretIdsOf({ entity: patient, type: utils_1.EntityWithDelegationTypeName.Patient }, hcpartyId);
            const topmostParentId = (yield this.dataOwnerApi.getCurrentDataOwnerHierarchyIds())[0];
            let invoices = usingPost
                ? yield this.findInvoicesByHCPartyPatientForeignKeysUsingPost(hcpartyId, [...new Set(extractedKeys)])
                : yield this.findInvoicesByHCPartyPatientForeignKeys(hcpartyId, [...new Set(extractedKeys)].join(','));
            return yield this.decrypt(hcpartyId, invoices);
        });
    }
    /**
     * Same as {@link findBy} but it will only return the ids of the invoices. It can also filter the invoices where Invoice.invoiceDate is between
     * startDate and endDate in ascending or descending order by that field (default: ascending).
     */
    findIdsBy(hcpartyId, patient, startDate, endDate, descending) {
        return __awaiter(this, void 0, void 0, function* () {
            const extractedKeys = yield this.crypto.xapi.secretIdsOf({ entity: patient, type: utils_1.EntityWithDelegationTypeName.Patient }, hcpartyId);
            const topmostParentId = (yield this.dataOwnerApi.getCurrentDataOwnerHierarchyIds())[0];
            return this.findInvoiceIdsByDataOwnerPatientInvoiceDate(hcpartyId, [...new Set(extractedKeys)], startDate, endDate, descending);
        });
    }
    /**
     * Encrypts a list of invoices. Currently a no-op as invoices do not have encrypted fields.
     * @param user the current user.
     * @param invoices the invoices to encrypt.
     * @return the invoices unchanged.
     */
    encrypt(user, invoices) {
        return Promise.resolve(invoices);
    }
    /**
     * Decrypts a list of invoices. Currently a no-op as invoices do not have encrypted fields.
     * @param hcpartyId the id of the healthcare party.
     * @param invoices the invoices to decrypt.
     * @return the invoices unchanged.
     */
    decrypt(hcpartyId, invoices) {
        return Promise.resolve(invoices);
    }
    /**
     * @param invoice a invoice
     * @return the id of the patient that the invoice 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(invoice) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.crypto.xapi.owningEntityIdsOf({ entity: invoice, type: utils_1.EntityWithDelegationTypeName.Invoice }, undefined);
        });
    }
    /**
     * @return if the logged data owner has write access to the content of the given invoice
     */
    hasWriteAccess(invoice) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.crypto.xapi.hasWriteAccess({ entity: invoice, type: utils_1.EntityWithDelegationTypeName.Invoice });
        });
    }
    /**
     * Share an existing invoice with other data owners, allowing them to access the non-encrypted data of the invoice 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 invoice.
     * @param invoice the invoice 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}). Note that by default a
     * invoice does not have encrypted content.
     * - sharePatientId: specifies if the id of the patient that this invoice 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, invoice_1) {
        return __awaiter(this, arguments, void 0, function* (delegateId, invoice, options = {}) {
            return this.shareWithMany(invoice, { [delegateId]: options });
        });
    }
    /**
     * Share an existing invoice with other data owners, allowing them to access the non-encrypted data of the invoice and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param invoice the invoice 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
     * invoice does not have encrypted content.
     * - sharePatientId: specifies if the id of the patient that this invoice 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(invoice, delegates) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.tryShareWithMany(invoice, delegates)).updatedEntityOrThrow;
        });
    }
    /**
     * Share an existing invoice with other data owners, allowing them to access the non-encrypted data of the invoice and optionally also
     * the encrypted content, with read-only or read-write permissions.
     * @param invoice the invoice 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
     * invoice does not have encrypted content.
     * - sharePatientId: specifies if the id of the patient that this invoice 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(invoice, 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(invoice, utils_1.EntityWithDelegationTypeName.Invoice);
            const updatedEntity = entityWithEncryptionKey ? yield this.modifyInvoice(entityWithEncryptionKey) : invoice;
            return this.crypto.xapi
                .simpleShareOrUpdateEncryptedEntityMetadata({
                entity: updatedEntity,
                type: utils_1.EntityWithDelegationTypeName.Invoice,
            }, Object.fromEntries(Object.entries(delegates).map(([delegateId, options]) => [
                delegateId,
                {
                    requestedPermissions: options.requestedPermissions,
                    shareEncryptionKeys: options.shareEncryptionKey,
                    shareOwningEntityIds: options.sharePatientId,
                    shareSecretIds: options.shareSecretIds,
                },
            ])), (x) => this.bulkShareInvoices(x))
                .then((r) => r.mapSuccessAsync((e) => this.decrypt(self, [e]).then((es) => es[0])));
        });
    }
    /**
     * Retrieves the data owners that have access to the given invoice, along with their access levels.
     * @param entity the invoice.
     * @return an object containing a map of data owner ids to their access levels, and a flag indicating if there are unknown anonymous data owners.
     */
    getDataOwnersWithAccessTo(entity) {
        return this.crypto.delegationsDeAnonymization.getDataOwnersWithAccessTo({ entity, type: utils_1.EntityWithDelegationTypeName.Invoice });
    }
    /**
     * Retrieves the encryption keys of the given invoice.
     * @param entity the invoice.
     * @return the encryption key ids.
     */
    getEncryptionKeysOf(entity) {
        return this.crypto.xapi.encryptionKeysOf({ entity, type: utils_1.EntityWithDelegationTypeName.Invoice }, undefined);
    }
    /**
     * Creates or updates de-anonymization metadata for the given invoice, allowing the specified delegates to
     * identify the data owners that have access to it.
     * @param entity the invoice.
     * @param delegates the data owner ids for which to create de-anonymization metadata.
     */
    createDelegationDeAnonymizationMetadata(entity, delegates) {
        return this.crypto.delegationsDeAnonymization.createOrUpdateDeAnonymizationInfo({ entity, type: utils_1.EntityWithDelegationTypeName.Invoice }, delegates);
    }
}
exports.IccInvoiceXApi = IccInvoiceXApi;
//# sourceMappingURL=icc-invoice-x-api.js.map