import { categorize } from './categorizer.js';
/** Similarity tolerance: ±30% of the target criteria count. */
const SIMILARITY_TOLERANCE = 0.3;
/**
 * Computes tag overlap as a fraction (0-1) between two tag arrays.
 */
function tagOverlap(tagsA, tagsB) {
    if (tagsA.length === 0 && tagsB.length === 0) {
        return 1;
    }
    if (tagsA.length === 0 || tagsB.length === 0) {
        return 0;
    }
    const setA = new Set(tagsA.map((t) => t.toLowerCase()));
    const setB = new Set(tagsB.map((t) => t.toLowerCase()));
    const intersection = [...setA].filter((t) => setB.has(t)).length;
    const union = new Set([...setA, ...setB]).size;
    return intersection / union;
}
/**
 * Checks whether a historical spec is similar to the target spec.
 * Similarity: criteria count within ±30% AND tag overlap > 0 (or both have no tags).
 */
function isSimilar(historical, targetCriteriaCount, targetTags) {
    const delta = Math.abs(historical.criteriaCount - targetCriteriaCount);
    const threshold = targetCriteriaCount * SIMILARITY_TOLERANCE;
    // Allow specs with 0 target criteria to match specs with 0 criteria
    const withinRange = targetCriteriaCount === 0 ? historical.criteriaCount === 0 : delta <= Math.max(threshold, 1);
    if (!withinRange) {
        return false;
    }
    const overlap = tagOverlap(targetTags, historical.tags ?? []);
    // Accept if there's any tag overlap, or if both have no tags at all
    const tagMatch = overlap > 0 || (targetTags.length === 0 && (historical.tags?.length ?? 0) === 0);
    return tagMatch;
}
/**
 * Suggests complexity level based on past similar specs.
 * Falls back to categorizer rules when no similar specs are found.
 */
export function adviseSimilarSpecs(targetCriteriaCount, targetTags, historicalSpecs) {
    const similar = historicalSpecs.filter((s) => isSimilar(s, targetCriteriaCount, targetTags));
    if (similar.length === 0) {
        // Fall back to rule-based categorizer
        const report = categorize({
            criteriaCount: targetCriteriaCount,
            estimatedHours: 0,
            filesToModify: 0,
            tags: targetTags,
        });
        return {
            suggestedCategory: report.category,
            reasoning: 'No similar historical specs found. Category derived from criteria count rules.',
            similarSpecsCount: 0,
            avgCriteriaCount: targetCriteriaCount,
            avgHours: 0,
        };
    }
    const avgCriteriaCount = similar.reduce((sum, s) => sum + s.criteriaCount, 0) / similar.length;
    const avgHours = similar.reduce((sum, s) => sum + (s.actualHours ?? s.estimatedHours), 0) / similar.length;
    // Determine the most common category among similar specs
    const categoryCounts = new Map();
    for (const spec of similar) {
        const cat = spec.category ??
            categorize({
                criteriaCount: spec.criteriaCount,
                estimatedHours: spec.estimatedHours,
                filesToModify: spec.filesCount,
            }).category;
        categoryCounts.set(cat, (categoryCounts.get(cat) ?? 0) + 1);
    }
    let suggestedCategory = 'small';
    let maxCount = 0;
    for (const [cat, count] of categoryCounts) {
        if (count > maxCount) {
            maxCount = count;
            suggestedCategory = cat;
        }
    }
    return {
        suggestedCategory,
        reasoning: `Based on ${similar.length} similar spec${similar.length === 1 ? '' : 's'} (avg ${avgCriteriaCount.toFixed(1)} criteria, avg ${avgHours.toFixed(1)}h). Most common category: ${suggestedCategory}.`,
        similarSpecsCount: similar.length,
        avgCriteriaCount,
        avgHours,
    };
}
//# sourceMappingURL=similar-specs-advisor.js.map