"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.resolveModulesRootFromContract = resolveModulesRootFromContract;
exports.resolveDocsRootFromContract = resolveDocsRootFromContract;
exports.resolveContractsRootFromContract = resolveContractsRootFromContract;
exports.resolveApiRootFromContract = resolveApiRootFromContract;
exports.resolveDatabaseRootFromContract = resolveDatabaseRootFromContract;
exports.resolveConfiguredModulesRoot = resolveConfiguredModulesRoot;
exports.listModuleDirectoryNames = listModuleDirectoryNames;
exports.discoverHierarchicalModuleRoots = discoverHierarchicalModuleRoots;
exports.discoverModuleRoots = discoverModuleRoots;
const path = __importStar(require("node:path"));
const node_fs_1 = require("node:fs");
const stackAdapters_1 = require("./stackAdapters");
function normalizePath(filePath) {
    return filePath.replace(/\\/g, '/');
}
async function pathExists(targetPath) {
    try {
        await node_fs_1.promises.access(targetPath);
        return true;
    }
    catch {
        return false;
    }
}
async function readTextFileIfExists(filePath) {
    try {
        return await node_fs_1.promises.readFile(filePath, { encoding: 'utf8' });
    }
    catch {
        return undefined;
    }
}
function pickModulesRootFromParsed(parsed, fallbackModulesRoot) {
    const topLevelRelative = typeof parsed.modulesRootRelativePath === 'string'
        ? normalizePath(parsed.modulesRootRelativePath)
        : undefined;
    const structureRaw = parsed.structure && typeof parsed.structure === 'object'
        ? parsed.structure
        : undefined;
    const structureRelative = typeof structureRaw?.modulesRootRelativePath === 'string'
        ? normalizePath(structureRaw.modulesRootRelativePath)
        : undefined;
    const structureModulesRoot = typeof structureRaw?.modulesRoot === 'string'
        ? normalizePath(structureRaw.modulesRoot)
        : undefined;
    return topLevelRelative ?? structureRelative ?? structureModulesRoot ?? normalizePath(fallbackModulesRoot);
}
function pickStructurePathFromContract(contract, structureKey, fallbackPath) {
    if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {
        return normalizePath(fallbackPath);
    }
    const contractRecord = contract;
    const structureRecord = contractRecord.structure && typeof contractRecord.structure === 'object'
        ? contractRecord.structure
        : undefined;
    const fromStructure = structureRecord?.[structureKey];
    return typeof fromStructure === 'string' && fromStructure.trim().length > 0
        ? normalizePath(fromStructure)
        : normalizePath(fallbackPath);
}
function resolveModulesRootFromContract(contract, fallbackModulesRoot = 'src/modules') {
    if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {
        return normalizePath(fallbackModulesRoot);
    }
    const adapterFallback = (0, stackAdapters_1.resolveDefaultModulesRootFromContract)(contract, fallbackModulesRoot);
    return pickModulesRootFromParsed(contract, adapterFallback);
}
function resolveDocsRootFromContract(contract, fallbackDocsRoot = 'docs') {
    return pickStructurePathFromContract(contract, 'docsRoot', fallbackDocsRoot);
}
function resolveContractsRootFromContract(contract, fallbackContractsRoot = '.archpilot/contracts') {
    return pickStructurePathFromContract(contract, 'contractsRoot', fallbackContractsRoot);
}
function resolveApiRootFromContract(contract, fallbackApiRoot = 'contracts') {
    return pickStructurePathFromContract(contract, 'apiRoot', fallbackApiRoot);
}
function resolveDatabaseRootFromContract(contract, fallbackDatabaseRoot = 'db/sql') {
    return pickStructurePathFromContract(contract, 'databaseRoot', fallbackDatabaseRoot);
}
async function resolveConfiguredModulesRoot(workspaceRoot, fallbackModulesRoot = 'src/modules') {
    const architectureConfigPath = path.join(workspaceRoot, '.archpilot', 'architecture.json');
    const configRaw = await readTextFileIfExists(architectureConfigPath);
    if (!configRaw) {
        return normalizePath(fallbackModulesRoot);
    }
    try {
        const parsed = JSON.parse(configRaw);
        const adapterFallback = (0, stackAdapters_1.resolveDefaultModulesRootFromContract)(parsed, fallbackModulesRoot);
        return pickModulesRootFromParsed(parsed, adapterFallback);
    }
    catch {
        return normalizePath(fallbackModulesRoot);
    }
}
async function listModuleDirectoryNames(workspaceRoot, modulesRootRelativePath = 'src/modules') {
    const modulesRoot = path.join(workspaceRoot, ...modulesRootRelativePath.split('/'));
    if (!(await pathExists(modulesRoot))) {
        return [];
    }
    const entries = await node_fs_1.promises.readdir(modulesRoot, { withFileTypes: true });
    return entries
        .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
        .map((entry) => entry.name)
        .sort((left, right) => left.localeCompare(right));
}
const ignoredModuleDirectoryNames = new Set([
    '.git',
    '.archpilot',
    '.vscode',
    '.vscode-test',
    'node_modules',
    'dist',
    'build',
    'out',
    'coverage',
    '.next',
    'generated',
    'target',
    'vendor',
    '.venv',
    'venv',
    '__pycache__',
]);
const noisyModuleDirectoryNames = new Set([
    'components',
    'hooks',
    'utils',
    'util',
    'lib',
    'common',
    'shared',
    'shared-ui',
    'types',
    'constants',
    'config',
    'test',
    'tests',
    '__tests__',
    'fixtures',
    'mocks',
    '__mocks__',
    'generated',
    'dist',
    'build',
    'out',
    'node_modules',
]);
const sourceFileExtensions = new Set([
    '.ts',
    '.tsx',
    '.js',
    '.jsx',
    '.vue',
    '.java',
    '.py',
    '.php',
    '.go',
    '.cs',
    '.rb',
    '.kt',
    '.swift',
    '.rs',
]);
const publicEntrypointNames = new Set([
    'index.ts',
    'index.tsx',
    'index.js',
    'index.jsx',
    'index.vue',
    '__init__.py',
    'main.go',
    'mod.ts',
    'mod.js',
]);
function isIgnoredDirectoryName(directoryName) {
    return ignoredModuleDirectoryNames.has(directoryName.toLowerCase());
}
function isNoisyModuleName(moduleName) {
    return noisyModuleDirectoryNames.has(moduleName.toLowerCase());
}
function toPathSegments(relativePath) {
    return normalizePath(relativePath).split('/').filter((segment) => segment.length > 0);
}
function toModuleIdFromPath(relativePath, fallbackRoot) {
    const segments = toPathSegments(relativePath);
    const lowerSegments = segments.map((segment) => segment.toLowerCase());
    const sourceIndex = lowerSegments.lastIndexOf('src');
    if (sourceIndex >= 0 && sourceIndex + 2 < segments.length) {
        const parent = segments[sourceIndex - 1];
        const container = lowerSegments[sourceIndex + 1];
        if (['modules', 'features'].includes(container)) {
            return [parent, segments[sourceIndex + 2]].filter(Boolean).join('/');
        }
        if (['domain', 'domains', 'application', 'infrastructure'].includes(container)) {
            return [segments[sourceIndex + 1], segments[sourceIndex + 2]].join('/');
        }
        return [parent, segments[sourceIndex + 1]].filter(Boolean).join('/');
    }
    if (sourceIndex >= 0 && sourceIndex + 1 < segments.length) {
        const parent = segments[sourceIndex - 1];
        return [parent, segments[sourceIndex + 1]].filter(Boolean).join('/');
    }
    if (segments[0] === 'src' && segments.length >= 3) {
        return `${segments[1]}/${segments[2]}`;
    }
    if (fallbackRoot) {
        const rootSegments = toPathSegments(fallbackRoot);
        const relativeSegments = segments.slice(rootSegments.length);
        if (relativeSegments.length > 0) {
            return relativeSegments.join('/');
        }
    }
    return segments.slice(-1)[0] ?? relativePath;
}
function isKnownNestedModuleRoot(relativePath) {
    const segments = toPathSegments(relativePath).map((segment) => segment.toLowerCase());
    if (segments.length < 2) {
        return false;
    }
    const lastParent = segments[segments.length - 2];
    if (['modules', 'features', 'domain', 'domains', 'application', 'infrastructure'].includes(lastParent)) {
        return true;
    }
    if (segments.length >= 3 && segments[segments.length - 3] === 'src') {
        return true;
    }
    return false;
}
async function inspectPotentialModuleRoot(absolutePath) {
    let sourceFileCount = 0;
    let hasPublicEntrypoint = false;
    const queue = [{ absolutePath, depth: 0 }];
    while (queue.length > 0) {
        const current = queue.shift();
        if (!current) {
            continue;
        }
        let entries;
        try {
            entries = await node_fs_1.promises.readdir(current.absolutePath, { withFileTypes: true });
        }
        catch {
            continue;
        }
        for (const entry of entries) {
            const entryPath = path.join(current.absolutePath, entry.name);
            if (entry.isDirectory()) {
                if (current.depth >= 2 || isIgnoredDirectoryName(entry.name)) {
                    continue;
                }
                queue.push({ absolutePath: entryPath, depth: current.depth + 1 });
                continue;
            }
            if (!entry.isFile()) {
                continue;
            }
            if (sourceFileExtensions.has(path.extname(entry.name).toLowerCase())) {
                sourceFileCount += 1;
            }
            if (current.depth === 0 && publicEntrypointNames.has(entry.name.toLowerCase())) {
                hasPublicEntrypoint = true;
            }
        }
    }
    return { sourceFileCount, hasPublicEntrypoint };
}
async function addModuleIfStrongCandidate(workspaceRoot, discovered, sourcePath, moduleId, options) {
    const normalizedSourcePath = normalizePath(sourcePath);
    const name = normalizedSourcePath.split('/').pop() ?? moduleId;
    if (options?.allowNoisyNames !== true && isNoisyModuleName(name)) {
        return;
    }
    const absolutePath = path.join(workspaceRoot, ...normalizedSourcePath.split('/'));
    const inspected = await inspectPotentialModuleRoot(absolutePath);
    const hasStrongEvidence = inspected.sourceFileCount >= 2 ||
        inspected.hasPublicEntrypoint ||
        isKnownNestedModuleRoot(normalizedSourcePath);
    if (options?.requireStrongEvidence !== false && !hasStrongEvidence) {
        return;
    }
    const normalizedModuleId = normalizePath(moduleId || toModuleIdFromPath(normalizedSourcePath, options?.fallbackRoot));
    if (!normalizedModuleId ||
        (options?.allowNoisyNames !== true &&
            isNoisyModuleName(normalizedModuleId.split('/').pop() ?? normalizedModuleId))) {
        return;
    }
    if (!discovered.has(normalizedModuleId)) {
        discovered.set(normalizedModuleId, {
            moduleId: normalizedModuleId,
            sourcePath: normalizedSourcePath,
        });
    }
}
async function addChildrenFromRoot(workspaceRoot, discovered, rootRelativePath, options) {
    const normalizedRoot = normalizePath(rootRelativePath);
    const absoluteRoot = path.join(workspaceRoot, ...normalizedRoot.split('/'));
    if (!(await pathExists(absoluteRoot))) {
        return;
    }
    const entries = (await node_fs_1.promises.readdir(absoluteRoot, { withFileTypes: true }))
        .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && !isIgnoredDirectoryName(entry.name))
        .sort((left, right) => left.name.localeCompare(right.name));
    for (const entry of entries) {
        const sourcePath = `${normalizedRoot}/${entry.name}`;
        const moduleId = options?.includeParentPrefix && options.parentId
            ? `${options.parentId}/${entry.name}`
            : toModuleIdFromPath(sourcePath, normalizedRoot);
        await addModuleIfStrongCandidate(workspaceRoot, discovered, sourcePath, moduleId, {
            fallbackRoot: normalizedRoot,
            ...(options?.requireStrongEvidence !== undefined
                ? { requireStrongEvidence: options.requireStrongEvidence }
                : {}),
            ...(options?.allowNoisyNames !== undefined
                ? { allowNoisyNames: options.allowNoisyNames }
                : {}),
        });
    }
}
async function addMonorepoContainerModules(workspaceRoot, discovered, container) {
    const absoluteContainer = path.join(workspaceRoot, container);
    if (!(await pathExists(absoluteContainer))) {
        return;
    }
    const entries = (await node_fs_1.promises.readdir(absoluteContainer, { withFileTypes: true }))
        .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && !isIgnoredDirectoryName(entry.name))
        .sort((left, right) => left.name.localeCompare(right.name));
    for (const entry of entries) {
        const packagePath = `${container}/${entry.name}`;
        await addModuleIfStrongCandidate(workspaceRoot, discovered, packagePath, entry.name, {
            requireStrongEvidence: false,
        });
        for (const sourceRoot of [
            `${packagePath}/src`,
            `${packagePath}/src/modules`,
            `${packagePath}/src/features`,
            `${packagePath}/src/domain`,
            `${packagePath}/src/domains`,
            `${packagePath}/src/application`,
            `${packagePath}/src/infrastructure`,
        ]) {
            await addChildrenFromRoot(workspaceRoot, discovered, sourceRoot, {
                parentId: entry.name,
                includeParentPrefix: true,
            });
        }
    }
}
async function discoverHierarchicalModuleRoots(workspaceRoot, modulesRootRelativePath = 'src/modules') {
    const discovered = new Map();
    await addChildrenFromRoot(workspaceRoot, discovered, normalizePath(modulesRootRelativePath), {
        requireStrongEvidence: false,
        allowNoisyNames: normalizePath(modulesRootRelativePath) === 'src',
    });
    for (const root of [
        'src/modules',
        'src/features',
        'src/domain',
        'src/domains',
        'src/application',
        'src/infrastructure',
    ]) {
        await addChildrenFromRoot(workspaceRoot, discovered, root);
    }
    for (const container of ['packages', 'apps', 'services']) {
        await addMonorepoContainerModules(workspaceRoot, discovered, container);
    }
    return [...discovered.values()].sort((left, right) => left.moduleId.localeCompare(right.moduleId));
}
async function discoverModuleRoots(workspaceRoot, modulesRootRelativePath = 'src/modules') {
    const hierarchicalModules = await discoverHierarchicalModuleRoots(workspaceRoot, modulesRootRelativePath);
    if (hierarchicalModules.length > 0) {
        return new Map(hierarchicalModules.map((entry) => [
            entry.moduleId,
            path.join(workspaceRoot, ...entry.sourcePath.split('/')),
        ]));
    }
    const moduleDirectories = await listModuleDirectoryNames(workspaceRoot, modulesRootRelativePath);
    const modulesRoot = path.join(workspaceRoot, ...modulesRootRelativePath.split('/'));
    return new Map(moduleDirectories.map((moduleName) => [moduleName, path.join(modulesRoot, moduleName)]));
}
//# sourceMappingURL=moduleDiscovery.js.map