"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.Account = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const lodash_1 = require("lodash");
const internal_1 = require("../../internal");
const queries_1 = require("../../middleware/queries");
const types_1 = require("../../types");
const conversion_1 = require("../../utils/conversion");
const internal_2 = require("../../utils/internal");
/**
 * Represents an account in the Polymesh blockchain. Accounts can hold POLYX, control Identities and vote on proposals (among other things)
 */
class Account extends internal_1.Entity {
    /**
     * @hidden
     */
    constructor(identifiers, context) {
        super(identifiers, context);
        const { address } = identifiers;
        internal_2.assertFormatValid(address, context.ss58Format);
        this.address = address;
        this.key = conversion_1.addressToKey(address, context);
        this.authorizations = new internal_1.Authorizations(this, context);
        this.leaveIdentity = internal_2.createProcedureMethod({ getProcedureAndArgs: () => [internal_1.leaveIdentity, { account: this }], voidArgs: true }, context);
    }
    /**
     * @hidden
     * Check if a value is of type [[UniqueIdentifiers]]
     */
    static isUniqueIdentifiers(identifier) {
        const { address } = identifier;
        return typeof address === 'string';
    }
    // eslint-disable-next-line require-jsdoc
    getBalance(callback) {
        const { context, address } = this;
        if (callback) {
            return context.accountBalance(address, callback);
        }
        return context.accountBalance(address);
    }
    // eslint-disable-next-line require-jsdoc
    getSubsidy(callback) {
        const { context, address } = this;
        if (callback) {
            return context.accountSubsidy(address, callback);
        }
        return context.accountSubsidy(address);
    }
    /**
     * Retrieve the Identity associated to this Account (null if there is none)
     */
    getIdentity() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { identity }, }, }, context, address, } = this;
            const identityId = yield identity.keyToIdentityIds(conversion_1.stringToAccountId(address, context));
            if (identityId.isEmpty) {
                return null;
            }
            const did = conversion_1.identityIdToString(identityId);
            return new internal_1.Identity({ did }, context);
        });
    }
    /**
     * Retrieve a list of transactions signed by this Account. Can be filtered using parameters
     *
     * @note if both `blockNumber` and `blockHash` are passed, only `blockNumber` is taken into account
     *
     * @param filters.tag - tag associated with the transaction
     * @param filters.success - whether the transaction was successful or not
     * @param filters.size - page size
     * @param filters.start - page offset
     *
     * @note uses the middleware
     */
    getTransactionHistory(filters = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, address } = this;
            const { tag, success, size, start, orderBy, blockHash } = filters;
            let { blockNumber } = filters;
            if (!blockNumber && blockHash) {
                const { block: { header: { number }, }, } = yield context.polymeshApi.rpc.chain.getBlock(conversion_1.stringToHash(blockHash, context));
                blockNumber = conversion_1.u32ToBigNumber(number.unwrap());
            }
            let moduleId;
            let callId;
            if (tag) {
                ({ moduleId, callId } = conversion_1.txTagToExtrinsicIdentifier(tag));
            }
            /* eslint-disable @typescript-eslint/naming-convention */
            const result = yield context.queryMiddleware(queries_1.transactions({
                block_id: blockNumber ? blockNumber.toNumber() : undefined,
                address: conversion_1.addressToKey(address, context),
                module_id: moduleId,
                call_id: callId,
                success,
                count: size,
                skip: start,
                orderBy,
            }));
            const { data: { transactions: { items: transactionList, totalCount: count }, }, } = result;
            const data = transactionList.map(({ block_id, extrinsic_idx, address: rawAddress, nonce, module_id, call_id, params, success: txSuccess, spec_version_id, extrinsic_hash, block, }) => {
                // TODO remove null check once types fixed
                /* eslint-disable @typescript-eslint/no-non-null-assertion */
                return {
                    blockNumber: new bignumber_js_1.default(block_id),
                    blockHash: block.hash,
                    extrinsicIdx: extrinsic_idx,
                    address: rawAddress !== null && rawAddress !== void 0 ? rawAddress : null,
                    nonce: nonce,
                    txTag: conversion_1.extrinsicIdentifierToTxTag({ moduleId: module_id, callId: call_id }),
                    params,
                    success: !!txSuccess,
                    specVersionId: spec_version_id,
                    extrinsicHash: extrinsic_hash,
                };
                /* eslint-enabled @typescript-eslint/no-non-null-assertion */
            });
            /* eslint-enable @typescript-eslint/naming-convention */
            const next = internal_2.calculateNextKey(count, size, start);
            return {
                data,
                next,
                count,
            };
        });
    }
    /**
     * Check whether this Account is frozen. If frozen, it cannot perform any action until the primary key of the Identity unfreezes all secondary keys
     */
    isFrozen() {
        return __awaiter(this, void 0, void 0, function* () {
            const { address } = this;
            const identity = yield this.getIdentity();
            if (identity === null) {
                return false;
            }
            const primaryKey = yield identity.getPrimaryKey();
            if (address === primaryKey.address) {
                return false;
            }
            return identity.areSecondaryKeysFrozen();
        });
    }
    /**
     * Retrieve the Permissions this Account has as a Signing Key for its corresponding Identity
     */
    getPermissions() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context, address } = this;
            const currentIdentity = yield context.getCurrentIdentity();
            const [primaryKey, secondaryKeys] = yield Promise.all([
                currentIdentity.getPrimaryKey(),
                currentIdentity.getSecondaryKeys(),
            ]);
            if (address === primaryKey.address) {
                return {
                    tokens: null,
                    transactions: null,
                    transactionGroups: [],
                    portfolios: null,
                };
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const key = secondaryKeys.find(({ signer }) => address === conversion_1.signerToString(signer));
            return key.permissions;
        });
    }
    /**
     * Check if this Account posseses certain Permissions to act on behalf of its corresponding Identity
     *
     * @return which permissions the Account is missing (if any) and the final result
     */
    checkPermissions(permissions) {
        return __awaiter(this, void 0, void 0, function* () {
            const { tokens, transactions, portfolios } = permissions;
            const { tokens: currentTokens, transactions: currentTransactions, portfolios: currentPortfolios, } = yield this.getPermissions();
            const missingPermissions = {};
            const missingTokenPermissions = getMissingTokenPermissions(tokens, currentTokens);
            const hasTokens = missingTokenPermissions === undefined;
            if (!hasTokens) {
                missingPermissions.tokens = missingTokenPermissions;
            }
            const missingTransactionPermissions = getMissingTransactionPermissions(transactions, currentTransactions);
            const hasTransactions = missingTransactionPermissions === undefined;
            if (!hasTransactions) {
                missingPermissions.transactions = missingTransactionPermissions;
            }
            const missingPortfolioPermissions = getMissingPortfolioPermissions(portfolios, currentPortfolios);
            const hasPortfolios = missingPortfolioPermissions === undefined;
            if (!hasPortfolios) {
                missingPermissions.portfolios = missingPortfolioPermissions;
            }
            const result = hasTokens && hasTransactions && hasPortfolios;
            if (result) {
                return { result };
            }
            return {
                result,
                missingPermissions,
            };
        });
    }
    /**
     * Check if this Account possesses certain Permissions to act on behalf of its corresponding Identity
     *
     * @deprecated in favor of `checkPermissions`
     */
    hasPermissions(permissions) {
        return __awaiter(this, void 0, void 0, function* () {
            const { result } = yield this.checkPermissions(permissions);
            return result;
        });
    }
    /**
     * Determine whether this Account exists on chain
     */
    exists() {
        return __awaiter(this, void 0, void 0, function* () {
            return true;
        });
    }
    /**
     * Return the Account's address
     */
    toJson() {
        return this.address;
    }
}
exports.Account = Account;
/**
 * @hidden
 *
 * Calculate the difference between the required Token permissions and the current ones
 */
