// engine/validator/criterion-enricher.ts — Enrich acceptance criteria with verify blocks (SPEC-186)
//
// Adds machine-verifiable `verify` blocks to acceptance criteria text.
// Verification metadata is stored as HTML comments in spec.md output —
// invisible in rendered markdown but fully parseable by tooling.
// ---------------------------------------------------------------------------
// Built-in templates — priority-ordered (first match wins)
// ---------------------------------------------------------------------------
/** Exported for extensibility: callers may add domain-specific templates. */
export const VERIFY_TEMPLATES = [
    // i18n / hardcoded strings
    {
        keywords: ['hardcoded string', 'hardcoded text', 'raw string', 'literal string'],
        type: 'grep_absent',
        patternTemplate: '[áéíóúñ¿¡]|"[A-Z][a-z]+ [a-z]+"',
        scopeTemplate: 'src/**/*.{tsx,jsx,vue}',
        description: 'No hardcoded strings in components',
    },
    // Type safety
    {
        keywords: ['no any type', 'strict typing', 'no typescript any', 'no-any', 'no any'],
        type: 'grep_absent',
        patternTemplate: ':\\s*any[\\s,;>)]',
        scopeTemplate: 'src/**/*.ts',
        description: 'No any types in source',
    },
    // Security — no secrets
    {
        keywords: ['no secrets', 'no credentials', 'no api key', 'no password', 'no hardcoded secret'],
        type: 'grep_absent',
        patternTemplate: 'password\\s*=|api_key\\s*=|secret\\s*=',
        scopeTemplate: 'src/**/*.ts',
        description: 'No hardcoded secrets',
    },
    // Test coverage / test existence
    {
        keywords: ['test for every', 'test coverage', 'tests exist for', 'test file'],
        type: 'file_exists',
        scopeTemplate: 'tests/**/*.test.ts',
        description: 'Test files exist for source files',
    },
    // File / line limits
    {
        keywords: ['no file exceeds', 'max lines', 'maximum lines', 'line limit', 'lines per file'],
        type: 'metric_max',
        expected: 300,
        description: 'No file exceeds line limit',
    },
    // Error handling
    {
        keywords: ['error handling', 'handles errors', 'try catch', 'try/catch', 'error boundary'],
        type: 'grep_present',
        patternTemplate: 'try\\s*\\{|catch\\s*\\(',
        description: 'Error handling present',
    },
    // Imports / dependency injection / architecture pattern
    {
        keywords: ['import from', 'uses dependency injection', 'follows pattern', 'injects'],
        type: 'grep_present',
        description: 'Pattern usage detected',
    },
];
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/** Extract the first integer found in a string (e.g. "200 lines" → 200). */
function extractNumber(text) {
    const match = /\b(\d+)\b/.exec(text);
    return match?.[1] !== undefined ? parseInt(match[1], 10) : undefined;
}
/**
 * Extract an explicit scope path from criterion text.
 * Only matches directory paths that contain at least one slash (e.g. "src/components/").
 * This prevents plain words like "source" or "target" from being treated as paths.
 */
function extractScope(text) {
    // Require at least one slash in the captured path to distinguish paths from words
    const match = /\bin\s+([\w.*{},-]+(?:\/[\w.*{},-]*)+(?:\/\*\*)?)|under\s+([\w.*{},-]+(?:\/[\w.*{},-]*)+(?:\/\*\*)?)/i.exec(text);
    const raw = match?.[1] ?? match?.[2];
    if (!raw) {
        return undefined;
    }
    // Normalize trailing slash before appending glob
    const normalized = raw.replace(/\/+$/, '');
    return normalized.endsWith('/**') || normalized.includes('*') ? normalized : `${normalized}/**`;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Match a criterion text against the built-in (and optionally injected) templates.
 * Returns the first matching template or null if none match.
 */
export function matchTemplate(criterion) {
    const lower = criterion.toLowerCase();
    for (const template of VERIFY_TEMPLATES) {
        if (template.keywords.some((kw) => lower.includes(kw))) {
            return template;
        }
    }
    return null;
}
/**
 * Build a VerifyBlock from a matched template, extracting numeric/scope hints
 * from the criterion text when possible.
 */
export function buildVerifyBlock(criterion, template) {
    const block = {
        type: template.type,
        description: template.description,
    };
    if (template.patternTemplate !== undefined) {
        block.pattern = template.patternTemplate;
    }
    // Prefer explicit scope from criterion text; fall back to template default
    const scopeFromText = extractScope(criterion);
    if (scopeFromText !== undefined) {
        block.scope = scopeFromText;
    }
    else if (template.scopeTemplate !== undefined) {
        block.scope = template.scopeTemplate;
    }
    // For metric checks, try to extract an explicit number from the criterion
    if (template.type === 'metric_max' || template.type === 'metric_min') {
        const extracted = extractNumber(criterion);
        block.expected = extracted ?? template.expected;
    }
    else if (template.expected !== undefined) {
        block.expected = template.expected;
    }
    return block;
}
/**
 * Enrich a single criterion text with a VerifyBlock.
 * Falls back to `manual` when no template matches.
 */
export function enrichCriterion(criterion) {
    const template = matchTemplate(criterion);
    if (template !== null) {
        return { text: criterion, verify: buildVerifyBlock(criterion, template) };
    }
    return {
        text: criterion,
        verify: { type: 'manual', description: 'Requires manual verification' },
    };
}
/**
 * Enrich a batch of criterion texts.
 * Empty or whitespace-only entries are silently skipped.
 */
export function enrichCriteria(criteria) {
    return criteria.filter((c) => c.trim().length > 0).map((c) => enrichCriterion(c));
}
/**
 * Parse raw markdown AC lines (e.g. "- [ ] Criterion text") into plain strings.
 * Strips list prefixes so enrichers receive clean criterion text.
 */
export function parseCriteriaText(lines) {
    return lines
        .map((line) => line.replace(/^\s*[-*+]\s+\[?\s*[xX ]?\s*\]?\s*/, '').trim())
        .filter((line) => line.length > 0);
}
/**
 * Format enriched criteria as markdown list items with embedded verify blocks.
 *
 * Output format:
 * ```
 * - [ ] Criterion text
 *   <!-- verify: {"type":"grep_absent","pattern":"...","scope":"..."} -->
 * ```
 *
 * The `<!-- verify: ... -->` HTML comment is invisible in rendered markdown
 * but fully parseable by automated tooling.
 */
export function formatVerifiableCriteria(criteria) {
    if (criteria.length === 0) {
        return '';
    }
    return criteria
        .map((c) => {
        const verifyJson = JSON.stringify(c.verify);
        return `- [ ] ${c.text}\n  <!-- verify: ${verifyJson} -->`;
    })
        .join('\n');
}
//# sourceMappingURL=criterion-enricher.js.map