// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export function analyzeCrossModuleDependencies(results) {
    const successful = results.filter((r) => r.status === 'success');
    const dependencies = buildDependencyGraph(successful);
    const circularDeps = detectCircularDeps(dependencies);
    const antipatterns = detectAntipatterns(successful, dependencies, circularDeps);
    const orphanModules = findOrphanModules(successful, dependencies);
    const duplications = findDuplications(successful);
    return { dependencies, circularDeps, antipatterns, duplications, orphanModules };
}
// ---------------------------------------------------------------------------
// Step 1: Build cross-module dependency graph
// ---------------------------------------------------------------------------
function buildDependencyGraph(results) {
    const modulePathMap = buildModulePathMap(results);
    const deps = [];
    const depIndex = new Map();
    for (const result of results) {
        const fromModule = result.module.name;
        const nodes = result.analysis.dependencyGraph.nodes;
        for (const node of nodes) {
            for (const importPath of node.imports) {
                const toModule = resolveImportToModule(importPath, modulePathMap, fromModule);
                if (toModule === null) {
                    continue;
                }
                const key = `${fromModule}→${toModule}`;
                const existing = depIndex.get(key);
                if (existing) {
                    if (!existing.files.includes(node.file)) {
                        existing.files.push(node.file);
                    }
                }
                else {
                    const dep = {
                        from: fromModule,
                        to: toModule,
                        type: 'import',
                        files: [node.file],
                    };
                    depIndex.set(key, dep);
                    deps.push(dep);
                }
            }
        }
    }
    return deps;
}
function buildModulePathMap(results) {
    const map = new Map();
    for (const r of results) {
        map.set(r.module.name, normalizePath(r.module.path));
    }
    return map;
}
function normalizePath(p) {
    return p.replace(/\\/g, '/').replace(/\/$/, '');
}
function resolveImportToModule(importPath, modulePathMap, fromModule) {
    const normalized = normalizePath(importPath);
    for (const [moduleName, modulePath] of modulePathMap) {
        if (moduleName === fromModule) {
            continue;
        }
        // Check if the import path contains the module's directory path
        if (normalized.includes(modulePath) || normalized.includes(`/${moduleName}/`)) {
            return moduleName;
        }
    }
    return null;
}
// ---------------------------------------------------------------------------
// Step 2: Detect circular dependencies via DFS
// ---------------------------------------------------------------------------
function detectCircularDeps(deps) {
    const graph = buildAdjacencyMap(deps);
    const cycles = [];
    const visited = new Set();
    const inStack = new Set();
    for (const node of graph.keys()) {
        if (!visited.has(node)) {
            dfsDetectCycles(node, graph, visited, inStack, [], cycles);
        }
    }
    return deduplicateCycles(cycles);
}
function buildAdjacencyMap(deps) {
    const graph = new Map();
    for (const dep of deps) {
        if (!graph.has(dep.from)) {
            graph.set(dep.from, []);
        }
        if (!graph.has(dep.to)) {
            graph.set(dep.to, []);
        }
        const fromEdges = graph.get(dep.from);
        if (fromEdges) {
            fromEdges.push(dep.to);
        }
    }
    return graph;
}
function dfsDetectCycles(node, graph, visited, inStack, path, cycles) {
    visited.add(node);
    inStack.add(node);
    path.push(node);
    for (const neighbor of graph.get(node) ?? []) {
        if (!visited.has(neighbor)) {
            dfsDetectCycles(neighbor, graph, visited, inStack, path, cycles);
        }
        else if (inStack.has(neighbor)) {
            const cycleStart = path.indexOf(neighbor);
            cycles.push(path.slice(cycleStart));
        }
    }
    path.pop();
    inStack.delete(node);
}
function deduplicateCycles(cycles) {
    const seen = new Set();
    const result = [];
    for (const cycle of cycles) {
        const sorted = [...cycle].sort().join('|');
        if (!seen.has(sorted)) {
            seen.add(sorted);
            result.push(cycle);
        }
    }
    return result;
}
// ---------------------------------------------------------------------------
// Step 3: Detect antipatterns
// ---------------------------------------------------------------------------
function detectAntipatterns(results, deps, circularDeps) {
    return [
        ...detectCircularAntipatterns(circularDeps),
        ...detectGodModule(results, deps),
        ...detectOrphanAntipatterns(results, deps),
        ...detectInconsistentTestPatterns(results),
        ...detectDuplicatedLogicAntipatterns(results),
    ];
}
function detectCircularAntipatterns(circularDeps) {
    return circularDeps.map((cycle) => ({
        description: `Circular dependency detected: ${cycle.join(' → ')}`,
        severity: 'high',
        modules: cycle,
        suggestion: 'Extract shared interfaces or abstractions into a dedicated module to break the cycle.',
    }));
}
function detectGodModule(results, deps) {
    if (results.length === 0 || deps.length === 0) {
        return [];
    }
    const incomingCount = countIncomingDeps(results, deps);
    const threshold = results.length * 0.5;
    const antipatterns = [];
    for (const [moduleName, count] of incomingCount) {
        if (count > threshold) {
            antipatterns.push({
                description: `God module: "${moduleName}" receives ${count} of ${deps.length} cross-module dependencies (${Math.round((count / results.length) * 100)}% of modules depend on it).`,
                severity: 'medium',
                modules: [moduleName],
                suggestion: 'Split this module into smaller, focused modules with well-defined responsibilities.',
            });
        }
    }
    return antipatterns;
}
function countIncomingDeps(results, deps) {
    const counts = new Map();
    for (const r of results) {
        counts.set(r.module.name, 0);
    }
    for (const dep of deps) {
        counts.set(dep.to, (counts.get(dep.to) ?? 0) + 1);
    }
    return counts;
}
function detectOrphanAntipatterns(results, deps) {
    const connectedModules = new Set();
    for (const dep of deps) {
        connectedModules.add(dep.from);
        connectedModules.add(dep.to);
    }
    const orphans = results.map((r) => r.module.name).filter((name) => !connectedModules.has(name));
    return orphans.map((name) => ({
        description: `Orphan module: "${name}" has no cross-module dependencies (neither imports nor is imported).`,
        severity: 'low',
        modules: [name],
        suggestion: 'Verify this module is intentionally standalone or consider integrating it with the project.',
    }));
}
function detectInconsistentTestPatterns(results) {
    const withTests = results.filter((r) => r.analysis.testCoverage.testedModules.length > 0);
    const withoutTests = results.filter((r) => r.analysis.testCoverage.testedModules.length === 0);
    if (withTests.length === 0 || withoutTests.length === 0) {
        return [];
    }
    return [
        {
            description: `Inconsistent test coverage: ${withTests.length} modules have tests but ${withoutTests.length} module(s) have none.`,
            severity: 'medium',
            modules: withoutTests.map((r) => r.module.name),
            suggestion: 'Establish a uniform testing policy and add test coverage to untested modules.',
        },
    ];
}
function detectDuplicatedLogicAntipatterns(results) {
    const duplications = findDuplications(results);
    return duplications.map((dup) => ({
        description: `Duplicated logic detected: function signature "${dup.signature}" appears in ${dup.modules.length} modules.`,
        severity: 'low',
        modules: dup.modules,
        suggestion: 'Extract duplicated logic into a shared utility module to avoid divergence over time.',
    }));
}
// ---------------------------------------------------------------------------
// Step 4: Find orphan modules (no cross-module connections)
// ---------------------------------------------------------------------------
function findOrphanModules(results, deps) {
    const connected = new Set();
    for (const dep of deps) {
        connected.add(dep.from);
        connected.add(dep.to);
    }
    return results.map((r) => r.module.name).filter((name) => !connected.has(name));
}
// ---------------------------------------------------------------------------
// Step 5: Find duplications via function name similarity
// ---------------------------------------------------------------------------
function findDuplications(results) {
    const functionsByModule = extractFunctionNames(results);
    const allNames = Array.from(new Set(functionsByModule.flatMap(({ names }) => names)));
    const duplications = [];
    const reported = new Set();
    for (let i = 0; i < allNames.length; i++) {
        const nameA = allNames[i] ?? '';
        if (nameA === '' || reported.has(nameA)) {
            continue;
        }
        const cluster = [nameA];
        for (let j = i + 1; j < allNames.length; j++) {
            const nameB = allNames[j] ?? '';
            if (!reported.has(nameB) && stringSimilarity(nameA, nameB) >= 0.8) {
                cluster.push(nameB);
            }
        }
        if (cluster.length < 2) {
            continue;
        }
        const modulesWithCluster = functionsByModule
            .filter(({ names }) => cluster.some((c) => names.includes(c)))
            .map(({ module }) => module);
        if (modulesWithCluster.length >= 2) {
            cluster.forEach((n) => reported.add(n));
            duplications.push({ signature: nameA, modules: modulesWithCluster });
        }
    }
    return duplications;
}
function extractFunctionNames(results) {
    return results.map((r) => ({
        module: r.module.name,
        names: r.analysis.complexityMetrics.flatMap((m) => m.hotspots.map((h) => h.name)),
    }));
}
/**
 * Character overlap ratio similarity (0..1).
 * Counts shared characters relative to the longer string.
 */
function stringSimilarity(a, b) {
    if (a === b) {
        return 1;
    }
    const longer = a.length >= b.length ? a : b;
    const shorter = a.length < b.length ? a : b;
    if (longer.length === 0) {
        return 1;
    }
    let matches = 0;
    const usedIndices = new Set();
    for (const char of shorter) {
        const idx = findUnusedIndex(longer, char, usedIndices);
        if (idx !== -1) {
            matches++;
            usedIndices.add(idx);
        }
    }
    return matches / longer.length;
}
function findUnusedIndex(str, char, usedIndices) {
    for (let i = 0; i < str.length; i++) {
        if (str.charAt(i) === char && !usedIndices.has(i)) {
            return i;
        }
    }
    return -1;
}
//# sourceMappingURL=cross-module-analyzer.js.map