// engine/config-health/go-checker.ts — SPEC-042
// Go project 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' };
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function readFileSafe(filePath) {
    try {
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return '';
    }
}
// ---------------------------------------------------------------------------
// go.mod parser helpers
// ---------------------------------------------------------------------------
function parseGoMod(content) {
    const lines = content.split('\n');
    let moduleName = '';
    let goVersion = '';
    const requires = [];
    for (const line of lines) {
        const trimmed = line.trim();
        const moduleMatch = /^module\s+([\w./\\-]+)/.exec(trimmed);
        if (moduleMatch?.[1]) {
            moduleName = moduleMatch[1];
            continue;
        }
        const goMatch = /^go\s+([\d.]+)/.exec(trimmed);
        if (goMatch?.[1]) {
            goVersion = goMatch[1];
            continue;
        }
        // Handle require blocks: "require github.com/foo/bar v1.2.3"
        // or lines inside require ( ... ) blocks
        const reqMatch = /^(?:require\s+)?([\w./\\-]+(?:\/[\w./\\-]+)*)\s+(v[\d.]+\S*)/.exec(trimmed);
        if (reqMatch?.[1] && reqMatch[2]) {
            requires.push({ module: reqMatch[1], version: reqMatch[2] });
        }
    }
    return { moduleName, goVersion, requires };
}
function compareGoVersions(a, b) {
    const parseVersion = (v) => v.split('.').map((n) => parseInt(n, 10) || 0);
    const partsA = parseVersion(a);
    const partsB = parseVersion(b);
    const len = Math.max(partsA.length, partsB.length);
    for (let i = 0; i < len; i++) {
        const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
        if (diff !== 0) {
            return diff;
        }
    }
    return 0;
}
// ---------------------------------------------------------------------------
// Check: go.mod version vs dependency requirements
// ---------------------------------------------------------------------------
/**
 * Detect go.mod with a declared Go version that is lower than what some
 * dependencies claim to require.
 */
export async function checkGoModVersion(projectPath) {
    const findings = [];
    const goModContent = await readFileSafe(join(projectPath, 'go.mod'));
    if (!goModContent) {
        return findings;
    }
    const goMod = parseGoMod(goModContent);
    if (!goMod.goVersion) {
        return findings;
    }
    // Check for dependencies that commonly require newer Go versions
    // We look for known module version patterns that imply minimum Go version
    const KNOWN_MIN_VERSIONS = {
        'golang.org/x/exp': '1.18',
        'golang.org/x/generics': '1.18',
    };
    for (const req of goMod.requires) {
        const minVersion = KNOWN_MIN_VERSIONS[req.module];
        if (minVersion && compareGoVersions(goMod.goVersion, minVersion) < 0) {
            findings.push({
                severity: 'error',
                checker: 'go-mod-imports',
                file: 'go.mod',
                message: `Dependency '${req.module}' requires Go ${minVersion}+ but go.mod declares 'go ${goMod.goVersion}'`,
                fix: `Update the 'go' directive in go.mod to at least '${minVersion}': run 'go mod edit -go=${minVersion}'`,
            });
        }
    }
    // Check if go version seems very old (< 1.16 means no module support improvements)
    if (compareGoVersions(goMod.goVersion, '1.16') < 0) {
        findings.push({
            severity: 'warning',
            checker: 'go-mod-imports',
            file: 'go.mod',
            message: `go.mod declares 'go ${goMod.goVersion}' which is quite old — many modern features unavailable`,
            fix: `Update to at least 'go 1.21' to get improved toolchain support: run 'go mod edit -go=1.21'`,
        });
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Check: imports in *.go files not declared in go.mod
// ---------------------------------------------------------------------------
/**
 * Detect imports in .go source files that reference packages not declared
 * in go.mod. This catches missing `go get` calls.
 */
export async function checkGoImports(projectPath) {
    const findings = [];
    const goModContent = await readFileSafe(join(projectPath, 'go.mod'));
    if (!goModContent) {
        return findings;
    }
    const goMod = parseGoMod(goModContent);
    const declaredModules = new Set(goMod.requires.map((r) => r.module));
    // Find Go source files (skip vendor/)
    let goFiles;
    try {
        goFiles = await glob('**/*.go', {
            cwd: projectPath,
            nodir: true,
            ignore: ['vendor/**', 'testdata/**'],
        });
    }
    catch {
        /* v8 ignore next 1 */
        return findings;
    }
    // Sample up to 20 files to avoid excessive processing
    const filesToCheck = goFiles.slice(0, 20);
    const missingModules = new Set();
    for (const goFile of filesToCheck) {
        const content = await readFileSafe(join(projectPath, goFile));
        if (!content) {
            continue;
        }
        // Extract import paths from Go files
        // Match: import "github.com/foo/bar" or import ( "github.com/foo/bar" )
        const importPattern = /"(github\.com\/[^"]+)"|"(golang\.org\/x\/[^"]+)"/gm;
        let match;
        while ((match = importPattern.exec(content)) !== null) {
            const importPath = match[1] ?? match[2] ?? '';
            if (!importPath) {
                continue;
            }
            // Extract the module root (first 3 path segments for github.com packages)
            const parts = importPath.split('/');
            const moduleRoot = parts.length >= 3 ? `${parts[0]}/${parts[1]}/${parts[2]}` : importPath;
            if (!declaredModules.has(moduleRoot) && !missingModules.has(moduleRoot)) {
                // Also check if it's the current module
                if (moduleRoot !== goMod.moduleName && !goMod.moduleName.startsWith(moduleRoot)) {
                    missingModules.add(moduleRoot);
                    findings.push({
                        severity: 'warning',
                        checker: 'go-mod-imports',
                        file: goFile,
                        message: `Import '${moduleRoot}' is used in source but not declared in go.mod`,
                        fix: `Run 'go get ${moduleRoot}' to add it to go.mod, then 'go mod tidy'`,
                    });
                }
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Check: .golangci.yml with unknown linters
// ---------------------------------------------------------------------------
/**
 * Detect .golangci.yml files that enable linters not recognized by golangci-lint.
 */
export async function checkGolangciConfig(projectPath) {
    const findings = [];
    const configFiles = ['.golangci.yml', '.golangci.yaml', '.golangci.toml', '.golangci.json'];
    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 knownLinters = healthSchemas.golangci.knownLinters;
    // Extract linter names from 'enable:' or 'disable:' lists in YAML
    const enablePattern = /^\s*-\s+([\w-]+)\s*$/gm;
    let match;
    // Only check enable lists (lines after 'enable:' key)
    const enableSectionMatch = /enable:\s*\n((?:\s*-\s+[\w-]+\s*\n?)+)/gm;
    let sectionMatch;
    while ((sectionMatch = enableSectionMatch.exec(configContent)) !== null) {
        const enableBlock = sectionMatch[1] ?? '';
        enablePattern.lastIndex = 0;
        while ((match = enablePattern.exec(enableBlock)) !== null) {
            const linterName = match[1] ?? '';
            if (linterName && !knownLinters.includes(linterName)) {
                findings.push({
                    severity: 'warning',
                    checker: 'golangci-config',
                    file: configFile,
                    message: `Linter '${linterName}' in ${configFile} is not in the known golangci-lint linter list`,
                    fix: `Check if '${linterName}' is available in your golangci-lint version. Run 'golangci-lint linters' to see available linters`,
                });
            }
        }
    }
    return findings;
}
//# sourceMappingURL=go-checker.js.map