"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config_service_1 = require("../config/config.service");
const logger_1 = require("../../utils/logger");
const file_helper_1 = require("./file.helper");
const github_store_1 = require("./stores/github.store");
const local_store_1 = require("./stores/local.store");
const s3_store_1 = require("./stores/s3.store");
const version_selector_1 = require("./version.selector");
const fs_1 = require("fs");
const path_1 = require("path");
const component_config_1 = require("./component.config");
const colors_1 = require("../../utils/colors");
/**
 * Service for managing components
 */
class ComponentsService {
    logger = logger_1.Logger.getInstance("components.service");
    componentsStore;
    getComponentsStore() {
        if (this.componentsStore) {
            return this.componentsStore;
        }
        if (config_service_1.ConfigManager.config.components.store === "github") {
            this.componentsStore = new github_store_1.GitHubComponentsStore(config_service_1.ConfigManager.config.components.github.repo, config_service_1.ConfigManager.config.components.github.usePrereleases || false);
        }
        else if (config_service_1.ConfigManager.config.components.store === "local") {
            this.componentsStore = new local_store_1.LocalComponentsStore(config_service_1.ConfigManager.config.components.local.path);
        }
        else if (config_service_1.ConfigManager.config.components.store === "s3") {
            this.componentsStore = new s3_store_1.S3ComponentsStore(config_service_1.ConfigManager.config.components.s3);
        }
        else {
            throw new Error(`Unknown components store: ${config_service_1.ConfigManager.config.components.store}`);
        }
        this.logger.info(`Using components store: ${this.componentsStore.getInfo()}`);
        return this.componentsStore;
    }
    async listComponentsInThisProject() {
        // Logic to extract components from the definition
        const componentsList = config_service_1.ConfigManager.config.components.list;
        return Object.keys(componentsList).map(key => ({
            name: key,
            version: componentsList[key]
        }));
    }
    async browseComponentsFromStore() {
        this.logger.info(`Listing available components ...`);
        const all = await this.getComponentsStore().getAll();
        const byComponent = all.reduce((acc, component) => {
            let current = acc[component.name];
            if (!current) {
                current = {
                    versions: new Set()
                };
                acc[component.name] = current;
            }
            current.versions.add(component.version);
            return acc;
        }, {});
        this.logger.debug(`Found ${Object.keys(byComponent).length} components:`);
        for (const [name, { versions }] of Object.entries(byComponent)) {
            this.logger.debug(`Component: ${name}, Versions: ${Array.from(versions).join(", ")}`);
        }
        return all;
    }
    async installComponent(name, version) {
        version = version ?? "latest";
        if (name.indexOf('@') >= 0) {
            const parts = name.split('@');
            name = parts[0];
            version = parts[1] || version;
        }
        // install the component
        const resolvedVersion = await this.installComponentCore(name, version);
        // Save the new component to the cnf
        this.logger.debug(`Updating iocd.cli.config.json to include ${name}@${resolvedVersion}...`);
        const componentsList = config_service_1.ConfigManager.config.components.list;
        componentsList[name] = resolvedVersion;
        await config_service_1.ConfigManager.saveComponents(componentsList);
        this.logger.debug(`Updated iocd.cli.config.json to include ${name}@${resolvedVersion}`);
    }
    async uninstallComponent(name) {
        this.logger.info(`Uninstalling component ${name}...`);
        // Remove from config first
        const componentsList = config_service_1.ConfigManager.config.components.list;
        if (componentsList[name]) {
            delete componentsList[name];
            await config_service_1.ConfigManager.saveComponents(componentsList);
            this.logger.debug(`Updated iocd.cli.config.json to remove ${name}`);
        }
        else {
            this.logger.warn(`Component ${name} not found in iocd.cli.config.json`);
        }
        // Now remove the files
        const fileService = new file_helper_1.FileHelper();
        await fileService.removeComponent(name);
        this.logger.info(`Component ${name} uninstalled successfully.`);
    }
    async installComponents() {
        this.logger.info('Installing all components described in iocd.cli.config.json...');
        // Use the ComponentsStore to download all components described in package.json under ioCDComponents
        const components = await this.listComponentsInThisProject();
        for (const component of components) {
            try {
                await this.installComponentCore(component.name, component.version);
            }
            catch (error) {
                const errorMsg = `Failed to install component ${component.name}@${component.version}: ${error}`;
                this.logger.error(errorMsg, error);
                if (component.name === "iocd") {
                    throw new Error("iocd component failed to install, aborting further installations...");
                }
            }
        }
        this.logger.info(`Components installation completed successfully.`);
    }
    async installComponentsForBuild() {
        this.logger.info('Installing components for build (excluding development-only components)...');
        // Get all components from configuration
        const components = await this.listComponentsInThisProject();
        // Get list of components to exclude from build
        const excludeFromBuild = config_service_1.ConfigManager.config.components.excludeFromBuild || [];
        this.logger.debug(`Components excluded from build: ${excludeFromBuild.join(', ')}`);
        // Filter out excluded components
        const buildComponents = components.filter(component => !excludeFromBuild.includes(component.name));
        for (const component of buildComponents) {
            try {
                await this.installComponentCore(component.name, component.version);
            }
            catch (error) {
                const errorMsg = `Failed to install component ${component.name}@${component.version}: ${error}`;
                this.logger.error(errorMsg, error);
                if (component.name === "iocd") {
                    throw new Error("iocd component failed to install, aborting further installations...");
                }
            }
        }
        this.logger.info(`Installation completed successfully.`);
    }
    /**
     * Pin all "latest" component versions to their currently installed specific versions
     * This ensures reproducible builds by replacing "latest" with actual version numbers
     */
    async pinComponentVersions() {
        this.logger.debug('Pinning component versions...');
        const componentsList = config_service_1.ConfigManager.config.components.list;
        const pinnedVersions = {};
        const changedComponents = [];
        for (const [componentName, currentVersion] of Object.entries(componentsList)) {
            if (currentVersion === "latest") {
                // Get the actual installed version by checking what's available
                try {
                    const availableComponents = await this.getComponentsStore().getAll();
                    const matchingComponents = availableComponents.filter(c => c.name === componentName);
                    if (matchingComponents.length > 0) {
                        // Use the version selector to get the latest version
                        const selectedComponent = (0, version_selector_1.selectVersion)("latest", matchingComponents);
                        if (selectedComponent) {
                            pinnedVersions[componentName] = selectedComponent.version;
                            changedComponents.push(`${componentName}: "latest" → "${selectedComponent.version}"`);
                            this.logger.debug(`Pinned ${componentName} from "latest" to "${selectedComponent.version}"`);
                        }
                        else {
                            // Keep as latest if no version could be resolved
                            pinnedVersions[componentName] = currentVersion;
                            this.logger.warn(`Could not resolve latest version for ${componentName}, keeping as "latest"`);
                        }
                    }
                    else {
                        // Component not found in store, keep current version
                        pinnedVersions[componentName] = currentVersion;
                        this.logger.warn(`Component ${componentName} not found in store, keeping version as "${currentVersion}"`);
                    }
                }
                catch (error) {
                    // On error, keep the current version
                    pinnedVersions[componentName] = currentVersion;
                    this.logger.error(`Error resolving version for ${componentName}: ${error}`);
                }
            }
            else {
                // Already pinned or specific version, keep as is
                pinnedVersions[componentName] = currentVersion;
            }
        }
        // Save the pinned versions if any changes were made
        if (changedComponents.length > 0) {
            await config_service_1.ConfigManager.saveComponents(pinnedVersions);
            this.logger.debug(`Component versions pinned successfully:`);
            changedComponents.forEach(change => this.logger.debug(`  • ${change}`));
        }
        else {
            this.logger.debug('All component versions are already pinned (no "latest" versions found)');
        }
    }
    async installComponentCore(name, version) {
        this.logger.info(`Installing component ${name}@${version}...`);
        const componentData = await this.getComponentsStore().download(name, version);
        const fileService = new file_helper_1.FileHelper();
        const componentDir = await fileService.storeComponent(componentData);
        // find component.json inside the componentDir
        const componentJsonPath = (0, path_1.join)(componentDir, 'component.json');
        this.logger.debug(`Looking for component.json at ${componentJsonPath}...`);
        if ((0, fs_1.existsSync)(componentJsonPath)) {
            this.logger.debug(`Found component.json for ${name}@${version}, processing components config...`);
            await this.processComponentConfig(componentDir, componentJsonPath);
        }
        const actualVersion = componentData.actualVersion;
        this.logger.info((0, colors_1.green)(`Component ${name}@${actualVersion} installed successfully.`));
        return actualVersion;
    }
    async processComponentConfig(componentDir, componentJsonPath) {
        await (0, component_config_1.copyModifications)(componentDir, componentJsonPath);
    }
}
exports.default = new ComponentsService();
//# sourceMappingURL=components.service.js.map