"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SVMBlockFinder = void 0;
exports.averageBlockTime = averageBlockTime;
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const lodash_1 = require("lodash");
const BlockFinder_1 = require("../../utils/BlockFinder");
const TypeGuards_1 = require("../../utils/TypeGuards");
const TimeUtils_1 = require("../../utils/TimeUtils");
const constants_1 = require("../../constants");
const utils_1 = require("./utils");
const now = (0, TimeUtils_1.getCurrentTime)();
const averageBlockTimes = {
    [constants_1.CHAIN_IDs.SOLANA]: { average: 0.4, timestamp: now, blockRange: 1 },
};
function averageBlockTime() {
    return averageBlockTimes[constants_1.CHAIN_IDs.SOLANA];
}
function estimateBlocksElapsed(seconds, cushionPercentage = 0.0, _provider) {
    const cushionMultiplier = cushionPercentage + 1.0;
    const { average } = averageBlockTime();
    return Math.floor((seconds * cushionMultiplier) / average);
}
class SVMBlockFinder extends BlockFinder_1.BlockFinder {
    provider;
    blocks;
    logger;
    constructor(provider, blocks = [], logger) {
        super();
        this.provider = provider;
        this.blocks = blocks;
        this.logger = logger;
    }
    async getBlockForTimestamp(timestamp, hints = {}) {
        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) {
            const block = await this.getLatestBlock();
            if (timestamp >= block.timestamp)
                return block;
        }
        await Promise.all(Object.values(hints)
            .filter(TypeGuards_1.isDefined)
            .map((blockNumber) => this.getBlock(blockNumber)));
        if (this.blocks[0].timestamp > timestamp) {
            const initialBlock = this.blocks[0];
            const cushion = 1;
            const incrementDistance = Math.max(estimateBlocksElapsed(initialBlock.timestamp - timestamp, cushion, this.provider), 1);
            for (let multiplier = 1;; multiplier++) {
                const distance = multiplier * incrementDistance;
                const blockNumber = Math.max(0, initialBlock.number - distance);
                const block = await this.getBlock(blockNumber);
                if (block.timestamp <= timestamp)
                    break;
                (0, assert_1.default)(blockNumber > 0, "timestamp is before block 0");
            }
        }
        const index = (0, lodash_1.sortedIndexBy)(this.blocks, { timestamp }, "timestamp");
        return this.findBlock(this.blocks[index - 1], this.blocks[index], timestamp);
    }
    getBlockTime(slot) {
        const opts = (0, TypeGuards_1.isDefined)(slot) ? { slot } : undefined;
        return (0, utils_1.getNearestSlotTime)(this.provider, opts, this.logger);
    }
    async getLatestBlock() {
        const { slot, timestamp } = await this.getBlockTime();
        const block = {
            timestamp,
            number: Number(slot),
        };
        const index = (0, lodash_1.sortedIndexBy)(this.blocks, block, "number");
        if (this.blocks[index]?.number !== block.number)
            this.blocks.splice(index, 0, block);
        return this.blocks[index];
    }
    async getBlock(number) {
        let index = (0, lodash_1.sortedIndexBy)(this.blocks, { number }, "number");
        if (this.blocks[index]?.number === number)
            return this.blocks[index];
        const { slot: _slot, timestamp } = await this.getBlockTime(BigInt(number));
        const slot = Number(_slot);
        const block = {
            timestamp,
            number: slot,
        };
        index = (0, lodash_1.sortedIndexBy)(this.blocks, { number: slot }, "number");
        if (this.blocks[index]?.number === slot)
            return this.blocks[index];
        this.blocks.splice(index, 0, block);
        return block;
    }
    async findBlock(_startSlot, _endSlot, timestamp) {
        const [startBlock, endBlock] = [_startSlot, _endSlot];
        if (endBlock.timestamp === timestamp)
            return endBlock;
        if (endBlock.number === startBlock.number + 1)
            return 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");
        const totalTimeDifference = endBlock.timestamp - startBlock.timestamp;
        const totalBlockDistance = endBlock.number - startBlock.number;
        const blockPercentile = (timestamp - startBlock.timestamp) / totalTimeDifference;
        const estimatedBlock = startBlock.number + Math.round(blockPercentile * totalBlockDistance);
        const newBlock = await this.getBlock((0, lodash_1.clamp)(estimatedBlock, startBlock.number + 1, endBlock.number - 1));
        if (newBlock.timestamp < timestamp) {
            return this.findBlock(newBlock, endBlock, timestamp);
        }
        else {
            return this.findBlock(startBlock, newBlock, timestamp);
        }
    }
}
exports.SVMBlockFinder = SVMBlockFinder;
//# sourceMappingURL=BlockUtils.js.map