"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MerkleTree = exports.EMPTY_MERKLE_ROOT = void 0;
// Inlined from @uma/common MerkleTree. Based on the Uniswap merkle-distributor implementation:
// https://github.com/Uniswap/merkle-distributor/blob/master/src/merkle-tree.ts
const bytes_1 = require("@ethersproject/bytes");
const keccak256_1 = require("@ethersproject/keccak256");
exports.EMPTY_MERKLE_ROOT = "0x0000000000000000000000000000000000000000000000000000000000000000";
const bufferToHex = (buf) => (0, bytes_1.hexlify)(buf);
const keccak256 = (buf) => Buffer.from((0, keccak256_1.keccak256)(buf).slice(2), "hex");
class MerkleTree {
    constructor(leaves, hashFn) {
        this.hashFn = hashFn;
        this.elements = leaves.map((leaf) => this.leafToBuf(leaf));
        this.elements.sort(Buffer.compare);
        this.elements = MerkleTree.bufDedup(this.elements);
        this.bufferElementPositionIndex = this.elements.reduce((memo, el, index) => {
            memo[bufferToHex(el)] = index;
            return memo;
        }, {});
        this.layers = this.getLayers(this.elements);
    }
    isEmpty() {
        return this.layers.length === 0;
    }
    getLayers(elements) {
        const layers = [];
        if (elements.length === 0)
            return layers;
        layers.push(elements);
        while (layers[layers.length - 1].length > 1) {
            layers.push(this.getNextLayer(layers[layers.length - 1]));
        }
        return layers;
    }
    getNextLayer(elements) {
        return elements.reduce((layer, el, idx, arr) => {
            if (idx % 2 === 0)
                layer.push(MerkleTree.combinedHash(el, arr[idx + 1]));
            return layer;
        }, []);
    }
    static combinedHash(first, second) {
        if (!first)
            return second;
        if (!second)
            return first;
        return keccak256(MerkleTree.sortAndConcat(first, second));
    }
    getRoot() {
        return this.layers[this.layers.length - 1][0];
    }
    getHexRoot() {
        if (this.isEmpty())
            return exports.EMPTY_MERKLE_ROOT;
        return bufferToHex(this.getRoot());
    }
    getProof(leaf) {
        return this.getProofRawBuf(this.leafToBuf(leaf));
    }
    getHexProof(leaf) {
        return this.getHexProofRawBuf(this.leafToBuf(leaf));
    }
    leafToBuf(element) {
        const hash = this.hashFn(element);
        const hexString = hash.startsWith("0x") ? hash.substring(2) : hash;
        return Buffer.from(hexString.toLowerCase(), "hex");
    }
    getProofRawBuf(element) {
        let idx = this.bufferElementPositionIndex[bufferToHex(element)];
        if (typeof idx !== "number")
            throw new Error("Element does not exist in Merkle tree");
        return this.layers.reduce((proof, layer) => {
            const pairElement = MerkleTree.getPairElement(idx, layer);
            if (pairElement)
                proof.push(pairElement);
            idx = Math.floor(idx / 2);
            return proof;
        }, []);
    }
    getHexProofRawBuf(el) {
        const proof = this.getProofRawBuf(el);
        return MerkleTree.bufArrToHexArr(proof);
    }
    static getPairElement(idx, layer) {
        const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
        return pairIdx < layer.length ? layer[pairIdx] : null;
    }
    static bufDedup(elements) {
        return elements.filter((el, idx) => idx === 0 || !elements[idx - 1].equals(el));
    }
    static bufArrToHexArr(arr) {
        if (arr.some((el) => !Buffer.isBuffer(el)))
            throw new Error("Array is not an array of buffers");
        return arr.map((el) => "0x" + el.toString("hex"));
    }
    static sortAndConcat(...args) {
        return Buffer.concat([...args].sort(Buffer.compare));
    }
}
exports.MerkleTree = MerkleTree;
