"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildCFG = buildCFG;
const typescript_1 = __importDefault(require("typescript"));
const diagnosticCodes_js_1 = require("../diagnosticCodes.js");
class CFGBuilderState {
    counter = 0;
    blocks = new Map();
    deferredCalls = new Map();
    newBlockId(label) {
        const id = label
            ? `${label}_${this.counter++}`
            : `bb_${this.counter++}`;
        return id;
    }
    addBlock(id, statements, terminator, returnTargetVar) {
        this.blocks.set(id, { id, statements, terminator, ...(returnTargetVar && { returnTargetVar }) });
    }
}
/**
 * Build a control flow graph from a function body block.
 *
 * Takes a `ts.Block` (the factory function body), not a full
 * `StepFunctionCallSite` — keeps CFG construction decoupled from discovery.
 *
 * When `helperRegistry` is provided, awaited calls to functions in the
 * registry are inlined: the helper's body statements are spliced into the
 * CFG at the call site, and parameter-to-argument bindings are returned
 * so the variable resolver can map helper parameters to caller values.
 */
function buildCFG(context, body, helperRegistry) {
    const state = new CFGBuilderState();
    const inlineBindings = [];
    const entryId = state.newBlockId('entry');
    const statements = Array.from(body.statements);
    processStatements(state, context, statements, entryId, [], { helperRegistry, inlineBindings });
    return {
        cfg: {
            entry: entryId,
            blocks: state.blocks,
            ...(state.deferredCalls.size > 0 && { deferredCalls: state.deferredCalls }),
        },
        inlineBindings,
    };
}
// ---------------------------------------------------------------------------
// Core statement processor
// ---------------------------------------------------------------------------
/**
 * Process a list of statements into basic blocks.
 *
 * Accumulates non-branching statements into the current block. When a
 * branching construct is encountered, finalizes the current block and
 * recursively processes sub-blocks.
 *
 * Returns the ID of the last block in the sequence, or `null` if the
 * sequence always terminates (return/throw in all paths).
 */
