"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 bignumber_js_1 = __importDefault(require("bignumber.js"));
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 utils_3 = require("../../../utils");
const constants_1 = require("../../../utils/constants");
const conversion_1 = require("../../../utils/conversion");
const internal_2 = require("../../../utils/internal");
const AssetPermissions_1 = require("./AssetPermissions");
const IdentityAuthorizations_1 = require("./IdentityAuthorizations");
const Portfolios_1 = require("./Portfolios");
/**
 * 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.assetPermissions = new AssetPermissions_1.AssetPermissions(this, context);
    }
    /**
     * @hidden
     * Checks if a value is of type {@link 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 ((0, utils_3.isTickerOwnerRole)(role)) {
                const { ticker } = role;
                const reservation = new internal_1.TickerReservation({ ticker }, context);
                const { owner } = yield reservation.details();
                return owner ? this.isEqual(owner) : false;
            }
            else if ((0, utils_3.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 ((0, utils_3.isVenueOwnerRole)(role)) {
                const venue = new internal_1.Venue({ id: role.venueId }, context);
                const { owner } = yield venue.details();
                return this.isEqual(owner);
            }
            else if ((0, utils_3.isPortfolioCustodianRole)(role)) {
                const { portfolioId } = role;
                const portfolio = (0, conversion_1.portfolioIdToPortfolio)(portfolioId, context);
                return portfolio.isCustodiedBy();
            }
            else if ((0, utils_3.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
    getAssetBalance(args, callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context, context: { polymeshApi: { query: { asset }, }, }, } = this;
            const { ticker } = args;
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            const rawIdentityId = (0, conversion_1.stringToIdentityId)(did, context);
            const meshAsset = yield asset.tokens(rawTicker);
            if (meshAsset.owner_did.isEmpty) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: `There is no Asset with ticker "${ticker}"`,
                });
            }
            if (callback) {
                return asset.balanceOf(rawTicker, rawIdentityId, res => {
                    callback((0, conversion_1.balanceToBigNumber)(res));
                });
            }
            const balance = yield asset.balanceOf(rawTicker, rawIdentityId);
            return (0, 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 = (0, conversion_1.stringToIdentityId)(did, context);
            const result = yield rpc.identity.isIdentityHasValidCdd(identityId);
            return (0, 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
    getPrimaryAccount(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { identity }, }, }, did, context, } = this;
            const assembleResult = ({ primary_key: primaryKey }) => {
                return {
                    account: new internal_1.Account({ address: (0, conversion_1.accountIdToString)(primaryKey) }, context),
                    permissions: {
                        assets: null,
                        portfolios: null,
                        transactions: null,
                        transactionGroups: (0, conversion_1.transactionPermissionsToTxGroups)(null),
                    },
                };
            };
            const rawDid = (0, 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 Assets which were held at one point by this Identity
     *
     * @note uses the middleware
     * @note supports pagination
     */
    getHeldAssets(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((0, queries_1.tokensHeldByDid)({
                did,
                count: size === null || size === void 0 ? void 0 : size.toNumber(),
                skip: start === null || start === void 0 ? void 0 : start.toNumber(),
                order,
            }));
            const { data: { tokensHeldByDid: { items: assetsHeldByDidList, totalCount }, }, } = result;
            const count = new bignumber_js_1.default(totalCount);
            const data = assetsHeldByDidList.map(ticker => new internal_1.Asset({ ticker }, context));
            const next = (0, internal_2.calculateNextKey)(count, size, start);
            return {
                data,
                next,
                count,
            };
        });
    }
    /**
     * Check whether this Identity possesses all specified roles
     */
    checkRoles(roles) {
        return __awaiter(this, void 0, void 0, function* () {
            const missingRoles = yield bluebird_1.default.filter(roles, (role) => __awaiter(this, void 0, void 0, function* () {
                const hasRole = yield this.hasRole(role);
                return !hasRole;
            }));
            if (missingRoles.length) {
                return {
                    missingRoles,
                    result: false,
                };
            }
            return {
                result: true,
            };
        });
    }
    /**
     * Check whether this Identity possesses all specified roles
     *
     * @deprecated in favor of `checkRoles`
     */
    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 Assets for which this Identity is a trusted claim issuer
     *
     * @note uses the middleware
     */
    getTrustingAssets() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            const { data: { tokensByTrustedClaimIssuer: tickers }, } = yield context.queryMiddleware((0, queries_1.tokensByTrustedClaimIssuer)({ claimIssuerDid: did }));
            return tickers.map(ticker => new internal_1.Asset({ ticker: (0, 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: (0, conversion_1.u64ToBigNumber)(id) }, context));
            const rawDid = (0, 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 Asset
     *
     * @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 { asset } = args;
            const ticker = (0, internal_2.getTicker)(asset);
            const scopeId = yield context.polymeshApi.query.asset.scopeIdOf((0, conversion_1.stringToTicker)(ticker, context), (0, conversion_1.stringToIdentityId)(did, context));
            return (0, 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 => (0, utils_1.assertPortfolioExists)(portfolioId, context));
            const portfolioIdChunks = (0, 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((0, conversion_1.portfolioIdToMeshPortfolioId)(portfolioId, context)));
                const uniqueEntries = (0, lodash_1.uniqBy)((0, 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: (0, 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 => (0, utils_1.assertPortfolioExists)(portfolioId, context));
            const portfolioIdChunks = (0, 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((0, conversion_1.portfolioIdToMeshPortfolioId)(portfolioId, context)));
                const instructionIds = (0, lodash_1.uniqBy)((0, lodash_1.flatten)(auths).map(([key]) => key.args[1]), id => id.toNumber());
                return settlement.instructionDetails.multi(instructionIds);
            }));
            const rawInstructions = (0, lodash_1.flatten)(chunkedInstructions);
            return rawInstructions
                .filter(({ status }) => status.isPending)
                .map(({ instruction_id: id }) => new internal_1.Instruction({ id: (0, conversion_1.u64ToBigNumber)(id) }, context));
        });
    }
    // eslint-disable-next-line require-jsdoc
    areSecondaryAccountsFrozen(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context: { polymeshApi: { query: { identity }, }, }, context, } = this;
            const rawIdentityId = (0, conversion_1.stringToIdentityId)(did, context);
            if (callback) {
                return identity.isDidFrozen(rawIdentityId, frozen => {
                    callback((0, conversion_1.boolToBoolean)(frozen));
                });
            }
            const result = yield identity.isDidFrozen(rawIdentityId);
            return (0, 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 Assets this Identity has held
     */
    getPendingDistributions() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, did } = this;
            let assets = [];
            let allFetched = false;
            let start;
            while (!allFetched) {
                const { data, next } = yield this.getHeldAssets({ size: constants_1.MAX_PAGE_SIZE, start });
                start = next ? new bignumber_js_1.default(next) : undefined;
                allFetched = !next;
                assets = [...assets, ...data];
            }
            const distributions = yield this.context.getDividendDistributionsForAssets({ assets: assets });
            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, asset: { 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((0, utils_2.tuple)((0, conversion_1.corporateActionIdentifierToCaId)({ ticker, localId }, context), (0, conversion_1.stringToIdentityId)(did, context)));
                return !(0, conversion_1.boolToBoolean)(holderPaid);
            }));
        });
    }
    // eslint-disable-next-line require-jsdoc
    getSecondaryAccounts(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { did, context, context: { polymeshApi: { query: { identity }, }, }, } = this;
            const assembleResult = ({ secondary_keys: secondaryAccounts, }) => {
                return secondaryAccounts.map(({ signer: rawSigner, permissions }) => ({
                    account: (0, conversion_1.signatoryToAccount)(rawSigner, context),
                    permissions: (0, 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((0, conversion_1.stringToIdentityId)(did, context));
            return !recordSize.isZero();
        });
    }
    /**
     * Return the Identity's DID
     */
    toJson() {
        return this.did;
    }
}
exports.Identity = Identity;
//# sourceMappingURL=index.js.map