"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.investInSto = exports.prepareStorage = exports.getAuthorization = exports.prepareInvestInSto = exports.calculateTierStats = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const internal_1 = require("../../internal");
const types_1 = require("../../types");
const conversion_1 = require("../../utils/conversion");
/**
 * @hidden
 */
const calculateTierStats = (tiers, purchaseAmount, maxPrice) => {
    const { remainingTotal, price: calculatedPrice } = tiers.reduce(({ remainingToPurchase: prevRemainingToPurchase, remainingTotal: prevRemainingTotal, price: prevPrice, }, { remaining, price }) => {
        if (!prevRemainingToPurchase.isZero()) {
            const tierPurchaseAmount = bignumber_js_1.default.minimum(remaining, prevRemainingToPurchase);
            const newRemainingTotal = prevRemainingTotal.plus(tierPurchaseAmount);
            const newPrice = prevPrice.plus(tierPurchaseAmount.multipliedBy(price));
            const newRemainingToPurchase = prevRemainingToPurchase.minus(tierPurchaseAmount);
            const newAvgPrice = newPrice.dividedBy(purchaseAmount.minus(newRemainingToPurchase));
            if (!maxPrice || newAvgPrice.lte(maxPrice)) {
                return {
                    remainingTotal: newRemainingTotal,
                    price: newPrice,
                    remainingToPurchase: newRemainingToPurchase,
                };
            }
        }
        return {
            remainingTotal: prevRemainingTotal,
            price: prevPrice,
            remainingToPurchase: prevRemainingToPurchase,
        };
    }, {
        remainingTotal: new bignumber_js_1.default(0),
        price: new bignumber_js_1.default(0),
        remainingToPurchase: purchaseAmount,
    });
    return {
        remainingTotal,
        price: calculatedPrice,
    };
};
exports.calculateTierStats = calculateTierStats;
/**
 * @hidden
 */
function prepareInvestInSto(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context: { polymeshApi: { tx: { sto: txSto }, }, }, context, storage: { purchasePortfolioId, fundingPortfolioId }, } = this;
        const { ticker, id, purchaseAmount, maxPrice } = args;
        const sto = new internal_1.Sto({ ticker, id }, context);
        const portfolio = conversion_1.portfolioIdToPortfolio(fundingPortfolioId, context);
        const { status: { sale, timing }, minInvestment, tiers, raisingCurrency, } = yield sto.details();
        const [{ free: freeTokenBalance }] = yield portfolio.getTokenBalances({
            tokens: [raisingCurrency],
        });
        if (sale !== types_1.StoSaleStatus.Live || timing !== types_1.StoTimingStatus.Started) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'The STO is not accepting investments at the moment',
            });
        }
        const { remainingTotal, price: priceTotal } = exports.calculateTierStats(tiers, purchaseAmount, maxPrice);
        if (priceTotal.lt(minInvestment)) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: 'Minimum investment not reached',
                data: { priceTotal },
            });
        }
        if (freeTokenBalance.lt(priceTotal)) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.InsufficientBalance,
                message: 'The Portfolio does not have enough free balance for this investment',
                data: { free: freeTokenBalance, priceTotal },
            });
        }
        if (remainingTotal.lt(purchaseAmount)) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: `The STO does not have enough remaining tokens${maxPrice ? ' below the stipulated max price' : ''}`,
            });
        }
        this.addTransaction(txSto.invest, {}, conversion_1.portfolioIdToMeshPortfolioId(purchasePortfolioId, context), conversion_1.portfolioIdToMeshPortfolioId(fundingPortfolioId, context), conversion_1.stringToTicker(ticker, context), conversion_1.numberToU64(id, context), conversion_1.numberToBalance(purchaseAmount, context), maxPrice ? conversion_1.numberToBalance(maxPrice, context) : null, null);
    });
}
exports.prepareInvestInSto = prepareInvestInSto;
/**
 * @hidden
 */
function getAuthorization() {
    const { storage: { purchasePortfolioId, fundingPortfolioId }, context, } = this;
    return {
        roles: [
            { type: types_1.RoleType.PortfolioCustodian, portfolioId: purchasePortfolioId },
            { type: types_1.RoleType.PortfolioCustodian, portfolioId: fundingPortfolioId },
        ],
        permissions: {
            transactions: [types_1.TxTags.sto.Invest],
            tokens: [],
            portfolios: [
                conversion_1.portfolioIdToPortfolio(purchasePortfolioId, context),
                conversion_1.portfolioIdToPortfolio(fundingPortfolioId, context),
            ],
        },
    };
}
exports.getAuthorization = getAuthorization;
/**
 * @hidden
 */
function prepareStorage({ purchasePortfolio, fundingPortfolio }) {
    return {
        purchasePortfolioId: conversion_1.portfolioLikeToPortfolioId(purchasePortfolio),
        fundingPortfolioId: conversion_1.portfolioLikeToPortfolioId(fundingPortfolio),
    };
}
exports.prepareStorage = prepareStorage;
/**
 * @hidden
 */
const investInSto = () => new internal_1.Procedure(prepareInvestInSto, getAuthorization, prepareStorage);
exports.investInSto = investInSto;
//# sourceMappingURL=investInSto.js.map