/**
 * Semgrep YAML rule importer (Pipeline 4).
 * Parses Semgrep rule files and produces CompiledRule objects.
 */
import { parse as parseYaml } from 'yaml';
import { hashLesson, validateRegex } from './compiler.js';
// ─── Language-to-glob mapping ───────────────────────
const LANGUAGE_GLOBS = {
    javascript: ['**/*.js', '**/*.jsx'],
    typescript: ['**/*.ts', '**/*.tsx'],
    js: ['**/*.js', '**/*.jsx'],
    ts: ['**/*.ts', '**/*.tsx'],
    python: ['**/*.py'],
    rust: ['**/*.rs'],
    go: ['**/*.go'],
    java: ['**/*.java'],
    ruby: ['**/*.rb'],
    c: ['**/*.c', '**/*.h'],
    cpp: ['**/*.cpp', '**/*.hpp', '**/*.cc'],
};
function languagesToGlobs(languages) {
    if (!languages || languages.length === 0)
        return undefined;
    const globs = [];
    for (const lang of languages) {
        const mapped = LANGUAGE_GLOBS[lang.toLowerCase()];
        if (mapped)
            globs.push(...mapped);
    }
    return globs.length > 0 ? globs : undefined;
}
// ─── Parser ─────────────────────────────────────────
/** Parse Semgrep YAML rules into CompiledRule objects. */
export function parseSemgrepRules(yamlContent) {
    const rules = [];
    const skipped = [];
    let doc;
    try {
        doc = parseYaml(yamlContent);
    }
    catch {
        return { rules, skipped: [{ id: '(root)', reason: 'Invalid YAML' }] };
    }
    if (!doc ||
        typeof doc !== 'object' ||
        !('rules' in doc) ||
        !Array.isArray(doc.rules)) {
        return { rules, skipped: [{ id: '(root)', reason: 'No "rules" array found in YAML' }] };
    }
    const now = new Date().toISOString();
    for (const entry of doc.rules) {
        if (!entry || typeof entry !== 'object')
            continue;
        const rule = entry;
        const id = typeof rule.id === 'string' ? rule.id : undefined;
        if (!id) {
            skipped.push({ id: '(unknown)', reason: 'Missing rule id' });
            continue;
        }
        // Extract pattern — prefer pattern-regex, fall back to simple string pattern
        let pattern;
        if (typeof rule['pattern-regex'] === 'string') {
            pattern = rule['pattern-regex'];
        }
        else if (typeof rule.pattern === 'string' && !rule.patterns && !rule['pattern-either']) {
            // Simple string patterns like "eval(...)" — convert to regex
            // 1. Replace metavariables with placeholder before escaping
            // 2. Escape regex special chars
            // 3. Restore metavariable placeholders and `...` wildcards
            const METAVAR_PLACEHOLDER = '\x00METAVAR\x00';
            const withPlaceholders = rule.pattern.replace(/\$\w+/g, METAVAR_PLACEHOLDER);
            const escaped = withPlaceholders.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            pattern = escaped
                .replace(/\\\.\\\.\\\./g, '.*')
                .replace(new RegExp(METAVAR_PLACEHOLDER.replace(/\x00/g, '\\x00'), 'g'), '\\w+');
        }
        if (!pattern) {
            const reason = rule.patterns
                ? 'Compound pattern (patterns/pattern-either)'
                : 'No pattern or pattern-regex field';
            skipped.push({ id, reason });
            continue;
        }
        // Validate regex for syntax and ReDoS safety
        const validation = validateRegex(pattern);
        if (!validation.valid) {
            skipped.push({ id, reason: `Invalid regex: ${validation.reason}` });
            continue;
        }
        const message = typeof rule.message === 'string' ? rule.message : `[semgrep] ${id}`;
        // Severity
        const sevRaw = typeof rule.severity === 'string' ? rule.severity.toUpperCase() : 'WARNING';
        const severity = sevRaw === 'ERROR' ? 'error' : 'warning';
        // File globs from languages + paths
        const languages = Array.isArray(rule.languages)
            ? rule.languages.filter((l) => typeof l === 'string')
            : undefined;
        const langGlobs = languagesToGlobs(languages);
        const paths = rule.paths;
        const includeGlobs = paths && Array.isArray(paths.include)
            ? paths.include.filter((p) => typeof p === 'string')
            : [];
        const excludeGlobs = paths && Array.isArray(paths.exclude)
            ? paths.exclude.filter((p) => typeof p === 'string').map((p) => `!${p}`)
            : [];
        const fileGlobs = [
            ...(includeGlobs.length > 0 ? includeGlobs : (langGlobs ?? [])),
            ...excludeGlobs,
        ];
        // Category from metadata (validate against allowed values)
        const VALID_CATEGORIES = new Set(['security', 'architecture', 'style', 'performance']);
        const metadata = rule.metadata;
        const rawCategory = metadata && typeof metadata.category === 'string' ? metadata.category : undefined;
        const category = rawCategory && VALID_CATEGORIES.has(rawCategory)
            ? rawCategory
            : undefined;
        const heading = `[semgrep] ${id}`;
        rules.push({
            lessonHash: hashLesson(heading, message),
            lessonHeading: heading,
            pattern,
            message,
            engine: 'regex',
            severity,
            compiledAt: now,
            createdAt: now,
            ...(fileGlobs.length > 0 ? { fileGlobs } : {}),
            ...(category ? { category } : {}),
        });
    }
    return { rules, skipped };
}
//# sourceMappingURL=semgrep-adapter.js.map