import * as fs from 'node:fs';
import * as path from 'node:path';
const SOURCE_EXTENSIONS = new Set([
    '.ts',
    '.tsx',
    '.js',
    '.jsx',
    '.mjs',
    '.cjs',
    '.py',
    '.go',
    '.java',
    '.kt',
    '.rs',
    '.rb',
]);
const IGNORE_DIRS = new Set([
    'node_modules',
    '.git',
    'dist',
    'build',
    'out',
    '.next',
    '.nuxt',
    'coverage',
    'vendor',
    '__pycache__',
]);
// Patterns that increase cyclomatic complexity
const BRANCH_PATTERNS = [
    /\bif\s*\(/g,
    /\belse\s+if\s*\(/g,
    /\belse\s*\{/g,
    /\bswitch\s*\(/g,
    /\bcase\s+/g,
    /\bfor\s*\(/g,
    /\bfor\s+\w+/g, // Go/Python for
    /\bwhile\s*\(/g,
    /\bcatch\s*\(/g,
    /\?\?/g, // nullish coalescing
    /&&/g,
    /\|\|/g,
    /\?\s*[^:?]/g, // ternary (approximate)
];
// Function declaration patterns
const FUNCTION_PATTERNS = [
    // JS/TS: function name(, async function name(, export function
    /(?:export\s+)?(?:async\s+)?function\s+\w+\s*\(/g,
    // Arrow functions assigned: const name = (...) =>
    /(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g,
    // Method definitions: name( or async name(
    /^\s+(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?\w+\s*\([^)]*\)\s*(?::\s*\w[^{]*)?{/gm,
    // Python: def name(
    /\bdef\s+\w+\s*\(/g,
    // Go: func name(
    /\bfunc\s+(?:\([^)]*\)\s*)?\w+\s*\(/g,
    // Java: public/private/protected ... name(
    /(?:public|private|protected)\s+(?:static\s+)?[\w<>[\]]+\s+\w+\s*\(/g,
];
function collectSourceFiles(dir) {
    const results = [];
    const queue = [dir];
    while (queue.length > 0) {
        const current = queue.pop();
        if (!current) {
            continue;
        }
        let entries;
        try {
            entries = fs.readdirSync(current, { withFileTypes: true });
        }
        catch {
            continue;
        }
        for (const entry of entries) {
            if (IGNORE_DIRS.has(entry.name)) {
                continue;
            }
            const full = path.join(current, entry.name);
            if (entry.isDirectory()) {
                queue.push(full);
            }
            else if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
                results.push(full);
            }
        }
    }
    return results;
}
function countBranches(content) {
    let count = 0;
    for (const pattern of BRANCH_PATTERNS) {
        const regex = new RegExp(pattern.source, pattern.flags);
        while (regex.exec(content) !== null) {
            count++;
        }
    }
    return count + 1; // base complexity of 1
}
function extractFunctions(content) {
    const lines = content.split('\n');
    const functions = [];
    for (const pattern of FUNCTION_PATTERNS) {
        const regex = new RegExp(pattern.source, pattern.flags);
        let match;
        while ((match = regex.exec(content)) !== null) {
            const startOffset = match.index;
            const startLine = content.substring(0, startOffset).split('\n').length;
            const name = extractFunctionName(match[0]);
            // Estimate function end by counting braces
            const endLine = findFunctionEnd(lines, startLine - 1);
            const funcLines = endLine - startLine + 1;
            const funcContent = lines.slice(startLine - 1, endLine).join('\n');
            const complexity = countBranches(funcContent);
            functions.push({
                name,
                startLine,
                endLine,
                lines: funcLines,
                complexity,
            });
        }
    }
    // Deduplicate overlapping detections (keep longest)
    return deduplicateFunctions(functions);
}
function extractFunctionName(declaration) {
    const patterns = [
        /function\s+(\w+)/,
        /(?:const|let|var)\s+(\w+)/,
        /def\s+(\w+)/,
        /func\s+(?:\([^)]*\)\s*)?(\w+)/,
        /\s(\w+)\s*\(/,
    ];
    for (const p of patterns) {
        const m = declaration.match(p);
        if (m?.[1]) {
            return m[1];
        }
    }
    return 'anonymous';
}
function findFunctionEnd(lines, startIdx) {
    let braceCount = 0;
    let foundOpen = false;
    for (let i = startIdx; i < lines.length; i++) {
        const line = lines[i] ?? '';
        for (const ch of line) {
            if (ch === '{') {
                braceCount++;
                foundOpen = true;
            }
            else if (ch === '}') {
                braceCount--;
            }
        }
        if (foundOpen && braceCount <= 0) {
            return i + 1;
        }
    }
    // For Python/other non-brace languages, estimate ~20 lines
    return Math.min(startIdx + 20, lines.length);
}
function deduplicateFunctions(functions) {
    if (functions.length === 0) {
        return [];
    }
    // Sort by start line
    const sorted = [...functions].sort((a, b) => a.startLine - b.startLine);
    const first = sorted[0];
    if (!first) {
        return [];
    }
    const result = [first];
    for (let i = 1; i < sorted.length; i++) {
        const prev = result[result.length - 1];
        const curr = sorted[i];
        if (!prev || !curr) {
            continue;
        }
        // If overlapping, keep the one with more lines
        if (curr.startLine <= prev.endLine) {
            if (curr.lines > prev.lines) {
                result[result.length - 1] = curr;
            }
        }
        else {
            result.push(curr);
        }
    }
    return result;
}
export function analyzeComplexity(rootDir) {
    const files = collectSourceFiles(rootDir);
    const metrics = [];
    for (const file of files) {
        let content;
        try {
            content = fs.readFileSync(file, 'utf-8');
        }
        catch {
            continue;
        }
        const lines = content.split('\n');
        const lineCount = lines.length;
        const functions = extractFunctions(content);
        const functionCount = functions.length;
        const maxFunctionLength = functions.reduce((max, f) => Math.max(max, f.lines), 0);
        const avgComplexity = functionCount > 0
            ? Math.round((functions.reduce((sum, f) => sum + f.complexity, 0) / functionCount) * 100) /
                100
            : 0;
        // Flag hotspots: functions > 80 lines or > 15 complexity
        const hotspots = functions
            .filter((f) => f.lines > 80 || f.complexity > 15)
            .map((f) => ({ name: f.name, complexity: f.complexity, lines: f.lines }))
            .sort((a, b) => b.complexity - a.complexity);
        metrics.push({
            file: path.relative(rootDir, file),
            lineCount,
            functionCount,
            maxFunctionLength,
            avgComplexity,
            hotspots,
        });
    }
    // Return top 10 by complexity
    return metrics.sort((a, b) => b.avgComplexity - a.avgComplexity).slice(0, 10);
}
//# sourceMappingURL=complexity-analyzer.js.map