/**
 * Run-management I/O functions extracted from OrchestratorEngine.
 * Each function takes a narrow `RunManagerContext` instead of `this`, making
 * them testable in isolation and safe to call from any module.
 *
 * OrchestratorEngine delegates to these via thin wrapper methods that pass
 * `this._runMgrCtx()` as the first argument.
 */
import * as fs from 'fs';
import * as path from 'path';
import { randomUUID } from 'crypto';
import { execAsync } from '../../utils/exec-async.js';
import { getSharedSummarizer } from '../self-summarizer.js';
import { computeFilePriors } from '../priors/file-priors.js';
import { dedupeStrings, } from './scoring.js';
// ─── Session summary ──────────────────────────────────────────────────────────
/** Returns the files that should be included in the session summary snapshot. */
export function getSummaryActiveFiles(ctx) {
    const runtimeSelectedFiles = ctx.lastRun?.knowledgeFabric?.repo.selectedFiles ?? [];
    const lastRunFiles = ctx.lastRun?.workerManifests.flatMap((manifest) => manifest.files.map((file) => file.path)) ?? [];
    const modifiedFiles = ctx.lastRun?.workerResults.flatMap((result) => result.modifiedFiles) ?? [];
    return dedupeStrings([
        ...modifiedFiles,
        ...lastRunFiles,
        ...runtimeSelectedFiles,
    ]).slice(0, 30);
}
/**
 * Persist a session summary snapshot to the shared summarizer.
 * Returns the summarizer result, or null if summarization fails.
 * (public — called from dispose() and from MCP layer)
 */
export function persistSessionSummary(ctx, currentTokenCount) {
    try {
        const projectId = ctx.getRepoScopedId('project');
        const summarizer = getSharedSummarizer({ projectId });
        const priorSummary = summarizer.bootstrapFromSummary(projectId);
        return summarizer.summarize({
            conversationHistory: ctx.sessionState.objectiveHistory ?? [],
            activeFiles: getSummaryActiveFiles(ctx),
            taskState: ctx.sessionState.lastPrompt ?? '',
            priorSummaryCount: priorSummary?.priorSummaryCount ?? 0,
            currentTokenCount: currentTokenCount ??
                ctx.getUsageSnapshot().tokens?.grossInputTokens ??
                ctx.sessionState.tokenSummary?.grossInputTokens ??
                0,
        });
    }
    catch {
        return null;
    }
}
// ─── Bootstrap recovery ───────────────────────────────────────────────────────
/**
 * Attempts to recover a prior session summary for bootstrapping context.
 * Returns a summary info object, or undefined if none exists.
 */
export function recoverSessionSummaryBootstrap(ctx, projectId) {
    try {
        const summarizer = getSharedSummarizer({ projectId });
        const priorSummary = summarizer.bootstrapFromSummary(projectId);
        if (!priorSummary)
            return undefined;
        return {
            summary: priorSummary.summary,
            summaryTokens: priorSummary.summaryTokens,
            originalTokens: priorSummary.originalTokens,
            savedTokens: priorSummary.originalTokens - priorSummary.summaryTokens,
        };
    }
    catch {
        return undefined;
    }
}
/**
 * Attempts to recover recent project-scoped memory excerpts for bootstrapping.
 * Returns a count + excerpts object, or undefined if no relevant memory found.
 */
export function recoverProjectMemoryBootstrap(ctx, repoId) {
    try {
        const repoScopedMatches = ctx.memory
            .listSnapshots(6, { scope: 'project', repoId })
            .filter((snapshot) => !snapshot.tags.includes('#repo-profile'))
            .map((snapshot) => snapshot.excerpt.trim())
            .filter(Boolean)
            .slice(0, 4);
        if (repoScopedMatches.length === 0)
            return undefined;
        return { count: repoScopedMatches.length, excerpts: repoScopedMatches };
    }
    catch {
        return undefined;
    }
}
// ─── Current dirty files ──────────────────────────────────────────────────────
/**
 * Returns a list of currently modified (dirty) files in the working tree,
 * resolved to absolute paths. Best-effort — returns [] on any error.
 */
