"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.setTransferRestrictions = exports.prepareStorage = exports.getAuthorization = exports.prepareSetTransferRestrictions = void 0;
const bluebird_1 = __importDefault(require("bluebird"));
const lodash_1 = require("lodash");
const internal_1 = require("../../internal");
const types_1 = require("../../types");
const utils_1 = require("../../types/utils");
const conversion_1 = require("../../utils/conversion");
/**
 * @hidden
 */
function prepareSetTransferRestrictions(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context: { polymeshApi: { tx: { statistics }, consts, }, }, storage: { restrictionsToAdd, restrictionsToRemove, exemptionsToAdd, exemptionsToRemove, occupiedSlots, exemptionsRepeated, }, } = this;
        const { restrictions: { length: newRestrictionAmount }, } = args;
        if (exemptionsRepeated) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: 'One or more restrictions have repeated exempted Scope IDs/Identities',
            });
        }
        const restrictionsToAddAmount = restrictionsToAdd.length;
        const restrictionsToRemoveAmount = restrictionsToRemove.length;
        const exemptionsToAddAmount = exemptionsToAdd.length;
        const exemptionsToRemoveAmount = exemptionsToRemove.length;
        if (!restrictionsToAddAmount &&
            !restrictionsToRemoveAmount &&
            !exemptionsToAddAmount &&
            !exemptionsToRemoveAmount) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: newRestrictionAmount
                    ? 'The supplied restrictions are already in place'
                    : 'There are no restrictions to remove',
            });
        }
        const maxTransferManagers = conversion_1.u32ToBigNumber(consts.statistics.maxTransferManagersPerAsset).toNumber();
        const finalCount = occupiedSlots + newRestrictionAmount;
        if (finalCount >= maxTransferManagers) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.ValidationError,
                message: 'Cannot set more Transfer Restrictions than there are slots available',
                data: {
                    availableSlots: maxTransferManagers - occupiedSlots,
                },
            });
        }
        if (restrictionsToRemoveAmount) {
            this.addBatchTransaction(statistics.removeTransferManager, {}, restrictionsToRemove);
        }
        if (restrictionsToAddAmount) {
            this.addBatchTransaction(statistics.addTransferManager, {}, restrictionsToAdd);
        }
        if (exemptionsToRemoveAmount) {
            this.addBatchTransaction(statistics.removeExemptedEntities, {}, exemptionsToRemove);
        }
        if (exemptionsToAddAmount) {
            this.addBatchTransaction(statistics.addExemptedEntities, {}, exemptionsToAdd);
        }
        return finalCount;
    });
}
exports.prepareSetTransferRestrictions = prepareSetTransferRestrictions;
/**
 * @hidden
 */
function getAuthorization({ ticker }) {
    const { restrictionsToRemove, restrictionsToAdd, exemptionsToAdd, exemptionsToRemove, } = this.storage;
    const transactions = [];
    if (restrictionsToAdd.length) {
        transactions.push(types_1.TxTags.statistics.AddTransferManager);
    }
    if (restrictionsToRemove.length) {
        transactions.push(types_1.TxTags.statistics.RemoveTransferManager);
    }
    if (exemptionsToAdd.length) {
        transactions.push(types_1.TxTags.statistics.AddExemptedEntities);
    }
    if (exemptionsToRemove.length) {
        transactions.push(types_1.TxTags.statistics.RemoveExemptedEntities);
    }
    return {
        permissions: {
            tokens: [new internal_1.SecurityToken({ ticker }, this.context)],
            transactions,
            portfolios: [],
        },
    };
}
exports.getAuthorization = getAuthorization;
/**
 * @hidden
 *
 * Merge an array of Identity IDs and Scope IDs into an array of only Scope IDs
 *
 * @note fetches missing scope IDs from the chain
 */
const getScopeIds = (identities, scopeIds, context, ticker) => __awaiter(void 0, void 0, void 0, function* () {
    const identityScopeIds = yield bluebird_1.default.map(identities, (value) => __awaiter(void 0, void 0, void 0, function* () {
        let identity;
        if (typeof value === 'string') {
            identity = new internal_1.Identity({ did: value }, context);
        }
        else {
            identity = value;
        }
        return identity.getScopeId({ token: ticker });
    }));
    return [...scopeIds, ...identityScopeIds];
});
/**
 * @hidden
 */
