// engine/compliance-checker.ts — Compliance-as-Specs logic (SPEC-319)
import { readFileSync, existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
// ---------------------------------------------------------------------------
// Profile loading
// ---------------------------------------------------------------------------
let _profiles = null;
function loadProfiles() {
    if (_profiles !== null) {
        return _profiles;
    }
    const dir = fileURLToPath(new URL('../config', import.meta.url));
    const raw = readFileSync(join(dir, 'compliance-profiles.json'), 'utf-8');
    _profiles = JSON.parse(raw);
    return _profiles;
}
const ALL_FRAMEWORKS = ['gdpr', 'hipaa', 'pci-dss', 'soc2', 'ccpa'];
// ---------------------------------------------------------------------------
// Context detection
// ---------------------------------------------------------------------------
/**
 * Detect which compliance frameworks apply based on keyword matching
 * in the spec title and description text.
 */
export function detectComplianceContext(title, description) {
    const profiles = loadProfiles();
    const text = `${title} ${description}`.toLowerCase();
    const detected = [];
    for (const framework of ALL_FRAMEWORKS) {
        const profile = profiles[framework];
        if (profile === undefined) {
            continue;
        }
        const matches = profile.keywords.some((kw) => text.includes(kw.toLowerCase()));
        if (matches) {
            detected.push(framework);
        }
    }
    return detected;
}
// ---------------------------------------------------------------------------
// Gap analysis
// ---------------------------------------------------------------------------
/**
 * Evaluate a spec's acceptance criteria against required compliance criteria.
 * Reads the spec markdown file to extract AC content.
 * Returns a full gap report including missing criteria and coverage percentage.
 */
export async function checkCompliance(spec, frameworks) {
    const profiles = loadProfiles();
    const specContent = await readSpecContent(spec);
    const acText = `${spec.title} ${specContent}`.toLowerCase();
    const gaps = [];
    for (const framework of frameworks) {
        const profile = profiles[framework];
        if (profile === undefined) {
            continue;
        }
        for (const criterion of profile.criteria) {
            const status = matchCriterion(criterion, acText);
            if (status !== 'present') {
                gaps.push({ framework, criterion, status });
            }
        }
    }
    const totalCriteria = countTotalCriteria(frameworks, profiles);
    const missingCriteria = gaps.filter((g) => g.status === 'missing').map((g) => g.criterion);
    const presentCount = totalCriteria - gaps.length;
    const coveragePercent = totalCriteria === 0 ? 100 : Math.round((presentCount / totalCriteria) * 100);
    return {
        specId: spec.id,
        frameworks,
        gaps,
        missingCriteria,
        coveragePercent,
    };
}
/**
 * Return the full list of compliance criteria for the specified frameworks.
 * Used when injecting missing criteria into a spec.
 */
export function getComplianceCriteria(frameworks) {
    const profiles = loadProfiles();
    const criteria = [];
    for (const framework of frameworks) {
        const profile = profiles[framework];
        if (profile === undefined) {
            continue;
        }
        criteria.push(...profile.criteria);
    }
    return criteria;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function readSpecContent(spec) {
    /* v8 ignore start */
    if (!spec.specPath) {
        return '';
    }
    if (existsSync(spec.specPath)) {
        return readFile(spec.specPath, 'utf-8');
    }
    /* v8 ignore stop */
    return '';
}
/**
 * Check how well a compliance criterion is represented in the spec content text.
 * Uses keyword extraction to match against the full spec markdown.
 */
function matchCriterion(criterion, acText) {
    const keywords = extractKeywords(criterion);
    if (keywords.length === 0) {
        return 'missing';
    }
    const matched = keywords.filter((kw) => acText.includes(kw));
    const ratio = matched.length / keywords.length;
    if (ratio >= 0.7) {
        return 'present';
    }
    if (ratio >= 0.3) {
        return 'partial';
    }
    return 'missing';
}
const STOPWORDS = new Set([
    'a',
    'an',
    'the',
    'is',
    'are',
    'in',
    'of',
    'for',
    'and',
    'or',
    'to',
    'with',
    'all',
    'at',
    'be',
    'by',
    'on',
    'that',
    'this',
    'it',
    'its',
    'not',
    'as',
    'from',
    'before',
    'after',
]);
function extractKeywords(text) {
    return text
        .toLowerCase()
        .replace(/[()[\]{}.,;:!?'"]/g, ' ')
        .split(/\s+/)
        .filter((w) => w.length > 2 && !STOPWORDS.has(w));
}
function countTotalCriteria(frameworks, profiles) {
    let total = 0;
    for (const fw of frameworks) {
        total += profiles[fw]?.criteria.length ?? 0;
    }
    return total;
}
//# sourceMappingURL=compliance-checker.js.map