"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.detectEventArchitectureFindings = detectEventArchitectureFindings;
const path = __importStar(require("node:path"));
const node_fs_1 = require("node:fs");
const ts = __importStar(require("typescript"));
const moduleDiscovery_1 = require("./moduleDiscovery");
const archpilotIgnore_1 = require("./smartInit/archpilotIgnore");
const validationProfiling_1 = require("./validationProfiling");
const RuleIds = {
    UNHANDLED_EVENT: 'AP-EVT-001',
    CIRCULAR_EVENT_CHAIN: 'AP-EVT-002',
    SYNC_EVENT_DEPENDENCY: 'AP-EVT-003',
    NAMING_INCONSISTENCY: 'AP-EVT-004',
};
const maxFileSizeBytes = 384 * 1024;
const supportedExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.java', '.cs', '.py', '.php', '.go']);
const ignoredDirectoryNames = new Set([
    '.git',
    '.archpilot',
    '.next',
    '.vscode-test',
    'node_modules',
    'dist',
    'build',
    'out',
    'coverage',
    'test',
    'tests',
    '__tests__',
    'fixtures',
    'smoke',
    'examples',
    'docs',
    'reports',
    'generated',
]);
const phpEventHandlerPattern = /\bEvent::listen\s*\(\s*([A-Z][A-Za-z0-9]*Event)::class\b|\bfunction\s+handle\s*\(\s*([A-Z][A-Za-z0-9]*Event)\s+\$\w+/gu;
const phpEventPublishPattern = /((?:return\s+)?(?:\$?\w+\s*=\s*)?(?:event|Event::dispatch|[A-Z][A-Za-z0-9]*Event::dispatch)\s*\(([^;]+)\))/gu;
const csharpHandlerPattern = /class\s+\w+[^{]*:\s*(?:INotificationHandler|IConsumer)<\s*([A-Z][A-Za-z0-9]*Event)\s*>/gu;
const csharpPublishPattern = /((?:await\s+)?(?:return\s+)?(?:[\w<>]+\s+\w+\s*=\s*)?[\w.]+(?:Publish|PublishAsync|SendMessageAsync)\s*\(([^;]+)\))/gu;
const pythonSubscriberPattern = /@(?:\w+\.)?(?:subscriber|consumer|task)\s*\(([^)]*)\)/gu;
const pythonPublishPattern = /((?:await\s+)?(?:return\s+)?[\w.]+(?:publish|send_task)\s*\(([^)\n]+)\))/gu;
const goHandlerPattern = /\.(?:Subscribe|QueueSubscribe|SubscribeSync|Consume)\s*\(([^)]*)\)/gu;
const goPublishPattern = /((?:[\w]+\s*:=\s*)?[\w.]+\.(?:Publish|Produce)\s*\(([^)\n]+)\))/gu;
const routeStringPattern = /['"`]([^'"`]+)['"`]/u;
function normalizePath(value) {
    return value.replace(/\\/g, '/');
}
function toPosixRelative(workspaceRoot, absolutePath) {
    return normalizePath(path.relative(workspaceRoot, absolutePath));
}
async function readTextFileIfReasonable(filePath, sourceInventory) {
    if (sourceInventory) {
        return sourceInventory.readText(filePath, { maxFileSizeBytes });
    }
    try {
        const stats = await node_fs_1.promises.stat(filePath);
        if (stats.size > maxFileSizeBytes) {
            return undefined;
        }
        return await node_fs_1.promises.readFile(filePath, { encoding: 'utf8' });
    }
    catch {
        return undefined;
    }
}
function inferModuleForPath(contract, relativePath) {
    const normalized = normalizePath(relativePath);
    const modulesRoot = (0, moduleDiscovery_1.resolveModulesRootFromContract)(contract);
    for (const [moduleName, reference] of Object.entries(contract.modules ?? {})) {
        const modulePath = normalizePath(reference.path ?? `${modulesRoot}/${moduleName}`);
        if (normalized === modulePath || normalized.startsWith(`${modulePath}/`)) {
            return moduleName;
        }
    }
    return undefined;
}
async function collectSourceFiles(workspaceRoot, contract, sourceInventory) {
    if (sourceInventory) {
        const files = await sourceInventory.getTextFiles({
            extensions: [...supportedExtensions],
            ignoredDirectoryNames: [...ignoredDirectoryNames],
            maxFileSizeBytes,
        });
        return files.map((file) => ({
            relativePath: file.relativePath,
            content: file.content,
            extension: file.extension,
            moduleName: inferModuleForPath(contract, file.relativePath),
        }));
    }
    const ignoreMatcher = (0, archpilotIgnore_1.loadArchpilotIgnoreMatcher)(workspaceRoot);
    const collected = [];
    const stack = [workspaceRoot];
    while (stack.length > 0) {
        const current = stack.pop();
        if (!current) {
            continue;
        }
        let entries;
        try {
            entries = await node_fs_1.promises.readdir(current, { withFileTypes: true });
        }
        catch {
            continue;
        }
        for (const entry of entries) {
            const absoluteEntryPath = path.join(current, entry.name);
            const relativeEntryPath = toPosixRelative(workspaceRoot, absoluteEntryPath);
            if (ignoreMatcher.isIgnored(relativeEntryPath)) {
                continue;
            }
            if (entry.isDirectory()) {
                if (entry.name.startsWith('.') || ignoredDirectoryNames.has(entry.name)) {
                    continue;
                }
                stack.push(absoluteEntryPath);
                continue;
            }
            const extension = path.extname(entry.name).toLowerCase();
            if (!supportedExtensions.has(extension)) {
                continue;
            }
            const content = await readTextFileIfReasonable(absoluteEntryPath, sourceInventory);
            if (!content) {
                continue;
            }
            collected.push({
                relativePath: relativeEntryPath,
                content,
                extension,
                module: inferModuleForPath(contract, relativeEntryPath),
            });
        }
    }
    return collected.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
}
function getLineStarts(file) {
    if (file.lineStarts) {
        return file.lineStarts;
    }
    const lineStarts = [0];
    for (let index = 0; index < file.content.length; index += 1) {
        if (file.content[index] === '\n') {
            lineStarts.push(index + 1);
        }
    }
    file.lineStarts = lineStarts;
    return lineStarts;
}
function lineNumberForOffset(file, offset) {
    const lineStarts = getLineStarts(file);
    let low = 0;
    let high = lineStarts.length - 1;
    while (low <= high) {
        const middle = Math.floor((low + high) / 2);
        const lineStart = lineStarts[middle] ?? 0;
        if (lineStart <= offset) {
            low = middle + 1;
        }
        else {
            high = middle - 1;
        }
    }
    return Math.max(1, high + 1);
}
function findMatchingBrace(content, openBraceIndex) {
    let depth = 0;
    for (let index = openBraceIndex; index < content.length; index += 1) {
        if (content[index] === '{') {
            depth += 1;
        }
        if (content[index] === '}') {
            depth -= 1;
            if (depth === 0) {
                return index;
            }
        }
    }
    return Math.min(content.length - 1, openBraceIndex + 5000);
}
function findPythonBlockEnd(content, blockStart) {
    const lineStart = content.lastIndexOf('\n', blockStart) + 1;
    const lineEnd = content.indexOf('\n', blockStart);
    const line = content.slice(lineStart, lineEnd === -1 ? content.length : lineEnd);
    const baseIndent = /^\s*/u.exec(line)?.[0].length ?? 0;
    let offset = lineEnd === -1 ? content.length : lineEnd + 1;
    while (offset < content.length) {
        const nextLineEnd = content.indexOf('\n', offset);
        const nextEnd = nextLineEnd === -1 ? content.length : nextLineEnd;
        const nextLine = content.slice(offset, nextEnd);
        if (nextLine.trim().length > 0) {
            const indent = /^\s*/u.exec(nextLine)?.[0].length ?? 0;
            if (indent <= baseIndent) {
                return offset;
            }
        }
        if (nextLineEnd === -1) {
            return content.length;
        }
        offset = nextLineEnd + 1;
    }
    return content.length;
}
function normalizeEventName(value) {
    return value
        .replace(/Event$/u, '')
        .replace(/([a-z0-9])([A-Z])/gu, '$1.$2')
        .replace(/[_\s-]+/gu, '.')
        .toLowerCase()
        .replace(/[^a-z0-9.]/gu, '')
        .replace(/\.+/gu, '.')
        .replace(/^\.|\.$/gu, '');
}
function classifyEventStyle(value) {
    if (/^[A-Z0-9_]+$/u.test(value) && value.includes('_')) {
        return 'constant';
    }
    if (/^[a-z0-9]+(?:\.[a-z0-9]+)+$/u.test(value)) {
        return 'dot';
    }
    if (/^[A-Z][A-Za-z0-9]+$/u.test(value)) {
        return value.endsWith('Event') ? 'class' : 'pascal';
    }
    return 'pascal';
}
function createFinding(input) {
    return {
        id: input.id,
        severity: 'warning',
        passed: false,
        message: input.message,
        findingType: 'file-scope',
        filePath: input.signal.file.relativePath,
        sourceFile: input.signal.file.relativePath,
        sourceLine: input.signal.line,
        module: input.signal.module,
        target: input.target ?? input.signal.eventName,
    };
}
function getStringLiteralText(node) {
    if (ts.isStringLiteralLike(node)) {
        return node.text;
    }
    if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
        return node.expression.text;
    }
    if (ts.isIdentifier(node)) {
        return node.text.endsWith('Event') ? node.text : undefined;
    }
    return undefined;
}
function getEventClassName(node) {
    if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text.endsWith('Event')) {
        return node.expression.text;
    }
    if (ts.isIdentifier(node) && node.text.endsWith('Event')) {
        return node.text;
    }
    return undefined;
}
function getPropertyAccessParts(expression) {
    if (!ts.isPropertyAccessExpression(expression)) {
        return undefined;
    }
    return {
        receiverText: expression.expression.getText(),
        propertyName: expression.name.text,
    };
}
function hasKnownTypeScriptEventFrameworkEvidence(file) {
    return /@nestjs\/event-emitter|OnEvent|EventEmitter2|from ['"]events['"]|EventEmitter|EventBus|VendureEvent|faststream|bullmq|nats|kafka|rabbit/iu.test(file.content);
}
function isMeaningfulEventTopic(value) {
    return (/^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)+$/u.test(value) ||
        /^[A-Z0-9_]+$/u.test(value) && value.includes('_') ||
        /^[A-Z][A-Za-z0-9]*Event$/u.test(value));
}
function getFirstMeaningfulEventTopic(node) {
    if (!node) {
        return undefined;
    }
    const literal = getStringLiteralText(node);
    return literal && isMeaningfulEventTopic(literal) ? literal : undefined;
}
function receiverLooksLikeKnownEventBus(receiverText) {
    return /(?:^|\.)eventBus$/u.test(receiverText) || /(?:^|\.)eventEmitter$/u.test(receiverText);
}
function getMethodName(node) {
    let current = node;
    while (current) {
        if (ts.isMethodDeclaration(current) && current.name) {
            const name = current.name;
            if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
                return name.text;
            }
        }
        if (ts.isFunctionDeclaration(current)) {
            return current.name?.text;
        }
        current = current.parent;
    }
    return undefined;
}
function getStringLiteralFromText(value) {
    const match = routeStringPattern.exec(value);
    return match?.[1];
}
function getEventNameFromExpressionText(value) {
    const stringLiterals = [...value.matchAll(/['"`]([^'"`]+)['"`]/gu)].map((match) => match[1]).filter(Boolean);
    const eventLikeLiteral = stringLiterals.find((entry) => /^[a-z0-9]+(?:\.[a-z0-9]+)+$/u.test(entry)) ??
        stringLiterals.find((entry) => /^[A-Z0-9_]+$/u.test(entry) && entry.includes('_')) ??
        stringLiterals.find((entry) => /Event$/u.test(entry)) ??
        stringLiterals.at(0);
    if (eventLikeLiteral) {
        return eventLikeLiteral;
    }
    const classMatch = /\b(?:new\s+)?([A-Z][A-Za-z0-9]*Event)\b/u.exec(value);
    if (classMatch?.[1]) {
        return classMatch[1];
    }
    const classRefMatch = /\b([A-Z][A-Za-z0-9]*Event)::class\b/u.exec(value);
    return classRefMatch?.[1];
}
function getTypeScriptPublishEventName(file, node) {
    const access = getPropertyAccessParts(node.expression);
    if (!access) {
        return undefined;
    }
    const firstArg = node.arguments[0];
    const eventClassName = firstArg ? getEventClassName(firstArg) : undefined;
    const hasFrameworkEvidence = hasKnownTypeScriptEventFrameworkEvidence(file);
    if (['publish', 'publishEvent', 'dispatch'].includes(access.propertyName) &&
        eventClassName &&
        (receiverLooksLikeKnownEventBus(access.receiverText) || hasFrameworkEvidence)) {
        return eventClassName;
    }
    if (access.propertyName === 'emit' &&
        (receiverLooksLikeKnownEventBus(access.receiverText) || hasFrameworkEvidence)) {
        return getFirstMeaningfulEventTopic(firstArg);
    }
    if (['request', 'requestReply', 'sendAndReceive'].includes(access.propertyName) &&
        (receiverLooksLikeKnownEventBus(access.receiverText) || hasFrameworkEvidence)) {
        return getFirstMeaningfulEventTopic(firstArg);
    }
    return undefined;
}
function collectTypeScriptEventModuleInfo(file, sourceFile) {
    const names = new Set();
    const reExports = [];
    const visit = (node) => {
        if (ts.isClassDeclaration(node) &&
            node.name?.text.endsWith('Event') &&
            node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
            names.add(normalizeEventName(node.name.text));
        }
        if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
            const specifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)
                ? node.moduleSpecifier.text
                : undefined;
            const exportedNames = [];
            for (const element of node.exportClause.elements) {
                const exported = element.name.text;
                if (exported.endsWith('Event')) {
                    names.add(normalizeEventName(exported));
                    exportedNames.push(exported);
                }
            }
            if (specifier && exportedNames.length > 0) {
                reExports.push({ moduleSpecifier: specifier, names: exportedNames });
            }
        }
        if (ts.isExportDeclaration(node) && !node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
            reExports.push({ moduleSpecifier: node.moduleSpecifier.text });
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    for (const match of file.content.matchAll(/\bexport\s+(?:type\s+)?\{\s*([^}]+)\s*\}/gu)) {
        for (const part of (match[1] ?? '').split(',')) {
            const exported = part.trim().split(/\s+as\s+/iu).at(-1)?.trim();
            if (exported && /^[A-Z]\w*Event$/u.test(exported)) {
                names.add(normalizeEventName(exported));
            }
        }
    }
    return { file, declaredEventNames: names, reExports };
}
function getTypeScriptHandlerEventName(file, node) {
    const access = getPropertyAccessParts(node.expression);
    if (!access) {
        return undefined;
    }
    if (access.propertyName === 'ofType') {
        const firstArg = node.arguments[0];
        return firstArg ? getEventClassName(firstArg) : undefined;
    }
    if (access.propertyName === 'registerBlockingEventHandler') {
        const firstArg = node.arguments[0];
        if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
            for (const property of firstArg.properties) {
                if (!ts.isPropertyAssignment(property)) {
                    continue;
                }
                const name = property.name;
                const propertyName = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
                if (propertyName !== 'event') {
                    continue;
                }
                const initializer = property.initializer;
                if (ts.isArrayLiteralExpression(initializer)) {
                    for (const element of initializer.elements) {
                        const eventName = getEventClassName(element);
                        if (eventName) {
                            return eventName;
                        }
                    }
                }
                return getEventClassName(initializer);
            }
        }
    }
    if (['on', 'addListener', 'subscribe', 'consume'].includes(access.propertyName) &&
        (receiverLooksLikeKnownEventBus(access.receiverText) || hasKnownTypeScriptEventFrameworkEvidence(file))) {
        return getFirstMeaningfulEventTopic(node.arguments[0]);
    }
    return undefined;
}
function isAwaitedOrResultUsed(node) {
    const parent = node.parent;
    const awaited = ts.isAwaitExpression(parent);
    const outer = awaited ? parent.parent : parent;
    return {
        awaited,
        assignedOrReturned: ts.isReturnStatement(outer) ||
            ts.isVariableDeclaration(outer) ||
            ts.isBinaryExpression(outer),
    };
}
function getHandlerFromDecorators(file, sourceFile, node) {
    if (!ts.canHaveDecorators(node)) {
        return undefined;
    }
    const decorators = ts.getDecorators(node) ?? [];
    for (const decorator of decorators) {
        const expression = decorator.expression;
        if (!ts.isCallExpression(expression) || !/(?:OnEvent|EventPattern|EventListener)$/u.test(expression.expression.getText())) {
            continue;
        }
        const firstArg = expression.arguments[0];
        const eventName = firstArg ? getStringLiteralText(firstArg) : undefined;
        if (!eventName) {
            continue;
        }
        return {
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, decorator.getStart(sourceFile, false)),
            methodName: getMethodName(node),
            module: file.module,
            style: classifyEventStyle(eventName),
            publishes: [],
        };
    }
    return undefined;
}
function extractTypeScriptSignals(file, astCache) {
    const sourceFile = astCache
        ? astCache.parseSourceFile({
            filePath: file.relativePath,
            sourceText: file.content,
            scriptTarget: ts.ScriptTarget.Latest,
            setParentNodes: true,
        })
        : ts.createSourceFile(file.relativePath, file.content, ts.ScriptTarget.Latest, true);
    const publishes = [];
    const handlers = [];
    const handlerStack = [];
    const visit = (node) => {
        if (ts.isMethodDeclaration(node)) {
            const handler = getHandlerFromDecorators(file, sourceFile, node);
            if (handler) {
                handlers.push(handler);
                handlerStack.push(handler);
                if (node.body) {
                    ts.forEachChild(node.body, visit);
                }
                handlerStack.pop();
                return;
            }
        }
        if (ts.isCallExpression(node)) {
            const eventName = getTypeScriptHandlerEventName(file, node);
            if (eventName) {
                const handler = {
                    eventName,
                    normalizedName: normalizeEventName(eventName),
                    file,
                    line: lineNumberForOffset(file, node.getStart(sourceFile, false)),
                    methodName: getMethodName(node),
                    module: file.module,
                    style: classifyEventStyle(eventName),
                    publishes: [],
                };
                handlers.push(handler);
                const callback = node.arguments.at(-1);
                if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && callback.body) {
                    handlerStack.push(handler);
                    ts.forEachChild(callback.body, visit);
                    handlerStack.pop();
                }
            }
        }
        if (ts.isCallExpression(node)) {
            const eventName = getTypeScriptPublishEventName(file, node);
            if (eventName) {
                const usage = isAwaitedOrResultUsed(node);
                const signal = {
                    eventName,
                    normalizedName: normalizeEventName(eventName),
                    file,
                    line: lineNumberForOffset(file, node.getStart(sourceFile, false)),
                    methodName: getMethodName(node),
                    module: file.module,
                    style: classifyEventStyle(eventName),
                    awaited: usage.awaited,
                    assignedOrReturned: usage.assignedOrReturned,
                };
                publishes.push(signal);
                const activeHandler = handlerStack[handlerStack.length - 1];
                if (activeHandler) {
                    activeHandler.publishes.push(signal);
                }
            }
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
    return { publishes, handlers, publicEventNames: new Set() };
}
function extractJavaSignals(file) {
    const publishes = [];
    const handlers = [];
    for (const match of file.content.matchAll(/@EventListener\s*(?:\(\s*([A-Za-z]\w*Event)\.class\s*\))?/gu)) {
        const eventName = match[1] ?? 'ApplicationEvent';
        handlers.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, match.index ?? 0),
            module: file.module,
            style: classifyEventStyle(eventName),
            publishes: [],
        });
    }
    for (const match of file.content.matchAll(/(?:await\s+)?(?:\w+\.)?(?:publishEvent|publish|emit)\s*\(\s*(?:new\s+)?(["']?)([A-Za-z][\w.]*Event?|[a-z][\w.]+)\1/gu)) {
        const eventName = match[2];
        if (!eventName) {
            continue;
        }
        publishes.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, match.index ?? 0),
            module: file.module,
            style: classifyEventStyle(eventName),
            awaited: /^\s*await\b/u.test(match[0]),
            assignedOrReturned: false,
        });
    }
    return { publishes, handlers, publicEventNames: new Set() };
}
function extractPublishSignalsFromText(file, body, baseOffset, pattern) {
    const publishes = [];
    for (const match of body.matchAll(pattern)) {
        const callText = match[1] ?? match[0] ?? '';
        const argsText = match[2] ?? callText;
        const eventName = getEventNameFromExpressionText(argsText);
        if (!eventName) {
            continue;
        }
        publishes.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, baseOffset + (match.index ?? 0)),
            module: file.module,
            style: classifyEventStyle(eventName),
            awaited: /\bawait\b/u.test(callText),
            assignedOrReturned: /\breturn\b/u.test(callText) || /(?:^|[\s(])[\w$]+\s*(?::=|=)\s*/u.test(callText),
        });
    }
    return publishes;
}
function extractCsharpSignals(file) {
    const publishes = extractPublishSignalsFromText(file, file.content, 0, csharpPublishPattern);
    const handlers = [];
    for (const match of file.content.matchAll(csharpHandlerPattern)) {
        const eventName = match[1];
        if (!eventName) {
            continue;
        }
        const classStart = match.index ?? 0;
        const classOpenBrace = file.content.indexOf('{', classStart);
        const classEnd = classOpenBrace >= 0 ? findMatchingBrace(file.content, classOpenBrace) : classStart;
        const classBody = classOpenBrace >= 0 ? file.content.slice(classOpenBrace, classEnd + 1) : '';
        handlers.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, classStart),
            module: file.module,
            style: classifyEventStyle(eventName),
            publishes: extractPublishSignalsFromText(file, classBody, classOpenBrace >= 0 ? classOpenBrace : classStart, csharpPublishPattern),
        });
    }
    return { publishes, handlers, publicEventNames: new Set() };
}
function extractPythonSignals(file) {
    const publishes = extractPublishSignalsFromText(file, file.content, 0, pythonPublishPattern);
    const handlers = [];
    for (const match of file.content.matchAll(pythonSubscriberPattern)) {
        const eventName = getEventNameFromExpressionText(match[1] ?? '');
        if (!eventName) {
            continue;
        }
        const blockStart = (match.index ?? 0) + match[0].length;
        const blockEnd = findPythonBlockEnd(file.content, blockStart);
        const block = file.content.slice(blockStart, blockEnd);
        handlers.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, match.index ?? 0),
            module: file.module,
            style: classifyEventStyle(eventName),
            publishes: extractPublishSignalsFromText(file, block, blockStart, pythonPublishPattern),
        });
    }
    return { publishes, handlers, publicEventNames: new Set() };
}
function extractPhpSignals(file) {
    const publishes = extractPublishSignalsFromText(file, file.content, 0, phpEventPublishPattern);
    const handlers = [];
    for (const match of file.content.matchAll(phpEventHandlerPattern)) {
        const eventName = match[1] ?? match[2];
        if (!eventName) {
            continue;
        }
        const classStart = Math.max(0, (match.index ?? 0) - 300);
        const nextClass = file.content.lastIndexOf('class ', match.index ?? 0);
        const classOpenBrace = nextClass >= 0 ? file.content.indexOf('{', nextClass) : -1;
        const classEnd = classOpenBrace >= 0 ? findMatchingBrace(file.content, classOpenBrace) : match.index ?? 0;
        const classBody = classOpenBrace >= 0 && classEnd > classOpenBrace ? file.content.slice(classOpenBrace, classEnd + 1) : '';
        handlers.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, match.index ?? 0),
            module: file.module,
            style: classifyEventStyle(eventName),
            publishes: extractPublishSignalsFromText(file, classBody, classOpenBrace >= 0 ? classOpenBrace : classStart, phpEventPublishPattern),
        });
    }
    return { publishes, handlers, publicEventNames: new Set() };
}
function extractGoSignals(file) {
    const publishes = extractPublishSignalsFromText(file, file.content, 0, goPublishPattern);
    const handlers = [];
    for (const match of file.content.matchAll(goHandlerPattern)) {
        const eventName = getEventNameFromExpressionText(match[1] ?? '');
        if (!eventName) {
            continue;
        }
        handlers.push({
            eventName,
            normalizedName: normalizeEventName(eventName),
            file,
            line: lineNumberForOffset(file, match.index ?? 0),
            module: file.module,
            style: classifyEventStyle(eventName),
            publishes: [],
        });
    }
    return { publishes, handlers, publicEventNames: new Set() };
}
function isTypeScriptLikeExtension(extension) {
    return ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(extension);
}
function parseTypeScriptFile(file, astCache) {
    return astCache
        ? astCache.parseSourceFile({
            filePath: file.relativePath,
            sourceText: file.content,
            scriptTarget: ts.ScriptTarget.Latest,
            setParentNodes: true,
        })
        : ts.createSourceFile(file.relativePath, file.content, ts.ScriptTarget.Latest, true);
}
function resolveRelativeExportTarget(fromFile, moduleSpecifier, filesByPath) {
    if (!moduleSpecifier.startsWith('.')) {
        return undefined;
    }
    const fromDirectory = path.posix.dirname(fromFile.relativePath);
    const basePath = normalizePath(path.posix.normalize(path.posix.join(fromDirectory, moduleSpecifier)));
    const candidates = [
        basePath,
        `${basePath}.ts`,
        `${basePath}.tsx`,
        `${basePath}.js`,
        `${basePath}.jsx`,
        `${basePath}/index.ts`,
        `${basePath}/index.tsx`,
        `${basePath}/index.js`,
        `${basePath}/index.jsx`,
    ];
    return candidates.find((candidate) => filesByPath.has(candidate));
}
function getConfiguredPublicEntrypoints(contract) {
    const entrypoints = new Set();
    for (const reference of Object.values(contract.modules ?? {})) {
        for (const entrypoint of reference.publicEntrypoints ?? []) {
            entrypoints.add(normalizePath(entrypoint));
        }
    }
    return entrypoints;
}
function isDefaultPublicEntrypoint(file) {
    const normalized = normalizePath(file.relativePath);
    return /(?:^|\/)index\.(?:ts|tsx|js|jsx|mjs|cjs)$/u.test(normalized);
}
function collectPublicTypeScriptEventNames(files, contract, astCache) {
    const filesByPath = new Map(files.map((file) => [normalizePath(file.relativePath), file]));
    const moduleInfoByPath = new Map();
    for (const file of files) {
        if (!isTypeScriptLikeExtension(file.extension)) {
            continue;
        }
        moduleInfoByPath.set(normalizePath(file.relativePath), collectTypeScriptEventModuleInfo(file, parseTypeScriptFile(file, astCache)));
    }
    const configuredEntrypoints = getConfiguredPublicEntrypoints(contract);
    const queue = [...moduleInfoByPath.values()]
        .filter((info) => isDefaultPublicEntrypoint(info.file) ||
        configuredEntrypoints.has(normalizePath(info.file.relativePath)))
        .map((info) => normalizePath(info.file.relativePath));
    const visited = new Set();
    const publicEventNames = new Set();
    while (queue.length > 0) {
        const currentPath = queue.shift();
        if (!currentPath || visited.has(currentPath)) {
            continue;
        }
        visited.add(currentPath);
        const info = moduleInfoByPath.get(currentPath);
        if (!info) {
            continue;
        }
        for (const eventName of info.declaredEventNames) {
            publicEventNames.add(eventName);
        }
        for (const reExport of info.reExports) {
            const targetPath = resolveRelativeExportTarget(info.file, reExport.moduleSpecifier, filesByPath);
            if (!targetPath || visited.has(targetPath)) {
                continue;
            }
            if (reExport.names && reExport.names.length > 0) {
                const targetInfo = moduleInfoByPath.get(targetPath);
                if (targetInfo) {
                    for (const eventName of reExport.names) {
                        if (targetInfo.declaredEventNames.has(normalizeEventName(eventName))) {
                            publicEventNames.add(normalizeEventName(eventName));
                        }
                    }
                }
                continue;
            }
            queue.push(targetPath);
        }
    }
    return publicEventNames;
}
function collectSignals(files, contract, astCache) {
    const publishes = [];
    const handlers = [];
    const publicEventNames = collectPublicTypeScriptEventNames(files, contract, astCache);
    for (const file of files) {
        let signals;
        switch (file.extension) {
            case '.java':
                signals = extractJavaSignals(file);
                break;
            case '.cs':
                signals = extractCsharpSignals(file);
                break;
            case '.py':
                signals = extractPythonSignals(file);
                break;
            case '.php':
                signals = extractPhpSignals(file);
                break;
            case '.go':
                signals = extractGoSignals(file);
                break;
            default:
                signals = extractTypeScriptSignals(file, astCache);
                break;
        }
        publishes.push(...signals.publishes);
        handlers.push(...signals.handlers);
    }
    return { publishes, handlers, publicEventNames };
}
function buildEventFindings(signals) {
    const findings = [];
    const handledNames = new Set(signals.handlers.map((handler) => handler.normalizedName));
    for (const publish of signals.publishes) {
        if (!handledNames.has(publish.normalizedName)) {
            if (signals.publicEventNames.has(publish.normalizedName)) {
                continue;
            }
            findings.push(createFinding({
                id: RuleIds.UNHANDLED_EVENT,
                message: `Published event '${publish.eventName}' has no statically visible handler/listener.`,
                signal: publish,
            }));
        }
        if (publish.assignedOrReturned) {
            findings.push(createFinding({
                id: RuleIds.SYNC_EVENT_DEPENDENCY,
                message: `Event '${publish.eventName}' appears to be used as a synchronous request/response dependency.`,
                signal: publish,
            }));
        }
    }
    const handlerByName = new Map(signals.handlers.map((handler) => [handler.normalizedName, handler]));
    for (const handler of signals.handlers) {
        for (const publish of handler.publishes) {
            const nextHandler = handlerByName.get(publish.normalizedName);
            if (!nextHandler) {
                continue;
            }
            if (nextHandler.publishes.some((nextPublish) => nextPublish.normalizedName === handler.normalizedName)) {
                findings.push(createFinding({
                    id: RuleIds.CIRCULAR_EVENT_CHAIN,
                    message: `Circular event chain risk: handler for '${handler.eventName}' publishes '${publish.eventName}', whose handler publishes '${handler.eventName}'.`,
                    signal: publish,
                    target: `${handler.eventName}<->${publish.eventName}`,
                }));
            }
        }
    }
    const styleGroups = new Map();
    const representative = new Map();
    for (const signal of [...signals.publishes, ...signals.handlers]) {
        const existing = styleGroups.get(signal.normalizedName) ?? new Set();
        existing.add(signal.style);
        styleGroups.set(signal.normalizedName, existing);
        representative.set(signal.normalizedName, signal);
    }
    for (const [eventName, styles] of styleGroups.entries()) {
        if (styles.size >= 3) {
            const signal = representative.get(eventName);
            if (signal) {
                findings.push(createFinding({
                    id: RuleIds.NAMING_INCONSISTENCY,
                    message: `Event '${eventName}' uses mixed naming styles (${[...styles].sort().join(', ')}).`,
                    signal,
                    target: eventName,
                }));
            }
        }
    }
    return findings
        .sort((left, right) => (left.sourceFile ?? '').localeCompare(right.sourceFile ?? '') || left.id.localeCompare(right.id))
        .filter((finding, index, values) => {
        const previous = values[index - 1];
        return !previous || previous.id !== finding.id || previous.sourceFile !== finding.sourceFile || previous.target !== finding.target;
    });
}
async function detectEventArchitectureFindings(input) {
    const started = Date.now();
    const files = await collectSourceFiles(input.workspaceRoot, input.contract, input.sourceInventory);
    (0, validationProfiling_1.emitValidationProfile)('AP-EVT analyzer file scan', Date.now() - started, [
        { name: 'files', value: files.length },
    ]);
    const signals = collectSignals(files, input.contract, input.astCache);
    return buildEventFindings(signals).filter((finding) => !(input.disabledRuleIds?.has(finding.id) ?? false));
}
//# sourceMappingURL=eventArchitectureAnalysis.js.map