// tools/reverse-engineer/handler.ts — MCP tool handler for reverse_engineer
import { readFile, stat, mkdir, writeFile } from 'node:fs/promises';
import { join, extname, basename, relative, resolve } from 'node:path';
import { glob } from 'glob';
import { specStore, knowledgeStore } from '../../storage/index.js';
import { ti } from '../../i18n/index.js';
import { inferDifficulty } from '../../engine/estimator.js';
import { CODE_EXTENSIONS, detectLanguageFromExtension, getFrameworkTerminology, detectTechDebt, IGNORE_PATTERNS, } from './analyzer.js';
import { runDeepAnalysis, enrichSpecFromAnalysis, generateAnalysisSummary, } from '../../engine/reverse-engineer/index.js';
import { generateLeanSpecContent } from '../../engine/spec-format/lean-spec-generator.js';
import { generateLeanTechnicalContent } from '../../engine/spec-format/lean-technical-generator.js';
import { buildUnifiedSpecContent } from '../../engine/spec-format/unified-spec-builder.js';
async function analyzeCodeStructure(files, targetPath, projectPath, isDir, depth) {
    const interfaces = [];
    const dependencies = [];
    const layers = [];
    const exports = [];
    const patterns = [];
    let totalLines = 0;
    const limit = depth === 'deep' || depth === 'deep-v2' ? files.length : Math.min(files.length, 100);
    for (let i = 0; i < limit; i++) {
        const file = files[i];
        /* v8 ignore next */
        if (!file) {
            continue;
        }
        const fullPath = isDir ? join(targetPath, file) : targetPath;
        try {
            const content = await readFile(fullPath, 'utf-8');
            totalLines += content.split('\n').length;
            extractSymbols(content, interfaces, exports, dependencies);
            inferLayers(isDir ? file : relative(projectPath, targetPath), layers);
            detectPatterns(content, patterns);
        }
        catch {
            /* skip unreadable */
        }
    }
    const languageCounts = new Map();
    for (const file of files) {
        const lang = detectLanguageFromExtension(extname(file));
        if (lang !== 'unknown') {
            languageCounts.set(lang, (languageCounts.get(lang) ?? 0) + 1);
        }
    }
    const dominantLanguage = Array.from(languageCounts.entries()).reduce((max, entry) => (entry[1] > max[1] ? entry : max), ['unknown', 0])[0];
    return {
        interfaces,
        dependencies,
        layers,
        exports,
        patterns,
        totalLines,
        limit,
        dominantLanguage,
        frameworkTerminology: getFrameworkTerminology(dominantLanguage, dependencies),
        techDebt: detectTechDebt(files, totalLines, patterns),
    };
}
function extractSymbols(content, ifaces, exps, deps) {
    for (const m of content.matchAll(/(?:interface|type|class|struct|trait|protocol)\s+(\w+)/g)) {
        if (m[1] && !ifaces.includes(m[1])) {
            ifaces.push(m[1]);
        }
    }
    for (const m of content.matchAll(/export\s+(?:default\s+)?(?:async\s+)?(?:function|const|class)\s+(\w+)/g)) {
        if (m[1] && !exps.includes(m[1])) {
            exps.push(m[1]);
        }
    }
    for (const m of content.matchAll(/(?:import|from)\s+['"]([^'"]+)['"]/g)) {
        if (m[1] && !m[1].startsWith('.') && !deps.includes(m[1])) {
            deps.push(m[1]);
        }
    }
}
function inferLayers(relPath, layers) {
    const layerMap = [
        [/(?:component|page|view|screen|ui)\//i, 'presentation'],
        [/(?:service|handler|use-case)\//i, 'application'],
        [/(?:model|entity|domain|core)\//i, 'domain'],
        [/(?:repository|adapter|infra|db|api)\//i, 'infrastructure'],
    ];
    for (const [re, name] of layerMap) {
        if (re.exec(relPath) && !layers.includes(name)) {
            layers.push(name);
        }
    }
}
function detectPatterns(content, patterns) {
    const patternMap = [
        [() => content.includes('useState') || content.includes('useEffect'), 'React hooks'],
        [() => /(?:Repository|Service|Controller|Handler)\s/.test(content), 'layered-architecture'],
        [() => /async\s+function|await\s/.test(content), 'async/await'],
    ];
    for (const [check, name] of patternMap) {
        if (check() && !patterns.includes(name)) {
            patterns.push(name);
        }
    }
}
export async function handleReverseEngineer(args) {
    const { path: targetPath, projectId, depth = 'shallow' } = args;
    try {
        await stat(targetPath);
    }
    catch {
        return {
            content: [
                { type: 'text', text: ti('reverseEngineer.pathNotFound', { path: targetPath }) },
            ],
            isError: true,
        };
    }
    const knowledge = await knowledgeStore.getKnowledge(projectId);
    const projectPath = knowledge?.projectPath ?? targetPath;
    const fileStat = await stat(targetPath);
    const isDir = fileStat.isDirectory();
    let files;
    if (isDir) {
        const maxDepthValue = depth === 'deep' || depth === 'deep-v2' ? 10 : 4;
        const found = await glob('**/*', {
            cwd: targetPath,
            nodir: true,
            ignore: IGNORE_PATTERNS,
            maxDepth: maxDepthValue,
        });
        files = found.filter((f) => CODE_EXTENSIONS.has(extname(f)));
        if (files.length > 200 && depth === 'shallow') {
            return {
                content: [
                    {
                        type: 'text',
                        text: ti('reverseEngineer.tooLarge', { count: String(files.length) }),
                    },
                ],
                isError: true,
            };
        }
    }
    else {
        files = [basename(targetPath)];
    }
    const ca = await analyzeCodeStructure(files, targetPath, projectPath, isDir, depth);
    // 5. Infer spec metadata
    const dirName = isDir ? basename(targetPath) : basename(targetPath, extname(targetPath));
    const scope = files.length > 20 ? 'cross-module' : files.length > 5 ? 'feature' : 'trivial';
    const specType = ca.patterns.includes('layered-architecture') ? 'refactor' : 'feature';
    const difficulty = inferDifficulty(specType, scope);
    // 6. Build spec
    const specId = `SPEC-RE-${Date.now().toString(36).toUpperCase()}`;
    const slug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
    const now = new Date().toISOString();
    const spec = {
        id: specId,
        title: `[Reverse-engineered] ${dirName}`,
        slug,
        type: specType,
        scope,
        status: 'draft',
        difficulty,
        risk: scope === 'cross-module' ? 'medium' : 'low',
        projectId,
        createdAt: now,
        updatedAt: now,
        specPath: '',
        technicalPath: '',
        estimation: {
            devHours: 0,
            reviewHours: 0,
            recommendedModel: 'sonnet',
            tokensOpus: 0,
            tokensSonnet: 0,
            apiCostUsd: 0,
            hourlyRate: 0,
            humanCostUsd: 0,
            totalCostUsd: 0,
            tokenOptimization: { mode: 'local', reasoning: '', estimatedTokens: 0, savings: '' },
        },
        actuals: null,
        target: ca.layers.includes('presentation')
            ? 'frontend'
            : ca.layers.includes('infrastructure')
                ? 'backend'
                : 'shared',
        tags: [...ca.patterns],
        dependencies: [],
        blockedBy: [],
        gitBranch: '',
        impactAnalysis: {
            affectedModules: ca.layers,
            affectedFiles: files.slice(0, 50),
            breakingChanges: false,
            requiresMigration: false,
            migrationReversible: true,
            requiresFeatureFlag: false,
            rollbackPlan: '',
            testingStrategy: { unitTests: [], integrationTests: [], e2eTests: [], manualTests: [] },
            environments: ['development'],
        },
    };
    // 6b. Write lean spec files to disk (SPEC-461)
    const projectKnowledge = await knowledgeStore.getKnowledge(projectId);
    const specLocation = projectKnowledge?.specLocation ?? 'planu/specs';
    const specDir = resolve(join(projectPath, specLocation, `${specId}-${slug}`));
    const specFilePath = join(specDir, 'spec.md');
    const technicalFilePath = specFilePath;
    spec.specPath = specFilePath;
    spec.technicalPath = technicalFilePath;
    try {
        await mkdir(specDir, { recursive: true });
        const leanSpec = generateLeanSpecContent({
            spec,
            description: `Reverse-engineered from ${targetPath}. ${String(files.length)} files analyzed.`,
            estimation: spec.estimation,
        });
        const filesToModify = files.slice(0, 20).map((f) => ({
            path: relative(projectPath, f),
            status: 'pending',
        }));
        const leanTech = generateLeanTechnicalContent({
            specId,
            filesToModify,
        });
        // SPEC-709: write unified spec.md (technical content embedded as `## Technical` section)
        const unified = buildUnifiedSpecContent(leanSpec, leanTech);
        await writeFile(specFilePath, unified, 'utf-8');
    }
    catch {
        // Best-effort — file write failure shouldn't block the tool
    }
    // 7. Run deep-v2 analysis and enrich spec
    let deepV2Result = null;
    let deepV2Summary = null;
    if (depth === 'deep-v2' && isDir) {
        deepV2Result = await runDeepAnalysis(targetPath);
        const enrichment = enrichSpecFromAnalysis(deepV2Result);
        // Enrich spec with analysis results (AC-9)
        if (spec.impactAnalysis) {
            spec.impactAnalysis.affectedFiles = enrichment.affectedFiles;
        }
        spec.tags = [...new Set([...spec.tags, ...enrichment.tags])];
        deepV2Summary = generateAnalysisSummary(deepV2Result);
    }
    // 8. Save spec
    await specStore.createSpec(projectId, spec);
    // 9. Build output
    const analysis = {
        filesAnalyzed: ca.limit,
        totalLines: ca.totalLines,
        interfacesDetected: ca.interfaces.slice(0, 30),
        exportsDetected: ca.exports.slice(0, 30),
        dependenciesFound: ca.dependencies.slice(0, 30),
        layersIdentified: ca.layers,
        patternsDetected: ca.patterns,
        inferredScope: scope,
        inferredType: specType,
        inferredDifficulty: difficulty,
        techDebt: ca.techDebt,
        dominantLanguage: ca.dominantLanguage,
        frameworkTerminology: ca.frameworkTerminology,
        specStatus: 'draft',
        requiresHumanReview: true,
    };
    // Merge deep-v2 results into output (AC-6)
    if (deepV2Result) {
        const enrichment = enrichSpecFromAnalysis(deepV2Result);
        analysis.testCoverage = deepV2Result.testCoverage;
        analysis.dependencyGraph = deepV2Result.dependencyGraph;
        analysis.apiRoutes = deepV2Result.apiRoutes;
        analysis.complexityMetrics = deepV2Result.complexityMetrics;
        analysis.configAnalysis = deepV2Result.configAnalysis;
        analysis.analysisTimeMs = deepV2Result.analysisTimeMs;
        analysis.techDebt = [...ca.techDebt, ...enrichment.techDebt];
    }
    const content = [
        {
            type: 'text',
            text: ti('reverseEngineer.completed', { count: String(ca.limit) }),
        },
        {
            type: 'text',
            text: JSON.stringify({
                specId,
                title: spec.title,
                status: 'draft',
                analysis,
            }, null, 2),
        },
    ];
    // AC-10: Add human-readable summary for deep-v2
    if (deepV2Summary) {
        content.push({
            type: 'text',
            text: deepV2Summary,
        });
    }
    return { content };
}
//# sourceMappingURL=handler.js.map