function processStatements(state, context, statements, currentBlockId, loopStack, inlining = {}) {
    const accumulated = [];
    // Pre-scan: detect deferred-await patterns (const x = call(); ... await x)
    const pendingPromises = findPendingPromises(context, statements);
    for (let i = 0; i < statements.length; i++) {
        const stmt = statements[i];
        const remaining = statements.slice(i + 1);
        // --- Skip pending promise declarations (consumed at the await point) ---
        if (pendingPromises.size > 0 && typescript_1.default.isVariableStatement(stmt)) {
            const decl = stmt.declarationList.declarations[0];
            if (decl && typescript_1.default.isIdentifier(decl.name)) {
                const sym = context.checker.getSymbolAtLocation(decl.name);
                if (sym && pendingPromises.has(sym)) {
                    // Record in deferredCalls for state builder fallback (single-await case)
                    state.deferredCalls.set(sym, pendingPromises.get(sym).callExpr);
                    continue;
                }
            }
        }
        // --- Return statement ---
        if (typescript_1.default.isReturnStatement(stmt)) {
            // Inside an inlined helper: return → fall to continuation
            if (inlining.returnOverride) {
                if (stmt.expression) {
                    if (inlining.returnVarName && inlining.returnVarSymbol) {
                        // Value-returning helper: caller captures the result
                        const returnTarget = { name: inlining.returnVarName, symbol: inlining.returnVarSymbol };
                        if (typescript_1.default.isAwaitExpression(stmt.expression)) {
                            // return await svc.call(...) → push as fire-and-forget statement,
                            // then override ResultPath via returnTargetVar on the block
                            const exprStmt = typescript_1.default.factory.createExpressionStatement(stmt.expression);
                            typescript_1.default.setTextRange(exprStmt, stmt);
                            accumulated.push(exprStmt);
                            state.addBlock(currentBlockId, accumulated, {
                                kind: 'fall',
                                target: inlining.returnOverride,
                            }, returnTarget);
                        }
                        else {
                            // return someVar → alias the caller's variable to this expression
                            state.addBlock(currentBlockId, accumulated, {
                                kind: 'fall',
                                target: inlining.returnOverride,
                            }, { ...returnTarget, expression: stmt.expression });
                        }
                    }
                    else if (typescript_1.default.isAwaitExpression(stmt.expression)) {
                        // Void call of value-returning helper: result discarded but side effects preserved
                        const exprStmt = typescript_1.default.factory.createExpressionStatement(stmt.expression);
                        typescript_1.default.setTextRange(exprStmt, stmt);
                        accumulated.push(exprStmt);
                        state.addBlock(currentBlockId, accumulated, {
                            kind: 'fall',
                            target: inlining.returnOverride,
                        });
                    }
                    else {
                        // Non-await return value discarded in void call — no effect needed
                        state.addBlock(currentBlockId, accumulated, {
                            kind: 'fall',
                            target: inlining.returnOverride,
                        });
                    }
                }
                else {
                    // Void return — fall to continuation
                    state.addBlock(currentBlockId, accumulated, {
                        kind: 'fall',
                        target: inlining.returnOverride,
                    });
                }
                return null;
            }
            state.addBlock(currentBlockId, accumulated, {
                kind: 'return',
                expression: stmt.expression,
            });
            return null;
        }
        // --- Throw statement ---
        if (typescript_1.default.isThrowStatement(stmt)) {
            state.addBlock(currentBlockId, accumulated, {
                kind: 'throw',
                expression: stmt.expression,
            });
            return null;
        }
        // --- Break statement ---
        if (typescript_1.default.isBreakStatement(stmt)) {
            const loop = loopStack[loopStack.length - 1];
            if (!loop) {
                context.addError(stmt, 'break statement outside of loop', diagnosticCodes_js_1.ErrorCodes.Cfg.BreakOutsideLoop.code);
                continue;
            }
            state.addBlock(currentBlockId, accumulated, {
                kind: 'break',
                target: loop.exitBlock,
            });
            return null;
        }
        // --- Continue statement ---
        if (typescript_1.default.isContinueStatement(stmt)) {
            // Skip switch contexts — continue targets the enclosing loop, not the switch
            let loop;
            for (let i = loopStack.length - 1; i >= 0; i--) {
                if (!loopStack[i].isSwitch) {
                    loop = loopStack[i];
                    break;
                }
            }
            if (!loop) {
                context.addError(stmt, 'continue statement outside of loop', diagnosticCodes_js_1.ErrorCodes.Cfg.ContinueOutsideLoop.code);
                continue;
            }
            state.addBlock(currentBlockId, accumulated, {
                kind: 'continue',
                target: loop.conditionBlock,
            });
            return null;
        }
        // --- If/else ---
        if (typescript_1.default.isIfStatement(stmt)) {
            const result = processIfStatement(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            return result;
        }
        // --- While loop ---
        if (typescript_1.default.isWhileStatement(stmt)) {
            const afterLoop = processWhileStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining);
            if (afterLoop === null)
                return null;
            // Continue processing remaining statements in the exit block
            if (remaining.length > 0) {
                return processStatements(state, context, remaining, afterLoop, loopStack, inlining);
            }
            return afterLoop;
        }
        // --- Do-while loop ---
        if (typescript_1.default.isDoStatement(stmt)) {
            const afterLoop = processDoWhileStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining);
            if (afterLoop === null)
                return null;
            if (remaining.length > 0) {
                return processStatements(state, context, remaining, afterLoop, loopStack, inlining);
            }
            return afterLoop;
        }
        // --- For(;;) loop ---
        if (typescript_1.default.isForStatement(stmt)) {
            const afterLoop = processForStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining);
            if (afterLoop === null)
                return null;
            if (remaining.length > 0) {
                return processStatements(state, context, remaining, afterLoop, loopStack, inlining);
            }
            return afterLoop;
        }
        // --- For...of loop (map state) ---
        if (typescript_1.default.isForOfStatement(stmt)) {
            const afterLoop = processForOfStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining);
            if (afterLoop === null)
                return null;
            if (remaining.length > 0) {
                return processStatements(state, context, remaining, afterLoop, loopStack, inlining);
            }
            return afterLoop;
        }
        // --- Try/catch/finally ---
        if (typescript_1.default.isTryStatement(stmt)) {
            const afterTry = processTryStatement(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            return afterTry;
        }
        // --- Switch/case ---
        if (typescript_1.default.isSwitchStatement(stmt)) {
            const afterSwitch = processSwitchStatement(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            return afterSwitch;
        }
        // --- Steps.map() → Map state with options ---
        {
            const stepsMapResult = tryExtractStepsMap(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            if (stepsMapResult !== undefined) {
                return stepsMapResult;
            }
        }
        // --- Steps.distributedMap() → Distributed Map state ---
        {
            const distMapResult = tryExtractStepsDistributedMap(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            if (distMapResult !== undefined) {
                return distMapResult;
            }
        }
        // --- Steps.parallel() → Parallel state with options (retry) ---
        {
            const stepsParallelResult = tryExtractStepsParallel(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            if (stepsParallelResult !== undefined) {
                return stepsParallelResult;
            }
        }
        // --- Deferred-await batch → Parallel state ---
        if (pendingPromises.size > 0) {
            const batchResult = tryBatchDeferredAwaits(state, context, statements, i, accumulated, currentBlockId, loopStack, inlining, pendingPromises);
            if (batchResult) {
                const { exitBlockId, advanceTo } = batchResult;
                const afterBatch = statements.slice(advanceTo);
                if (afterBatch.length > 0) {
                    return processStatements(state, context, afterBatch, exitBlockId, loopStack, inlining);
                }
                return exitBlockId;
            }
        }
        // --- Promise.race / Promise.any → compile-time error ---
        if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression) &&
            isPromiseRaceOrAny(stmt.expression.expression)) {
            context.addError(stmt.expression.expression, `Promise.race() and Promise.any() are not supported. ASL Parallel states wait for all branches to complete. ` +
                `Use Promise.all() instead, or delegate race logic to a Lambda function.`, diagnosticCodes_js_1.ErrorCodes.Cfg.PromiseRaceNotSupported.code);
        }
        // --- Promise.all (fire-and-forget, ExpressionStatement) → Parallel state ---
        if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression)) {
            const promiseAll = extractPromiseAllFromAwait(context, stmt.expression, pendingPromises);
            if (promiseAll) {
                const exitBlockId = state.newBlockId('parallel_exit');
                const classifiedBranches = classifyParallelBranches(state, context, promiseAll.branches, loopStack, inlining);
                state.addBlock(currentBlockId, accumulated, {
                    kind: 'parallel',
                    branches: classifiedBranches,
                    resultBindings: [],
                    resultSymbols: [],
                    exitBlock: exitBlockId,
                });
                if (remaining.length > 0) {
                    return processStatements(state, context, remaining, exitBlockId, loopStack, inlining);
                }
                return exitBlockId;
            }
        }
        // --- Promise.race / Promise.any (variable statement) → compile-time error ---
        if (typescript_1.default.isVariableStatement(stmt)) {
            const decl = stmt.declarationList.declarations[0];
            if (decl?.initializer && typescript_1.default.isAwaitExpression(decl.initializer) &&
                typescript_1.default.isCallExpression(decl.initializer.expression) &&
                isPromiseRaceOrAny(decl.initializer.expression)) {
                context.addError(decl.initializer.expression, `Promise.race() and Promise.any() are not supported. ASL Parallel states wait for all branches to complete. ` +
                    `Use Promise.all() instead, or delegate race logic to a Lambda function.`, diagnosticCodes_js_1.ErrorCodes.Cfg.PromiseRaceNotSupported.code);
            }
        }
        // --- Promise.all (with destructuring, VariableStatement) → Parallel state ---
        if (typescript_1.default.isVariableStatement(stmt)) {
            const promiseAll = extractPromiseAll(context, stmt);
            if (promiseAll) {
                const exitBlockId = state.newBlockId('parallel_exit');
                // Resolve deferred promise references in branches
                const resolvedBranches = pendingPromises.size > 0
                    ? promiseAll.branches.map(branch => {
                        if (typescript_1.default.isIdentifier(branch)) {
                            const sym = context.checker.getSymbolAtLocation(branch);
                            if (sym && pendingPromises.has(sym))
                                return pendingPromises.get(sym).callExpr;
                        }
                        return branch;
                    })
                    : promiseAll.branches;
                // Classify each branch: direct expression or substep
                const classifiedBranches = classifyParallelBranches(state, context, resolvedBranches, loopStack, inlining);
                state.addBlock(currentBlockId, accumulated, {
                    kind: 'parallel',
                    branches: classifiedBranches,
                    resultBindings: promiseAll.resultBindings,
                    resultSymbols: promiseAll.resultSymbols,
                    exitBlock: exitBlockId,
                });
                if (remaining.length > 0) {
                    return processStatements(state, context, remaining, exitBlockId, loopStack, inlining);
                }
                return exitBlockId;
            }
        }
        // --- Async helper function call (inlining) ---
        if (inlining.helperRegistry) {
            const helperResult = tryInlineHelperCall(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining);
            if (helperResult !== undefined) {
                return helperResult;
            }
        }
        // --- Ternary variable declaration → Choice + Pass desugaring ---
        if (typescript_1.default.isVariableStatement(stmt)) {
            const decls = stmt.declarationList.declarations;
            if (decls.length === 1 && decls[0].initializer &&
                typescript_1.default.isConditionalExpression(decls[0].initializer) &&
                typescript_1.default.isIdentifier(decls[0].name)) {
                const decl = decls[0];
                const ternary = decl.initializer;
                const varSym = context.checker.getSymbolAtLocation(decl.name);
                if (varSym) {
                    const continuationId = state.newBlockId('after_ternary');
                    state.addBlock(currentBlockId, accumulated, {
                        kind: 'ternaryAssign',
                        condition: ternary.condition,
                        variableName: decl.name.text,
                        variableSymbol: varSym,
                        thenExpression: ternary.whenTrue,
                        elseExpression: ternary.whenFalse,
                        continuation: continuationId,
                    });
                    if (remaining.length > 0) {
                        return processStatements(state, context, remaining, continuationId, loopStack, inlining);
                    }
                    return continuationId;
                }
            }
        }
        // --- Sequential: accumulate non-branching statements ---
        accumulated.push(stmt);
    }
    // End of statements — finalize block with a fall-through to a terminal block
    // Inside an inlined helper: fall to the continuation block instead of implicit return
    if (inlining.returnOverride) {
        if (accumulated.length > 0) {
            const bridgeId = state.newBlockId('inline_exit');
            state.addBlock(currentBlockId, accumulated, { kind: 'fall', target: bridgeId });
            state.addBlock(bridgeId, [], { kind: 'fall', target: inlining.returnOverride });
            return bridgeId;
        }
        state.addBlock(currentBlockId, accumulated, { kind: 'fall', target: inlining.returnOverride });
        return currentBlockId;
    }
    // If there are no remaining statements, this is an implicit return
    const exitId = state.newBlockId('exit');
    state.addBlock(currentBlockId, accumulated, {
        kind: 'fall',
        target: exitId,
    });
    state.addBlock(exitId, [], { kind: 'return', expression: undefined });
    return exitId;
}
// ---------------------------------------------------------------------------
// If/else
// ---------------------------------------------------------------------------
function processIfStatement(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining = {}) {
    const thenBlockId = state.newBlockId('then');
    const elseBlockId = state.newBlockId('else');
    // Finalize current block with branch terminator
    state.addBlock(currentBlockId, accumulated, {
        kind: 'branch',
        condition: stmt.expression,
        thenBlock: thenBlockId,
        elseBlock: elseBlockId,
    });
    // Process then branch
    const thenBody = getBlockStatements(stmt.thenStatement);
    const thenExit = processStatements(state, context, thenBody, thenBlockId, loopStack, inlining);
    // Process else branch
    let elseExit;
    if (stmt.elseStatement) {
        const elseBody = getBlockStatements(stmt.elseStatement);
        elseExit = processStatements(state, context, elseBody, elseBlockId, loopStack, inlining);
    }
    else {
        // No else: the else block is empty and falls through to merge
        elseExit = elseBlockId;
        // Will be finalized below when creating merge block
    }
    // If both branches terminate, no merge block needed
    if (thenExit === null && elseExit === null) {
        // No else body was provided but thenExit is null — need to finalize empty else
        if (!stmt.elseStatement) {
            const mergeId = state.newBlockId('merge');
            state.addBlock(elseBlockId, [], { kind: 'fall', target: mergeId });
            if (remaining.length > 0) {
                return processStatements(state, context, remaining, mergeId, loopStack, inlining);
            }
            state.addBlock(mergeId, [], { kind: 'return', expression: undefined });
            return mergeId;
        }
        return null;
    }
    // Create merge block for remaining statements
    const mergeId = state.newBlockId('merge');
    // Wire non-null exits to merge
    if (thenExit !== null) {
        patchBlockTerminator(state, thenExit, { kind: 'fall', target: mergeId });
    }
    if (elseExit !== null) {
        if (!stmt.elseStatement) {
            // Empty else — add block with fall to merge
            state.addBlock(elseBlockId, [], { kind: 'fall', target: mergeId });
        }
        else {
            patchBlockTerminator(state, elseExit, { kind: 'fall', target: mergeId });
        }
    }
    // Process remaining statements in merge block
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, mergeId, loopStack, inlining);
    }
    // No remaining — merge falls through to implicit return
    state.addBlock(mergeId, [], { kind: 'return', expression: undefined });
    return mergeId;
}
// ---------------------------------------------------------------------------
// While loop
// ---------------------------------------------------------------------------
function processWhileStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining = {}) {
    const condBlockId = state.newBlockId('while_cond');
    const bodyBlockId = state.newBlockId('while_body');
    const exitBlockId = state.newBlockId('while_exit');
    // Finalize current block → fall to condition
    state.addBlock(currentBlockId, accumulated, {
        kind: 'fall',
        target: condBlockId,
    });
    // Condition block → loop terminator
    state.addBlock(condBlockId, [], {
        kind: 'loop',
        condition: stmt.expression,
        bodyBlock: bodyBlockId,
        exitBlock: exitBlockId,
    });
    // Process body with loop context for break/continue
    const loopCtx = { conditionBlock: condBlockId, exitBlock: exitBlockId };
    const bodyStatements = getBlockStatements(stmt.statement);
    const bodyExit = processStatements(state, context, bodyStatements, bodyBlockId, [...loopStack, loopCtx], inlining);
    // Body exit loops back to condition
    if (bodyExit !== null) {
        patchBlockTerminator(state, bodyExit, { kind: 'loopBack', target: condBlockId });
    }
    return exitBlockId;
}
// ---------------------------------------------------------------------------
// Do-while loop
// ---------------------------------------------------------------------------
function processDoWhileStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining = {}) {
    const bodyBlockId = state.newBlockId('do_body');
    const condBlockId = state.newBlockId('do_cond');
    const exitBlockId = state.newBlockId('do_exit');
    // Finalize current block → fall to body
    state.addBlock(currentBlockId, accumulated, {
        kind: 'fall',
        target: bodyBlockId,
    });
    // Process body with loop context
    const loopCtx = { conditionBlock: condBlockId, exitBlock: exitBlockId };
    const bodyStatements = getBlockStatements(stmt.statement);
    const bodyExit = processStatements(state, context, bodyStatements, bodyBlockId, [...loopStack, loopCtx], inlining);
    // Body exit falls to condition check
    if (bodyExit !== null) {
        patchBlockTerminator(state, bodyExit, { kind: 'fall', target: condBlockId });
    }
    // Condition block: branch back to body or exit
    state.addBlock(condBlockId, [], {
        kind: 'branch',
        condition: stmt.expression,
        thenBlock: bodyBlockId,
        elseBlock: exitBlockId,
    });
    return exitBlockId;
}
// ---------------------------------------------------------------------------
// For(;;) loop
// ---------------------------------------------------------------------------
function processForStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining = {}) {
    // Process initializer as part of the pre-loop block
    const initStatements = [...accumulated];
    if (stmt.initializer) {
        if (typescript_1.default.isVariableDeclarationList(stmt.initializer)) {
            // Wrap in a variable statement for consistent handling
            initStatements.push(typescript_1.default.factory.createVariableStatement(undefined, stmt.initializer));
        }
        else {
            initStatements.push(typescript_1.default.factory.createExpressionStatement(stmt.initializer));
        }
    }
    const condBlockId = state.newBlockId('for_cond');
    const bodyBlockId = state.newBlockId('for_body');
    const updateBlockId = state.newBlockId('for_update');
    const exitBlockId = state.newBlockId('for_exit');
    // Finalize current block with init → fall to condition
    state.addBlock(currentBlockId, initStatements, {
        kind: 'fall',
        target: condBlockId,
    });
    // Condition block
    if (stmt.condition) {
        state.addBlock(condBlockId, [], {
            kind: 'loop',
            condition: stmt.condition,
            bodyBlock: bodyBlockId,
            exitBlock: exitBlockId,
        });
    }
    else {
        // No condition = infinite loop (condition always true)
        state.addBlock(condBlockId, [], {
            kind: 'fall',
            target: bodyBlockId,
        });
    }
    // Process body — continue targets the update block, not the condition
    const loopCtx = { conditionBlock: updateBlockId, exitBlock: exitBlockId };
    const bodyStatements = getBlockStatements(stmt.statement);
    const bodyExit = processStatements(state, context, bodyStatements, bodyBlockId, [...loopStack, loopCtx], inlining);
    // Body exit → update block
    if (bodyExit !== null) {
        patchBlockTerminator(state, bodyExit, { kind: 'fall', target: updateBlockId });
    }
    // Update block → loop back to condition
    const updateStatements = [];
    if (stmt.incrementor) {
        updateStatements.push(typescript_1.default.factory.createExpressionStatement(stmt.incrementor));
    }
    state.addBlock(updateBlockId, updateStatements, {
        kind: 'loopBack',
        target: condBlockId,
    });
    return exitBlockId;
}
// ---------------------------------------------------------------------------
// For...of loop (map state)
// ---------------------------------------------------------------------------
function processForOfStatement(state, context, stmt, accumulated, currentBlockId, loopStack, inlining = {}) {
    const bodyBlockId = state.newBlockId('forof_body');
    const exitBlockId = state.newBlockId('forof_exit');
    // Detect if results are collected (const results = [...]; for of; results.push)
    // For now, set collectResults = false — full detection is a Stage 6 concern
    const collectResults = false;
    // Extract iteration variable name and symbol
    let iterVarName = 'item';
    let iterVarSymbol;
    if (typescript_1.default.isVariableDeclarationList(stmt.initializer)) {
        const decl = stmt.initializer.declarations[0];
        if (decl && typescript_1.default.isIdentifier(decl.name)) {
            iterVarName = decl.name.text;
            iterVarSymbol = context.checker.getSymbolAtLocation(decl.name);
        }
    }
    // Detect Steps.sequential() / Steps.items() wrappers and unwrap to the real items expression
    let itemsExpression = stmt.expression;
    let maxConcurrency;
    let retryExpression;
    if (typescript_1.default.isCallExpression(stmt.expression)) {
        if (isStepsCall(stmt.expression, 'sequential')) {
            itemsExpression = stmt.expression.arguments[0];
            maxConcurrency = 1;
        }
        else if (isStepsCall(stmt.expression, 'items')) {
            itemsExpression = stmt.expression.arguments[0];
            const optionsArg = stmt.expression.arguments[1];
            if (optionsArg && typescript_1.default.isObjectLiteralExpression(optionsArg)) {
                for (const prop of optionsArg.properties) {
                    if (!typescript_1.default.isPropertyAssignment(prop) || !typescript_1.default.isIdentifier(prop.name))
                        continue;
                    if (prop.name.text === 'maxConcurrency' && typescript_1.default.isNumericLiteral(prop.initializer)) {
                        maxConcurrency = Number(prop.initializer.text);
                    }
                    else if (prop.name.text === 'retry') {
                        retryExpression = prop.initializer;
                    }
                }
            }
        }
    }
    // Finalize current block with mapState terminator
    state.addBlock(currentBlockId, accumulated, {
        kind: 'mapState',
        expression: stmt,
        itemsExpression,
        iterVarName,
        iterVarSymbol,
        bodyBlock: bodyBlockId,
        exitBlock: exitBlockId,
        collectResults,
        ...(maxConcurrency != null && { maxConcurrency }),
        ...(retryExpression && { retryExpression }),
    });
    // Process body — break/continue targets loop exit/body start
    const loopCtx = { conditionBlock: bodyBlockId, exitBlock: exitBlockId };
    const bodyStatements = getBlockStatements(stmt.statement);
    const bodyExit = processStatements(state, context, bodyStatements, bodyBlockId, [...loopStack, loopCtx], inlining);
    // Body exit: implicit end of iteration
    if (bodyExit !== null) {
        // The body's exit doesn't loop back — each iteration is independent in Map state
        patchBlockTerminator(state, bodyExit, { kind: 'return', expression: undefined });
    }
    return exitBlockId;
}
// ---------------------------------------------------------------------------
// Try/catch/finally
// ---------------------------------------------------------------------------
function processTryStatement(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining = {}) {
    const tryBlockId = state.newBlockId('try');
    const finallyBlockId = stmt.finallyBlock ? state.newBlockId('finally') : undefined;
    const mergeId = state.newBlockId('try_merge');
    // Extract catch parameter name
    let catchParam;
    if (stmt.catchClause?.variableDeclaration?.name && typescript_1.default.isIdentifier(stmt.catchClause.variableDeclaration.name)) {
        catchParam = stmt.catchClause.variableDeclaration.name.text;
    }
    // Analyze catch block for instanceof chains
    const targetAfterCatch = finallyBlockId ?? mergeId;
    let errorHandlers = [];
    let catchFallback;
    let catchExit = null;
    if (stmt.catchClause) {
        const catchStatements = Array.from(stmt.catchClause.block.statements);
        const instanceofChain = catchParam
            ? extractInstanceofChain(catchStatements, catchParam)
            : null;
        if (instanceofChain && instanceofChain.handlers.length > 0) {
            // Process each instanceof handler as a separate CFG block
            for (const handler of instanceofChain.handlers) {
                const handlerBlockId = state.newBlockId(`catch_${handler.className}`);
                const handlerExit = processStatements(state, context, handler.statements, handlerBlockId, loopStack, inlining);
                if (handlerExit !== null) {
                    patchBlockTerminator(state, handlerExit, { kind: 'fall', target: targetAfterCatch });
                }
                errorHandlers.push({ errorClassName: handler.className, blockId: handlerBlockId });
            }
            // Process fallback (else block or remaining statements)
            if (instanceofChain.fallbackStatements.length > 0) {
                const fallbackBlockId = state.newBlockId('catch_fallback');
                const fallbackExit = processStatements(state, context, instanceofChain.fallbackStatements, fallbackBlockId, loopStack, inlining);
                if (fallbackExit !== null) {
                    patchBlockTerminator(state, fallbackExit, { kind: 'fall', target: targetAfterCatch });
                }
                catchFallback = fallbackBlockId;
                catchExit = fallbackExit;
            }
        }
        else {
            // No instanceof pattern — whole catch block is a States.ALL fallback
            const catchBlockId = state.newBlockId('catch');
            catchExit = processStatements(state, context, catchStatements, catchBlockId, loopStack, inlining);
            if (catchExit !== null) {
                patchBlockTerminator(state, catchExit, { kind: 'fall', target: targetAfterCatch });
            }
            catchFallback = catchBlockId;
        }
    }
    // Finalize current block with tryCatch terminator
    state.addBlock(currentBlockId, accumulated, {
        kind: 'tryCatch',
        tryBlock: tryBlockId,
        catchParam,
        errorHandlers,
        catchFallback,
        finallyBlock: finallyBlockId,
        mergeBlock: mergeId,
    });
    // Process try block
    const tryStatements = Array.from(stmt.tryBlock.statements);
    const tryExit = processStatements(state, context, tryStatements, tryBlockId, loopStack, inlining);
    // Wire try exit to merge (or finally)
    const targetAfterTry = finallyBlockId ?? mergeId;
    if (tryExit !== null) {
        patchBlockTerminator(state, tryExit, { kind: 'fall', target: targetAfterTry });
    }
    // Process finally block
    if (finallyBlockId && stmt.finallyBlock) {
        const finallyStatements = Array.from(stmt.finallyBlock.statements);
        const finallyExit = processStatements(state, context, finallyStatements, finallyBlockId, loopStack, inlining);
        if (finallyExit !== null) {
            patchBlockTerminator(state, finallyExit, { kind: 'fall', target: mergeId });
        }
    }
    // Process remaining statements in merge block
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, mergeId, loopStack, inlining);
    }
    // Always create merge block so the CFG is structurally complete
    state.addBlock(mergeId, [], { kind: 'return', expression: undefined });
    // If both try and catch terminate and there's no finally, merge is unreachable
    if (tryExit === null && catchExit === null && !stmt.finallyBlock) {
        return null;
    }
    return mergeId;
}
/**
 * Analyze catch block statements for an instanceof chain pattern:
 *   if (e instanceof FooError) { ... }
 *   else if (e instanceof BarError) { ... }
 *   else { ... }
 *
 * Returns null if the first statement is not an instanceof if-chain.
 */
