"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.Portfolio = 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");
const notExistsMessage = "The Portfolio doesn't exist or was removed by its owner";
/**
 * Represents a base Portfolio for a specific Identity in the Polymesh blockchain
 */
class Portfolio extends internal_1.Entity {
    /**
     * @hidden
     */
    constructor(identifiers, context) {
        super(identifiers, context);
        const { did, id } = identifiers;
        this.owner = new internal_1.Identity({ did }, context);
        this._id = id;
        this.setCustodian = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.setCustodian, Object.assign(Object.assign({}, args), { did, id })] }, context);
        this.moveFunds = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.moveFunds, Object.assign(Object.assign({}, args), { from: this })] }, context);
        this.quitCustody = internal_2.createProcedureMethod({ getProcedureAndArgs: () => [internal_1.quitCustody, { portfolio: this }], voidArgs: true }, context);
    }
    /**
     * @hidden
     * Check if a value is of type [[UniqueIdentifiers]]
     */
    static isUniqueIdentifiers(identifier) {
        const { did, id } = identifier;
        return typeof did === 'string' && (id === undefined || id instanceof bignumber_js_1.default);
    }
    /**
     * Return whether an Identity is the Portfolio owner
     *
     * @param args.identity - defaults to the current Identity
     */
    isOwnedBy(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { owner: { did: ownerDid }, context, } = this;
            const did = yield internal_2.getDid(args === null || args === void 0 ? void 0 : args.identity, context);
            return ownerDid === did;
        });
    }
    /**
     * Return whether an Identity is the Portfolio custodian
     *
     * @param args.identity - optional, defaults to the current Identity
     */
    isCustodiedBy(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context } = this;
            const [portfolioCustodian, targetDid] = yield Promise.all([
                this.getCustodian(),
                internal_2.getDid(args === null || args === void 0 ? void 0 : args.identity, context),
            ]);
            return portfolioCustodian.did === targetDid;
        });
    }
    /**
     * Retrieve the balances of all Assets in this Portfolio
     *
     * @param args.assets - array of Assets (or tickers) for which to fetch balances (optional, all balances are retrieved if not passed)
     */
    getAssetBalances(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { owner: { did }, _id: portfolioId, context: { polymeshApi: { query: { portfolio }, }, }, context, } = this;
            const rawPortfolioId = conversion_1.portfolioIdToMeshPortfolioId({ did, number: portfolioId }, context);
            const [exists, totalBalanceEntries, lockedBalanceEntries] = yield Promise.all([
                this.exists(),
                portfolio.portfolioAssetBalances.entries(rawPortfolioId),
                portfolio.portfolioLockedAssets.entries(rawPortfolioId),
            ]);
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: notExistsMessage,
                });
            }
            const assetBalances = {};
            totalBalanceEntries.forEach(([key, balance]) => {
                const ticker = conversion_1.tickerToString(key.args[1]);
                const total = conversion_1.balanceToBigNumber(balance);
                assetBalances[ticker] = {
                    asset: new internal_1.Asset({ ticker }, context),
                    total,
                    locked: new bignumber_js_1.default(0),
                    free: total,
                };
            });
            lockedBalanceEntries.forEach(([key, balance]) => {
                const ticker = conversion_1.tickerToString(key.args[1]);
                const locked = conversion_1.balanceToBigNumber(balance);
                assetBalances[ticker].locked = locked;
                assetBalances[ticker].free = assetBalances[ticker].total.minus(locked);
            });
            const mask = args === null || args === void 0 ? void 0 : args.assets.map(asset => ({
                total: new bignumber_js_1.default(0),
                locked: new bignumber_js_1.default(0),
                free: new bignumber_js_1.default(0),
                asset: internal_2.getAsset(asset, context),
            }));
            if (mask) {
                return mask.map(portfolioBalance => {
                    var _a;
                    const { asset: { ticker }, } = portfolioBalance;
                    return (_a = assetBalances[ticker]) !== null && _a !== void 0 ? _a : portfolioBalance;
                });
            }
            return lodash_1.values(assetBalances);
        });
    }
    /**
     * Retrieve the custodian Identity of this Portfolio
     *
     * @note if no custodian is set, the owner Identity is returned
     */
    getCustodian() {
        return __awaiter(this, void 0, void 0, function* () {
            const { owner, owner: { did }, _id: portfolioId, context: { polymeshApi: { query: { portfolio }, }, }, context, } = this;
            const rawPortfolioId = conversion_1.portfolioIdToMeshPortfolioId({ did, number: portfolioId }, context);
            const [portfolioCustodian, exists] = yield Promise.all([
                portfolio.portfolioCustodian(rawPortfolioId),
                this.exists(),
            ]);
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: notExistsMessage,
                });
            }
            try {
                const rawIdentityId = portfolioCustodian.unwrap();
                return new internal_1.Identity({ did: conversion_1.identityIdToString(rawIdentityId) }, context);
            }
            catch (_) {
                return owner;
            }
        });
    }
    /**
     * Retrieve a list of transactions where this portfolio was involved. Can be filtered using parameters
     *
     * @param filters.account - account involved in the settlement
     * @param filters.ticker - ticker involved in the transaction
     * @param filters.size - page size
     * @param filters.start - page offset
     *
     * @note supports pagination
     * @note uses the middleware
     */
    getTransactionHistory(filters = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { system }, }, }, context, owner: { did }, _id: portfolioId, } = this;
            const { account, ticker, size, start } = filters;
            const settlementsPromise = context.queryMiddleware(queries_1.settlements({
                identityId: did,
                portfolioNumber: portfolioId ? portfolioId.toString() : null,
                addressFilter: account ? conversion_1.addressToKey(account, context) : undefined,
                tickerFilter: ticker,
                count: size === null || size === void 0 ? void 0 : size.toNumber(),
                skip: start === null || start === void 0 ? void 0 : start.toNumber(),
            }));
            const [result, exists] = yield Promise.all([settlementsPromise, this.exists()]);
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: notExistsMessage,
                });
            }
            const { data: { settlements: settlementsResult }, } = result;
            /* eslint-disable @typescript-eslint/no-non-null-assertion */
            const { items, totalCount } = settlementsResult;
            const multiParams = [];
            const data = [];
            items.forEach(item => {
                const { block_id: blockId, result: status, addresses, legs: settlementLegs } = item;
                const blockNumber = new bignumber_js_1.default(blockId);
                multiParams.push(conversion_1.bigNumberToU32(blockNumber, context));
                data.push({
                    blockNumber,
                    status,
                    accounts: addresses.map(address => new internal_1.Account({ address: conversion_1.keyToAddress('0x' + address, context) }, context)),
                    legs: settlementLegs.map(leg => {
                        return {
                            asset: new internal_1.Asset({ ticker: leg.ticker }, context),
                            amount: new bignumber_js_1.default(leg.amount).shiftedBy(-6),
                            direction: leg.direction,
                            from: conversion_1.middlewarePortfolioToPortfolio(leg.from, context),
                            to: conversion_1.middlewarePortfolioToPortfolio(leg.to, context),
                        };
                    }),
                });
            });
            const count = new bignumber_js_1.default(totalCount);
            /* eslint-enable @typescript-eslint/no-non-null-assertion */
            const next = internal_2.calculateNextKey(count, size, start);
            let hashes = [];
            if (multiParams.length) {
                hashes = yield system.blockHash.multi(multiParams);
            }
            return {
                data: data.map((settlement, index) => (Object.assign(Object.assign({}, settlement), { blockHash: conversion_1.hashToString(hashes[index]) }))),
                next,
                count,
            };
        });
    }
    /**
     * Return the Portfolio ID and owner DID
     */
    toJson() {
        const { _id: id, owner: { did }, } = this;
        const result = {
            did,
        };
        return id ? internal_2.toHumanReadable(Object.assign(Object.assign({}, result), { id })) : result;
    }
}
exports.Portfolio = Portfolio;
//# sourceMappingURL=index.js.map