/**
 * Detection Rule Loader
 *
 * Loads and validates detection rules from YAML files or inline definitions.
 *
 * @module scanners/detection/rules/loader
 */
import { readFile, readdir } from "fs/promises";
import { join, extname } from "path";
import { parse as parseYAML } from "yaml";
const VALID_SEVERITIES = ["critical", "high", "medium", "low", "info"];
const VALID_ENGINES = ["astQuery", "dataFlow", "controlFlow"];
export class RuleValidationError extends Error {
    ruleId;
    field;
    constructor(ruleId, field, message) {
        super(`Rule ${ruleId}: ${field} - ${message}`);
        this.ruleId = ruleId;
        this.field = field;
        this.name = "RuleValidationError";
    }
}
function validateRule(rule, source) {
    if (typeof rule !== "object" || rule === null) {
        throw new RuleValidationError(source, "root", "Rule must be an object");
    }
    const r = rule;
    if (typeof r.id !== "string" || !r.id) {
        throw new RuleValidationError(source, "id", "Required string field");
    }
    if (typeof r.name !== "string" || !r.name) {
        throw new RuleValidationError(r.id, "name", "Required string field");
    }
    if (typeof r.description !== "string" || !r.description) {
        throw new RuleValidationError(r.id, "description", "Required string field");
    }
    if (typeof r.category !== "string" || !r.category) {
        throw new RuleValidationError(r.id, "category", "Required string field");
    }
    if (!VALID_SEVERITIES.includes(r.severity)) {
        throw new RuleValidationError(r.id, "severity", `Must be one of: ${VALID_SEVERITIES.join(", ")}`);
    }
    if (typeof r.confidence !== "number" || r.confidence < 0 || r.confidence > 100) {
        throw new RuleValidationError(r.id, "confidence", "Must be a number between 0 and 100");
    }
    if (typeof r.engines !== "object" || r.engines === null) {
        throw new RuleValidationError(r.id, "engines", "Required object field");
    }
    const engines = r.engines;
    const hasEngine = VALID_ENGINES.some((e) => engines[e] !== undefined);
    if (!hasEngine) {
        throw new RuleValidationError(r.id, "engines", `Must have at least one of: ${VALID_ENGINES.join(", ")}`);
    }
    if (engines.dataFlow) {
        const df = engines.dataFlow;
        if (!Array.isArray(df.sources) || df.sources.length === 0) {
            throw new RuleValidationError(r.id, "engines.dataFlow.sources", "Required non-empty array");
        }
        if (!Array.isArray(df.sinks) || df.sinks.length === 0) {
            throw new RuleValidationError(r.id, "engines.dataFlow.sinks", "Required non-empty array");
        }
    }
    if (engines.astQuery) {
        const aq = engines.astQuery;
        if (typeof aq.pattern !== "string" || !aq.pattern) {
            throw new RuleValidationError(r.id, "engines.astQuery.pattern", "Required string field");
        }
    }
    return {
        id: r.id,
        name: r.name,
        description: r.description,
        category: r.category,
        severity: r.severity,
        confidence: r.confidence,
        enabled: r.enabled !== false,
        engines: r.engines,
        cweIds: Array.isArray(r.cweIds) ? r.cweIds : undefined,
        owaspRefs: Array.isArray(r.owaspRefs) ? r.owaspRefs : undefined,
        autofixPatternId: typeof r.autofixPatternId === "string" ? r.autofixPatternId : undefined,
        metadata: typeof r.metadata === "object" ? r.metadata : undefined,
    };
}
export async function loadRuleFromYAML(filePath) {
    const content = await readFile(filePath, "utf-8");
    const parsed = parseYAML(content);
    return validateRule(parsed, filePath);
}
export async function loadRulesFromDirectory(dirPath) {
    const rules = [];
    const entries = await readdir(dirPath, { withFileTypes: true });
    for (const entry of entries) {
        if (!entry.isFile())
            continue;
        const ext = extname(entry.name).toLowerCase();
        if (ext !== ".yaml" && ext !== ".yml")
            continue;
        try {
            const rule = await loadRuleFromYAML(join(dirPath, entry.name));
            rules.push(rule);
        }
        catch (error) {
            console.warn(`Failed to load rule ${entry.name}:`, error);
        }
    }
    return rules;
}
export function createRule(config) {
    return validateRule({ ...config, enabled: config.enabled ?? true }, config.id);
}
//# sourceMappingURL=loader.js.map