function extractInstanceofChain(statements, catchParamName) {
    if (statements.length === 0)
        return null;
    const firstStmt = statements[0];
    if (!typescript_1.default.isIfStatement(firstStmt))
        return null;
    // Check that the condition is `catchParam instanceof ClassName`
    if (!isInstanceofCheck(firstStmt.expression, catchParamName))
        return null;
    const handlers = [];
    let fallbackStatements = [];
    // Track which top-level statement index we've consumed
    let stmtIndex = 0;
    let current = firstStmt;
    while (current) {
        const className = extractInstanceofClassName(current.expression, catchParamName);
        if (!className)
            break;
        const body = getBlockStatements(current.thenStatement);
        handlers.push({ className, statements: Array.from(body) });
        if (current.elseStatement) {
            if (typescript_1.default.isIfStatement(current.elseStatement)) {
                // else if — continue the chain
                if (isInstanceofCheck(current.elseStatement.expression, catchParamName)) {
                    current = current.elseStatement;
                }
                else {
                    // else if with non-instanceof condition — treat as fallback
                    fallbackStatements = [current.elseStatement];
                    current = undefined;
                }
            }
            else {
                // else block — this is the fallback
                fallbackStatements = Array.from(getBlockStatements(current.elseStatement));
                current = undefined;
            }
        }
        else {
            // No else — check if the NEXT statement is also an instanceof check
            // This handles: if (e instanceof A) { ... } if (e instanceof B) { ... }
            current = undefined;
            stmtIndex++;
            while (stmtIndex < statements.length) {
                const nextStmt = statements[stmtIndex];
                if (typescript_1.default.isIfStatement(nextStmt) && isInstanceofCheck(nextStmt.expression, catchParamName)) {
                    current = nextStmt;
                    break;
                }
                else {
                    // Non-instanceof statement — stop scanning
                    break;
                }
            }
        }
    }
    // Include any remaining statements after the consumed if-chain as part of the fallback
    const remainingStart = stmtIndex + 1;
    if (remainingStart < statements.length) {
        fallbackStatements = [...fallbackStatements, ...statements.slice(remainingStart)];
    }
    return handlers.length > 0 ? { handlers, fallbackStatements } : null;
}
/**
 * Check if an expression is `paramName instanceof ClassName`.
 */
