// engine/self-healing/strategies/coverage-fix.ts — SPEC-474: Coverage fix strategy
import { readFile, writeFile, mkdir, access } from 'node:fs/promises';
import { join, dirname, basename, relative } from 'node:path';
/**
 * Reads coverage/coverage-final.json and returns files with 0% statement coverage.
 */
async function findUncoveredFiles(projectPath) {
    const coveragePath = join(projectPath, 'coverage', 'coverage-final.json');
    let raw;
    try {
        raw = await readFile(coveragePath, 'utf-8');
    }
    catch {
        throw new Error(`coverage-final.json not found at ${coveragePath}. Run pnpm test:coverage first.`);
    }
    const data = JSON.parse(raw);
    const uncovered = [];
    for (const [absPath, fileCov] of Object.entries(data)) {
        const stmtHits = Object.values(fileCov.s);
        const totalStmts = stmtHits.length;
        if (totalStmts === 0) {
            continue;
        }
        const hitStmts = stmtHits.filter((n) => n > 0).length;
        if (hitStmts === 0) {
            const relPath = relative(projectPath, absPath);
            uncovered.push({ absolutePath: absPath, relPath, stmtCount: totalStmts });
        }
    }
    return uncovered;
}
/**
 * Derives the test file path from a source file path.
 * src/engine/foo.ts -> tests/engine/foo.test.ts
 */
function deriveTestPath(projectPath, relPath) {
    const withoutSrc = relPath.startsWith('src/') ? relPath.slice(4) : relPath;
    const dir = dirname(withoutSrc);
    const file = basename(withoutSrc, '.ts');
    const testFile = `${file}.test.ts`;
    const testDir = dir === '.' ? 'tests' : join('tests', dir);
    return join(projectPath, testDir, testFile);
}
/**
 * Generates a minimal vitest scaffold test file for an uncovered module.
 */
function generateScaffold(relPath) {
    const withoutSrc = relPath.startsWith('src/') ? relPath.slice(4) : relPath;
    const dir = dirname(withoutSrc);
    const depth = dir === '.' ? 1 : dir.split('/').length + 1;
    const prefix = Array.from({ length: depth }, () => '..').join('/');
    const importPath = `${prefix}/${relPath.replace(/\.ts$/, '.js')}`;
    const moduleName = basename(relPath, '.ts');
    return `// AUTO-GENERATED by coverage-fix strategy — SPEC-474
// This file was scaffolded because ${relPath} had 0% test coverage.
// Replace the placeholder tests with real assertions.

import { describe, it, expect } from 'vitest';

describe('${moduleName}', () => {
  it('should be importable without errors', async () => {
    const mod = await import('${importPath}');
    expect(mod).toBeDefined();
  });

  it('should export at least one member', async () => {
    const mod = await import('${importPath}');
    const exports = Object.keys(mod);
    expect(exports.length).toBeGreaterThan(0);
  });
});
`;
}
/**
 * Checks whether a file already exists on the filesystem.
 */
async function fileExists(path) {
    try {
        await access(path);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Scaffolds a single test file for an uncovered source file.
 * Returns the relative test path on success, or null if skipped/failed.
 */
async function scaffoldTestFile(projectPath, file) {
    const testPath = deriveTestPath(projectPath, file.relPath);
    if (await fileExists(testPath)) {
        return { created: null, skipReason: `${file.relPath}: test file already exists` };
    }
    const scaffold = generateScaffold(file.relPath);
    const testDir = dirname(testPath);
    try {
        await mkdir(testDir, { recursive: true });
        await writeFile(testPath, scaffold, 'utf-8');
        return { created: relative(projectPath, testPath), skipReason: null };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { created: null, skipReason: `${file.relPath}: failed to write scaffold — ${msg}` };
    }
}
/**
 * Identifies source files with 0% coverage and generates scaffold test files for each.
 */
export async function applyCoverageFix(projectPath, _details) {
    let uncovered;
    try {
        uncovered = await findUncoveredFiles(projectPath);
    }
    catch (err) {
        return { fixed: false, reason: err instanceof Error ? err.message : String(err) };
    }
    // Only scaffold for src/ files, skip barrel index.ts files
    const srcFiles = uncovered.filter((f) => f.relPath.startsWith('src/') && !f.relPath.includes('index.ts'));
    if (srcFiles.length === 0) {
        return {
            fixed: false,
            reason: 'No uncovered src/ files found in coverage report (index.ts files are excluded)',
        };
    }
    const created = [];
    const skipped = [];
    for (const file of srcFiles) {
        const { created: createdPath, skipReason } = await scaffoldTestFile(projectPath, file);
        if (createdPath !== null) {
            created.push(createdPath);
        }
        else if (skipReason !== null) {
            skipped.push(skipReason);
        }
    }
    if (created.length === 0) {
        return {
            fixed: false,
            reason: `Found ${srcFiles.length.toString()} uncovered file(s) but could not scaffold any (${skipped.length.toString()} skipped). Manual tests required.`,
        };
    }
    const allDone = skipped.length === 0;
    return {
        fixed: allDone,
        reason: allDone
            ? `Scaffolded ${created.length.toString()} test file(s) for previously uncovered modules`
            : `Scaffolded ${created.length.toString()} of ${srcFiles.length.toString()} uncovered file(s); ${skipped.length.toString()} skipped`,
        fixedIssues: created.map((f) => `Created ${f}`),
    };
}
//# sourceMappingURL=coverage-fix.js.map