"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SVMBlockFinder = exports.averageBlockTime = void 0;
var tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert"));
var lodash_1 = require("lodash");
var BlockFinder_1 = require("../../utils/BlockFinder");
var TypeGuards_1 = require("../../utils/TypeGuards");
var TimeUtils_1 = require("../../utils/TimeUtils");
var constants_1 = require("../../constants");
var utils_1 = require("./utils");
var now = (0, TimeUtils_1.getCurrentTime)();
var averageBlockTimes = (_a = {},
    _a[constants_1.CHAIN_IDs.SOLANA] = { average: 0.4, timestamp: now, blockRange: 1 },
    _a);
function averageBlockTime() {
    return averageBlockTimes[constants_1.CHAIN_IDs.SOLANA];
}
exports.averageBlockTime = averageBlockTime;
function estimateBlocksElapsed(seconds, cushionPercentage, _provider) {
    if (cushionPercentage === void 0) { cushionPercentage = 0.0; }
    var cushionMultiplier = cushionPercentage + 1.0;
    var average = averageBlockTime().average;
    return Math.floor((seconds * cushionMultiplier) / average);
}
var SVMBlockFinder = (function (_super) {
    tslib_1.__extends(SVMBlockFinder, _super);
    function SVMBlockFinder(provider, blocks) {
        if (blocks === void 0) { blocks = []; }
        var _this = _super.call(this) || this;
        _this.provider = provider;
        _this.blocks = blocks;
        return _this;
    }
    SVMBlockFinder.prototype.getBlockForTimestamp = function (timestamp, hints) {
        if (hints === void 0) { hints = {}; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var block, initialBlock, cushion, incrementDistance, multiplier, distance, blockNumber, block, index;
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        timestamp = Number(timestamp);
                        (0, assert_1.default)(timestamp !== undefined && timestamp !== null, "timestamp must be provided");
                        if (!(this.blocks.length === 0 || this.blocks[this.blocks.length - 1].timestamp < timestamp)) return [3, 2];
                        return [4, this.getLatestBlock()];
                    case 1:
                        block = _a.sent();
                        if (timestamp >= block.timestamp)
                            return [2, block];
                        _a.label = 2;
                    case 2: return [4, Promise.all(Object.values(hints)
                            .filter(TypeGuards_1.isDefined)
                            .map(function (blockNumber) { return _this.getBlock(blockNumber); }))];
                    case 3:
                        _a.sent();
                        if (!(this.blocks[0].timestamp > timestamp)) return [3, 7];
                        initialBlock = this.blocks[0];
                        cushion = 1;
                        incrementDistance = Math.max(estimateBlocksElapsed(initialBlock.timestamp - timestamp, cushion, this.provider), 1);
                        multiplier = 1;
                        _a.label = 4;
                    case 4:
                        distance = multiplier * incrementDistance;
                        blockNumber = Math.max(0, initialBlock.number - distance);
                        return [4, this.getBlock(blockNumber)];
                    case 5:
                        block = _a.sent();
                        if (block.timestamp <= timestamp)
                            return [3, 7];
                        (0, assert_1.default)(blockNumber > 0, "timestamp is before block 0");
                        _a.label = 6;
                    case 6:
                        multiplier++;
                        return [3, 4];
                    case 7:
                        index = (0, lodash_1.sortedIndexBy)(this.blocks, { timestamp: timestamp }, "timestamp");
                        return [2, this.findBlock(this.blocks[index - 1], this.blocks[index], timestamp)];
                }
            });
        });
    };
    SVMBlockFinder.prototype.getBlockTime = function (slot) {
        var opts = (0, TypeGuards_1.isDefined)(slot) ? { slot: slot } : undefined;
        return (0, utils_1.getNearestSlotTime)(this.provider, opts);
    };
    SVMBlockFinder.prototype.getLatestBlock = function () {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _b, slot, timestamp, block, index;
            return tslib_1.__generator(this, function (_c) {
                switch (_c.label) {
                    case 0: return [4, this.getBlockTime()];
                    case 1:
                        _b = _c.sent(), slot = _b.slot, timestamp = _b.timestamp;
                        block = {
                            timestamp: timestamp,
                            number: Number(slot),
                        };
                        index = (0, lodash_1.sortedIndexBy)(this.blocks, block, "number");
                        if (((_a = this.blocks[index]) === null || _a === void 0 ? void 0 : _a.number) !== block.number)
                            this.blocks.splice(index, 0, block);
                        return [2, this.blocks[index]];
                }
            });
        });
    };
    SVMBlockFinder.prototype.getBlock = function (number) {
        var _a, _b;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var index, _c, _slot, timestamp, slot, block;
            return tslib_1.__generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        index = (0, lodash_1.sortedIndexBy)(this.blocks, { number: number }, "number");
                        if (((_a = this.blocks[index]) === null || _a === void 0 ? void 0 : _a.number) === number)
                            return [2, this.blocks[index]];
                        return [4, this.getBlockTime(BigInt(number))];
                    case 1:
                        _c = _d.sent(), _slot = _c.slot, timestamp = _c.timestamp;
                        slot = Number(_slot);
                        block = {
                            timestamp: timestamp,
                            number: slot,
                        };
                        index = (0, lodash_1.sortedIndexBy)(this.blocks, { number: slot }, "number");
                        if (((_b = this.blocks[index]) === null || _b === void 0 ? void 0 : _b.number) === slot)
                            return [2, this.blocks[index]];
                        this.blocks.splice(index, 0, block);
                        return [2, block];
                }
            });
        });
    };
    SVMBlockFinder.prototype.findBlock = function (_startSlot, _endSlot, timestamp) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _a, startBlock, endBlock, totalTimeDifference, totalBlockDistance, blockPercentile, estimatedBlock, newBlock;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        _a = [_startSlot, _endSlot], startBlock = _a[0], endBlock = _a[1];
                        if (endBlock.timestamp === timestamp)
                            return [2, endBlock];
                        if (endBlock.number === startBlock.number + 1)
                            return [2, startBlock];
                        (0, assert_1.default)(endBlock.number !== startBlock.number, "startBlock cannot equal endBlock");
                        (0, assert_1.default)(timestamp < endBlock.timestamp && timestamp > startBlock.timestamp, "timestamp not in between start and end blocks");
                        totalTimeDifference = endBlock.timestamp - startBlock.timestamp;
                        totalBlockDistance = endBlock.number - startBlock.number;
                        blockPercentile = (timestamp - startBlock.timestamp) / totalTimeDifference;
                        estimatedBlock = startBlock.number + Math.round(blockPercentile * totalBlockDistance);
                        return [4, this.getBlock((0, lodash_1.clamp)(estimatedBlock, startBlock.number + 1, endBlock.number - 1))];
                    case 1:
                        newBlock = _b.sent();
                        if (newBlock.timestamp < timestamp) {
                            return [2, this.findBlock(newBlock, endBlock, timestamp)];
                        }
                        else {
                            return [2, this.findBlock(startBlock, newBlock, timestamp)];
                        }
                        return [2];
                }
            });
        });
    };
    return SVMBlockFinder;
}(BlockFinder_1.BlockFinder));
exports.SVMBlockFinder = SVMBlockFinder;
//# sourceMappingURL=BlockUtils.js.map