"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.addTransferRestriction = exports.prepareStorage = exports.getAuthorization = exports.prepareAddTransferRestriction = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const internal_1 = require("../../internal");
const types_1 = require("../../types");
const internal_2 = require("../../types/internal");
const conversion_1 = require("../../utils/conversion");
const internal_3 = require("../../utils/internal");
/**
 * @hidden
 */
function prepareAddTransferRestriction(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context: { polymeshApi: { tx: { statistics }, query, consts, }, }, storage: { needStat, currentStats, currentRestrictions }, context, } = this;
        const { ticker, exemptedIdentities = [], type } = args;
        const tickerKey = (0, conversion_1.stringToTickerKey)(ticker, context);
        const maxConditions = (0, conversion_1.u32ToBigNumber)(consts.statistics.maxTransferConditionsPerAsset);
        const restrictionAmount = new bignumber_js_1.default(currentRestrictions.length);
        if (restrictionAmount.gte(maxConditions)) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.LimitExceeded,
                message: 'Transfer Restriction limit reached',
                data: { limit: maxConditions },
            });
        }
        let value;
        let chainType;
        if (type === types_1.TransferRestrictionType.Count) {
            value = args.count;
            chainType = types_1.TransferRestrictionType.Count;
        }
        else {
            value = args.percentage;
            chainType = types_1.TransferRestrictionType.Percentage;
        }
        const exists = !!currentRestrictions.find(transferRestriction => {
            if (transferRestriction.isMaxInvestorCount && type === types_1.TransferRestrictionType.Count) {
                const currentCount = (0, conversion_1.u64ToBigNumber)(transferRestriction.asMaxInvestorCount);
                return currentCount.eq(value);
            }
            else if (transferRestriction.isMaxInvestorOwnership &&
                type === types_1.TransferRestrictionType.Percentage) {
                const currentOwnership = (0, conversion_1.permillToBigNumber)(transferRestriction.asMaxInvestorOwnership);
                return currentOwnership.eq(value);
            }
            return false;
        });
        if (exists) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.NoDataChange,
                message: 'Cannot add the same restriction more than once',
            });
        }
        const rawTransferCondition = (0, conversion_1.transferRestrictionToPolymeshTransferCondition)({ type: chainType, value }, context);
        // The chain requires BTreeSets to be sorted or else it will reject the transaction
        const conditions = [...currentRestrictions, rawTransferCondition].sort();
        const transactions = [];
        const op = type === types_1.TransferRestrictionType.Count
            ? (0, conversion_1.statisticsOpTypeToStatOpType)(internal_2.StatisticsOpType.Count, context)
            : (0, conversion_1.statisticsOpTypeToStatOpType)(internal_2.StatisticsOpType.Balance, context);
        if (needStat) {
            const newStat = (0, conversion_1.statisticsOpTypeToStatType)(op, context);
            currentStats.push(newStat);
            currentStats.sort().reverse(); // sort needed as it is a BTreeSet
            transactions.push((0, internal_3.checkTxType)({
                transaction: statistics.setActiveAssetStats,
                args: [tickerKey, currentStats],
            }));
            // If the stat restriction is a Count the actual value needs to be set. This is due to the potentially slow operation of counting all holders for the chain
            if (type === types_1.TransferRestrictionType.Count) {
                const holders = yield query.asset.balanceOf.entries(tickerKey.Ticker);
                const holderCount = new bignumber_js_1.default(holders.length);
                // if an asset has many investors this could be slow and instead should be fetched from SubQuery
                // These should happen near the assets inception, so for now query the chain directly
                const secondKey = (0, conversion_1.createStat2ndKey)(context);
                const stat = (0, conversion_1.statUpdate)(secondKey, (0, conversion_1.bigNumberToU128)(holderCount, context), context);
                const statValue = (0, conversion_1.statUpdatesToBtreeStatUpdate)([stat], context);
                transactions.push((0, internal_3.checkTxType)({
                    transaction: statistics.batchUpdateAssetStats,
                    args: [tickerKey, newStat, statValue],
                }));
            }
        }
        if (exemptedIdentities.length) {
            const exemptedIds = yield (0, internal_3.getExemptedIds)(exemptedIdentities, context, ticker);
            const exemptedScopeIds = exemptedIds.map(entityId => (0, conversion_1.stringToIdentityId)(entityId, context));
            const btreeIds = (0, conversion_1.scopeIdsToBtreeSetIdentityId)(exemptedScopeIds, context);
            const exemptKey = (0, conversion_1.toExemptKey)(tickerKey, op);
            transactions.push((0, internal_3.checkTxType)({
                transaction: statistics.setEntitiesExempt,
                feeMultiplier: new bignumber_js_1.default(exemptedIds.length),
                args: [true, exemptKey, btreeIds],
            }));
        }
        transactions.push((0, internal_3.checkTxType)({
            transaction: statistics.setAssetTransferCompliance,
            args: [tickerKey, conditions],
        }));
        this.addBatchTransaction({ transactions });
        return restrictionAmount.plus(1);
    });
}
exports.prepareAddTransferRestriction = prepareAddTransferRestriction;
/**
 * @hidden
 */
function getAuthorization({ ticker, exemptedIdentities = [] }) {
    const { storage: { needStat }, } = this;
    const transactions = [types_1.TxTags.statistics.SetAssetTransferCompliance];
    if (exemptedIdentities.length) {
        transactions.push(types_1.TxTags.statistics.SetEntitiesExempt);
    }
    if (needStat) {
        transactions.push(types_1.TxTags.statistics.SetActiveAssetStats);
    }
    return {
        permissions: {
            assets: [new internal_1.Asset({ ticker }, this.context)],
            transactions,
            portfolios: [],
        },
    };
}
exports.getAuthorization = getAuthorization;
/**
 * @hidden
 */
function prepareStorage(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context, context: { polymeshApi: { query: { statistics }, }, }, } = this;
        const { ticker, type } = args;
        const tickerKey = (0, conversion_1.stringToTickerKey)(ticker, context);
        const [{ requirements: currentRestrictions }, currentStats] = yield Promise.all([
            statistics.assetTransferCompliances(tickerKey),
            statistics.activeAssetStats(tickerKey),
        ]);
        const needStat = !currentStats.find(s => {
            const stat = (0, conversion_1.meshStatToStatisticsOpType)(s);
            const cmpStat = stat === internal_2.StatisticsOpType.Balance
                ? types_1.TransferRestrictionType.Percentage
                : types_1.TransferRestrictionType.Count;
            return cmpStat === type;
        });
        return {
            currentRestrictions,
            needStat,
            currentStats,
        };
    });
}
exports.prepareStorage = prepareStorage;
/**
 * @hidden
 */
const addTransferRestriction = () => new internal_1.Procedure(prepareAddTransferRestriction, getAuthorization, prepareStorage);
exports.addTransferRestriction = addTransferRestriction;
//# sourceMappingURL=addTransferRestriction.js.map