import { Repository } from "@lodestar/db";
import { ssz } from "@lodestar/types";
import { bytesToInt } from "@lodestar/utils";
import { getSignedBlockTypeFromBytes } from "../../util/multifork.js";
import { Bucket, getBucketNameByValue } from "../buckets.js";
import { deleteParentRootIndex, deleteRootIndex, getParentRootIndex, getRootIndex, storeParentRootIndex, storeRootIndex, } from "./blockArchiveIndex.js";
/**
 * Stores finalized blocks. Block slot is identifier.
 */
export class BlockArchiveRepository extends Repository {
    constructor(config, db) {
        const bucket = Bucket.allForks_blockArchive;
        const type = ssz.phase0.SignedBeaconBlock; // Pick some type but won't be used
        super(config, db, bucket, type, getBucketNameByValue(bucket));
    }
    // Overrides for multi-fork
    encodeValue(value) {
        return this.config.getForkTypes(value.message.slot).SignedBeaconBlock.serialize(value);
    }
    decodeValue(data) {
        return getSignedBlockTypeFromBytes(this.config, data).deserialize(data);
    }
    // Handle key as slot
    getId(value) {
        return value.message.slot;
    }
    decodeKey(data) {
        return bytesToInt(super.decodeKey(data), "be");
    }
    // Overrides to index
    async put(key, value) {
        const blockRoot = this.config.getForkTypes(value.message.slot).BeaconBlock.hashTreeRoot(value.message);
        const slot = value.message.slot;
        await Promise.all([
            super.put(key, value),
            storeRootIndex(this.db, slot, blockRoot),
            storeParentRootIndex(this.db, slot, value.message.parentRoot),
        ]);
    }
    async batchPut(items) {
        await Promise.all([
            super.batchPut(items),
            Array.from(items).map((item) => {
                const slot = item.value.message.slot;
                const blockRoot = this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(item.value.message);
                return storeRootIndex(this.db, slot, blockRoot);
            }),
            Array.from(items).map((item) => {
                const slot = item.value.message.slot;
                const parentRoot = item.value.message.parentRoot;
                return storeParentRootIndex(this.db, slot, parentRoot);
            }),
        ]);
    }
    async batchPutBinary(items) {
        await Promise.all([
            super.batchPutBinary(items),
            Array.from(items).map((item) => storeRootIndex(this.db, item.slot, item.blockRoot)),
            Array.from(items).map((item) => storeParentRootIndex(this.db, item.slot, item.parentRoot)),
        ]);
    }
    async remove(value) {
        await Promise.all([
            super.remove(value),
            deleteRootIndex(this.db, this.config.getForkTypes(value.message.slot).SignedBeaconBlock, value),
            deleteParentRootIndex(this.db, value),
        ]);
    }
    async batchRemove(values) {
        await Promise.all([
            super.batchRemove(values),
            Array.from(values).map((value) => deleteRootIndex(this.db, this.config.getForkTypes(value.message.slot).SignedBeaconBlock, value)),
            Array.from(values).map((value) => deleteParentRootIndex(this.db, value)),
        ]);
    }
    async *valuesStream(opts) {
        const firstSlot = this.getFirstSlot(opts);
        const valuesStream = super.valuesStream(opts);
        const step = opts?.step ?? 1;
        for await (const value of valuesStream) {
            if ((value.message.slot - firstSlot) % step === 0) {
                yield value;
            }
        }
    }
    async values(opts) {
        return await Array.fromAsync(this.valuesStream(opts));
    }
    // INDEX
    async getByRoot(root) {
        const slot = await this.getSlotByRoot(root);
        return slot !== null ? this.get(slot) : null;
    }
    async getBinaryEntryByRoot(root) {
        const slot = await this.getSlotByRoot(root);
        return slot !== null ? { key: slot, value: await this.getBinary(slot) } : null;
    }
    async getByParentRoot(root) {
        const slot = await this.getSlotByParentRoot(root);
        return slot !== null ? this.get(slot) : null;
    }
    async getSlotByRoot(root) {
        return this.parseSlot(await getRootIndex(this.db, root));
    }
    async getSlotByParentRoot(root) {
        return this.parseSlot(await getParentRootIndex(this.db, root));
    }
    parseSlot(slotBytes) {
        if (!slotBytes)
            return null;
        const slot = bytesToInt(slotBytes, "be");
        // TODO: Is this necessary? How can bytesToInt return a non-integer?
        return Number.isInteger(slot) ? slot : null;
    }
    getFirstSlot(opts) {
        const dbFilterOpts = this.dbFilterOptions(opts);
        const firstSlot = dbFilterOpts.gt
            ? this.decodeKey(dbFilterOpts.gt) + 1
            : dbFilterOpts.gte
                ? this.decodeKey(dbFilterOpts.gte)
                : null;
        if (firstSlot === null)
            throw Error("specify opts.gt or opts.gte");
        return firstSlot;
    }
}
//# sourceMappingURL=blockArchive.js.map