import { toRootHex } from "@lodestar/utils";
import { MapTracker } from "./mapMetrics.js";
const MAX_STATES = 3 * 32;
/**
 * Old implementation of StateCache (used to call `StateContextCache`)
 * - Prune per checkpoint so number of states ranges from 96 to 128
 * - Keep a separate head state to make sure it is always available
 */
export class BlockStateCacheImpl {
    /**
     * Max number of states allowed in the cache
     */
    maxStates;
    cache;
    /** Epoch -> Set<blockRoot> */
    epochIndex = new Map();
    metrics;
    /**
     * Strong reference to prevent head state from being pruned.
     * null if head state is being regen and not available at the moment.
     */
    head = null;
    constructor({ maxStates = MAX_STATES, metrics }) {
        this.maxStates = maxStates;
        this.cache = new MapTracker(metrics?.stateCache);
        if (metrics) {
            this.metrics = metrics.stateCache;
            metrics.stateCache.size.addCollect(() => metrics.stateCache.size.set(this.cache.size));
        }
    }
    get(rootHex, opts) {
        this.metrics?.lookups.inc();
        const item = this.head?.stateRoot === rootHex ? this.head.state : this.cache.get(rootHex);
        if (!item) {
            return null;
        }
        this.metrics?.hits.inc();
        this.metrics?.stateClonedCount.observe(item.clonedCount);
        return item.clone(opts?.dontTransferCache);
    }
    add(item) {
        const key = toRootHex(item.hashTreeRoot());
        if (this.cache.get(key)) {
            return;
        }
        this.metrics?.adds.inc();
        this.cache.set(key, item);
        const epoch = item.epochCtx.epoch;
        const blockRoots = this.epochIndex.get(epoch);
        if (blockRoots) {
            blockRoots.add(key);
        }
        else {
            this.epochIndex.set(epoch, new Set([key]));
        }
    }
    setHeadState(item) {
        if (item) {
            const key = toRootHex(item.hashTreeRoot());
            this.head = { state: item, stateRoot: key };
        }
        else {
            this.head = null;
        }
    }
    /**
     * Get a seed state for state reload.
     * This is to conform to the api only as this cache is not used in n-historical state.
     * See ./fifoBlockStateCache.ts for implementation
     */
    getSeedState() {
        throw Error("Not implemented for BlockStateCacheImpl");
    }
    clear() {
        this.cache.clear();
        this.epochIndex.clear();
    }
    get size() {
        return this.cache.size;
    }
    /**
     * TODO make this more robust.
     * Without more thought, this currently breaks our assumptions about recent state availablity
     */
    prune(headStateRootHex) {
        const keys = Array.from(this.cache.keys());
        if (keys.length > this.maxStates) {
            // object keys are stored in insertion order, delete keys starting from the front
            for (const key of keys.slice(0, keys.length - this.maxStates)) {
                if (key !== headStateRootHex) {
                    const item = this.cache.get(key);
                    if (item) {
                        this.epochIndex.get(item.epochCtx.epoch)?.delete(key);
                        this.cache.delete(key);
                    }
                }
            }
        }
    }
    /**
     * Prune per finalized epoch.
     */
    deleteAllBeforeEpoch(finalizedEpoch) {
        for (const epoch of this.epochIndex.keys()) {
            if (epoch < finalizedEpoch) {
                this.deleteAllEpochItems(epoch);
            }
        }
    }
    /** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
    dumpSummary() {
        return Array.from(this.cache.entries()).map(([key, state]) => ({
            slot: state.slot,
            root: toRootHex(state.hashTreeRoot()),
            reads: this.cache.readCount.get(key) ?? 0,
            lastRead: this.cache.lastRead.get(key) ?? 0,
            checkpointState: false,
        }));
    }
    getStates() {
        return this.cache.values();
    }
    deleteAllEpochItems(epoch) {
        for (const rootHex of this.epochIndex.get(epoch) || []) {
            this.cache.delete(rootHex);
        }
        this.epochIndex.delete(epoch);
    }
}
//# sourceMappingURL=blockStateCacheImpl.js.map