import path from 'node:path';
import fsp from 'node:fs/promises';
import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
import { evaluateGitWorktreeCapability } from './git-worktree-capability.js';
import { gitBlocker, gitOutputLine, runGitCommand } from './git-worktree-runner.js';
import { sanitizePathPart } from './git-worktree-root.js';
import { appendParallelRuntimeEvent } from '../agents/parallel-runtime-proof.js';
export async function allocateWorkerWorktree(input) {
    const eventRoot = input.repoRoot || process.cwd();
    const writeProofEvents = await shouldWriteWorktreeAllocationProof(eventRoot, input.missionId);
    if (writeProofEvents)
        await appendParallelRuntimeEvent(eventRoot, input.missionId, {
            event_type: 'worktree_allocation_started',
            slot_id: input.slotId || input.workerId || null,
            generation_index: input.generationIndex || 1,
            session_id: null,
            pid: null,
            backend: 'git-worktree',
            placement: 'unknown',
            worktree_id: input.workerId || null
        }).catch(() => undefined);
    const capability = await evaluateGitWorktreeCapability({
        root: input.repoRoot || process.cwd(),
        missionId: input.missionId,
        requireGitWorktree: true
    });
    const repoRoot = capability.detection.root || path.resolve(input.repoRoot || process.cwd());
    const root = capability.root_resolution?.root || path.join(repoRoot, '.sneakoscope', 'blocked-worktrees');
    const workerId = sanitizePathPart(input.workerId);
    const slotId = sanitizePathPart(input.slotId || workerId);
    const generationIndex = Math.max(1, Math.floor(Number(input.generationIndex || 1)));
    const baseRef = input.baseRef || capability.detection.head || 'HEAD';
    const branchPrefix = sanitizeBranchPart(input.branchPrefix || 'sks');
    const branch = `${branchPrefix}/${sanitizeBranchPart(input.missionId)}/${sanitizeBranchPart(slotId)}-gen-${generationIndex}-${workerId}`;
    const worktreePath = path.join(root, `${slotId}-gen-${generationIndex}-${workerId}`);
    const blockers = [...capability.blockers];
    let baseHead = capability.detection.head;
    if (capability.ok) {
        await ensureDir(root);
        let add = null;
        for (let attempt = 1; attempt <= 3; attempt += 1) {
            if (attempt > 1) {
                await sleep(250 * attempt);
                await runGitCommand(repoRoot, ['worktree', 'prune'], { timeoutMs: 30000 }).catch(() => null);
                await fsp.rm(worktreePath, { recursive: true, force: true }).catch(() => null);
            }
            const existingBranch = await runGitCommand(repoRoot, ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
            const args = existingBranch.ok
                ? ['worktree', 'add', worktreePath, branch]
                : ['worktree', 'add', '-b', branch, worktreePath, baseRef];
            add = await runGitCommand(repoRoot, args, { timeoutMs: 120000 });
            if (add.ok)
                break;
        }
        if (!add?.ok)
            blockers.push(gitBlocker('git_worktree_add_failed', add));
        if (add?.ok) {
            const head = await runGitCommand(worktreePath, ['rev-parse', 'HEAD']);
            baseHead = head.ok ? gitOutputLine(head) : baseHead;
        }
    }
    const allocation = {
        schema: 'sks.git-worktree-allocation.v1',
        ok: blockers.length === 0,
        created_at: nowIso(),
        mission_id: input.missionId,
        worker_id: workerId,
        slot_id: slotId,
        generation_index: generationIndex,
        repo_root: repoRoot,
        main_repo_root: repoRoot,
        worktree_path: worktreePath,
        branch,
        base_ref: baseRef,
        base_head: baseHead,
        manifest_path: path.join(root, 'git-worktree-manifest.json'),
        capability,
        blockers: [...new Set(blockers)]
    };
    await appendWorktreeManifest(allocation);
    if (writeProofEvents)
        await appendParallelRuntimeEvent(eventRoot, input.missionId, {
            event_type: 'worktree_allocation_completed',
            slot_id: allocation.slot_id,
            generation_index: allocation.generation_index,
            session_id: null,
            pid: null,
            backend: 'git-worktree',
            placement: 'unknown',
            worktree_id: `${allocation.slot_id}-gen-${allocation.generation_index}`,
            meta: {
                ok: allocation.ok,
                worktree_path: allocation.worktree_path,
                branch: allocation.branch,
                blockers: allocation.blockers
            }
        }).catch(() => undefined);
    return allocation;
}
export async function allocateWorkerWorktreesBatch(input) {
    const maxParallel = Math.max(1, Math.floor(Number(input.maxParallel || 1)));
    const queue = uniqueWorktreeAllocationInputs(input.workers);
    const allocations = [];
    const workers = Array.from({ length: Math.min(maxParallel, queue.length) }, async () => {
        while (queue.length) {
            const next = queue.shift();
            if (!next)
                continue;
            allocations.push(await allocateWorkerWorktree({
                repoRoot: input.root,
                missionId: input.missionId,
                workerId: next.workerId,
                ...(next.slotId === undefined ? {} : { slotId: next.slotId }),
                ...(next.generationIndex === undefined ? {} : { generationIndex: next.generationIndex }),
                ...(next.baseRef === undefined ? {} : { baseRef: next.baseRef }),
                ...(next.branchPrefix === undefined ? {} : { branchPrefix: next.branchPrefix })
            }));
        }
    });
    await Promise.all(workers);
    return allocations;
}
function uniqueWorktreeAllocationInputs(workers = []) {
    const seen = new Set();
    const unique = [];
    for (const worker of workers) {
        const workerId = sanitizePathPart(worker.workerId || 'worker');
        const slotId = sanitizePathPart(worker.slotId || workerId);
        const generationIndex = Math.max(1, Math.floor(Number(worker.generationIndex || 1)));
        const key = `${workerId}:${slotId}:${generationIndex}`;
        if (seen.has(key))
            continue;
        seen.add(key);
        unique.push({ ...worker, workerId, slotId, generationIndex });
    }
    return unique;
}
async function appendWorktreeManifest(allocation) {
    const current = await readJson(allocation.manifest_path, null).catch(() => null);
    const allocations = Array.isArray(current?.allocations) ? current.allocations : [];
    const key = (row) => `${row.mission_id}:${row.slot_id}:${row.generation_index}:${row.worker_id}`;
    const nextAllocations = [
        ...allocations.filter((row) => key(row) !== key(allocation)),
        allocation
    ];
    const manifest = {
        schema: 'sks.git-worktree-manifest.v1',
        updated_at: nowIso(),
        mission_id: allocation.mission_id,
        repo_root: allocation.repo_root,
        root: path.dirname(allocation.worktree_path),
        allocations: nextAllocations
    };
    await writeJsonAtomic(allocation.manifest_path, manifest);
}
async function shouldWriteWorktreeAllocationProof(root, missionId) {
    if (process.env.SKS_GIT_WORKTREE_ALLOCATION_PROOF === '0')
        return false;
    try {
        const stat = await fsp.stat(path.join(root, '.sneakoscope', 'missions', missionId, 'agents'));
        return stat.isDirectory();
    }
    catch {
        return false;
    }
}
function sanitizeBranchPart(value) {
    return sanitizePathPart(value).replace(/\./g, '-').slice(0, 48) || 'item';
}
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
//# sourceMappingURL=git-worktree-manager.js.map