"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.detect = detect;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const tool_registry_1 = require("./analyzers/tools/tool-registry");
const constants_1 = require("./constants");
const languages_1 = require("./languages");
function getInstalledNodeVersion() {
    try {
        const output = (0, child_process_1.execSync)('node --version', { stdio: 'pipe' }).toString().trim();
        const match = output.replace(/^v/, '').match(/^(\d+)/);
        if (match)
            return match[1];
    }
    catch {
        /* node not installed */
    }
    return undefined;
}
function fileExists(cwd, ...segments) {
    return fs.existsSync(path.join(cwd, ...segments));
}
function readFileOr(cwd, filePath, fallback) {
    try {
        return fs.readFileSync(path.join(cwd, filePath), 'utf-8');
    }
    catch {
        return fallback;
    }
}
function globExists(cwd, pattern) {
    try {
        const entries = fs.readdirSync(cwd);
        return entries.some((e) => e.match(new RegExp(pattern)));
    }
    catch {
        return false;
    }
}
function extractPythonVersion(cwd) {
    // Try pyproject.toml requires-python
    const pyproject = readFileOr(cwd, 'pyproject.toml', '');
    const match = pyproject.match(/requires-python\s*=\s*"[><=!~]*(\d+\.\d+)/);
    if (match)
        return match[1];
    // Try .python-version
    if (fileExists(cwd, '.python-version')) {
        const ver = readFileOr(cwd, '.python-version', '').trim();
        if (ver.match(/^\d+\.\d+/))
            return ver.split('.').slice(0, 2).join('.');
    }
    return undefined;
}
function extractGoVersion(cwd) {
    const goMod = readFileOr(cwd, 'go.mod', '');
    const match = goMod.match(/^go\s+(\d+\.\d+(?:\.\d+)?)/m);
    return match ? match[1] : undefined;
}
function extractNodeVersion(cwd) {
    // 1. Try .nvmrc (explicit pin — most authoritative)
    if (fileExists(cwd, '.nvmrc')) {
        const ver = readFileOr(cwd, '.nvmrc', '').trim().replace(/^v/, '');
        if (ver.match(/^\d+/))
            return ver.split('.')[0];
    }
    // 2. Try package.json volta.node (pinned version manager)
    const pkg = readFileOr(cwd, 'package.json', '{}');
    try {
        const parsed = JSON.parse(pkg);
        const voltaNode = parsed?.volta?.node;
        if (voltaNode) {
            const match = voltaNode.match(/^(\d+)/);
            if (match)
                return match[1];
        }
        // 3. Try engines.node
        const nodeEngine = parsed?.engines?.node;
        if (nodeEngine) {
            // Exact pins: "20", "20.x", "^20", "~20" → use directly
            const isRange = /[>|]/.test(nodeEngine);
            if (!isRange) {
                const match = nodeEngine.match(/(\d+)/);
                if (match)
                    return match[1];
            }
            // For ranges like ">=10", ">=18", prefer installed version
            // but fall back to the range minimum if node isn't installed
            const installedVersion = getInstalledNodeVersion();
            if (installedVersion)
                return installedVersion;
            // Last resort: extract from range
            const match = nodeEngine.match(/(\d+)/);
            if (match)
                return match[1];
        }
    }
    catch {
        /* ignore parse errors */
    }
    // 4. Try installed Node version (no package.json or no engines field)
    const installed = getInstalledNodeVersion();
    if (installed)
        return installed;
    return undefined;
}
function extractRustVersion(cwd) {
    // Try rust-toolchain.toml
    const toolchain = readFileOr(cwd, 'rust-toolchain.toml', '');
    const match = toolchain.match(/channel\s*=\s*"([^"]+)"/);
    if (match)
        return match[1];
    // Try Cargo.toml rust-version
    const cargo = readFileOr(cwd, 'Cargo.toml', '');
    const rvMatch = cargo.match(/rust-version\s*=\s*"([^"]+)"/);
    if (rvMatch)
        return rvMatch[1];
    return undefined;
}
function findFileRecursive(cwd, pattern, maxDepth = 3) {
    function search(dir, depth) {
        if (depth > maxDepth)
            return null;
        try {
            for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
                if (entry.name.startsWith('.') ||
                    ['node_modules', 'vendor', 'bin', 'obj', 'target'].includes(entry.name))
                    continue;
                const full = path.join(dir, entry.name);
                if (entry.isFile() && pattern.test(entry.name))
                    return full;
                if (entry.isDirectory()) {
                    const found = search(full, depth + 1);
                    if (found)
                        return found;
                }
            }
        }
        catch {
            /* permission error, skip */
        }
        return null;
    }
    return search(cwd, 0);
}
function extractCsharpVersion(cwd) {
    // Try .csproj TargetFramework
    const csproj = findFileRecursive(cwd, /\.csproj$/);
    if (csproj) {
        const content = fs.readFileSync(csproj, 'utf-8');
        const match = content.match(/<TargetFramework>net(\d+\.\d+)<\/TargetFramework>/);
        if (match)
            return match[1];
    }
    // Try global.json SDK version
    const globalJson = readFileOr(cwd, 'global.json', '');
    if (globalJson) {
        try {
            const parsed = JSON.parse(globalJson);
            const ver = parsed?.sdk?.version;
            if (ver)
                return ver.split('.').slice(0, 2).join('.');
        }
        catch {
            /* ignore */
        }
    }
    return undefined;
}
function detectNextjs(cwd) {
    if (globExists(cwd, '^next\\.config\\.'))
        return true;
    if (fileExists(cwd, 'frontend', 'next.config.js') ||
        fileExists(cwd, 'frontend', 'next.config.mjs') ||
        fileExists(cwd, 'frontend', 'next.config.ts'))
        return true;
    const pkg = readFileOr(cwd, 'package.json', '{}');
    try {
        const parsed = JSON.parse(pkg);
        const deps = { ...parsed.dependencies, ...parsed.devDependencies };
        return 'next' in deps;
    }
    catch {
        return false;
    }
}
function detectDockerComposeContent(cwd) {
    for (const name of ['docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml']) {
        const content = readFileOr(cwd, name, '');
        if (content)
            return content;
    }
    return '';
}
function detectProjectName(cwd) {
    const cwdBasename = path.basename(cwd);
    // Prefer cwd basename when it's strictly more descriptive than the
    // manifest-declared name. Triggers when the manifest name is a
    // substring of the cwd basename AND the cwd is longer — e.g.
    // package.json `name: "client"` in a directory called `web-app`.
    // The customer running `dxkit X /path/to/web-app` expects
    // "web-app" in the report header, not "client".
    function preferCwdWhenMoreDescriptive(manifestName) {
        if (cwdBasename.length > manifestName.length &&
            cwdBasename.toLowerCase().includes(manifestName.toLowerCase())) {
            return cwdBasename;
        }
        return manifestName;
    }
    // Try package.json name
    const pkg = readFileOr(cwd, 'package.json', '{}');
    try {
        const parsed = JSON.parse(pkg);
        if (parsed.name && !parsed.name.startsWith('@')) {
            return preferCwdWhenMoreDescriptive(parsed.name);
        }
        if (parsed.name) {
            const unscoped = parsed.name.split('/').pop() || cwdBasename;
            return preferCwdWhenMoreDescriptive(unscoped);
        }
    }
    catch {
        /* ignore */
    }
    // Try go.mod module name
    const goMod = readFileOr(cwd, 'go.mod', '');
    const goMatch = goMod.match(/^module\s+\S+\/(\S+)/m);
    if (goMatch)
        return preferCwdWhenMoreDescriptive(goMatch[1]);
    // Try pyproject.toml name
    const pyproject = readFileOr(cwd, 'pyproject.toml', '');
    const pyMatch = pyproject.match(/name\s*=\s*"([^"]+)"/);
    if (pyMatch)
        return preferCwdWhenMoreDescriptive(pyMatch[1]);
    // Fallback to directory name
    return cwdBasename;
}
function detectProjectDescription(cwd) {
    const pkg = readFileOr(cwd, 'package.json', '{}');
    try {
        const parsed = JSON.parse(pkg);
        if (parsed.description)
            return parsed.description;
    }
    catch {
        /* ignore */
    }
    const pyproject = readFileOr(cwd, 'pyproject.toml', '');
    const match = pyproject.match(/description\s*=\s*"([^"]+)"/);
    if (match)
        return match[1];
    return '';
}
function detectTestRunner(cwd) {
    const pkg = readFileOr(cwd, 'package.json', '{}');
    try {
        const parsed = JSON.parse(pkg);
        const deps = { ...parsed.dependencies, ...parsed.devDependencies };
        const scripts = parsed.scripts || {};
        const testScript = scripts.test || '';
        // Check for specific test frameworks in deps and scripts
        if (deps.vitest || testScript.includes('vitest')) {
            return {
                command: 'npx vitest',
                framework: 'vitest',
                coverageCommand: 'npx vitest --coverage',
            };
        }
        if (deps.jest || deps['ts-jest'] || testScript.includes('jest')) {
            return { command: 'npx jest', framework: 'jest', coverageCommand: 'npx jest --coverage' };
        }
        if (deps.mocha ||
            deps['lb-mocha'] ||
            deps['@loopback/testlab'] ||
            testScript.includes('mocha') ||
            testScript.includes('lb-mocha')) {
            const hasNyc = !!deps.nyc || !!deps.c8;
            const coverageCmd = hasNyc ? (deps.c8 ? 'npx c8 npm test' : 'npx nyc npm test') : undefined;
            return { command: 'npm test', framework: 'mocha', coverageCommand: coverageCmd };
        }
        if (deps.ava || testScript.includes('ava')) {
            return { command: 'npx ava', framework: 'ava', coverageCommand: 'npx c8 npx ava' };
        }
        if (deps.tap || testScript.includes('tap')) {
            return {
                command: 'npx tap',
                framework: 'tap',
                coverageCommand: 'npx tap --coverage-report=text',
            };
        }
        // Fallback: if there's a test script, use npm test
        if (testScript && testScript !== 'echo "Error: no test specified" && exit 1') {
            return { command: 'npm test', framework: 'unknown', coverageCommand: undefined };
        }
    }
    catch {
        /* ignore */
    }
    // Python
    if (fileExists(cwd, 'pyproject.toml') || fileExists(cwd, 'setup.py')) {
        const pyproject = readFileOr(cwd, 'pyproject.toml', '');
        if (pyproject.includes('pytest')) {
            return {
                command: 'pytest',
                framework: 'pytest',
                coverageCommand: 'pytest --cov --cov-report=term-missing',
            };
        }
    }
    // Go
    if (fileExists(cwd, 'go.mod')) {
        return {
            command: 'go test ./...',
            framework: 'go-test',
            coverageCommand: 'go test -coverprofile=coverage.out ./...',
        };
    }
    // Rust
    if (fileExists(cwd, 'Cargo.toml')) {
        return { command: 'cargo test', framework: 'cargo-test', coverageCommand: undefined };
    }
    // C#
    if (findFileRecursive(cwd, /\.csproj$/)) {
        return {
            command: 'dotnet test',
            framework: 'dotnet-test',
            coverageCommand: 'dotnet test --collect:"XPlat Code Coverage"',
        };
    }
    return undefined;
}
function detectFramework(cwd) {
    const pkg = readFileOr(cwd, 'package.json', '{}');
    try {
        const parsed = JSON.parse(pkg);
        const deps = { ...parsed.dependencies, ...parsed.devDependencies };
        if (deps['@loopback/core'] || deps['@loopback/rest'])
            return 'loopback';
        if (deps['next'])
            return 'nextjs';
        if (deps['@nestjs/core'])
            return 'nestjs';
        if (deps['express'])
            return 'express';
        if (deps['fastify'])
            return 'fastify';
        if (deps['koa'])
            return 'koa';
        if (deps['hapi'] || deps['@hapi/hapi'])
            return 'hapi';
    }
    catch {
        /* ignore */
    }
    // Python
    const pyproject = readFileOr(cwd, 'pyproject.toml', '');
    const requirements = readFileOr(cwd, 'requirements.txt', '');
    const pyDeps = pyproject + requirements;
    if (pyDeps.includes('fastapi'))
        return 'fastapi';
    if (pyDeps.includes('django'))
        return 'django';
    if (pyDeps.includes('flask'))
        return 'flask';
    // Go
    const goMod = readFileOr(cwd, 'go.mod', '');
    if (goMod.includes('github.com/gin-gonic/gin'))
        return 'gin';
    if (goMod.includes('github.com/labstack/echo'))
        return 'echo';
    if (goMod.includes('github.com/gofiber/fiber'))
        return 'fiber';
    return undefined;
}
// detectRequiredTools now delegates to the central tool registry
// (src/analyzers/tools/tool-registry.ts). This ensures both detect.ts
// and the `vyuh-dxkit tools` subcommand read from the same source of truth.
function detectRequiredTools(languages) {
    return (0, tool_registry_1.buildRequiredTools)(languages);
}
function detect(cwd) {
    const composeContent = detectDockerComposeContent(cwd);
    const isNextjs = detectNextjs(cwd);
    // Pack-driven detection. Each LanguageSupport declares its own
    // `.detect(cwd)` — single source of truth for "is this a <lang>
    // project?". `Record<LanguageId, boolean>` shape (10f.4) means
    // adding a 6th pack only extends the LanguageId union; this
    // function never changes.
    //
    // typescript pack matches any package.json — covers both Node and
    // Next.js projects. nextjs is NOT a separate language flag; it's
    // surfaced via the top-level `framework` field below.
    const languages = Object.fromEntries(languages_1.LANGUAGES.map((lang) => [lang.id, lang.detect(cwd)]));
    return {
        languages,
        infrastructure: {
            docker: fileExists(cwd, 'Dockerfile') || composeContent.length > 0,
            postgres: composeContent.includes('postgres'),
            redis: composeContent.includes('redis'),
        },
        projectName: detectProjectName(cwd),
        projectDescription: detectProjectDescription(cwd),
        versions: {
            python: extractPythonVersion(cwd) ?? constants_1.DEFAULT_VERSIONS.python,
            go: extractGoVersion(cwd) ?? constants_1.DEFAULT_VERSIONS.go,
            node: extractNodeVersion(cwd) ?? constants_1.DEFAULT_VERSIONS.node,
            rust: extractRustVersion(cwd) ?? constants_1.DEFAULT_VERSIONS.rust,
            csharp: extractCsharpVersion(cwd) ?? constants_1.DEFAULT_VERSIONS.csharp,
        },
        testRunner: detectTestRunner(cwd),
        // nextjs detection takes precedence — it's a more specific signal
        // than detectFramework's package.json scan (10f.4: nextjs moved out
        // of `languages` and is now exclusively the framework signal).
        framework: isNextjs ? 'nextjs' : detectFramework(cwd),
        requiredTools: detectRequiredTools(languages),
    };
}
//# sourceMappingURL=detect.js.map