export async function summarizeCurrentDirtyFiles(ctx, limit = 8) {
    try {
        const { stdout } = await execAsync('git status --porcelain=v1 --untracked-files=no', {
            cwd: ctx.repoRoot,
            encoding: 'utf8',
            timeout: 2_000,
        });
        const output = stdout.trim();
        if (!output)
            return [];
        return output
            .split('\n')
            .map((line) => line.slice(3).trim())
            .filter(Boolean)
            .slice(0, limit)
            .map((file) => path.resolve(ctx.repoRoot, file));
    }
    catch {
        return [];
    }
}
// ─── File-prior state ─────────────────────────────────────────────────────────
/** Absolute path to the persisted file-prior state JSON. */
export function filePriorStatePath(ctx) {
    return path.join(ctx.stateRoot, 'file-priors.json');
}
/** Loads the persisted file-prior state from disk, returning an empty baseline on any failure. */
export async function loadFilePriorState(ctx) {
    const target = filePriorStatePath(ctx);
    if (!fs.existsSync(target)) {
        return { version: 1, updatedAt: 0, history: [], priors: [] };
    }
    try {
        const parsed = JSON.parse(await fs.promises.readFile(target, 'utf8'));
        return {
            version: 1,
            updatedAt: Number(parsed.updatedAt ?? 0),
            history: Array.isArray(parsed.history) ? parsed.history : [],
            priors: Array.isArray(parsed.priors) ? parsed.priors : [],
        };
    }
    catch {
        return { version: 1, updatedAt: 0, history: [], priors: [] };
    }
}
/** Persists the file-prior state to disk. Logs on error but does not throw. */
export async function saveFilePriorState(ctx, state) {
    try {
        const target = filePriorStatePath(ctx);
        fs.mkdirSync(path.dirname(target), { recursive: true });
        await fs.promises.writeFile(target, JSON.stringify(state, null, 2), 'utf8');
    }
    catch (error) {
        console.error('[RunManagement] Failed to persist file priors:', error?.message);
    }
}
/** Returns a Map from resolved absolute file path to FilePrior. */
export async function getFilePriorMap(ctx) {
    return new Map((await loadFilePriorState(ctx)).priors.map((prior) => [path.resolve(prior.filePath), prior]));
}
/** Updates file-prior history based on the outcomes of a completed execution run. */
export async function updateFilePriorsFromRun(ctx, run) {
    const modifiedFiles = dedupeStrings(run.workerResults.flatMap((worker) => worker.modifiedFiles ?? []))
        .map((filePath) => ctx.resolveRepoScopedPath(filePath) ?? filePath)
        .filter((filePath) => Boolean(filePath) && fs.existsSync(filePath));
    if (modifiedFiles.length === 0)
        return;
    const verified = run.verificationResults.length > 0
        ? run.verificationResults.some((result) => result.passed)
        : run.workerResults.some((worker) => worker.verified);
    const wasSuccessful = run.state === 'merged';
    const wasRetry = ctx.sessionState.repeatedFailures > 0 || run.continuationChildren.length > 0;
    const state = await loadFilePriorState(ctx);
    const additions = modifiedFiles.map((filePath) => ({
        filePath,
        runId: run.runId,
        wasSuccessful,
        wasVerified: verified,
        coChangedWith: modifiedFiles.filter((candidate) => candidate !== filePath),
        wasRetry,
    }));
    const history = [...state.history, ...additions].slice(-512);
    const priors = computeFilePriors(history);
    await saveFilePriorState(ctx, {
        version: 1,
        updatedAt: Date.now(),
        history,
        priors,
    });
}
// ─── Artifact outcome history ─────────────────────────────────────────────────
/**
 * Normalizes a raw (possibly legacy or partial) artifact outcome history value
 * into the canonical `ArtifactOutcomeHistoryMap` shape.
 */
