// engine/doc-compliance-checker.ts — SPEC-385
// Checks a spec against official documentation for anti-patterns and missing best practices.
import { readFile } from 'node:fs/promises';
import { detectTechsFromText } from './web-fetcher/stack-detector.js';
import { fetchDocsBatch } from './web-fetcher/docs-intelligence.js';
/**
 * Checks if any anti-pattern from official docs appears in spec content.
 * Returns a DocComplianceIssue for each match found.
 */
export function detectAntiPatterns(tech, docsResult, specContent) {
    const issues = [];
    const lower = specContent.toLowerCase();
    for (const ap of docsResult.antiPatterns) {
        // Extract key terms from the anti-pattern description (first 5 words)
        const keyTerms = ap
            .toLowerCase()
            .replace(/[^a-z0-9\s]/g, ' ')
            .split(/\s+/)
            .filter((w) => w.length > 3)
            .slice(0, 5);
        const matchCount = keyTerms.filter((term) => lower.includes(term)).length;
        if (matchCount >= 2) {
            issues.push({
                tech,
                issueType: 'anti-pattern',
                description: ap,
                source: docsResult.docsUrl,
                severity: 'high',
            });
        }
    }
    return issues;
}
/**
 * Identifies best practices from official docs that are NOT covered in the spec.
 * Returns a list of missing best practice descriptions.
 */
export function detectMissingBestPractices(tech, docsResult, specContent) {
    const issues = [];
    const lower = specContent.toLowerCase();
    for (const bp of docsResult.bestPractices.slice(0, 5)) {
        const keyTerms = bp
            .toLowerCase()
            .replace(/[^a-z0-9\s]/g, ' ')
            .split(/\s+/)
            .filter((w) => w.length > 3)
            .slice(0, 5);
        if (keyTerms.length === 0) {
            continue;
        }
        const matchCount = keyTerms.filter((term) => lower.includes(term)).length;
        // If fewer than 1/3 of key terms appear, best practice is likely missing
        if (matchCount < Math.max(1, Math.ceil(keyTerms.length / 3))) {
            issues.push({
                tech,
                issueType: 'missing-best-practice',
                description: `Missing: ${bp}`,
                source: docsResult.docsUrl,
                severity: 'medium',
            });
        }
    }
    return issues;
}
/**
 * Calculates a compliance score 0-100 based on issues found.
 * High severity issues cost more points.
 */
export function calculateComplianceScore(issues, totalChecks) {
    if (totalChecks === 0) {
        return 100;
    }
    const penalty = issues.reduce((sum, issue) => {
        return sum + (issue.severity === 'high' ? 15 : issue.severity === 'medium' ? 8 : 3);
    }, 0);
    return Math.max(0, Math.min(100, 100 - penalty));
}
/**
 * Extracts spec title from frontmatter or first heading.
 */
function extractSpecTitle(specContent, fallback) {
    const titleMatch = /^title:\s*"?([^"\n]+)"?/m.exec(specContent) ??
        /^#\s+(?:SPEC-\d+[:\s]+)?(.+)/m.exec(specContent);
    return titleMatch?.[1]?.trim() ?? fallback;
}
/**
 * Main entry point: checks a spec file against official documentation.
 * Reads the spec file, detects techs, fetches docs, and checks for issues.
 */
export async function checkDocCompliance(specPath, specId) {
    // Read spec content
    let specContent;
    let specTitle;
    try {
        specContent = await readFile(specPath, 'utf-8');
        specTitle = extractSpecTitle(specContent, specId);
    }
    catch {
        // fallback to specId as search text
        specContent = specId;
        specTitle = specId;
    }
    // Detect technologies
    const detected = detectTechsFromText(specContent);
    const techs = detected
        .filter((t) => t.confidence === 'high' || t.confidence === 'medium')
        .map((t) => t.name)
        .slice(0, 6);
    if (techs.length === 0) {
        return {
            specId,
            specTitle,
            checkedAt: new Date().toISOString(),
            techs: [],
            issues: [],
            bestPracticesCovered: [],
            complianceScore: 100,
            summary: 'No technologies detected in spec — skipping doc compliance check.',
        };
    }
    // Fetch docs for detected techs
    const docsMap = await fetchDocsBatch(techs, { timeout: 5_000, concurrency: 3 });
    // Run checks
    const allIssues = [];
    const bestPracticesCovered = [];
    let totalChecks = 0;
    for (const [tech, docsResult] of docsMap) {
        if (docsResult.source === 'fallback') {
            continue;
        }
        const antiPatternIssues = detectAntiPatterns(tech, docsResult, specContent);
        const missingBpIssues = detectMissingBestPractices(tech, docsResult, specContent);
        allIssues.push(...antiPatternIssues);
        allIssues.push(...missingBpIssues);
        totalChecks += docsResult.antiPatterns.length + docsResult.bestPractices.slice(0, 5).length;
        // Track covered best practices
        const lower = specContent.toLowerCase();
        for (const bp of docsResult.bestPractices.slice(0, 5)) {
            const keyTerms = bp
                .toLowerCase()
                .replace(/[^a-z0-9\s]/g, ' ')
                .split(/\s+/)
                .filter((w) => w.length > 3)
                .slice(0, 5);
            const matchCount = keyTerms.filter((term) => lower.includes(term)).length;
            if (matchCount >= Math.max(1, Math.ceil(keyTerms.length / 3))) {
                bestPracticesCovered.push(`[${tech}] ${bp.slice(0, 80)}`);
            }
        }
    }
    const complianceScore = calculateComplianceScore(allIssues, totalChecks);
    const highIssues = allIssues.filter((i) => i.severity === 'high').length;
    const mediumIssues = allIssues.filter((i) => i.severity === 'medium').length;
    const summary = allIssues.length === 0
        ? `Spec is fully compliant with official docs for: ${techs.join(', ')}.`
        : `Found ${highIssues} high + ${mediumIssues} medium issues against official docs for: ${techs.join(', ')}.`;
    return {
        specId,
        specTitle,
        checkedAt: new Date().toISOString(),
        techs,
        issues: allIssues,
        bestPracticesCovered,
        complianceScore,
        summary,
    };
}
//# sourceMappingURL=doc-compliance-checker.js.map