// engine/skill-bootstrap/stack-matcher.ts — SPEC-439: Detect project stack and match to skills
// SPEC-597-EXEMPT: npm-package→canonical-name mapping. Lookup table consumed
// at runtime; JSON externalization adds I/O without payoff.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
const DEP_TO_TECH = {
    // Frontend frameworks
    next: 'nextjs',
    react: 'react',
    vue: 'vuejs',
    '@angular/core': 'angular',
    svelte: 'svelte',
    '@sveltejs/kit': 'sveltekit',
    'solid-js': 'solidjs',
    '@solidjs/start': 'solidstart',
    nuxt: 'nuxt',
    astro: 'astro',
    remix: 'remix',
    gatsby: 'gatsby',
    qwik: 'qwik',
    // Styling
    tailwindcss: 'tailwind',
    'styled-components': 'styled-components',
    '@emotion/react': 'emotion',
    sass: 'sass',
    unocss: 'unocss',
    // UI libraries
    '@shadcn/ui': 'shadcn-ui',
    '@radix-ui/react-dialog': 'radix-ui',
    '@mui/material': 'material-ui',
    antd: 'antd',
    '@chakra-ui/react': 'chakra-ui',
    // State management
    zustand: 'zustand',
    jotai: 'jotai',
    '@reduxjs/toolkit': 'redux-toolkit',
    recoil: 'recoil',
    mobx: 'mobx',
    nanostores: 'nanostores',
    // Backend / Node
    express: 'express',
    fastify: 'fastify',
    hono: 'hono',
    koa: 'koa',
    nestjs: 'nestjs',
    '@nestjs/core': 'nestjs',
    elysia: 'elysia',
    // ORMs / DB
    prisma: 'prisma',
    '@prisma/client': 'prisma',
    'drizzle-orm': 'drizzle',
    typeorm: 'typeorm',
    sequelize: 'sequelize',
    mongoose: 'mongoose',
    '@planetscale/database': 'planetscale',
    '@vercel/postgres': 'vercel-postgres',
    'better-sqlite3': 'sqlite',
    // BaaS / Cloud
    '@supabase/supabase-js': 'supabase',
    firebase: 'firebase',
    'firebase-admin': 'firebase',
    '@aws-sdk/client-s3': 'aws',
    '@aws-amplify/ui-react': 'amplify',
    convex: 'convex',
    pocketbase: 'pocketbase',
    appwrite: 'appwrite',
    // Auth
    'next-auth': 'nextauth',
    '@auth/core': 'authjs',
    lucia: 'lucia',
    clerk: '@clerk/nextjs',
    '@clerk/nextjs': 'clerk',
    '@clerk/remix': 'clerk',
    'better-auth': 'better-auth',
    // Testing
    vitest: 'vitest',
    jest: 'jest',
    playwright: 'playwright',
    cypress: 'cypress',
    '@testing-library/react': 'testing-library',
    storybook: 'storybook',
    '@storybook/react': 'storybook',
    // Build / Meta
    typescript: 'typescript',
    vite: 'vite',
    turbo: 'turborepo',
    nx: 'nx',
    webpack: 'webpack',
    rollup: 'rollup',
    esbuild: 'esbuild',
    bun: 'bun',
    // AI / ML
    openai: 'openai',
    '@anthropic-ai/sdk': 'anthropic',
    ai: 'vercel-ai-sdk',
    langchain: 'langchain',
    '@langchain/core': 'langchain',
    ollama: 'ollama',
    // Payments
    stripe: 'stripe',
    '@stripe/stripe-js': 'stripe',
    // APIs / GraphQL
    graphql: 'graphql',
    'apollo-server': 'apollo',
    '@apollo/client': 'apollo',
    trpc: 'trpc',
    '@trpc/server': 'trpc',
    // Mobile
    'react-native': 'react-native',
    expo: 'expo',
    // Python (detectados en requirements.txt)
    fastapi: 'fastapi',
    django: 'django',
    flask: 'flask',
    sqlalchemy: 'sqlalchemy',
    pydantic: 'pydantic',
};
async function readFileSafe(filePath) {
    try {
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return null;
    }
}
async function detectFromPackageJson(projectPath) {
    const content = await readFileSafe(join(projectPath, 'package.json'));
    if (!content) {
        return [];
    }
    let pkg;
    try {
        pkg = JSON.parse(content);
    }
    catch {
        return [];
    }
    const allDeps = {
        ...pkg.dependencies,
        ...pkg.devDependencies,
    };
    const techs = [];
    for (const [dep, tech] of Object.entries(DEP_TO_TECH)) {
        if (dep in allDeps) {
            if (!techs.includes(tech)) {
                techs.push(tech);
            }
        }
    }
    return techs;
}
const PYTHON_DEP_TO_TECH = {
    fastapi: 'fastapi',
    django: 'django',
    flask: 'flask',
    sqlalchemy: 'sqlalchemy',
    pydantic: 'pydantic',
    celery: 'celery',
    pytest: 'pytest',
    uvicorn: 'fastapi',
    starlette: 'fastapi',
    alembic: 'sqlalchemy',
    torch: 'pytorch',
    tensorflow: 'tensorflow',
    pandas: 'pandas',
    numpy: 'numpy',
    scikit_learn: 'scikit-learn',
    'scikit-learn': 'scikit-learn',
};
async function detectFromRequirementsTxt(projectPath) {
    const content = await readFileSafe(join(projectPath, 'requirements.txt'));
    if (!content) {
        return [];
    }
    const techs = [];
    const lines = content.split('\n').map((l) => l
        .toLowerCase()
        .trim()
        .split(/[>=<!]/)[0] ?? '');
    for (const [dep, tech] of Object.entries(PYTHON_DEP_TO_TECH)) {
        if (lines.some((l) => l === dep) && !techs.includes(tech)) {
            techs.push(tech);
        }
    }
    return techs;
}
async function detectFromGemfile(projectPath) {
    const content = await readFileSafe(join(projectPath, 'Gemfile'));
    if (!content) {
        return [];
    }
    const techs = [];
    if (content.includes('rails')) {
        techs.push('rails');
    }
    return techs;
}
async function detectFromGoMod(projectPath) {
    const content = await readFileSafe(join(projectPath, 'go.mod'));
    if (!content) {
        return [];
    }
    return ['go'];
}
async function detectFromCargoToml(projectPath) {
    const content = await readFileSafe(join(projectPath, 'Cargo.toml'));
    if (!content) {
        return [];
    }
    return ['rust'];
}
export async function detectProjectStack(projectPath) {
    const results = await Promise.all([
        detectFromPackageJson(projectPath),
        detectFromRequirementsTxt(projectPath),
        detectFromGemfile(projectPath),
        detectFromGoMod(projectPath),
        detectFromCargoToml(projectPath),
    ]);
    const combined = results.flat();
    // deduplicate
    return [...new Set(combined)];
}
export function matchTechsToAvailable(detectedStack, availableTechs) {
    const matched = [];
    for (const detected of detectedStack) {
        const lower = detected.toLowerCase();
        const found = availableTechs.find((available) => {
            const availLower = available.toLowerCase();
            return availLower.includes(lower) || lower.includes(availLower);
        });
        if (found && !matched.includes(found)) {
            matched.push(found);
        }
    }
    return matched;
}
export function deduplicateSkillContent(skills) {
    const seenLines = new Set();
    const sections = [];
    for (const skill of skills) {
        const lines = skill.content.split('\n');
        const uniqueLines = [];
        for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed || !seenLines.has(trimmed)) {
                if (trimmed) {
                    seenLines.add(trimmed);
                }
                uniqueLines.push(line);
            }
        }
        if (uniqueLines.length > 0) {
            sections.push(uniqueLines.join('\n'));
        }
    }
    return sections.join('\n\n');
}
//# sourceMappingURL=stack-matcher.js.map