"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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.modifyClaims = exports.getAuthorization = exports.prepareModifyClaims = exports.groupByDid = void 0;
const bluebird_1 = __importDefault(require("bluebird"));
const lodash_1 = require("lodash");
const types_1 = require("../../polkadot/types");
const internal_1 = require("../../internal");
const queries_1 = require("../../middleware/queries");
const types_2 = require("../../types");
const internal_2 = require("../../types/internal");
const utils_1 = require("../../types/utils");
const conversion_1 = require("../../utils/conversion");
/**
 * @hidden
 */
function groupByDid([target]) {
    return conversion_1.identityIdToString(target);
}
exports.groupByDid = groupByDid;
const areSameClaims = (claim, { scope, type }) => {
    let isSameScope = true;
    if (types_2.isScopedClaim(claim)) {
        isSameScope = scope ? lodash_1.isEqual(conversion_1.middlewareScopeToScope(scope), claim.scope) : false;
    }
    return isSameScope && types_2.ClaimType[type] === claim.type;
};
const findClaimsByOtherIssuers = (claims, claimsByDid) => claims.reduce((prev, { target, claim }) => {
    var _a;
    const targetClaims = (_a = claimsByDid[conversion_1.signerToString(target)]) !== null && _a !== void 0 ? _a : [];
    const claimExists = !!targetClaims.find(targetClaim => areSameClaims(claim, targetClaim));
    if (!claimExists) {
        return [...prev, claim];
    }
    return [...prev];
}, []);
const findPositiveBalanceIuClaims = (claims, context) => bluebird_1.default.reduce(claims, (prev, { claim }) => __awaiter(void 0, void 0, void 0, function* () {
    if (types_2.isInvestorUniquenessClaim(claim)) {
        const { scope: { value }, scopeId, } = claim;
        const balance = yield context.polymeshApi.query.asset.aggregateBalance(conversion_1.stringToTicker(value, context), conversion_1.stringToScopeId(scopeId, context));
        if (!conversion_1.balanceToBigNumber(balance).isZero()) {
            return [...prev, claim];
        }
    }
    return [...prev];
}), []);
/**
 * @hidden
 *
 * Return all new CDD claims for identities that have an existing CDD claim with a different ID
 */
const findInvalidCddClaims = (claims, context) => __awaiter(void 0, void 0, void 0, function* () {
    const invalidCddClaims = [];
    const newCddClaims = claims.filter(({ claim: { type } }) => type === types_2.ClaimType.CustomerDueDiligence);
    if (newCddClaims.length) {
        const issuedCddClaims = yield context.issuedClaims({
            targets: newCddClaims.map(({ target }) => target),
            claimTypes: [types_2.ClaimType.CustomerDueDiligence],
            includeExpired: false,
        });
        newCddClaims.forEach(({ target, claim }) => {
            const did = conversion_1.signerToString(target);
            const issuedClaimsForTarget = issuedCddClaims.data.filter(({ target: issuedTarget }) => issuedTarget.did === did);
            if (issuedClaimsForTarget.length) {
                // we know both claims are CDD claims
                const { id: newCddId } = issuedClaimsForTarget[0].claim;
                const { id: currentCddId } = claim;
                if (newCddId !== currentCddId) {
                    invalidCddClaims.push({
                        target: typeof target === 'string' ? new internal_1.Identity({ did: target }, context) : target,
                        currentCddId,
                        newCddId,
                    });
                }
            }
        });
    }
    return invalidCddClaims;
});
/**
 * @hidden
 */
function prepareModifyClaims(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { claims, operation } = args;
        const { context: { polymeshApi: { tx: { identity }, }, }, context, } = this;
        const modifyClaimArgs = [];
        let allTargets = [];
        claims.forEach(({ target, expiry, claim }) => {
            const rawExpiry = expiry ? conversion_1.dateToMoment(expiry, context) : null;
            allTargets.push(conversion_1.signerToString(target));
            modifyClaimArgs.push(utils_1.tuple(conversion_1.stringToIdentityId(conversion_1.signerToString(target), context), conversion_1.claimToMeshClaim(claim, context), rawExpiry));
        });
        allTargets = lodash_1.uniq(allTargets);
        const [nonExistentDids, middlewareAvailable] = yield Promise.all([
            context.getInvalidDids(allTargets),
            context.isMiddlewareAvailable(),
        ]);
        if (nonExistentDids.length) {
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.DataUnavailable,
                message: 'Some of the supplied Identity IDs do not exist',
                data: {
                    nonExistentDids,
                },
            });
        }
        const shouldValidateWithMiddleware = operation !== internal_2.ClaimOperation.Add && middlewareAvailable;
        // skip validation if the middleware is unavailable
        if (shouldValidateWithMiddleware) {
            const { did: currentDid } = yield context.getCurrentIdentity();
            const { data: { didsWithClaims: { items: currentClaims }, }, } = yield context.queryMiddleware(queries_1.didsWithClaims({
                dids: allTargets,
                trustedClaimIssuers: [currentDid],
                includeExpired: true,
                count: allTargets.length,
            }));
            const claimsByDid = currentClaims.reduce((prev, { did, claims: didClaims }) => {
                const copy = lodash_1.cloneDeep(prev);
                copy[did] = didClaims;
                return copy;
            }, {});
            const claimsByOtherIssuers = findClaimsByOtherIssuers(claims, claimsByDid);
            if (claimsByOtherIssuers.length) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.UnmetPrerequisite,
                    message: `Attempt to ${operation.toLowerCase()} claims that weren't issued by the current Identity`,
                    data: {
                        claimsByOtherIssuers,
                    },
                });
            }
        }
        if (operation === internal_2.ClaimOperation.Revoke) {
            const claimsWithBalance = yield findPositiveBalanceIuClaims(claims, context);
            if (claimsWithBalance.length) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.EntityInUse,
                    message: 'Attempt to revoke Investor Uniqueness claims from investors with positive balance',
                    data: {
                        claimsWithBalance,
                    },
                });
            }
            this.addBatchTransaction(identity.revokeClaim, { groupByFn: groupByDid }, modifyClaimArgs.map(([identityId, claim]) => utils_1.tuple(identityId, claim)));
            return;
        }
        if (operation === internal_2.ClaimOperation.Add) {
            const invalidCddClaims = yield findInvalidCddClaims(claims, context);
            if (invalidCddClaims.length) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.UnmetPrerequisite,
                    message: 'A target Identity cannot have CDD claims with different IDs',
                    data: {
                        invalidCddClaims,
                    },
                });
            }
        }
        this.addBatchTransaction(identity.addClaim, { groupByFn: groupByDid }, modifyClaimArgs);
    });
}
exports.prepareModifyClaims = prepareModifyClaims;
/**
 * @hidden
 */
function getAuthorization({ claims, operation, }) {
    const permissions = {
        transactions: [
            operation === internal_2.ClaimOperation.Revoke ? types_1.TxTags.identity.RevokeClaim : types_1.TxTags.identity.AddClaim,
        ],
        tokens: [],
        portfolios: [],
    };
    if (claims.some(({ claim: { type } }) => type === types_2.ClaimType.CustomerDueDiligence)) {
        return {
            roles: [{ type: types_2.RoleType.CddProvider }],
            permissions,
        };
    }
    return {
        permissions,
    };
}
exports.getAuthorization = getAuthorization;
/**
 * @hidden
 */
const modifyClaims = () => new internal_1.Procedure(prepareModifyClaims, getAuthorization);
exports.modifyClaims = modifyClaims;
//# sourceMappingURL=modifyClaims.js.map