// engine/workers/handlers/test-gaps.ts — Detects files without tests (SPEC-078 AC-05)
import { readdir, stat } from 'node:fs/promises';
import { join, extname, basename, dirname, relative } from 'node:path';
const CONVENTIONS = {
    typescript: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
        findTestPaths: (srcFile, projectDir) => {
            const rel = relative(projectDir, srcFile);
            const base = basename(rel, extname(rel));
            const dir = dirname(rel);
            return [
                join(projectDir, 'tests', dir, `${base}.test${extname(rel)}`),
                join(projectDir, 'tests', `${base}.test${extname(rel)}`),
                join(projectDir, '__tests__', dir, `${base}.test${extname(rel)}`),
                join(projectDir, dir, `${base}.spec${extname(rel)}`),
                join(projectDir, dir, `${base}.test${extname(rel)}`),
            ];
        },
    },
    python: {
        extensions: ['.py'],
        findTestPaths: (srcFile, projectDir) => {
            const rel = relative(projectDir, srcFile);
            const base = basename(rel, '.py');
            const dir = dirname(rel);
            return [
                join(projectDir, 'tests', dir, `test_${base}.py`),
                join(projectDir, 'tests', `test_${base}.py`),
                join(projectDir, 'tests', dir, `${base}_test.py`),
                join(projectDir, 'tests', `${base}_test.py`),
            ];
        },
    },
    go: {
        extensions: ['.go'],
        findTestPaths: (srcFile) => {
            const base = basename(srcFile, '.go');
            const dir = dirname(srcFile);
            return [join(dir, `${base}_test.go`)];
        },
    },
    rust: {
        extensions: ['.rs'],
        findTestPaths: (srcFile, projectDir) => {
            const base = basename(srcFile, '.rs');
            const dir = dirname(srcFile);
            return [join(dir, `${base}_test.rs`), join(projectDir, 'tests', `${base}.rs`)];
        },
    },
    java: {
        extensions: ['.java', '.kt'],
        findTestPaths: (srcFile) => {
            const base = basename(srcFile, extname(srcFile));
            const dir = dirname(srcFile).replace(/src\/main/, 'src/test');
            return [
                join(dir, `${base}Test${extname(srcFile)}`),
                join(dir, `${base}Tests${extname(srcFile)}`),
            ];
        },
    },
};
// ---------------------------------------------------------------------------
// Ignore directories
// ---------------------------------------------------------------------------
const IGNORE_DIRS = new Set([
    'node_modules',
    '.git',
    'dist',
    'build',
    '__pycache__',
    'target',
    '.venv',
    'vendor',
    '.next',
    '.nuxt',
    'coverage',
    '.turbo',
    '.cache',
]);
// ---------------------------------------------------------------------------
// File collection
// ---------------------------------------------------------------------------
async function collectSourceFiles(dir, files) {
    let entries;
    try {
        entries = await readdir(dir);
    }
    catch {
        return;
    }
    for (const entry of entries) {
        if (IGNORE_DIRS.has(entry)) {
            continue;
        }
        const fullPath = join(dir, entry);
        let st;
        try {
            st = await stat(fullPath);
        }
        catch {
            continue;
        }
        if (st.isDirectory()) {
            await collectSourceFiles(fullPath, files);
        }
        else if (st.isFile()) {
            files.push(fullPath);
        }
    }
}
function isTestFile(filePath) {
    const base = basename(filePath);
    return (base.includes('.test.') ||
        base.includes('.spec.') ||
        base.includes('_test.') ||
        base.startsWith('test_') ||
        base.endsWith('_test.go') ||
        base.endsWith('Test.java') ||
        base.endsWith('Test.kt'));
}
function getConvention(ext) {
    for (const conv of Object.values(CONVENTIONS)) {
        if (conv.extensions.includes(ext)) {
            return conv;
        }
    }
    return undefined;
}
// ---------------------------------------------------------------------------
// Public handler
// ---------------------------------------------------------------------------
export async function runTestGaps(projectDir, _changedFiles) {
    const startedAt = new Date().toISOString();
    const start = Date.now();
    const findings = [];
    const allFiles = [];
    await collectSourceFiles(projectDir, allFiles);
    const sourceFiles = allFiles.filter((f) => {
        const ext = extname(f);
        const conv = getConvention(ext);
        return conv !== undefined && !isTestFile(f);
    });
    const testFileSet = new Set(allFiles.filter(isTestFile).map((f) => f.toLowerCase()));
    let filesWithTests = 0;
    for (const srcFile of sourceFiles) {
        const ext = extname(srcFile);
        const conv = getConvention(ext);
        if (!conv) {
            continue;
        }
        const testPaths = conv.findTestPaths(srcFile, projectDir);
        const hasTest = testPaths.some((tp) => testFileSet.has(tp.toLowerCase()));
        if (hasTest) {
            filesWithTests++;
        }
        else {
            findings.push({
                severity: 'warning',
                file: relative(projectDir, srcFile),
                message: `No test file found for ${relative(projectDir, srcFile)}`,
                suggestion: `Create a test file at: ${relative(projectDir, testPaths[0] ?? srcFile)}`,
                category: 'test-gaps',
            });
        }
    }
    const total = sourceFiles.length;
    const coveragePct = total > 0 ? Math.round((filesWithTests / total) * 100) : 100;
    if (total > 0) {
        findings.unshift({
            severity: 'info',
            file: '',
            message: `Test coverage by file count: ${String(filesWithTests)}/${String(total)} (${String(coveragePct)}%)`,
            category: 'test-gaps',
        });
    }
    return {
        workerName: 'test-gaps',
        startedAt,
        completedAt: new Date().toISOString(),
        durationMs: Date.now() - start,
        status: 'success',
        findings,
        analyzedFiles: total,
    };
}
//# sourceMappingURL=test-gaps.js.map