"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectArchitectureStyleSignals = detectArchitectureStyleSignals;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const ignoredDirectoryNames = new Set([
    '.git',
    '.archpilot',
    '.vscode-test',
    'node_modules',
    'dist',
    'build',
    'out',
    'coverage',
    '.next',
    'target',
    'vendor',
    '.venv',
    'venv',
    '__pycache__',
]);
function normalizePath(value) {
    return value.replaceAll('\\', '/');
}
function isUnderJvmResourceRoot(relativePath) {
    return /(^|\/)src\/main\/resources(?:\/|$)/u.test(normalizePath(relativePath).toLowerCase());
}
function unique(values) {
    return [...new Set(values)];
}
function addEvidence(evidence, value) {
    if (!evidence.includes(value)) {
        evidence.push(value);
    }
}
function safeReadDirEntries(directoryPath) {
    try {
        return fs.readdirSync(directoryPath, { withFileTypes: true });
    }
    catch {
        return [];
    }
}
function safeReadText(filePath, maxChars = 30_000) {
    try {
        return fs.readFileSync(filePath, { encoding: 'utf8' }).slice(0, maxChars);
    }
    catch {
        return undefined;
    }
}
function pathExists(pathValue) {
    try {
        fs.accessSync(pathValue);
        return true;
    }
    catch {
        return false;
    }
}
function hasManifest(workspaceRoot, root) {
    const rootAbsolutePath = root === '.'
        ? workspaceRoot
        : path.join(workspaceRoot, ...root.split('/'));
    return ['package.json', 'pom.xml', 'requirements.txt', 'go.mod', 'composer.json']
        .some((fileName) => pathExists(path.join(rootAbsolutePath, fileName)));
}
function isIgnoredPath(ignoreMatcher, relativePath) {
    return ignoreMatcher?.isIgnored(normalizePath(relativePath)) ?? false;
}
function isSupportingAssetRoot(relativePath) {
    const normalized = normalizePath(relativePath).replace(/^\.\//u, '');
    if (normalized.length === 0 || normalized === '.') {
        return false;
    }
    const supportingSegments = new Set([
        'public',
        'static',
        'assets',
        'vendor',
        'plugins',
        'plugin',
        'themes',
        'theme',
        'generated',
        'storage',
        'dist',
        'build',
        'coverage',
    ]);
    return normalized.split('/').some((segment) => supportingSegments.has(segment.toLowerCase()));
}
function scanRoot(rootAbsolutePath, options) {
    const maxDepth = options?.maxDepth ?? 4;
    const files = [];
    const directories = [];
    const queue = [
        { absolutePath: rootAbsolutePath, relativePath: '', depth: 0 },
    ];
    while (queue.length > 0) {
        const current = queue.shift();
        if (!current) {
            continue;
        }
        const entries = safeReadDirEntries(current.absolutePath)
            .sort((left, right) => left.name.localeCompare(right.name));
        for (const entry of entries) {
            const absoluteEntryPath = path.join(current.absolutePath, entry.name);
            const relativeEntryPath = current.relativePath.length > 0
                ? `${current.relativePath}/${entry.name}`
                : entry.name;
            const repositoryRelativePath = options?.rootRelativePath && options.rootRelativePath !== '.'
                ? `${options.rootRelativePath}/${relativeEntryPath}`
                : relativeEntryPath;
            if (isIgnoredPath(options?.ignoreMatcher, repositoryRelativePath)) {
                continue;
            }
            if (entry.isDirectory()) {
                if (current.depth >= maxDepth || ignoredDirectoryNames.has(entry.name)) {
                    continue;
                }
                directories.push(normalizePath(relativeEntryPath));
                queue.push({
                    absolutePath: absoluteEntryPath,
                    relativePath: relativeEntryPath,
                    depth: current.depth + 1,
                });
                continue;
            }
            if (entry.isFile()) {
                files.push(normalizePath(relativeEntryPath));
            }
        }
    }
    return {
        files: unique(files).sort((left, right) => left.localeCompare(right)),
        directories: unique(directories).sort((left, right) => left.localeCompare(right)),
    };
}
function fileExists(files, fileName) {
    return files.some((entry) => entry === fileName || entry.endsWith(`/${fileName}`));
}
function hasDirectory(directories, directoryName) {
    return directories.some((entry) => entry === directoryName || entry.endsWith(`/${directoryName}`));
}
function readPackageJsonSummary(rootAbsolutePath) {
    const packageJsonPath = path.join(rootAbsolutePath, 'package.json');
    const content = safeReadText(packageJsonPath, 60_000);
    if (!content) {
        return {
            dependencies: new Set(),
            hasExports: false,
            hasMainLikeFields: false,
        };
    }
    try {
        const parsed = JSON.parse(content);
        const dependencies = new Set();
        for (const fieldName of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
            const value = parsed[fieldName];
            if (!value || typeof value !== 'object' || Array.isArray(value)) {
                continue;
            }
            for (const dependencyName of Object.keys(value)) {
                dependencies.add(dependencyName);
            }
        }
        return {
            dependencies,
            hasExports: parsed.exports !== undefined,
            hasMainLikeFields: typeof parsed.main === 'string' ||
                typeof parsed.module === 'string' ||
                typeof parsed.types === 'string',
        };
    }
    catch {
        return {
            dependencies: new Set(),
            hasExports: false,
            hasMainLikeFields: false,
        };
    }
}
function hasAnyDependency(summary, dependencyNames) {
    return dependencyNames.some((name) => summary.dependencies.has(name));
}
function collectCandidateRoots(workspaceRoot, topology, ignoreMatcher) {
    const roots = new Set();
    for (const root of topology.detectedRoots ?? []) {
        if (root && root !== '.' && !isIgnoredPath(ignoreMatcher, root) && !isSupportingAssetRoot(root) && !isUnderJvmResourceRoot(root)) {
            roots.add(normalizePath(root));
        }
    }
    for (const base of ['apps', 'services', 'packages', 'libs']) {
        const absoluteBase = path.join(workspaceRoot, base);
        for (const entry of safeReadDirEntries(absoluteBase)) {
            const relativePath = `${base}/${entry.name}`;
            if (!entry.isDirectory() ||
                ignoredDirectoryNames.has(entry.name) ||
                isIgnoredPath(ignoreMatcher, relativePath) ||
                isSupportingAssetRoot(relativePath) ||
                isUnderJvmResourceRoot(relativePath) ||
                ((topology.detectedRoots?.length ?? 0) > 0 &&
                    !topology.detectedRoots?.includes(relativePath) &&
                    !hasManifest(workspaceRoot, relativePath))) {
                continue;
            }
            roots.add(relativePath);
        }
    }
    for (const root of ['backend', 'frontend']) {
        if (!isIgnoredPath(ignoreMatcher, root) && pathExists(path.join(workspaceRoot, root))) {
            roots.add(root);
        }
    }
    if (roots.size === 0 || topology.topology === 'single_project') {
        roots.add('.');
    }
    return [...roots].sort((left, right) => left.localeCompare(right));
}
function detectServiceCommunication(rootAbsolutePath, scan, packageJson, evidence) {
    if (hasAnyDependency(packageJson, [
        'axios',
        'got',
        'node-fetch',
        'undici',
        '@grpc/grpc-js',
        'amqplib',
        'kafkajs',
        'nats',
    ])) {
        addEvidence(evidence, 'Found HTTP/messaging client dependency');
        return true;
    }
    const candidateFiles = scan.files
        .filter((entry) => /\.(ts|tsx|js|jsx|py|java|kt)$/iu.test(entry))
        .filter((entry) => !/(^|\/)(test|spec|__tests__|fixtures)\//iu.test(entry))
        .slice(0, 80);
    for (const relativeFile of candidateFiles) {
        const content = safeReadText(path.join(rootAbsolutePath, ...relativeFile.split('/')));
        if (!content) {
            continue;
        }
        if (/\baxios\./u.test(content) ||
            /\bfetch\s*\(\s*['"`]https?:\/\//u.test(content) ||
            /\bClientProxyFactory\b/u.test(content) ||
            /\bgrpc\b/u.test(content) ||
            /\bkafka\b/u.test(content) ||
            /\bamqp\b/u.test(content)) {
            addEvidence(evidence, `Found service communication hint in ${relativeFile}`);
            return true;
        }
    }
    return false;
}
function inspectRoot(workspaceRoot, root, ignoreMatcher) {
    const rootAbsolutePath = root === '.'
        ? workspaceRoot
        : path.join(workspaceRoot, ...root.split('/'));
    const scan = scanRoot(rootAbsolutePath, { rootRelativePath: root, ignoreMatcher });
    const packageJson = readPackageJsonSummary(rootAbsolutePath);
    const evidence = [];
    const hasApiBoundary = hasDirectory(scan.directories, 'controllers') ||
        hasDirectory(scan.directories, 'routes') ||
        hasDirectory(scan.directories, 'router') ||
        hasDirectory(scan.directories, 'src/app/api') ||
        scan.files.some((entry) => /(^|\/)(.*controller.*|.*route.*|.*router.*)\.(ts|tsx|js|jsx|py|java|kt)$/iu.test(entry)) ||
        fileExists(scan.files, 'openapi.yaml') ||
        fileExists(scan.files, 'openapi.yml') ||
        fileExists(scan.files, 'swagger.yaml') ||
        fileExists(scan.files, 'swagger.yml') ||
        hasAnyDependency(packageJson, ['express', 'fastify', '@nestjs/core', 'koa', 'hapi', 'fastapi', '@apollo/server']);
    if (hasApiBoundary) {
        addEvidence(evidence, 'Detected controllers/routes or backend API framework signals');
    }
    const hasDatabaseAccess = hasDirectory(scan.directories, 'db') ||
        hasDirectory(scan.directories, 'migrations') ||
        fileExists(scan.files, 'prisma/schema.prisma') ||
        fileExists(scan.files, 'ormconfig.ts') ||
        fileExists(scan.files, 'ormconfig.js') ||
        fileExists(scan.files, 'ormconfig.json') ||
        fileExists(scan.files, 'knexfile.ts') ||
        fileExists(scan.files, 'knexfile.js') ||
        fileExists(scan.files, 'application.yml') ||
        fileExists(scan.files, 'application.yaml') ||
        fileExists(scan.files, 'application.properties') ||
        hasAnyDependency(packageJson, [
            '@prisma/client',
            'prisma',
            'typeorm',
            'sequelize',
            'knex',
            'drizzle-orm',
            'mongoose',
            'mongodb',
            'pg',
            'mysql2',
        ]);
    if (hasDatabaseAccess) {
        addEvidence(evidence, 'Detected database or ORM configuration');
    }
    const hasDeploymentConfig = scan.files.some((entry) => /(^|\/)Dockerfile(?:\.[^/]+)?$/u.test(entry)) ||
        hasDirectory(scan.directories, 'deploy') ||
        hasDirectory(scan.directories, 'deployment') ||
        hasDirectory(scan.directories, 'k8s') ||
        hasDirectory(scan.directories, 'helm');
    if (hasDeploymentConfig) {
        addEvidence(evidence, 'Detected service-level deployment configuration');
    }
    const hasServiceEnvConfig = scan.files.some((entry) => /(^|\/)\.env(?:\.[^/]+)?$/u.test(entry));
    if (hasServiceEnvConfig) {
        addEvidence(evidence, 'Detected service-specific environment configuration');
    }
    const hasOpenApiSpec = fileExists(scan.files, 'openapi.yaml') ||
        fileExists(scan.files, 'openapi.yml') ||
        fileExists(scan.files, 'openapi.json') ||
        fileExists(scan.files, 'contracts/openapi.yaml') ||
        fileExists(scan.files, 'contracts/openapi.yml') ||
        fileExists(scan.files, 'swagger.yaml') ||
        fileExists(scan.files, 'swagger.yml') ||
        fileExists(scan.files, 'swagger.json');
    if (hasOpenApiSpec) {
        addEvidence(evidence, 'Detected service-specific OpenAPI contract');
    }
    const hasModulesRoot = hasDirectory(scan.directories, 'src/modules') ||
        hasDirectory(scan.directories, 'modules');
    if (hasModulesRoot) {
        addEvidence(evidence, 'Detected modules root');
    }
    const hasFrontendSignals = fileExists(scan.files, 'next.config.js') ||
        fileExists(scan.files, 'next.config.ts') ||
        fileExists(scan.files, 'angular.json') ||
        fileExists(scan.files, 'vite.config.ts') ||
        fileExists(scan.files, 'vite.config.js') ||
        hasDirectory(scan.directories, 'pages') ||
        hasDirectory(scan.directories, 'public') ||
        hasAnyDependency(packageJson, ['next', 'react', 'vue', '@angular/core']);
    const hasRunnableFrontendSignals = fileExists(scan.files, 'next.config.js') ||
        fileExists(scan.files, 'next.config.ts') ||
        fileExists(scan.files, 'angular.json') ||
        fileExists(scan.files, 'vite.config.ts') ||
        fileExists(scan.files, 'vite.config.js') ||
        fileExists(scan.files, 'index.html') ||
        hasDirectory(scan.directories, 'pages') ||
        hasDirectory(scan.directories, 'src/app');
    const hasBackendSignals = hasApiBoundary ||
        hasDatabaseAccess ||
        fileExists(scan.files, 'pom.xml') ||
        fileExists(scan.files, 'requirements.txt') ||
        fileExists(scan.files, 'go.mod') ||
        hasDirectory(scan.directories, 'src/main/java');
    let classification = 'unknown';
    if ((root.startsWith('packages/') || root.startsWith('libs/')) &&
        !hasRunnableFrontendSignals &&
        !hasApiBoundary) {
        classification = 'shared_package';
    }
    else if (hasBackendSignals && !hasFrontendSignals) {
        classification = 'backend_app';
    }
    else if (hasFrontendSignals && !hasBackendSignals) {
        classification = 'frontend_app';
    }
    else if (root.startsWith('packages/') ||
        root.startsWith('libs/') ||
        (packageJson.hasExports || packageJson.hasMainLikeFields) && !hasApiBoundary) {
        classification = 'shared_package';
    }
    const hasServiceToServiceCommunication = detectServiceCommunication(rootAbsolutePath, scan, packageJson, evidence);
    const isStrongMicroserviceCandidate = classification === 'backend_app' &&
        hasApiBoundary &&
        hasDatabaseAccess &&
        (hasDeploymentConfig || hasServiceEnvConfig || hasOpenApiSpec);
    if (classification === 'backend_app') {
        addEvidence(evidence, `Classified ${root} as a backend app`);
    }
    else if (classification === 'frontend_app') {
        addEvidence(evidence, `Classified ${root} as a frontend app`);
    }
    else if (classification === 'shared_package') {
        addEvidence(evidence, `Classified ${root} as a shared package`);
    }
    return {
        root,
        classification,
        hasApiBoundary,
        hasDatabaseAccess,
        hasDeploymentConfig,
        hasServiceEnvConfig,
        hasOpenApiSpec,
        hasServiceToServiceCommunication,
        hasModulesRoot,
        isStrongMicroserviceCandidate,
        evidence,
    };
}
function detectArchitectureStyleSignals(workspaceRoot, topology, options) {
    const roots = collectCandidateRoots(workspaceRoot, topology, options?.ignoreMatcher)
        .map((root) => inspectRoot(workspaceRoot, root, options?.ignoreMatcher));
    const backendRoots = roots.filter((entry) => entry.classification === 'backend_app');
    const frontendRoots = roots.filter((entry) => entry.classification === 'frontend_app');
    const sharedPackages = roots.filter((entry) => entry.classification === 'shared_package');
    const strongMicroserviceRoots = backendRoots.filter((entry) => entry.isStrongMicroserviceCandidate);
    const evidence = [];
    if (backendRoots.length > 0) {
        addEvidence(evidence, `Detected ${backendRoots.length} backend app root${backendRoots.length === 1 ? '' : 's'}`);
    }
    if (frontendRoots.length > 0) {
        addEvidence(evidence, `Detected ${frontendRoots.length} frontend app root${frontendRoots.length === 1 ? '' : 's'}`);
    }
    if (sharedPackages.length > 0) {
        addEvidence(evidence, `Detected ${sharedPackages.length} shared package root${sharedPackages.length === 1 ? '' : 's'}`);
    }
    if (strongMicroserviceRoots.length > 0) {
        addEvidence(evidence, `Detected ${strongMicroserviceRoots.length} backend service root${strongMicroserviceRoots.length === 1 ? '' : 's'} with API + DB + deploy/env/OpenAPI evidence`);
    }
    const moduleRootCount = roots.filter((entry) => entry.hasModulesRoot).length;
    if (moduleRootCount > 0) {
        addEvidence(evidence, `Detected ${moduleRootCount} module root${moduleRootCount === 1 ? '' : 's'}`);
    }
    const serviceCommunicationCount = backendRoots.filter((entry) => entry.hasServiceToServiceCommunication).length;
    if (serviceCommunicationCount > 0) {
        addEvidence(evidence, `Detected service-to-service communication hints in ${serviceCommunicationCount} backend root${serviceCommunicationCount === 1 ? '' : 's'}`);
    }
    return {
        roots,
        backendAppCount: backendRoots.length,
        frontendAppCount: frontendRoots.length,
        sharedPackageCount: sharedPackages.length,
        moduleRootCount,
        strongMicroserviceCount: strongMicroserviceRoots.length,
        serviceCommunicationCount,
        deploymentConfiguredServiceCount: backendRoots.filter((entry) => entry.hasDeploymentConfig).length,
        envConfiguredServiceCount: backendRoots.filter((entry) => entry.hasServiceEnvConfig).length,
        openApiServiceCount: backendRoots.filter((entry) => entry.hasOpenApiSpec).length,
        evidence,
    };
}
//# sourceMappingURL=architectureStyleDetection.js.map