import * as fs from 'node:fs';
import * as path from 'node:path';
const TEST_PATTERNS = [
    /\.test\.\w+$/,
    /\.spec\.\w+$/,
    /_test\.\w+$/, // Go convention
    /Test\.java$/, // JUnit convention
];
const TEST_DIRS = new Set(['__tests__', 'test', 'tests', 'spec', 'specs']);
const SOURCE_EXTENSIONS = new Set([
    '.ts',
    '.tsx',
    '.js',
    '.jsx',
    '.mjs',
    '.cjs',
    '.py',
    '.go',
    '.java',
    '.kt',
    '.rs',
    '.rb',
    '.cs',
    '.cpp',
    '.c',
    '.swift',
]);
const IGNORE_DIRS = new Set([
    'node_modules',
    '.git',
    'dist',
    'build',
    'out',
    '.next',
    '.nuxt',
    'coverage',
    'vendor',
    '__pycache__',
]);
export function isTestFile(filePath) {
    const basename = path.basename(filePath);
    if (TEST_PATTERNS.some((p) => p.test(basename))) {
        return true;
    }
    const parts = filePath.split(path.sep);
    return parts.some((p) => TEST_DIRS.has(p));
}
function collectFiles(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 mapTestToSource(testFile, sourceFiles, rootDir) {
    const relative = path.relative(rootDir, testFile);
    // Strip test dir prefix (tests/, test/, __tests__/)
    const stripped = relative
        .replace(/^(?:tests?|__tests__|specs?)[/\\]/, '')
        .replace(/\.(?:test|spec)\./, '.')
        .replace(/_test\./, '.')
        .replace(/Test\.java$/, '.java');
    // Try exact match
    for (const src of sourceFiles) {
        const srcRel = path.relative(rootDir, src);
        if (srcRel === stripped) {
            return srcRel;
        }
    }
    // Try basename match
    const targetBase = path.basename(stripped, path.extname(stripped));
    for (const src of sourceFiles) {
        const srcBase = path.basename(src, path.extname(src));
        if (srcBase === targetBase) {
            return path.relative(rootDir, src);
        }
    }
    return undefined;
}
export function analyzeTestCoverage(rootDir) {
    const allFiles = collectFiles(rootDir);
    const testFiles = [];
    const sourceFiles = [];
    for (const f of allFiles) {
        if (isTestFile(f)) {
            testFiles.push(f);
        }
        else {
            sourceFiles.push(f);
        }
    }
    const testedSet = new Set();
    for (const tf of testFiles) {
        const mapped = mapTestToSource(tf, sourceFiles, rootDir);
        if (mapped) {
            testedSet.add(mapped);
        }
    }
    const testedModules = [...testedSet].sort();
    const untestedModules = sourceFiles
        .map((f) => path.relative(rootDir, f))
        .filter((f) => !testedSet.has(f))
        .sort();
    const testToSourceRatio = sourceFiles.length > 0 ? Math.round((testFiles.length / sourceFiles.length) * 100) / 100 : 0;
    return { testedModules, untestedModules, testToSourceRatio };
}
//# sourceMappingURL=test-analyzer.js.map