function isInstanceofCheck(expr, paramName) {
    if (!typescript_1.default.isBinaryExpression(expr))
        return false;
    if (expr.operatorToken.kind !== typescript_1.default.SyntaxKind.InstanceOfKeyword)
        return false;
    if (!typescript_1.default.isIdentifier(expr.left) || expr.left.text !== paramName)
        return false;
    if (!typescript_1.default.isIdentifier(expr.right))
        return false;
    return true;
}
/**
 * Extract the class name from `paramName instanceof ClassName`.
 */
function extractInstanceofClassName(expr, paramName) {
    if (!isInstanceofCheck(expr, paramName))
        return null;
    return expr.right.getText();
}
// ---------------------------------------------------------------------------
// Switch/case (desugared to chained if-else branches)
// ---------------------------------------------------------------------------
function processSwitchStatement(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining = {}) {
    const mergeId = state.newBlockId('switch_merge');
    const discriminant = stmt.expression;
    const clauses = Array.from(stmt.caseBlock.clauses);
    // Validate: no fall-through (each non-empty case must end with break/return/throw/continue)
    for (const clause of clauses) {
        if (clause.statements.length === 0)
            continue;
        const lastStmt = clause.statements[clause.statements.length - 1];
        if (!typescript_1.default.isBreakStatement(lastStmt) && !typescript_1.default.isReturnStatement(lastStmt) && !typescript_1.default.isThrowStatement(lastStmt) && !typescript_1.default.isContinueStatement(lastStmt)) {
            // Check if the last statement is a block ending with break/return/throw/continue
            if (typescript_1.default.isBlock(lastStmt)) {
                const blockLast = lastStmt.statements[lastStmt.statements.length - 1];
                if (blockLast && (typescript_1.default.isBreakStatement(blockLast) || typescript_1.default.isReturnStatement(blockLast) || typescript_1.default.isThrowStatement(blockLast) || typescript_1.default.isContinueStatement(blockLast))) {
                    continue;
                }
            }
            context.addError(clause, 'Switch case fall-through is not supported; each case must end with break, return, or throw', diagnosticCodes_js_1.ErrorCodes.Cfg.SwitchFallThrough.code);
        }
    }
    // Separate cases and default
    const caseClauses = [];
    let defaultClause;
    for (const clause of clauses) {
        if (typescript_1.default.isCaseClause(clause)) {
            caseClauses.push(clause);
        }
        else {
            defaultClause = clause;
        }
    }
    // Create a switch "loop context" so break exits to mergeId
    const switchCtx = { conditionBlock: mergeId, exitBlock: mergeId, isSwitch: true };
    const switchLoopStack = [...loopStack, switchCtx];
    // Process default body
    let defaultBlockId;
    if (defaultClause && defaultClause.statements.length > 0) {
        defaultBlockId = state.newBlockId('switch_default');
        const defaultBody = filterBreaks(Array.from(defaultClause.statements));
        const defaultExit = processStatements(state, context, defaultBody, defaultBlockId, switchLoopStack, inlining);
        if (defaultExit !== null) {
            patchBlockTerminator(state, defaultExit, { kind: 'fall', target: mergeId });
        }
    }
    else {
        // No default clause → fall through to merge
        defaultBlockId = state.newBlockId('switch_default');
        state.addBlock(defaultBlockId, [], { kind: 'fall', target: mergeId });
    }
    // Build chained branches in reverse order
    // Last case's else → default
    let currentElseBlock = defaultBlockId;
    for (let i = caseClauses.length - 1; i >= 0; i--) {
        const clause = caseClauses[i];
        // Create the body block for this case
        const caseBodyId = state.newBlockId(`switch_case_${i}`);
        const caseBody = filterBreaks(Array.from(clause.statements));
        const caseExit = processStatements(state, context, caseBody, caseBodyId, switchLoopStack, inlining);
        if (caseExit !== null) {
            patchBlockTerminator(state, caseExit, { kind: 'fall', target: mergeId });
        }
        // Create a condition block with branch terminator: discriminant === caseValue
        const condBlockId = state.newBlockId(`switch_cond_${i}`);
        const syntheticCondition = typescript_1.default.factory.createBinaryExpression(discriminant, typescript_1.default.factory.createToken(typescript_1.default.SyntaxKind.EqualsEqualsEqualsToken), clause.expression);
        state.addBlock(condBlockId, [], {
            kind: 'branch',
            condition: syntheticCondition,
            thenBlock: caseBodyId,
            elseBlock: currentElseBlock,
        });
        currentElseBlock = condBlockId;
    }
    // Finalize the current (pre-switch) block → fall to the first condition
    state.addBlock(currentBlockId, accumulated, {
        kind: 'fall',
        target: currentElseBlock,
    });
    // Process remaining statements in the merge block
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, mergeId, loopStack, inlining);
    }
    state.addBlock(mergeId, [], { kind: 'return', expression: undefined });
    return mergeId;
}
/**
 * Remove trailing break statements from case body (they're handled structurally).
 */
function filterBreaks(stmts) {
    if (stmts.length > 0 && typescript_1.default.isBreakStatement(stmts[stmts.length - 1])) {
        return stmts.slice(0, -1);
    }
    return stmts;
}
// ---------------------------------------------------------------------------
// Helper function inlining
// ---------------------------------------------------------------------------
/**
 * Detect and inline an awaited helper function call.
 *
 * Matches patterns:
 *   await helperFunc(arg1, arg2);                   // void call
 *   const x = await helperFunc(arg1, arg2);         // value-returning call
 *
 * Returns:
 *   - string | null if the statement was inlined (the exit block ID or null)
 *   - undefined if this statement is not a helper call (caller should continue)
 */
