import { CancellationError } from './CancellationError.js';
/**
 * Runtime execution context for generated workflows
 *
 * Manages variable storage, execution tracking, and pull execution (lazy evaluation).
 * This class is used internally by generated workflow code.
 *
 * Key Features:
 * - Variable storage with execution-scoped addressing
 * - Pull execution support (lazy evaluation)
 * - Debug event emission
 * - Async and sync execution modes
 *
 * @example
 * ```typescript
 * const ctx = new GeneratedExecutionContext(true, debugger);
 * const execIndex = ctx.addExecution('node1');
 * ctx.setVariable({ nodeName: 'node1', portName: 'result', executionIndex: execIndex }, 42);
 * const value = await ctx.getVariable({ nodeName: 'node1', portName: 'result', executionIndex: execIndex });
 * ```
 */
export class GeneratedExecutionContext {
    variables = new Map();
    executions = new Map();
    executionCounter = 0;
    isAsync;
    flowWeaverDebugger;
    pullExecutors = new Map();
    nodeExecutionIndices = new Map();
    abortSignal;
    /**
     * Create a new execution context
     * @param isAsync - Whether the workflow runs in async mode (default: true)
     * @param flowWeaverDebugger - Optional debugger for emitting execution events
     * @param abortSignal - Optional AbortSignal for cancellation support
     */
    constructor(isAsync = true, flowWeaverDebugger, abortSignal) {
        this.isAsync = isAsync;
        this.flowWeaverDebugger = flowWeaverDebugger;
        this.abortSignal = abortSignal;
    }
    registerPullExecutor(id, executor) {
        this.pullExecutors.set(id, executor);
    }
    addExecution(id, parentIndex, scopeName) {
        const index = this.executionCounter++;
        this.executions.set(this.getExecutionKey(id, index), {
            id,
            index,
            parentIndex,
            scopeName,
        });
        this.nodeExecutionIndices.set(id, index);
        return index;
    }
    setVariable(address, value) {
        const key = this.getVariableKey(address);
        this.variables.set(key, value);
        if (this.flowWeaverDebugger) {
            const actualValue = typeof value === 'function' ? value() : value;
            this.sendVariableSetEvent({
                identifier: {
                    nodeTypeName: address.nodeTypeName || 'unknown',
                    id: address.id,
                    portName: address.portName,
                    executionIndex: address.executionIndex,
                    key: 'default',
                    ...(address.scope !== undefined && { scope: address.scope }),
                    ...(address.side !== undefined && { side: address.side }),
                },
                value: actualValue,
            });
        }
        return this.isAsync ? Promise.resolve() : undefined;
    }
    getVariable(address) {
        const executor = this.pullExecutors.get(address.id);
        if (executor) {
            if (!this.hasVariable(address)) {
                const result = executor();
                // Handle async executor (returns Promise)
                if (result instanceof Promise) {
                    return result.then(() => {
                        const trackedIndex = this.nodeExecutionIndices.get(address.id);
                        const finalAddress = trackedIndex !== undefined ? { ...address, executionIndex: trackedIndex } : address;
                        return this.retrieveVariable(finalAddress);
                    });
                }
                // Handle sync executor (returns void)
                const trackedIndex = this.nodeExecutionIndices.get(address.id);
                const finalAddress = trackedIndex !== undefined ? { ...address, executionIndex: trackedIndex } : address;
                return this.retrieveVariable(finalAddress);
            }
        }
        return this.retrieveVariable(address);
    }
    retrieveVariable(address) {
        const key = this.getVariableKey(address);
        if (!this.variables.has(key)) {
            throw new Error(`Variable not found: ${address.id}.${address.portName}[${address.executionIndex}]`);
        }
        const value = this.variables.get(key);
        if (typeof value === 'function') {
            const result = value();
            if (result instanceof Promise) {
                return result;
            }
            return this.isAsync ? Promise.resolve(result) : result;
        }
        return this.isAsync ? Promise.resolve(value) : value;
    }
    hasVariable(address) {
        const key = this.getVariableKey(address);
        return this.variables.has(key);
    }
    getExecution(id, index) {
        return this.executions.get(this.getExecutionKey(id, index));
    }
    /**
     * Create an isolated execution scope for container nodes
     *
     * Scopes provide isolated variable storage for nodes like ForEach loops.
     * Child nodes execute within the scope, then variables are merged back.
     *
     * @param _parentNodeName - ID of the container node creating the scope
     * @param _parentIndex - Execution index of the container
     * @param _scopeName - Name of the scope (e.g., 'iteration')
     * @param cleanScope - If true, create fresh scope without parent variables (per-port scopes). If false, inherit parent variables (node-level scopes).
     * @returns New ExecutionContext for the scoped execution
     *
     * @example
     * ```typescript
     * // Per-port scope (clean=true): isolated variables
     * const scopedCtx = ctx.createScope('forEach1', 0, 'iteration', true);
     *
     * // Node-level scope (clean=false): inherited variables
     * const scopedCtx = ctx.createScope('container1', 0, 'block', false);
     * ```
     */
    createScope(_parentNodeName, _parentIndex, _scopeName, cleanScope = false) {
        const scopedContext = new GeneratedExecutionContext(this.isAsync, undefined, this.abortSignal);
        if (cleanScope) {
            // Fresh scope - don't copy parent variables (per-port scopes)
            scopedContext.executionCounter = this.executionCounter;
        }
        else {
            // Inherited scope - copy parent variables (node-level scopes)
            scopedContext.variables = new Map(this.variables);
            scopedContext.executions = new Map(this.executions);
            scopedContext.executionCounter = this.executionCounter;
        }
        return scopedContext;
    }
    /**
     * Merge a scoped execution context back into the parent context
     *
     * Copies all variables and execution info from the scoped context to this context.
     * Updates the execution counter to maintain unique execution indices.
     *
     * @param scopedContext - The scoped context to merge
     */
    mergeScope(scopedContext) {
        scopedContext.executions.forEach((info, key) => {
            this.executions.set(key, info);
        });
        scopedContext.variables.forEach((value, key) => {
            this.variables.set(key, value);
        });
        this.executionCounter = Math.max(this.executionCounter, scopedContext.executionCounter);
    }
    getVariableKey(address) {
        return `${address.id}:${address.portName}:${address.executionIndex}`;
    }
    getExecutionKey(id, index) {
        return `${id}:${index}`;
    }
    getExecutionCount() {
        return this.executionCounter;
    }
    reset() {
        this.variables.clear();
        this.executions.clear();
        this.executionCounter = 0;
    }
    /**
     * Check if the workflow has been aborted
     */
    isAborted() {
        return this.abortSignal?.aborted ?? false;
    }
    /**
     * Throw CancellationError if the workflow has been aborted
     * @param nodeId - Optional node ID to include in the error
     */
    checkAborted(nodeId) {
        if (this.abortSignal?.aborted) {
            throw new CancellationError(`Workflow execution cancelled${nodeId ? ` at ${nodeId}` : ''}`, this.executionCounter, nodeId);
        }
    }
    sendStatusChangedEvent(args) {
        if (this.flowWeaverDebugger) {
            this.flowWeaverDebugger.sendEvent({
                type: 'STATUS_CHANGED',
                ...args,
                innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,
            });
        }
    }
    sendVariableSetEvent(args) {
        if (this.flowWeaverDebugger) {
            this.flowWeaverDebugger.sendEvent({
                type: 'VARIABLE_SET',
                ...args,
                innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,
            });
        }
    }
    sendLogErrorEvent(args) {
        if (this.flowWeaverDebugger) {
            this.flowWeaverDebugger.sendEvent({
                type: 'LOG_ERROR',
                ...args,
                innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,
            });
        }
    }
    sendWorkflowCompletedEvent(args) {
        if (this.flowWeaverDebugger) {
            this.flowWeaverDebugger.sendEvent({
                type: 'WORKFLOW_COMPLETED',
                ...args,
                innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,
            });
        }
    }
}
//# sourceMappingURL=ExecutionContext.js.map