/**
 * Workflow executor for MCP-side compilation and execution.
 * Copies source to a temp file, compiles all workflows in-place, then dynamically imports and executes.
 */
import * as path from 'path';
import * as fs from 'fs';
import { pathToFileURL } from 'url';
import ts from 'typescript';
import { compileWorkflow } from '../api/index.js';
import { getAvailableWorkflows } from '../api/workflow-file-operations.js';
/**
 * Compiles and executes a workflow from a TypeScript source file.
 * Copies the source to a temp file, compiles all workflows in-place (preserving sibling
 * functions for workflow composition), injects a trace-capturing debugger, and dynamically
 * imports and runs the target workflow function.
 * @param filePath - Path to the workflow `.ts` source file.
 * @param params - Parameters to pass to the workflow function.
 * @param options - Execution options.
 * @param options.workflowName - Name of a specific exported workflow function to execute.
 *   If omitted, the first exported function is used.
 * @param options.production - Enable production mode (no debug events). Defaults to `!includeTrace`.
 * @param options.includeTrace - Whether to capture and return execution trace events. Defaults to `true`.
 * @returns The workflow result, function name, execution time, and optional trace.
 * @throws If no exported workflow function is found in the compiled module.
 */
export async function executeWorkflowFromFile(filePath, params, options) {
    const resolvedPath = path.resolve(filePath);
    const includeTrace = options?.includeTrace !== false;
    // Copy source to temp file and compile ALL workflows in-place there.
    // In-place compilation preserves all functions in the module (node types,
    // sibling workflows), which is required for workflow composition where one
    // workflow calls another as a node type.
    //
    // Temp files are written in the source file's directory (not os.tmpdir())
    // so that ESM module resolution can walk up to the project's node_modules.
    // On Windows, os.tmpdir() is disconnected from the project tree, causing
    // bare import specifiers (e.g. 'zod', 'openai') to fail with MODULE_NOT_FOUND.
    const tmpId = `fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
    const tmpBase = path.join(path.dirname(resolvedPath), tmpId);
    const tmpTsFile = `${tmpBase}.ts`;
    const tmpFile = `${tmpBase}.mjs`;
    try {
        fs.copyFileSync(resolvedPath, tmpTsFile);
        // Discover all workflows in the file
        const source = fs.readFileSync(resolvedPath, 'utf8');
        const allWorkflows = getAvailableWorkflows(source);
        // Compile each workflow in-place so all function bodies are generated.
        // Debug controller requires dev mode (production: false) so that
        // __ctrl__.beforeNode/afterNode hooks are emitted in generated code.
        const production = options?.debugController
            ? false
            : (options?.production ?? !includeTrace);
        for (const wf of allWorkflows) {
            await compileWorkflow(tmpTsFile, {
                write: true,
                inPlace: true,
                parse: { workflowName: wf.functionName },
                generate: { production },
            });
        }
        // Inject debugger binding: replace the TypeScript-only `declare const`
        // with an actual assignment from globalThis so the executor can pass
        // a trace-capturing debugger at runtime.
        let compiledCode = fs.readFileSync(tmpTsFile, 'utf8');
        compiledCode = compiledCode.replace('declare const __flowWeaverDebugger__: TDebugger | undefined;', 'const __flowWeaverDebugger__ = (globalThis as any).__fw_debugger__;');
        // Transpile TypeScript to JavaScript so Node.js can import it directly
        const jsOutput = ts.transpileModule(compiledCode, {
            compilerOptions: {
                module: ts.ModuleKind.ESNext,
                target: ts.ScriptTarget.ESNext,
                esModuleInterop: true,
            },
        });
        // When source lives under src/, rewrite relative imports to point to
        // dist/ equivalents so Node.js ESM resolver finds the compiled JS files.
        // This happens with marketplace packs that ship TS source for parsing
        // but only have compiled JS in dist/.
        let transpiledOutput = jsOutput.outputText;
        const srcDir = path.dirname(tmpTsFile);
        if (srcDir.includes(`${path.sep}src${path.sep}`)) {
            const distDir = srcDir.replace(`${path.sep}src${path.sep}`, `${path.sep}dist${path.sep}`);
            transpiledOutput = transpiledOutput.replace(/from\s+['"](\.[^'"]+)['"]/g, (_match, specifier) => {
                const resolvedSrc = path.resolve(srcDir, specifier);
                // Only rewrite if the source .js file doesn't exist but the dist equivalent does
                if (!fs.existsSync(resolvedSrc)) {
                    const distEquivalent = resolvedSrc.replace(`${path.sep}src${path.sep}`, `${path.sep}dist${path.sep}`);
                    if (fs.existsSync(distEquivalent)) {
                        const relative = path.relative(srcDir, distEquivalent);
                        const posixRelative = relative.replace(/\\/g, '/');
                        const normalized = posixRelative.startsWith('.') ? posixRelative : `./${posixRelative}`;
                        return `from '${normalized}'`;
                    }
                }
                return _match;
            });
        }
        fs.writeFileSync(tmpFile, transpiledOutput, 'utf8');
        // Create debugger to capture trace events
        const trace = [];
        const debugger_ = includeTrace
            ? {
                sendEvent: (event) => {
                    const traceEvent = {
                        type: event.type || 'UNKNOWN',
                        timestamp: Date.now(),
                        data: event,
                    };
                    trace.push(traceEvent);
                    options?.onEvent?.(traceEvent);
                },
                innerFlowInvocation: false,
            }
            : undefined;
        // Set global debugger before import so compiled code picks it up
        globalThis.__fw_debugger__ = debugger_;
        // Set mock config for built-in nodes (delay, waitForEvent, invokeWorkflow)
        if (options?.mocks) {
            globalThis.__fw_mocks__ = options.mocks;
        }
        // Set agent channel for waitForAgent pause/resume
        if (options?.agentChannel) {
            globalThis.__fw_agent_channel__ = options.agentChannel;
        }
        // Set debug controller for step-through debugging and checkpoint/resume
        if (options?.debugController) {
            globalThis.__fw_debug_controller__ = options.debugController;
        }
        // Dynamic import using file:// URL for cross-platform compatibility
        // (Windows paths like C:\... break with bare import() — "Received protocol 'c:'")
        const mod = await import(pathToFileURL(tmpFile).href);
        // Register exported functions for local invokeWorkflow resolution
        const workflowRegistry = {};
        for (const [key, value] of Object.entries(mod)) {
            if (typeof value === 'function' && key !== '__esModule') {
                workflowRegistry[key] = value;
            }
        }
        globalThis.__fw_workflow_registry__ = workflowRegistry;
        // Find the target exported function
        const exportedFn = findExportedFunction(mod, options?.workflowName);
        if (!exportedFn) {
            const available = Object.entries(mod)
                .filter(([k, v]) => k !== '__esModule' && typeof v === 'function')
                .map(([k]) => k);
            const availStr = available.length > 0 ? `. Available: ${available.join(', ')}` : '';
            throw new Error(options?.workflowName
                ? `Workflow "${options.workflowName}" not found in file${availStr}`
                : `No exported workflow function found in file${availStr}`);
        }
        const startTime = Date.now();
        // Execute the workflow function: (execute, params, abortSignal?)
        // In-place compiled functions use the module-level debugger, not a parameter.
        const result = await exportedFn.fn(true, params ?? {});
        const executionTime = Date.now() - startTime;
        return {
            result,
            functionName: exportedFn.name,
            executionTime,
            ...(includeTrace && { trace, summary: computeTraceSummary(trace) }),
        };
    }
    finally {
        // Clean up globals
        delete globalThis.__fw_debugger__;
        delete globalThis.__fw_mocks__;
        delete globalThis.__fw_workflow_registry__;
        delete globalThis.__fw_agent_channel__;
        delete globalThis.__fw_debug_controller__;
        // Clean up temp files
        try {
            fs.unlinkSync(tmpFile);
        }
        catch { /* ignore */ }
        try {
            fs.unlinkSync(tmpTsFile);
        }
        catch { /* ignore */ }
    }
}
/** Compute a concise summary from raw trace events. */
export function computeTraceSummary(trace) {
    if (trace.length === 0) {
        return { totalNodes: 0, succeeded: 0, failed: 0, cancelled: 0, nodeTimings: [], totalDurationMs: 0 };
    }
    const nodeStartTimes = new Map();
    const nodeFinalStatus = new Map();
    const nodeTimings = [];
    for (const event of trace) {
        if (event.type !== 'STATUS_CHANGED' || !event.data)
            continue;
        const id = event.data.id;
        const status = event.data.status;
        if (!id || !status)
            continue;
        if (status === 'RUNNING') {
            nodeStartTimes.set(id, event.timestamp);
        }
        if (status === 'SUCCEEDED' || status === 'FAILED' || status === 'CANCELLED') {
            nodeFinalStatus.set(id, status);
            const startTime = nodeStartTimes.get(id);
            if (startTime !== undefined) {
                nodeTimings.push({ nodeId: id, durationMs: event.timestamp - startTime });
            }
        }
    }
    let succeeded = 0;
    let failed = 0;
    let cancelled = 0;
    for (const status of nodeFinalStatus.values()) {
        if (status === 'SUCCEEDED')
            succeeded++;
        else if (status === 'FAILED')
            failed++;
        else if (status === 'CANCELLED')
            cancelled++;
    }
    const totalDurationMs = trace[trace.length - 1].timestamp - trace[0].timestamp;
    return {
        totalNodes: nodeFinalStatus.size,
        succeeded,
        failed,
        cancelled,
        nodeTimings,
        totalDurationMs,
    };
}
function findExportedFunction(mod, preferredName) {
    // If a preferred name is specified, try it first
    if (preferredName && typeof mod[preferredName] === 'function') {
        return { name: preferredName, fn: mod[preferredName] };
    }
    // Find first exported function (skip default if it's not a function)
    for (const [key, value] of Object.entries(mod)) {
        if (key === '__esModule')
            continue;
        if (typeof value === 'function') {
            return { name: key, fn: value };
        }
    }
    return null;
}
//# sourceMappingURL=workflow-executor.js.map