"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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var MempoolLoaderService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MempoolLoaderService = void 0;
const common_1 = require("@nestjs/common");
const uuid_1 = require("uuid");
/**
 * EVM MempoolLoaderService — two strategies:
 *
 * 'subscribe-ws': Subscribe to newPendingTransactions WS events.
 *   - Accumulate tx hashes in buffer
 *   - Periodically flush: batch-fetch metadata → dispatch RefreshMempoolCommand (mode=additive)
 *   - Eviction happens in AddBlocksBatchCommandHandler (cross-ref confirmed hashes)
 *
 * 'txpool-content': Poll txpool_content (Geth/Erigon).
 *   - Like bitcoin getrawmempool
 *   - Dispatch RefreshMempoolCommand (mode=snapshot)
 *   - Eviction automatic (not in next snapshot)
 *
 * Lifecycle mirrors bitcoin MempoolLoaderService:
 *   start() → enable + setup
 *   unlock() → allow next cycle
 *   refresh(height) → trigger new cycle
 */
let MempoolLoaderService = MempoolLoaderService_1 = class MempoolLoaderService {
    provider;
    executor;
    strategyName;
    log = new common_1.Logger(MempoolLoaderService_1.name);
    moduleName = 'blocks-queue';
    enabled = false;
    locked = false;
    lastKnownHeight = 0;
    // subscribe-ws mode
    pendingBuffer = new Set();
    subscription;
    flushTimer;
    flushIntervalMs;
    constructor(provider, executor, strategyName, flushIntervalMs = 5000) {
        this.provider = provider;
        this.executor = executor;
        this.strategyName = strategyName;
        this.flushIntervalMs = flushIntervalMs;
    }
    onModuleInit() {
        this.log.verbose('Mempool loader service initialized', {
            module: this.moduleName,
            args: { strategy: this.strategyName },
        });
    }
    async onModuleDestroy() {
        this.stop();
    }
    /** Called after EvmMempoolInitializedEvent. Idempotent. */
    async start() {
        if (this.enabled)
            return;
        if (!this.provider.isMempoolAvailable) {
            throw new Error(`Mempool loader strategy ${this.strategyName} is enabled but no compatible mempool provider is available`);
        }
        this.enabled = true;
        if (this.strategyName === 'subscribe-ws') {
            this.setupWsSubscription();
            this.flushTimer = setInterval(() => this.flush(), this.flushIntervalMs);
        }
        this.log.verbose('Mempool loader started', { module: this.moduleName, args: { strategy: this.strategyName } });
    }
    stop() {
        this.subscription?.unsubscribe();
        this.subscription = undefined;
        if (this.flushTimer) {
            clearInterval(this.flushTimer);
            this.flushTimer = undefined;
        }
        this.enabled = false;
        this.locked = false;
        this.pendingBuffer.clear();
    }
    /** External signal that current cycle finished — allow next cycle. */
    unlock() {
        if (this.locked) {
            this.locked = false;
            this.log.verbose('Mempool unlock', { module: this.moduleName });
        }
    }
    /**
     * Called after confirmProcessedBatch / reorganizeBlocks.
     * Updates lastKnownHeight and triggers next cycle.
     */
    async refresh(height) {
        if (!this.enabled)
            return;
        this.lastKnownHeight = height;
        this.unlock();
        if (this.strategyName === 'subscribe-ws') {
            await this.flush();
        }
        else {
            await this.pollTxpoolContent();
        }
    }
    // ===== SUBSCRIBE-WS STRATEGY =====
    setupWsSubscription() {
        try {
            this.subscription = this.provider.subscribeToPendingTransactions((txHash) => this.pendingBuffer.add(txHash));
        }
        catch (e) {
            this.log.warn('Failed to subscribe to pending transactions', {
                module: this.moduleName,
                args: { error: e.message },
            });
        }
    }
    async flush() {
        if (!this.enabled || this.locked || this.pendingBuffer.size === 0)
            return;
        const hashes = Array.from(this.pendingBuffer);
        this.pendingBuffer.clear();
        this.log.verbose('Mempool flush', { module: this.moduleName, args: { count: hashes.length } });
        // Batch fetch metadata
        const metas = [];
        // Fetch in parallel with a reasonable concurrency limit
        const CONCURRENCY = 20;
        for (let i = 0; i < hashes.length; i += CONCURRENCY) {
            const batch = hashes.slice(i, i + CONCURRENCY);
            const results = await Promise.allSettled(batch.map((hash) => this.provider.getPendingTransactionByHash(hash)));
            for (let j = 0; j < results.length; j++) {
                const result = results[j];
                if (result && result.status === 'fulfilled' && result.value) {
                    metas.push({ hash: batch[j], metadata: result.value });
                }
            }
        }
        if (metas.length === 0)
            return;
        const perProvider = { 'ws-stream': metas };
        try {
            await this.executor.handleSnapshot({
                requestId: (0, uuid_1.v4)(),
                height: this.lastKnownHeight,
                perProvider,
                mode: 'additive',
            });
            this.locked = true;
            this.log.verbose('Mempool additive snapshot dispatched', {
                module: this.moduleName,
                args: { count: metas.length },
            });
        }
        catch (e) {
            this.log.warn('Failed to dispatch mempool snapshot', { module: this.moduleName, args: { error: e.message } });
        }
    }
    // ===== TXPOOL-CONTENT STRATEGY =====
    async pollTxpoolContent() {
        if (!this.enabled || this.locked)
            return;
        try {
            const raw = await this.provider.getRawMempoolFromAll();
            if (!raw.length)
                return;
            const perProvider = {};
            for (const { providerName, value } of raw) {
                const items = Object.entries(value).map(([hash, meta]) => ({ hash, metadata: meta }));
                if (items.length > 0)
                    perProvider[providerName] = items;
            }
            if (Object.keys(perProvider).length === 0)
                return;
            await this.executor.handleSnapshot({
                requestId: (0, uuid_1.v4)(),
                height: this.lastKnownHeight,
                perProvider,
                mode: 'snapshot',
            });
            this.locked = true;
            this.log.verbose('Mempool snapshot dispatched (txpool-content)', {
                module: this.moduleName,
                args: { providers: Object.keys(perProvider).length },
            });
        }
        catch (e) {
            this.log.warn('txpool_content poll failed', { module: this.moduleName, args: { error: e.message } });
        }
    }
};
exports.MempoolLoaderService = MempoolLoaderService;
exports.MempoolLoaderService = MempoolLoaderService = MempoolLoaderService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(1, (0, common_1.Inject)('MempoolCommandExecutor')),
    __metadata("design:paramtypes", [Function, Object, String, Object])
], MempoolLoaderService);
