"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 }] }, 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
     *
     * @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 { blockNumber, tag, success, size, start, orderBy } = filters;
            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.forEach(({ block_id, extrinsic_idx, address: rawAddress, nonce, module_id, call_id, params, success: txSuccess, spec_version_id, extrinsic_hash, }) => {
                // TODO remove null check once types fixed
                /* eslint-disable @typescript-eslint/no-non-null-assertion */
                data.push({
                    blockNumber: new bignumber_js_1.default(block_id),
                    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 Signer 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 possesses certain Permissions for its corresponding Identity
     */
    hasPermissions(permissions) {
        return __awaiter(this, void 0, void 0, function* () {
            const { tokens, transactions, portfolios } = permissions;
            const { tokens: currentTokens, transactions: currentTransactions, portfolios: currentPortfolios, } = yield this.getPermissions();
            let hasTokens;
            if (currentTokens === null) {
                hasTokens = true;
            }
            else if (tokens === null) {
                hasTokens = false;
            }
            else if (tokens) {
                const { type: tokensType, values: tokensValues } = currentTokens;
                if (tokens.length === 0) {
                    hasTokens = true;
                }
                else {
                    if (tokensType === types_1.PermissionType.Include) {
                        hasTokens = !lodash_1.differenceBy(tokens, tokensValues, 'ticker').length;
                    }
                    else {
                        hasTokens = lodash_1.differenceBy(tokens, tokensValues, 'ticker').length === tokens.length;
                    }
                }
            }
            else {
                hasTokens = true;
            }
            // 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,
            ];
            let hasTransactions;
            if (currentTransactions === null) {
                hasTransactions = true;
            }
            else if (transactions === null) {
                hasTransactions = false;
            }
            else if (transactions) {
                const { type: transactionsType, values: transactionsValues, exceptions = [], } = currentTransactions;
                if (transactions.length === 0) {
                    hasTransactions = true;
                }
                else {
                    if (transactionsType === types_1.PermissionType.Include) {
                        const includedTransactions = lodash_1.differenceWith(lodash_1.union(transactionsValues, exemptedTransactions), exceptions, internal_2.isModuleOrTagMatch);
                        hasTransactions = transactions.every(tag => !!includedTransactions.find(included => internal_2.isModuleOrTagMatch(included, tag)));
                    }
                    else {
                        const excludedTransactions = lodash_1.differenceWith(transactionsValues, exemptedTransactions, exceptions, internal_2.isModuleOrTagMatch);
                        hasTransactions = !transactions.some(tag => !!excludedTransactions.find(excluded => internal_2.isModuleOrTagMatch(excluded, tag)));
                    }
                }
            }
            else {
                hasTransactions = true;
            }
            let hasPortfolios;
            if (currentPortfolios === null) {
                hasPortfolios = true;
            }
            else if (portfolios === null) {
                hasPortfolios = false;
            }
            else if (portfolios) {
                const { type: portfoliosType, values: portfoliosValues } = currentPortfolios;
                if (portfolios.length === 0) {
                    hasPortfolios = true;
                }
                else {
                    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) {
                        hasPortfolios = !lodash_1.differenceWith(portfolios, portfoliosValues, portfolioComparator).length;
                    }
                    else {
                        hasPortfolios =
                            lodash_1.differenceWith(portfolios, portfoliosValues, portfolioComparator).length ===
                                portfolios.length;
                    }
                }
            }
            else {
                hasPortfolios = true;
            }
            return hasTokens && hasTransactions && hasPortfolios;
        });
    }
    /**
     * 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;
//# sourceMappingURL=Account.js.map