"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const path_1 = require("path");
const logger_1 = require("../utils/logger");
const error_handler_1 = require("../utils/error.handler");
const concurrently_1 = __importDefault(require("concurrently"));
const colors_1 = require("../utils/colors");
const path_2 = require("../utils/path");
/**
 * Service for managing template applications.
 *
 * This service:
 * 1. Discovers all apps in the apps/ folder of generated projects
 * 2. Installs dependencies for all apps (npm install)
 * 3. Builds apps and copies them to modifications folder (npm run build)
 * 4. Provides progress feedback during operations
 */
class AppService {
    logger = logger_1.Logger.getInstance("app.service");
    /**
     * Install dependencies for all applications found in the apps/ directory
     */
    async installAllApps() {
        const apps = this.discoverApps();
        if (apps.length === 0) {
            this.logger.info("No applications found in apps/ directory");
            return;
        }
        this.logger.debug(`Found ${apps.length} applications to install dependencies for:`);
        for (const app of apps) {
            await this.installApp(app);
            this.logger.debug(`Component Installation Finished - ${app.name}`);
        }
    }
    /**
     * Install dependencies for a specific application by name
     */
    async installAppByName(appName) {
        const apps = this.discoverApps();
        const app = apps.find(a => a.name === appName);
        if (!app) {
            this.logger.warn(`Application '${appName}' not found in apps/ directory`);
            return;
        }
        await this.installApp(app);
    }
    /**
     * Build all applications found in the apps/ directory
     */
    async buildAllApps() {
        const apps = this.discoverApps();
        if (apps.length === 0) {
            this.logger.debug("No applications found in apps/ directory");
            return;
        }
        this.logger.info(`Found ${apps.length} applications to build: ${apps.map(a => a.name).join(", ")}`);
        for (const app of apps) {
            this.logger.info(`Processing application: ${app.name}`);
            // Copy top-level modifications first (these are always copied regardless of mode)
            this.processModifications(app, undefined);
            // Handle apps with build config
            if (app.config.build) {
                await this.buildApp(app);
                await this.processModifications(app, "build");
                this.logger.info(`Application ${app.name} built and processed successfully!`);
            }
            else {
                this.logger.debug(`Application ${app.name} has no build config - only copying top-level modifications`);
            }
        }
        this.logger.info("All applications built and moved successfully!");
    }
    async startAllAppsInDevelopment() {
        const apps = this.discoverApps();
        if (apps.length === 0) {
            this.logger.info("No applications found in apps/ directory");
            return;
        }
        this.logger.info(`Found ${apps.length} template applications to start in development mode: ${apps.map(a => a.name).join(", ")}`);
        // Copy base modifications for all apps (regardless of dev config)
        apps.forEach(app => this.processModifications(app, undefined));
        // Handle apps with dev config
        const appsWithDevConfig = apps.filter(app => app.config.dev);
        if (appsWithDevConfig.length > 0) {
            // Copy dev-specific modifications for apps that have a dev config
            appsWithDevConfig.forEach(app => this.processModifications(app, "dev"));
            // Then start all apps that have a dev script in parallel
            const appsThatHaveADevScript = appsWithDevConfig.filter(app => app.config.dev?.script);
            this.startAppsInParallel(appsThatHaveADevScript);
        }
        this.logger.info("All template applications started in development mode successfully!");
    }
    async copyBaseModifications() {
        const apps = this.discoverApps();
        if (apps.length === 0) {
            this.logger.debug("No applications found in apps/ directory");
            return;
        }
        this.logger.info("Going through applications for copying top-level modifications ...");
        this.logger.debug(`Found ${apps.length} template applications to copy static modifications from: ${apps.map(a => a.name).join(", ")}`);
        // Copy top-level modifications for all apps
        apps.forEach(app => this.processModifications(app, undefined));
        this.logger.info("All static modifications copied successfully!");
    }
    /**
     * Build a specific application
     */
    async buildApp(appConfig) {
        const appPath = (0, path_1.join)(process.cwd(), "apps", appConfig.name);
        this.logger.debug(`Building application ${appConfig.name}...`);
        try {
            const scriptToRun = appConfig.config.build?.script;
            if (!scriptToRun) {
                this.logger.debug(`No build script defined for ${appConfig.name} in iocd.app.json - skipping`);
                return;
            }
            // Execute npm run build in the app directory
            this.logger.debug(`Executing: npm run ${scriptToRun} in ${appPath}`);
            (0, child_process_1.execSync)(`npm run ${scriptToRun}`, {
                cwd: appPath,
                stdio: "pipe",
                encoding: "utf-8",
            });
            this.logger.debug(`Application ${appConfig.name} built successfully!`);
        }
        catch (error) {
            this.logger.error(`Failed to build application ${appConfig.name}: ${error}`);
            throw new error_handler_1.CLIError(`Failed to build application: ${appConfig.name}. ${error}`, { code: error_handler_1.ErrorCode.BUILD_ERROR });
        }
    }
    async getAllApps() {
        const apps = this.discoverApps();
        if (apps.length === 0) {
            this.logger.debug("No applications found in apps/ directory");
            return [];
        }
        return apps;
    }
    /**
     * Discover all app directories in the apps/ folder and return detailed app information
     */
    discoverApps() {
        try {
            const appsDir = path_2.PathUtils.getAppsDir();
            if (!(0, fs_1.existsSync)(appsDir)) {
                this.logger.debug("Apps directory does not exist");
                return [];
            }
            return (0, fs_1.readdirSync)(appsDir)
                .map(item => {
                const appPath = (0, path_1.join)(appsDir, item);
                const isDirectory = (0, fs_1.statSync)(appPath).isDirectory();
                if (!isDirectory) {
                    return null;
                }
                const iocdAppJsonPath = (0, path_1.join)(appPath, "iocd.app.json");
                const hasAppJson = (0, fs_1.existsSync)(iocdAppJsonPath);
                if (!hasAppJson) {
                    this.logger.debug(`Skipping ${item}: no iocd.app.json found`);
                    return null;
                }
                try {
                    // Parse the iocd.app.json configuration
                    const configContent = (0, fs_1.readFileSync)(iocdAppJsonPath, "utf-8");
                    const config = JSON.parse(configContent);
                    this.logger.debug(`Discovered app: ${item} with config: ${JSON.stringify(config, null, 4)}`);
                    return {
                        name: item,
                        path: appPath,
                        config: config,
                    };
                }
                catch (error) {
                    this.logger.error(`Failed to parse iocd.app.json for ${item}: ${error}`);
                    return null;
                }
            })
                .filter((app) => app !== null);
        }
        catch (error) {
            this.logger.error(`Error discovering apps: ${error}`);
            return [];
        }
    }
    /**
     * Install dependencies for a specific application
     */
    async installApp(appConfig) {
        const appPath = appConfig.path;
        this.logger.info(`Installing dependencies for application '${appConfig.name}'...`);
        try {
            // Check if package.json exists
            const packageJsonPath = (0, path_1.join)(appPath, "package.json");
            if (!(0, fs_1.existsSync)(packageJsonPath)) {
                this.logger.debug(` No package.json found for ${appConfig.name} - skipping`);
                return;
            }
            // Execute npm install in the app directory
            this.logger.debug(`Executing: npm install in ${appPath}`);
            (0, child_process_1.execSync)("npm install", {
                cwd: appPath,
                stdio: "pipe",
                encoding: "utf-8",
            });
            this.logger.info((0, colors_1.green)(`Dependencies for application '${appConfig.name}' installed successfully!`));
        }
        catch (error) {
            this.logger.error(`Failed to install dependencies for '${appConfig.name}': ${error}`);
            throw new error_handler_1.CLIError(`Failed to install dependencies for application: '${appConfig.name}'. ${error}`, { code: error_handler_1.ErrorCode.BUILD_ERROR });
        }
    }
    async startAppsInParallel(apps) {
        if (apps.length === 0) {
            this.logger.debug("No applications with a dev script found to start in development mode - skipping");
            return;
        }
        const tasks = apps.map(app => ({
            command: `npm run ${app.config.dev?.script}`,
            name: app.name,
            cwd: app.path,
        }));
        this.logger.debug(JSON.stringify(tasks));
        this.runConcurrently(tasks);
    }
    runConcurrently(tasks) {
        // Start all apps
        (0, concurrently_1.default)(tasks, {
            prefix: "name", // add app name before each log line
            killOthersOn: ["failure", "success"], // stop all if one fails
            restartTries: 0,
        }).result.then(() => console.log("All processes exited successfully!"), () => console.error("One of the processes failed"));
    }
    processModifications(appConfig, mode) {
        const modifications = (mode ? appConfig.config[mode]?.modifications : appConfig.config.base) ??
            [];
        // check if modifications is an array
        if (!Array.isArray(modifications)) {
            this.logger.debug(`${appConfig.name} app: modifications is not an array - skipping`);
            return;
        }
        const printFriendlyMode = mode ? mode : "base";
        this.logger.debug(`${appConfig.name} app: processing ${modifications.length} ${printFriendlyMode} modifications ${JSON.stringify(modifications, null, 4)}`);
        this.processModificationsCore(appConfig, modifications);
        if (modifications.length > 0) {
            this.logger.info(`${appConfig.name} app: ${modifications.length} ${printFriendlyMode} modifications copied successfully!`);
        }
    }
    processModificationsCore(appConfig, modificationsConfigs) {
        if (!modificationsConfigs) {
            this.logger.debug(`No modifications found  - skipping`);
            return;
        }
        // Copy each modification
        for (const config of modificationsConfigs) {
            const srcPath = (0, path_1.join)(appConfig.path, config.source);
            const destPath = (0, path_1.join)(process.cwd(), config.destination);
            this.logger.debug(`${appConfig.name} -> copying modification from ${srcPath} to ${destPath}`);
            try {
                // Check if source exists
                if (!(0, fs_1.existsSync)(srcPath)) {
                    this.logger.warn(`${appConfig.name} -> source path does not exist: ${srcPath} - skipping`);
                    continue;
                }
                // Get source stats to determine if it's a file or directory
                const srcStats = (0, fs_1.statSync)(srcPath);
                if (srcStats.isFile()) {
                    // Handle file copying
                    this.copyFile(srcPath, destPath);
                }
                else if (srcStats.isDirectory()) {
                    // Handle directory copying
                    this.copyDirectory(srcPath, destPath);
                }
                else {
                    this.logger.warn(`${appConfig.name} -> source is neither file nor directory: ${srcPath} - skipping`);
                    continue;
                }
                this.logger.debug(`${appConfig.name} -> successfully copied ${config.source} to ${config.destination}`);
            }
            catch (error) {
                this.logger.error(`Failed to copy ${config.source} to ${config.destination}: ${error}`);
                throw new error_handler_1.CLIError(`Failed to copy modification for ${appConfig.name}: ${error}`, { code: error_handler_1.ErrorCode.FILE_SYSTEM_ERROR });
            }
        }
    }
    /**
     * Copy a single file, creating destination directory if needed
     */
    copyFile(srcPath, destPath) {
        // Ensure destination directory exists
        const destDir = (0, path_1.dirname)(destPath);
        if (!(0, fs_1.existsSync)(destDir)) {
            (0, fs_1.mkdirSync)(destDir, { recursive: true });
            this.logger.debug(`Created destination directory: ${destDir}`);
        }
        // Copy the file
        (0, fs_1.copyFileSync)(srcPath, destPath);
        this.logger.debug(`Copied file: ${srcPath} → ${destPath}`);
    }
    /**
     * Copy a directory recursively, wiping out target first if it exists
     */
    copyDirectory(srcPath, destPath) {
        // If destination exists, remove it completely
        if ((0, fs_1.existsSync)(destPath)) {
            this.logger.debug(`Removing existing destination directory: ${destPath}`);
            (0, fs_1.rmSync)(destPath, { recursive: true, force: true });
        }
        // Create destination directory
        (0, fs_1.mkdirSync)(destPath, { recursive: true });
        this.logger.debug(`Created destination directory: ${destPath}`);
        // Copy all contents recursively
        this.copyDirectoryContents(srcPath, destPath);
    }
    /**
     * Recursively copy directory contents
     */
    copyDirectoryContents(srcDir, destDir) {
        const items = (0, fs_1.readdirSync)(srcDir);
        for (const item of items) {
            const srcItemPath = (0, path_1.join)(srcDir, item);
            const destItemPath = (0, path_1.join)(destDir, item);
            const itemStats = (0, fs_1.statSync)(srcItemPath);
            if (itemStats.isFile()) {
                (0, fs_1.copyFileSync)(srcItemPath, destItemPath);
                this.logger.debug(`Copied file: ${srcItemPath} → ${destItemPath}`);
            }
            else if (itemStats.isDirectory()) {
                (0, fs_1.mkdirSync)(destItemPath, { recursive: true });
                this.copyDirectoryContents(srcItemPath, destItemPath);
            }
        }
    }
}
exports.default = new AppService();
//# sourceMappingURL=app.service.js.map