export function normalizeArtifactOutcomeHistory(value) {
    if (!value || typeof value !== 'object')
        return {};
    const next = {};
    for (const [key, entry] of Object.entries(value)) {
        if (typeof entry === 'number') {
            next[key] = {
                score: Number(entry.toFixed(2)),
                helpful: entry > 0.7 ? 1 : 0,
                neutral: entry > 0.3 && entry <= 0.7 ? 1 : 0,
                redundant: entry > 0 && entry <= 0.3 ? 1 : 0,
                harmful: entry < 0 ? 1 : 0,
                updatedAt: Date.now(),
                lastVerificationResult: entry > 0 ? 'verified' : entry < 0 ? 'failed' : 'none',
            };
            continue;
        }
        if (!entry || typeof entry !== 'object')
            continue;
        const record = entry;
        next[key] = {
            score: Number(record.score ?? 0),
            helpful: Number(record.helpful ?? 0),
            neutral: Number(record.neutral ?? 0),
            redundant: Number(record.redundant ?? 0),
            harmful: Number(record.harmful ?? 0),
            updatedAt: Number(record.updatedAt ?? Date.now()),
            lastVerificationResult: record.lastVerificationResult ?? 'none',
        };
    }
    return next;
}
// ─── Session state ────────────────────────────────────────────────────────────
/** Absolute path to the JSON file for the current runtime's autonomy session. */
export function sessionStatePath(ctx) {
    return path.join(ctx.sessionsDir, `${ctx.getRuntimeId()}.json`);
}
/**
 * Loads session autonomy state from disk for the current runtime.
 * Returns a fresh default state (keyed to the current session/runtime) if the
 * file does not exist or is corrupt.
 */
export async function loadSessionState(ctx) {
    const target = sessionStatePath(ctx);
    if (fs.existsSync(target)) {
        try {
            const parsed = JSON.parse(await fs.promises.readFile(target, 'utf8'));
            return {
                ...parsed,
                artifactOutcomeHistory: normalizeArtifactOutcomeHistory(parsed.artifactOutcomeHistory),
            };
        }
        catch {
            const corruptPath = target.replace(/\.json$/, `.corrupt-${Date.now()}.json`);
            try {
                fs.copyFileSync(target, corruptPath);
            }
            catch {
                // ignore backup failures
            }
            console.error('[RunManagement] Corrupt session file backed up to:', corruptPath);
        }
    }
    const sessionId = ctx.getSessionId() ?? `autonomy-${randomUUID().slice(0, 8)}`;
    return {
        runtimeId: ctx.getRuntimeId(),
        sessionId,
        startedAt: Date.now(),
        updatedAt: Date.now(),
        lastPrompt: '',
        objectiveHistory: [],
        phases: [],
        mode: 'single-pass',
        repeatedFailures: 0,
        continuationDepth: 0,
        intent: { taskType: 'feature', riskClass: 'medium', complexity: 1 },
        selectedSpecialists: [],
        selectedSkills: [],
        selectedWorkflows: [],
        selectedHooks: [],
        selectedAutomations: [],
        lastMemoryMatches: [],
        artifactOutcomeHistory: {},
    };
}
/**
 * Persists the current session state from `ctx.sessionState` to disk.
 * Logs on error but does not throw.
 */
export async function persistSessionState(ctx) {
    try {
        await fs.promises.writeFile(sessionStatePath(ctx), JSON.stringify(ctx.sessionState, null, 2), 'utf8');
    }
    catch (err) {
        console.error('[RunManagement] Failed to persist session state:', err?.message);
    }
}
/** Removes autonomy-session JSON files older than 14 days. Best-effort. */
export async function pruneStaleAutonomySessions(ctx) {
    try {
        const TTL_MS = 14 * 24 * 60 * 60 * 1000;
        const now = Date.now();
        for (const entry of fs.readdirSync(ctx.sessionsDir)) {
            if (!entry.endsWith('.json'))
                continue;
            const filePath = path.join(ctx.sessionsDir, entry);
            try {
                const stat = await fs.promises.stat(filePath);
                if (now - stat.mtimeMs > TTL_MS) {
                    fs.unlinkSync(filePath);
                }
            }
            catch { /* best-effort */ }
        }
    }
    catch { /* best-effort */ }
}
// ─── Orchestration plan ───────────────────────────────────────────────────────
/**
 * Generates a human-readable orchestration plan markdown file under
 * `<repoRoot>/.nexus-prime/orchestration_plan.md`.
 *
 * The `workerPlan` and `instructionPacket` parameters use `any` intentionally:
 * their shapes span the planner and catalog layers which are not yet extracted.
 */
