"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
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.getPortfolioIdByName = exports.hasSameElements = exports.assembleBatchTransactions = exports.getCheckpointValue = exports.conditionsAreEqual = exports.toHumanReadable = exports.isModuleOrTagMatch = exports.optionize = exports.periodComplexity = exports.xor = exports.getAsset = exports.getTicker = exports.assertKeyringFormatValid = exports.assertFormatValid = exports.getCommonKeyring = exports.assertIsPositive = exports.assertIsInteger = exports.createProcedureMethod = exports.calculateNextKey = exports.batchArguments = exports.requestAtBlock = exports.requestPaginated = exports.isPrintableAscii = exports.removePadding = exports.padString = exports.filterEventRecords = exports.unwrapValues = exports.unwrapValue = exports.createClaim = exports.getDid = exports.unserialize = exports.serialize = exports.delay = void 0;
const util_1 = require("@polkadot/util");
const util_crypto_1 = require("@polkadot/util-crypto");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
const lodash_1 = require("lodash");
const internal_1 = require("../internal");
const types_1 = require("../types");
const constants_1 = require("./constants");
const conversion_1 = require("./conversion");
const typeguards_1 = require("./typeguards");
__exportStar(require("../generated/utils"), exports);
/**
 * @hidden
 * Promisified version of a timeout
 *
 * @param amount - time to wait
 */
function delay(amount) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve();
            }, amount);
        });
    });
}
exports.delay = delay;
/**
 * @hidden
 * Convert an entity type and its unique Identifiers to a base64 string
 */
function serialize(entityType, uniqueIdentifiers) {
    return Buffer.from(`${entityType}:${json_stable_stringify_1.default(uniqueIdentifiers)}`).toString('base64');
}
exports.serialize = serialize;
/**
 * @hidden
 * Convert a uuid string to an Identifier object
 */
function unserialize(id) {
    const unserialized = Buffer.from(id, 'base64').toString('utf8');
    const matched = unserialized.match(/^.*?:(.*)/);
    const errorMsg = 'Wrong ID format';
    if (!matched) {
        throw new Error(errorMsg);
    }
    const [, jsonString] = matched;
    try {
        return JSON.parse(jsonString);
    }
    catch (err) {
        throw new Error(errorMsg);
    }
}
exports.unserialize = unserialize;
/**
 * @hidden
 * Extract the DID from an Identity, or return the Current DID if no Identity is passed
 */
function getDid(value, context) {
    return __awaiter(this, void 0, void 0, function* () {
        let did;
        if (value) {
            did = conversion_1.signerToString(value);
        }
        else {
            ({ did } = yield context.getCurrentIdentity());
        }
        return did;
    });
}
exports.getDid = getDid;
/**
 * @hidden
 */
function createClaim(claimType, jurisdiction, middlewareScope, cddId, scopeId) {
    const type = claimType;
    const scope = (middlewareScope ? conversion_1.middlewareScopeToScope(middlewareScope) : {});
    switch (type) {
        case types_1.ClaimType.Jurisdiction: {
            return {
                type,
                // this assertion is necessary because CountryCode is not in the middleware types
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                code: util_1.stringUpperFirst(jurisdiction.toLowerCase()),
                scope,
            };
        }
        case types_1.ClaimType.NoData: {
            return {
                type,
            };
        }
        case types_1.ClaimType.CustomerDueDiligence: {
            return {
                type,
                id: cddId,
            };
        }
        case types_1.ClaimType.InvestorUniqueness: {
            return {
                type,
                scope,
                scopeId: scopeId,
                cddId: cddId,
            };
        }
        case types_1.ClaimType.InvestorUniquenessV2: {
            return {
                type,
                cddId: cddId,
            };
        }
    }
    return { type, scope };
}
exports.createClaim = createClaim;
/**
 * @hidden
 *
 * Unwrap a Post Transaction Value
 */
function unwrapValue(value) {
    if (value instanceof internal_1.PostTransactionValue) {
        return value.value;
    }
    return value;
}
exports.unwrapValue = unwrapValue;
/**
 * @hidden
 *
 * Unwrap all Post Transaction Values present in a tuple
 */
