import { encodeEvmBlockPayload } from '../../../blockchain-provider/codecs/block-payload-codec';
import { StrategyNames } from './load-strategy.interface';
export class SubscribeWsProviderStrategy {
    constructor(log, blockchainProvider, queue, config) {
        this.log = log;
        this.blockchainProvider = blockchainProvider;
        this.queue = queue;
        this.name = StrategyNames.WS_SUBSCRIBE;
        this.tracesEnabled = config.tracesEnabled ?? false;
        this.verifyTrie = config.verifyTrie ?? false;
        this.catchUpBatchSize = config.catchUpBatchSize ?? 50;
    }
    async load(currentNetworkHeight) {
        return new Promise((resolve, reject) => {
            if (this._subscription) {
                resolve();
                return;
            }
            (async () => {
                try {
                    await this.performCatchUp(currentNetworkHeight);
                    this._subscription = this.blockchainProvider.subscribeToNewBlocks(async (block) => {
                        try {
                            if (this.queue.isMaxHeightReached)
                                return;
                            if (this.queue.isQueueFull) {
                                this._subscription?.unsubscribe();
                                reject(new Error('Queue full'));
                                return;
                            }
                            if (this.tracesEnabled) {
                                block.traces = await this.blockchainProvider.getTracesByBlockHeight(block.blockNumber);
                            }
                            await this.enqueueBlock(block);
                        }
                        catch (e) {
                            this._subscription?.unsubscribe();
                            reject(e);
                        }
                    }, true, this.verifyTrie);
                    this.log.debug('WS subscription started, waiting for blocks');
                    await this._subscription;
                    resolve();
                }
                catch (e) {
                    reject(e);
                }
                finally {
                    this.cleanup();
                }
            })();
        });
    }
    async stop() {
        this.log.debug('Stopping WS subscribe strategy');
        try {
            this._subscription?.unsubscribe();
        }
        catch {
            /* ignore */
        }
        this._subscription = undefined;
    }
    cleanup() {
        try {
            if (typeof this._subscription?.unsubscribe === 'function')
                this._subscription.unsubscribe();
        }
        catch {
            /* ignore */
        }
        this._subscription = undefined;
    }
    async performCatchUp(currentNetworkHeight) {
        const start = this.queue.lastHeight + 1;
        if (start > currentNetworkHeight)
            return;
        const gap = currentNetworkHeight - this.queue.lastHeight;
        if (gap <= this.catchUpBatchSize) {
            const heights = Array.from({ length: gap }, (_, i) => start + i);
            const blocks = await this.blockchainProvider.getManyBlocksWithReceipts(heights, true, this.verifyTrie);
            await this.attachTracesIfNeeded(blocks);
            for (const block of blocks)
                await this.enqueueBlock(block);
            return;
        }
        for (let batchStart = start; batchStart <= currentNetworkHeight; batchStart += this.catchUpBatchSize) {
            const batchEnd = Math.min(batchStart + this.catchUpBatchSize - 1, currentNetworkHeight);
            const heights = Array.from({ length: batchEnd - batchStart + 1 }, (_, i) => batchStart + i);
            const blocks = await this.blockchainProvider.getManyBlocksWithReceipts(heights, true, this.verifyTrie);
            await this.attachTracesIfNeeded(blocks);
            for (const block of blocks)
                await this.enqueueBlock(block);
        }
    }
    async attachTracesIfNeeded(blocks) {
        if (!this.tracesEnabled)
            return;
        for (const block of blocks) {
            block.traces = await this.blockchainProvider.getTracesByBlockHeight(block.blockNumber);
        }
    }
    getRequiredBlockSize(block) {
        if (typeof block.size !== 'number' || !Number.isFinite(block.size) || block.size < 0) {
            throw new Error(`EVM block ${block.blockNumber} (${block.hash}) is missing required size`);
        }
        return block.size;
    }
    async enqueueBlock(block) {
        if (block.blockNumber <= this.queue.lastHeight)
            return;
        const bytes = encodeEvmBlockPayload(block);
        const raw = {
            hash: block.hash,
            height: block.blockNumber,
            size: this.getRequiredBlockSize(block),
            bytes,
        };
        await this.queue.enqueue(raw);
    }
}