function tryInlineHelperCall(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining) {
    const { helperRegistry, inlineBindings } = inlining;
    if (!helperRegistry || !inlineBindings)
        return undefined;
    // Match: await helperFunc(arg1, arg2)              — void call
    //   or:  const x = await helperFunc(arg1, arg2)  — value-returning call
    let callExpr;
    let resultVarName;
    let resultVarSymbol;
    // Pattern 1: await helperFunc(arg1, arg2) — void
    if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression)) {
        const awaited = stmt.expression.expression;
        if (typescript_1.default.isCallExpression(awaited)) {
            callExpr = awaited;
        }
    }
    // Pattern 2: const x = await helperFunc(arg1, arg2) — value-returning
    if (!callExpr && typescript_1.default.isVariableStatement(stmt)) {
        const decls = stmt.declarationList.declarations;
        if (decls.length === 1) {
            const decl = decls[0];
            if (typescript_1.default.isIdentifier(decl.name) && decl.initializer && typescript_1.default.isAwaitExpression(decl.initializer)) {
                const awaited = decl.initializer.expression;
                if (typescript_1.default.isCallExpression(awaited)) {
                    callExpr = awaited;
                    resultVarName = decl.name.text;
                    resultVarSymbol = context.checker.getSymbolAtLocation(decl.name);
                }
            }
        }
    }
    if (!callExpr)
        return undefined;
    // Resolve the called function's symbol
    const calleeSym = context.checker.getSymbolAtLocation(callExpr.expression);
    if (!calleeSym)
        return undefined;
    // Check if this symbol is in the helper registry
    // Follow aliases for imports
    const resolvedSym = (calleeSym.flags & typescript_1.default.SymbolFlags.Alias)
        ? context.checker.getAliasedSymbol(calleeSym)
        : calleeSym;
    const helper = helperRegistry.get(resolvedSym);
    if (!helper)
        return undefined;
    // Validate argument count (allow fewer args when trailing params have defaults)
    const requiredCount = helper.parameters.filter(p => !p.initializer).length;
    if (callExpr.arguments.length > helper.parameters.length ||
        callExpr.arguments.length < requiredCount) {
        context.addError(callExpr, `Substep '${helper.symbol.getName()}' expects ${requiredCount === helper.parameters.length ? String(helper.parameters.length) : `${requiredCount}-${helper.parameters.length}`} arguments but got ${callExpr.arguments.length}`, diagnosticCodes_js_1.ErrorCodes.Inlining.UninlinableFunction.code);
        return undefined;
    }
    // Record parameter-to-argument bindings for the variable resolver
    for (let p = 0; p < helper.parameters.length; p++) {
        const param = helper.parameters[p];
        // Use call-site argument if provided, otherwise fall back to default value
        const argExpr = p < callExpr.arguments.length
            ? callExpr.arguments[p]
            : param.initializer; // guaranteed present by requiredCount check
        if (typescript_1.default.isIdentifier(param.name)) {
            // Simple identifier parameter — one binding
            const paramSym = context.checker.getSymbolAtLocation(param.name);
            if (paramSym) {
                inlineBindings.push({ paramSymbol: paramSym, argExpression: argExpr });
            }
        }
        else if (typescript_1.default.isObjectBindingPattern(param.name)) {
            // Destructured parameter — one binding per element.
            // If the argument is an object literal, extract property values directly.
            // Otherwise, create synthetic property access expressions (for variable args).
            for (const element of param.name.elements) {
                const elemSym = context.checker.getSymbolAtLocation(element.name);
                if (!elemSym)
                    continue;
                // Property name: explicit rename ({ userId: id }) or same as binding name
                const propName = element.propertyName
                    ? element.propertyName.text
                    : element.name.text;
                let bindingExpr;
                // Try to extract directly from object literal argument
                if (typescript_1.default.isObjectLiteralExpression(argExpr)) {
                    const prop = argExpr.properties.find(p => {
                        if (typescript_1.default.isPropertyAssignment(p) && typescript_1.default.isIdentifier(p.name))
                            return p.name.text === propName;
                        if (typescript_1.default.isShorthandPropertyAssignment(p))
                            return p.name.text === propName;
                        return false;
                    });
                    if (prop) {
                        bindingExpr = typescript_1.default.isPropertyAssignment(prop)
                            ? prop.initializer
                            : prop.name;
                    }
                }
                // Fallback: synthetic property access (for variable/identifier args)
                if (!bindingExpr) {
                    bindingExpr = typescript_1.default.factory.createPropertyAccessExpression(argExpr, propName);
                }
                inlineBindings.push({ paramSymbol: elemSym, argExpression: bindingExpr });
            }
        }
    }
    // Finalize the current block with accumulated statements → fall to inline entry
    const inlineEntryId = state.newBlockId(`inline_${helper.symbol.getName()}`);
    const continuationId = state.newBlockId(`after_${helper.symbol.getName()}`);
    state.addBlock(currentBlockId, accumulated, {
        kind: 'fall',
        target: inlineEntryId,
    });
    // Process the helper's body statements with returnOverride → continuation
    const helperStatements = Array.from(helper.body.statements);
    const inlineInlining = {
        ...inlining,
        returnOverride: continuationId,
        returnVarName: resultVarName,
        returnVarSymbol: resultVarSymbol,
    };
    const inlineExit = processStatements(state, context, helperStatements, inlineEntryId, loopStack, inlineInlining);
    // Wire inline exit to continuation (if not already terminated)
    if (inlineExit !== null) {
        patchBlockTerminator(state, inlineExit, { kind: 'fall', target: continuationId });
    }
    // Process remaining statements in the continuation block
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, continuationId, loopStack, inlining);
    }
    // No remaining statements — continuation falls through
    state.addBlock(continuationId, [], { kind: 'return', expression: undefined });
    return continuationId;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Extract statements from a statement that may or may not be a Block.
 */
function getBlockStatements(stmt) {
    if (typescript_1.default.isBlock(stmt)) {
        return Array.from(stmt.statements);
    }
    return [stmt];
}
/**
 * Replace the terminator of an existing block, or create a new block
 * with just the terminator if the block doesn't exist yet.
 *
 * Used to wire exits from branches/loops to merge or continuation blocks.
 */
function patchBlockTerminator(state, blockId, terminator) {
    const existing = state.blocks.get(blockId);
    if (existing) {
        // Replace the block with updated terminator, preserving returnTargetVar
        state.blocks.set(blockId, {
            id: existing.id,
            statements: existing.statements,
            terminator,
            ...(existing.returnTargetVar && { returnTargetVar: existing.returnTargetVar }),
        });
    }
    else {
        state.addBlock(blockId, [], terminator);
    }
}
// ---------------------------------------------------------------------------
// Steps.map() detection
// ---------------------------------------------------------------------------
/**
 * Detect `await Steps.map(items, callback, options?)` or
 * `const results = await Steps.map(items, callback, options?)`.
 *
 * Returns the exit block ID if matched, undefined if not matched.
 */
function tryExtractStepsMap(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining) {
    // Pattern 1: const results = await Steps.map(items, cb, opts?)
    // Pattern 2: await Steps.map(items, cb, opts?)  (fire-and-forget)
    let callExpr;
    let collectResults = false;
    let resultBindingName;
    let resultSymbol;
    if (typescript_1.default.isVariableStatement(stmt)) {
        const decl = stmt.declarationList.declarations[0];
        if (decl?.initializer && typescript_1.default.isAwaitExpression(decl.initializer) &&
            typescript_1.default.isCallExpression(decl.initializer.expression)) {
            const call = decl.initializer.expression;
            if (isStepsMapCall(call)) {
                callExpr = call;
                collectResults = true;
                if (typescript_1.default.isIdentifier(decl.name)) {
                    resultBindingName = decl.name.text;
                    resultSymbol = context.checker.getSymbolAtLocation(decl.name);
                }
            }
        }
    }
    else if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression) &&
        typescript_1.default.isCallExpression(stmt.expression.expression)) {
        const call = stmt.expression.expression;
        if (isStepsMapCall(call)) {
            callExpr = call;
            collectResults = false;
        }
    }
    if (!callExpr)
        return undefined;
    // Extract arguments: Steps.map(items, callback, options?)
    const itemsArg = callExpr.arguments[0];
    const callbackArg = callExpr.arguments[1];
    const optionsArg = callExpr.arguments[2];
    if (!itemsArg || !callbackArg) {
        context.addError(callExpr, 'Steps.map() requires at least 2 arguments: items and callback', diagnosticCodes_js_1.ErrorCodes.Cfg.InvalidMapCall.code);
        return undefined;
    }
    // Callback must be an arrow function or function expression
    let callbackBody;
    let callbackParam;
    if (typescript_1.default.isArrowFunction(callbackArg) || typescript_1.default.isFunctionExpression(callbackArg)) {
        callbackParam = callbackArg.parameters[0];
        if (typescript_1.default.isBlock(callbackArg.body)) {
            callbackBody = callbackArg.body;
        }
        else {
            // Expression body: (item) => expr  →  wrap in { return expr; }
            // The CFG builder processes statements, so we treat the expression as a single return
            // For now, only block bodies are supported
            context.addError(callbackArg, 'Steps.map() callback must have a block body (use { } braces)', diagnosticCodes_js_1.ErrorCodes.Cfg.InvalidMapCall.code);
            return undefined;
        }
    }
    else {
        context.addError(callbackArg, 'Steps.map() callback must be an arrow function or function expression', diagnosticCodes_js_1.ErrorCodes.Cfg.InvalidMapCall.code);
        return undefined;
    }
    // Extract maxConcurrency and retry from options
    let maxConcurrency;
    let retryExpression;
    if (optionsArg && typescript_1.default.isObjectLiteralExpression(optionsArg)) {
        for (const prop of optionsArg.properties) {
            if (!typescript_1.default.isPropertyAssignment(prop) || !typescript_1.default.isIdentifier(prop.name))
                continue;
            if (prop.name.text === 'maxConcurrency' && typescript_1.default.isNumericLiteral(prop.initializer)) {
                maxConcurrency = Number(prop.initializer.text);
            }
            else if (prop.name.text === 'retry') {
                retryExpression = prop.initializer;
            }
        }
    }
    // Extract iteration variable name and symbol
    let iterVarName = 'item';
    let iterVarSymbol;
    if (callbackParam && typescript_1.default.isIdentifier(callbackParam.name)) {
        iterVarName = callbackParam.name.text;
        iterVarSymbol = context.checker.getSymbolAtLocation(callbackParam.name);
    }
    const bodyBlockId = state.newBlockId('map_body');
    const exitBlockId = state.newBlockId('map_exit');
    // Finalize current block with mapState terminator
    state.addBlock(currentBlockId, accumulated, {
        kind: 'mapState',
        itemsExpression: itemsArg,
        iterVarName,
        iterVarSymbol,
        bodyBlock: bodyBlockId,
        exitBlock: exitBlockId,
        collectResults,
        maxConcurrency,
        ...(resultBindingName && { resultBindingName }),
        ...(resultSymbol && { resultSymbol }),
        ...(retryExpression && { retryExpression }),
    });
    // Process the callback body
    const bodyStatements = Array.from(callbackBody.statements);
    const bodyExit = processStatements(state, context, bodyStatements, bodyBlockId, [...loopStack, { conditionBlock: bodyBlockId, exitBlock: exitBlockId }], inlining);
    // Body exit: implicit end of iteration (each iteration is independent in Map)
    if (bodyExit !== null) {
        patchBlockTerminator(state, bodyExit, { kind: 'return', expression: undefined });
    }
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, exitBlockId, loopStack, inlining);
    }
    return exitBlockId;
}
function isStepsMapCall(call) {
    if (!typescript_1.default.isPropertyAccessExpression(call.expression))
        return false;
    const prop = call.expression;
    return typescript_1.default.isIdentifier(prop.expression) && prop.expression.text === 'Steps' &&
        prop.name.text === 'map';
}
function isStepsDistributedMapCall(call) {
    if (!typescript_1.default.isPropertyAccessExpression(call.expression))
        return false;
    const prop = call.expression;
    return typescript_1.default.isIdentifier(prop.expression) && prop.expression.text === 'Steps' &&
        prop.name.text === 'distributedMap';
}
/**
 * Extract Steps.distributedMap(items, callback, options?) into a MapStateTerminator
 * with distributed: true and additional fields for ItemReader, ResultWriter, etc.
 */
