import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
const ACTIVE_RUNTIME_WINDOW_MS = 2 * 60 * 1000;
const STALE_PRUNE_WINDOW_MS = 2 * 60 * 60 * 1000; // 2 hours (was 12h — too long, caused stale runtime accumulation)
export function createEmptyUsageState() {
    return {
        memories: { status: 'unused' },
        skills: { status: 'unused' },
        roster: { status: 'unused' },
        crews: { status: 'unused' },
        plan: { status: 'unused' },
        workflows: { status: 'unused' },
        hooks: { status: 'unused' },
        automations: { status: 'unused' },
        governance: { status: 'unused' },
        federation: { status: 'unused' },
    };
}
export function createEmptyTokenSummary() {
    return {
        grossInputTokens: 0,
        compressedTokens: 0,
        savedTokens: 0,
        forwardedTokens: 0,
        compressionPct: 0,
        totalRuns: 0,
        totalEvents: 0,
        byPhase: {},
        bySubsystem: {},
        bySourceClass: {},
        timeline: [],
    };
}
export function resolveNexusStateDir() {
    const candidates = [
        process.env.NEXUS_STATE_DIR?.trim(),
        path.join(os.homedir(), '.nexus-prime'),
        path.join('/tmp', 'nexus-prime-state'),
    ].filter((c) => Boolean(c));
    for (const candidate of candidates) {
        try {
            fs.mkdirSync(candidate, { recursive: true });
            fs.accessSync(candidate, fs.constants.W_OK);
            return candidate;
        }
        catch {
            // Try the next candidate.
        }
    }
    const selected = path.join('/tmp', 'nexus-prime-state');
    fs.mkdirSync(selected, { recursive: true });
    return selected;
}
export class RuntimeRegistry {
    registryDir;
    constructor(rootDir) {
        this.registryDir = path.join(rootDir ?? resolveNexusStateDir(), 'runtime-registry');
        this.ensureDir();
    }
    ensureDir() {
        // Re-create on every entry. The directory may be wiped by `nexus-prime reset`,
        // a stale-clean script, or the user — without this, list/write/pruneStale will
        // ENOENT and crash nexus_doctor / nexus_runtime_health / nexus_orchestrate.
        try {
            fs.mkdirSync(this.registryDir, { recursive: true });
        }
        catch { /* best-effort */ }
    }
    list() {
        this.ensureDir();
        this.pruneStale();
        const now = Date.now();
        return fs.readdirSync(this.registryDir)
            .filter((entry) => entry.endsWith('.json'))
            .map((entry) => this.read(entry.replace(/\.json$/, '')))
            .filter((snapshot) => Boolean(snapshot))
            .map((snapshot) => ({
            ...snapshot,
            health: now - snapshot.lastHeartbeatAt > ACTIVE_RUNTIME_WINDOW_MS ? 'stale' : 'active',
        }))
            .sort((left, right) => right.lastActivityAt - left.lastActivityAt);
    }
    read(runtimeId) {
        this.ensureDir();
        const target = this.snapshotPath(runtimeId);
        if (!fs.existsSync(target))
            return undefined;
        try {
            const parsed = JSON.parse(fs.readFileSync(target, 'utf8'));
            return {
                ...parsed,
                usage: { ...createEmptyUsageState(), ...(parsed.usage ?? {}) },
                tokens: { ...createEmptyTokenSummary(), ...(parsed.tokens ?? {}) },
                knowledgeFabric: parsed.knowledgeFabric,
                bootstrapCalled: parsed.bootstrapCalled ?? false,
                orchestrateCalled: parsed.orchestrateCalled ?? false,
                plannerCalled: parsed.plannerCalled ?? false,
                skipReasons: parsed.skipReasons ?? [],
                lastToolCalls: parsed.lastToolCalls ?? [],
            };
        }
        catch {
            return undefined;
        }
    }
    write(snapshot) {
        const normalized = {
            ...snapshot,
            usage: { ...createEmptyUsageState(), ...(snapshot.usage ?? {}) },
            tokens: { ...createEmptyTokenSummary(), ...(snapshot.tokens ?? {}) },
            bootstrapCalled: snapshot.bootstrapCalled ?? false,
            orchestrateCalled: snapshot.orchestrateCalled ?? false,
            plannerCalled: snapshot.plannerCalled ?? false,
            skipReasons: snapshot.skipReasons ?? [],
            lastToolCalls: snapshot.lastToolCalls ?? [],
        };
        this.ensureDir();
        fs.writeFileSync(this.snapshotPath(snapshot.runtimeId), JSON.stringify(normalized, null, 2), 'utf8');
        return normalized;
    }
    remove(runtimeId) {
        const target = this.snapshotPath(runtimeId);
        if (fs.existsSync(target)) {
            fs.unlinkSync(target);
        }
    }
    pruneStalePublic() {
        this.pruneStale();
    }
    pruneStale() {
        this.ensureDir();
        const now = Date.now();
        for (const entry of fs.readdirSync(this.registryDir)) {
            if (!entry.endsWith('.json'))
                continue;
            const target = path.join(this.registryDir, entry);
            try {
                const snapshot = JSON.parse(fs.readFileSync(target, 'utf8'));
                const age = now - Number(snapshot.lastHeartbeatAt || 0);
                // Prune if older than the stale window
                if (age > STALE_PRUNE_WINDOW_MS) {
                    fs.unlinkSync(target);
                    continue;
                }
                // Prune if the owning process is dead (stale beyond active window)
                if (age > ACTIVE_RUNTIME_WINDOW_MS && snapshot.pid) {
                    try {
                        process.kill(snapshot.pid, 0);
                    }
                    catch {
                        // Process is dead — prune immediately
                        fs.unlinkSync(target);
                    }
                }
            }
            catch {
                fs.unlinkSync(target);
            }
        }
    }
    snapshotPath(runtimeId) {
        return path.join(this.registryDir, `${runtimeId}.json`);
    }
}
