"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigLoader = void 0;
/*
 * Copyright 2022 Fernando Boucquez
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const fs_1 = require("fs");
const _ = require("lodash");
const path_1 = require("path");
const symbol_sdk_1 = require("symbol-sdk");
const model_1 = require("../model");
const BootstrapUtils_1 = require("./BootstrapUtils");
const CommandUtils_1 = require("./CommandUtils");
const ConfigService_1 = require("./ConfigService");
const CryptoUtils_1 = require("./CryptoUtils");
class ConfigLoader {
    constructor(logger) {
        this.logger = logger;
    }
    async generateRandomConfiguration(oldAddresses, oldPresetData, presetData) {
        const networkType = presetData.networkType;
        const addresses = {
            version: this.getAddressesMigration(presetData.networkType).length + 1,
            networkType: networkType,
            nemesisGenerationHashSeed: presetData.nemesisGenerationHashSeed || (oldAddresses === null || oldAddresses === void 0 ? void 0 : oldAddresses.nemesisGenerationHashSeed) ||
                symbol_sdk_1.Convert.uint8ToHex(symbol_sdk_1.Crypto.randomBytes(32)),
            sinkAddress: presetData.sinkAddress || (oldAddresses === null || oldAddresses === void 0 ? void 0 : oldAddresses.sinkAddress) || symbol_sdk_1.Account.generateNewAccount(networkType).address.plain(),
        };
        if (presetData.nodes) {
            addresses.nodes = await this.generateNodeAccounts(oldAddresses, presetData, networkType);
        }
        if (!presetData.harvestNetworkFeeSinkAddress) {
            presetData.harvestNetworkFeeSinkAddress = addresses.sinkAddress;
        }
        if (!presetData.harvestNetworkFeeSinkAddressV1) {
            presetData.harvestNetworkFeeSinkAddressV1 = presetData.harvestNetworkFeeSinkAddress;
        }
        if (!presetData.mosaicRentalFeeSinkAddress) {
            presetData.mosaicRentalFeeSinkAddress = addresses.sinkAddress;
        }
        if (!presetData.mosaicRentalFeeSinkAddressV1) {
            presetData.mosaicRentalFeeSinkAddressV1 = presetData.mosaicRentalFeeSinkAddress;
        }
        if (!presetData.namespaceRentalFeeSinkAddress) {
            presetData.namespaceRentalFeeSinkAddress = addresses.sinkAddress;
        }
        if (!presetData.namespaceRentalFeeSinkAddressV1) {
            presetData.namespaceRentalFeeSinkAddressV1 = presetData.namespaceRentalFeeSinkAddress;
        }
        presetData.networkIdentifier = BootstrapUtils_1.BootstrapUtils.getNetworkIdentifier(presetData.networkType);
        presetData.networkName = BootstrapUtils_1.BootstrapUtils.getNetworkName(presetData.networkType);
        if (!presetData.nemesisGenerationHashSeed) {
            presetData.nemesisGenerationHashSeed = addresses.nemesisGenerationHashSeed;
        }
        const privateKeySecurityMode = CryptoUtils_1.CryptoUtils.getPrivateKeySecurityMode(presetData.privateKeySecurityMode);
        const shouldCreateNemesis = ConfigLoader.shouldCreateNemesis(presetData);
        if (shouldCreateNemesis) {
            addresses.nemesisSigner = this.generateAccount(networkType, privateKeySecurityMode, ConfigService_1.KeyName.NemesisSigner, oldAddresses === null || oldAddresses === void 0 ? void 0 : oldAddresses.nemesisSigner, presetData.nemesis.nemesisSignerPrivateKey, presetData.nemesisSignerPublicKey);
            presetData.nemesisSignerPublicKey = addresses.nemesisSigner.publicKey;
            presetData.nemesis.nemesisSignerPrivateKey = await CommandUtils_1.CommandUtils.resolvePrivateKey(this.logger, presetData.networkType, addresses.nemesisSigner, ConfigService_1.KeyName.NemesisSigner, '', 'creating the network nemesis seed and configuration');
        }
        const nemesisSignerAddress = symbol_sdk_1.Address.createFromPublicKey(presetData.nemesisSignerPublicKey, networkType);
        if (!presetData.currencyMosaicId)
            presetData.currencyMosaicId = symbol_sdk_1.MosaicId.createFromNonce(symbol_sdk_1.MosaicNonce.createFromNumber(0), nemesisSignerAddress).toHex();
        if (!presetData.harvestingMosaicId) {
            if (!presetData.nemesis) {
                throw new Error('nemesis must be defined!');
            }
            if (presetData.nemesis.mosaics && presetData.nemesis.mosaics.length > 1) {
                presetData.harvestingMosaicId = symbol_sdk_1.MosaicId.createFromNonce(symbol_sdk_1.MosaicNonce.createFromNumber(1), nemesisSignerAddress).toHex();
            }
            else {
                presetData.harvestingMosaicId = presetData.currencyMosaicId;
            }
        }
        if (shouldCreateNemesis) {
            if (oldAddresses) {
                if (!oldPresetData) {
                    throw new Error('oldPresetData must be defined when upgrading!');
                }
                // Nemesis configuration cannot be changed on upgrade.
                addresses.mosaics = oldAddresses.mosaics;
                presetData.nemesis = oldPresetData.nemesis;
            }
            else {
                addresses.mosaics = this.processNemesisBalances(presetData, addresses, nemesisSignerAddress);
            }
        }
        return addresses;
    }
    sum(distribution, mosaicName) {
        return distribution
            .map((d, index) => {
            if (d.amount < 0) {
                throw new Error(`Nemesis distribution balance cannot be less than 0. Mosaic ${mosaicName}, distribution address: ${d.address}, amount: ${d.amount}, index ${index}. \nDistributions are:\n${BootstrapUtils_1.BootstrapUtils.toYaml(distribution)}`);
            }
            return d.amount;
        })
            .reduce((a, b) => a + b, 0);
    }
    processNemesisBalances(presetData, addresses, nemesisSignerAddress) {
        const privateKeySecurityMode = CryptoUtils_1.CryptoUtils.getPrivateKeySecurityMode(presetData.privateKeySecurityMode);
        const networkType = presetData.networkType;
        const mosaics = [];
        presetData.nemesis.mosaics.forEach((m, mosaicIndex) => {
            var _a;
            const accounts = this.generateAddresses(networkType, privateKeySecurityMode, m.accounts);
            const id = symbol_sdk_1.MosaicId.createFromNonce(symbol_sdk_1.MosaicNonce.createFromNumber(mosaicIndex), nemesisSignerAddress).toHex();
            mosaics.push({
                id: id,
                name: m.name,
                accounts,
            });
            const getBalance = (nodeIndex) => {
                var _a, _b;
                const node = (_a = presetData === null || presetData === void 0 ? void 0 : presetData.nodes) === null || _a === void 0 ? void 0 : _a[nodeIndex];
                if (!node) {
                    return undefined;
                }
                const balance = (_b = node === null || node === void 0 ? void 0 : node.balances) === null || _b === void 0 ? void 0 : _b[mosaicIndex];
                if (balance !== undefined) {
                    return balance;
                }
                if (node.excludeFromNemesis) {
                    return 0;
                }
                return undefined;
            };
            const providedDistributions = [...(m.currencyDistributions || [])];
            (_a = addresses.nodes) === null || _a === void 0 ? void 0 : _a.forEach((node, index) => {
                const balance = getBalance(index);
                if (balance !== undefined)
                    providedDistributions.push({
                        address: node.main.address,
                        amount: balance,
                    });
            });
            const nodeMainAccounts = (addresses.nodes || []).filter((node, index) => node.main && getBalance(index) === undefined);
            const providedSupply = this.sum(providedDistributions, m.name);
            const remainingSupply = m.supply - providedSupply;
            if (remainingSupply < 0) {
                throw new Error(`Mosaic ${m.name}'s fixed distributed supply ${providedSupply} is grater than mosaic total supply ${m.supply}`);
            }
            const dynamicAccounts = accounts.length + nodeMainAccounts.length;
            const amountPerAccount = Math.floor(remainingSupply / dynamicAccounts);
            const maxHarvesterBalance = this.getMaxHarvesterBalance(presetData, mosaicIndex);
            const generatedAccounts = [
                ...accounts.map((a) => ({
                    address: a.address,
                    amount: amountPerAccount,
                })),
                ...nodeMainAccounts.map((n) => ({
                    address: n.main.address,
                    amount: Math.min(maxHarvesterBalance, amountPerAccount),
                })),
            ];
            m.currencyDistributions = [...generatedAccounts, ...providedDistributions].filter((d) => d.amount > 0);
            const generatedSupply = this.sum(generatedAccounts.slice(1), m.name);
            m.currencyDistributions[0].amount = m.supply - providedSupply - generatedSupply;
            const supplied = this.sum(m.currencyDistributions, m.name);
            if (m.supply != supplied) {
                throw new Error(`Invalid nemgen total supplied value, expected ${m.supply} but total is ${supplied}. \nDistributions are:\n${BootstrapUtils_1.BootstrapUtils.toYaml(m.currencyDistributions)}`);
            }
        });
        return mosaics;
    }
    getMaxHarvesterBalance(presetData, mosaicIndex) {
        return (presetData.nemesis.mosaics.length == 1 && mosaicIndex == 0) || (presetData.nemesis.mosaics.length > 1 && mosaicIndex == 1)
            ? presetData.maxHarvesterBalance
            : Number.MAX_SAFE_INTEGER;
    }
    static shouldCreateNemesis(presetData) {
        return (presetData.nemesis &&
            !presetData.nemesisSeedFolder &&
            (BootstrapUtils_1.BootstrapUtils.isYmlFile(presetData.preset) ||
                !fs_1.existsSync(path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'presets', presetData.preset, 'seed'))));
    }
    generateAddresses(networkType, privateKeySecurityMode, accounts) {
        if (typeof accounts == 'number') {
            return ConfigLoader.getArray(accounts).map(() => this.toConfig(symbol_sdk_1.Account.generateNewAccount(networkType)));
        }
        else {
            return accounts.map((key) => this.toConfig(symbol_sdk_1.PublicAccount.createFromPublicKey(key, networkType)));
        }
    }
    getAccount(networkType, publicKey, privateKey) {
        if (privateKey) {
            const account = symbol_sdk_1.Account.createFromPrivateKey(privateKey, networkType);
            if (publicKey && account.publicKey.toUpperCase() != publicKey.toUpperCase()) {
                throw new Error('Invalid provided public key/private key!');
            }
            return account;
        }
        if (publicKey) {
            return symbol_sdk_1.PublicAccount.createFromPublicKey(publicKey, networkType);
        }
        return undefined;
    }
    toConfig(account) {
        // isntanceof doesn't work when loaded in multiple libraries.
        //https://stackoverflow.com/questions/59265098/instanceof-not-work-correctly-in-typescript-library-project
        if (account.constructor.name === symbol_sdk_1.Account.name) {
            return {
                privateKey: account.privateKey,
                publicKey: account.publicKey,
                address: account.address.plain(),
            };
        }
        return {
            publicKey: account.publicKey,
            address: account.address.plain(),
        };
    }
    generateAccount(networkType, privateKeySecurityMode, keyName, oldStoredAccount, privateKey, publicKey) {
        var _a;
        const oldAccount = this.getAccount(networkType, oldStoredAccount === null || oldStoredAccount === void 0 ? void 0 : oldStoredAccount.publicKey.toUpperCase(), (_a = oldStoredAccount === null || oldStoredAccount === void 0 ? void 0 : oldStoredAccount.privateKey) === null || _a === void 0 ? void 0 : _a.toUpperCase());
        const newAccount = this.getAccount(networkType, publicKey === null || publicKey === void 0 ? void 0 : publicKey.toUpperCase(), privateKey === null || privateKey === void 0 ? void 0 : privateKey.toUpperCase());
        const getAccountLog = (account) => `${keyName} Account ${account.address.plain()} Public Key ${account.publicKey} `;
        if (oldAccount && !newAccount) {
            this.logger.info(`Reusing ${getAccountLog(oldAccount)}...`);
            return this.toConfig(oldAccount);
        }
        if (!oldAccount && newAccount) {
            this.logger.info(`${getAccountLog(newAccount)} has been provided`);
            return this.toConfig(newAccount);
        }
        if (oldAccount && newAccount) {
            if (oldAccount.address.equals(newAccount.address)) {
                this.logger.info(`Reusing ${getAccountLog(newAccount)}`);
                return Object.assign(Object.assign({}, this.toConfig(oldAccount)), this.toConfig(newAccount));
            }
            this.logger.info(`Old ${getAccountLog(oldAccount)} has been changed. New ${getAccountLog(newAccount)} replaces it.`);
            return this.toConfig(newAccount);
        }
        //Generation validation.
        if (keyName === ConfigService_1.KeyName.Main &&
            (privateKeySecurityMode === model_1.PrivateKeySecurityMode.PROMPT_ALL ||
                privateKeySecurityMode === model_1.PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT ||
                privateKeySecurityMode === model_1.PrivateKeySecurityMode.PROMPT_MAIN)) {
            throw new BootstrapUtils_1.KnownError(`Account ${keyName} cannot be generated when Private Key Security Mode is ${privateKeySecurityMode}. Account won't be stored anywhere!. Please use ${model_1.PrivateKeySecurityMode.ENCRYPT}, or provider your ${keyName} account with custom presets!`);
        }
        if (keyName === ConfigService_1.KeyName.Transport &&
            (privateKeySecurityMode === model_1.PrivateKeySecurityMode.PROMPT_ALL ||
                privateKeySecurityMode === model_1.PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT)) {
            throw new BootstrapUtils_1.KnownError(`Account ${keyName} cannot be generated when Private Key Security Mode is ${privateKeySecurityMode}. Account won't be stored anywhere!. Please use ${model_1.PrivateKeySecurityMode.ENCRYPT}, ${model_1.PrivateKeySecurityMode.PROMPT_MAIN}, or provider your ${keyName} account with custom presets!`);
        }
        else {
            if (privateKeySecurityMode === model_1.PrivateKeySecurityMode.PROMPT_ALL) {
                throw new BootstrapUtils_1.KnownError(`Account ${keyName} cannot be generated when Private Key Security Mode is ${privateKeySecurityMode}. Account won't be stored anywhere! Please use ${model_1.PrivateKeySecurityMode.ENCRYPT}, ${model_1.PrivateKeySecurityMode.PROMPT_MAIN}, ${model_1.PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT}, or provider your ${keyName} account with custom presets!`);
            }
        }
        this.logger.info(`Generating ${keyName} account...`);
        return ConfigLoader.toConfig(symbol_sdk_1.Account.generateNewAccount(networkType));
    }
    generateNodeAccount(oldNodeAccount, presetData, index, nodePreset, networkType) {
        const privateKeySecurityMode = CryptoUtils_1.CryptoUtils.getPrivateKeySecurityMode(presetData.privateKeySecurityMode);
        const name = nodePreset.name || `node-${index}`;
        const main = this.generateAccount(networkType, privateKeySecurityMode, ConfigService_1.KeyName.Main, oldNodeAccount === null || oldNodeAccount === void 0 ? void 0 : oldNodeAccount.main, nodePreset.mainPrivateKey, nodePreset.mainPublicKey);
        const transport = this.generateAccount(networkType, privateKeySecurityMode, ConfigService_1.KeyName.Transport, oldNodeAccount === null || oldNodeAccount === void 0 ? void 0 : oldNodeAccount.transport, nodePreset.transportPrivateKey, nodePreset.transportPublicKey);
        const friendlyName = nodePreset.friendlyName || main.publicKey.substr(0, 7);
        const nodeAccount = {
            name,
            friendlyName,
            roles: ConfigLoader.resolveRoles(nodePreset),
            main: main,
            transport: transport,
        };
        const useRemoteAccount = nodePreset.nodeUseRemoteAccount || presetData.nodeUseRemoteAccount;
        if (useRemoteAccount && (nodePreset.harvesting || nodePreset.voting))
            nodeAccount.remote = this.generateAccount(networkType, privateKeySecurityMode, ConfigService_1.KeyName.Remote, oldNodeAccount === null || oldNodeAccount === void 0 ? void 0 : oldNodeAccount.remote, nodePreset.remotePrivateKey, nodePreset.remotePublicKey);
        if (nodePreset.harvesting)
            nodeAccount.vrf = this.generateAccount(networkType, privateKeySecurityMode, ConfigService_1.KeyName.VRF, oldNodeAccount === null || oldNodeAccount === void 0 ? void 0 : oldNodeAccount.vrf, nodePreset.vrfPrivateKey, nodePreset.vrfPublicKey);
        return nodeAccount;
    }
    async generateNodeAccounts(oldAddresses, presetData, networkType) {
        return Promise.all((presetData.nodes || []).map((node, index) => { var _a; return this.generateNodeAccount((_a = oldAddresses === null || oldAddresses === void 0 ? void 0 : oldAddresses.nodes) === null || _a === void 0 ? void 0 : _a[index], presetData, index, node, networkType); }));
    }
    static getArray(size) {
        return [...Array(size).keys()];
    }
    loadCustomPreset(customPreset, password) {
        if (!customPreset) {
            return {};
        }
        if (!fs_1.existsSync(customPreset)) {
            throw new BootstrapUtils_1.KnownError(`Custom preset '${customPreset}' doesn't exist. Have you provided the right --customPreset <customPrestFileLocation> ?`);
        }
        return BootstrapUtils_1.BootstrapUtils.loadYaml(customPreset, password);
    }
    static loadAssembly(preset, assembly, workingDir) {
        const fileLocation = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'presets', 'assemblies', `assembly-${assembly}.yml`);
        const errorMessage = `Assembly '${assembly}' is not valid for preset '${preset}'. Have you provided the right --preset <preset> --assembly <assembly> ?`;
        return this.loadBundledPreset(assembly, fileLocation, workingDir, errorMessage);
    }
    static loadNetworkPreset(preset, workingDir) {
        const fileLocation = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'presets', preset, `network.yml`);
        const errorMessage = `Preset '${preset}' does not exist. Have you provided the right --preset <preset> ?`;
        return this.loadBundledPreset(preset, fileLocation, workingDir, errorMessage);
    }
    static loadBundledPreset(presetFile, bundledLocation, workingDir, errorMessage) {
        if (BootstrapUtils_1.BootstrapUtils.isYmlFile(presetFile)) {
            const assemblyFile = BootstrapUtils_1.BootstrapUtils.resolveWorkingDirPath(workingDir, presetFile);
            if (!fs_1.existsSync(assemblyFile)) {
                throw new BootstrapUtils_1.KnownError(errorMessage);
            }
            return BootstrapUtils_1.BootstrapUtils.loadYaml(assemblyFile, false);
        }
        if (fs_1.existsSync(bundledLocation)) {
            return BootstrapUtils_1.BootstrapUtils.loadYaml(bundledLocation, false);
        }
        throw new BootstrapUtils_1.KnownError(errorMessage);
    }
    static loadSharedPreset() {
        return BootstrapUtils_1.BootstrapUtils.loadYaml(path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'presets', 'shared.yml'), false);
    }
    mergePresets(object, ...otherArgs) {
        var _a, _b, _c;
        const presets = [object, ...otherArgs];
        const reversed = [...presets].reverse();
        const presetData = _.merge({}, ...presets);
        const inflation = (_a = reversed.find((p) => !_.isEmpty(p === null || p === void 0 ? void 0 : p.inflation))) === null || _a === void 0 ? void 0 : _a.inflation;
        const knownRestGateways = (_b = reversed.find((p) => !_.isEmpty(p === null || p === void 0 ? void 0 : p.knownRestGateways))) === null || _b === void 0 ? void 0 : _b.knownRestGateways;
        const knownPeers = (_c = reversed.find((p) => !_.isEmpty(p === null || p === void 0 ? void 0 : p.knownPeers))) === null || _c === void 0 ? void 0 : _c.knownPeers;
        if (inflation)
            presetData.inflation = inflation;
        if (knownRestGateways)
            presetData.knownRestGateways = knownRestGateways;
        if (knownPeers)
            presetData.knownPeers = knownPeers;
        return presetData;
    }
    createPresetData(params) {
        var _a, _b, _c;
        const customPreset = params.customPreset;
        const customPresetObject = params.customPresetObject;
        const oldPresetData = params.oldPresetData;
        const customPresetFileObject = this.loadCustomPreset(customPreset, params.password);
        const preset = params.preset || ((_a = params.customPresetObject) === null || _a === void 0 ? void 0 : _a.preset) || (customPresetFileObject === null || customPresetFileObject === void 0 ? void 0 : customPresetFileObject.preset) || (oldPresetData === null || oldPresetData === void 0 ? void 0 : oldPresetData.preset);
        if (!preset) {
            throw new BootstrapUtils_1.KnownError('Preset value could not be resolved from target folder contents. Please provide the --preset parameter when running the config/start command.');
        }
        const sharedPreset = ConfigLoader.loadSharedPreset();
        const networkPreset = ConfigLoader.loadNetworkPreset(preset, params.workingDir);
        const assembly = params.assembly || ((_b = params.customPresetObject) === null || _b === void 0 ? void 0 : _b.assembly) || (customPresetFileObject === null || customPresetFileObject === void 0 ? void 0 : customPresetFileObject.assembly) || ((_c = params.oldPresetData) === null || _c === void 0 ? void 0 : _c.assembly) ||
            ConfigService_1.defaultAssembly[preset];
        if (!assembly) {
            throw new BootstrapUtils_1.KnownError(`Preset ${preset} requires assembly (-a, --assembly option). Possible values are: ${Object.keys(ConfigService_1.Assembly).join(', ')}`);
        }
        const assemblyPreset = ConfigLoader.loadAssembly(preset, assembly, params.workingDir);
        const providedCustomPreset = this.mergePresets(customPresetFileObject, customPresetObject);
        const resolvedCustomPreset = _.isEmpty(providedCustomPreset) ? (oldPresetData === null || oldPresetData === void 0 ? void 0 : oldPresetData.customPresetCache) || {} : providedCustomPreset;
        const presetData = this.mergePresets(sharedPreset, networkPreset, assemblyPreset, resolvedCustomPreset);
        if (!ConfigLoader.presetInfoLogged) {
            this.logger.info(`Generating config from preset '${preset}'`);
            if (assembly) {
                this.logger.info(`Using assembly '${assembly}'`);
            }
            if (customPreset) {
                this.logger.info(`Using custom preset file '${customPreset}'`);
            }
        }
        if (!presetData.networkType) {
            throw new Error('Network Type could not be resolved. Have your provided the right --preset?');
        }
        ConfigLoader.presetInfoLogged = true;
        const presetDataWithDynamicDefaults = Object.assign(Object.assign({}, presetData), { version: 1, preset: preset, assembly: assembly, nodes: this.dynamicDefaultNodeConfiguration(presetData.nodes), customPresetCache: resolvedCustomPreset });
        return this.expandRepeat(presetDataWithDynamicDefaults);
    }
    dynamicDefaultNodeConfiguration(nodes) {
        return _.map(nodes || [], (node) => {
            return Object.assign(Object.assign({}, this.getDefaultConfiguration(node)), node);
        });
    }
    getDefaultConfiguration(node) {
        if (node.harvesting && node.api) {
            return {
                syncsource: true,
                filespooling: true,
                partialtransaction: true,
                addressextraction: true,
                mongo: true,
                zeromq: true,
                enableAutoSyncCleanup: false,
            };
        }
        if (node.api) {
            return {
                syncsource: false,
                filespooling: true,
                partialtransaction: true,
                addressextraction: true,
                mongo: true,
                zeromq: true,
                enableAutoSyncCleanup: false,
            };
        }
        // peer only (harvesting or not).
        return {
            syncsource: true,
            filespooling: false,
            partialtransaction: false,
            addressextraction: false,
            mongo: false,
            zeromq: false,
            enableAutoSyncCleanup: true,
        };
    }
    static resolveRoles(nodePreset) {
        if (nodePreset.roles) {
            return nodePreset.roles;
        }
        const roles = [];
        if (nodePreset.syncsource) {
            roles.push('Peer');
        }
        if (nodePreset.api) {
            roles.push('Api');
        }
        if (nodePreset.voting) {
            roles.push('Voting');
        }
        return roles.join(',');
    }
    static toConfig(account) {
        if (account instanceof symbol_sdk_1.Account) {
            return {
                privateKey: account.privateKey,
                publicKey: account.publicAccount.publicKey,
                address: account.address.plain(),
            };
        }
        else {
            return {
                publicKey: account.publicKey,
                address: account.address.plain(),
            };
        }
    }
    expandRepeat(presetData) {
        return Object.assign(Object.assign({}, presetData), { databases: this.expandServicesRepeat(presetData, presetData.databases || []), nodes: this.expandServicesRepeat(presetData, presetData.nodes || []), gateways: this.expandServicesRepeat(presetData, presetData.gateways || []), explorers: this.expandServicesRepeat(presetData, presetData.explorers || []), faucets: this.expandServicesRepeat(presetData, presetData.faucets || []), nemesis: this.applyValueTemplate(presetData, presetData.nemesis) });
    }
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    applyValueTemplate(context, value) {
        if (!value) {
            return value;
        }
        if (_.isArray(value)) {
            return this.expandServicesRepeat(context, value);
        }
        if (_.isObject(value)) {
            return _.mapValues(value, (v) => this.applyValueTemplate(Object.assign(Object.assign({}, context), value), v));
        }
        if (!_.isString(value)) {
            return value;
        }
        return BootstrapUtils_1.BootstrapUtils.runTemplate(value, context);
    }
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    expandServicesRepeat(context, services) {
        return _.flatMap(services || [], (service) => {
            if (!_.isObject(service)) {
                return service;
            }
            const repeat = service.repeat;
            if (repeat === 0) {
                return [];
            }
            return _.range(repeat || 1).map((index) => {
                return _.omit(_.mapValues(service, (v) => this.applyValueTemplate(Object.assign(Object.assign(Object.assign({}, context), service), { $index: index }), v)), 'repeat');
            });
        });
    }
    loadExistingPresetDataIfPreset(target, password) {
        const generatedPresetLocation = this.getGeneratedPresetLocation(target);
        if (fs_1.existsSync(generatedPresetLocation)) {
            return BootstrapUtils_1.BootstrapUtils.loadYaml(generatedPresetLocation, password);
        }
        return undefined;
    }
    loadExistingPresetData(target, password) {
        const presetData = this.loadExistingPresetDataIfPreset(target, password);
        if (!presetData) {
            throw new Error(`The file ${this.getGeneratedPresetLocation(target)} doesn't exist. Have you executed the 'config' command? Have you provided the right --target param?`);
        }
        return presetData;
    }
    loadExistingAddressesIfPreset(target, password) {
        const generatedAddressLocation = this.getGeneratedAddressLocation(target);
        if (fs_1.existsSync(generatedAddressLocation)) {
            const presetData = this.loadExistingPresetData(target, password);
            return this.migrateAddresses(BootstrapUtils_1.BootstrapUtils.loadYaml(generatedAddressLocation, password), presetData.networkType);
        }
        return undefined;
    }
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    migrateAddresses(addresses, networkType) {
        const migrations = this.getAddressesMigration(networkType);
        return BootstrapUtils_1.BootstrapUtils.migrate(this.logger, 'addresses.yml', addresses, migrations);
    }
    getAddressesMigration(networkType) {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const configLoader = this;
        return [
            {
                description: 'Key names migration',
                migrate(from) {
                    (from.nodes || []).forEach((nodeAddresses) => {
                        var _a;
                        if (nodeAddresses.signing) {
                            nodeAddresses.main = nodeAddresses.signing;
                        }
                        else {
                            if (nodeAddresses.ssl) {
                                nodeAddresses.main = ConfigLoader.toConfig(symbol_sdk_1.Account.createFromPrivateKey(nodeAddresses.ssl.privateKey, networkType));
                            }
                        }
                        nodeAddresses.transport = configLoader.generateAccount(networkType, model_1.PrivateKeySecurityMode.ENCRYPT, ConfigService_1.KeyName.Transport, undefined, (_a = nodeAddresses === null || nodeAddresses === void 0 ? void 0 : nodeAddresses.node) === null || _a === void 0 ? void 0 : _a.privateKey, undefined);
                        delete nodeAddresses.node;
                        delete nodeAddresses.signing;
                        delete nodeAddresses.ssl;
                    });
                    return from;
                },
            },
        ];
    }
    loadExistingAddresses(target, password) {
        const addresses = this.loadExistingAddressesIfPreset(target, password);
        if (!addresses) {
            throw new Error(`The file ${this.getGeneratedAddressLocation(target)} doesn't exist. Have you executed the 'config' command? Have you provided the right --target param?`);
        }
        return addresses;
    }
    getGeneratedPresetLocation(target) {
        return path_1.join(target, 'preset.yml');
    }
    getGeneratedAddressLocation(target) {
        return path_1.join(target, 'addresses.yml');
    }
}
exports.ConfigLoader = ConfigLoader;
ConfigLoader.presetInfoLogged = false;