function unwrapValues(values) {
    return values.map(unwrapValue);
}
exports.unwrapValues = unwrapValues;
// TODO @monitz87: use event enum instead of string when it exists
/**
 * @hidden
 * Find every occurrence of a specific event inside a receipt
 *
 * @throws If the event is not found
 */
function filterEventRecords(receipt, mod, eventName) {
    const eventRecords = receipt.filterRecords(mod, eventName);
    if (!eventRecords.length) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.UnexpectedError,
            message: `Event "${mod}.${eventName}" wasn't fired even though the corresponding transaction was completed. Please report this to the Polymath team`,
        });
    }
    return eventRecords.map(eventRecord => eventRecord.event);
}
exports.filterEventRecords = filterEventRecords;
/**
 * @hidden
 */
function padString(value, length) {
    return lodash_1.padEnd(value, length, '\0');
}
exports.padString = padString;
/**
 * @hidden
 */
function removePadding(value) {
    // eslint-disable-next-line no-control-regex
    return value.replace(/\u0000/g, '');
}
exports.removePadding = removePadding;
/**
 * @hidden
 *
 * Return whether the string is fully printable ASCII
 */
function isPrintableAscii(value) {
    // eslint-disable-next-line no-control-regex
    return /^[\x00-\x7F]*$/.test(value);
}
exports.isPrintableAscii = isPrintableAscii;
/**
 * @hidden
 *
 * Makes an entries request to the chain. If pagination options are supplied,
 *  the request will be paginated. Otherwise, all entries will be requested at once
 */
function requestPaginated(query, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const { arg, paginationOpts } = opts;
        let entries;
        let lastKey = null;
        const args = arg ? [arg] : [];
        if (paginationOpts) {
            const { size: pageSize, start: startKey } = paginationOpts;
            entries = yield query.entriesPaged({
                args,
                pageSize: pageSize.toNumber(),
                startKey,
            });
            if (pageSize.eq(entries.length)) {
                lastKey = entries[entries.length - 1][0].toHex();
            }
        }
        else {
            /*
             * NOTE @monitz87: this assertion is required because types
             *   are inconsistent in the polkadot repo
             */
            entries = yield query.entries(...args);
        }
        return {
            entries,
            lastKey,
        };
    });
}
exports.requestPaginated = requestPaginated;
/**
 * @hidden
 *
 * Makes a request to the chain. If a block hash is supplied,
 *   the request will be made at that block. Otherwise, the most recent block will be queried
 */
function requestAtBlock(query, opts, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const { blockHash, args } = opts;
        if (blockHash) {
            if (!context.isArchiveNode) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: 'Cannot query previous blocks in a non-archive node',
                });
            }
            return query.at(blockHash, ...args);
        }
        return query(...args);
    });
}
exports.requestAtBlock = requestAtBlock;
/**
 * @hidden
 *
 * Separates an array into smaller batches
 *
 * @param args - elements to separate
 * @param tag - transaction for which the elements are arguments. This serves to determine the size of the batches. A null value
 *   means that the minimum batch size will be used
 * @param groupByFn - optional function that takes an element and returns a value by which to group the elements.
 *   If supplied, all elements of the same group will be contained in the same batch
 */
function batchArguments(args, tag, groupByFn) {
    var _a;
    const batchLimit = (_a = (tag && constants_1.MAX_BATCH_ELEMENTS[tag])) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_MAX_BATCH_ELEMENTS;
    if (!groupByFn) {
        return lodash_1.chunk(args, batchLimit);
    }
    const groups = lodash_1.map(lodash_1.groupBy(args, groupByFn), group => group).sort(({ length: first }, { length: second }) => first - second);
    const batches = [];
    groups.forEach(group => {
        if (group.length > batchLimit) {
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnexpectedError,
                message: 'Batch size exceeds limit. Please report this to the Polymath team',
                data: {
                    batch: group,
                    limit: batchLimit,
                },
            });
        }
        let batchIndex = batches.findIndex(batch => batch.length + group.length <= batchLimit);
        if (batchIndex === -1) {
            batchIndex = batches.length;
            batches[batchIndex] = [];
        }
        batches[batchIndex] = [...batches[batchIndex], ...group];
    });
    return batches;
}
exports.batchArguments = batchArguments;
/**
 * @hidden
 *
 * Calculates next page number for paginated GraphQL ResultSet.
 * Returns null if there is no next page.
 *
 * @param size - page size requested
 * @param start - start index requested
 * @param totalCount - total amount of elements returned by query
 *
 * @hidden
 *
 */
