// Planu — Validator: code analyzer
// Scans the codebase to check whether spec criteria are satisfied.
import { readFile, stat } from 'node:fs/promises';
import { join, extname } from 'node:path';
import { glob } from 'glob';
/**
 * Scan the project directory to build a CodeState for the given spec.
 */
export async function scanCodeForSpec(spec, projectPath) {
    const affectedFiles = [];
    const unexpectedFiles = [];
    const fileContents = new Map();
    // Use impact analysis if available
    if (spec.impactAnalysis?.affectedFiles) {
        for (const file of spec.impactAnalysis.affectedFiles) {
            const fullPath = join(projectPath, file);
            try {
                const content = await readFile(fullPath, 'utf-8');
                affectedFiles.push(file);
                fileContents.set(file, content);
            }
            catch {
                // File doesn't exist — might be expected if not yet created
            }
        }
    }
    // Also scan by git branch name pattern if available
    if (spec.gitBranch) {
        // In a real implementation, we'd use git diff to find changed files
        // For now, we rely on impactAnalysis
    }
    // Scan for files matching spec slug
    try {
        const slugPattern = spec.slug.replace(/-/g, '[-_]');
        const matchingFiles = await glob(`**/*${slugPattern}*`, {
            cwd: projectPath,
            nodir: true,
            ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
            maxDepth: 6,
        });
        for (const file of matchingFiles) {
            if (!affectedFiles.includes(file)) {
                unexpectedFiles.push(file);
            }
        }
    }
    catch {
        // Glob error
    }
    return { affectedFiles, unexpectedFiles, fileContents };
}
/**
 * Check whether a single criterion is satisfied in the given code state.
 */
export async function checkCriterion(criterion, projectPath, codeState) {
    // Check for file existence criteria
    const fileMatch = /(?:create|add|implement)\s+(?:file\s+)?["']?([^\s"']+\.\w+)["']?/i.exec(criterion);
    if (fileMatch?.[1]) {
        try {
            await stat(join(projectPath, fileMatch[1]));
            return true;
        }
        catch {
            return false;
        }
    }
    // Check for component/function existence in code
    const componentMatch = /(?:component|function|class|module|service)\s+["']?(\w+)["']?/i.exec(criterion);
    if (componentMatch?.[1]) {
        const name = componentMatch[1];
        for (const [, content] of codeState.fileContents) {
            if (content.includes(name)) {
                return true;
            }
        }
        // Also do a broader search
        try {
            const files = await glob(`**/*.{ts,tsx,js,jsx,py,go,rs,java,kt,rb,php}`, {
                cwd: projectPath,
                nodir: true,
                ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
                maxDepth: 5,
            });
            for (const file of files.slice(0, 50)) {
                // Limit search
                try {
                    const content = await readFile(join(projectPath, file), 'utf-8');
                    if (content.includes(name)) {
                        return true;
                    }
                }
                catch {
                    // Skip unreadable files
                }
            }
        }
        catch {
            // Glob error
        }
        return false;
    }
    // Check for API endpoint criteria
    const endpointMatch = /(?:endpoint|route|api)\s+(?:for\s+)?["']?(\w+)["']?/i.exec(criterion);
    if (endpointMatch?.[1]) {
        for (const [, content] of codeState.fileContents) {
            if (content.toLowerCase().includes(endpointMatch[1].toLowerCase())) {
                return true;
            }
        }
    }
    // If we have affected files, assume basic criteria are met
    if (codeState.affectedFiles.length > 0) {
        return true; // Optimistic: if code exists, assume criterion may be met
    }
    return false;
}
/**
 * Classify the severity of a missing criterion based on its content.
 */
export function classifyDriftSeverity(criterion) {
    const lower = criterion.toLowerCase();
    if (lower.includes('security') || lower.includes('auth') || lower.includes('permission')) {
        return 'critical';
    }
    if (lower.includes('error') || lower.includes('validation') || lower.includes('test')) {
        return 'high';
    }
    if (lower.includes('performance') || lower.includes('cache') || lower.includes('optimize')) {
        return 'medium';
    }
    return 'low';
}
/**
 * Run lightweight quality checks on a single file and return any findings.
 */
export async function quickQualityCheck(file, projectPath) {
    const findings = [];
    const ext = extname(file);
    const codeExts = [
        '.ts',
        '.tsx',
        '.js',
        '.jsx',
        '.py',
        '.go',
        '.rs',
        '.java',
        '.kt',
        '.rb',
        '.php',
    ];
    if (!codeExts.includes(ext)) {
        return findings;
    }
    try {
        const content = await readFile(join(projectPath, file), 'utf-8');
        const lines = content.split('\n');
        // Check file length
        if (lines.length > 500) {
            findings.push({
                file,
                category: 'clean-code',
                severity: 'warning',
                rule: 'file-length',
                message: `File has ${lines.length} lines. Consider splitting into smaller modules.`,
                suggestion: 'Extract related functionality into separate files/modules.',
            });
        }
        // Check for TODO/FIXME/HACK
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            if (line !== undefined && /\b(TODO|FIXME|HACK|XXX)\b/.exec(line)) {
                findings.push({
                    file,
                    line: i + 1,
                    category: 'clean-code',
                    severity: 'info',
                    rule: 'pending-work',
                    message: `Found TODO/FIXME/HACK marker at line ${i + 1}`,
                    suggestion: 'Address the pending work or create a spec for it.',
                });
            }
        }
        // Check for console.log / print statements in code
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            if (line !== undefined &&
                /console\.(log|debug|info)\(/.exec(line) &&
                !file.includes('test') &&
                !file.includes('spec')) {
                findings.push({
                    file,
                    line: i + 1,
                    category: 'clean-code',
                    severity: 'warning',
                    rule: 'no-console',
                    message: `Console statement found at line ${i + 1}`,
                    suggestion: 'Use a proper logger instead of console statements.',
                });
            }
        }
    }
    catch {
        // Can't read file
    }
    return findings;
}
/**
 * Check if scope and difficulty rating are consistent with each other.
 */
export function validateScopeDifficulty(scope, difficulty) {
    const scopeDiffRange = {
        trivial: [1, 2],
        feature: [1, 3],
        'cross-module': [2, 4],
        architectural: [3, 5],
    };
    const range = scopeDiffRange[scope];
    if (!range) {
        return true;
    }
    return difficulty >= range[0] && difficulty <= range[1];
}
//# sourceMappingURL=analyzer.js.map