"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.XrpTransaction = void 0;
const metadata_1 = require("xrpl/dist/npm/models/transactions/metadata");
const constants_1 = require("../../utils/constants");
const utils_1 = require("../../utils/utils");
const TransactionBase_1 = require("../TransactionBase");
const genericMccTypes_1 = require("../../types/genericMccTypes");
// @Managed()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class XrpTransaction extends TransactionBase_1.TransactionBase {
    get data() {
        return this.privateData;
    }
    get txid() {
        return this.hash;
    }
    get stdTxid() {
        return this.txid;
    }
    get hash() {
        return this.data.result.hash;
    }
    get reference() {
        if (this.data.result.Memos) {
            return this.data.result.Memos.map((memoObj) => {
                return memoObj.Memo.MemoData || "";
            });
        }
        return [];
    }
    get stdPaymentReference() {
        const paymentReference = this.reference.length === 1 ? (0, utils_1.prefix0x)(this.reference[0]) : "";
        if ((0, utils_1.isValidBytes32Hex)(paymentReference)) {
            return paymentReference;
        }
        else {
            const alternative = (0, utils_1.bytesAsHexToString)(paymentReference);
            if ((0, utils_1.isValidBytes32Hex)(alternative)) {
                return alternative;
            }
            return utils_1.ZERO_BYTES_32;
        }
    }
    get unixTimestamp() {
        return (constants_1.XRP_UTD +
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            this.data.result.date);
    }
    get sourceAddresses() {
        const sourceAddresses = [];
        for (const addAmm of this.spentAmounts) {
            if (addAmm.address) {
                sourceAddresses.push(addAmm.address);
            }
        }
        return sourceAddresses;
    }
    get assetSourceAddresses() {
        throw new Error("Method not implemented.");
    }
    get receivingAddresses() {
        const receivingAddresses = [];
        for (const addAmm of this.receivedAmounts) {
            if (addAmm.address) {
                receivingAddresses.push(addAmm.address);
            }
        }
        return receivingAddresses;
    }
    get assetReceivingAddresses() {
        throw new Error("Method not implemented.");
    }
    get fee() {
        /* istanbul ignore if */
        if (!this.data.result.Fee) {
            return (0, utils_1.toBN)(0);
        }
        return (0, utils_1.toBN)(this.data.result.Fee);
    }
    get feeSignerTotalAmount() {
        const spentAmounts = this.spentAmounts;
        // Check if signer is already in source amounts
        const feeSigner = this.data.result.Account;
        for (const spentAmount of spentAmounts) {
            if (spentAmount.address === feeSigner) {
                return spentAmount;
            }
        }
        // Check if singer got positive amount
        const receivedAmounts = this.receivedAmounts;
        for (const receivedAmount of receivedAmounts) {
            if (receivedAmount.address === feeSigner) {
                const { amount } = receivedAmount, rest = __rest(receivedAmount, ["amount"]);
                return Object.assign({ amount: amount.neg() }, rest);
            }
        }
        // You cash a check for exactly fee amount
        return {
            amount: (0, utils_1.toBN)(0),
            address: feeSigner,
        };
    }
    get spentAmounts() {
        if (typeof this.data.result.meta === "string" || !this.data.result.meta) {
            throw new Error("Transaction meta is not available thus spent amounts cannot be calculated");
        }
        const spendAmounts = [];
        for (const node of this.data.result.meta.AffectedNodes) {
            if ((0, metadata_1.isModifiedNode)(node)) {
                if (node.ModifiedNode.LedgerEntryType === "AccountRoot" &&
                    node.ModifiedNode.FinalFields &&
                    node.ModifiedNode.PreviousFields &&
                    node.ModifiedNode.FinalFields.Account &&
                    node.ModifiedNode.FinalFields.Balance &&
                    node.ModifiedNode.PreviousFields.Balance) {
                    // TODO: this is due to xrpl.js lib mistakes
                    const diff = (0, utils_1.toBN)(node.ModifiedNode.FinalFields.Balance).sub((0, utils_1.toBN)(node.ModifiedNode.PreviousFields.Balance));
                    if (diff.lt((0, utils_1.toBN)(0))) {
                        spendAmounts.push({
                            address: node.ModifiedNode.FinalFields.Account,
                            amount: diff.neg(),
                        });
                    }
                }
            }
            if ((0, metadata_1.isCreatedNode)(node)) {
                // TODO: check if true
                // Created node can't affect spend amounts
            }
            if ((0, metadata_1.isDeletedNode)(node)) {
                if (node.DeletedNode.LedgerEntryType === "AccountRoot" && "PreviousFields" in node.DeletedNode) {
                    if (node.DeletedNode.FinalFields && node.DeletedNode.FinalFields.Account) {
                        if (node.DeletedNode.FinalFields.Balance) {
                            // TODO: this is due to xrpl.js lib mistakes
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            const diff = (0, utils_1.toBN)(node.DeletedNode.FinalFields.Balance).sub(
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            (0, utils_1.toBN)(node.DeletedNode.PreviousFields.Balance));
                            if (diff.lt((0, utils_1.toBN)(0))) {
                                spendAmounts.push({
                                    address: node.DeletedNode.FinalFields.Account,
                                    amount: diff.neg(),
                                });
                            }
                        }
                        else {
                            // TODO: this is due to xrpl.js lib mistakes
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-explicit-any
                            const diff = (0, utils_1.toBN)(node.DeletedNode.PreviousFields.Balance);
                            if (diff.gt((0, utils_1.toBN)(0))) {
                                spendAmounts.push({
                                    address: node.DeletedNode.FinalFields.Account,
                                    amount: diff,
                                });
                            }
                        }
                    }
                }
            }
        }
        // // Check if signer is already in source amounts
        // const feeSigner = this.data.result.Account;
        // for (const spendAmount of spendAmounts) {
        //    if (spendAmount.address === feeSigner) {
        //       return spendAmounts;
        //    }
        // }
        // // Check if singer got positive amount
        // const receivedAmounts = this.receivedAmounts;
        // for (const receivedAmount of receivedAmounts) {
        //    if (receivedAmount.address === feeSigner) {
        //       const { amount, ...rest } = receivedAmount;
        //       spendAmounts.push({
        //          amount: amount.neg(),
        //          ...rest,
        //       });
        //       return spendAmounts;
        //    }
        // }
        // // You cash a check for exactly fee amount
        // spendAmounts.push({
        //    amount: toBN(0),
        //    address: feeSigner,
        // });
        return spendAmounts;
    }
    get intendedSpentAmounts() {
        if (this.successStatus === genericMccTypes_1.TransactionSuccessStatus.SUCCESS) {
            return this.spentAmounts;
        }
        switch (this.type) {
            case "Payment": {
                const payment = this.data.result;
                if (this.isNativePayment) {
                    if (typeof payment.Amount === "string") {
                        return [
                            {
                                address: payment.Account,
                                amount: (0, utils_1.toBN)(payment.Amount).add(this.fee),
                            },
                        ];
                    } // Token transfer since Amount is IssuedCurrencyAmount
                    else {
                        return [
                            {
                                address: payment.Account,
                                amount: this.fee,
                            },
                        ];
                    }
                }
                else {
                    // Token transfer
                    return [
                        {
                            address: payment.Account,
                            amount: this.fee,
                        },
                    ];
                }
            }
            default:
                // TODO: what should be returned here?
                throw new Error(`Intended spend amounts for transaction type ${this.type} are not implemented`);
            // return [
            //    {
            //       address: this.sourceAddresses[0],
            //       amount: toBN(this.fee),
            //    },
            // ];
        }
    }
    get assetSpentAmounts() {
        throw new Error("Method not implemented.");
    }
    get receivedAmounts() {
        if (typeof this.data.result.meta === "string" || !this.data.result.meta) {
            throw new Error("Transaction meta is not available thus received amounts cannot be calculated");
        }
        const receivedAmounts = [];
        for (const node of this.data.result.meta.AffectedNodes) {
            if ((0, metadata_1.isModifiedNode)(node)) {
                if (node.ModifiedNode.LedgerEntryType === "AccountRoot" &&
                    node.ModifiedNode.FinalFields &&
                    node.ModifiedNode.PreviousFields &&
                    node.ModifiedNode.FinalFields.Account &&
                    node.ModifiedNode.FinalFields.Balance &&
                    node.ModifiedNode.PreviousFields.Balance) {
                    // TODO: this is due to xrpl.js lib mistakes
                    const diff = (0, utils_1.toBN)(node.ModifiedNode.FinalFields.Balance).sub((0, utils_1.toBN)(node.ModifiedNode.PreviousFields.Balance));
                    if (diff.gt((0, utils_1.toBN)(0))) {
                        receivedAmounts.push({
                            address: node.ModifiedNode.FinalFields.Account,
                            amount: diff,
                        });
                    }
                }
            }
            else if ((0, metadata_1.isCreatedNode)(node)) {
                if (node.CreatedNode.LedgerEntryType === "AccountRoot" && node.CreatedNode.NewFields && node.CreatedNode.NewFields.Account) {
                    if (node.CreatedNode.NewFields.Balance) {
                        receivedAmounts.push({
                            address: node.CreatedNode.NewFields.Account,
                            amount: (0, utils_1.toBN)(node.CreatedNode.NewFields.Balance),
                        });
                    }
                }
            }
            else if ((0, metadata_1.isDeletedNode)(node)) {
                // TODO: check if true
                // Created node can't affect spend amounts
            }
        }
        return receivedAmounts;
    }
    get intendedReceivedAmounts() {
        if (this.successStatus === genericMccTypes_1.TransactionSuccessStatus.SUCCESS) {
            return this.receivedAmounts;
        }
        switch (this.type) {
            case "Payment": {
                const payment = this.data.result;
                if (this.isNativePayment) {
                    if (typeof payment.Amount === "string") {
                        return [
                            {
                                address: payment.Destination,
                                amount: (0, utils_1.toBN)(payment.Amount),
                            },
                        ];
                    }
                }
                // Token transfer was intended
                return [];
            }
            default:
                throw new Error(`Intended received amounts for transaction type ${this.type} are not implemented`);
        }
    }
    get assetReceivedAmounts() {
        throw new Error("Method not implemented.");
    }
    get type() {
        return this.data.result.TransactionType;
    }
    get isNativePayment() {
        return this.currencyName === constants_1.XRP_NATIVE_TOKEN_NAME;
    }
    //!!! issuer is sometimes important !!!
    get currencyName() {
        // With ripple this is currency code
        if (this.type === "Payment") {
            if (this.data.result.Amount.currency) {
                return this.data.result.Amount.currency;
            }
            return constants_1.XRP_NATIVE_TOKEN_NAME;
        }
        // TODO Check for other types of transactions
        return "";
    }
    get elementaryUnits() {
        // TODO this is dependent on currency we are using
        return (0, utils_1.toBN)(constants_1.XRP_MDU);
    }
    get successStatus() {
        if (typeof this.data.result.meta === "string" || !this.data.result.meta) {
            throw new Error("Transaction meta is not available thus transaction status cannot be extracted");
        }
        const result = this.data.result.meta.TransactionResult;
        const prefix = result.slice(0, 3);
        // TODO: update
        // about statuses https://xrpl.org/transaction-results.html
        switch (prefix) {
            case "tes": // SUCCESS - Transaction was applied. Only final in a validated ledger.
                return genericMccTypes_1.TransactionSuccessStatus.SUCCESS;
            case "tec": {
                // FAILED - Transaction failed, and only the fee was charged. Only final in a validated ledger.
                const resultTec = result;
                switch (resultTec) {
                    case "tecDST_TAG_NEEDED": //can be perceived as sender's fault if destination tag was specified
                    case "tecNO_DST":
                    case "tecNO_DST_INSUF_XRP":
                    case "tecNO_PERMISSION": //can be perceived as sender's fault if DepositPreauth tag specifically required
                        return genericMccTypes_1.TransactionSuccessStatus.RECEIVER_FAILURE;
                    case "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER":
                    case "tecCLAIM": //unsure
                    case "tecCRYPTOCONDITION_ERROR":
                    case "tecDIR_FULL": //technically receivers fault, but so far irrelevant
                    case "tecDUPLICATE":
                    case "tecEXPIRED":
                    case "tecFAILED_PROCESSING": //unsure
                    case "tecFROZEN":
                    case "tecHAS_OBLIGATIONS":
                    case "tecINSUF_RESERVE_LINE":
                    case "tecINSUF_RESERVE_OFFER":
                    case "tecINSUFF_FEE":
                    case "tecINSUFFICIENT_FUNDS": //can be receivers fault, bu so far irrelevant
                    case "tecINSUFFICIENT_PAYMENT":
                    case "tecINSUFFICIENT_RESERVE":
                    case "tecINTERNAL": //unsure
                    case "tecINVARIANT_FAILED": //unsure
                    case "tecKILLED":
                    case "tecMAX_SEQUENCE_REACHED":
                    case "tecNEED_MASTER_KEY":
                    case "tecNFTOKEN_BUY_SELL_MISMATCH":
                    case "tecNFTOKEN_OFFER_TYPE_MISMATCH":
                    case "tecNO_ALTERNATIVE_KEY":
                    case "tecNO_AUTH": //can be receivers fault, but so far irrelevant
                    case "tecNO_ENTRY":
                    case "tecNO_ISSUER":
                    case "tecNO_LINE":
                    case "tecNO_LINE_INSUF_RESERVE":
                    case "tecNO_LINE_REDUNDANT":
                    case "tecNO_REGULAR_KEY":
                    case "tecNO_SUITABLE_NFTOKEN_PAGE":
                    case "tecNO_TARGET":
                    case "tecOBJECT_NOT_FOUND":
                    case "tecOVERSIZE": //unsure
                    case "tecOWNERS":
                    case "tecPATH_DRY": //can be receivers fault, but so far irrelevant
                    case "tecPATH_PARTIAL":
                    case "tecTOO_SOON":
                    case "tecUNFUNDED":
                    case "tecUNFUNDED_ADD":
                    case "tecUNFUNDED_PAYMENT":
                    case "tecUNFUNDED_OFFER":
                        return genericMccTypes_1.TransactionSuccessStatus.SENDER_FAILURE;
                    default:
                        // exhaustive switch guard: if a compile time error appears here, you have forgotten one of the cases
                        // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
                        ((_) => { })(resultTec);
                }
                break;
            }
            case "tef":
                // FAILED - The transaction cannot be applied to the server's current (in-progress) ledger or any later one. It may have already been applied, or the condition of the ledger makes it impossible to apply in the future.
                return genericMccTypes_1.TransactionSuccessStatus.SENDER_FAILURE;
            case "tel":
                // LOCAL_ERROR - The transaction was not applied. The transaction was not applied to the ledger because it failed local checks, such as a bad signature or an unavailable fee level.
                return genericMccTypes_1.TransactionSuccessStatus.SENDER_FAILURE;
            case "tem":
                // MALFORMED - The transaction was not applied. The transaction was not applied to the ledger because it was malformed in some way, such as a bad signature or an unavailable fee level.
                return genericMccTypes_1.TransactionSuccessStatus.SENDER_FAILURE;
            case "ter":
                // FAILED_PROCESSING - The transaction was not applied. The transaction was not applied to the ledger because it failed to meet some other constraint imposed by the server.
                return genericMccTypes_1.TransactionSuccessStatus.SENDER_FAILURE;
            default:
                // exhaustive switch guard: if a compile time error appears here, you have forgotten one of the cases
                // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
                ((_) => { })(prefix);
        }
        throw new Error(`Unexpected transaction status prefix found ${prefix}`);
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async paymentSummary(props) {
        if (this.type === "Payment" && this.isNativePayment) {
            // Is native transfer
            if (this.spentAmounts.length !== 1 || this.receivedAmounts.length !== 1) {
                return {
                    status: TransactionBase_1.PaymentSummaryStatus.UnexpectedNumberOfParticipants,
                };
            }
            const spendAmount = this.spentAmounts[0];
            const receiveAmount = this.receivedAmounts[0];
            if (!spendAmount.address) {
                return {
                    status: TransactionBase_1.PaymentSummaryStatus.NoSpentAmountAddress,
                };
            }
            if (!receiveAmount.address) {
                return {
                    status: TransactionBase_1.PaymentSummaryStatus.NoReceiveAmountAddress,
                };
            }
            if (this.successStatus !== genericMccTypes_1.TransactionSuccessStatus.SUCCESS) {
                if (this.intendedSpentAmounts.length !== 1 || this.intendedReceivedAmounts.length !== 1) {
                    return {
                        status: TransactionBase_1.PaymentSummaryStatus.UnexpectedNumberOfParticipants,
                    };
                }
            }
            const intendedSpendAmounts = this.intendedSpentAmounts[0];
            const intendedReceivedAmounts = this.intendedReceivedAmounts[0];
            if (!intendedSpendAmounts.address) {
                return {
                    status: TransactionBase_1.PaymentSummaryStatus.NoIntendedSpentAmountAddress,
                };
            }
            if (!intendedReceivedAmounts.address) {
                return {
                    status: TransactionBase_1.PaymentSummaryStatus.NoIntendedReceiveAmountAddress,
                };
            }
            return {
                status: TransactionBase_1.PaymentSummaryStatus.Success,
                response: {
                    blockTimestamp: this.unixTimestamp,
                    transactionHash: this.stdTxid,
                    sourceAddress: spendAmount.address,
                    sourceAddressHash: (0, utils_1.standardAddressHash)(spendAmount.address),
                    receivingAddressHash: (0, utils_1.standardAddressHash)(receiveAmount.address),
                    receivingAddress: receiveAmount.address,
                    spentAmount: spendAmount.amount,
                    // TODO: Check if intended sent value can be set
                    receivedAmount: this.successStatus === genericMccTypes_1.TransactionSuccessStatus.SUCCESS ? receiveAmount.amount : (0, utils_1.toBN)(0),
                    transactionStatus: this.successStatus,
                    // For transactions that are not successful but still in block
                    intendedSourceAddressHash: (0, utils_1.standardAddressHash)(intendedSpendAmounts.address),
                    intendedSourceAddress: intendedSpendAmounts.address,
                    intendedSourceAmount: (0, utils_1.toBN)(intendedSpendAmounts.amount),
                    intendedReceivingAddressHash: (0, utils_1.standardAddressHash)(intendedReceivedAmounts.address),
                    intendedReceivingAddress: intendedReceivedAmounts.address,
                    intendedReceivingAmount: (0, utils_1.toBN)(intendedReceivedAmounts.amount),
                    oneToOne: true,
                    paymentReference: this.stdPaymentReference,
                    isFull: true,
                },
            };
        }
        return {
            status: TransactionBase_1.PaymentSummaryStatus.NotNativePayment,
        };
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async balanceDecreasingSummary({ sourceAddressIndicator }) {
        if (!(0, utils_1.isValidBytes32Hex)(sourceAddressIndicator)) {
            return { status: TransactionBase_1.BalanceDecreasingSummaryStatus.NotValidSourceAddressFormat };
        }
        const spentAmounts = this.spentAmounts;
        for (const spendAmount of spentAmounts) {
            if (spendAmount.address && (0, utils_1.standardAddressHash)(spendAmount.address) === sourceAddressIndicator) {
                // We found the address we are looking for
                return {
                    status: TransactionBase_1.BalanceDecreasingSummaryStatus.Success,
                    response: {
                        blockTimestamp: this.unixTimestamp,
                        transactionHash: this.stdTxid,
                        sourceAddressIndicator: sourceAddressIndicator,
                        sourceAddressHash: (0, utils_1.standardAddressHash)(spendAmount.address),
                        sourceAddress: spendAmount.address,
                        spentAmount: spendAmount.amount,
                        paymentReference: this.stdPaymentReference,
                        transactionStatus: this.successStatus,
                        isFull: true,
                    },
                };
            }
        }
        const feeSigner = this.feeSignerTotalAmount;
        if (feeSigner.address && (0, utils_1.standardAddressHash)(feeSigner.address) === sourceAddressIndicator) {
            // We found the address we are looking for
            return {
                status: TransactionBase_1.BalanceDecreasingSummaryStatus.Success,
                response: {
                    blockTimestamp: this.unixTimestamp,
                    transactionHash: this.stdTxid,
                    sourceAddressIndicator: sourceAddressIndicator,
                    sourceAddressHash: (0, utils_1.standardAddressHash)(feeSigner.address),
                    sourceAddress: feeSigner.address,
                    spentAmount: feeSigner.amount,
                    paymentReference: this.stdPaymentReference,
                    transactionStatus: this.successStatus,
                    isFull: true,
                },
            };
        }
        // We didn't find the address we are looking for
        return { status: TransactionBase_1.BalanceDecreasingSummaryStatus.NoSourceAddress };
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async makeFull(client) {
        return;
    }
    //////////////////////////////
    //// Xrp specific methods ////
    //////////////////////////////
    get metaObject() {
        const noMetaError = new Error("Transaction meta is not available");
        if (this.data.result.meta) {
            if (typeof this.data.result.meta === "string") {
                throw noMetaError;
            }
            else {
                return this.data.result.meta;
            }
        }
        // We handle this in the client, but transaction metadata is provided as different named properties depending if getting transactions from block or directly from transaction hash
        if (this.data.result.metaData) {
            return this.data.result.metaData;
        }
        throw noMetaError;
    }
    get isAccountCreate() {
        if (this.type === "Payment") {
            if (this.data.result.meta) {
                if (typeof this.data.result.meta === "string") {
                    return false;
                }
                const Meta = this.data.result.meta;
                for (const elem of Meta.AffectedNodes) {
                    if ("CreatedNode" in elem) {
                        if ("Account" in elem.CreatedNode.NewFields) {
                            if (elem.CreatedNode.NewFields.Account === this.receivingAddresses[0]) {
                                return true;
                            }
                        }
                    }
                }
                return false;
            }
            return false;
        }
        return false;
    }
}
exports.XrpTransaction = XrpTransaction;
//# sourceMappingURL=XrpTransaction.js.map