// src/engine/spec-obesity-healer.ts — SPEC-971: heal bloated specs
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { dirname, join, basename } from 'path';
import { globSync } from 'glob';
const DEFAULT_THRESHOLD = 500;
const DEFAULT_CUTOFF = '2026-04-01';
// Markers for auto-generated / generic criteria that can be safely removed
const GENERIC_MARKERS = [
    /\[REQUIRED\]/i,
    /\[RECOMMENDED\]/i,
    /\[OPTIONAL\]/i,
    /OWASP\s*\[?A\d{2}/i,
    /\[A\d{2}[-\w]*\]/i, // Matches [A01], [A01-broken-access-control], etc.
    /GDPR/i,
    /HIPAA/i,
    /PCI-DSS/i,
    /SOC2/i,
    /Core Web Vitals/i,
    /LCP\s*\(/i,
    /INP\s*\(/i,
    /CLS\s*\(/i,
    /P95\s+latency/i,
    /P99\s+latency/i,
    /throughput/i,
    /availability target/i,
    /graceful degradation/i,
    /distributed tracing/i,
    /alerts configured/i,
    /mutation score/i,
    /factory_boy/i,
    /fishery\/factory/i,
    /React\.lazy/i,
    /defineAsyncComponent/i,
    /lazy-loaded/i,
    /bundle.*gzipped/i,
    /CSS total/i,
    /golden path/i,
    /self-service/i,
    /developer portal/i,
    /platform team/i,
    /quality-principles\.json/i,
    /NVD MENU/i,
    /National Vulnerability Database/i,
    /cutting-edge backend architectures/i,
    /Understanding these TypeScript antipatterns/i,
    /supply chain.*trustPolicy/i,
    /pnpm also supports/i,
    /Information Technology Laboratory/i,
];
const HTML_ARTIFACT_PATTERNS = [/<\/?[a-zA-Z][^>]*>/, /&[a-zA-Z]+;/, /\{\{[^}]+\}\}/];
const SCRAPE_MARKERS = [/^\[REQUIRED\]/i, /^\[RECOMMENDED\]/i, /^\[OPTIONAL\]/i];
function isGenericCriteria(text) {
    return GENERIC_MARKERS.some((pattern) => pattern.test(text));
}
function isHtmlArtifact(text) {
    return HTML_ARTIFACT_PATTERNS.some((pattern) => pattern.test(text));
}
function isScrapeMarker(text) {
    return SCRAPE_MARKERS.some((pattern) => pattern.test(text));
}
function extractCriteriaText(line) {
    const match = /- text:\s*"(.*)"/.exec(line);
    return match?.[1] ?? line;
}
function shouldRemoveCriteria(text) {
    if (isScrapeMarker(text)) {
        return { remove: true, reason: 'scrape-marker' };
    }
    if (isGenericCriteria(text)) {
        return { remove: true, reason: 'generic-criteria' };
    }
    if (isHtmlArtifact(text)) {
        return { remove: true, reason: 'html-artifact' };
    }
    return { remove: false, reason: null };
}
function generateBackupPath(specPath) {
    const dir = dirname(specPath);
    const base = basename(specPath, '.md');
    const date = new Date().toISOString().split('T')[0];
    return join(dir, '..', '..', 'backups', `${base}-${date}.md`);
}
function healSpec(specPath, threshold, cutoffDate, apply) {
    const content = readFileSync(specPath, 'utf-8');
    const lines = content.split('\n');
    const originalLines = lines.length;
    if (originalLines <= threshold) {
        return null;
    }
    // Anti-loop guard: check created date in frontmatter
    const createdMatch = /created:\s*(\d{4}-\d{2}-\d{2})/.exec(content);
    if (createdMatch?.[1] && createdMatch[1] >= cutoffDate) {
        return null;
    } // Already lean format
    // parseSpec not needed for line-based healing
    const bloatSources = [];
    const removedExamples = {
        'generic-criteria': [],
        'html-artifact': [],
        'scrape-marker': [],
        'duplicate-checklist': [],
    };
    let criteriaRemoved = 0;
    const keptBodyLines = [];
    let inCriteria = false;
    for (const line of lines) {
        // Detect criteria section
        if (line.trim() === 'criteria:') {
            inCriteria = true;
            keptBodyLines.push(line);
            continue;
        }
        if (inCriteria && !line.trim().startsWith('- ') && line.trim() !== '') {
            inCriteria = false;
        }
        if (inCriteria && line.trim().startsWith('- text:')) {
            const text = extractCriteriaText(line);
            const decision = shouldRemoveCriteria(text);
            if (decision.remove && decision.reason) {
                criteriaRemoved++;
                if (removedExamples[decision.reason].length < 3) {
                    removedExamples[decision.reason].push(text.substring(0, 120));
                }
                continue; // Skip this line
            }
        }
        // Remove HTML artifacts from non-criteria lines too
        if (isHtmlArtifact(line)) {
            criteriaRemoved++;
            if (removedExamples['html-artifact'].length < 3) {
                removedExamples['html-artifact'].push(line.substring(0, 120));
            }
            continue;
        }
        keptBodyLines.push(line);
    }
    // Build bloat sources report
    for (const [type, examples] of Object.entries(removedExamples)) {
        if (examples.length > 0) {
            bloatSources.push({
                type: type,
                count: examples.length,
                examples,
            });
        }
    }
    const healedContent = keptBodyLines.join('\n');
    const healedLines = keptBodyLines.length;
    const linesSaved = originalLines - healedLines;
    if (linesSaved <= 0 && criteriaRemoved <= 0) {
        return null;
    }
    // Generate backup
    const backupPath = generateBackupPath(specPath);
    if (apply) {
        mkdirSync(dirname(backupPath), { recursive: true });
        writeFileSync(backupPath, content, 'utf-8');
        writeFileSync(specPath, healedContent, 'utf-8');
    }
    // Extract specId from path or content
    const specIdMatch = /id:\s*(SPEC-\d+)/.exec(content);
    const specId = specIdMatch?.[1] ?? basename(specPath, '.md');
    return {
        specId,
        specPath,
        originalLines,
        healedLines,
        linesSaved,
        criteriaRemoved,
        bloatSources,
        backupPath: apply ? backupPath : undefined,
        applied: apply,
    };
}
// eslint-disable-next-line @typescript-eslint/require-await -- Must return Promise<ToolResult> for caller compatibility
export async function runSpecObesityHealer(input) {
    const { projectPath, threshold = DEFAULT_THRESHOLD, apply = false } = input;
    const cutoffDate = input.cutoffDate ?? DEFAULT_CUTOFF;
    const specsDir = join(projectPath, 'planu', 'specs');
    if (!existsSync(specsDir)) {
        return {
            scannedSpecs: 0,
            obeseSpecs: 0,
            healedSpecs: 0,
            totalLinesSaved: 0,
            totalCriteriaRemoved: 0,
            specs: [],
            dryRun: !apply,
        };
    }
    const specFiles = globSync('**/spec.md', { cwd: specsDir, absolute: true });
    const healedSpecs = [];
    for (const specPath of specFiles) {
        try {
            const result = healSpec(specPath, threshold, cutoffDate, apply);
            if (result) {
                healedSpecs.push(result);
            }
        }
        catch {
            // Skip unreadable specs
            continue;
        }
    }
    return {
        scannedSpecs: specFiles.length,
        obeseSpecs: healedSpecs.length,
        healedSpecs: healedSpecs.length,
        totalLinesSaved: healedSpecs.reduce((sum, s) => sum + s.linesSaved, 0),
        totalCriteriaRemoved: healedSpecs.reduce((sum, s) => sum + s.criteriaRemoved, 0),
        specs: healedSpecs,
        dryRun: !apply,
    };
}
//# sourceMappingURL=spec-obesity-healer.js.map