export async function generateOrchestrationPlan(ctx, task, intent, 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
workerPlan, 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
instructionPacket) {
    const planPath = path.join(ctx.repoRoot, '.nexus-prime', 'orchestration_plan.md');
    try {
        if (!fs.existsSync(path.dirname(planPath))) {
            fs.mkdirSync(path.dirname(planPath), { recursive: true });
        }
        const content = [
            '# Nexus Prime Orchestration Plan',
            `> Generated at ${new Date().toISOString()}`,
            '',
            '## 1. Intent & Routing',
            `- **Goal**: ${task}`,
            `- **Detected Intent**: \`${intent.taskType}\` (Risk: ${intent.riskClass}, Complexity: ${intent.complexity})`,
            `- **Mathematical Routing**: Applied strict TF-IDF/Vector heuristics weighted for the \`${intent.taskType}\` domain.`,
            '',
            '## 2. Swarm Topology (Parallel Sub-Agents)',
            `- **Total Agents Allocated**: ${workerPlan?.totalWorkers ?? 0}`,
            `- **Execution Mode**: ${workerPlan?.mode || 'default'}`,
            ...(workerPlan?.workers || []).map((w, idx) => `  - **Agent ${idx + 1}**: Uses isolated Git Worktree. Type: \`${w.type}\``),
            '',
            '## 3. POD Network & Resources',
            `- **Selected Crew**: ${instructionPacket.selectedCrew?.name ?? 'Default Engineering Crew'}`,
            `- **Injected Skills**: ${instructionPacket.selectedSkills.map((s) => s.name).join(', ') || 'None'}`,
            '- **Memory Bridges**: Active. Discoveries inside Phantom workers are tagged with `#shared` and synced to Cortex.',
            '',
            '## 4. Verification Check',
            "- Swarm consensus is **enforced**. The MergeOracle requires the Verifier sub-agent to strictly approve the Coder's git worktree diff before applying patches to origin.",
        ].join('\n');
        await fs.promises.writeFile(planPath, content, 'utf-8');
        // Auto-enhance standard Antigravity/Cursor implementation plans if explicitly enabled
        const igPlanPath = path.join(ctx.repoRoot, 'implementation_plan.md');
        const annotateImplementationPlan = process.env.NEXUS_ANNOTATE_IMPL_PLAN === 'true';
        if (!annotateImplementationPlan && fs.existsSync(igPlanPath)) {
            console.warn('[orchestrator] Implicit implementation_plan.md mutation is disabled. ' +
                'Set NEXUS_ANNOTATE_IMPL_PLAN=true to re-enable the deprecated behavior.');
        }
        if (annotateImplementationPlan && fs.existsSync(igPlanPath)) {
            let igPlan = await fs.promises.readFile(igPlanPath, 'utf8');
            if (!igPlan.includes('## Swarm Orchestration Details')) {
                igPlan +=
                    `\n\n## Swarm Orchestration Details\n*Nexus Prime has intercepted this plan and is enforcing multi-agent swarming.* See \`.nexus-prime/orchestration_plan.md\` for the parallel topology.\n`;
                await fs.promises.writeFile(igPlanPath, igPlan, 'utf8');
            }
        }
    }
    catch (error) {
        console.error('[RunManagement] Failed to write orchestration_plan.md:', error);
    }
}
