"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.AssetPermissions = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const bluebird_1 = __importDefault(require("bluebird"));
const internal_1 = require("../../../internal");
const queries_1 = require("../../../middleware/queries");
const types_1 = require("../../../middleware/types");
const types_2 = require("../../../types");
const constants_1 = require("../../../utils/constants");
const conversion_1 = require("../../../utils/conversion");
const internal_2 = require("../../../utils/internal");
/**
 * @hidden
 *
 * Check whether a tag is "present" in (represented by) a set of values and exceptions, using the following criteria:
 *
 * - if type is include:
 *   the passed tags are in the values array AND are not in the exceptions array (isInValues && !isInExceptions)
 * - if type is exclude:
 *   the passed tags are not in the values array OR are in the exceptions array (!isInValues || isInExceptions)
 */
function isPresent(tag, values, exceptions, flipResult) {
    const isInValues = values.some(value => (0, internal_2.isModuleOrTagMatch)(value, tag));
    const isInExceptions = !!(exceptions === null || exceptions === void 0 ? void 0 : exceptions.includes(tag));
    const result = isInValues && !isInExceptions;
    return flipResult ? result : !result;
}
/**
 * Handles all Asset Permissions (External Agents) related functionality on the Identity side
 */
class AssetPermissions extends internal_1.Namespace {
    /**
     * @hidden
     */
    constructor(parent, context) {
        super(parent, context);
        this.waive = (0, internal_2.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.waivePermissions, Object.assign(Object.assign({}, args), { identity: parent })] }, context);
        this.setGroup = (0, internal_2.createProcedureMethod)({ getProcedureAndArgs: args => [internal_1.setPermissionGroup, Object.assign(Object.assign({}, args), { identity: parent })] }, context);
    }
    /**
     * Retrieve all the Assets over which this Identity has permissions, with the corresponding Permission Group
     */
    get() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { externalAgents }, }, }, parent: { did }, context, } = this;
            const rawDid = (0, conversion_1.stringToIdentityId)(did, context);
            const assetEntries = yield externalAgents.agentOf.entries(rawDid);
            return bluebird_1.default.map(assetEntries, ([key]) => __awaiter(this, void 0, void 0, function* () {
                const ticker = (0, conversion_1.tickerToString)(key.args[1]);
                const asset = new internal_1.Asset({ ticker }, context);
                const group = yield this.getGroup({ asset });
                return {
                    asset,
                    group,
                };
            }));
        });
    }
    /**
     * Check whether this Identity has specific transaction Permissions over an Asset
     */
    checkPermissions(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { externalAgents }, }, }, context, parent: { did }, } = this;
            const { asset, transactions } = args;
            if ((transactions === null || transactions === void 0 ? void 0 : transactions.length) === 0) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.ValidationError,
                    message: 'Cannot check Permissions for an empty transaction array',
                });
            }
            const ticker = (0, internal_2.getTicker)(asset);
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            const groupOption = yield externalAgents.groupOfAgent(rawTicker, (0, conversion_1.stringToIdentityId)(did, context));
            if (groupOption.isNone) {
                return {
                    missingPermissions: transactions,
                    result: false,
                    message: 'The Identity is not an Agent for the Asset',
                };
            }
            const group = groupOption.unwrap();
            if (group.isFull) {
                return {
                    result: true,
                };
            }
            let missingPermissions;
            if (group.isCustom) {
                const groupId = group.asCustom;
                const groupPermissionsOption = yield externalAgents.groupPermissions(rawTicker, groupId);
                const permissions = (0, conversion_1.extrinsicPermissionsToTransactionPermissions)(groupPermissionsOption.unwrap());
                if (permissions === null) {
                    return {
                        result: true,
                    };
                }
                if (transactions === null) {
                    return {
                        result: false,
                        missingPermissions: null,
                    };
                }
                const { type, exceptions, values } = permissions;
                const isInclude = type === types_2.PermissionType.Include;
                missingPermissions = transactions.filter(tag => !isPresent(tag, values, exceptions, isInclude));
            }
            if (transactions === null) {
                return {
                    result: false,
                    missingPermissions: null,
                };
            }
            /*
             * Not authorized:
             *   - externalAgents
             */
            if (group.isExceptMeta) {
                missingPermissions = transactions.filter(tag => tag.split('.')[0] === types_2.ModuleName.ExternalAgents);
            }
            /*
             * Authorized:
             *   - asset.issue
             *   - asset.redeem
             *   - asset.controllerTransfer
             *   - sto (except for sto.invest)
             */
            if (group.isPolymeshV1Pia) {
                missingPermissions = transactions.filter(tag => {
                    const isSto = tag.split('.')[0] === types_2.ModuleName.Sto && tag !== types_2.TxTags.sto.Invest;
                    const isAsset = [
                        types_2.TxTags.asset.Issue,
                        types_2.TxTags.asset.Redeem,
                        types_2.TxTags.asset.ControllerTransfer,
                    ].includes(tag);
                    return !isSto && !isAsset;
                });
            }
            /*
             * Authorized:
             *   - corporateAction
             *   - corporateBallot
             *   - capitalDistribution
             */
            if (group.isPolymeshV1Caa) {
                missingPermissions = transactions.filter(tag => ![
                    types_2.ModuleName.CorporateAction,
                    types_2.ModuleName.CorporateBallot,
                    types_2.ModuleName.CapitalDistribution,
                ].includes(tag.split('.')[0]));
            }
            /* eslint-disable @typescript-eslint/no-non-null-assertion */
            if (missingPermissions.length) {
                return {
                    missingPermissions: missingPermissions,
                    result: false,
                };
            }
            /* eslint-enable @typescript-eslint/no-non-null-assertion */
            return {
                result: true,
            };
        });
    }
    /**
     * Check whether this Identity has specific transaction Permissions over an Asset
     *
     * @deprecated in favor of `checkPermissions`
     */
    hasPermissions(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { result } = yield this.checkPermissions(args);
            return result;
        });
    }
    /**
     * Retrieve this Identity's Permission Group for a specific Asset
     */
    getGroup({ asset, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { externalAgents }, }, }, context, parent: { did }, } = this;
            const ticker = (0, internal_2.getTicker)(asset);
            const rawTicker = (0, conversion_1.stringToTicker)(ticker, context);
            const rawIdentityId = (0, conversion_1.stringToIdentityId)(did, context);
            const rawGroupPermissions = yield externalAgents.groupOfAgent(rawTicker, rawIdentityId);
            if (rawGroupPermissions.isNone) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.DataUnavailable,
                    message: 'This Identity is no longer an Agent for this Asset',
                });
            }
            const agentGroup = rawGroupPermissions.unwrap();
            return (0, conversion_1.agentGroupToPermissionGroup)(agentGroup, ticker, context);
        });
    }
    /**
     * Retrieve the identifier data (block number, date and event index) of the event that was emitted when this Identity was enabled/added as
     *   an Agent with permissions over a specific Asset
     *
     * @note uses the middleware
     * @note there is a possibility that the data is not ready by the time it is requested. In that case, `null` is returned
     */
    enabledAt({ asset }) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context } = this;
            const ticker = (0, internal_2.getTicker)(asset);
            const { data: { eventByIndexedArgs: event }, } = yield context.queryMiddleware((0, queries_1.eventByIndexedArgs)({
                // cSpell: disable-next-line
                moduleId: types_1.ModuleIdEnum.Externalagents,
                eventId: types_1.EventIdEnum.AgentAdded,
                eventArg1: (0, internal_2.padString)(ticker, constants_1.MAX_TICKER_LENGTH),
            }));
            return (0, internal_2.optionize)(conversion_1.middlewareEventToEventIdentifier)(event);
        });
    }
    /**
     * Retrieve all Events triggered by Operations this Identity has performed on a specific Asset
     *
     * @param opts.moduleId - filters results by module
     * @param opts.eventId - filters results by event
     * @param opts.size - page size
     * @param opts.start - page offset
     *
     * @note uses the middleware
     * @note supports pagination
     */
    getOperationHistory(opts) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { system }, }, }, context, parent: { did }, } = this;
            /* eslint-disable @typescript-eslint/naming-convention */
            const { asset, moduleId: pallet_name, eventId: event_id, size, start } = opts;
            const ticker = (0, internal_2.getTicker)(asset);
            const result = yield context.queryMiddleware((0, queries_1.tickerExternalAgentActions)({
                ticker,
                caller_did: did,
                pallet_name,
                event_id,
                count: size === null || size === void 0 ? void 0 : size.toNumber(),
                skip: start === null || start === void 0 ? void 0 : start.toNumber(),
            }));
            /* eslint-enable @typescript-eslint/naming-convention */
            const { data: { tickerExternalAgentActions: tickerExternalAgentActionsResult }, } = result;
            const { items, totalCount } = tickerExternalAgentActionsResult;
            const multiParams = [];
            const data = [];
            items.forEach(item => {
                const { block_id: blockId, datetime, event_idx: eventIndex } = item;
                const blockNumber = new bignumber_js_1.default(blockId);
                multiParams.push((0, conversion_1.bigNumberToU32)(blockNumber, context));
                data.push({
                    blockNumber,
                    blockDate: new Date(`${datetime}Z`),
                    eventIndex: new bignumber_js_1.default(eventIndex),
                });
            });
            let hashes = [];
            if (multiParams.length) {
                hashes = yield system.blockHash.multi(multiParams);
            }
            const count = new bignumber_js_1.default(totalCount);
            const next = (0, internal_2.calculateNextKey)(count, size, start);
            return {
                data: data.map((event, index) => (Object.assign(Object.assign({}, event), { blockHash: (0, conversion_1.hashToString)(hashes[index]) }))),
                next,
                count,
            };
        });
    }
}
exports.AssetPermissions = AssetPermissions;
//# sourceMappingURL=AssetPermissions.js.map