/**
 * Copyright (C) 2024 Hedera Hashgraph, LLC
 *
 * 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.
 *
 */
import { ComponentType } from './enumerations.js';
import { SoloError } from '../../errors.js';
import { BaseComponent } from './components/base_component.js';
import { RelayComponent } from './components/relay_component.js';
import { HaProxyComponent } from './components/ha_proxy_component.js';
import { MirrorNodeComponent } from './components/mirror_node_component.js';
import { EnvoyProxyComponent } from './components/envoy_proxy_component.js';
import { ConsensusNodeComponent } from './components/consensus_node_component.js';
import { MirrorNodeExplorerComponent } from './components/mirror_node_explorer_component.js';
/**
 * Represent the components in the remote config and handles:
 * - CRUD operations on the components.
 * - Validation.
 * - Conversion FROM and TO plain object.
 */
export class ComponentsDataWrapper {
    relays;
    haProxies;
    mirrorNodes;
    envoyProxies;
    consensusNodes;
    mirrorNodeExplorers;
    /**
     * @param relays - Relay record mapping service name to relay components
     * @param haProxies - HA Proxies record mapping service name to ha proxies components
     * @param mirrorNodes - Mirror Nodes record mapping service name to mirror nodes components
     * @param envoyProxies - Envoy Proxies record mapping service name to envoy proxies components
     * @param consensusNodes - Consensus Nodes record mapping service name to consensus nodes components
     * @param mirrorNodeExplorers - Mirror Node Explorers record mapping service name to mirror node explorers components
     */
    constructor(relays = {}, haProxies = {}, mirrorNodes = {}, envoyProxies = {}, consensusNodes = {}, mirrorNodeExplorers = {}) {
        this.relays = relays;
        this.haProxies = haProxies;
        this.mirrorNodes = mirrorNodes;
        this.envoyProxies = envoyProxies;
        this.consensusNodes = consensusNodes;
        this.mirrorNodeExplorers = mirrorNodeExplorers;
        this.validate();
    }
    /* -------- Modifiers -------- */
    /** Used to add new component to their respective group. */
    add(serviceName, component) {
        const self = this;
        if (!serviceName || typeof serviceName !== 'string') {
            throw new SoloError(`Service name is required ${serviceName}`);
        }
        if (!(component instanceof BaseComponent)) {
            throw new SoloError('Component must be instance of BaseComponent', null, BaseComponent);
        }
        function addComponentCallback(components) {
            if (self.exists(components, component)) {
                throw new SoloError('Component exists', null, component.toObject());
            }
            components[serviceName] = component;
        }
        self.applyCallbackToComponentGroup(component.type, serviceName, addComponentCallback);
    }
    /** Used to edit an existing component from their respective group. */
    edit(serviceName, component) {
        const self = this;
        if (!serviceName || typeof serviceName !== 'string') {
            throw new SoloError(`Service name is required ${serviceName}`);
        }
        if (!(component instanceof BaseComponent)) {
            throw new SoloError('Component must be instance of BaseComponent', null, BaseComponent);
        }
        function editComponentCallback(components) {
            if (!components[serviceName]) {
                throw new SoloError(`Component doesn't exist, name: ${serviceName}`, null, { component });
            }
            components[serviceName] = component;
        }
        self.applyCallbackToComponentGroup(component.type, serviceName, editComponentCallback);
    }
    /** Used to remove specific component from their respective group. */
    remove(serviceName, type) {
        const self = this;
        if (!serviceName || typeof serviceName !== 'string') {
            throw new SoloError(`Service name is required ${serviceName}`);
        }
        if (!Object.values(ComponentType).includes(type)) {
            throw new SoloError(`Invalid component type ${type}`);
        }
        function deleteComponentCallback(components) {
            if (!components[serviceName]) {
                throw new SoloError(`Component ${serviceName} of type ${type} not found while attempting to remove`);
            }
            delete components[serviceName];
        }
        self.applyCallbackToComponentGroup(type, serviceName, deleteComponentCallback);
    }
    /* -------- Utilities -------- */
    getComponent(type, serviceName) {
        let component;
        function getComponentCallback(components) {
            if (!components[serviceName]) {
                throw new SoloError(`Component ${serviceName} of type ${type} not found while attempting to read`);
            }
            component = components[serviceName];
        }
        this.applyCallbackToComponentGroup(type, serviceName, getComponentCallback);
        return component;
    }
    /**
     * Method used to map the type to the specific component group
     * and pass it to a callback to apply modifications
     */
    applyCallbackToComponentGroup(type, serviceName, callback) {
        switch (type) {
            case ComponentType.Relay:
                callback(this.relays);
                break;
            case ComponentType.HaProxy:
                callback(this.haProxies);
                break;
            case ComponentType.MirrorNode:
                callback(this.mirrorNodes);
                break;
            case ComponentType.EnvoyProxy:
                callback(this.envoyProxies);
                break;
            case ComponentType.ConsensusNode:
                callback(this.consensusNodes);
                break;
            case ComponentType.MirrorNodeExplorer:
                callback(this.mirrorNodeExplorers);
                break;
            default:
                throw new SoloError(`Unknown component type ${type}, service name: ${serviceName}`);
        }
        this.validate();
    }
    /**
     * Handles creating instance of the class from plain object.
     *
     * @param components - component groups distinguished by their type.
     */
    static fromObject(components) {
        const relays = {};
        const haProxies = {};
        const mirrorNodes = {};
        const envoyProxies = {};
        const consensusNodes = {};
        const mirrorNodeExplorers = {};
        Object.entries(components).forEach(([type, components]) => {
            switch (type) {
                case ComponentType.Relay:
                    Object.entries(components).forEach(([name, component]) => {
                        relays[name] = RelayComponent.fromObject(component);
                    });
                    break;
                case ComponentType.HaProxy:
                    Object.entries(components).forEach(([name, component]) => {
                        haProxies[name] = HaProxyComponent.fromObject(component);
                    });
                    break;
                case ComponentType.MirrorNode:
                    Object.entries(components).forEach(([name, component]) => {
                        mirrorNodes[name] = MirrorNodeComponent.fromObject(component);
                    });
                    break;
                case ComponentType.EnvoyProxy:
                    Object.entries(components).forEach(([name, component]) => {
                        envoyProxies[name] = EnvoyProxyComponent.fromObject(component);
                    });
                    break;
                case ComponentType.ConsensusNode:
                    Object.entries(components).forEach(([name, component]) => {
                        consensusNodes[name] = ConsensusNodeComponent.fromObject(component);
                    });
                    break;
                case ComponentType.MirrorNodeExplorer:
                    Object.entries(components).forEach(([name, component]) => {
                        mirrorNodeExplorers[name] = MirrorNodeExplorerComponent.fromObject(component);
                    });
                    break;
                default:
                    throw new SoloError(`Unknown component type ${type}`);
            }
        });
        return new ComponentsDataWrapper(relays, haProxies, mirrorNodes, envoyProxies, consensusNodes, mirrorNodeExplorers);
    }
    /** Used to create an empty instance used to keep the constructor private */
    static initializeEmpty() {
        return new ComponentsDataWrapper();
    }
    /** checks if component exists in the respective group */
    exists(components, newComponent) {
        return Object.values(components).some(component => BaseComponent.compare(component, newComponent));
    }
    validate() {
        function testComponentsObject(components, expectedInstance) {
            Object.entries(components).forEach(([name, component]) => {
                if (!name || typeof name !== 'string') {
                    throw new SoloError(`Invalid component service name ${{ [name]: component?.constructor?.name }}`);
                }
                if (!(component instanceof expectedInstance)) {
                    throw new SoloError(`Invalid component type, service name: ${name}, ` +
                        `expected ${expectedInstance?.name}, actual: ${component?.constructor?.name}`, null, { component });
                }
            });
        }
        testComponentsObject(this.relays, RelayComponent);
        testComponentsObject(this.haProxies, HaProxyComponent);
        testComponentsObject(this.mirrorNodes, MirrorNodeComponent);
        testComponentsObject(this.envoyProxies, EnvoyProxyComponent);
        testComponentsObject(this.consensusNodes, ConsensusNodeComponent);
        testComponentsObject(this.mirrorNodeExplorers, MirrorNodeExplorerComponent);
    }
    toObject() {
        function transform(components) {
            const transformedComponents = {};
            Object.entries(components).forEach(([name, component]) => {
                transformedComponents[name] = component.toObject();
            });
            return transformedComponents;
        }
        return {
            [ComponentType.Relay]: transform(this.relays),
            [ComponentType.HaProxy]: transform(this.haProxies),
            [ComponentType.MirrorNode]: transform(this.mirrorNodes),
            [ComponentType.EnvoyProxy]: transform(this.envoyProxies),
            [ComponentType.ConsensusNode]: transform(this.consensusNodes),
            [ComponentType.MirrorNodeExplorer]: transform(this.mirrorNodeExplorers),
        };
    }
    clone() {
        const data = this.toObject();
        return ComponentsDataWrapper.fromObject(data);
    }
}
//# sourceMappingURL=components_data_wrapper.js.map