// engine/spec-decomposer/ownership-resolver.ts — Resolve file ownership conflicts (SPEC-157)
// Ensures no two agent tasks claim the same file. When conflicts exist, the task
// with the highest file count wins ownership; other tasks receive a dependency instead.
// ---------------------------------------------------------------------------
// Conflict detection
// ---------------------------------------------------------------------------
/**
 * Find files claimed by more than one mapped unit.
 * Returns a map of filePath → unitIds that claim it.
 */
function detectConflicts(units) {
    const fileToUnits = new Map();
    for (const unit of units) {
        for (const file of unit.files) {
            const existing = fileToUnits.get(file) ?? [];
            existing.push(unit.id);
            fileToUnits.set(file, existing);
        }
    }
    // Keep only files with >1 claimant
    const conflicts = new Map();
    for (const [file, unitIds] of fileToUnits) {
        if (unitIds.length > 1) {
            conflicts.set(file, unitIds);
        }
    }
    return conflicts;
}
// ---------------------------------------------------------------------------
// Ownership resolution
// ---------------------------------------------------------------------------
/**
 * Resolve ownership conflicts by awarding each contested file to the unit with
 * the most files (primary owner) and removing it from the others.
 * Returns a clean map of unitId → Set<filePath>.
 */
function resolveOwnership(units, conflicts) {
    // Start: each unit owns all its files
    const ownership = new Map(units.map((u) => [u.id, new Set(u.files)]));
    for (const [file, claimantIds] of conflicts) {
        // Primary owner = unit with most files (acts as heaviest dependency)
        const sorted = [...claimantIds].sort((a, b) => {
            const aSize = ownership.get(a)?.size ?? 0;
            const bSize = ownership.get(b)?.size ?? 0;
            return bSize - aSize;
        });
        const winner = sorted[0];
        if (!winner) {
            continue;
        }
        // Remove file from all losers
        for (const loserId of sorted.slice(1)) {
            ownership.get(loserId)?.delete(file);
        }
    }
    return ownership;
}
// ---------------------------------------------------------------------------
// Execution phase planning
// ---------------------------------------------------------------------------
/**
 * Assign tasks to execution phases.
 * Tasks with no dependencies → phase 1 (parallel).
 * Tasks with dependencies → subsequent phases.
 */
function buildExecutionPhases(tasks) {
    if (tasks.length === 0) {
        return [];
    }
    const phases = [];
    const placed = new Set();
    let remaining = [...tasks];
    while (remaining.length > 0) {
        const phaseNumber = phases.length + 1;
        // Tasks whose dependencies are all placed
        const ready = remaining.filter((t) => t.dependencies.every((dep) => placed.has(dep)));
        if (ready.length === 0) {
            // Circular dependency safety: place everything left in one phase
            const ids = remaining.map((t) => t.id);
            phases.push({
                phase: phaseNumber,
                taskIds: ids,
                reason: 'Remaining tasks (possible circular dependency detected)',
            });
            break;
        }
        const reason = ready.length === 1
            ? `Single task with all dependencies satisfied`
            : `${String(ready.length)} tasks with no file conflicts — run in parallel`;
        phases.push({
            phase: phaseNumber,
            taskIds: ready.map((t) => t.id),
            reason,
        });
        for (const t of ready) {
            placed.add(t.id);
        }
        remaining = remaining.filter((t) => !placed.has(t.id));
    }
    return phases;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Resolve file ownership and produce final AgentTask list, FileOwnership records,
 * detected conflicts, and execution phases.
 */
export function resolveFileOwnership(units) {
    const rawConflicts = detectConflicts(units);
    const ownershipMap = resolveOwnership(units, rawConflicts);
    // Build dependency edges: if unit B lost a file to unit A, B depends on A
    const dependencyEdges = new Map();
    for (const unit of units) {
        dependencyEdges.set(unit.id, new Set());
    }
    const resolvedConflicts = [];
    for (const [file, claimantIds] of rawConflicts) {
        // Winner is the one that still owns the file after resolution
        const winner = claimantIds.find((id) => ownershipMap.get(id)?.has(file));
        if (!winner) {
            continue;
        }
        const losers = claimantIds.filter((id) => id !== winner);
        for (const loser of losers) {
            dependencyEdges.get(loser)?.add(winner);
        }
        resolvedConflicts.push({
            filePath: file,
            conflictingTaskIds: claimantIds,
            resolution: `Ownership awarded to ${winner}; ${losers.join(', ')} will depend on it.`,
        });
    }
    // Build AgentTask array
    const tasks = units.map((unit) => {
        const ownedFiles = [...(ownershipMap.get(unit.id) ?? [])].sort();
        const dependencies = [...(dependencyEdges.get(unit.id) ?? [])].sort();
        // A task can run in parallel if it has no dependencies
        const canRunParallel = dependencies.length === 0;
        return {
            id: unit.id,
            title: unit.title,
            description: buildDescription(unit, ownedFiles),
            criteria: unit.criteria,
            ownedFiles,
            dependencies,
            recommendedModel: unit.recommendedModel,
            estimatedHours: unit.estimatedHours,
            canRunParallel,
        };
    });
    // Build FileOwnership records
    const fileOwnership = tasks.flatMap((task) => task.ownedFiles.map((filePath) => ({ filePath, taskId: task.id })));
    const executionPhases = buildExecutionPhases(tasks);
    return { tasks, fileOwnership, conflicts: resolvedConflicts, executionPhases };
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function buildDescription(unit, ownedFiles) {
    const criteriaLines = unit.criteria.length > 0
        ? `\nAcceptance criteria:\n${unit.criteria.map((c) => `- ${c}`).join('\n')}`
        : '';
    const fileLines = ownedFiles.length > 0
        ? `\nOwned files (exclusive):\n${ownedFiles.map((f) => `- ${f}`).join('\n')}`
        : '';
    return `Implement ${unit.title}.${criteriaLines}${fileLines}`;
}
//# sourceMappingURL=ownership-resolver.js.map