function calculateNextKey(totalCount, size, start) {
    const next = (start !== null && start !== void 0 ? start : new bignumber_js_1.default(0)).plus(size !== null && size !== void 0 ? size : constants_1.DEFAULT_GQL_PAGE_SIZE);
    return totalCount.gt(next) ? next : null;
}
exports.calculateNextKey = calculateNextKey;
// eslint-disable-next-line require-jsdoc
function createProcedureMethod(args, context) {
    const { getProcedureAndArgs, transformer, voidArgs } = args;
    if (voidArgs) {
        const voidMethod = (opts = {}) => {
            const [proc, procArgs] = getProcedureAndArgs();
            return proc().prepare({ args: procArgs, transformer }, context, opts);
        };
        voidMethod.checkAuthorization = (opts = {}) => __awaiter(this, void 0, void 0, function* () {
            const [proc, procArgs] = getProcedureAndArgs();
            return proc().checkAuthorization(procArgs, context, opts);
        });
        return voidMethod;
    }
    const method = (methodArgs, opts = {}) => {
        const [proc, procArgs] = getProcedureAndArgs(methodArgs);
        return proc().prepare({ args: procArgs, transformer }, context, opts);
    };
    method.checkAuthorization = (methodArgs, opts = {}) => __awaiter(this, void 0, void 0, function* () {
        const [proc, procArgs] = getProcedureAndArgs(methodArgs);
        return proc().checkAuthorization(procArgs, context, opts);
    });
    return method;
}
exports.createProcedureMethod = createProcedureMethod;
/**
 * @hidden
 */
function assertIsInteger(value) {
    if (!value.isInteger()) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.ValidationError,
            message: 'The number must be an integer',
        });
    }
}
exports.assertIsInteger = assertIsInteger;
/**
 * @hidden
 */
function assertIsPositive(value) {
    if (value.isNegative()) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.ValidationError,
            message: 'The number must be positive',
        });
    }
}
exports.assertIsPositive = assertIsPositive;
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
 * @hidden
 */
function isUiKeyring(keyring) {
    return !!keyring.keyring;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/**
 * @hidden
 */
function getCommonKeyring(keyring) {
    return isUiKeyring(keyring) ? keyring.keyring : keyring;
}
exports.getCommonKeyring = getCommonKeyring;
/**
 * @hidden
 */
function assertFormatValid(address, ss58Format) {
    const encodedAddress = util_crypto_1.encodeAddress(util_crypto_1.decodeAddress(address), ss58Format.toNumber());
    if (address !== encodedAddress) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.ValidationError,
            message: "The supplied address is not encoded with the chain's SS58 format",
            data: {
                ss58Format,
            },
        });
    }
}
exports.assertFormatValid = assertFormatValid;
/**
 * @hidden
 */
function assertKeyringFormatValid(keyring, ss58Format) {
    const dummyAddress = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY';
    const encodedAddress = keyring.encodeAddress(dummyAddress);
    const wellEncodedAddress = util_crypto_1.encodeAddress(dummyAddress, ss58Format.toNumber());
    if (encodedAddress !== wellEncodedAddress) {
        throw new internal_1.PolymeshError({
            code: types_1.ErrorCode.FatalError,
            message: "The supplied keyring is not using the chain's SS58 format",
            data: {
                ss58Format,
            },
        });
    }
}
exports.assertKeyringFormatValid = assertKeyringFormatValid;
/**
 * @hidden
 */
function getTicker(asset) {
    return typeof asset === 'string' ? asset : asset.ticker;
}
exports.getTicker = getTicker;
/**
 * @hidden
 */
