// engine/config-health/ts-checker.ts — SPEC-042
// TypeScript/JavaScript configuration health checks.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
import healthSchemas from '../../config/config-health-schemas.json' with { type: 'json' };
export { checkPackageScripts, checkHuskyScripts } from './ts-checker-scripts.js';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function readFileSafe(filePath) {
    try {
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return '';
    }
}
async function fileExists(filePath) {
    const content = await readFileSafe(filePath);
    return content !== '';
}
// ---------------------------------------------------------------------------
// Check: TS files not covered by tsconfig include patterns
// ---------------------------------------------------------------------------
/**
 * Detect .ts files in the project root that are not covered by tsconfig.json
 * include patterns. This catches config files (vitest.config.ts, eslint.config.ts)
 * that developers forget to include.
 */
export async function checkTsCoverage(projectPath) {
    const findings = [];
    const tsconfigPath = join(projectPath, 'tsconfig.json');
    const tsconfigContent = await readFileSafe(tsconfigPath);
    if (!tsconfigContent) {
        return findings;
    }
    let tsconfig;
    try {
        tsconfig = JSON.parse(tsconfigContent);
    }
    catch {
        findings.push({
            severity: 'error',
            checker: 'ts-coverage',
            file: 'tsconfig.json',
            message: 'tsconfig.json is not valid JSON — TypeScript compiler cannot parse it',
            fix: 'Run `tsc --noEmit` to see the parse error and fix it',
        });
        return findings;
    }
    // If no include array is specified, TypeScript includes everything by default
    if (!tsconfig.include) {
        return findings;
    }
    // Find .ts files in the project root (not node_modules, not dist)
    let rootTsFiles;
    try {
        rootTsFiles = await glob('*.ts', {
            cwd: projectPath,
            nodir: true,
            ignore: ['node_modules/**', 'dist/**'],
        });
    }
    catch {
        return findings;
    }
    const includePatterns = tsconfig.include;
    const excludePatterns = tsconfig.exclude ?? ['node_modules', 'dist'];
    for (const tsFile of rootTsFiles) {
        const isExcluded = excludePatterns.some((p) => tsFile === p || tsFile.startsWith(p.replace('/**', '/')));
        if (isExcluded) {
            continue;
        }
        // Check if the file matches any include pattern
        const isCovered = includePatterns.some((pattern) => {
            // Handle glob patterns like "src/**/*" or "src"
            if (pattern === tsFile) {
                return true;
            }
            if (pattern.endsWith('/**/*') || pattern.endsWith('/**')) {
                return false;
            }
            // Pattern like "src/**" does not match root-level files
            if (pattern.includes('/')) {
                return false;
            }
            // Pattern like "**/*" or "**" would match all
            if (pattern === '**/*' || pattern === '**') {
                return true;
            }
            return false;
        });
        if (!isCovered) {
            findings.push({
                severity: 'warning',
                checker: 'ts-coverage',
                file: tsFile,
                message: `'${tsFile}' is not covered by tsconfig.json include patterns — IDE may show errors`,
                fix: `Add '${tsFile}' or a pattern like '*.ts' to the 'include' array in tsconfig.json`,
            });
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Check: Invalid options in vitest.config.ts
// ---------------------------------------------------------------------------
/**
 * Detect known-invalid options in vitest.config.ts. These are options that
 * were removed or moved in vitest v4+ (e.g., minWorkers, maxWorkers are now
 * top-level, not under poolOptions.threads).
 */
export async function checkVitestSchema(projectPath) {
    const findings = [];
    // Check if vitest is installed
    const vitestPath = join(projectPath, 'node_modules', 'vitest');
    const isInstalled = await fileExists(join(vitestPath, 'package.json'));
    if (!isInstalled) {
        findings.push({
            severity: 'info',
            checker: 'vitest-schema',
            file: 'vitest.config.ts',
            message: 'vitest not installed — skipping vitest config check',
            fix: 'Install vitest: pnpm add -D vitest',
        });
        return findings;
    }
    const configFiles = ['vitest.config.ts', 'vitest.config.js', 'vitest.config.mts'];
    let configContent = '';
    let configFile = '';
    for (const fname of configFiles) {
        const content = await readFileSafe(join(projectPath, fname));
        if (content) {
            configContent = content;
            configFile = fname;
            break;
        }
    }
    if (!configContent) {
        return findings;
    }
    const invalidOptions = healthSchemas.vitest.knownInvalidOptions;
    for (const option of invalidOptions) {
        // Match patterns like: minWorkers: 2, minWorkers:2, minWorkers : 2
        const optionName = option.split('.').pop() ?? option;
        const pattern = new RegExp(`\\b${optionName}\\s*:`);
        if (pattern.test(configContent)) {
            findings.push({
                severity: 'warning',
                checker: 'vitest-schema',
                file: configFile,
                message: `'${option}' is not a valid vitest v4+ InlineConfig option — silently ignored`,
                fix: `Remove '${optionName}' from vitest config. In vitest v4+, use top-level 'minWorkers'/'maxWorkers' directly under the 'test' key`,
            });
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Check: Invalid options in jest.config.{ts,js}
// ---------------------------------------------------------------------------
/**
 * Detect known-deprecated options in jest.config.ts/js.
 */
export async function checkJestSchema(projectPath) {
    const findings = [];
    const jestPath = join(projectPath, 'node_modules', 'jest');
    const isInstalled = await fileExists(join(jestPath, 'package.json'));
    if (!isInstalled) {
        return findings; // Jest not installed — skip silently (not vitest project)
    }
    const configFiles = ['jest.config.ts', 'jest.config.js', 'jest.config.mjs'];
    let configContent = '';
    let configFile = '';
    for (const fname of configFiles) {
        const content = await readFileSafe(join(projectPath, fname));
        if (content) {
            configContent = content;
            configFile = fname;
            break;
        }
    }
    if (!configContent) {
        return findings;
    }
    const deprecatedOptions = healthSchemas.jest.knownDeprecatedOptions;
    for (const option of deprecatedOptions) {
        const optionName = option.split('.').pop() ?? option;
        const pattern = new RegExp(`\\b${optionName}\\s*:`);
        if (pattern.test(configContent)) {
            findings.push({
                severity: 'warning',
                checker: 'jest-schema',
                file: configFile,
                message: `'${option}' is a deprecated Jest config option`,
                fix: `Remove or migrate '${optionName}' — see Jest migration guide for the replacement`,
            });
        }
    }
    return findings;
}
//# sourceMappingURL=ts-checker.js.map