"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallGraphNode = exports.CallNodeType = void 0;
exports.createCallGraph = createCallGraph;
const typescript_1 = __importDefault(require("typescript"));
const diagnosticCodes_js_1 = require("../diagnosticCodes.js");
const utils_js_1 = require("./utils.js");
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
var CallNodeType;
(function (CallNodeType) {
    /** Entry point of a step function. */
    CallNodeType[CallNodeType["EntryPoint"] = 0] = "EntryPoint";
    /** An intrinsic method or function (e.g., JSON.stringify, Steps.format). */
    CallNodeType[CallNodeType["Intrinsic"] = 1] = "Intrinsic";
    /** A user-defined function that may be inlined. */
    CallNodeType[CallNodeType["UserFunction"] = 2] = "UserFunction";
    /** A service binding method (e.g., lambda.call). */
    CallNodeType[CallNodeType["ServiceCall"] = 3] = "ServiceCall";
})(CallNodeType || (exports.CallNodeType = CallNodeType = {}));
class CallGraphNode {
    symbol;
    declaration;
    edges;
    nodeType;
    constructor(symbol, declaration, edges, nodeType = CallNodeType.UserFunction) {
        this.symbol = symbol;
        this.declaration = declaration;
        this.edges = edges;
        this.nodeType = nodeType;
    }
    get name() {
        return this.symbol?.getName() ?? '<anonymous>';
    }
}
exports.CallGraphNode = CallGraphNode;
// ---------------------------------------------------------------------------
// Call graph construction
// ---------------------------------------------------------------------------
/**
 * Build a call graph starting from a step function entry point.
 *
 * The graph tracks:
 * - Which functions are called from the entry point (and transitively)
 * - Whether each call is `await`ed (only awaited service calls become Task states)
 * - Symbol resolution for matching against intrinsics / service registry
 */
function createCallGraph(context, callSite) {
    const cache = new Map();
    const inProgress = new Set();
    const edges = parseFunctionEdges(context, cache, inProgress, callSite.factoryFunction.factory);
    return new CallGraphNode(null, callSite.factoryFunction.factory, edges, CallNodeType.EntryPoint);
}
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
function parseFunctionEdges(context, cache, inProgress, functionDecl) {
    const edges = [];
    // Guard against recursion
    if (inProgress.has(functionDecl)) {
        context.addError(functionDecl, 'Recursive function calls are not supported in step functions', diagnosticCodes_js_1.ErrorCodes.Discovery.RecursiveCall.code);
        return edges;
    }
    inProgress.add(functionDecl);
    const visit = (node) => {
        // Detect await wrapping a call expression:
        //   await someFunc()  →  AwaitExpression > CallExpression
        if (typescript_1.default.isAwaitExpression(node) && typescript_1.default.isCallExpression(node.expression)) {
            const callExpr = node.expression;
            const symbol = context.checker.getSymbolAtLocation(callExpr.expression);
            const callee = resolveCallee(context, cache, inProgress, symbol);
            edges.push({
                await: true,
                callsite: callExpr,
                symbol,
                callee,
            });
            // Still visit the arguments of the call (they may contain nested calls)
            for (const arg of callExpr.arguments) {
                typescript_1.default.forEachChild(arg, visit);
            }
            return;
        }
        // Non-awaited call expression
        if (typescript_1.default.isCallExpression(node)) {
            const symbol = context.checker.getSymbolAtLocation(node.expression);
            const callee = resolveCallee(context, cache, inProgress, symbol);
            edges.push({
                await: false,
                callsite: node,
                symbol,
                callee,
            });
            // Visit arguments for nested calls
            for (const arg of node.arguments) {
                typescript_1.default.forEachChild(arg, visit);
            }
            return;
        }
        // Don't descend into nested function bodies — they have their own scope
        if (typescript_1.default.isFunctionDeclaration(node) ||
            typescript_1.default.isFunctionExpression(node) ||
            typescript_1.default.isArrowFunction(node)) {
            return;
        }
        typescript_1.default.forEachChild(node, visit);
    };
    if (functionDecl.body) {
        typescript_1.default.forEachChild(functionDecl.body, visit);
    }
    inProgress.delete(functionDecl);
    return edges;
}
/**
 * If the symbol resolves to a user-defined function with a body,
 * recursively build its call graph node. Otherwise return undefined
 * (external / intrinsic / service — resolved later).
 */
function resolveCallee(context, cache, inProgress, symbol) {
    if (!symbol)
        return undefined;
    const decl = (0, utils_js_1.getFunctionDeclarationFromSymbol)(symbol);
    if (!decl)
        return undefined;
    // Return cached node if already built
    if (cache.has(decl)) {
        return cache.get(decl);
    }
    // Recursively build edges for this function
    const childEdges = parseFunctionEdges(context, cache, inProgress, decl);
    const node = new CallGraphNode(symbol, decl, childEdges, CallNodeType.UserFunction);
    cache.set(decl, node);
    return node;
}
//# sourceMappingURL=callGraph.js.map