function tryExtractStepsDistributedMap(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining) {
    // Pattern 1: const results = await Steps.distributedMap(items, cb, opts?)
    // Pattern 2: await Steps.distributedMap(items, cb, opts?)  (fire-and-forget)
    let callExpr;
    let collectResults = false;
    let resultBindingName;
    let resultSymbol;
    if (typescript_1.default.isVariableStatement(stmt)) {
        const decl = stmt.declarationList.declarations[0];
        if (decl?.initializer && typescript_1.default.isAwaitExpression(decl.initializer) &&
            typescript_1.default.isCallExpression(decl.initializer.expression)) {
            const call = decl.initializer.expression;
            if (isStepsDistributedMapCall(call)) {
                callExpr = call;
                collectResults = true;
                if (typescript_1.default.isIdentifier(decl.name)) {
                    resultBindingName = decl.name.text;
                    resultSymbol = context.checker.getSymbolAtLocation(decl.name);
                }
            }
        }
    }
    else if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression) &&
        typescript_1.default.isCallExpression(stmt.expression.expression)) {
        const call = stmt.expression.expression;
        if (isStepsDistributedMapCall(call)) {
            callExpr = call;
            collectResults = false;
        }
    }
    if (!callExpr)
        return undefined;
    // Extract arguments: Steps.distributedMap(items, callback, options?)
    const itemsArg = callExpr.arguments[0];
    const callbackArg = callExpr.arguments[1];
    const optionsArg = callExpr.arguments[2];
    if (!itemsArg || !callbackArg) {
        context.addError(callExpr, 'Steps.distributedMap() requires at least 2 arguments: items and callback', diagnosticCodes_js_1.ErrorCodes.Cfg.InvalidDistributedMapCall.code);
        return undefined;
    }
    // Callback must be an arrow function or function expression
    let callbackBody;
    let callbackParam;
    if (typescript_1.default.isArrowFunction(callbackArg) || typescript_1.default.isFunctionExpression(callbackArg)) {
        callbackParam = callbackArg.parameters[0];
        if (typescript_1.default.isBlock(callbackArg.body)) {
            callbackBody = callbackArg.body;
        }
        else {
            context.addError(callbackArg, 'Steps.distributedMap() callback must have a block body (use { } braces)', diagnosticCodes_js_1.ErrorCodes.Cfg.InvalidDistributedMapCall.code);
            return undefined;
        }
    }
    else {
        context.addError(callbackArg, 'Steps.distributedMap() callback must be an arrow function or function expression', diagnosticCodes_js_1.ErrorCodes.Cfg.InvalidDistributedMapCall.code);
        return undefined;
    }
    // Extract options
    let maxConcurrency;
    let executionType;
    let retryExpression;
    let itemReaderExpression;
    let resultWriterExpression;
    let itemBatcherExpression;
    let toleratedFailurePercentage;
    let toleratedFailureCount;
    let label;
    if (optionsArg && typescript_1.default.isObjectLiteralExpression(optionsArg)) {
        for (const prop of optionsArg.properties) {
            if (!typescript_1.default.isPropertyAssignment(prop) || !typescript_1.default.isIdentifier(prop.name))
                continue;
            switch (prop.name.text) {
                case 'maxConcurrency':
                    if (typescript_1.default.isNumericLiteral(prop.initializer)) {
                        maxConcurrency = Number(prop.initializer.text);
                    }
                    break;
                case 'executionType':
                    if (typescript_1.default.isStringLiteral(prop.initializer)) {
                        const val = prop.initializer.text;
                        if (val === 'STANDARD' || val === 'EXPRESS') {
                            executionType = val;
                        }
                    }
                    break;
                case 'retry':
                    retryExpression = prop.initializer;
                    break;
                case 'itemReader':
                    itemReaderExpression = prop.initializer;
                    break;
                case 'resultWriter':
                    resultWriterExpression = prop.initializer;
                    break;
                case 'itemBatcher':
                    itemBatcherExpression = prop.initializer;
                    break;
                case 'toleratedFailurePercentage':
                    if (typescript_1.default.isNumericLiteral(prop.initializer)) {
                        toleratedFailurePercentage = Number(prop.initializer.text);
                    }
                    break;
                case 'toleratedFailureCount':
                    if (typescript_1.default.isNumericLiteral(prop.initializer)) {
                        toleratedFailureCount = Number(prop.initializer.text);
                    }
                    break;
                case 'label':
                    if (typescript_1.default.isStringLiteral(prop.initializer)) {
                        label = prop.initializer.text;
                    }
                    break;
            }
        }
    }
    // Extract iteration variable name and symbol
    let iterVarName = 'item';
    let iterVarSymbol;
    if (callbackParam && typescript_1.default.isIdentifier(callbackParam.name)) {
        iterVarName = callbackParam.name.text;
        iterVarSymbol = context.checker.getSymbolAtLocation(callbackParam.name);
    }
    const bodyBlockId = state.newBlockId('distmap_body');
    const exitBlockId = state.newBlockId('distmap_exit');
    // Finalize current block with mapState terminator (distributed)
    state.addBlock(currentBlockId, accumulated, {
        kind: 'mapState',
        itemsExpression: itemsArg,
        iterVarName,
        iterVarSymbol,
        bodyBlock: bodyBlockId,
        exitBlock: exitBlockId,
        collectResults,
        maxConcurrency,
        ...(resultBindingName && { resultBindingName }),
        ...(resultSymbol && { resultSymbol }),
        ...(retryExpression && { retryExpression }),
        distributed: true,
        ...(executionType && { executionType }),
        ...(itemReaderExpression && { itemReaderExpression }),
        ...(resultWriterExpression && { resultWriterExpression }),
        ...(itemBatcherExpression && { itemBatcherExpression }),
        ...(toleratedFailurePercentage != null && { toleratedFailurePercentage }),
        ...(toleratedFailureCount != null && { toleratedFailureCount }),
        ...(label && { label }),
    });
    // Process the callback body
    const bodyStatements = Array.from(callbackBody.statements);
    const bodyExit = processStatements(state, context, bodyStatements, bodyBlockId, [...loopStack, { conditionBlock: bodyBlockId, exitBlock: exitBlockId }], inlining);
    // Body exit: implicit end of iteration
    if (bodyExit !== null) {
        patchBlockTerminator(state, bodyExit, { kind: 'return', expression: undefined });
    }
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, exitBlockId, loopStack, inlining);
    }
    return exitBlockId;
}
/** Check if an expression is a call to Steps.<method>() */
function isStepsCall(expr, method) {
    if (!typescript_1.default.isCallExpression(expr))
        return false;
    if (!typescript_1.default.isPropertyAccessExpression(expr.expression))
        return false;
    const prop = expr.expression;
    return typescript_1.default.isIdentifier(prop.expression) && prop.expression.text === 'Steps' &&
        prop.name.text === method;
}
// ---------------------------------------------------------------------------
// Steps.parallel() → Parallel state with retry options
// ---------------------------------------------------------------------------
/**
 * Detect `const [a, b] = await Steps.parallel([fn1, fn2], { retry: {...} })`
 * or `await Steps.parallel([fn1, fn2], { retry: {...} })` (fire-and-forget).
 */