function getAsset(asset, context) {
    return typeof asset === 'string' ? new internal_1.Asset({ ticker: asset }, context) : asset;
}
exports.getAsset = getAsset;
/**
 * @hidden
 */
function xor(a, b) {
    return a !== b;
}
exports.xor = xor;
/**
 * @hidden
 */
function secondsInUnit(unit) {
    const SECOND = new bignumber_js_1.default(1);
    const MINUTE = SECOND.multipliedBy(60);
    const HOUR = MINUTE.multipliedBy(60);
    const DAY = HOUR.multipliedBy(24);
    const WEEK = DAY.multipliedBy(7);
    const MONTH = DAY.multipliedBy(30);
    const YEAR = DAY.multipliedBy(365);
    switch (unit) {
        case types_1.CalendarUnit.Second: {
            return SECOND;
        }
        case types_1.CalendarUnit.Minute: {
            return MINUTE;
        }
        case types_1.CalendarUnit.Hour: {
            return HOUR;
        }
        case types_1.CalendarUnit.Day: {
            return DAY;
        }
        case types_1.CalendarUnit.Week: {
            return WEEK;
        }
        case types_1.CalendarUnit.Month: {
            return MONTH;
        }
        case types_1.CalendarUnit.Year: {
            return YEAR;
        }
    }
}
/**
 * @hidden
 * Calculate the numeric complexity of a calendar period
 */
function periodComplexity(period) {
    const secsInYear = secondsInUnit(types_1.CalendarUnit.Year);
    const { amount, unit } = period;
    if (amount.isZero()) {
        return new bignumber_js_1.default(1);
    }
    const secsInUnit = secondsInUnit(unit);
    const complexity = secsInYear.dividedBy(secsInUnit.multipliedBy(amount));
    return bignumber_js_1.default.maximum(2, complexity.integerValue(bignumber_js_1.default.ROUND_FLOOR));
}
exports.periodComplexity = periodComplexity;
/**
 * @hidden
 * Transform a conversion util into a version that returns null if the input is falsy
 */
function optionize(converter) {
    return (value, ...rest) => {
        const data = value !== null && value !== void 0 ? value : null;
        return data && converter(data, ...rest);
    };
}
exports.optionize = optionize;
/**
 * @hidden
 * Compare two tags/modules and return true if they are equal, or if one is the other one's module
 */
function isModuleOrTagMatch(a, b) {
    const aIsTag = a.includes('.');
    const bIsTag = b.includes('.');
    // a tag b module
    if (aIsTag && !bIsTag) {
        return a.split('.')[0] === b;
    }
    // a module b tag
    if (!aIsTag && bIsTag) {
        return a === b.split('.')[0];
    }
    // both tags or both modules
    return a === b;
}
exports.isModuleOrTagMatch = isModuleOrTagMatch;
/**
 * @hidden
 *
 * Recursively convert a value into a human readable (JSON compliant) version:
 *   - Entities are converted via their `.toJson` method
 *   - Dates are converted to ISO strings
 *   - BigNumbers are converted to numerical strings
 */
function toHumanReadable(obj) {
    if (typeguards_1.isEntity(obj)) {
        return obj.toJson();
    }
    if (obj instanceof bignumber_js_1.default) {
        return obj.toString();
    }
    if (obj instanceof Date) {
        return obj.toISOString();
    }
    if (Array.isArray(obj)) {
        return obj.map(toHumanReadable);
    }
    if (obj && typeof obj === 'object') {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return lodash_1.mapValues(obj, val => toHumanReadable(val));
    }
    return obj;
}
exports.toHumanReadable = toHumanReadable;
/**
 * @hidden
 *
 * Perform a deep comparison between two compliance conditions
 */
