import { randomUUID } from 'crypto';
import { execSync } from 'child_process';
import { nexusEventBus } from '../../engines/event-bus.js';
import { assertGovernanceBoundary } from '../../engines/governance-boundary.js';
import { finalizeConvergenceRun, getConvergenceRun, getWorkItemsForWorklist, insertConvergenceRun, updateWorkItemStatus } from '../worklist/crud.js';
let convergencePaused = false;
const BRANCH_VERIFY_CACHE_TTL_MS = 60_000;
const branchVerifyCache = new Map();
export function setConvergencePaused(nextValue) {
    convergencePaused = nextValue;
}
function attemptBatchMerge(root, items) {
    // Governance boundary: architects may verify branches (read-only) but not merge them.
    // Actual merges are delegated to Phantom's MutationEngine via the merge-oracle.
    assertGovernanceBoundary('architects', 'git.rev-parse');
    for (const item of items) {
        if (!item.branch)
            throw new Error(`WorkItem ${item.id} missing branch`);
        const cacheKey = `${root}:${item.branch}`;
        const cached = branchVerifyCache.get(cacheKey);
        if (cached && Date.now() - cached.checkedAt < BRANCH_VERIFY_CACHE_TTL_MS) {
            if (!cached.verified) {
                throw new Error(`WorkItem ${item.id} branch ${item.branch} failed verification`);
            }
            continue;
        }
        try {
            execSync(`git rev-parse --verify ${item.branch}`, { cwd: root, stdio: 'ignore' });
            branchVerifyCache.set(cacheKey, { verified: true, checkedAt: Date.now() });
        }
        catch {
            branchVerifyCache.set(cacheKey, { verified: false, checkedAt: Date.now() });
            throw new Error(`WorkItem ${item.id} branch ${item.branch} failed verification`);
        }
    }
}
async function bisectMerge(root, items) {
    try {
        attemptBatchMerge(root, items);
    }
    catch (error) {
        if (items.length === 1)
            throw error;
        const mid = Math.floor(items.length / 2);
        await bisectMerge(root, items.slice(0, mid));
        await bisectMerge(root, items.slice(mid));
    }
}
export async function runConvergenceQueue(db, repoRoot, worklistId) {
    if (convergencePaused) {
        return insertConvergenceRun(db, {
            id: randomUUID(),
            worklistId,
            strategy: 'bisecting',
            workItemIds: [],
            status: 'deferred',
        });
    }
    const ready = getWorkItemsForWorklist(db, worklistId)
        .filter((item) => item.status === 'done' && !item.mergedAt && item.branch);
    const run = insertConvergenceRun(db, {
        id: randomUUID(),
        worklistId,
        strategy: 'bisecting',
        workItemIds: ready.map((item) => item.id),
        status: 'running',
    });
    nexusEventBus.emit('architects.convergence.started', { runId: run.id, worklistId, itemCount: ready.length });
    try {
        if (ready.length > 0) {
            await bisectMerge(repoRoot, ready);
            ready.forEach((item) => updateWorkItemStatus(db, item.id, 'done', item.branch));
        }
        finalizeConvergenceRun(db, run.id, 'merged');
        nexusEventBus.emit('architects.convergence.merged', { runId: run.id, worklistId });
    }
    catch (error) {
        finalizeConvergenceRun(db, run.id, 'failed');
        nexusEventBus.emit('architects.convergence.failed', { runId: run.id, worklistId, error: String(error?.message ?? error) });
    }
    return getConvergenceRun(db, run.id);
}
