// Planu — Architecture pattern, layer, boundary, and deploy unit detection
import { readdir, stat } from 'node:fs/promises';
import { join, relative } from 'node:path';
import { fileExists, hasAnyFile, collectDependencies } from './detection-utils.js';
/**
 * Detect architecture patterns, layers, boundaries, communication patterns, and deploy units.
 */
export async function detectArchitecture(projectPath, archConfig) {
    const detectedPatterns = [];
    for (const [key, config] of Object.entries(archConfig.patterns)) {
        const score = await scoreArchitecturePattern(projectPath, config);
        if (score > 0) {
            detectedPatterns.push({ pattern: key, score });
        }
    }
    // Sort by score descending
    detectedPatterns.sort((a, b) => b.score - a.score);
    const primary = detectedPatterns[0]?.pattern ?? 'monolith';
    const secondary = detectedPatterns.slice(1, 4).map((p) => p.pattern);
    const layers = await detectLayers(projectPath);
    const boundaries = await detectBoundaries(projectPath);
    return {
        primary,
        secondary,
        layers,
        boundaries,
        communicationPatterns: inferCommunicationPatterns(primary),
        deploymentUnits: await detectDeploymentUnits(projectPath),
    };
}
/**
 * Score how well a project matches an architecture pattern.
 */
export async function scoreArchitecturePattern(projectPath, config) {
    let score = 0;
    const indicators = config.indicators;
    // Check directories
    if (indicators.directories && Array.isArray(indicators.directories)) {
        for (const dir of indicators.directories) {
            const cleanDir = dir.replace(/\/$/, '');
            try {
                const s = await stat(join(projectPath, cleanDir));
                if (s.isDirectory()) {
                    score += 2;
                }
            }
            catch {
                // Not found
            }
        }
    }
    // Check files
    if (indicators.files && Array.isArray(indicators.files)) {
        for (const file of indicators.files) {
            if (await hasAnyFile(projectPath, [file])) {
                score += 2;
            }
        }
    }
    // Check dependencies
    if (indicators.dependencies && Array.isArray(indicators.dependencies)) {
        const deps = await collectDependencies(projectPath);
        for (const dep of indicators.dependencies) {
            if (deps.has(dep)) {
                score += 1;
            }
        }
    }
    return score;
}
const LAYER_MAP = {
    presentation: {
        dirs: ['pages', 'views', 'screens', 'components', 'ui', 'app'],
        responsibility: 'User interface and request handling',
        dependsOn: ['application'],
        neverDependsOn: ['infrastructure'],
    },
    application: {
        dirs: ['services', 'use-cases', 'usecases', 'application', 'handlers'],
        responsibility: 'Business logic orchestration and use cases',
        dependsOn: ['domain'],
        neverDependsOn: [],
    },
    domain: {
        dirs: ['domain', 'models', 'entities', 'core'],
        responsibility: 'Business rules and domain entities',
        dependsOn: [],
        neverDependsOn: ['infrastructure', 'presentation'],
    },
    infrastructure: {
        dirs: ['repositories', 'adapters', 'infrastructure', 'db', 'api', 'lib', 'utils'],
        responsibility: 'External systems integration: database, APIs, file system',
        dependsOn: ['domain'],
        neverDependsOn: [],
    },
};
/**
 * Detect architectural layers from directory structure.
 */
export async function detectLayers(projectPath) {
    const layers = [];
    for (const [name, config] of Object.entries(LAYER_MAP)) {
        const foundDirs = [];
        for (const dir of config.dirs) {
            for (const prefix of ['src', '']) {
                const fullPath = prefix ? join(projectPath, prefix, dir) : join(projectPath, dir);
                try {
                    const s = await stat(fullPath);
                    if (s.isDirectory()) {
                        foundDirs.push(relative(projectPath, fullPath));
                    }
                }
                catch {
                    // Not found
                }
            }
        }
        if (foundDirs.length > 0) {
            layers.push({
                name,
                directories: foundDirs,
                responsibility: config.responsibility,
                dependsOn: config.dependsOn,
                neverDependsOn: config.neverDependsOn,
            });
        }
    }
    return layers;
}
/**
 * Detect module boundaries from directory structure.
 */
export async function detectBoundaries(projectPath) {
    const boundaries = [];
    const moduleDirs = ['src/modules', 'src/features', 'packages', 'apps'];
    for (const moduleDir of moduleDirs) {
        const fullPath = join(projectPath, moduleDir);
        try {
            const entries = await readdir(fullPath, { withFileTypes: true });
            for (const entry of entries) {
                if (entry.isDirectory()) {
                    const modulePath = join(fullPath, entry.name);
                    const hasIndex = (await fileExists(modulePath, 'index.ts')) ||
                        (await fileExists(modulePath, 'index.js')) ||
                        (await fileExists(modulePath, '__init__.py')) ||
                        (await fileExists(modulePath, 'mod.rs'));
                    boundaries.push({
                        name: entry.name,
                        directories: [relative(projectPath, modulePath)],
                        publicApi: hasIndex ? [`${relative(projectPath, modulePath)}/index.*`] : [],
                        internalOnly: [],
                    });
                }
            }
        }
        catch {
            // Directory doesn't exist
        }
    }
    return boundaries;
}
/**
 * Infer communication patterns from the primary architecture pattern.
 */
export function inferCommunicationPatterns(primary) {
    const patternComms = {
        monolith: ['direct-import'],
        'modular-monolith': ['module-public-api', 'direct-import'],
        layered: ['top-down-import'],
        'clean-architecture': ['dependency-inversion'],
        hexagonal: ['ports-adapters'],
        microservices: ['REST', 'gRPC', 'message-queue'],
        serverless: ['HTTP', 'events'],
        'event-driven': ['async-events', 'message-queue'],
        monorepo: ['workspace-imports'],
        cqrs: ['commands', 'queries', 'events'],
        'event-sourcing': ['event-log', 'projections'],
        ddd: ['domain-events', 'anti-corruption-layer'],
        'vertical-slice': ['mediator-pattern', 'direct'],
        spa: ['REST-API', 'client-state'],
        ssr: ['server-render', 'hydration'],
    };
    return patternComms[primary] ?? ['direct-import'];
}
/**
 * Detect deployment units/platforms used in the project.
 */
export async function detectDeploymentUnits(projectPath) {
    const units = [];
    const checks = [
        ['docker', 'Dockerfile'],
        ['docker-compose', ['docker-compose.yml', 'docker-compose.yaml']],
        ['vercel', 'vercel.json'],
        ['netlify', 'netlify.toml'],
        ['railway', ['railway.json', 'railway.toml']],
        ['fly.io', 'fly.toml'],
        ['render', 'render.yaml'],
    ];
    for (const [unit, files] of checks) {
        const fileList = Array.isArray(files) ? files : [files];
        for (const file of fileList) {
            if (await fileExists(projectPath, file)) {
                units.push(unit);
                break;
            }
        }
    }
    if (await hasAnyFile(projectPath, ['*.tf'])) {
        units.push('terraform');
    }
    if (units.length === 0) {
        units.push('manual');
    }
    return units;
}
//# sourceMappingURL=architecture-detector.js.map