function tryExtractStepsParallel(state, context, stmt, accumulated, currentBlockId, remaining, loopStack, inlining) {
    let callExpr;
    let resultBindings = [];
    let resultSymbols = [];
    // Pattern 1: const [a, b] = await Steps.parallel([...], opts)
    if (typescript_1.default.isVariableStatement(stmt)) {
        const decl = stmt.declarationList.declarations[0];
        if (decl?.initializer && typescript_1.default.isAwaitExpression(decl.initializer) &&
            typescript_1.default.isCallExpression(decl.initializer.expression) &&
            isStepsCall(decl.initializer.expression, 'parallel')) {
            callExpr = decl.initializer.expression;
            if (typescript_1.default.isArrayBindingPattern(decl.name)) {
                for (const el of decl.name.elements) {
                    if (typescript_1.default.isBindingElement(el) && typescript_1.default.isIdentifier(el.name)) {
                        resultBindings.push(el.name.text);
                        resultSymbols.push(context.checker.getSymbolAtLocation(el.name));
                    }
                    else {
                        resultBindings.push('_');
                        resultSymbols.push(undefined);
                    }
                }
            }
            else if (typescript_1.default.isIdentifier(decl.name)) {
                resultBindings = [decl.name.text];
                resultSymbols = [context.checker.getSymbolAtLocation(decl.name)];
            }
        }
    }
    // Pattern 2: await Steps.parallel([...], opts)  (fire-and-forget)
    if (!callExpr && typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression) &&
        typescript_1.default.isCallExpression(stmt.expression.expression) &&
        isStepsCall(stmt.expression.expression, 'parallel')) {
        callExpr = stmt.expression.expression;
    }
    if (!callExpr)
        return undefined;
    // First arg: array of branch functions
    const branchesArg = callExpr.arguments[0];
    if (!branchesArg || !typescript_1.default.isArrayLiteralExpression(branchesArg)) {
        context.addError(callExpr, 'Steps.parallel() first argument must be an array literal of functions', diagnosticCodes_js_1.ErrorCodes.Cfg.PromiseAllNotArray.code);
        return undefined;
    }
    // Extract branch expressions: unwrap () => expr to just expr
    const branches = [];
    for (const el of branchesArg.elements) {
        if (typescript_1.default.isArrowFunction(el) && !typescript_1.default.isBlock(el.body)) {
            branches.push(el.body);
        }
        else if (typescript_1.default.isArrowFunction(el) && typescript_1.default.isBlock(el.body)) {
            // Block body arrow: () => { return await svc.call(...) }
            // Extract the await expression from the single return statement
            const stmts = el.body.statements;
            if (stmts.length === 1 && typescript_1.default.isReturnStatement(stmts[0]) && stmts[0].expression) {
                branches.push(stmts[0].expression);
            }
            else {
                branches.push(el);
            }
        }
        else {
            branches.push(el);
        }
    }
    // Second arg: options with retry
    let retryExpression;
    const optionsArg = callExpr.arguments[1];
    if (optionsArg && typescript_1.default.isObjectLiteralExpression(optionsArg)) {
        for (const prop of optionsArg.properties) {
            if (!typescript_1.default.isPropertyAssignment(prop) || !typescript_1.default.isIdentifier(prop.name))
                continue;
            if (prop.name.text === 'retry') {
                retryExpression = prop.initializer;
            }
        }
    }
    const exitBlockId = state.newBlockId('parallel_exit');
    const classifiedBranches = classifyParallelBranches(state, context, branches, loopStack, inlining);
    state.addBlock(currentBlockId, accumulated, {
        kind: 'parallel',
        branches: classifiedBranches,
        resultBindings,
        resultSymbols,
        exitBlock: exitBlockId,
        ...(retryExpression && { retryExpression }),
    });
    if (remaining.length > 0) {
        return processStatements(state, context, remaining, exitBlockId, loopStack, inlining);
    }
    return exitBlockId;
}
// ---------------------------------------------------------------------------
// Parallel branch classification
// ---------------------------------------------------------------------------
/**
 * Classify Promise.all branches as direct expressions or substep calls.
 * When a branch is a call to a registered substep, inline the substep body
 * into a sub-block to produce a multi-state branch.
 */
function classifyParallelBranches(state, context, branches, loopStack, inlining) {
    const result = [];
    for (const expr of branches) {
        // Unwrap await
        let callExpr = expr;
        if (typescript_1.default.isAwaitExpression(callExpr)) {
            callExpr = callExpr.expression;
        }
        // Check if this is a call to a registered substep/helper
        if (typescript_1.default.isCallExpression(callExpr) && inlining.helperRegistry) {
            const calleeSym = context.checker.getSymbolAtLocation(callExpr.expression);
            if (calleeSym) {
                const resolvedSym = (calleeSym.flags & typescript_1.default.SymbolFlags.Alias)
                    ? context.checker.getAliasedSymbol(calleeSym)
                    : calleeSym;
                const helper = inlining.helperRegistry.get(resolvedSym);
                if (helper) {
                    // This is a substep call — inline its body into a sub-block
                    const branchBlockId = state.newBlockId(`parallel_branch_${helper.symbol.getName()}`);
                    // Record parameter-to-argument bindings
                    if (inlining.inlineBindings) {
                        for (let p = 0; p < helper.parameters.length; p++) {
                            const param = helper.parameters[p];
                            const argExpr = p < callExpr.arguments.length
                                ? callExpr.arguments[p]
                                : param.initializer;
                            if (typescript_1.default.isIdentifier(param.name)) {
                                const paramSym = context.checker.getSymbolAtLocation(param.name);
                                if (paramSym) {
                                    inlining.inlineBindings.push({ paramSymbol: paramSym, argExpression: argExpr });
                                }
                            }
                            else if (typescript_1.default.isObjectBindingPattern(param.name)) {
                                for (const element of param.name.elements) {
                                    const elemSym = context.checker.getSymbolAtLocation(element.name);
                                    if (!elemSym)
                                        continue;
                                    const propName = element.propertyName
                                        ? element.propertyName.text
                                        : element.name.text;
                                    let bindingExpr;
                                    if (typescript_1.default.isObjectLiteralExpression(argExpr)) {
                                        const prop = argExpr.properties.find(p => {
                                            if (typescript_1.default.isPropertyAssignment(p) && typescript_1.default.isIdentifier(p.name))
                                                return p.name.text === propName;
                                            if (typescript_1.default.isShorthandPropertyAssignment(p))
                                                return p.name.text === propName;
                                            return false;
                                        });
                                        if (prop) {
                                            bindingExpr = typescript_1.default.isPropertyAssignment(prop)
                                                ? prop.initializer
                                                : prop.name;
                                        }
                                    }
                                    if (!bindingExpr) {
                                        bindingExpr = typescript_1.default.factory.createPropertyAccessExpression(argExpr, propName);
                                    }
                                    inlining.inlineBindings.push({ paramSymbol: elemSym, argExpression: bindingExpr });
                                }
                            }
                        }
                    }
                    // Process the helper's body into a sub-graph
                    const bodyStatements = Array.from(helper.body.statements);
                    const bodyExit = processStatements(state, context, bodyStatements, branchBlockId, loopStack, { ...inlining, returnOverride: undefined, returnVarName: undefined, returnVarSymbol: undefined });
                    // Body exit: end of branch (implicit return)
                    if (bodyExit !== null) {
                        patchBlockTerminator(state, bodyExit, { kind: 'return', expression: undefined });
                    }
                    result.push({ kind: 'substep', bodyBlock: branchBlockId });
                    continue;
                }
            }
        }
        // Not a substep — direct expression
        result.push({ kind: 'expression', expression: expr });
    }
    return result;
}
/**
 * Extract Promise.all([...]) from a variable statement.
 * Supports:
 *   const [a, b] = await Promise.all([exprA, exprB]);
 *   const result = await Promise.all([exprA, exprB]);
 */
function extractPromiseAll(context, stmt) {
    const decl = stmt.declarationList.declarations[0];
    if (!decl || !decl.initializer)
        return null;
    // Must be: await Promise.all([...])
    if (!typescript_1.default.isAwaitExpression(decl.initializer))
        return null;
    const awaitedExpr = decl.initializer.expression;
    if (!typescript_1.default.isCallExpression(awaitedExpr))
        return null;
    // Check callee is Promise.all
    if (!typescript_1.default.isPropertyAccessExpression(awaitedExpr.expression))
        return null;
    const propAccess = awaitedExpr.expression;
    if (!typescript_1.default.isIdentifier(propAccess.expression) || propAccess.expression.text !== 'Promise')
        return null;
    if (propAccess.name.text !== 'all')
        return null;
    // Extract array argument
    const arg = awaitedExpr.arguments[0];
    if (!arg || !typescript_1.default.isArrayLiteralExpression(arg)) {
        context.addError(awaitedExpr, 'Promise.all argument must be an array literal', diagnosticCodes_js_1.ErrorCodes.Cfg.PromiseAllNotArray.code);
        return null;
    }
    const branches = [...arg.elements];
    const resultBindings = [];
    const resultSymbols = [];
    // Extract result variable names
    if (typescript_1.default.isArrayBindingPattern(decl.name)) {
        // const [a, b] = await Promise.all([...])
        for (const element of decl.name.elements) {
            if (typescript_1.default.isBindingElement(element) && typescript_1.default.isIdentifier(element.name)) {
                resultBindings.push(element.name.text);
                resultSymbols.push(context.checker.getSymbolAtLocation(element.name));
            }
            else {
                resultBindings.push('_');
                resultSymbols.push(undefined);
            }
        }
    }
    else if (typescript_1.default.isIdentifier(decl.name)) {
        // const result = await Promise.all([...])
        resultBindings.push(decl.name.text);
        resultSymbols.push(context.checker.getSymbolAtLocation(decl.name));
    }
    return { branches, resultBindings, resultSymbols };
}
/**
 * Returns true if the expression is a call to `Promise.race(...)` or `Promise.any(...)`.
 */