function conditionsAreEqual(a, b) {
    let equalClaims = false;
    if (typeguards_1.isSingleClaimCondition(a) && typeguards_1.isSingleClaimCondition(b)) {
        equalClaims = lodash_1.isEqual(a.claim, b.claim);
    }
    else if (typeguards_1.isMultiClaimCondition(a) && typeguards_1.isMultiClaimCondition(b)) {
        const { claims: aClaims } = a;
        const { claims: bClaims } = b;
        equalClaims = hasSameElements(aClaims, bClaims);
    }
    else if (a.type === types_1.ConditionType.IsIdentity && b.type === types_1.ConditionType.IsIdentity) {
        equalClaims = conversion_1.signerToString(a.identity) === conversion_1.signerToString(b.identity);
    }
    else if (a.type === types_1.ConditionType.IsExternalAgent && b.type === types_1.ConditionType.IsExternalAgent) {
        equalClaims = true;
    }
    const { trustedClaimIssuers: aClaimIssuers = [] } = a;
    const { trustedClaimIssuers: bClaimIssuers = [] } = b;
    const equalClaimIssuers = hasSameElements(aClaimIssuers, bClaimIssuers, ({ identity: aIdentity, trustedFor: aTrustedFor }, { identity: bIdentity, trustedFor: bTrustedFor }) => conversion_1.signerToString(aIdentity) === conversion_1.signerToString(bIdentity) &&
        hasSameElements(aTrustedFor || [], bTrustedFor || []));
    return equalClaims && equalClaimIssuers;
}
exports.conditionsAreEqual = conditionsAreEqual;
/**
 * @hidden
 *
 * Transforms `InputCACheckpoint` values to `Checkpoint | CheckpointSchedule | Date` for easier processing
 */
function getCheckpointValue(checkpoint, asset, context) {
    return __awaiter(this, void 0, void 0, function* () {
        if (checkpoint instanceof internal_1.Checkpoint ||
            checkpoint instanceof internal_1.CheckpointSchedule ||
            checkpoint instanceof Date) {
            return checkpoint;
        }
        const assetEntity = getAsset(asset, context);
        const { type, id } = checkpoint;
        if (type === types_1.CaCheckpointType.Existing) {
            return assetEntity.checkpoints.getOne({ id });
        }
        else {
            return (yield assetEntity.checkpoints.schedules.getOne({
                id,
            })).schedule;
        }
    });
}
exports.getCheckpointValue = getCheckpointValue;
// * TODO @monitz87: delete this function when we eliminate `addBatchTransaction`
/**
 * @hidden
 */
function mapArgs({ transaction, argsArray, }) {
    return argsArray.map(args => ({
        transaction,
        args,
    }));
}
// * TODO @monitz87: delete this function when we eliminate `addBatchTransaction`
/**
 * Assemble the `transactions` array that has to be passed to `addBatchTransaction` from a set of parameter arrays with their
 *   respective transaction
 */
function assembleBatchTransactions(txsAndArgs) {
    return lodash_1.flatMap(txsAndArgs, mapArgs);
}
exports.assembleBatchTransactions = assembleBatchTransactions;
/**
 * @hidden
 *
 * Return whether the two arrays have same elements.
 * It uses a `comparator` function to check if elements are equal.
 * If no comparator function is provided, it uses `isEqual` function of `lodash`
 */
function hasSameElements(first, second, comparator = lodash_1.isEqual) {
    return !lodash_1.differenceWith(first, second, comparator).length && first.length === second.length;
}
exports.hasSameElements = hasSameElements;
/**
 * @hidden
 *
 * Returns portfolio number for a given portfolio name
 */
function getPortfolioIdByName(rawIdentityId, rawName, context) {
    return __awaiter(this, void 0, void 0, function* () {
        const { polymeshApi: { query: { portfolio }, }, } = context;
        const rawPortfolioNumber = yield portfolio.nameToNumber(rawIdentityId, rawName);
        const portfolioId = conversion_1.u64ToBigNumber(rawPortfolioNumber);
        // TODO @prashantasdeveloper remove this logic once nameToNumber returns Option<PortfolioNumber>
        if (portfolioId.eq(1)) {
            /**
             * since nameToNumber returns 1 for non-existing portfolios,
             * we need to check if the name matches against the portfolio number 1
             */
            const rawExistingPortfolioName = yield portfolio.portfolios(rawIdentityId, rawPortfolioNumber);
            if (!rawName.eq(rawExistingPortfolioName)) {
                return null;
            }
        }
        return portfolioId;
    });
}
exports.getPortfolioIdByName = getPortfolioIdByName;
//# sourceMappingURL=internal.js.map