import { createNarutoGeneration, completeNarutoGeneration } from './naruto-generation-scheduler.js';
export async function runNarutoRealActivePool(input) {
    const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
    const visibleCap = Math.max(0, input.governor.safe_zellij_visible_panes);
    const pending = [...input.graph.work_items];
    const active = new Map();
    const completed = new Map();
    const byId = new Map(input.graph.work_items.map((item) => [item.id, item]));
    const timeline = [];
    const lifecycle = [];
    const refillLatencies = [];
    let tick = 0;
    let refillEvents = 0;
    let maxObserved = 0;
    let visibleRunning = 0;
    while (pending.length || active.size) {
        const beforeLaunch = Date.now();
        let launched = 0;
        for (;;) {
            if (active.size >= safeActiveWorkers)
                break;
            const item = popRunnable(pending, new Set(completed.keys()), activeToGenerationMap(active), byId);
            if (!item)
                break;
            const placement = visibleRunning < visibleCap
                ? { placement: 'zellij-pane', visible_index: visibleRunning + 1, reason: 'within_visible_cap' }
                : { placement: 'headless', visible_index: null, reason: `visible_pane_cap:${visibleCap}` };
            if (placement.placement === 'zellij-pane')
                visibleRunning += 1;
            const handle = await input.spawnWorker(item, placement);
            active.set(handle.id, handle);
            lifecycle.push({ work_item_id: item.id, placement: placement.placement, pid: handle.pid || null, worker_artifact_dir: handle.worker_artifact_dir || null, started_at: handle.started_at, completed_at: null, ok: null });
            launched += 1;
            await input.updateDashboard({ event_type: 'worker_spawned', work_item_id: item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement });
        }
        if (launched) {
            refillEvents += launched;
            refillLatencies.push(Date.now() - beforeLaunch);
            await input.updateDashboard({ event_type: 'refill', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
        }
        maxObserved = Math.max(maxObserved, active.size);
        timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
        const done = await nextCollectableWorkers(active);
        if (!done.length)
            break;
        for (const handle of done) {
            active.delete(handle.id);
            if (handle.placement.placement === 'zellij-pane')
                visibleRunning = Math.max(0, visibleRunning - 1);
            const result = await input.collectWorker(handle);
            completed.set(result.item.id, result);
            const row = lifecycle.find((entry) => entry.work_item_id === result.item.id && entry.completed_at == null);
            if (row) {
                row.completed_at = result.completed_at;
                row.ok = result.ok;
                row.pid = result.pid || row.pid || null;
                row.worker_artifact_dir = result.worker_artifact_dir || row.worker_artifact_dir || null;
            }
            await input.updateDashboard({ event_type: 'worker_completed', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
            if (result.item.verification_required) {
                await input.enqueueVerification(result);
                await input.updateDashboard({ event_type: 'verification_enqueued', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
            }
        }
        tick += 1;
        if (tick > input.graph.work_items.length * 4 + 20)
            break;
    }
    await input.updateDashboard({ event_type: 'pool_drained', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
    const failedCount = [...completed.values()].filter((result) => !result.ok).length;
    const activeSamples = timeline.map((row) => row.active);
    const averageActiveWorkers = activeSamples.length
        ? activeSamples.reduce((sum, value) => sum + value, 0) / activeSamples.length
        : 0;
    const saturatedSamples = timeline
        .filter((row) => row.pending + row.active >= safeActiveWorkers)
        .map((row) => row.active);
    const averageSaturatedActiveWorkers = saturatedSamples.length
        ? saturatedSamples.reduce((sum, value) => sum + value, 0) / saturatedSamples.length
        : averageActiveWorkers;
    const utilizationDenominator = Math.max(1, safeActiveWorkers);
    const activePoolUtilization = Math.min(1, averageSaturatedActiveWorkers / utilizationDenominator);
    const enoughWorkForUtilization = input.graph.total_work_items >= safeActiveWorkers * 2;
    const blockers = [
        ...(pending.length ? ['naruto_real_active_pool_pending_not_drained'] : []),
        ...(active.size ? ['naruto_real_active_pool_active_not_drained'] : []),
        ...(maxObserved > safeActiveWorkers ? ['naruto_real_active_pool_exceeded_safe_workers'] : []),
        ...(enoughWorkForUtilization && maxObserved < Math.ceil(safeActiveWorkers * 0.8) ? ['naruto_real_active_pool_underutilized'] : []),
        ...(enoughWorkForUtilization && activePoolUtilization < 0.8 ? ['naruto_real_active_pool_low_sustained_utilization'] : []),
        ...[...completed.values()].flatMap((result) => result.blockers || [])
    ];
    return {
        schema: 'sks.naruto-active-pool.v1',
        ok: blockers.length === 0,
        runtime_mode: 'real-worker-lifecycle',
        active_cap: safeActiveWorkers,
        safe_active_workers: safeActiveWorkers,
        total_work_items: input.graph.total_work_items,
        completed_count: completed.size,
        failed_count: failedCount,
        refill_events: refillEvents,
        max_observed_active_workers: maxObserved,
        duplicate_execution_count: 0,
        conflict_items_enqueued: 0,
        max_observed_write_lease_conflicts: 0,
        average_active_workers: Number(averageSaturatedActiveWorkers.toFixed(4)),
        active_pool_utilization: Number(activePoolUtilization.toFixed(4)),
        visible_workers: lifecycle.filter((row) => row.placement === 'zellij-pane').length,
        headless_workers: lifecycle.filter((row) => row.placement === 'headless').length,
        refill_latency_ms_p95: percentile(refillLatencies, 0.95),
        worker_lifecycle: lifecycle,
        timeline,
        blockers
    };
}
async function nextCollectableWorkers(active) {
    const handles = [...active.values()];
    const handlesWithExit = handles.filter((handle) => typeof handle.exit?.then === 'function');
    if (!handlesWithExit.length)
        return handles.slice(0, Math.max(1, Math.ceil(active.size / 2)));
    const completed = await Promise.race(handlesWithExit.map(async (handle) => {
        await handle.exit.catch(() => undefined);
        return handle;
    }));
    return completed ? [completed] : [];
}
export async function runNarutoActivePool(input) {
    const base = simulateNarutoActivePool(input);
    const allocations = [];
    for (const item of input.graph.work_items) {
        if (!item.write_allowed)
            continue;
        const mode = item.worktree?.mode || input.graph.worktree_policy.mode;
        if (mode !== 'git-worktree') {
            allocations.push({ work_item_id: item.id, mode, allocation_status: 'skipped', blockers: [] });
            continue;
        }
        if (!input.allocateWorktree) {
            allocations.push({ work_item_id: item.id, mode, allocation_status: 'planned', blockers: [] });
            continue;
        }
        const allocated = await input.allocateWorktree(item);
        allocations.push({
            work_item_id: item.id,
            mode,
            allocation_status: allocated.ok ? 'allocated' : 'planned',
            ...(allocated.worktree_path ? { worktree_path: allocated.worktree_path } : {}),
            ...(allocated.branch ? { branch: allocated.branch } : {}),
            blockers: allocated.blockers || []
        });
    }
    const allocationBlockers = allocations.flatMap((row) => row.blockers);
    return {
        ...base,
        ok: base.ok && allocationBlockers.length === 0,
        worktree_mode: input.graph.worktree_policy.mode,
        worktree_allocation_required_count: allocations.filter((row) => row.mode === 'git-worktree').length,
        worktree_allocations: allocations,
        blockers: [...base.blockers, ...allocationBlockers]
    };
}
function activeToGenerationMap(active) {
    const out = new Map();
    let index = 1;
    for (const handle of active.values()) {
        out.set(handle.id, createNarutoGeneration(handle.item, index, 0));
        index += 1;
    }
    return out;
}
function percentile(values, quantile) {
    if (!values.length)
        return 0;
    const sorted = [...values].sort((a, b) => a - b);
    const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * quantile) - 1));
    return sorted[index] || 0;
}
export function simulateNarutoActivePool(input) {
    const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
    const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
    const failIds = new Set((input.failWorkItemIds || []).map(String));
    const pending = [...input.graph.work_items];
    const active = new Map();
    const completed = new Set();
    const failed = new Set();
    const executed = new Map();
    const byId = new Map(input.graph.work_items.map((item) => [item.id, item]));
    const timeline = [];
    let generationIndex = 1;
    let tick = 0;
    let refillEvents = 0;
    let maxObserved = 0;
    let maxObservedWriteLeaseConflicts = 0;
    let conflictItemsEnqueued = 0;
    while (pending.length || active.size) {
        let launched = 0;
        for (;;) {
            if (active.size >= safeActiveWorkers)
                break;
            const next = popRunnable(pending, completed, active, byId);
            if (!next)
                break;
            const generation = createNarutoGeneration(next, generationIndex, tick);
            generationIndex += 1;
            active.set(generation.generation_id, generation);
            executed.set(next.id, (executed.get(next.id) || 0) + 1);
            launched += 1;
        }
        if (launched)
            refillEvents += launched;
        maxObserved = Math.max(maxObserved, active.size);
        maxObservedWriteLeaseConflicts = Math.max(maxObservedWriteLeaseConflicts, countActiveWriteLeaseConflicts(active, byId));
        timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
        const done = [...active.values()].slice(0, Math.max(1, Math.ceil(active.size / 2)));
        if (!done.length && pending.length)
            break;
        for (const generation of done) {
            active.delete(generation.generation_id);
            const shouldFail = failIds.has(generation.work_item_id) && (executed.get(generation.work_item_id) || 0) <= retryLimit;
            completeNarutoGeneration(generation, tick + 1, shouldFail);
            if (shouldFail) {
                failed.add(generation.work_item_id);
                conflictItemsEnqueued += 1;
                const followup = conflictResolutionFollowup(generation.work_item_id, input.graph.work_items.length + conflictItemsEnqueued);
                pending.push(followup);
                byId.set(followup.id, followup);
            }
            else {
                completed.add(generation.work_item_id);
            }
        }
        tick += 1;
        if (tick > input.graph.work_items.length * 4 + 20)
            break;
    }
    const duplicateExecutionCount = [...executed.values()].filter((count) => count > 1).length;
    const blockers = [
        ...(pending.length ? ['naruto_active_pool_pending_not_drained'] : []),
        ...(active.size ? ['naruto_active_pool_active_not_drained'] : []),
        ...(maxObserved > safeActiveWorkers ? ['naruto_active_pool_exceeded_safe_workers'] : []),
        ...(maxObservedWriteLeaseConflicts > 0 ? ['naruto_active_pool_overlapping_write_leases'] : []),
        ...(duplicateExecutionCount > conflictItemsEnqueued ? ['naruto_active_pool_duplicate_execution_without_retry'] : [])
    ];
    return {
        schema: 'sks.naruto-active-pool.v1',
        ok: blockers.length === 0,
        safe_active_workers: safeActiveWorkers,
        total_work_items: input.graph.total_work_items,
        completed_count: completed.size,
        failed_count: failed.size,
        refill_events: refillEvents,
        max_observed_active_workers: maxObserved,
        duplicate_execution_count: duplicateExecutionCount,
        conflict_items_enqueued: conflictItemsEnqueued,
        max_observed_write_lease_conflicts: maxObservedWriteLeaseConflicts,
        timeline,
        blockers
    };
}
function popRunnable(pending, completed, active, byId) {
    const activeWorkIds = new Set([...active.values()].map((item) => item.work_item_id));
    for (let index = 0; index < pending.length; index += 1) {
        const item = pending[index];
        if (!item)
            continue;
        if (activeWorkIds.has(item.id))
            continue;
        if (!item.dependencies.every((dep) => completed.has(dep)))
            continue;
        const writeConflict = [...active.values()].some((generation) => {
            const activeItem = byId.get(generation.work_item_id);
            return activeItem?.write_paths.some((file) => item.write_paths.includes(file));
        });
        if (writeConflict)
            continue;
        pending.splice(index, 1);
        return item;
    }
    return null;
}
function countActiveWriteLeaseConflicts(active, byId) {
    const counts = new Map();
    for (const generation of active.values()) {
        const item = byId.get(generation.work_item_id);
        for (const file of item?.write_paths || [])
            counts.set(file, (counts.get(file) || 0) + 1);
    }
    return [...counts.values()].filter((count) => count > 1).reduce((sum, count) => sum + count - 1, 0);
}
function conflictResolutionFollowup(failedId, index) {
    const id = `NW-CONFLICT-${String(index).padStart(4, '0')}`;
    return {
        id,
        kind: 'conflict_resolution',
        title: `Resolve failed work item ${failedId}`,
        target_paths: [],
        readonly_paths: [],
        write_paths: [`.sneakoscope/naruto/conflicts/${id}.json`],
        required_role: 'conflict_resolver',
        write_allowed: true,
        verification_required: true,
        dependencies: [],
        can_run_in_parallel_with: [],
        conflicts_with: [],
        estimated_cost: { tokens: 4000, latency_ms: 45000, cpu_weight: 1, memory_mb: 256, gpu_weight: 0 },
        lease_requirements: [{ path: `.sneakoscope/naruto/conflicts/${id}.json`, kind: 'write' }],
        acceptance: { requires_patch_envelope: true, requires_verification: true, requires_gpt_final: true }
    };
}
//# sourceMappingURL=naruto-active-pool.js.map