"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Network = void 0;
const cqrs_1 = require("@easylayer/common/cqrs");
const blockchain_structure_1 = require("./blockchain.structure");
const network_1 = require("../../events/network");
const errors_1 = require("../errors");
/**
 * Network aggregate for EVM blockchain storage.
 *
 * Memory Strategy: circular buffer with O(1) height lookups.
 * Stores LightBlock (hashes only) — no transaction bodies, no receipts.
 *
 * lastBlockHeight is intentionally NOT overridden here.
 * It is managed exclusively by AggregateRoot (via events blockHeight field),
 * which is the same pattern as the Bitcoin Network aggregate.
 */
class Network extends cqrs_1.AggregateRoot {
    __maxSize;
    chain;
    constructor({ maxSize, aggregateId, blockHeight, options, }) {
        super(aggregateId, blockHeight, options);
        this.__maxSize = maxSize;
        this.chain = new blockchain_structure_1.Blockchain({ maxSize, baseBlockHeight: blockHeight });
    }
    // ===== GETTERS =====
    getLastBlock() {
        return this.chain.lastBlock;
    }
    getBlockByHeight(height) {
        return this.chain.findBlockByHeight(height);
    }
    getLastNBlocks(count) {
        return this.chain.getLastNBlocks(count);
    }
    getAllBlocks() {
        return this.chain.toArray();
    }
    // ===== SNAPSHOTS =====
    serializeUserState() {
        return { maxSize: this.__maxSize, chain: this.chain.toArray() };
    }
    restoreUserState(state) {
        if (typeof state?.maxSize === 'number')
            this.__maxSize = state.maxSize;
        this.chain = new blockchain_structure_1.Blockchain({ maxSize: this.__maxSize, baseBlockHeight: super.lastBlockHeight });
        if (Array.isArray(state?.chain))
            this.chain.fromArray(state.chain);
        Object.setPrototypeOf(this, Network.prototype);
    }
    // ===== COMMANDS =====
    async init({ requestId, startHeight, currentNetworkHeight, logger, }) {
        this.apply(new network_1.EvmNetworkInitializedEvent({ aggregateId: this.aggregateId, requestId, blockHeight: startHeight }, {}));
        logger?.log('Network successfully initialized', {
            module: 'network-model',
            args: {
                lastIndexedHeight: startHeight,
                nextBlockToProcess: startHeight + 1,
                currentNetworkHeight,
            },
        });
    }
    async clearChain({ requestId }) {
        this.apply(new network_1.EvmNetworkClearedEvent({ aggregateId: this.aggregateId, requestId, blockHeight: -1 }, {}));
    }
    async addBlocks({ blocks, requestId, logger, }) {
        if (!this.chain.validateNextBlocks(blocks)) {
            throw new errors_1.BlockchainValidationError();
        }
        const blockHeight = blocks[blocks.length - 1]?.blockNumber ?? -1;
        this.apply(new network_1.EvmNetworkBlocksAddedEvent({
            aggregateId: this.aggregateId,
            requestId,
            blockHeight,
        }, { blocks }));
        logger?.log('Blocks successfully added', {
            module: 'network-model',
            args: { blockHeight },
        });
    }
    async reorganisation({ reorgHeight, requestId, service, blocks, logger, }) {
        if (reorgHeight < 0) {
            throw new Error('Reorganisation failed: reached genesis without fork point');
        }
        const localBlock = this.chain.findBlockByHeight(reorgHeight);
        const remoteBlock = await service.getOneBlockByHeight(reorgHeight);
        const hasLocal = localBlock != null;
        const hasRemote = remoteBlock != null;
        if (hasLocal && hasRemote) {
            const isFork = remoteBlock.hash === localBlock.hash && remoteBlock.parentHash === localBlock.parentHash;
            if (isFork) {
                this.apply(new network_1.EvmNetworkReorganizedEvent({ aggregateId: this.aggregateId, blockHeight: reorgHeight, requestId }, { blocks }));
                logger?.log('Blocks successfully reorganized', {
                    module: 'network-model',
                    args: { blockHeight: reorgHeight },
                });
                return;
            }
        }
        const newBlocks = hasLocal ? [...blocks, localBlock] : blocks;
        return this.reorganisation({
            reorgHeight: reorgHeight - 1,
            requestId,
            service,
            blocks: newBlocks,
            logger,
        });
    }
    // ===== EVENT HANDLERS =====
    onEvmNetworkInitializedEvent({ blockHeight }) {
        this.chain.truncateToBlock(Number(blockHeight));
    }
    onEvmNetworkBlocksAddedEvent({ payload }) {
        const { blocks } = payload;
        const incomingLastHash = blocks[blocks.length - 1]?.hash;
        if (incomingLastHash && incomingLastHash === this.chain.lastBlockHash)
            return; // idempotent
        this.chain.addBlocks(blocks.map((b) => ({
            blockNumber: Number(b.blockNumber),
            hash: b.hash,
            parentHash: b.parentHash || '',
            transactions: b.transactions,
            receipts: b.receipts,
            transactionsRoot: b.transactionsRoot,
            receiptsRoot: b.receiptsRoot,
            stateRoot: b.stateRoot,
        })));
    }
    onEvmNetworkReorganizedEvent({ blockHeight }) {
        this.chain.truncateToBlock(Number(blockHeight));
    }
    onEvmNetworkClearedEvent(_) {
        this.chain.truncateToBlock(-1);
    }
}
exports.Network = Network;
