// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
// — must enumerate framework names by design; no detection logic to migrate.
/**
 * Stack detection and spec adaptation for registry specs.
 * Detects project technology stack from config files and applies
 * framework-specific substitutions to spec content.
 */
import { readFile, access, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function fileExists(filePath) {
    try {
        await access(filePath);
        return true;
    }
    catch {
        return false;
    }
}
/* v8 ignore start -- safe file readers, catch branches are defensive */
async function readJsonSafe(filePath) {
    try {
        const raw = await readFile(filePath, 'utf-8');
        return JSON.parse(raw);
    }
    catch {
        return null;
    }
}
async function readTextSafe(filePath) {
    try {
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return null;
    }
}
/* v8 ignore stop */
function hasDep(pkg, name) {
    const deps = pkg.dependencies;
    const devDeps = pkg.devDependencies;
    return !!(deps?.[name] ?? devDeps?.[name]);
}
function hasDepOnly(pkg, name) {
    const deps = pkg.dependencies;
    return !!deps?.[name];
}
function hasDevDep(pkg, name) {
    const devDeps = pkg.devDependencies;
    return !!devDeps?.[name];
}
function textContains(text, keyword) {
    return text.toLowerCase().includes(keyword.toLowerCase());
}
// ---------------------------------------------------------------------------
// Node / JS ecosystem detection — extracted helpers to reduce complexity
// ---------------------------------------------------------------------------
function detectNodeFramework(pkg) {
    if (hasDep(pkg, 'next')) {
        return 'nextjs';
    }
    if (hasDep(pkg, 'nuxt')) {
        return 'nuxt';
    }
    if (hasDep(pkg, '@sveltejs/kit') || hasDep(pkg, 'svelte')) {
        return 'sveltekit';
    }
    if (hasDep(pkg, '@remix-run/node') || hasDep(pkg, 'remix')) {
        return 'remix';
    }
    if (hasDep(pkg, 'astro')) {
        return 'astro';
    }
    if (hasDep(pkg, 'fastify')) {
        return 'fastify';
    }
    if (hasDep(pkg, 'express')) {
        return 'express';
    }
    return 'unknown';
}
function detectNodeOrm(pkg) {
    if (hasDep(pkg, '@prisma/client') || hasDep(pkg, 'prisma')) {
        return 'prisma';
    }
    if (hasDep(pkg, 'drizzle-orm')) {
        return 'drizzle';
    }
    if (hasDep(pkg, 'sequelize')) {
        return 'sequelize';
    }
    if (hasDep(pkg, 'typeorm')) {
        return 'typeorm';
    }
    return undefined;
}
function detectNodeTestRunner(pkg) {
    if (hasDevDep(pkg, 'vitest')) {
        return 'vitest';
    }
    if (hasDevDep(pkg, 'jest')) {
        return 'jest';
    }
    if (hasDevDep(pkg, 'mocha')) {
        return 'mocha';
    }
    return undefined;
}
/* v8 ignore start -- Node.js build/pkg detection branches */
function detectNodeBuildTool(pkg) {
    if (hasDevDep(pkg, 'vite') || hasDepOnly(pkg, 'vite')) {
        return 'vite';
    }
    if (hasDevDep(pkg, 'webpack')) {
        return 'webpack';
    }
    if (hasDevDep(pkg, 'esbuild')) {
        return 'esbuild';
    }
    return undefined;
}
async function detectNodePackageManager(projectPath) {
    if (await fileExists(join(projectPath, 'pnpm-lock.yaml'))) {
        return 'pnpm';
    }
    if (await fileExists(join(projectPath, 'yarn.lock'))) {
        return 'yarn';
    }
    if (await fileExists(join(projectPath, 'bun.lockb'))) {
        return 'bun';
    }
    if (await fileExists(join(projectPath, 'package-lock.json'))) {
        return 'npm';
    }
    return undefined;
}
/* v8 ignore stop */
async function detectNodeStack(projectPath, pkg) {
    const result = { language: 'javascript', framework: 'unknown' };
    if (await fileExists(join(projectPath, 'tsconfig.json'))) {
        result.language = 'typescript';
    }
    result.framework = detectNodeFramework(pkg);
    result.orm = detectNodeOrm(pkg);
    result.testRunner = detectNodeTestRunner(pkg);
    result.buildTool = detectNodeBuildTool(pkg);
    result.packageManager = await detectNodePackageManager(projectPath);
    return result;
}
// ---------------------------------------------------------------------------
// Python ecosystem detection
// ---------------------------------------------------------------------------
async function detectPythonStack(projectPath) {
    const result = { language: 'python', framework: 'unknown' };
    const pyproject = await readTextSafe(join(projectPath, 'pyproject.toml'));
    const requirements = await readTextSafe(join(projectPath, 'requirements.txt'));
    const combined = [pyproject, requirements].filter(Boolean).join('\n');
    /* v8 ignore start -- Python framework detection branches */
    if (textContains(combined, 'fastapi')) {
        result.framework = 'fastapi';
    }
    else if (textContains(combined, 'django')) {
        result.framework = 'django';
    }
    else if (textContains(combined, 'flask')) {
        result.framework = 'flask';
    }
    if (textContains(combined, 'sqlalchemy')) {
        result.orm = 'sqlalchemy';
    }
    if (textContains(combined, 'pytest')) {
        result.testRunner = 'pytest';
    }
    if (await fileExists(join(projectPath, 'poetry.lock'))) {
        result.packageManager = 'poetry';
    }
    else {
        result.packageManager = 'pip';
    }
    /* v8 ignore stop */
    return result;
}
// ---------------------------------------------------------------------------
// Go ecosystem detection
// ---------------------------------------------------------------------------
/* v8 ignore start -- Go/Rust detection tested via unit tests */
async function detectGoStack(projectPath) {
    const result = { language: 'go', framework: 'unknown' };
    const goMod = await readTextSafe(join(projectPath, 'go.mod'));
    if (!goMod) {
        return result;
    }
    if (goMod.includes('github.com/gin-gonic/gin')) {
        result.framework = 'gin';
    }
    else if (goMod.includes('github.com/gofiber/fiber')) {
        result.framework = 'fiber';
    }
    if (goMod.includes('gorm.io/gorm')) {
        result.orm = 'gorm';
    }
    return result;
}
async function detectRustStack(projectPath) {
    const result = { language: 'rust', framework: 'unknown' };
    const cargo = await readTextSafe(join(projectPath, 'Cargo.toml'));
    if (!cargo) {
        return result;
    }
    if (textContains(cargo, 'actix-web')) {
        result.framework = 'actix-web';
    }
    else if (textContains(cargo, 'axum')) {
        result.framework = 'axum';
    }
    if (textContains(cargo, 'diesel')) {
        result.orm = 'diesel';
    }
    return result;
}
/* v8 ignore stop */
// ---------------------------------------------------------------------------
// Public: detectStack
// ---------------------------------------------------------------------------
export async function detectStack(projectPath) {
    // Node / JS ecosystem (most common, check first)
    const pkg = await readJsonSafe(join(projectPath, 'package.json'));
    if (pkg) {
        return detectNodeStack(projectPath, pkg);
    }
    // Python
    if ((await fileExists(join(projectPath, 'pyproject.toml'))) ||
        (await fileExists(join(projectPath, 'requirements.txt')))) {
        return detectPythonStack(projectPath);
    }
    // Go
    if (await fileExists(join(projectPath, 'go.mod'))) {
        return detectGoStack(projectPath);
    }
    // Rust
    if (await fileExists(join(projectPath, 'Cargo.toml'))) {
        return detectRustStack(projectPath);
    }
    return { language: 'unknown', framework: 'unknown' };
}
// ---------------------------------------------------------------------------
// Public: adaptSpec
// ---------------------------------------------------------------------------
/* v8 ignore start -- adaptSpec branches tested via unit tests */
export async function adaptSpec(specDir, projectStack, adaptableMap) {
    // Load adaptable map from registry metadata if not provided
    let map = adaptableMap;
    if (!map) {
        const registryJson = await readJsonSafe(join(specDir, 'planu-registry.json'));
        if (registryJson && typeof registryJson.adaptableMap === 'object') {
            map = registryJson.adaptableMap;
        }
    }
    if (!map || Object.keys(map).length === 0) {
        return [];
    }
    const targetFiles = ['technical.md', 'spec.md'];
    const adaptations = [];
    for (const fileName of targetFiles) {
        const filePath = join(specDir, fileName);
        const original = await readTextSafe(filePath);
        if (!original) {
            continue;
        }
        let content = original;
        let modified = false;
        for (const [marker, replacements] of Object.entries(map)) {
            if (!content.includes(marker)) {
                continue;
            }
            const replacement = replacements[projectStack.framework];
            if (replacement) {
                content = content.replaceAll(marker, replacement);
                modified = true;
            }
            else {
                // No exact match — prepend [ADAPT] marker if not already present
                const adaptMarker = `[ADAPT] ${marker}`;
                if (!content.includes(adaptMarker)) {
                    content = content.replaceAll(marker, adaptMarker);
                    modified = true;
                }
            }
        }
        if (modified) {
            await writeFile(filePath, content, 'utf-8');
            adaptations.push({
                file: fileName,
                original,
                adapted: content,
                marker: Object.keys(map).find((m) => original.includes(m)),
            });
        }
    }
    return adaptations;
}
/* v8 ignore stop */
//# sourceMappingURL=adapter.js.map