"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.Instruction = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const internal_1 = require("../../../internal");
const queries_1 = require("../../../middleware/queries");
const types_1 = require("../../../middleware/types");
const types_2 = require("../../../types");
const internal_2 = require("../../../types/internal");
const conversion_1 = require("../../../utils/conversion");
const internal_3 = require("../../../utils/internal");
const types_3 = require("./types");
const executedMessage = 'Instruction has already been executed/rejected and it was purged from chain';
/**
 * Represents a settlement Instruction to be executed on a certain Venue
 */
class Instruction extends internal_1.Entity {
    /**
     * @hidden
     */
    constructor(identifiers, context) {
        super(identifiers, context);
        const { id } = identifiers;
        this.id = id;
        this.reject = internal_3.createProcedureMethod({
            getProcedureAndArgs: () => [
                internal_1.modifyInstructionAffirmation,
                { id, operation: internal_2.InstructionAffirmationOperation.Reject },
            ],
            voidArgs: true,
        }, context);
        this.affirm = internal_3.createProcedureMethod({
            getProcedureAndArgs: () => [
                internal_1.modifyInstructionAffirmation,
                { id, operation: internal_2.InstructionAffirmationOperation.Affirm },
            ],
            voidArgs: true,
        }, context);
        this.withdraw = internal_3.createProcedureMethod({
            getProcedureAndArgs: () => [
                internal_1.modifyInstructionAffirmation,
                { id, operation: internal_2.InstructionAffirmationOperation.Withdraw },
            ],
            voidArgs: true,
        }, context);
        this.reschedule = internal_3.createProcedureMethod({
            getProcedureAndArgs: () => [internal_1.rescheduleInstruction, { id }],
            voidArgs: true,
        }, context);
    }
    /**
     * @hidden
     * Check if a value is of type [[UniqueIdentifiers]]
     */
    static isUniqueIdentifiers(identifier) {
        const { id } = identifier;
        return id instanceof bignumber_js_1.default;
    }
    /**
     * Retrieve whether the Instruction has already been executed and pruned from
     *   the chain.
     */
    isExecuted() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, id, context, } = this;
            const [{ status }, exists] = yield Promise.all([
                settlement.instructionDetails(conversion_1.bigNumberToU64(id, context)),
                this.exists(),
            ]);
            const statusResult = conversion_1.meshInstructionStatusToInstructionStatus(status);
            return statusResult === internal_2.InstructionStatus.Unknown && exists;
        });
    }
    /**
     * Retrieve whether the Instruction is still pending on chain
     */
    isPending() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, id, context, } = this;
            const { status } = yield settlement.instructionDetails(conversion_1.bigNumberToU64(id, context));
            const statusResult = conversion_1.meshInstructionStatusToInstructionStatus(status);
            return statusResult === internal_2.InstructionStatus.Pending;
        });
    }
    /**
     * Determine whether this Instruction exists on chain (or existed and was pruned)
     */
    exists() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, id, } = this;
            const instructionCounter = yield settlement.instructionCounter();
            return id.lte(conversion_1.u64ToBigNumber(instructionCounter));
        });
    }
    /**
     * Retrieve information specific to this Instruction
     */
    details() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, id, context, } = this;
            const { status: rawStatus, created_at: createdAt, trade_date: tradeDate, value_date: valueDate, settlement_type: type, venue_id: venueId, } = yield settlement.instructionDetails(conversion_1.bigNumberToU64(id, context));
            const status = conversion_1.meshInstructionStatusToInstructionStatus(rawStatus);
            if (status === internal_2.InstructionStatus.Unknown) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.DataUnavailable,
                    message: executedMessage,
                });
            }
            const details = {
                status: status === internal_2.InstructionStatus.Pending
                    ? types_3.InstructionStatus.Pending
                    : types_3.InstructionStatus.Failed,
                createdAt: conversion_1.momentToDate(createdAt.unwrap()),
                tradeDate: tradeDate.isSome ? conversion_1.momentToDate(tradeDate.unwrap()) : null,
                valueDate: valueDate.isSome ? conversion_1.momentToDate(valueDate.unwrap()) : null,
                venue: new internal_1.Venue({ id: conversion_1.u64ToBigNumber(venueId) }, context),
            };
            if (type.isSettleOnAffirmation) {
                return Object.assign(Object.assign({}, details), { type: types_3.InstructionType.SettleOnAffirmation });
            }
            return Object.assign(Object.assign({}, details), { type: types_3.InstructionType.SettleOnBlock, endBlock: conversion_1.u32ToBigNumber(type.asSettleOnBlock) });
        });
    }
    /**
     * Retrieve every authorization generated by this Instruction (status and authorizing Identity)
     *
     * @note supports pagination
     */
    getAffirmations(paginationOpts) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, id, context, } = this;
            const isExecuted = yield this.isExecuted();
            if (isExecuted) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.DataUnavailable,
                    message: executedMessage,
                });
            }
            const { entries, lastKey: next } = yield internal_3.requestPaginated(settlement.affirmsReceived, {
                arg: conversion_1.bigNumberToU64(id, context),
                paginationOpts,
            });
            const data = entries.map(([{ args }, meshAffirmationStatus]) => {
                const [, { did }] = args;
                return {
                    identity: new internal_1.Identity({ did: conversion_1.identityIdToString(did) }, context),
                    status: conversion_1.meshAffirmationStatusToAffirmationStatus(meshAffirmationStatus),
                };
            });
            return {
                data,
                next,
            };
        });
    }
    /**
     * Retrieve all legs of this Instruction
     *
     * @note supports pagination
     */
    getLegs(paginationOpts) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query: { settlement }, }, }, id, context, } = this;
            const isExecuted = yield this.isExecuted();
            if (isExecuted) {
                throw new internal_1.PolymeshError({
                    code: types_2.ErrorCode.DataUnavailable,
                    message: executedMessage,
                });
            }
            const { entries: legs, lastKey: next } = yield internal_3.requestPaginated(settlement.instructionLegs, {
                arg: conversion_1.bigNumberToU64(id, context),
                paginationOpts,
            });
            const data = legs.map(([, leg]) => {
                const { from, to, amount, asset } = leg;
                const ticker = conversion_1.tickerToString(asset);
                const fromPortfolio = conversion_1.meshPortfolioIdToPortfolio(from, context);
                const toPortfolio = conversion_1.meshPortfolioIdToPortfolio(to, context);
                return {
                    from: fromPortfolio,
                    to: toPortfolio,
                    amount: conversion_1.balanceToBigNumber(amount),
                    asset: new internal_1.Asset({ ticker }, context),
                };
            });
            return {
                data,
                next,
            };
        });
    }
    /**
     * Retrieve current status of this Instruction
     *
     * @note uses the middleware
     */
    getStatus() {
        return __awaiter(this, void 0, void 0, function* () {
            const isPending = yield this.isPending();
            if (isPending) {
                return {
                    status: types_3.InstructionStatus.Pending,
                };
            }
            let eventIdentifier = yield this.getInstructionEventFromMiddleware(types_1.EventIdEnum.InstructionExecuted);
            if (eventIdentifier) {
                return {
                    status: types_3.InstructionStatus.Executed,
                    eventIdentifier,
                };
            }
            eventIdentifier = yield this.getInstructionEventFromMiddleware(types_1.EventIdEnum.InstructionFailed);
            if (eventIdentifier) {
                return {
                    status: types_3.InstructionStatus.Failed,
                    eventIdentifier,
                };
            }
            throw new internal_1.PolymeshError({
                code: types_2.ErrorCode.DataUnavailable,
                message: "It isn't possible to determine the current status of this Instruction",
            });
        });
    }
    /**
     * @hidden
     * Retrieve Instruction status event from middleware
     */
    getInstructionEventFromMiddleware(eventId) {
        return __awaiter(this, void 0, void 0, function* () {
            const { id, context } = this;
            const { data: { eventByIndexedArgs: event }, } = yield context.queryMiddleware(queries_1.eventByIndexedArgs({
                moduleId: types_1.ModuleIdEnum.Settlement,
                eventId: eventId,
                eventArg1: id.toString(),
            }));
            return internal_3.optionize(conversion_1.middlewareEventToEventIdentifier)(event);
        });
    }
    /**
     * Return the Instruction's ID
     */
    toJson() {
        return this.id.toString();
    }
}
exports.Instruction = Instruction;
//# sourceMappingURL=index.js.map