function getMissingTokenPermissions(requiredPermissions, currentPermissions) {
    if (currentPermissions === null) {
        return undefined;
    }
    else if (requiredPermissions === null) {
        return null;
    }
    else if (requiredPermissions) {
        const { type: tokensType, values: tokensValues } = currentPermissions;
        if (requiredPermissions.length) {
            let missingPermissions;
            if (tokensType === types_1.PermissionType.Include) {
                missingPermissions = lodash_1.differenceBy(requiredPermissions, tokensValues, 'ticker');
            }
            else {
                missingPermissions = lodash_1.intersectionBy(requiredPermissions, tokensValues, 'ticker');
            }
            if (missingPermissions.length) {
                return missingPermissions;
            }
        }
    }
    return undefined;
}
/**
 * @hidden
 *
 * Calculate the difference between the required Transaction permissions and the current ones
 */
function getMissingTransactionPermissions(requiredPermissions, currentPermissions) {
    // these transactions are allowed to any account, independent of permissions
    const exemptedTransactions = [
        types_1.TxTags.identity.LeaveIdentityAsKey,
        types_1.TxTags.identity.JoinIdentityAsKey,
        types_1.TxTags.multiSig.AcceptMultisigSignerAsKey,
        ...lodash_1.difference(Object.values(types_1.TxTags.balances), [
            types_1.TxTags.balances.DepositBlockRewardReserveBalance,
            types_1.TxTags.balances.BurnAccountBalance,
        ]),
        types_1.ModuleName.Staking,
        types_1.ModuleName.Sudo,
        types_1.ModuleName.Session,
        types_1.ModuleName.Authorship,
        types_1.ModuleName.Babe,
        types_1.ModuleName.Grandpa,
        types_1.ModuleName.ImOnline,
        types_1.ModuleName.Indices,
        types_1.ModuleName.Scheduler,
        types_1.ModuleName.System,
        types_1.ModuleName.Timestamp,
    ];
    if (currentPermissions === null) {
        return undefined;
    }
    else if (requiredPermissions === null) {
        return null;
    }
    else if (requiredPermissions) {
        const { type: transactionsType, values: transactionsValues, exceptions = [], } = currentPermissions;
        if (requiredPermissions.length) {
            let missingPermissions;
            const exceptionMatches = lodash_1.intersection(requiredPermissions, exceptions);
            if (transactionsType === types_1.PermissionType.Include) {
                const includedTransactions = lodash_1.union(transactionsValues, exemptedTransactions);
                missingPermissions = lodash_1.union(lodash_1.differenceWith(requiredPermissions, includedTransactions, internal_2.isModuleOrTagMatch), exceptionMatches);
            }
            else {
                const excludedTransactions = lodash_1.differenceWith(transactionsValues, exemptedTransactions, (tx, exemptedTx) => {
                    /*
                     * if the exclusion is a module, we only remove it from the list if the module itself is present in `exemptedTransactions`.
                     *   Otherwise, if, for example, `transactionsValues` contains `ModuleName.Identity`,
                     *   since `exemptedTransactions` contains `TxTags.identity.LeaveIdentityAsKey`, we would be
                     *   removing the entire Identity module from the result, which doesn't make sense
                     */
                    if (!tx.includes('.')) {
                        return tx === exemptedTx;
                    }
                    return internal_2.isModuleOrTagMatch(tx, exemptedTx);
                });
                missingPermissions = lodash_1.difference(lodash_1.intersectionWith(requiredPermissions, excludedTransactions, internal_2.isModuleOrTagMatch), exceptionMatches);
            }
            if (missingPermissions.length) {
                return missingPermissions;
            }
        }
    }
    return undefined;
}
/**
 * @hidden
 *
 * Calculate the difference between the required Transaction permissions and the current ones
 */
function getMissingPortfolioPermissions(requiredPermissions, currentPermissions) {
    if (currentPermissions === null) {
        return undefined;
    }
    else if (requiredPermissions === null) {
        return null;
    }
    else if (requiredPermissions) {
        const { type: portfoliosType, values: portfoliosValues } = currentPermissions;
        if (requiredPermissions.length) {
            let missingPermissions;
            const portfolioComparator = (a, b) => {
                const aId = conversion_1.portfolioToPortfolioId(a);
                const bId = conversion_1.portfolioToPortfolioId(b);
                return lodash_1.isEqual(aId, bId);
            };
            if (portfoliosType === types_1.PermissionType.Include) {
                missingPermissions = lodash_1.differenceWith(requiredPermissions, portfoliosValues, portfolioComparator);
            }
            else {
                missingPermissions = lodash_1.intersectionWith(requiredPermissions, portfoliosValues, portfolioComparator);
            }
            if (missingPermissions.length) {
                return missingPermissions;
            }
        }
    }
    return undefined;
}
//# sourceMappingURL=Account.js.map