// tools/orchestrate-distribution.ts — Distribution, pre-check, decompose logic
// Extracted from orchestrate.ts to keep each module under line budget.
import { readdir, readFile } from 'node:fs/promises';
// ---------------------------------------------------------------------------
// Action: distribute
// ---------------------------------------------------------------------------
const PRIORITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
function specPriority(spec) {
    if (spec.risk === 'critical') {
        return 'critical';
    }
    if (spec.risk === 'high' || spec.scope === 'architectural') {
        return 'high';
    }
    if (spec.scope === 'trivial') {
        return 'low';
    }
    return 'medium';
}
export function buildDistribution(activeSessions, allSpecs) {
    const specMap = new Map(allSpecs.map((s) => [s.id, s]));
    const assignableSpecs = allSpecs.filter((s) => s.status === 'approved' || s.status === 'draft' || s.status === 'implementing');
    const alreadyAssigned = new Set(activeSessions.flatMap((s) => s.activeSpecs));
    const assignments = assignableSpecs.map((spec) => {
        const blockedByUnfinished = spec.dependencies.filter((depId) => {
            const dep = specMap.get(depId);
            return dep && dep.status !== 'done';
        });
        const isBlocked = blockedByUnfinished.length > 0;
        const isAssigned = alreadyAssigned.has(spec.id);
        const assignedSession = activeSessions.find((s) => s.activeSpecs.includes(spec.id));
        return {
            specId: spec.id,
            assignedTo: assignedSession?.sessionId,
            status: isBlocked ? 'blocked' : isAssigned ? 'in-progress' : 'available',
            blockedBy: isBlocked ? blockedByUnfinished : undefined,
            estimatedHours: spec.estimation.devHours,
            priority: specPriority(spec),
        };
    });
    assignments.sort((a, b) => {
        if (a.status === 'blocked' && b.status !== 'blocked') {
            return 1;
        }
        if (a.status !== 'blocked' && b.status === 'blocked') {
            return -1;
        }
        /* v8 ignore next -- ?? 2 fallback: all priority values are always in PRIORITY_ORDER */
        return (PRIORITY_ORDER[a.priority] ?? 2) - (PRIORITY_ORDER[b.priority] ?? 2);
    });
    // Detect dependency conflicts between agents
    const conflicts = [];
    for (const spec of assignableSpecs) {
        const workingSession = activeSessions.find((s) => s.activeSpecs.includes(spec.id));
        if (!workingSession) {
            continue;
        }
        for (const depId of spec.dependencies) {
            const depSession = activeSessions.find((s) => s.activeSpecs.includes(depId));
            if (!depSession || depSession.sessionId === workingSession.sessionId) {
                continue;
            }
            const alreadyReported = conflicts.some((c) => c.agents.includes(workingSession.sessionId) &&
                c.agents.includes(depSession.sessionId) &&
                c.resource === spec.id);
            if (!alreadyReported) {
                conflicts.push({
                    type: 'dependency-conflict',
                    agents: [workingSession.sessionId, depSession.sessionId],
                    resource: spec.id,
                    resolution: `'${spec.id}' depends on '${depId}'. Coordinate between '${workingSession.sessionId}' and '${depSession.sessionId}'.`,
                    severity: 'warning',
                });
            }
        }
    }
    return { assignments, conflicts };
}
// ---------------------------------------------------------------------------
// Action: pre-check (file overlap detection)
// ---------------------------------------------------------------------------
/** Attempt to find and read technical.md (or legacy FICHA-TECNICA.md) for a specId. */
async function readFichaTecnica(specId) {
    // Try modern location first, then legacy
    const locations = [
        { dir: 'planu/specs', file: 'technical.md' },
        { dir: 'docs/sdd/specs', file: 'FICHA-TECNICA.md' },
    ];
    for (const loc of locations) {
        try {
            const entries = await readdir(loc.dir);
            const match = entries.find((e) => e.startsWith(specId));
            /* v8 ignore next 3 — defensive: spec directory may not exist in all environments */
            if (!match) {
                continue;
            }
            return await readFile(`${loc.dir}/${match}/${loc.file}`, 'utf-8');
        }
        catch {
            continue;
        }
    }
    return undefined;
}
/** Extract file paths from a FICHA-TECNICA.md table (first backtick column). */
function extractFilesFromFicha(content) {
    const files = [];
    const lineRegex = /^\s*\|[^|]*`([^`|\n]+)`/gm;
    let match;
    while ((match = lineRegex.exec(content)) !== null) {
        const path = match[1] ?? '';
        if (path.length > 0 &&
            (path.startsWith('src/') ||
                path.startsWith('tests/') ||
                path.endsWith('.ts') ||
                path.endsWith('.json'))) {
            files.push(path);
        }
    }
    return [...new Set(files)];
}
export async function buildPreCheck(specIds) {
    const filesBySpec = new Map();
    for (const specId of specIds) {
        const content = await readFichaTecnica(specId);
        filesBySpec.set(specId, content ? extractFilesFromFicha(content) : []);
    }
    // Find files that appear in more than one spec
    const fileToSpecs = new Map();
    for (const [specId, files] of filesBySpec) {
        for (const file of files) {
            const list = fileToSpecs.get(file) ?? [];
            list.push(specId);
            fileToSpecs.set(file, list);
        }
    }
    const overlaps = [...fileToSpecs.entries()]
        .filter(([, specs]) => specs.length > 1)
        .map(([file, specs]) => ({ file, specs }));
    const hasConflicts = overlaps.length > 0;
    const recommendation = hasConflicts
        ? `Conflicto detectado: ${overlaps.length} archivo(s) compartidos. Ejecutar secuencialmente o usar 'decompose' para paralelismo intra-spec.`
        : 'Sin conflictos. Las specs pueden ejecutarse en paralelo.';
    return { specs: specIds, overlaps, hasConflicts, recommendation };
}
// ---------------------------------------------------------------------------
// Action: decompose (split spec into file-exclusive subtasks)
// ---------------------------------------------------------------------------
export async function buildDecompose(specId, agentCount) {
    const content = await readFichaTecnica(specId);
    if (!content) {
        return `No se encontró FICHA-TECNICA.md para '${specId}'. Verifica que el specId es correcto.`;
    }
    const allFiles = extractFilesFromFicha(content);
    if (allFiles.length === 0) {
        return `No se encontraron archivos en FICHA-TECNICA.md de '${specId}'.`;
    }
    const n = Math.min(agentCount, allFiles.length);
    const subtasks = Array.from({ length: n }, (_, i) => ({ index: i, files: [] }));
    // Round-robin assignment guarantees exclusive ownership
    allFiles.forEach((file, i) => {
        const target = subtasks[i % n];
        if (target) {
            target.files.push(file);
        }
    });
    return {
        specId,
        subtasks: subtasks.filter((t) => t.files.length > 0),
        totalFiles: allFiles.length,
    };
}
// ---------------------------------------------------------------------------
// ToolResult wrappers
// ---------------------------------------------------------------------------
function ok(data) {
    return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
}
function err(msg) {
    return {
        content: [{ type: 'text', text: JSON.stringify({ error: msg }, null, 2) }],
        isError: true,
    };
}
export function handleDistribute(activeSessions, allSpecs) {
    if (activeSessions.length === 0) {
        const result = {
            action: 'distribute',
            distribution: [],
            message: 'No active agent sessions. Register agents first.',
        };
        return ok(result);
    }
    const { assignments, conflicts } = buildDistribution(activeSessions, allSpecs);
    const result = {
        action: 'distribute',
        sessions: activeSessions,
        distribution: assignments,
        conflicts: conflicts.length > 0 ? conflicts : undefined,
        message: `${assignments.length} task(s) across ${activeSessions.length} active agent(s)${conflicts.length > 0 ? `, ${conflicts.length} conflict(s) detected` : ''}`,
    };
    return ok(result);
}
export async function handlePreCheck(specIds) {
    if (specIds.length === 0) {
        return err('specIds must have at least one spec ID');
    }
    const result = await buildPreCheck(specIds);
    return ok(result);
}
export async function handleDecompose(specId, agentCount = 2) {
    const result = await buildDecompose(specId, agentCount);
    if (typeof result === 'string') {
        return err(result);
    }
    return ok(result);
}
//# sourceMappingURL=orchestrate-distribution.js.map