/**
 * Nexus Prime - Darwin Loop Orchestrator
 *
 * Enforces controlled self-improvement via bounded modification spaces.
 * Ensures proposed hypotheses target allowed directories before logging them.
 *
 * Phase: 8F (Darwin Loop)
 */
import { DarwinJournal } from './darwin-journal.js';
import { nexusEventBus } from './event-bus.js';
import { getSharedGithubBridge } from './github-bridge.js';
import { getSharedTelemetry } from './telemetry-remote.js';
// ─────────────────────────────────────────────────────────────────────────────
// Self-Improvement Gate
// ─────────────────────────────────────────────────────────────────────────────
const SELF_IMPROVE_ENABLED = (process.env.NEXUS_SELF_IMPROVE ?? '1') !== '0';
// ─────────────────────────────────────────────────────────────────────────────
// Bounded Improvement Space Config
// ─────────────────────────────────────────────────────────────────────────────
const ALLOWED_PATHS = [
    'src/engines/',
    'src/phantom/'
];
const FORBIDDEN_PATHS = [
    'src/agents/adapters/mcp.ts',
    'src/cli.ts',
    'src/index.ts',
    'package.json',
    'tsconfig.json'
];
// ─────────────────────────────────────────────────────────────────────────────
// Orchestrator
// ─────────────────────────────────────────────────────────────────────────────
export class DarwinLoop {
    journal;
    memory;
    githubBridge;
    remoteTelemetry;
    constructor(memory, options = {}) {
        this.journal = new DarwinJournal();
        this.memory = memory;
        this.githubBridge = options.githubBridge ?? getSharedGithubBridge();
        this.remoteTelemetry = options.telemetry ?? getSharedTelemetry();
    }
    /**
     * Determine if a target file is within the bounded improvement space.
     */
    isAllowedTarget(targetFile) {
        // Normalize path separators
        const normalized = targetFile.replace(/\\/g, '/');
        // Check forbidden explicitly
        for (const f of FORBIDDEN_PATHS) {
            if (normalized === f || normalized.endsWith(`/${f}`)) {
                return { allowed: false, reason: `Path explicitly forbidden by core safety bounds: ${f}` };
            }
        }
        // Check allowed prefixes
        for (const a of ALLOWED_PATHS) {
            if (normalized.includes(a)) {
                return { allowed: true };
            }
        }
        return { allowed: false, reason: 'Path is outside of ALLOWED structural bounds (must be engines/ or phantom/)' };
    }
    /**
     * Propose an improvement hypothesis if bounds check passes.
     */
    propose(hypothesis, targetFile, approach) {
        const boundsCheck = this.isAllowedTarget(targetFile);
        if (!boundsCheck.allowed) {
            throw new Error(`[Darwin Loop Rejected Proposal]: ${boundsCheck.reason}`);
        }
        // Forward to journal
        const cycle = this.journal.propose(hypothesis, targetFile, approach);
        this.remoteTelemetry.trackAutonomyEvent('autonomy_candidate', {
            cycleId: cycle.id,
            targetFile,
            approach,
            bounded: true,
        });
        return cycle;
    }
    /**
     * Run a build step to validate the code still compiles.
     */
    async validateBuild() {
        const { execAsync } = await import('../utils/exec-async.js');
        try {
            await execAsync('npm run build', { cwd: process.cwd(), timeout: 30000 });
        }
        catch (error) {
            const isTimeout = String(error?.message || '').includes('timed out') || error?.killed;
            if (isTimeout) {
                throw new Error('Build validation timed out after 30s. Build may be hung or system is under load.');
            }
            throw new Error(`Build validation failed:\n${error.stdout || error.message}`);
        }
    }
    /**
     * Review a pending cycle. Evaluates builds before allowing 'apply'.
     */
    async review(cycleId, action, learnings = []) {
        const cycle = this.journal.getCycle(cycleId);
        if (!cycle) {
            throw new Error(`Darwin Cycle ${cycleId} not found.`);
        }
        if (cycle.outcome !== 'pending') {
            throw new Error(`Darwin Cycle ${cycleId} is already ${cycle.outcome}.`);
        }
        if (action === 'apply') {
            // Before applying, validate that the build succeeds
            this.remoteTelemetry.trackAutonomyEvent('autonomy_apply_started', {
                cycleId,
                targetFile: cycle.targetFile,
            });
            try {
                await this.validateBuild();
            }
            catch (error) {
                this.remoteTelemetry.trackAutonomyEvent('autonomy_apply_blocked', {
                    cycleId,
                    targetFile: cycle.targetFile,
                    reason: 'build_validation_failed',
                    detail: error?.message ?? String(error),
                });
                throw error;
            }
        }
        // Update state
        const outcomeMap = {
            'apply': 'applied',
            'reject': 'rejected',
            'defer': 'deferred'
        };
        const updated = this.journal.updateCycle(cycleId, {
            outcome: outcomeMap[action],
            learnings
        });
        if (updated && (updated.outcome === 'applied' || updated.outcome === 'rejected')) {
            const fitnessDelta = (updated.metricsAfter?.fitness ?? 0) - (updated.metricsBefore?.fitness ?? 0);
            const content = [
                `Darwin cycle ${updated.id}: hypothesis "${updated.hypothesis}"`,
                `Target: ${updated.targetFile}. Outcome: ${updated.outcome}.`,
                `Build: ${updated.buildPassed ? 'PASS' : 'FAIL'}, Tests: ${updated.testsPassed ? 'PASS' : 'FAIL'}.`,
                `Fitness delta: ${fitnessDelta}.`,
                updated.learnings.length ? `Learnings: ${updated.learnings.join('; ')}` : '',
            ].filter(Boolean).join(' ');
            this.memory?.store(content, 0.5, ['#darwin', `#darwin:${updated.outcome}`, `#target:${updated.targetFile}`], undefined, 0, {
                tier: 'cortex',
                state: 'active',
                trust: updated.outcome === 'applied' ? 0.9 : 0.6,
                source: 'system',
                provenance: {
                    source: 'runtime',
                    summary: 'Darwin cycle outcome',
                    tags: ['#darwin'],
                },
            });
            nexusEventBus.emit('darwin.cycle.complete', {
                id: updated.id,
                outcome: updated.outcome,
                targetFile: updated.targetFile,
            });
        }
        if (updated?.outcome === 'applied') {
            const promotion = await this.githubBridge.promoteToPR({
                hypothesis: updated.hypothesis,
                cycleId: updated.id,
                targetFile: updated.targetFile,
                learnings: updated.learnings,
            });
            if (promotion.status === 'pr_created') {
                this.remoteTelemetry.trackAutonomyEvent('autonomy_apply_finished', {
                    cycleId: updated.id,
                    targetFile: updated.targetFile,
                    branch: promotion.branch,
                    url: promotion.url,
                    method: promotion.method,
                });
            }
            else {
                this.remoteTelemetry.trackAutonomyEvent('autonomy_apply_blocked', {
                    cycleId: updated.id,
                    targetFile: updated.targetFile,
                    method: promotion.method,
                    reason: promotion.reason ?? 'artifact_saved',
                    artifactPath: promotion.artifactPath,
                });
            }
        }
        return updated;
    }
    /**
     * Get pending cycles
     */
    getPending() {
        return this.journal.getPending();
    }
    /**
     * Whether self-improvement is enabled for this runtime instance.
     */
    static isEnabled() {
        return SELF_IMPROVE_ENABLED;
    }
    /**
     * Auto-propose an improvement based on orchestration learnings.
     * Called post-execution; never auto-applies (review gate always required).
     * Returns null if disabled, budget exceeded, or no clear improvement found.
     */
    autoPropose(learnings, task) {
        if (!SELF_IMPROVE_ENABLED)
            return null;
        // Budget cap: at most 1 pending cycle at a time
        if (this.journal.getPending().length > 0)
            return null;
        // Extract a target file from learnings (look for src/engines/ or src/phantom/ paths)
        const pathPattern = /src\/(engines|phantom)\/[\w\-.]+\.ts/;
        const targetFile = learnings.map((l) => pathPattern.exec(l)?.[0]).find(Boolean) ?? null;
        if (!targetFile)
            return null;
        // Build hypothesis from learnings
        const hypothesis = `Auto-improvement opportunity observed during: ${task.slice(0, 80)}. Learnings: ${learnings.slice(0, 2).join(' | ').slice(0, 200)}`;
        const approach = 'Analyze the target file for the pattern surfaced during execution and propose a minimal improvement.';
        try {
            return this.propose(hypothesis, targetFile, approach);
        }
        catch {
            return null; // Bounds check rejected — not an error, just skip
        }
    }
}
