"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");
/**
 * 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();
    /**
     * Install dependencies for all applications found in the apps/ directory
     */
    async installAllApps() {
        const appsDir = (0, path_1.join)(process.cwd(), 'apps');
        if (!(0, fs_1.existsSync)(appsDir)) {
            this.logger.debug('No apps/ directory found - skipping app installs');
            return;
        }
        const apps = this.discoverApps(appsDir);
        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:`);
        apps.forEach(app => this.logger.debug(`Component Installation Finished(  - ${app.name}`));
        for (const app of apps) {
            await this.installApp(app);
        }
    }
    /**
     * Build all applications found in the apps/ directory
     */
    async buildAllApps() {
        const appsDir = (0, path_1.join)(process.cwd(), 'apps');
        if (!(0, fs_1.existsSync)(appsDir)) {
            this.logger.debug('No apps/ directory found - skipping app builds');
            return;
        }
        const apps = this.discoverApps(appsDir);
        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(`Building application: ${app.name}`);
            await this.buildApp(app);
            await this.copyModifications(app, "build");
            this.logger.info(`Application ${app.name} built and moved successfully!`);
        }
        this.logger.info('All applications built and moved successfully!');
    }
    async startAllAppsInDevMode() {
        const appsDir = (0, path_1.join)(process.cwd(), 'apps');
        if (!(0, fs_1.existsSync)(appsDir)) {
            this.logger.info('No apps/ directory found - skipping app starts');
            return;
        }
        const apps = this.discoverApps(appsDir);
        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 dev mode: ${apps.map(a => a.name).join(", ")}`);
        // read the iocd.app.json and copy modifications
        apps.forEach(app => this.copyModifications(app, "dev"));
        this.startAppsInParallel(apps);
        this.logger.info('All template applications started in dev mode 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 {
            // Check if build script exists
            const packageJsonPath = (0, path_1.join)(appPath, 'package.json');
            const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
            if (!packageJson.scripts?.build) {
                this.logger.warn(`No build script found for ${appConfig.name} - skipping`);
                return;
            }
            // Execute npm run build in the app directory
            this.logger.debug(`Executing: npm run build in ${appPath}`);
            (0, child_process_1.execSync)('npm run build', {
                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 });
        }
    }
    /**
     * Start a specific application in development mode
     */
    async startAppInDevMode(appName) {
        const appPath = (0, path_1.join)(process.cwd(), 'apps', appName);
        this.logger.info(`Starting ${appName} in dev mode...`);
        try {
            // Check if build script exists
            const packageJsonPath = (0, path_1.join)(appPath, 'package.json');
            const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
            if (!packageJson.scripts?.build) {
                this.logger.warn(`No build script found for ${appName} - skipping`);
                return;
            }
            // Execute npm run dev in the app directory
            this.logger.debug(`Executing: npm run dev in ${appPath}`);
            (0, child_process_1.exec)('npm run dev', {
                cwd: appPath,
                encoding: 'utf-8'
            });
            this.logger.info(`${appName} started successfully!`);
        }
        catch (error) {
            this.logger.error(`Failed to start ${appName}: ${error}`);
            throw new error_handler_1.CLIError(`Failed to start application: ${appName}. ${error}`, { code: error_handler_1.ErrorCode.BUILD_ERROR });
        }
    }
    async getAllApps() {
        const appsDir = (0, path_1.join)(process.cwd(), 'apps');
        if (!(0, fs_1.existsSync)(appsDir)) {
            this.logger.debug('No apps/ directory found - skipping app listing');
            return [];
        }
        const apps = this.discoverApps(appsDir);
        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(appsDir) {
        try {
            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 hasPackageJson = (0, fs_1.existsSync)((0, path_1.join)(appPath, 'package.json'));
                const iocdAppJsonPath = (0, path_1.join)(appPath, 'iocd.app.json');
                const hasAppJson = (0, fs_1.existsSync)(iocdAppJsonPath);
                if (!hasPackageJson) {
                    this.logger.debug(`Skipping ${item}: no package.json found`);
                    return null;
                }
                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.warn(` 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) {
        const tasks = apps.map(app => ({
            command: 'npm run start',
            name: app.name,
            cwd: app.path
        }));
        this.logger.debug(JSON.stringify(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'));
    }
    copyModifications(appConfig, mode) {
        const modificationsConfigs = appConfig.config[mode]?.modifications;
        if (!modificationsConfigs) {
            this.logger.warn(`No modifications found for ${appConfig.name} in ${mode} mode - skipping`);
            return;
        }
        this.logger.debug(`Copying ${modificationsConfigs.length} modification(s) for ${appConfig.name} in ${mode} mode`);
        // 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(`Copying modification from ${srcPath} to ${destPath}`);
            try {
                // Check if source exists
                if (!(0, fs_1.existsSync)(srcPath)) {
                    this.logger.warn(`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(`Source is neither file nor directory: ${srcPath} - skipping`);
                    continue;
                }
                this.logger.debug(`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 });
            }
        }
        this.logger.debug(`Modifications copied successfully!`);
    }
    /**
     * 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