function prepareStorage(args) {
    return __awaiter(this, void 0, void 0, function* () {
        const { context } = this;
        const { ticker } = args;
        const rawTicker = conversion_1.stringToTicker(ticker, context);
        const { transferRestrictions: { count, percentage }, } = new internal_1.SecurityToken({ ticker }, context);
        const [{ restrictions: currentCountRestrictions }, { restrictions: currentPercentageRestrictions },] = yield Promise.all([count.get(), percentage.get()]);
        const currentRestrictions = [];
        const currentExemptions = [];
        const toAddRestrictions = [];
        const toAddExemptionPromises = [];
        let occupiedSlots = currentCountRestrictions.length + currentPercentageRestrictions.length;
        if (args.type === types_1.TransferRestrictionType.Count) {
            args.restrictions.forEach(({ exemptedScopeIds = [], exemptedIdentities = [], count: value }) => {
                const restriction = { type: types_1.TransferRestrictionType.Count, value };
                toAddRestrictions.push(restriction);
                toAddExemptionPromises.push(utils_1.tuple(restriction, getScopeIds(exemptedIdentities, exemptedScopeIds, context, ticker)));
            });
            currentCountRestrictions.forEach(({ exemptedScopeIds = [], count: value }) => {
                const restriction = { type: types_1.TransferRestrictionType.Count, value };
                currentRestrictions.push(restriction);
                currentExemptions.push(utils_1.tuple(restriction, exemptedScopeIds));
            });
        }
        else {
            args.restrictions.forEach(({ exemptedIdentities = [], exemptedScopeIds = [], percentage: value }) => {
                const restriction = { type: types_1.TransferRestrictionType.Percentage, value };
                toAddRestrictions.push(restriction);
                toAddExemptionPromises.push(utils_1.tuple(restriction, getScopeIds(exemptedIdentities, exemptedScopeIds, context, ticker)));
            });
            currentPercentageRestrictions.forEach(({ exemptedScopeIds = [], percentage: value }) => {
                const restriction = { type: types_1.TransferRestrictionType.Percentage, value };
                currentRestrictions.push(restriction);
                currentExemptions.push(utils_1.tuple(restriction, exemptedScopeIds));
            });
        }
        const toAddExemptions = yield bluebird_1.default.map(toAddExemptionPromises, ([restriction, exemptedPromise]) => __awaiter(this, void 0, void 0, function* () {
            const exempted = yield exemptedPromise;
            return utils_1.tuple(restriction, exempted);
        }));
        const newRestrictions = lodash_1.differenceWith(toAddRestrictions, currentRestrictions, lodash_1.isEqual);
        const toRemoveRestrictions = lodash_1.differenceWith(currentRestrictions, toAddRestrictions, lodash_1.isEqual);
        const transformRestriction = (restriction) => utils_1.tuple(rawTicker, conversion_1.transferRestrictionToTransferManager(restriction, context));
        const restrictionsToRemove = toRemoveRestrictions.map(transformRestriction);
        const restrictionsToAdd = newRestrictions.map(transformRestriction);
        occupiedSlots -= currentRestrictions.length;
        const newExemptions = [];
        const toRemoveExemptions = [];
        toAddExemptions.forEach(exemption => {
            const [restriction, exempted] = exemption;
            if (newRestrictions.find(res => lodash_1.isEqual(restriction, res)) && exempted.length) {
                newExemptions.push(exemption);
                return;
            }
            const currentExemption = currentExemptions.find(([res]) => lodash_1.isEqual(res, restriction));
            const currentExempted = (currentExemption === null || currentExemption === void 0 ? void 0 : currentExemption[1]) || [];
            // scope IDs that weren't exempted before for that restriction
            const newExempted = lodash_1.difference(exempted, currentExempted);
            if (newExempted.length) {
                newExemptions.push(utils_1.tuple(restriction, newExempted));
            }
            // scope IDs that will no longer be exempted for that restriction
            const toRemoveExempted = lodash_1.difference(currentExempted, exempted);
            if (toRemoveExempted.length) {
                toRemoveExemptions.push(utils_1.tuple(restriction, toRemoveExempted));
            }
        });
        // also remove all exemptions of the restrictions that will be removed
        toRemoveRestrictions.forEach(restriction => {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const [, currentExempted] = currentExemptions.find(([res]) => lodash_1.isEqual(res, restriction));
            if (currentExempted.length) {
                toRemoveExemptions.push(utils_1.tuple(restriction, currentExempted));
            }
        });
        const exemptionsRepeated = lodash_1.some(newExemptions, ([, scopeIds]) => lodash_1.uniq(scopeIds).length !== scopeIds.length);
        const transformExemptions = ([restriction, scopeIds]) => utils_1.tuple(rawTicker, conversion_1.transferRestrictionToTransferManager(restriction, context), scopeIds.map(scopeId => conversion_1.stringToScopeId(scopeId, context)));
        const exemptionsToAdd = newExemptions.map(transformExemptions);
        const exemptionsToRemove = toRemoveExemptions.map(transformExemptions);
        return {
            restrictionsToRemove,
            restrictionsToAdd,
            exemptionsToAdd,
            exemptionsToRemove,
            occupiedSlots,
            exemptionsRepeated,
        };
    });
}
exports.prepareStorage = prepareStorage;
/**
 * @hidden
 */
const setTransferRestrictions = () => new internal_1.Procedure(prepareSetTransferRestrictions, getAuthorization, prepareStorage);
exports.setTransferRestrictions = setTransferRestrictions;
//# sourceMappingURL=setTransferRestrictions.js.map