// engine/spec-lint-engine.ts — Custom Spec Lint Rules Engine (SPEC-364)
import { createHash } from 'node:crypto';
// ---------------------------------------------------------------------------
// ID generation
// ---------------------------------------------------------------------------
export function generateRuleId(name) {
    const input = `${name}-${String(Date.now())}`;
    const hash = createHash('sha256').update(input).digest('hex').slice(0, 8);
    return `rule-${hash}`;
}
// ---------------------------------------------------------------------------
// Field extraction from spec
// ---------------------------------------------------------------------------
function getFieldValue(spec, field) {
    switch (field) {
        case 'title':
            return spec.title;
        case 'description':
            // description is not a direct Spec field; use title as a proxy for text checks
            return spec.title;
        case 'tags':
            return spec.tags;
        case 'acceptanceCriteria':
            // acceptanceCriteria is not a direct Spec field; return title as a text proxy
            return spec.title;
        case 'estimatedHours':
            // maps to spec.estimation.devHours
            return spec.estimation.devHours;
        case 'priority':
            // maps to spec.difficulty
            return spec.difficulty;
    }
}
// ---------------------------------------------------------------------------
// Condition evaluation
// ---------------------------------------------------------------------------
function toStr(v) {
    if (typeof v === 'string') {
        return v;
    }
    if (typeof v === 'number') {
        return String(v);
    }
    return '';
}
export function evaluateCondition(spec, condition) {
    const value = getFieldValue(spec, condition.field);
    switch (condition.operator) {
        case 'exists':
            return value !== undefined && value !== '';
        case 'not-exists':
            return value === undefined || value === '';
        case 'includes': {
            if (Array.isArray(value)) {
                return value.some((v) => toStr(v).toLowerCase() === toStr(condition.value).toLowerCase());
            }
            return toStr(value).toLowerCase().includes(toStr(condition.value).toLowerCase());
        }
        case 'not-includes': {
            if (Array.isArray(value)) {
                return !value.some((v) => toStr(v).toLowerCase() === toStr(condition.value).toLowerCase());
            }
            return !toStr(value).toLowerCase().includes(toStr(condition.value).toLowerCase());
        }
        case 'min-length': {
            const len = Array.isArray(value) ? value.length : toStr(value).length;
            return len >= Number(condition.value ?? 0);
        }
        case 'max-length': {
            const len = Array.isArray(value) ? value.length : toStr(value).length;
            return len <= Number(condition.value ?? 0);
        }
        case 'any-contains': {
            if (!Array.isArray(value)) {
                return false;
            }
            return value.some((v) => toStr(v).toLowerCase().includes(toStr(condition.value).toLowerCase()));
        }
        case 'none-contain': {
            if (!Array.isArray(value)) {
                return true;
            }
            return !value.some((v) => toStr(v).toLowerCase().includes(toStr(condition.value).toLowerCase()));
        }
        case 'greater-than':
            return typeof value === 'number' && value > Number(condition.value ?? 0);
        case 'less-than':
            return typeof value === 'number' && value < Number(condition.value ?? 0);
        default:
            return false;
    }
}
// ---------------------------------------------------------------------------
// Rule applicability
// ---------------------------------------------------------------------------
function ruleApplies(rule, spec) {
    if (rule.appliesTo.all === true) {
        return true;
    }
    if (rule.appliesTo.tags !== undefined && rule.appliesTo.tags.length > 0) {
        const specTags = spec.tags.map((t) => t.toLowerCase());
        return rule.appliesTo.tags.some((t) => specTags.includes(t.toLowerCase()));
    }
    return false;
}
// ---------------------------------------------------------------------------
// Single spec lint
// ---------------------------------------------------------------------------
export function lintSpec(spec, rules) {
    const violations = [];
    for (const rule of rules) {
        if (!ruleApplies(rule, spec)) {
            continue;
        }
        // If there's a `when` guard, skip rule if guard is not satisfied
        if (rule.when !== undefined && !evaluateCondition(spec, rule.when)) {
            continue;
        }
        // Check the `then` condition — violation if it fails
        if (!evaluateCondition(spec, rule.then)) {
            violations.push({
                specId: spec.id,
                ruleId: rule.id,
                ruleName: rule.name,
                message: buildViolationMessage(spec.id, rule),
                severity: rule.severity,
                field: rule.then.field,
            });
        }
    }
    return violations;
}
function buildViolationMessage(specId, rule) {
    const cond = rule.then;
    const base = `[${specId}] Rule "${rule.name}": field "${cond.field}" must ${cond.operator}`;
    return cond.value !== undefined ? `${base} ${toStr(cond.value)}` : base;
}
// ---------------------------------------------------------------------------
// Project-wide lint
// ---------------------------------------------------------------------------
export function runLint(specs, rules) {
    const allViolations = [];
    const failedSpecIds = new Set();
    for (const spec of specs) {
        const violations = lintSpec(spec, rules);
        allViolations.push(...violations);
        if (violations.length > 0) {
            failedSpecIds.add(spec.id);
        }
    }
    const errorCount = allViolations.filter((v) => v.severity === 'error').length;
    const warningCount = allViolations.filter((v) => v.severity === 'warning').length;
    return {
        projectPath: '',
        specIds: specs.map((s) => s.id),
        violations: allViolations,
        errorCount,
        warningCount,
        rulesApplied: rules.length,
        passedSpecs: specs.length - failedSpecIds.size,
        failedSpecs: failedSpecIds.size,
    };
}
//# sourceMappingURL=spec-lint-engine.js.map