"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MerkleTree = exports.pedersenLeftRight = void 0;
// @ts-ignore
const bb_js_1 = require("@aztec/bb.js");
// @ts-ignore -- no types
async function pedersenLeftRight(barretenberg, left, right) {
    let leftBuffer = bb_js_1.Fr.fromBufferReduce(Buffer.from(left.slice(2), 'hex'));
    let rightBuffer = bb_js_1.Fr.fromBufferReduce(Buffer.from(right.slice(2), 'hex'));
    let hashRes = await barretenberg.pedersenHashWithHashIndex([leftBuffer, rightBuffer], 0);
    return hashRes.toString();
}
exports.pedersenLeftRight = pedersenLeftRight;
class MerkleTree {
    constructor(levels, barretenberg, defaultLeaves = [], hashLeftRight = pedersenLeftRight) {
        this.zeroValue = "0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720"; // sha256("Momiji") % Fr.MODULUS
        this.getAllLeaves = () => Array.from(this.storage.values()).slice(0, 2 ** this.levels);
        this.levels = levels;
        this.hashLeftRight = hashLeftRight;
        this.storage = new Map();
        this.zeros = [];
        this.totalLeaves = 0;
        this.barretenberg = barretenberg;
        this.defaultLeaves = defaultLeaves;
    }
    async init() {
        // build zeros depends on tree levels
        let currentZero = this.zeroValue;
        this.zeros.push(currentZero);
        for (let i = 0; i < this.levels; i++) {
            currentZero = await this.hashLeftRight(this.barretenberg, currentZero, currentZero);
            this.zeros.push(currentZero);
        }
        if (this.defaultLeaves.length > 0) {
            this.totalLeaves = this.defaultLeaves.length;
            // store leaves with key value pair
            let level = 0;
            this.defaultLeaves.forEach((leaf, index) => {
                this.storage.set(MerkleTree.indexToKey(level, index), leaf);
            });
            // build tree with initial leaves
            level++;
            let numberOfNodesInLevel = Math.ceil(this.totalLeaves / 2);
            for (level; level <= this.levels; level++) {
                for (let i = 0; i < numberOfNodesInLevel; i++) {
                    const leftKey = MerkleTree.indexToKey(level - 1, 2 * i);
                    const rightKey = MerkleTree.indexToKey(level - 1, 2 * i + 1);
                    const left = this.storage.get(leftKey);
                    const right = this.storage.get(rightKey) || this.zeros[level - 1];
                    if (!left)
                        throw new Error("leftKey not found");
                    const node = await this.hashLeftRight(this.barretenberg, left, right);
                    this.storage.set(MerkleTree.indexToKey(level, i), node);
                }
                numberOfNodesInLevel = Math.ceil(numberOfNodesInLevel / 2);
            }
        }
    }
    static indexToKey(level, index) {
        return `${level}-${index}`;
    }
    getIndex(leaf) {
        for (const [key, value] of this.storage) {
            if (value === leaf) {
                return Number(key.split("-")[1]);
            }
        }
        return -1;
    }
    root() {
        return this.storage.get(MerkleTree.indexToKey(this.levels, 0)) || this.zeros[this.levels];
    }
    async proof(indexOfLeaf) {
        let pathElements = [];
        let pathIndices = [];
        const leaf = this.storage.get(MerkleTree.indexToKey(0, indexOfLeaf));
        if (!leaf)
            throw new Error("leaf not found");
        // store sibling into pathElements and target's indices into pathIndices
        const handleIndex = (level, currentIndex, siblingIndex) => {
            const siblingValue = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level];
            pathElements.push(siblingValue);
            pathIndices.push(currentIndex % 2);
        };
        await this.traverse(indexOfLeaf, handleIndex);
        return {
            root: this.root(),
            pathElements,
            pathIndices,
            leaf: leaf,
        };
    }
    async insert(leaf) {
        const index = this.totalLeaves;
        await this.update(index, leaf, true);
        this.totalLeaves++;
    }
    async update(index, newLeaf, isInsert = false) {
        if (!isInsert && index >= this.totalLeaves) {
            throw Error("Use insert method for new elements.");
        }
        else if (isInsert && index < this.totalLeaves) {
            throw Error("Use update method for existing elements.");
        }
        let keyValueToStore = [];
        let currentElement = newLeaf;
        const handleIndex = async (level, currentIndex, siblingIndex) => {
            const siblingElement = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level];
            let left;
            let right;
            if (currentIndex % 2 === 0) {
                left = currentElement;
                right = siblingElement;
            }
            else {
                left = siblingElement;
                right = currentElement;
            }
            keyValueToStore.push({
                key: MerkleTree.indexToKey(level, currentIndex),
                value: currentElement,
            });
            currentElement = await this.hashLeftRight(this.barretenberg, left, right);
        };
        await this.traverse(index, handleIndex);
        // push root to the end
        keyValueToStore.push({
            key: MerkleTree.indexToKey(this.levels, 0),
            value: currentElement,
        });
        keyValueToStore.forEach((o) => {
            this.storage.set(o.key, o.value);
        });
    }
    // traverse from leaf to root with handler for target node and sibling node
    async traverse(indexOfLeaf, handler) {
        let currentIndex = indexOfLeaf;
        for (let i = 0; i < this.levels; i++) {
            let siblingIndex;
            if (currentIndex % 2 === 0) {
                siblingIndex = currentIndex + 1;
            }
            else {
                siblingIndex = currentIndex - 1;
            }
            await handler(i, currentIndex, siblingIndex);
            currentIndex = Math.floor(currentIndex / 2);
        }
    }
}
exports.MerkleTree = MerkleTree;