function isPromiseRaceOrAny(expr) {
    if (!typescript_1.default.isCallExpression(expr))
        return false;
    if (!typescript_1.default.isPropertyAccessExpression(expr.expression))
        return false;
    const prop = expr.expression;
    if (!typescript_1.default.isIdentifier(prop.expression) || prop.expression.text !== 'Promise')
        return false;
    return prop.name.text === 'race' || prop.name.text === 'any';
}
/**
 * Extract Promise.all from a standalone await expression (fire-and-forget):
 *   await Promise.all([exprA, exprB])
 * Resolves deferred promise references in branches.
 */
function extractPromiseAllFromAwait(context, awaitExpr, pendingPromises) {
    const callExpr = awaitExpr.expression;
    if (!typescript_1.default.isCallExpression(callExpr))
        return null;
    if (!typescript_1.default.isPropertyAccessExpression(callExpr.expression))
        return null;
    const propAccess = callExpr.expression;
    if (!typescript_1.default.isIdentifier(propAccess.expression) || propAccess.expression.text !== 'Promise')
        return null;
    if (propAccess.name.text !== 'all')
        return null;
    const arg = callExpr.arguments[0];
    if (!arg || !typescript_1.default.isArrayLiteralExpression(arg)) {
        context.addError(callExpr, 'Promise.all argument must be an array literal', diagnosticCodes_js_1.ErrorCodes.Cfg.PromiseAllNotArray.code);
        return null;
    }
    const branches = [...arg.elements].map(branch => {
        if (typescript_1.default.isIdentifier(branch)) {
            const sym = context.checker.getSymbolAtLocation(branch);
            if (sym && pendingPromises.has(sym))
                return pendingPromises.get(sym).callExpr;
        }
        return branch;
    });
    return { branches };
}
/**
 * Pre-scan statements to find the deferred-await pattern:
 *   const foo = svc.call(params)   // non-awaited call
 *   ...
 *   await foo                       // awaited later
 *
 * Only returns candidates that are confirmed to be awaited later
 * in the same statement list (via `await x`, `const y = await x`,
 * or inside `Promise.all([x, ...])`).
 */
function findPendingPromises(context, statements) {
    const candidates = new Map();
    // Pass 1: find non-awaited call declarations
    for (const stmt of statements) {
        if (!typescript_1.default.isVariableStatement(stmt))
            continue;
        const decl = stmt.declarationList.declarations[0];
        if (!decl?.initializer || !typescript_1.default.isIdentifier(decl.name))
            continue;
        if (typescript_1.default.isAwaitExpression(decl.initializer))
            continue;
        if (!typescript_1.default.isCallExpression(decl.initializer))
            continue;
        const sym = context.checker.getSymbolAtLocation(decl.name);
        if (sym) {
            candidates.set(sym, {
                callExpr: decl.initializer,
                varName: decl.name.text,
                varSymbol: sym,
            });
        }
    }
    if (candidates.size === 0)
        return candidates;
    // Pass 2: check which candidates are awaited later
    const awaited = new Set();
    for (const stmt of statements) {
        visitAwaitedIdentifiers(context, stmt, candidates, (sym) => {
            awaited.add(sym);
        });
    }
    // Only keep confirmed
    for (const sym of candidates.keys()) {
        if (!awaited.has(sym))
            candidates.delete(sym);
    }
    return candidates;
}
/**
 * Walk a statement looking for identifiers that are awaited:
 * - `await x` (ExpressionStatement)
 * - `const y = await x` (VariableDeclaration)
 * - `Promise.all([x, ...])` (identifier inside array argument)
 */
function visitAwaitedIdentifiers(context, stmt, candidates, callback) {
    // Pattern: await x (ExpressionStatement)
    if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression)) {
        const inner = stmt.expression.expression;
        if (typescript_1.default.isIdentifier(inner)) {
            const sym = context.checker.getSymbolAtLocation(inner);
            if (sym && candidates.has(sym))
                callback(sym);
        }
        // Also check: await Promise.all([x, ...])
        if (typescript_1.default.isCallExpression(inner)) {
            checkPromiseAllArgs(context, inner, candidates, callback);
        }
    }
    // Pattern: const y = await x (VariableStatement)
    if (typescript_1.default.isVariableStatement(stmt)) {
        const decl = stmt.declarationList.declarations[0];
        if (decl?.initializer && typescript_1.default.isAwaitExpression(decl.initializer)) {
            const inner = decl.initializer.expression;
            if (typescript_1.default.isIdentifier(inner)) {
                const sym = context.checker.getSymbolAtLocation(inner);
                if (sym && candidates.has(sym))
                    callback(sym);
            }
            // Also check: const [a, b] = await Promise.all([x, ...])
            if (typescript_1.default.isCallExpression(inner)) {
                checkPromiseAllArgs(context, inner, candidates, callback);
            }
        }
    }
}
/**
 * If callExpr is Promise.all([...]), check array elements for candidate identifiers.
 */
function checkPromiseAllArgs(context, callExpr, candidates, callback) {
    if (!typescript_1.default.isPropertyAccessExpression(callExpr.expression))
        return;
    const propAccess = callExpr.expression;
    if (!typescript_1.default.isIdentifier(propAccess.expression) || propAccess.expression.text !== 'Promise')
        return;
    if (propAccess.name.text !== 'all')
        return;
    const arg = callExpr.arguments[0];
    if (!arg || !typescript_1.default.isArrayLiteralExpression(arg))
        return;
    for (const el of arg.elements) {
        if (typescript_1.default.isIdentifier(el)) {
            const sym = context.checker.getSymbolAtLocation(el);
            if (sym && candidates.has(sym))
                callback(sym);
        }
    }
}
/**
 * Check if a statement is an await-on-pending-promise pattern.
 * Returns info if it is, undefined otherwise.
 */
function extractDeferredAwait(context, stmt, pendingPromises) {
    // Pattern: await x (fire-and-forget)
    if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isAwaitExpression(stmt.expression)) {
        const inner = stmt.expression.expression;
        if (typescript_1.default.isIdentifier(inner)) {
            const sym = context.checker.getSymbolAtLocation(inner);
            if (sym && pendingPromises.has(sym)) {
                return {
                    callExpr: pendingPromises.get(sym).callExpr,
                    resultName: undefined,
                    resultSymbol: undefined,
                };
            }
        }
    }
    // Pattern: const y = await x (result capture)
    if (typescript_1.default.isVariableStatement(stmt)) {
        const decl = stmt.declarationList.declarations[0];
        if (decl?.initializer && typescript_1.default.isAwaitExpression(decl.initializer) && typescript_1.default.isIdentifier(decl.name)) {
            const inner = decl.initializer.expression;
            if (typescript_1.default.isIdentifier(inner)) {
                const sym = context.checker.getSymbolAtLocation(inner);
                if (sym && pendingPromises.has(sym)) {
                    return {
                        callExpr: pendingPromises.get(sym).callExpr,
                        resultName: decl.name.text,
                        resultSymbol: context.checker.getSymbolAtLocation(decl.name),
                    };
                }
            }
        }
    }
    return undefined;
}
/**
 * Try to batch consecutive deferred-await statements into a ParallelTerminator.
 * Returns the index to advance to (exclusive) if a batch was created, or -1 if not.
 */
function tryBatchDeferredAwaits(state, context, statements, startIndex, accumulated, currentBlockId, loopStack, inlining, pendingPromises) {
    const first = extractDeferredAwait(context, statements[startIndex], pendingPromises);
    if (!first)
        return undefined;
    // Collect consecutive deferred awaits
    const batch = [first];
    let j = startIndex + 1;
    while (j < statements.length) {
        const next = extractDeferredAwait(context, statements[j], pendingPromises);
        if (!next)
            break;
        batch.push(next);
        j++;
    }
    // Single deferred await — don't create Parallel; handled by state builder fallback
    if (batch.length < 2)
        return undefined;
    const exitBlockId = state.newBlockId('parallel_exit');
    // Classify branches (handles both service calls and substep calls)
    const classifiedBranches = classifyParallelBranches(state, context, batch.map(b => b.callExpr), loopStack, inlining);
    const resultBindings = batch.map(b => b.resultName ?? '_');
    const resultSymbols = batch.map(b => b.resultSymbol);
    state.addBlock(currentBlockId, accumulated, {
        kind: 'parallel',
        branches: classifiedBranches,
        resultBindings,
        resultSymbols,
        exitBlock: exitBlockId,
    });
    return { exitBlockId, advanceTo: j };
}
//# sourceMappingURL=cfgBuilder.js.map