"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContractStorage = void 0;
/* eslint-disable import/no-extraneous-dependencies, no-console */
const lowdb_1 = __importDefault(require("lowdb"));
const FileSync_1 = __importDefault(require("lowdb/adapters/FileSync"));
const LocalStorage_1 = __importDefault(require("lowdb/adapters/LocalStorage"));
const path_1 = __importDefault(require("path"));
const logger_1 = require("../../ts/logger");
/**
 * @notice Contract storage keeps all deployed contracts with addresses, arguments in the json file.
 * This class is using for incremental deployment and verification.
 */
class ContractStorage {
    /**
     * Initialize class properties only once
     */
    constructor(storagePath) {
        /**
         * Set contract verification in the json file
         *
         * @param address - contract address
         * @param network - selected network
         * @param verified - verified or not
         */
        this.setVerified = (address, network, verified) => {
            this.db.set(`${network}.verified.${address}`, verified).write();
        };
        this.db = (0, lowdb_1.default)(typeof window !== "undefined"
            ? new LocalStorage_1.default("deployed-contracts")
            : new FileSync_1.default(storagePath ?? path_1.default.resolve(process.cwd(), "./deployed-contracts.json")));
    }
    /**
     * Get singleton object
     *
     * @param storagePath - path to the storage file
     * @returns {ContractStorage} singleton object
     */
    static getInstance(storagePath) {
        if (!ContractStorage.INSTANCE) {
            ContractStorage.INSTANCE = new ContractStorage(storagePath);
        }
        return ContractStorage.INSTANCE;
    }
    /**
     * Register contract and save contract address, constructor args in the json file
     *
     * @param {IRegisterContract} args - register arguments
     */
    async register({ id, key, contract, network, args, name, implementation, libraries, }) {
        const contractAddress = await contract.getAddress();
        const deploymentTx = contract.deploymentTransaction();
        const contractId = String(id);
        (0, logger_1.logMagenta)({ text: `*** ${contractId} ***\n` });
        (0, logger_1.logMagenta)({ text: `Network: ${network}` });
        (0, logger_1.logMagenta)({ text: `contract address: ${contractAddress}` });
        if (deploymentTx) {
            (0, logger_1.logMagenta)({ text: `tx: ${deploymentTx.hash}` });
            (0, logger_1.logMagenta)({ text: `deployer address: ${deploymentTx.from}` });
            (0, logger_1.logMagenta)({ text: `gas price: ${deploymentTx.gasPrice}` });
            (0, logger_1.logMagenta)({ text: `gas used: ${deploymentTx.gasLimit}` });
        }
        (0, logger_1.logMagenta)({ text: `\n******\n` });
        const logEntry = {
            id: contractId,
            deploymentTxHash: deploymentTx?.hash,
        };
        if (args !== undefined) {
            logEntry.verify = {
                name,
                impl: implementation,
                args: JSON.stringify(args),
                libraries: libraries ? JSON.stringify(libraries) : undefined,
            };
        }
        this.db.set(`${network}.instance.${contractAddress}`, logEntry).write();
        const namedEntry = this.db.get(`${network}.named.${contractId}${key !== undefined ? `.${key}` : ""}`).value();
        const count = namedEntry?.count ?? 0;
        this.db
            .set(`${network}.named.${contractId}${key !== undefined ? `.${key}` : ""}`, {
            address: contractAddress,
            count: count + 1,
        })
            .write();
    }
    /**
     * Get contract instances from the json file
     *
     * @param network - selected network
     * @returns {[string, IStorageInstanceEntry][]} storage instance entries
     */
    getInstances(network) {
        const collection = this.db.get(`${network}.instance`);
        const value = collection.value();
        return Object.entries(value || []);
    }
    /**
     * Check if contract is verified or not locally
     *
     * @param address - contract address
     * @param network - selected network
     * @returns contract verified or not
     */
    getVerified(address, network) {
        return this.db.get(`${network}.verified.${address}`).value();
    }
    /**
     * Get deployment arguments from the json file
     *
     * @param id - contract name
     * @param network - selected network
     * @param key - contract key
     * @returns deployment arguments
     */
    getContractArgs(id, network, key) {
        const address = this.getAddress(id, network, key);
        const collection = this.db.get(`${network}.instance.${address}`);
        const instanceEntry = collection.value();
        if (!instanceEntry?.verify?.args) {
            return undefined;
        }
        return JSON.parse(instanceEntry.verify.args);
    }
    /**
     * Get contract address by name from the json file
     *
     * @param id - contract name
     * @param network - selected network
     * @returns contract address
     */
    getAddress(id, network, key) {
        const collection = this.db.get(`${network}.named.${id}${key !== undefined ? `.${key}` : ""}`);
        const namedEntry = collection.value();
        return namedEntry?.address;
    }
    /**
     * Get contract addresses by names from the json file
     *
     * @param ids - contract names
     * @param network - selected network
     * @returns contract address
     */
    getAddresses(ids, network, keys = []) {
        return ids
            .map((id, index) => {
            const entry = this.db.get(`${network}.named.${id}`).value();
            if (entry && keys[index] in entry) {
                return entry[keys[index]];
            }
            return entry;
        })
            .map((entity) => entity?.address);
    }
    /**
     * Get contract address by name from the json file
     *
     * @param id - contract name
     * @param network - selected network
     * @throws {Error} if there is no address the error will be thrown
     * @returns contract address
     */
    mustGetAddress(id, network, key) {
        const address = this.getAddress(id, network, key);
        if (!address) {
            throw new Error(`Contract ${id} is not saved`);
        }
        return address;
    }
    /**
     * Get Contract Deployment Transaction Hash
     */
    getDeploymentTxHash(id, network, address) {
        const collection = this.db.get(`${network}.instance.${address}`);
        const instanceEntry = collection.value();
        if (instanceEntry?.id !== id) {
            throw new Error(`Contract ${id} with address ${address} and network ${network} not found.`);
        }
        return instanceEntry.deploymentTxHash;
    }
    /**
     * Get contract from the json file with sizes and multi count
     *
     * @param deployer - deployer address
     * @param network - selected network
     * @returns {[entries: Map<string, string>, length: number, multiCount: number]}
     */
    printContracts(deployer, network) {
        (0, logger_1.logMagenta)({ text: `Contracts deployed at ${network} by ${deployer}` });
        (0, logger_1.logMagenta)({ text: "---------------------------------" });
        const entryMap = new Map();
        const data = this.db.get(network).value();
        const { named, instance } = data || {};
        const namedEntries = Object.entries(named || {});
        const instanceEntries = Object.entries(instance || {});
        let multiCount = 0;
        namedEntries.forEach(([key, value]) => {
            if (key.startsWith("~")) {
                return;
            }
            if (typeof value.count === "number" && typeof value.address === "string") {
                if (value.count > 1) {
                    (0, logger_1.logMagenta)({ text: `\t${key}: N=${value.count}` });
                    multiCount += 1;
                }
                else {
                    (0, logger_1.logMagenta)({ text: `\t${key}: ${value.address}` });
                    entryMap.set(key, value.address);
                }
            }
            else {
                const entries = Object.entries(value);
                entries.forEach(([id, nested]) => {
                    if (typeof nested.count === "number" && typeof nested.address === "string") {
                        if (nested.count > 1) {
                            (0, logger_1.logMagenta)({ text: `\t${key}-${id}: N=${nested.count}` });
                            multiCount += 1;
                        }
                        else {
                            (0, logger_1.logMagenta)({ text: `\t${key}-${id}: ${nested.address}` });
                            entryMap.set(key, nested.address);
                        }
                    }
                    else {
                        const nestedEntries = Object.entries(nested);
                        nestedEntries.forEach(([subId, subNested]) => {
                            if (subNested.count > 1) {
                                (0, logger_1.logMagenta)({ text: `\t${key}-${id}-${subId}: N=${subNested.count}` });
                                multiCount += 1;
                            }
                            else {
                                (0, logger_1.logMagenta)({ text: `\t${key}-${id}-${subId}: ${subNested.address}` });
                                entryMap.set(`${key}-${id}-${subId}`, subNested.address);
                            }
                        });
                    }
                });
            }
        });
        (0, logger_1.logMagenta)({ text: "---------------------------------" });
        (0, logger_1.logMagenta)({ text: `N# Contracts: ${entryMap.size + multiCount} / ${instanceEntries.length}` });
        return [entryMap, instanceEntries.length, multiCount];
    }
    /**
     * Clean json file for selected network
     *
     * @param network - selected network
     */
    cleanup(network) {
        this.db.set(network, {}).write();
    }
}
exports.ContractStorage = ContractStorage;
//# sourceMappingURL=ContractStorage.js.map