/**
 * Self-Improvement Service
 *
 * Enables the orchestrator to safely modify the Crewly codebase,
 * using the marker system to persist state across hot-reload restarts.
 *
 * @module services/orchestrator/self-improvement
 */
import * as fs from 'fs/promises';
import * as path from 'path';
import * as crypto from 'crypto';
import { spawn } from 'child_process';
import { getImprovementMarkerService, } from './improvement-marker.service.js';
import { getSlackBridgeLazy } from './slack-bridge-lazy.js';
import { LoggerService } from '../core/logger.service.js';
/**
 * Self-improvement service singleton
 */
let selfImprovementInstance = null;
/**
 * SelfImprovementService class
 *
 * Manages the self-improvement workflow using the marker system
 * to persist state across hot-reload restarts.
 *
 * Flow:
 * 1. planImprovement() - Creates marker in 'planning' phase
 * 2. executeImprovement() - Creates backups, applies changes
 * 3. Process is killed by hot-reload
 * 4. Startup hook handles validation and rollback
 *
 * @example
 * ```typescript
 * const service = getSelfImprovementService();
 *
 * const plan = await service.planImprovement({
 *   description: 'Add new feature',
 *   targetFiles: ['src/feature.ts'],
 *   changes: [{ file: 'src/feature.ts', type: 'create', description: 'New feature', content: '...' }],
 * });
 *
 * const result = await service.executeImprovement(request);
 * // Hot-reload will kill the process, startup hook will validate
 * ```
 */
