"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.generateImpactAnalysis = generateImpactAnalysis;
const path = __importStar(require("node:path"));
const node_fs_1 = require("node:fs");
const architectureMap_1 = require("./architectureMap");
const impactAnalysisEngine_1 = require("./impactAnalysisEngine");
function normalizePath(value) {
    return value.replace(/\\/g, '/');
}
function sortUnique(values) {
    return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
async function pathExists(targetPath) {
    try {
        await node_fs_1.promises.access(targetPath);
        return true;
    }
    catch {
        return false;
    }
}
function buildOutgoingMap(map) {
    const outgoing = new Map();
    for (const moduleEntry of map.modules) {
        outgoing.set(moduleEntry.moduleName, new Set());
    }
    for (const edge of map.edges) {
        outgoing.get(edge.source)?.add(edge.target);
    }
    return new Map([...outgoing.entries()]
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([moduleName, targets]) => [
        moduleName,
        [...targets].sort((left, right) => left.localeCompare(right)),
    ]));
}
function buildIncomingMap(map) {
    const incoming = new Map();
    for (const moduleEntry of map.modules) {
        incoming.set(moduleEntry.moduleName, new Set());
    }
    for (const edge of map.edges) {
        incoming.get(edge.target)?.add(edge.source);
    }
    return new Map([...incoming.entries()]
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([moduleName, sources]) => [
        moduleName,
        [...sources].sort((left, right) => left.localeCompare(right)),
    ]));
}
function resolveModuleFromInput(workspaceRoot, map, input, treatAsModule) {
    const trimmed = input.trim();
    if (!trimmed) {
        return { mode: 'unresolved', note: 'Target input is empty.' };
    }
    const modules = [...map.modules].sort((left, right) => left.moduleName.localeCompare(right.moduleName));
    const moduleNames = new Set(modules.map((entry) => entry.moduleName));
    if (treatAsModule) {
        if (moduleNames.has(trimmed)) {
            return { moduleName: trimmed, mode: 'module-flag' };
        }
        return {
            mode: 'unresolved',
            note: `Module '${trimmed}' was not found in the discovered architecture map.`,
        };
    }
    if (moduleNames.has(trimmed)) {
        return { moduleName: trimmed, mode: 'module-name' };
    }
    const resolvedInputPath = normalizePath(path.resolve(path.isAbsolute(trimmed) ? trimmed : path.join(workspaceRoot, trimmed)));
    const moduleRoots = modules
        .map((entry) => ({
        moduleName: entry.moduleName,
        absoluteRoot: normalizePath(path.resolve(workspaceRoot, entry.sourcePath)),
    }))
        .sort((left, right) => {
        const lengthCompare = right.absoluteRoot.length - left.absoluteRoot.length;
        if (lengthCompare !== 0) {
            return lengthCompare;
        }
        return left.moduleName.localeCompare(right.moduleName);
    });
    for (const candidate of moduleRoots) {
        if (resolvedInputPath === candidate.absoluteRoot ||
            resolvedInputPath.startsWith(`${candidate.absoluteRoot}/`)) {
            return { moduleName: candidate.moduleName, mode: 'file-path' };
        }
    }
    return {
        mode: 'unresolved',
        note: `Input '${trimmed}' did not match a discovered module name and is not located under any discovered module path.`,
    };
}
function computeTransitiveDependents(targetModule, incomingByModule) {
    const direct = incomingByModule.get(targetModule) ?? [];
    const directSet = new Set(direct);
    const visited = new Set();
    const queue = [...direct];
    while (queue.length > 0) {
        const current = queue.shift();
        if (!current) {
            continue;
        }
        const dependents = incomingByModule.get(current) ?? [];
        for (const dependent of dependents) {
            if (dependent === targetModule ||
                directSet.has(dependent) ||
                visited.has(dependent)) {
                continue;
            }
            visited.add(dependent);
            queue.push(dependent);
        }
    }
    return [...visited].sort((left, right) => left.localeCompare(right));
}
function computeRiskModules(map) {
    const hotspotByModule = new Map(map.moduleHotspotSummary.map((entry) => [entry.moduleName, entry]));
    const cycleModules = new Set(map.cycles.flatMap((cycle) => cycle).filter((moduleName) => moduleName.length > 0));
    const hotspotHubs = new Set(map.hotspots.highlyConnectedModules.map((entry) => entry.moduleName));
    const heavyDependents = new Set(map.moduleHotspotSummary
        .filter((entry) => entry.incomingDependencyCount >= 3)
        .map((entry) => entry.moduleName));
    const candidates = sortUnique([...cycleModules, ...hotspotHubs, ...heavyDependents]);
    return candidates
        .map((moduleName) => {
        const hotspot = hotspotByModule.get(moduleName);
        if (!hotspot) {
            return undefined;
        }
        const reasons = [];
        if (heavyDependents.has(moduleName)) {
            reasons.push('Many dependent modules');
        }
        if (cycleModules.has(moduleName)) {
            reasons.push('Participates in cycle');
        }
        if (hotspotHubs.has(moduleName)) {
            reasons.push('Connectivity hotspot');
        }
        return {
            moduleName,
            reasons: reasons.sort((left, right) => left.localeCompare(right)),
            incomingDependencyCount: hotspot.incomingDependencyCount,
            outgoingDependencyCount: hotspot.outgoingDependencyCount,
        };
    })
        .filter((entry) => !!entry)
        .sort((left, right) => {
        const reasonCompare = right.reasons.length - left.reasons.length;
        if (reasonCompare !== 0) {
            return reasonCompare;
        }
        const incomingCompare = right.incomingDependencyCount - left.incomingDependencyCount;
        if (incomingCompare !== 0) {
            return incomingCompare;
        }
        return left.moduleName.localeCompare(right.moduleName);
    });
}
function resolveRelativePathFromInput(workspaceRoot, target) {
    const trimmed = target.trim();
    if (!trimmed) {
        return undefined;
    }
    const absolutePath = path.resolve(path.isAbsolute(trimmed) ? trimmed : path.join(workspaceRoot, trimmed));
    const relativePath = normalizePath(path.relative(workspaceRoot, absolutePath));
    if (!relativePath || relativePath.startsWith('../') || relativePath === '..') {
        return undefined;
    }
    return relativePath;
}
function deriveGovernanceSignalsFromTarget(targetPath) {
    if (!targetPath) {
        return {
            touchedContracts: [],
            touchedPublicEntrypoints: [],
            touchedArchitectureArtifacts: [],
            touchedAdrs: [],
        };
    }
    const touchedContracts = /^\.archpilot\/contracts\/.+\.contract\.json$/u.test(targetPath)
        ? [targetPath]
        : [];
    const touchedPublicEntrypoints = /modules\/[^/]+\/index\.[jt]sx?$/u.test(targetPath) || /modules\/[^/]+\/public\//u.test(targetPath)
        ? [targetPath]
        : [];
    const touchedArchitectureArtifacts = targetPath.startsWith('.archpilot/') ||
        targetPath === 'docs/architecture/overview.md' ||
        targetPath === 'contracts/openapi.yaml'
        ? [targetPath]
        : [];
    const touchedAdrs = /^docs\/adrs\/.+\.md$/u.test(targetPath) ? [targetPath] : [];
    return {
        touchedContracts,
        touchedPublicEntrypoints,
        touchedArchitectureArtifacts,
        touchedAdrs,
    };
}
async function generateImpactAnalysis(workspaceRoot, target, options) {
    const map = await (0, architectureMap_1.generateArchitectureMap)(workspaceRoot, {
        ...(options?.generatedAtUtc ? { generatedAtUtc: options.generatedAtUtc } : {}),
    });
    const incomingByModule = buildIncomingMap(map);
    const outgoingByModule = buildOutgoingMap(map);
    const resolution = resolveModuleFromInput(workspaceRoot, map, target, options?.treatInputAsModule ?? false);
    const riskModules = computeRiskModules(map);
    const graph = {
        modules: map.modules.map((entry) => entry.moduleName),
        edges: map.edges.map((edge) => ({ source: edge.source, target: edge.target })),
    };
    if (!resolution.moduleName) {
        const notes = sortUnique([
            resolution.note ?? 'Unable to resolve target to a discovered module.',
            ...(map.modules.length === 0
                ? ['No modules were discovered in the current workspace.']
                : []),
        ].filter((value) => value.length > 0));
        return {
            reportVersion: 1,
            projectName: map.projectName,
            generatedAtUtc: map.generatedAtUtc,
            target,
            moduleResolved: false,
            resolutionMode: resolution.mode,
            modulesDiscovered: map.modules.length,
            directDependents: [],
            transitiveDependents: [],
            dependencies: [],
            inCycle: false,
            orphan: false,
            riskModules,
            suggestedModulesToReview: [],
            suggestedFilesToInspect: [],
            impact: (0, impactAnalysisEngine_1.analyzeImpact)({ graph, changedModules: [] }),
            notes,
        };
    }
    const moduleName = resolution.moduleName;
    const directDependents = incomingByModule.get(moduleName) ?? [];
    const transitiveDependents = computeTransitiveDependents(moduleName, incomingByModule);
    const dependencies = outgoingByModule.get(moduleName) ?? [];
    const moduleHotspot = map.moduleHotspotSummary.find((entry) => entry.moduleName === moduleName);
    const inCycle = moduleHotspot?.participatesInCycle ?? false;
    const orphan = moduleHotspot?.orphan ?? false;
    const suggestedModulesToReview = sortUnique([
        ...directDependents,
        ...transitiveDependents,
        ...dependencies,
    ]);
    const affectedModules = sortUnique([moduleName, ...directDependents, ...transitiveDependents]);
    const moduleEntry = map.modules.find((entry) => entry.moduleName === moduleName);
    const suggestedFilesToInspect = [];
    if (moduleEntry) {
        const readmePath = `${moduleEntry.sourcePath}/README.md`;
        const indexPath = `${moduleEntry.sourcePath}/index.ts`;
        const contractPath = moduleEntry.contractPath;
        suggestedFilesToInspect.push({
            path: readmePath,
            exists: await pathExists(path.join(workspaceRoot, ...readmePath.split('/'))),
            kind: 'readme',
        });
        suggestedFilesToInspect.push({
            path: indexPath,
            exists: await pathExists(path.join(workspaceRoot, ...indexPath.split('/'))),
            kind: 'entrypoint',
        });
        if (contractPath) {
            suggestedFilesToInspect.push({
                path: contractPath,
                exists: await pathExists(path.join(workspaceRoot, ...contractPath.split('/'))),
                kind: 'contract',
            });
        }
    }
    const affectedModuleEntries = map.modules.filter((entry) => affectedModules.includes(entry.moduleName));
    const impactedContracts = sortUnique(affectedModuleEntries
        .map((entry) => entry.contractPath)
        .filter((entry) => typeof entry === 'string' && entry.length > 0));
    const impactedPublicEntrypoints = sortUnique(affectedModuleEntries.map((entry) => `${entry.sourcePath}/index.ts`));
    const targetRelativePath = resolution.mode === 'file-path'
        ? resolveRelativePathFromInput(workspaceRoot, target)
        : undefined;
    const targetGovernanceSignals = deriveGovernanceSignalsFromTarget(targetRelativePath);
    const impact = (0, impactAnalysisEngine_1.analyzeImpact)({
        graph,
        changedModules: [moduleName],
        touchedContracts: sortUnique([
            ...impactedContracts,
            ...targetGovernanceSignals.touchedContracts,
        ]),
        touchedPublicEntrypoints: sortUnique([
            ...impactedPublicEntrypoints,
            ...targetGovernanceSignals.touchedPublicEntrypoints,
        ]),
        touchedAdrs: targetGovernanceSignals.touchedAdrs,
        touchedArchitectureArtifacts: targetGovernanceSignals.touchedArchitectureArtifacts,
    });
    const notes = sortUnique([
        inCycle ? 'Detected cycle participation for analyzed module.' : '',
        orphan ? 'Analyzed module is orphaned (no dependencies and no dependents).' : '',
    ].filter((value) => value.length > 0));
    return {
        reportVersion: 1,
        projectName: map.projectName,
        generatedAtUtc: map.generatedAtUtc,
        target,
        module: moduleName,
        moduleResolved: true,
        resolutionMode: resolution.mode,
        modulesDiscovered: map.modules.length,
        directDependents: [...directDependents].sort((left, right) => left.localeCompare(right)),
        transitiveDependents,
        dependencies: [...dependencies].sort((left, right) => left.localeCompare(right)),
        inCycle,
        orphan,
        riskModules,
        suggestedModulesToReview,
        suggestedFilesToInspect: suggestedFilesToInspect.sort((left, right) => {
            const kindCompare = left.kind.localeCompare(right.kind);
            if (kindCompare !== 0) {
                return kindCompare;
            }
            return left.path.localeCompare(right.path);
        }),
        impact,
        notes,
    };
}
//# sourceMappingURL=generateImpactAnalysis.js.map