"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.XrpTransactionStorage = exports.XrpTransactionModel = void 0;
const Loggify_1 = require("../../../decorators/Loggify");
const logger_1 = __importDefault(require("../../../logger"));
const baseTransaction_1 = require("../../../models/baseTransaction");
const cache_1 = require("../../../models/cache");
const coin_1 = require("../../../models/coin");
const events_1 = require("../../../models/events");
const config_1 = require("../../../services/config");
const storage_1 = require("../../../services/storage");
const utils_1 = require("../../../utils");
let XrpTransactionModel = class XrpTransactionModel extends baseTransaction_1.BaseTransaction {
    constructor(storage = storage_1.Storage) {
        super(storage);
    }
    onConnect() {
        super.onConnect();
        this.collection.createIndex({ chain: 1, network: 1, from: 1, nonce: 1 }, { background: true, sparse: true });
    }
    async batchImport(params) {
        const txOps = await this.addTransactions({ ...params });
        const coinOps = (await this.addCoins({ ...params }));
        const batchSize = config_1.Config.get().maxPoolSize;
        logger_1.default.debug('Writing Transactions: %o', txOps.length);
        await Promise.all((0, utils_1.partition)(txOps, txOps.length / batchSize).map(txBatch => this.collection.bulkWrite(txBatch.map(op => this.toMempoolSafeUpsert(op, 0 /* SpentHeightIndicators.minimum */)), {
            ordered: false
        })));
        await Promise.all((0, utils_1.partition)(coinOps, coinOps.length / batchSize).map(coinBatch => coin_1.CoinStorage.collection.bulkWrite(coinBatch.map(op => this.toMempoolSafeUpsert(op, 0 /* SpentHeightIndicators.minimum */)), { ordered: false })));
        if (params.initialSyncComplete) {
            await this.expireBalanceCache(coinOps);
        }
        // Create events for mempool txs
        if (params.height != undefined && params.height < 0 /* SpentHeightIndicators.minimum */) {
            for (const op of txOps) {
                const filter = op.updateOne.filter;
                const tx = { ...op.updateOne.update.$set, ...filter };
                await events_1.EventStorage.signalTx(tx);
            }
            for (const coinOp of coinOps) {
                const coin = { ...coinOp.updateOne.filter, ...coinOp.updateOne.update.$set };
                await events_1.EventStorage.signalAddressCoin({
                    address: coin.address,
                    coin: {
                        value: coin.value,
                        address: coin.address,
                        chain: params.chain,
                        network: params.network,
                        mintTxid: coin.mintTxid
                    }
                });
            }
        }
    }
    async expireBalanceCache(coinOps) {
        const batch = new Array();
        for (const coinOp of coinOps) {
            const coin = { ...coinOp.updateOne.filter, ...coinOp.updateOne.update.$set };
            const { address, chain, network } = coin;
            batch.push({ address, chain, network });
        }
        for (const payload of batch) {
            const { address, chain, network } = payload;
            const lowerAddress = address.toLowerCase();
            const cacheKey = `getBalanceForAddress-${chain}-${network}-${lowerAddress}`;
            await cache_1.CacheStorage.expire(cacheKey);
        }
    }
    async addTransactions(params) {
        const { blockTimeNormalized, chain, height, network, parentChain, forkHeight } = params;
        if (parentChain && forkHeight && height != undefined && height < forkHeight) {
            const parentTxs = await exports.XrpTransactionStorage.collection
                .find({ blockHeight: height, chain: parentChain, network })
                .toArray();
            return parentTxs.map(parentTx => {
                return {
                    updateOne: {
                        filter: { txid: parentTx.txid, chain, network },
                        update: {
                            $set: {
                                ...parentTx,
                                wallets: new Array()
                            }
                        },
                        upsert: true,
                        forceServerObjectId: true
                    }
                };
            });
        }
        else {
            return Promise.all(params.txs.map(async (tx) => {
                const { txid, wallets } = tx;
                return {
                    updateOne: {
                        filter: { txid, chain, network },
                        update: {
                            $set: {
                                ...tx,
                                blockTimeNormalized: tx.blockTimeNormalized || blockTimeNormalized,
                                wallets
                            }
                        },
                        upsert: true,
                        forceServerObjectId: true
                    }
                };
            }));
        }
    }
    async addCoins(params) {
        const { chain, height, network, parentChain, forkHeight } = params;
        if (parentChain && forkHeight && height != undefined && height < forkHeight) {
            const parentChainCoins = await coin_1.CoinStorage.collection
                .find({ blockHeight: height, chain: parentChain, network })
                .toArray();
            return parentChainCoins.map((coin) => {
                const { mintTxid } = coin;
                return {
                    updateOne: {
                        filter: {
                            mintTxid,
                            mintIndex: coin.mintIndex,
                            chain,
                            network
                        },
                        update: {
                            $set: {
                                ...coin,
                                wallets: []
                            }
                        },
                        upsert: true,
                        forceServerObjectId: true
                    }
                };
            });
        }
        else {
            return params.coins.map((coin) => {
                const { mintTxid, wallets, address } = coin;
                return {
                    updateOne: {
                        filter: {
                            mintTxid,
                            mintIndex: coin.mintIndex,
                            chain,
                            network
                        },
                        update: {
                            $set: {
                                chain,
                                network,
                                address,
                                mintHeight: coin.mintHeight || height,
                                coinbase: coin.coinbase,
                                value: coin.value
                            },
                            $setOnInsert: {
                                wallets
                            }
                        },
                        upsert: true,
                        forceServerObjectId: true
                    }
                };
            });
        }
    }
    getTransactions(params) {
        const originalQuery = params.query;
        const { query, options } = storage_1.Storage.getFindOptions(this, params.options);
        const finalQuery = Object.assign({}, originalQuery, query);
        return this.collection.find(finalQuery, options).addCursorFlag('noCursorTimeout', true);
    }
    _apiTransform(tx, options) {
        const transaction = {
            txid: tx.txid || '',
            network: tx.network || '',
            chain: tx.chain || '',
            blockHeight: (0, utils_1.valueOrDefault)(tx.blockHeight, -1),
            blockHash: tx.blockHash || '',
            blockTime: tx.blockTime ? tx.blockTime.toISOString() : '',
            blockTimeNormalized: tx.blockTimeNormalized ? tx.blockTimeNormalized.toISOString() : '',
            fee: (0, utils_1.valueOrDefault)(tx.fee, -1),
            value: (0, utils_1.valueOrDefault)(tx.value, -1),
            from: tx.from || '',
            nonce: (0, utils_1.valueOrDefault)(tx.nonce, -1),
            to: (0, utils_1.valueOrDefault)(tx.to, ''),
            currency: (0, utils_1.valueOrDefault)(tx.currency, 'XRP'),
            invoiceID: (0, utils_1.valueOrDefault)(tx.invoiceID, '')
        };
        if (options && options.object) {
            return transaction;
        }
        return JSON.stringify(transaction);
    }
};
exports.XrpTransactionModel = XrpTransactionModel;
exports.XrpTransactionModel = XrpTransactionModel = __decorate([
    Loggify_1.LoggifyClass
], XrpTransactionModel);
exports.XrpTransactionStorage = new XrpTransactionModel();
//# sourceMappingURL=transaction.js.map