// @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
// engine/agent-role-mapper.ts — SPEC-638
import { readFile } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadRoleTemplate } from './role-template-loader.js';
const REGISTRY_PATH = join(dirname(fileURLToPath(import.meta.url)), '../../config/agent-role-registry.json');
function collectCandidates(registry, normalizedTags) {
    const candidateMap = new Map();
    for (const mapping of registry.mappings) {
        const matchedTags = mapping.tags.filter((t) => normalizedTags.includes(t));
        if (matchedTags.length === 0) {
            continue;
        }
        const existing = candidateMap.get(mapping.role);
        if (existing !== undefined) {
            const mergedTags = Array.from(new Set([...existing.matchedTags, ...matchedTags]));
            const mergedPatterns = Array.from(new Set([...existing.fileOwnershipPattern, ...mapping.fileOwnershipPattern]));
            candidateMap.set(mapping.role, {
                role: mapping.role,
                fileOwnershipPattern: mergedPatterns,
                matchedTags: mergedTags,
            });
        }
        else {
            candidateMap.set(mapping.role, {
                role: mapping.role,
                fileOwnershipPattern: mapping.fileOwnershipPattern,
                matchedTags,
            });
        }
    }
    return candidateMap;
}
async function buildAssignment(role, fileOwnershipPattern, matchedTags, source, registry, rationale) {
    try {
        const template = await loadRoleTemplate(role);
        const mapping = registry.mappings.find((m) => m.role === role);
        const model = mapping?.modelOverride ?? template.model;
        return { role, template, fileOwnershipPattern, model, matchedTags, source };
    }
    catch {
        rationale.push(`${role}: skipped — failed to load template`);
        return null;
    }
}
export async function mapTagsToRoles(tags, registryPath = REGISTRY_PATH) {
    const raw = await readFile(registryPath, 'utf-8');
    const registry = JSON.parse(raw);
    const normalizedTags = tags.map((t) => t.toLowerCase().trim()).filter((t) => t.length > 0);
    const candidateMap = collectCandidates(registry, normalizedTags);
    const assignments = [];
    const rationale = [];
    const addedRoles = new Set();
    // Phase 1: alwaysInclude except 'qa'
    for (const role of registry.alwaysInclude) {
        if (role === 'qa') {
            continue;
        }
        if (addedRoles.has(role)) {
            continue;
        }
        const assignment = await buildAssignment(role, [], [], 'always-include', registry, rationale);
        if (assignment !== null) {
            assignments.push(assignment);
            addedRoles.add(role);
            rationale.push(`${role}: always-include`);
        }
    }
    // Phase 2: tag-matched roles in registry-mappings order
    for (const mapping of registry.mappings) {
        const candidate = candidateMap.get(mapping.role);
        if (candidate === undefined) {
            continue;
        }
        if (addedRoles.has(mapping.role)) {
            continue;
        }
        const assignment = await buildAssignment(mapping.role, candidate.fileOwnershipPattern, candidate.matchedTags, 'tag-match', registry, rationale);
        if (assignment !== null) {
            assignments.push(assignment);
            addedRoles.add(mapping.role);
            rationale.push(`${mapping.role}: matched tags [${candidate.matchedTags.join(', ')}]`);
        }
    }
    // Phase 3: 'qa' always last if in alwaysInclude and not yet added
    if (registry.alwaysInclude.includes('qa') && !addedRoles.has('qa')) {
        const assignment = await buildAssignment('qa', [], [], 'always-include', registry, rationale);
        if (assignment !== null) {
            assignments.push(assignment);
            addedRoles.add('qa');
            rationale.push('qa: always-include (last)');
        }
    }
    const unmatchedTags = normalizedTags.filter((tag) => {
        return !registry.mappings.some((m) => m.tags.includes(tag));
    });
    return { assignments, rationale, unmatchedTags };
}
//# sourceMappingURL=agent-role-mapper.js.map