import { doctorGitWorktrees } from '../../engines/worktree-health.js';
import { nexusEventBus } from '../../engines/event-bus.js';
import { ArchitectsConfig } from '../config.js';
export class ArchitectsSentinel {
    db;
    strikeTeamId;
    repoRoot;
    operativeActivity;
    timer;
    /**
     * Static guard: refuse `nexus_session_bootstrap` when this process was
     * spawned by a parent push-dispatch run (`NEXUS_PARENT_DISPATCH` is set).
     * Prevents infinite agent-loop nesting.
     */
    static guardNestedDispatch(toolName) {
        const parentRunId = process.env.NEXUS_PARENT_DISPATCH;
        if (!parentRunId)
            return { ok: true };
        // Only bootstrap starts a new orchestration cycle — other tools are safe.
        if (toolName !== 'nexus_session_bootstrap')
            return { ok: true };
        return {
            ok: false,
            error: {
                code: 'nested-dispatch-refused',
                message: 'nexus_session_bootstrap is blocked inside a push-dispatch child process.',
                retriable: false,
                hint: `Parent dispatch run: ${parentRunId}. Nested orchestration is prohibited to prevent agent loops.`,
            },
        };
    }
    constructor(db, strikeTeamId, repoRoot, operativeActivity) {
        this.db = db;
        this.strikeTeamId = strikeTeamId;
        this.repoRoot = repoRoot;
        this.operativeActivity = operativeActivity;
    }
    start() {
        this.timer = setInterval(() => {
            void this.patrol();
        }, ArchitectsConfig.sentinelPatrolMs);
        this.timer.unref();
    }
    stop() {
        if (this.timer)
            clearInterval(this.timer);
        this.timer = undefined;
    }
    async patrol() {
        const worktree = await doctorGitWorktrees(this.repoRoot);
        const now = Date.now();
        const operatives = [...this.operativeActivity.entries()]
            .filter(([, activity]) => activity.strikeTeamId === this.strikeTeamId);
        let stalledCount = 0;
        let zombieCount = 0;
        const operativeReports = operatives.map(([operativeId, activity]) => {
            const ms = activity.lastSortieAt ? now - new Date(activity.lastSortieAt).getTime() : Number.POSITIVE_INFINITY;
            const state = !activity.lastSortieAt ? 'IDLE' :
                ms > 15 * 60_000 ? 'ZOMBIE' :
                    ms > 5 * 60_000 ? 'STALLED' :
                        'WORKING';
            if (state === 'ZOMBIE') {
                zombieCount += 1;
                nexusEventBus.emit('architects.sentinel.zombie', { operativeId, strikeTeamId: this.strikeTeamId });
            }
            else if (state === 'STALLED') {
                stalledCount += 1;
                nexusEventBus.emit('architects.sentinel.stall', { operativeId, strikeTeamId: this.strikeTeamId });
            }
            return {
                operativeId,
                state,
                wtHealthy: worktree.overall === 'healthy',
                lastSortieAt: activity.lastSortieAt ?? null,
            };
        });
        const report = {
            strikeTeamId: this.strikeTeamId,
            operativeReports,
            overallHealth: zombieCount > 0 ? 'critical' : stalledCount > 0 ? 'degraded' : 'healthy',
            stalledCount,
            zombieCount,
            generatedAt: new Date().toISOString(),
        };
        nexusEventBus.emit('architects.sentinel.patrol', report);
        return report;
    }
}
