"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.Identity = void 0;
const bluebird_1 = __importDefault(require("bluebird"));
const lodash_1 = require("lodash");
const utils_1 = require("../../procedures/utils");
const internal_1 = require("../../../internal");
const queries_1 = require("../../../middleware/queries");
const types_1 = require("../../../types");
const utils_2 = require("../../../types/utils");
const constants_1 = require("../../../utils/constants");
const conversion_1 = require("../../../utils/conversion");
const internal_2 = require("../../../utils/internal");
const IdentityAuthorizations_1 = require("./IdentityAuthorizations");
const Portfolios_1 = require("./Portfolios");
const TokenPermissions_1 = require("./TokenPermissions");
/**
 * Represents an Identity in the Polymesh blockchain
 */
class Identity extends internal_1.Entity {
    /**
     * Create an Identity entity
     */
    constructor(identifiers, context) {
        super(identifiers, context);
        const { did } = identifiers;
        this.did = did;
        this.authorizations = new IdentityAuthorizations_1.IdentityAuthorizations(this, context);
        this.portfolios = new Portfolios_1.Portfolios(this, context);
        this.tokenPermissions = new TokenPermissions_1.TokenPermissions(this, context);
        this.removeSecondaryKeys = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.removeSecondaryKeys, Object.assign(Object.assign({}, args), { identity: this })] }, context);
        this.revokePermissions = internal_2.createProcedureMethod({
            getProcedureAndArgs: args => {
                const { secondaryKeys } = args;
                const signers = secondaryKeys.map(signer => {
                    return {
                        signer,
                        permissions: {
                            tokens: { type: types_1.PermissionType.Include, values: [] },
                            transactions: { type: types_1.PermissionType.Include, values: [] },
                            portfolios: { type: types_1.PermissionType.Include, values: [] },
                        },
                    };
                });
                return [internal_1.modifySignerPermissions, { secondaryKeys: signers, identity: this }];
            },
        }, context);
        this.modifyPermissions = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.modifySignerPermissions, Object.assign(Object.assign({}, args), { identity: this })] }, context);
        this.inviteAccount = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.inviteAccount, Object.assign(Object.assign({}, args), { identity: this })] }, context);
        this.createVenue = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.createVenue, args] }, context);
        this.freezeSecondaryKeys = internal_2.createProcedureMethod({ getProcedureAndArgs: () => [internal_1.toggleFreezeSecondaryKeys, { freeze: true, identity: this }] }, context);
        this.unfreezeSecondaryKeys = internal_2.createProcedureMethod({ getProcedureAndArgs: () => [internal_1.toggleFreezeSecondaryKeys, { freeze: false, identity: this }] }, context);
    }
    /**
     * @hidden
     * Checks if a value is of type [[UniqueIdentifiers]]
     */
    static isUniqueIdentifiers(identifier) {
        const { did } = identifier;
        return typeof did === 'string';
    }
    /**
     * Check whether this Identity possesses the specified Role
     */
    hasRole(role) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            if (types_1.isTickerOwnerRole(role)) {
                const { ticker } = role;
                const reservation = new internal_1.TickerReservation({ ticker }, context);
                const { owner } = yield reservation.details();
                return (owner === null || owner === void 0 ? void 0 : owner.did) === did;
            }
            else if (types_1.isCddProviderRole(role)) {
                const { polymeshApi: { query: { cddServiceProviders }, }, } = context;
                const activeMembers = yield cddServiceProviders.activeMembers();
                const memberDids = activeMembers.map(conversion_1.identityIdToString);
                return memberDids.includes(did);
            }
            else if (types_1.isVenueOwnerRole(role)) {
                const venue = new internal_1.Venue({ id: role.venueId }, context);
                const { owner } = yield venue.details();
                return owner.did === did;
            }
            else if (types_1.isPortfolioCustodianRole(role)) {
                const { portfolioId } = role;
                const portfolio = conversion_1.portfolioIdToPortfolio(portfolioId, context);
                return portfolio.isCustodiedBy();
            }
            else if (types_1.isIdentityRole(role)) {
                return did === role.did;
            }
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: `Unrecognized role "${JSON.stringify(role)}"`,
            });
        });
    }
    // eslint-disable-next-line require-jsdoc
    getTokenBalance(args, callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context, context: { polymeshApi: { query: { asset }, }, }, } = this;
            const { ticker } = args;
            const rawTicker = conversion_1.stringToTicker(ticker, context);
            const rawIdentityId = conversion_1.stringToIdentityId(did, context);
            const token = yield asset.tokens(rawTicker);
            if (token.owner_did.isEmpty) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.FatalError,
                    message: `There is no Security Token with ticker "${ticker}"`,
                });
            }
            if (callback) {
                return asset.balanceOf(rawTicker, rawIdentityId, res => {
                    callback(conversion_1.balanceToBigNumber(res));
                });
            }
            const balance = yield asset.balanceOf(rawTicker, rawIdentityId);
            return conversion_1.balanceToBigNumber(balance);
        });
    }
    /**
     * Check whether this Identity has a valid CDD claim
     */
    hasValidCdd() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did, context: { polymeshApi: { rpc }, }, } = this;
            const identityId = conversion_1.stringToIdentityId(did, context);
            const result = yield rpc.identity.isIdentityHasValidCdd(identityId);
            return conversion_1.cddStatusToBoolean(result);
        });
    }
    /**
     * Check whether this Identity is Governance Committee member
     */
    isGcMember() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { committeeMembership }, }, }, did, } = this;
            const activeMembers = yield committeeMembership.activeMembers();
            return activeMembers.map(conversion_1.identityIdToString).includes(did);
        });
    }
    /**
     * Check whether this Identity is a CDD provider
     */
    isCddProvider() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { cddServiceProviders }, }, }, did, } = this;
            const activeMembers = yield cddServiceProviders.activeMembers();
            return activeMembers.map(conversion_1.identityIdToString).includes(did);
        });
    }
    // eslint-disable-next-line require-jsdoc
    getPrimaryKey(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { identity }, }, }, did, context, } = this;
            const assembleResult = ({ primary_key: primaryKey }) => {
                return new internal_1.Account({ address: conversion_1.accountIdToString(primaryKey) }, context);
            };
            const rawDid = conversion_1.stringToIdentityId(did, context);
            if (callback) {
                return identity.didRecords(rawDid, records => callback(assembleResult(records)));
            }
            const didRecords = yield identity.didRecords(rawDid);
            return assembleResult(didRecords);
        });
    }
    /**
     * Retrieve a list of all tokens which were held at one point by this Identity
     *
     * @note uses the middleware
     * @note supports pagination
     */
    getHeldTokens(opts = { order: types_1.Order.Asc }) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            const { size, start, order } = opts;
            const result = yield context.queryMiddleware(queries_1.tokensHeldByDid({
                did,
                count: size,
                skip: start,
                order,
            }));
            const { data: { tokensHeldByDid: { items: tokensHeldByDidList, totalCount: count }, }, } = result;
            const data = tokensHeldByDidList.map(ticker => new internal_1.SecurityToken({ ticker }, context));
            const next = internal_2.calculateNextKey(count, size, start);
            return {
                data,
                next,
                count,
            };
        });
    }
    /**
     * Check whether this Identity possesses all specified roles
     */
    hasRoles(roles) {
        return __awaiter(this, void 0, void 0, function* () {
            const checkedRoles = yield Promise.all(roles.map(this.hasRole.bind(this)));
            return checkedRoles.every(hasRole => hasRole);
        });
    }
    /**
     * Get the list of tokens for which this Identity is a trusted claim issuer
     *
     * @note uses the middleware
     */
    getTrustingTokens(args = { order: types_1.Order.Asc }) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            const { order } = args;
            const { data: { tokensByTrustedClaimIssuer: tickers }, } = yield context.queryMiddleware(queries_1.tokensByTrustedClaimIssuer({ claimIssuerDid: did, order }));
            return tickers.map(ticker => new internal_1.SecurityToken({ ticker: internal_2.removePadding(ticker) }, context));
        });
    }
    // eslint-disable-next-line require-jsdoc
    getVenues(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, did, context, } = this;
            const assembleResult = (ids) => ids.map(id => new internal_1.Venue({ id: conversion_1.u64ToBigNumber(id) }, context));
            const rawDid = conversion_1.stringToIdentityId(did, context);
            if (callback) {
                return settlement.userVenues(rawDid, ids => callback(assembleResult(ids)));
            }
            const venueIds = yield settlement.userVenues(rawDid);
            return assembleResult(venueIds);
        });
    }
    /**
     * Retrieve the Scope ID associated to this Identity's Investor Uniqueness Claim for a specific Security Token
     *
     * @note more on Investor Uniqueness: https://developers.polymesh.live/confidential_identity
     */
    getScopeId(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            const { token } = args;
            const ticker = internal_2.getTicker(token);
            const scopeId = yield context.polymeshApi.query.asset.scopeIdOf(conversion_1.stringToTicker(ticker, context), conversion_1.stringToIdentityId(did, context));
            return conversion_1.scopeIdToString(scopeId);
        });
    }
    /**
     * Retrieve all Instructions where this Identity is a participant,
     *   grouped by status
     */
    getInstructions() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, did, portfolios, context, } = this;
            const ownedPortfolios = yield portfolios.getPortfolios();
            const [ownedCustodiedPortfolios, { data: custodiedPortfolios }] = yield Promise.all([
                bluebird_1.default.filter(ownedPortfolios, portfolio => portfolio.isCustodiedBy({ identity: did })),
                this.portfolios.getCustodiedPortfolios(),
            ]);
            const allPortfolios = [...ownedCustodiedPortfolios, ...custodiedPortfolios];
            const portfolioIds = allPortfolios.map(conversion_1.portfolioLikeToPortfolioId);
            yield bluebird_1.default.map(portfolioIds, portfolioId => utils_1.assertPortfolioExists(portfolioId, context));
            const portfolioIdChunks = lodash_1.chunk(portfolioIds, constants_1.MAX_CONCURRENT_REQUESTS);
            const affirmed = [];
            const pending = [];
            const failed = [];
            yield bluebird_1.default.each(portfolioIdChunks, (portfolioIdChunk) => __awaiter(this, void 0, void 0, function* () {
                const auths = yield bluebird_1.default.map(portfolioIdChunk, portfolioId => settlement.userAffirmations.entries(conversion_1.portfolioIdToMeshPortfolioId(portfolioId, context)));
                const uniqueEntries = lodash_1.uniqBy(lodash_1.flatten(auths).map(([key, status]) => ({ id: key.args[1], status })), ({ id }) => id.toNumber());
                const instructions = yield settlement.instructionDetails.multi(uniqueEntries.map(({ id }) => id));
                uniqueEntries.forEach(({ id, status }, index) => {
                    const instruction = new internal_1.Instruction({ id: conversion_1.u64ToBigNumber(id) }, context);
                    if (instructions[index].status.isFailed) {
                        failed.push(instruction);
                    }
                    else if (status.isAffirmed) {
                        affirmed.push(instruction);
                    }
                    else if (status.isPending) {
                        pending.push(instruction);
                    }
                });
            }));
            return {
                affirmed,
                pending,
                failed,
            };
        });
    }
    /**
     * Retrieve all pending Instructions involving this Identity
     *
     * @deprecated in favor of `getInstructions`
     */
    getPendingInstructions() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, did, portfolios, context, } = this;
            const ownedPortfolios = yield portfolios.getPortfolios();
            const [ownedCustodiedPortfolios, { data: custodiedPortfolios }] = yield Promise.all([
                bluebird_1.default.filter(ownedPortfolios, portfolio => portfolio.isCustodiedBy({ identity: did })),
                this.portfolios.getCustodiedPortfolios(),
            ]);
            const allPortfolios = [...ownedCustodiedPortfolios, ...custodiedPortfolios];
            const portfolioIds = allPortfolios.map(conversion_1.portfolioLikeToPortfolioId);
            yield bluebird_1.default.map(portfolioIds, portfolioId => utils_1.assertPortfolioExists(portfolioId, context));
            const portfolioIdChunks = lodash_1.chunk(portfolioIds, constants_1.MAX_CONCURRENT_REQUESTS);
            const chunkedInstructions = yield bluebird_1.default.mapSeries(portfolioIdChunks, (portfolioIdChunk) => __awaiter(this, void 0, void 0, function* () {
                const auths = yield bluebird_1.default.map(portfolioIdChunk, portfolioId => settlement.userAffirmations.entries(conversion_1.portfolioIdToMeshPortfolioId(portfolioId, context)));
                const instructionIds = lodash_1.uniqBy(lodash_1.flatten(auths).map(([key]) => key.args[1]), id => id.toNumber());
                return settlement.instructionDetails.multi(instructionIds);
            }));
            const rawInstructions = lodash_1.flatten(chunkedInstructions);
            return rawInstructions
                .filter(({ status }) => status.isPending)
                .map(({ instruction_id: id }) => new internal_1.Instruction({ id: conversion_1.u64ToBigNumber(id) }, context));
        });
    }
    // eslint-disable-next-line require-jsdoc
    areSecondaryKeysFrozen(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context: { polymeshApi: { query: { identity }, }, }, context, } = this;
            const rawIdentityId = conversion_1.stringToIdentityId(did, context);
            if (callback) {
                return identity.isDidFrozen(rawIdentityId, frozen => {
                    callback(conversion_1.boolToBoolean(frozen));
                });
            }
            const result = yield identity.isDidFrozen(rawIdentityId);
            return conversion_1.boolToBoolean(result);
        });
    }
    /**
     * Retrieve every Dividend Distribution for which this Identity is eligible and hasn't been paid
     *
     * @note uses the middleware
     * @note this query can be potentially **SLOW** depending on which Tokens this Identity has held
     */
    getPendingDistributions() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            let tokens = [];
            let allFetched = false;
            let start;
            while (!allFetched) {
                const { data, next } = yield this.getHeldTokens({ size: constants_1.MAX_PAGE_SIZE, start });
                start = next || undefined;
                allFetched = !next;
                tokens = [...tokens, ...data];
            }
            const distributions = yield this.context.getDividendDistributionsForTokens({ tokens });
            const now = new Date();
            /*
             * We filter distributions out if:
             *   - They have expired
             *   - They have not begun
             *   - This Identity has already been paid
             */
            return bluebird_1.default.filter(distributions, ({ distribution }) => __awaiter(this, void 0, void 0, function* () {
                const { expiryDate, ticker, id: localId, paymentDate } = distribution;
                const isExpired = expiryDate && expiryDate < now;
                const hasNotStarted = paymentDate > now;
                if (isExpired || hasNotStarted) {
                    return false;
                }
                const holderPaid = yield context.polymeshApi.query.capitalDistribution.holderPaid(utils_2.tuple(conversion_1.corporateActionIdentifierToCaId({ ticker, localId }, context), conversion_1.stringToIdentityId(did, context)));
                return !conversion_1.boolToBoolean(holderPaid);
            }));
        });
    }
    // eslint-disable-next-line require-jsdoc
    getSecondaryKeys(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context, context: { polymeshApi: { query: { identity }, }, }, } = this;
            const assembleResult = ({ secondary_keys: secondaryKeys }) => {
                return secondaryKeys.map(({ signer: rawSigner, permissions }) => ({
                    signer: conversion_1.signerValueToSigner(conversion_1.signatoryToSignerValue(rawSigner), context),
                    permissions: conversion_1.meshPermissionsToPermissions(permissions, context),
                }));
            };
            if (callback) {
                return identity.didRecords(did, records => callback(assembleResult(records)));
            }
            const didRecords = yield identity.didRecords(did);
            return assembleResult(didRecords);
        });
    }
    /**
     * Determine whether this Identity exists on chain
     */
    exists() {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context } = this;
            const recordSize = yield context.polymeshApi.query.identity.didRecords.size(conversion_1.stringToIdentityId(did, context));
            return !recordSize.isZero();
        });
    }
    /**
     * Return the Identity's DID
     */
    toJson() {
        return this.did;
    }
}
exports.Identity = Identity;
//# sourceMappingURL=index.js.map