"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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleRegistry = void 0;
const path = __importStar(require("path"));
const module_resolver_1 = require("./module-resolver");
class ModuleRegistry {
    constructor() {
        this.modules = new Map();
        this.dependencyGraph = new Map();
        this.resolver = new module_resolver_1.ModuleResolver();
    }
    /**
     * Register a loaded module
     */
    register(module) {
        // Validate module ID is absolute path or external module
        if (!path.isAbsolute(module.id) && !module.id.startsWith('external:')) {
            throw new Error(`Module ID must be absolute path or external module, got: ${module.id}`);
        }
        const metadata = {
            id: module.id,
            resolvedPath: module.resolvedPath,
            dependencies: [...module.dependencies],
            dependents: [],
            exports: { ...module.exports },
            imports: this.extractImports(module.ast),
            lastModified: new Date(),
            size: module.source.length,
        };
        this.modules.set(module.id, metadata);
        this.updateDependencyGraph(module.id, module.dependencies);
    }
    /**
     * Get module metadata
     */
    get(moduleId) {
        return this.modules.get(moduleId);
    }
    /**
     * Check if module is registered
     */
    has(moduleId) {
        return this.modules.has(moduleId);
    }
    /**
     * Get all registered modules
     */
    getAll() {
        return Array.from(this.modules.values());
    }
    /**
     * Get module dependencies
     */
    getDependencies(moduleId) {
        const meta = this.modules.get(moduleId);
        return meta ? [...meta.dependencies] : [];
    }
    /**
     * Get module dependents (modules that depend on this one)
     */
    getDependents(moduleId) {
        const node = this.dependencyGraph.get(moduleId);
        return node ? [...node.dependents] : [];
    }
    /**
     * Get dependency graph
     */
    getDependencyGraph() {
        return new Map(this.dependencyGraph);
    }
    /**
     * Get topological sort of modules
     */
    getTopologicalSort() {
        const visited = new Set();
        const visiting = new Set();
        const result = [];
        const visit = (moduleId) => {
            if (visited.has(moduleId))
                return;
            if (visiting.has(moduleId)) {
                throw new Error(`Circular dependency detected: ${moduleId}`);
            }
            visiting.add(moduleId);
            const deps = this.getResolvedDependencies(moduleId);
            for (const depId of deps) {
                visit(depId);
            }
            visiting.delete(moduleId);
            visited.add(moduleId);
            result.push(moduleId);
        };
        for (const moduleId of this.dependencyGraph.keys()) {
            visit(moduleId);
        }
        return result;
    }
    /**
     * Find circular dependencies
     */
    findCircularDependencies() {
        const cycles = [];
        const visited = new Set();
        const visiting = new Set();
        const path = [];
        const visit = (moduleId) => {
            if (visited.has(moduleId))
                return;
            if (visiting.has(moduleId)) {
                // Found a cycle
                const cycleStart = path.indexOf(moduleId);
                cycles.push([...path.slice(cycleStart), moduleId]);
                return;
            }
            visiting.add(moduleId);
            path.push(moduleId);
            const deps = this.getResolvedDependencies(moduleId);
            for (const depId of deps) {
                visit(depId);
            }
            path.pop();
            visiting.delete(moduleId);
            visited.add(moduleId);
        };
        for (const moduleId of this.dependencyGraph.keys()) {
            visit(moduleId);
        }
        return cycles;
    }
    /**
     * Get module statistics
     */
    getStatistics() {
        const totalModules = this.modules.size;
        const allDependencies = Array.from(this.dependencyGraph.values()).map(node => node.dependencies.length);
        const totalDependencies = allDependencies.reduce((sum, count) => sum + count, 0);
        const averageDependencies = totalModules > 0 ? totalDependencies / totalModules : 0;
        const maxDependencyDepth = Math.max(...Array.from(this.dependencyGraph.values()).map(node => node.level), 0);
        const circularDependencies = this.findCircularDependencies().length;
        return {
            totalModules,
            totalDependencies,
            averageDependencies,
            maxDependencyDepth,
            circularDependencies,
        };
    }
    /**
     * Clear registry
     */
    clear() {
        this.modules.clear();
        this.dependencyGraph.clear();
    }
    /**
     * Remove module from registry
     */
    remove(moduleId) {
        const removed = this.modules.delete(moduleId);
        this.dependencyGraph.delete(moduleId);
        // Remove from dependents
        for (const node of this.dependencyGraph.values()) {
            const index = node.dependents.indexOf(moduleId);
            if (index !== -1) {
                node.dependents.splice(index, 1);
            }
        }
        return removed;
    }
    updateDependencyGraph(moduleId, dependencies) {
        // Create or update node
        let node = this.dependencyGraph.get(moduleId);
        if (node) {
            // Update existing node
            node.dependencies = [...dependencies];
        }
        else {
            // Create new node
            node = {
                id: moduleId,
                dependencies: [...dependencies],
                dependents: [],
                level: 0,
            };
            this.dependencyGraph.set(moduleId, node);
        }
        // Update dependents for dependencies (resolve specifiers to module IDs when possible)
        for (const dep of dependencies) {
            try {
                const resolved = this.resolver.resolve(dep, moduleId);
                // Ensure dependency node exists
                if (!this.dependencyGraph.has(resolved.resolvedPath)) {
                    this.dependencyGraph.set(resolved.resolvedPath, {
                        id: resolved.resolvedPath,
                        dependencies: [],
                        dependents: [],
                        level: 0,
                    });
                }
                const depNode = this.dependencyGraph.get(resolved.resolvedPath);
                if (!depNode.dependents.includes(moduleId))
                    depNode.dependents.push(moduleId);
            }
            catch {
                // Ignore unresolved here; validation will report them
            }
        }
        // Calculate levels
        this.calculateLevels();
    }
    calculateLevels() {
        const visited = new Set();
        const calculateLevel = (moduleId) => {
            if (visited.has(moduleId))
                return 0;
            visited.add(moduleId);
            const node = this.dependencyGraph.get(moduleId);
            if (!node || node.dependencies.length === 0) {
                node && (node.level = 0);
                return 0;
            }
            // Use resolved adjacency for level calculation
            const resolvedDeps = this.getResolvedDependencies(moduleId);
            const maxDepLevel = resolvedDeps.length
                ? Math.max(...resolvedDeps.map(depId => calculateLevel(depId)))
                : 0;
            node.level = maxDepLevel + 1;
            return node.level;
        };
        for (const moduleId of this.dependencyGraph.keys()) {
            calculateLevel(moduleId);
        }
    }
    extractImports(ast) {
        const imports = { named: {} };
        for (const statement of ast.body) {
            if (statement.type === 'ImportDeclaration') {
                const importDecl = statement;
                const source = String(importDecl.source.value);
                this.processImportSpecifiers(importDecl.specifiers || [], source, imports);
            }
        }
        return imports;
    }
    /**
     * Get modules that have no dependencies (entry points)
     */
    getEntryPoints() {
        return Array.from(this.dependencyGraph.values())
            .filter(node => node.dependencies.length === 0)
            .map(node => node.id);
    }
    /**
     * Get modules that are not depended upon (dead code candidates)
     */
    getDeadCodeCandidates() {
        return Array.from(this.dependencyGraph.values())
            .filter(node => node.dependents.length === 0)
            .map(node => node.id);
    }
    /**
     * Get dependency tree for a specific module
     */
    getDependencyTree(moduleId, visited = new Set()) {
        // Return a structured dependency tree node. Use a discriminated union for circular nodes.
        if (visited.has(moduleId)) {
            return { id: moduleId, circular: true };
        }
        visited.add(moduleId);
        const node = this.dependencyGraph.get(moduleId);
        if (!node) {
            return { id: moduleId, dependencies: [] };
        }
        return {
            id: moduleId,
            level: node.level,
            dependencies: node.dependencies.map(dep => this.getDependencyTree(dep, new Set(visited))),
        };
    }
    processImportSpecifiers(specifiers, source, imports) {
        for (const specifier of specifiers) {
            if (specifier.type === 'ImportDefaultSpecifier') {
                // Initialize default import list lazily
                imports.default ?? (imports.default = []);
                imports.default.push(source);
            }
            else if (specifier.type === 'ImportSpecifier') {
                if (!imports.named[source])
                    imports.named[source] = [];
                imports.named[source].push(specifier.imported?.name || '');
            }
            else if (specifier.type === 'ImportNamespaceSpecifier') {
                imports.namespace ?? (imports.namespace = []);
                imports.namespace.push(source);
            }
        }
    }
    // Resolve specifier dependencies for a given module ID to registered module IDs
    getResolvedDependencies(moduleId) {
        const node = this.dependencyGraph.get(moduleId);
        if (!node)
            return [];
        const deps = [];
        for (const depSpec of node.dependencies) {
            try {
                const resolved = this.resolver.resolve(depSpec, moduleId);
                const depId = resolved.resolvedPath;
                if (this.dependencyGraph.has(depId)) {
                    deps.push(depId);
                }
            }
            catch {
                // Unresolvable dependency will be caught by validation; ignore for traversal
            }
        }
        return deps;
    }
}
exports.ModuleRegistry = ModuleRegistry;
//# sourceMappingURL=module-registry.js.map