// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
// — must enumerate framework names by design; no detection logic to migrate.
// Planu — Env Var Scanner: .env.example generation and service info (SPEC-025)
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { scanDirectory, scanFileForEnvVars, scanFileForSecrets } from './scanner.js';
// === Registry loading ===
let patternsCache = null;
/** Resets the in-memory cache — used in tests to force reload */
export function resetEnvPatternsCache() {
    patternsCache = null;
}
async function loadEnvPatterns() {
    if (patternsCache) {
        return patternsCache;
    }
    try {
        const configPath = new URL('../../config/env-patterns.json', import.meta.url);
        const content = await readFile(configPath, 'utf-8');
        patternsCache = JSON.parse(content);
        return patternsCache;
    }
    catch {
        return { detectionPatterns: {}, secretPatterns: [], services: {} };
    }
}
// === Service info inference ===
function inferServiceInfo(varName, services) {
    for (const [, service] of Object.entries(services)) {
        if (service.prefix.some((p) => varName.startsWith(p.replace('_', '')))) {
            const varEntry = service.vars[varName];
            if (varEntry) {
                return {
                    description: varEntry.description,
                    format: varEntry.format,
                    howToObtain: varEntry.howToObtain,
                    estimatedTime: varEntry.estimatedTime,
                    isSecret: varEntry.isSecret ?? false,
                    dashboardUrl: service.dashboardUrl,
                };
            }
        }
    }
    // Generic inference
    const upper = varName.toUpperCase();
    const isSecret = upper.includes('KEY') ||
        upper.includes('SECRET') ||
        upper.includes('TOKEN') ||
        upper.includes('PASSWORD') ||
        upper.includes('PASS');
    return {
        description: `Environment variable: ${varName}`,
        format: 'string',
        howToObtain: 'Check your service provider documentation',
        estimatedTime: '5 minutes',
        isSecret,
        dashboardUrl: null,
    };
}
export function generateEnvExampleContent(variables) {
    const lines = [
        '# Auto-generated .env.example by Planu',
        '# Fill in the values below. Never commit .env with real values!',
        '',
    ];
    // Group by service
    const grouped = new Map();
    for (const v of variables) {
        const service = inferServiceFromName(v.name);
        const group = grouped.get(service) ?? [];
        group.push(v);
        grouped.set(service, group);
    }
    for (const [service, vars] of grouped) {
        lines.push(`# === ${service.toUpperCase()} ===`);
        for (const v of vars) {
            if (v.description) {
                lines.push(`# ${v.description}`);
            }
            if (v.howToObtain) {
                lines.push(`# How to get: ${v.howToObtain}`);
            }
            lines.push(`${v.name}=`);
            lines.push('');
        }
    }
    return lines.join('\n');
}
function inferServiceFromName(name) {
    const upper = name.toUpperCase();
    if (upper.includes('SUPABASE')) {
        return 'supabase';
    }
    if (upper.includes('STRIPE')) {
        return 'stripe';
    }
    if (upper.includes('FIREBASE')) {
        return 'firebase';
    }
    if (upper.includes('AWS')) {
        return 'aws';
    }
    if (upper.includes('DATABASE') ||
        upper.includes('DB_') ||
        upper.includes('POSTGRES') ||
        upper.includes('MYSQL') ||
        upper.includes('MONGO')) {
        return 'database';
    }
    if (upper.includes('REDIS')) {
        return 'redis';
    }
    if (upper.includes('OPENAI') || upper.includes('ANTHROPIC') || upper.includes('CLAUDE')) {
        return 'ai';
    }
    if (upper.includes('GITHUB') || upper.includes('GITLAB')) {
        return 'git';
    }
    return 'app';
}
// === Main scan function helpers ===
function buildPatternRegexes(detectionPatterns) {
    const all = [];
    for (const patterns of Object.values(detectionPatterns)) {
        for (const p of patterns) {
            try {
                all.push(new RegExp(p, 'g'));
            }
            catch {
                // Invalid regex
            }
        }
    }
    return all;
}
async function readExampleVars(projectPath) {
    const vars = new Set();
    for (const exampleFile of ['.env.example', '.env.local.example', '.env.sample']) {
        try {
            const content = await readFile(join(projectPath, exampleFile), 'utf-8');
            for (const line of content.split('\n')) {
                const trimmed = line.trim();
                if (!trimmed || trimmed.startsWith('#')) {
                    continue;
                }
                const eqIdx = trimmed.indexOf('=');
                if (eqIdx !== -1) {
                    vars.add(trimmed.substring(0, eqIdx).trim());
                }
            }
            return { vars, exists: true };
        }
        catch {
            // File doesn't exist — try next
        }
    }
    return { vars, exists: false };
}
export async function scanEnvVars(projectPath) {
    const config = await loadEnvPatterns();
    const allPatterns = buildPatternRegexes(config.detectionPatterns);
    const secretRegexes = config.secretPatterns.flatMap((p) => {
        try {
            return [new RegExp(p, 'g')];
        }
        catch {
            return [];
        }
    });
    // Scan source files
    const sourceFiles = [];
    await scanDirectory(projectPath, sourceFiles);
    const foundVarMap = new Map();
    const hardcodedSecrets = [];
    for (const file of sourceFiles) {
        const vars = await scanFileForEnvVars(file, allPatterns);
        for (const v of vars) {
            if (!foundVarMap.has(v.name)) {
                foundVarMap.set(v.name, v);
            }
        }
        const secrets = await scanFileForSecrets(file, secretRegexes, projectPath);
        hardcodedSecrets.push(...secrets);
    }
    const { vars: exampleVars, exists: envExampleExists } = await readExampleVars(projectPath);
    const allVarNames = new Set([...foundVarMap.keys(), ...exampleVars]);
    const variables = [];
    for (const varName of allVarNames) {
        const inCode = foundVarMap.has(varName);
        const inExample = exampleVars.has(varName);
        const info = inferServiceInfo(varName, config.services);
        variables.push({
            name: varName,
            source: inCode && inExample ? 'both' : inCode ? 'code' : 'env-example',
            description: info.description ?? '',
            howToObtain: info.howToObtain ?? '',
            dashboardUrl: info.dashboardUrl ?? null,
            expectedFormat: info.format ?? 'string',
            estimatedTime: info.estimatedTime ?? '5 minutes',
            isSecret: info.isSecret ?? false,
        });
    }
    const missingInExample = [...foundVarMap.keys()].filter((v) => !exampleVars.has(v));
    let gitignoreCoversEnv = false;
    try {
        const gitignore = await readFile(join(projectPath, '.gitignore'), 'utf-8');
        gitignoreCoversEnv = gitignore.includes('.env');
    }
    catch {
        // File doesn't exist — gitignore doesn't cover .env
    }
    return { variables, missingInExample, hardcodedSecrets, gitignoreCoversEnv, envExampleExists };
}
//# sourceMappingURL=env-example.js.map