// SPDX-License-Identifier: Apache-2.0
import { SoloError } from '../../errors/solo-error.js';
import { ComponentTypes } from './enumerations/component-types.js';
import { BaseStateSchema } from '../../../data/schema/model/remote/state/base-state-schema.js';
import { isValidEnum } from '../../util/validation-helpers.js';
import * as constants from '../../constants.js';
import { Templates } from '../../templates.js';
export class ComponentsDataWrapper {
    state;
    constructor(state) {
        this.state = state;
    }
    get componentIds() {
        return this.state.componentIds;
    }
    /* -------- Modifiers -------- */
    /** Used to add new component to their respective group. */
    addNewComponent(component, type, isReplace, skipIncrement = false) {
        const componentId = component.metadata.id;
        if (typeof componentId !== 'number') {
            throw new SoloError(`Component id is required ${componentId}`);
        }
        if (!(component instanceof BaseStateSchema)) {
            throw new SoloError('Component must be instance of BaseState', undefined, BaseStateSchema);
        }
        const addComponentCallback = (components) => {
            if (this.checkComponentExists(components, component) && !isReplace) {
                throw new SoloError('Component exists', undefined, component);
            }
            components.push(component);
        };
        this.applyCallbackToComponentGroup(type, addComponentCallback, componentId);
        if (!skipIncrement) {
            // Increment the component id counter for the specified type when adding
            this.componentIds[type] += 1;
        }
    }
    // TODO: Remove once unified method is fully utilized
    changeNodePhase(componentId, phase) {
        if (!this.state.consensusNodes.some((component) => +component.metadata.id === +componentId)) {
            throw new SoloError(`Consensus node ${componentId} doesn't exist`);
        }
        const component = this.state.consensusNodes.find((component) => +component.metadata.id === +componentId);
        component.metadata.phase = phase;
    }
    changeComponentPhase(componentId, type, phase) {
        if (typeof componentId !== 'number') {
            throw new SoloError(`Component id is required ${componentId}`);
        }
        const updateComponentCallback = (components) => {
            const component = components.find((component) => component.metadata.id === componentId);
            if (!component) {
                throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to update`);
            }
            component.metadata.phase = phase;
        };
        this.applyCallbackToComponentGroup(type, updateComponentCallback, componentId);
    }
    /** Used to remove specific component from their respective group. */
    removeComponent(componentId, type) {
        if (typeof componentId !== 'number') {
            throw new SoloError(`Component id is required ${componentId}`);
        }
        if (!isValidEnum(type, ComponentTypes)) {
            throw new SoloError(`Invalid component type ${type}`);
        }
        const removeComponentCallback = (components) => {
            const index = components.findIndex((component) => component.metadata.id === componentId);
            if (index === -1) {
                throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to remove`);
            }
            components.splice(index, 1);
        };
        this.applyCallbackToComponentGroup(type, removeComponentCallback, componentId);
    }
    /* -------- Utilities -------- */
    getComponent(type, componentId) {
        let component;
        const getComponentCallback = (components) => {
            component = components.find((component) => component.metadata.id === componentId);
            if (!component) {
                throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to read`);
            }
        };
        this.applyCallbackToComponentGroup(type, getComponentCallback, componentId);
        return component;
    }
    getComponentByType(type) {
        let components = [];
        const getComponentsByTypeCallback = (comps) => {
            components = comps;
        };
        this.applyCallbackToComponentGroup(type, getComponentsByTypeCallback);
        return components;
    }
    getComponentsByClusterReference(type, clusterReference) {
        let filteredComponents = [];
        const getComponentsByClusterReferenceCallback = (components) => {
            filteredComponents = components.filter((component) => component.metadata.cluster === clusterReference);
        };
        this.applyCallbackToComponentGroup(type, getComponentsByClusterReferenceCallback);
        return filteredComponents;
    }
    getComponentById(type, id) {
        let filteredComponent;
        const getComponentByIdCallback = (components) => {
            filteredComponent = components.find((component) => +component.metadata.id === +id);
        };
        this.applyCallbackToComponentGroup(type, getComponentByIdCallback);
        if (!filteredComponent) {
            throw new SoloError(`Component of type ${type} with id ${id} was not found in remote config`);
        }
        return filteredComponent;
    }
    /**
     * Method used to map the type to the specific component group
     * and pass it to a callback to apply modifications
     */
    applyCallbackToComponentGroup(componentType, callback, componentId) {
        switch (componentType) {
            case ComponentTypes.RelayNodes: {
                callback(this.state.relayNodes);
                break;
            }
            case ComponentTypes.HaProxy: {
                callback(this.state.haProxies);
                break;
            }
            case ComponentTypes.MirrorNode: {
                callback(this.state.mirrorNodes);
                break;
            }
            case ComponentTypes.EnvoyProxy: {
                callback(this.state.envoyProxies);
                break;
            }
            case ComponentTypes.ConsensusNode: {
                callback(this.state.consensusNodes);
                break;
            }
            case ComponentTypes.Explorer: {
                callback(this.state.explorers);
                break;
            }
            case ComponentTypes.BlockNode: {
                callback(this.state.blockNodes);
                break;
            }
            case ComponentTypes.Postgres: {
                callback(this.state.postgres);
                break;
            }
            case ComponentTypes.Redis: {
                callback(this.state.redis);
                break;
            }
            default: {
                throw new SoloError(`Unknown component type ${componentType}, component id: ${componentId}`);
            }
        }
    }
    /** checks if component exists in the respective group */
    checkComponentExists(components, newComponent) {
        return components.some((component) => component.metadata.id === newComponent.metadata.id);
    }
    getNewComponentId(componentType) {
        return this.componentIds[componentType];
    }
    /**
     * Manages port forwarding for a component, checking if it's already enabled and persisting configuration
     * @param clusterReference The cluster reference to forward to
     * @param podReference The pod reference to forward to
     * @param podPort The port on the pod to forward from
     * @param localPort The local port to forward to (starting port if not available)
     * @param k8Client The Kubernetes client to use for port forwarding
     * @param logger Logger for messages
     * @param componentType The component type for persistence
     * @param label Label for the port forward
     * @param reuse Whether to reuse existing port forward if available
     * @param nodeId Optional node ID for finding component when cluster reference is not available
     * @returns The local port number that was used for port forwarding
     */
    async managePortForward(clusterReference, podReference, podPort, localPort, k8Client, logger, componentType, label, reuse = false, nodeId, persist = false) {
        // found component by cluster reference or nodeId
        let component;
        if (clusterReference) {
            const schemeComponents = this.getComponentsByClusterReference(componentType, clusterReference);
            component = schemeComponents[0];
        }
        else {
            const componentId = Templates.renderComponentIdFromNodeId(nodeId);
            component = this.getComponentById(componentType, componentId);
        }
        if (component === undefined) {
            // it is possible we are upgrading a chart and previous version has no clusterReference save in configMap
            // so we will not be able to find component by clusterReference
            reuse = true;
            logger.showUser(`Port forward config not found for previous installed ${label}, reusing existing port forward`);
        }
        else if (component.metadata.portForwardConfigs) {
            for (const portForwardConfig of component.metadata.portForwardConfigs) {
                if (reuse === true && portForwardConfig.podPort === podPort) {
                    if (portForwardConfig.localPort === localPort) {
                        logger.showUser(`${label} Port forward already enabled at ${portForwardConfig.localPort}`);
                        return portForwardConfig.localPort;
                    }
                    // localPort changed (migration) — kill the old process so portForward() reuse logic
                    // does not find it and return the stale port, then remove the stale config.
                    logger.showUser(`${label} Port forward migrating from ${portForwardConfig.localPort} to ${localPort}`);
                    // eslint-disable-next-line unicorn/no-null
                    await k8Client.pods().readByReference(null).stopPortForward(portForwardConfig.localPort);
                    component.metadata.portForwardConfigs = component.metadata.portForwardConfigs.filter((c) => !(c.podPort === podPort && c.localPort === portForwardConfig.localPort));
                    break;
                }
            }
        }
        // Enable port forwarding
        const portForwardPortNumber = await k8Client
            .pods()
            .readByReference(podReference)
            .portForward(localPort, podPort, reuse, persist);
        logger.addMessageGroup(constants.PORT_FORWARDING_MESSAGE_GROUP, 'Port forwarding enabled');
        logger.addMessageGroupMessage(constants.PORT_FORWARDING_MESSAGE_GROUP, `${label} port forward enabled on localhost:${portForwardPortNumber}`);
        if (component !== undefined) {
            component.metadata.portForwardConfigs ||= [];
            // Check if this exact podPort and localPort pair already exists
            const existingConfig = component.metadata.portForwardConfigs.find((config) => config.podPort === podPort && config.localPort === portForwardPortNumber);
            if (existingConfig) {
                logger.info(`port forward config already exists: localPort=${portForwardPortNumber}, podPort=${podPort}`);
            }
            else {
                logger.info(`add port localPort=${portForwardPortNumber}, podPort=${podPort}`);
                // Save port forward config to component
                component.metadata.portForwardConfigs.push({
                    podPort: podPort,
                    localPort: portForwardPortNumber,
                });
            }
        }
        return portForwardPortNumber;
    }
    /**
     * Stops port forwarding for a component by removing the configuration and stopping the forward
     * @param clusterReference The cluster reference
     * @param podReference The pod reference
     * @param podPort The port on the pod
     * @param localPort The local port
     * @param k8Client The Kubernetes client to use for stopping port forwarding
     * @param logger Logger for messages
     * @param componentType The component type
     * @param label Label for the port forward
     * @param nodeId Optional node ID for finding component when cluster reference is not available
     */
    async stopPortForwards(clusterReference, podReference, podPort, localPort, k8Client, logger, componentType, label, nodeId) {
        // Find component by cluster reference or nodeId
        let component;
        if (clusterReference) {
            const schemeComponents = this.getComponentsByClusterReference(componentType, clusterReference);
            component = schemeComponents[0];
        }
        else {
            const componentId = Templates.renderComponentIdFromNodeId(nodeId);
            component = this.getComponentById(componentType, componentId);
        }
        if (component === undefined || !component.metadata.portForwardConfigs) {
            logger.showUser(`No port forward config found for ${label}`);
            return;
        }
        // Find the matching port forward config
        const configIndex = component.metadata.portForwardConfigs.findIndex((config) => config.podPort === podPort && config.localPort === localPort);
        if (configIndex === -1) {
            logger.showUser(`Port forward config not found for ${label} with podPort=${podPort}, localPort=${localPort}`);
            return;
        }
        // Stop the port forward - use any pod reference since stopping should work regardless of pod
        // eslint-disable-next-line unicorn/no-null
        await k8Client.pods().readByReference(null).stopPortForward(localPort);
        logger.addMessageGroup(constants.PORT_FORWARDING_MESSAGE_GROUP, 'Port forwarding stopped');
        logger.addMessageGroupMessage(constants.PORT_FORWARDING_MESSAGE_GROUP, `${label} port forward stopped on localhost:${localPort}`);
        // Remove the config from component metadata
        component.metadata.portForwardConfigs.splice(configIndex, 1);
    }
}
//# sourceMappingURL=components-data-wrapper.js.map