"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.CoinStorage = exports.CoinModel = void 0;
const Loggify_1 = require("../decorators/Loggify");
const logger_1 = __importDefault(require("../logger"));
const libs_1 = require("../providers/libs");
const utils_1 = require("../utils");
const base_1 = require("./base");
const block_1 = require("./block");
let CoinModel = class CoinModel extends base_1.BaseModel {
    constructor(storage) {
        super('coins', storage);
        this.allowedPaging = [
            { key: 'mintHeight', type: 'number' },
            { key: 'spentHeight', type: 'number' }
        ];
    }
    onConnect() {
        this.collection.createIndex({ mintTxid: 1, mintIndex: 1 }, { background: true });
        this.collection.createIndex({ address: 1, chain: 1, network: 1 }, {
            background: true,
            partialFilterExpression: {
                spentHeight: { $lt: 0 }
            }
        });
        this.collection.createIndex({ address: 1 }, { background: true });
        this.collection.createIndex({ chain: 1, network: 1, mintHeight: 1 }, { background: true });
        this.collection.createIndex({ spentTxid: 1 }, { background: true, sparse: true });
        this.collection.createIndex({ chain: 1, network: 1, spentHeight: 1 }, { background: true });
        this.collection.createIndex({ wallets: 1, spentHeight: 1, value: 1, mintHeight: 1 }, { background: true, partialFilterExpression: { 'wallets.0': { $exists: true } } });
        this.collection.createIndex({ wallets: 1, spentTxid: 1 }, { background: true, partialFilterExpression: { 'wallets.0': { $exists: true } } });
        this.collection.createIndex({ wallets: 1, mintTxid: 1 }, { background: true, partialFilterExpression: { 'wallets.0': { $exists: true } } });
    }
    async getBalance(params, options = {}) {
        const { query } = params;
        const result = await this.collection
            .aggregate([
            { $match: query },
            {
                $project: {
                    value: 1,
                    status: {
                        $cond: {
                            if: { $gte: ['$mintHeight', 0 /* SpentHeightIndicators.minimum */] },
                            then: 'confirmed',
                            else: 'unconfirmed'
                        }
                    },
                    _id: 0
                }
            },
            {
                $group: {
                    _id: '$status',
                    balance: { $sum: '$value' }
                }
            }
        ], options)
            .toArray();
        return result.reduce((acc, cur) => {
            acc[cur._id] = cur.balance;
            acc.balance += cur.balance;
            return acc;
        }, { confirmed: 0, unconfirmed: 0, balance: 0 });
    }
    async getBalanceAtTime(params) {
        const { query, time, chain, network } = params;
        const [block] = await block_1.BitcoinBlockStorage.collection
            .find({
            $query: {
                chain,
                network,
                timeNormalized: { $lte: new Date(time) }
            }
        })
            .limit(1)
            .sort({ timeNormalized: -1 })
            .toArray();
        const blockHeight = block.height;
        const combinedQuery = Object.assign({}, {
            $or: [{ spentHeight: { $gt: blockHeight } }, { spentHeight: { $lt: 0 /* SpentHeightIndicators.minimum */ } }],
            mintHeight: { $lte: blockHeight }
        }, query);
        return this.getBalance({ query: combinedQuery }, { hint: { wallets: 1, spentHeight: 1, value: 1, mintHeight: 1 } });
    }
    resolveAuthhead(mintTxid, chain, network) {
        return this.collection
            .aggregate([
            {
                $match: {
                    mintTxid: mintTxid.toLowerCase(),
                    mintIndex: 0,
                    ...(typeof chain === 'string' ? { chain } : {}),
                    ...(typeof network === 'string' ? { network } : {})
                }
            },
            {
                $graphLookup: {
                    from: 'coins',
                    startWith: '$spentTxid',
                    connectFromField: 'spentTxid',
                    connectToField: 'mintTxid',
                    as: 'authheads',
                    maxDepth: 1000000,
                    restrictSearchWithMatch: {
                        mintIndex: 0
                    }
                }
            },
            {
                $project: {
                    chain: '$chain',
                    network: '$network',
                    authbase: '$mintTxid',
                    identityOutputs: {
                        $filter: {
                            input: '$authheads',
                            as: 'authhead',
                            cond: {
                                $and: [
                                    {
                                        $lte: ['$$authhead.spentHeight', -1]
                                    },
                                    {
                                        $eq: ['$$authhead.chain', '$chain']
                                    },
                                    {
                                        $eq: ['$$authhead.network', '$network']
                                    }
                                ]
                            }
                        }
                    }
                }
            }
        ])
            .toArray();
    }
    _apiTransform(coin, options) {
        // try to parse coin.address if its 'false' and script exists
        if (coin.address == 'false' && coin.script != undefined && coin.script.toString() != '') {
            try {
                const lib = libs_1.Libs.get(coin.chain).lib;
                const address = lib
                    .Script(coin.script.toString('hex'))
                    .toAddress(coin.network)
                    .toString();
                if (lib.Address.isValid(address, coin.network)) {
                    coin.address = address;
                    // update coin record in db - do it asynchronously as we don't need to wait for result
                    exports.CoinStorage.collection.updateOne({ _id: coin._id }, { $set: { address: coin.address } });
                }
            }
            catch {
                logger_1.default.debug(`Could not parse address on "${coin.chain}:${coin.network}" for coin ${coin.mintTxid}[${coin.mintIndex}]`);
            }
        }
        const transform = {
            chain: (0, utils_1.valueOrDefault)(coin.chain, ''),
            network: (0, utils_1.valueOrDefault)(coin.network, ''),
            coinbase: (0, utils_1.valueOrDefault)(coin.coinbase, false),
            mintIndex: (0, utils_1.valueOrDefault)(coin.mintIndex, -1),
            spentTxid: (0, utils_1.valueOrDefault)(coin.spentTxid, ''),
            mintTxid: (0, utils_1.valueOrDefault)(coin.mintTxid, ''),
            mintHeight: (0, utils_1.valueOrDefault)(coin.mintHeight, -1),
            spentHeight: (0, utils_1.valueOrDefault)(coin.spentHeight, -4 /* SpentHeightIndicators.error */),
            address: (0, utils_1.valueOrDefault)(coin.address, ''),
            script: (0, utils_1.valueOrDefault)(coin.script, Buffer.alloc(0)).toString('hex'),
            value: (0, utils_1.valueOrDefault)(coin.value, -1),
            confirmations: (0, utils_1.valueOrDefault)(coin.confirmations ?? options?.confirmations, -1),
            sequenceNumber: (0, utils_1.valueOrDefault)(coin.sequenceNumber, undefined)
        };
        if (options && options.object) {
            return transform;
        }
        return JSON.stringify(transform);
    }
};
exports.CoinModel = CoinModel;
exports.CoinModel = CoinModel = __decorate([
    Loggify_1.LoggifyClass
], CoinModel);
exports.CoinStorage = new CoinModel();
//# sourceMappingURL=coin.js.map