// engine/spec-coverage/criteria-mapper.ts — Extracts acceptance criteria from
// HU.md and maps test files to each criterion using 3 strategies.
import { findDirectReferences, findSpecReferences, findKeywordMatches } from './test-matchers.js';
import { extractKeywords } from './keyword-extractor.js';
// Re-export for backward compatibility
export { extractKeywords } from './keyword-extractor.js';
// ---------------------------------------------------------------------------
// AC extraction
// ---------------------------------------------------------------------------
/**
 * Parses HU.md content and returns a list of acceptance criteria with their
 * identifiers and descriptions. Supports flexible AC formats:
 *   - `- [ ] **AC-01**: text`
 *   - `- **AC-01**: text`
 *   - `AC-01: text`
 *   - `- AC-01 text`
 */
export function extractCriteria(huContent) {
    const criteria = [];
    // Primary pattern: - [ ] **AC-NN**: ... or - **AC-NN**: ...
    const checkboxPattern = /[-*]\s+(?:\[[ xX]\]\s+)?\*{0,2}(AC-\d{2,3})\*{0,2}[:\s]+(.+)/gm;
    // Fallback: plain AC-NN: ... (no list marker)
    const plainPattern = /^(AC-\d{2,3})[:\s]+(.+)/gm;
    const seen = new Set();
    for (const pattern of [checkboxPattern, plainPattern]) {
        let match;
        pattern.lastIndex = 0;
        while ((match = pattern.exec(huContent)) !== null) {
            /* v8 ignore next 2 */
            const acId = match[1] ?? '';
            const rawText = match[2]?.trim() ?? '';
            if (!acId || seen.has(acId)) {
                continue;
            }
            seen.add(acId);
            criteria.push({
                acId,
                description: truncate(rawText, 80),
                rawText,
            });
        }
    }
    // Sort by AC number
    return criteria.sort((a, b) => {
        const numA = parseInt(a.acId.replace('AC-', ''), 10);
        const numB = parseInt(b.acId.replace('AC-', ''), 10);
        return numA - numB;
    });
}
// ---------------------------------------------------------------------------
// Test-to-criterion mapping
// ---------------------------------------------------------------------------
/**
 * Maps a list of test files to a single criterion using the 3 strategies
 * defined in SPEC-068. Returns the full CriterionCoverage for that criterion.
 */
export function mapTestsToCriterion(criterion, testFiles, testContents, specId) {
    const { acId, description, rawText } = criterion;
    // Strategy 1 — Direct reference (high confidence)
    const directMatches = findDirectReferences(acId, testFiles, testContents);
    if (directMatches.length > 0) {
        return buildResult(acId, description, 'covered', 'direct-reference', directMatches, testFiles);
    }
    // Strategy 2 — Spec reference (medium confidence)
    const specMatches = findSpecReferences(specId, testFiles, testContents);
    if (specMatches.length > 0) {
        return buildResult(acId, description, 'covered', 'spec-reference', specMatches, testFiles);
    }
    // Strategy 3 — Keyword heuristic (low confidence)
    const keywords = extractKeywords(rawText);
    if (keywords.length >= 2) {
        const heuristicMatches = findKeywordMatches(keywords, testFiles, testContents);
        if (heuristicMatches.length > 0) {
            return buildResult(acId, description, 'superficial', 'heuristic', heuristicMatches, testFiles);
        }
    }
    // No match
    return {
        acId,
        description,
        status: 'uncovered',
        tests: [],
    };
}
// ---------------------------------------------------------------------------
// Result builder
// ---------------------------------------------------------------------------
function buildResult(acId, description, status, strategy, rawMatches, testFiles) {
    // Degrade to superficial if ALL matched files are superficial
    const fileSuperficialMap = new Map(testFiles.map((tf) => [tf.file, tf.isSuperficial]));
    // Map.get returns undefined for missing keys, which correctly evaluates as !== true
    const allSuperficial = rawMatches.length > 0 && rawMatches.every((m) => fileSuperficialMap.get(m.file) === true);
    const effectiveStatus = allSuperficial ? 'superficial' : status;
    return {
        acId,
        description,
        status: effectiveStatus,
        strategy,
        tests: rawMatches,
    };
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function truncate(text, maxLen) {
    return text.length <= maxLen ? text : text.slice(0, maxLen - 1) + '…';
}
//# sourceMappingURL=criteria-mapper.js.map