export class SelfImprovementService {
    projectRoot;
    logger = LoggerService.getInstance().createComponentLogger('SelfImprovement');
    /**
     * Create a new SelfImprovementService.
     *
     * @param projectRoot - Root directory of the project (defaults to process.cwd())
     */
    constructor(projectRoot) {
        this.projectRoot = projectRoot || process.cwd();
    }
    /**
     * Plan an improvement (creates marker in 'planning' phase).
     *
     * @param request - The improvement request configuration
     * @returns ImprovementPlan for review before execution
     * @throws Error if another improvement is already pending
     */
    async planImprovement(request) {
        const markerService = getImprovementMarkerService();
        await markerService.initialize();
        // Check for existing pending improvement
        const existing = await markerService.getPendingImprovement();
        if (existing) {
            throw new Error(`Another improvement is already pending: ${existing.id} (${existing.description})`);
        }
        const taskId = `si-${Date.now()}`;
        // Analyze risk level
        const riskLevel = this.analyzeRisk(request.targetFiles);
        const requiresRestart = this.checkRequiresRestart(request.targetFiles);
        // Create marker
        await markerService.createMarker(taskId, request.description, request.targetFiles, request.slackContext);
        // Notify via Slack
        await this.notifySlack(`:wrench: *Self-improvement planned:* ${request.description}\n` +
            `Files: ${request.targetFiles.length}\n` +
            `Risk: ${riskLevel}\n` +
            `Requires restart: ${requiresRestart}`, 'normal');
        this.logger.info('Task planned', { taskId });
        return {
            id: taskId,
            description: request.description,
            targetFiles: request.targetFiles,
            changes: request.changes.map(c => ({
                file: c.file,
                type: c.type,
                description: c.description,
            })),
            riskLevel,
            requiresRestart,
        };
    }
    /**
     * Execute the planned improvement.
     *
     * IMPORTANT: This method:
     * 1. Creates backups
     * 2. Writes marker with backup info
     * 3. Applies changes (triggers hot-reload)
     * 4. Process gets killed by hot-reload
     * 5. Startup hook handles validation
     *
     * @param request - The improvement request with content
     * @returns Result indicating that changes were started
     * @throws Error if no improvement is planned
     */
    async executeImprovement(request) {
        const markerService = getImprovementMarkerService();
        // Get the pending marker
        const marker = await markerService.getPendingImprovement();
        if (!marker) {
            throw new Error('No improvement planned. Call planImprovement first.');
        }
        if (marker.phase !== 'planning') {
            throw new Error(`Cannot execute: improvement is in '${marker.phase}' phase`);
        }
        try {
            // Step 1: Create git checkpoint
            this.logger.info('Creating git checkpoint...');
            const gitInfo = await this.createGitCheckpoint();
            // Step 2: Create file backups
            this.logger.info('Creating file backups...');
            const backups = await this.createFileBackups(request.targetFiles);
            // Step 3: Record backup in marker (CRITICAL: do this before changes)
            await markerService.recordBackup(gitInfo.commit, gitInfo.branch, backups);
            // Notify that we're about to make changes
            await this.notifySlack(`:hammer: *Starting self-improvement changes...*\n` +
                `Backup created: ${gitInfo.commit?.substring(0, 7) || 'file backups'}\n` +
                `Files to modify: ${request.changes.length}\n\n` +
                `:warning: Hot-reload will restart the process. Validation will run automatically.`, 'normal');
            // Step 4: Apply changes (this will trigger hot-reload!)
            this.logger.info('Applying changes...');
            const appliedChanges = await this.applyChanges(request.changes);
            // Step 5: Record that changes were applied
            await markerService.recordChangesApplied(appliedChanges);
            // At this point, hot-reload will likely kill us.
            // If we survive (somehow), we could run validation here,
            // but the startup hook will handle it anyway.
            return {
                started: true,
                message: 'Changes applied. Hot-reload will restart the process. ' +
                    'Validation will run automatically on startup.',
                taskId: marker.id,
            };
        }
        catch (error) {
            this.logger.error('Error during execution', { error: error instanceof Error ? error.message : String(error) });
            await markerService.recordError(error.message, error.stack);
            await this.notifySlack(`:x: *Self-improvement failed:* ${error.message}`, 'critical');
            throw error;
        }
    }
    /**
     * Cancel a planned improvement.
     *
     * @throws Error if improvement is past planning phase
     */
    async cancelImprovement() {
        const markerService = getImprovementMarkerService();
        const marker = await markerService.getPendingImprovement();
        if (!marker) {
            return; // Nothing to cancel
        }
        if (marker.phase !== 'planning') {
            throw new Error(`Cannot cancel: improvement is in '${marker.phase}' phase. ` +
                `Rollback may be needed.`);
        }
        await markerService.deleteMarker();
        await this.notifySlack(`:no_entry: Self-improvement cancelled: ${marker.description}`, 'normal');
        this.logger.info('Task cancelled', { taskId: marker.id });
    }
    /**
     * Get current improvement status.
     *
     * @returns Current improvement marker or null
     */
    async getStatus() {
        const markerService = getImprovementMarkerService();
        return await markerService.getPendingImprovement();
    }
    /**
     * Get improvement history.
     *
     * @param limit - Maximum number of history items to return
     * @returns Array of historical improvement markers
     */
    async getHistory(limit = 10) {
        const markerService = getImprovementMarkerService();
        return await markerService.getHistory(limit);
    }
    /**
     * Create git checkpoint.
     *
     * @returns Git commit and branch info
     */
    async createGitCheckpoint() {
        try {
            const commit = (await this.runCommand('git', ['rev-parse', 'HEAD'])).trim();
            const branch = (await this.runCommand('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).trim();
            return { commit, branch };
        }
        catch {
            this.logger.warn('Git checkpoint failed, using file backups only');
            return {};
        }
    }
    /**
     * Create file backups.
     *
     * @param files - Files to backup
     * @returns Array of backup records
     */
    async createFileBackups(files) {
        const backups = [];
        const backupDir = path.join(this.projectRoot, '.crewly', 'self-improvement', 'backups', `backup-${Date.now()}`);
        await fs.mkdir(backupDir, { recursive: true });
        for (const file of files) {
            const fullPath = path.join(this.projectRoot, file);
            const backupPath = path.join(backupDir, file);
            try {
                const content = await fs.readFile(fullPath, 'utf-8');
                const checksum = crypto.createHash('md5').update(content).digest('hex');
                await fs.mkdir(path.dirname(backupPath), { recursive: true });
                await fs.writeFile(backupPath, content);
                backups.push({
                    originalPath: file,
                    backupPath,
                    checksum,
                    existed: true,
                });
            }
            catch (error) {
                if (error.code === 'ENOENT') {
                    // File doesn't exist (will be created)
                    backups.push({
                        originalPath: file,
                        backupPath,
                        checksum: '',
                        existed: false,
                    });
                }
                else {
                    throw error;
                }
            }
        }
        return backups;
    }
    /**
     * Apply file changes.
     *
     * @param changes - Changes to apply
     * @returns Array of applied changes
     */
    async applyChanges(changes) {
        const applied = [];
        for (const change of changes) {
            const fullPath = path.join(this.projectRoot, change.file);
            switch (change.type) {
                case 'delete':
                    await fs.unlink(fullPath);
                    break;
                case 'create':
                case 'modify':
                    if (change.content) {
                        await fs.mkdir(path.dirname(fullPath), { recursive: true });
                        await fs.writeFile(fullPath, change.content);
                    }
                    break;
            }
            applied.push({
                file: change.file,
                type: change.type,
                description: change.description,
            });
            this.logger.info('Applied change', { type: change.type, file: change.file });
        }
        return applied;
    }
    /**
     * Analyze risk level.
     *
     * @param files - Files to analyze
     * @returns Risk level
     */
    analyzeRisk(files) {
        const highRiskPatterns = [
            /index\.ts$/,
            /server\.ts$/,
            /app\.ts$/,
            /package\.json$/,
            /tsconfig\.json$/,
        ];
        const mediumRiskPatterns = [
            /service\.ts$/,
            /controller\.ts$/,
            /middleware/,
        ];
        for (const file of files) {
            if (highRiskPatterns.some(p => p.test(file))) {
                return 'high';
            }
        }
        for (const file of files) {
            if (mediumRiskPatterns.some(p => p.test(file))) {
                return 'medium';
            }
        }
        return 'low';
    }
    /**
     * Check if changes require restart.
     *
     * @param files - Files being changed
     * @returns True if restart is needed
     */
    checkRequiresRestart(files) {
        const restartPatterns = [
            /backend\/src\//,
            /package\.json$/,
            /tsconfig\.json$/,
        ];
        return files.some(file => restartPatterns.some(pattern => pattern.test(file)));
    }
    /**
     * Run shell command.
     *
     * @param cmd - Command to run
     * @param args - Command arguments
     * @returns Command output
     */
    runCommand(cmd, args) {
        return new Promise((resolve, reject) => {
            const child = spawn(cmd, args, {
                cwd: this.projectRoot,
                stdio: ['pipe', 'pipe', 'pipe'],
            });
            let stdout = '';
            let stderr = '';
            child.stdout.on('data', (data) => { stdout += data; });
            child.stderr.on('data', (data) => { stderr += data; });
            child.on('close', (code) => {
                if (code === 0) {
                    resolve(stdout);
                }
                else {
                    reject(new Error(stderr || stdout));
                }
            });
            child.on('error', reject);
        });
    }
    /**
     * Send Slack notification.
     *
     * @param message - Message to send
     * @param urgency - Urgency level
     */
    async notifySlack(message, urgency) {
        try {
            const slackBridge = await getSlackBridgeLazy();
            await slackBridge.sendNotification({
                type: 'alert',
                title: 'Self-Improvement',
                message,
                urgency,
                timestamp: new Date().toISOString(),
            });
        }
        catch {
            // Slack not available
        }
    }
}
/**
 * Get singleton instance.
 *
 * @returns SelfImprovementService instance
 */
export function getSelfImprovementService() {
    if (!selfImprovementInstance) {
        selfImprovementInstance = new SelfImprovementService();
    }
    return selfImprovementInstance;
}
/**
 * Reset instance (for testing).
 */
export function resetSelfImprovementService() {
    selfImprovementInstance = null;
}
//# sourceMappingURL=self-improvement.service.js.map