"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvalExecutionService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const node_crypto_1 = require("node:crypto");
const node_types_1 = require("../../../node-types");
const workflow_execute_additional_data_1 = require("../../../workflow-execute-additional-data");
const workflow_finder_service_1 = require("../../../workflows/workflow-finder.service");
const workflow_sdk_1 = require("@n8n/workflow-sdk");
const pin_data_generator_1 = require("./pin-data-generator");
const workflow_analysis_1 = require("./workflow-analysis");
const mock_handler_1 = require("./mock-handler");
const MAX_OUTPUT_ITEMS_PER_NODE = 5;
let EvalExecutionService = class EvalExecutionService {
    constructor(workflowFinderService, nodeTypes, logger) {
        this.workflowFinderService = workflowFinderService;
        this.nodeTypes = nodeTypes;
        this.logger = logger;
    }
    async executeWithLlmMock(workflowId, user, options = {}) {
        const executionId = (0, node_crypto_1.randomUUID)();
        const workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
            'workflow:execute',
        ]);
        if (!workflowEntity) {
            return this.errorResult(executionId, `Workflow ${workflowId} not found or not accessible`);
        }
        const hints = await this.analyzeWorkflow(workflowEntity, options.scenarioHints);
        return await this.execute(workflowEntity, user, executionId, hints, options.scenarioHints);
    }
    async analyzeWorkflow(workflowEntity, scenarioHints) {
        const hintNodes = (0, workflow_analysis_1.identifyNodesForHints)(workflowEntity);
        const nodeNames = hintNodes.map((n) => n.name);
        this.logger.debug(`[EvalMock] Generating hints for ${nodeNames.length} nodes: ${nodeNames.join(', ')}`);
        const hints = await (0, workflow_analysis_1.generateMockHints)({
            workflow: workflowEntity,
            nodeNames,
            scenarioHints,
        });
        if (!hints.globalContext && nodeNames.length > 0) {
            this.logger.warn('[EvalMock] Phase 1 hint generation returned empty — mock responses will lack cross-node consistency');
        }
        this.logger.debug(`[EvalMock] Phase 1 result — globalContext: ${hints.globalContext ? 'present' : 'EMPTY'}, triggerContent keys: ${JSON.stringify(Object.keys(hints.triggerContent))}, nodeHints: ${Object.keys(hints.nodeHints).join(', ')}`);
        const bypassNodes = (0, workflow_analysis_1.identifyNodesForPinData)(workflowEntity);
        const bypassNodeNames = bypassNodes.map((n) => n.name);
        if (bypassNodeNames.length > 0) {
            this.logger.debug(`[EvalMock] Generating pin data for ${bypassNodeNames.length} bypass nodes: ${bypassNodeNames.join(', ')}`);
            hints.bypassPinData = await this.generateBypassPinData(workflowEntity, bypassNodeNames, hints.globalContext, scenarioHints);
            this.logger.debug(`[EvalMock] Phase 1.5 result — pinned nodes: ${Object.keys(hints.bypassPinData).join(', ') || 'none'}`);
        }
        return hints;
    }
    async generateBypassPinData(workflowEntity, bypassNodeNames, globalContext, scenarioHints) {
        if (bypassNodeNames.length === 0)
            return {};
        try {
            const dataDescription = [globalContext, scenarioHints].filter(Boolean).join('\n\n');
            const result = await (0, pin_data_generator_1.generatePinData)({
                workflow: workflowEntity,
                nodeNames: bypassNodeNames,
                instructions: dataDescription ? { dataDescription } : undefined,
            });
            return (0, workflow_sdk_1.normalizePinData)(result);
        }
        catch (error) {
            const errorMsg = error instanceof Error ? error.message : String(error);
            this.logger.error(`[EvalMock] Phase 1.5 pin data generation failed: ${errorMsg}`);
            return (0, workflow_sdk_1.normalizePinData)(Object.fromEntries(bypassNodeNames.map((nodeName) => [nodeName, [{ json: {} }]])));
        }
    }
    async execute(workflowEntity, user, executionId, hints, scenarioHints) {
        const nodeResults = {};
        const workflow = this.buildWorkflow(workflowEntity);
        const startNode = this.findStartNode(workflow);
        if (!startNode) {
            return this.errorResult(executionId, 'No trigger or start node found in the workflow');
        }
        const mockHandler = (0, mock_handler_1.createLlmMockHandler)({
            scenarioHints,
            globalContext: hints.globalContext,
            nodeHints: hints.nodeHints,
        });
        const additionalData = await (0, workflow_execute_additional_data_1.getBase)({
            userId: user.id,
            workflowId: workflowEntity.id,
            workflowSettings: workflowEntity.settings ?? {},
        });
        additionalData.evalLlmMockHandler = this.createInterceptingHandler(mockHandler, nodeResults);
        additionalData.hooks = new n8n_core_1.ExecutionLifecycleHooks('evaluation', executionId, workflowEntity);
        const triggerPinData = this.buildTriggerPinData(startNode, hints.triggerContent);
        const pinData = { ...triggerPinData, ...hints.bypassPinData };
        const pinDataNodeNames = Object.keys(pinData);
        this.checkNodeConfig(workflow, nodeResults, pinDataNodeNames);
        const executionData = this.buildExecutionData(startNode, pinData);
        if (Object.keys(triggerPinData).length > 0) {
            const existing = nodeResults[startNode.name];
            nodeResults[startNode.name] = {
                output: null,
                interceptedRequests: [],
                executionMode: 'pinned',
                ...(existing?.configIssues ? { configIssues: existing.configIssues } : {}),
            };
        }
        for (const nodeName of Object.keys(hints.bypassPinData)) {
            const existing = nodeResults[nodeName];
            nodeResults[nodeName] = {
                output: null,
                interceptedRequests: [],
                executionMode: 'pinned',
                ...(existing?.configIssues ? { configIssues: existing.configIssues } : {}),
            };
        }
        try {
            const result = await this.runWorkflow(workflow, additionalData, executionData);
            return this.buildResult(executionId, result, nodeResults, hints);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            this.logger.error(`[EvalMock] Workflow execution failed: ${message}`);
            return {
                executionId,
                success: false,
                nodeResults,
                errors: [`Execution failed: ${message}`],
                hints,
            };
        }
    }
    buildWorkflow(workflowEntity) {
        return new n8n_workflow_1.Workflow({
            id: workflowEntity.id,
            name: workflowEntity.name,
            nodes: workflowEntity.nodes,
            connections: workflowEntity.connections,
            active: false,
            nodeTypes: this.nodeTypes,
            staticData: workflowEntity.staticData,
            settings: workflowEntity.settings ?? {},
        });
    }
    findStartNode(workflow) {
        return workflow.getStartNode() ?? this.findWebhookNode(workflow);
    }
    findWebhookNode(workflow) {
        return Object.values(workflow.nodes).find((node) => {
            if (node.disabled)
                return false;
            const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
            return nodeType !== undefined && 'webhook' in nodeType;
        });
    }
    checkNodeConfig(workflow, nodeResults, pinDataNodeNames) {
        for (const node of Object.values(workflow.nodes)) {
            if (node.disabled)
                continue;
            const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
            if (!nodeType)
                continue;
            const issues = n8n_workflow_1.NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node, nodeType.description, pinDataNodeNames);
            if (issues?.parameters && Object.keys(issues.parameters).length > 0) {
                const entry = (nodeResults[node.name] ??= {
                    output: null,
                    interceptedRequests: [],
                    executionMode: 'real',
                });
                entry.configIssues = issues.parameters;
            }
        }
    }
    buildTriggerPinData(startNode, triggerContent) {
        if (Object.keys(triggerContent).length === 0)
            return {};
        return { [startNode.name]: [{ json: triggerContent }] };
    }
    buildExecutionData(startNode, pinData) {
        return (0, n8n_workflow_1.createRunExecutionData)({
            startData: {},
            resultData: { pinData, runData: {} },
            executionData: {
                contextData: {},
                metadata: {},
                nodeExecutionStack: [
                    {
                        node: startNode,
                        data: { main: [[{ json: {} }]] },
                        source: null,
                    },
                ],
                waitingExecution: {},
                waitingExecutionSource: {},
            },
        });
    }
    async runWorkflow(workflow, additionalData, executionData) {
        const workflowExecute = new n8n_core_1.WorkflowExecute(additionalData, 'evaluation', executionData);
        return await workflowExecute.processRunExecutionData(workflow);
    }
    createInterceptingHandler(mockHandler, nodeResults) {
        return async (requestOptions, node) => {
            const entry = (nodeResults[node.name] ??= {
                output: null,
                interceptedRequests: [],
                executionMode: 'mocked',
            });
            entry.executionMode = 'mocked';
            const response = await mockHandler(requestOptions, node);
            entry.interceptedRequests.push({
                url: requestOptions.url,
                method: requestOptions.method ?? 'GET',
                nodeType: node.type,
                requestBody: requestOptions.body,
                mockResponse: response?.body,
            });
            this.logger.debug(`[EvalMock] Intercepted ${requestOptions.method ?? 'GET'} ${requestOptions.url} from "${node.name}" (${node.type})`);
            return response;
        };
    }
    buildResult(executionId, result, nodeResults, hints) {
        const errors = [];
        const runData = result.data?.resultData?.runData ?? {};
        for (const [nodeName, nodeRuns] of Object.entries(runData)) {
            const entry = (nodeResults[nodeName] ??= {
                output: null,
                interceptedRequests: [],
                executionMode: 'real',
            });
            const lastRun = nodeRuns[nodeRuns.length - 1];
            if (lastRun?.startTime) {
                entry.startTime = lastRun.startTime;
            }
            if (lastRun?.data?.main) {
                const allOutputs = lastRun.data.main
                    .flat()
                    .filter(Boolean)
                    .slice(0, MAX_OUTPUT_ITEMS_PER_NODE);
                if (allOutputs.length > 0) {
                    entry.output = allOutputs;
                }
            }
            if (lastRun?.error) {
                errors.push(`Node "${nodeName}": ${lastRun.error.message}`);
            }
        }
        const executionError = result.data?.resultData?.error;
        if (executionError) {
            errors.push(`Workflow error: ${executionError.message}`);
        }
        return {
            executionId,
            success: executionError === undefined && errors.length === 0,
            nodeResults,
            errors,
            hints,
        };
    }
    errorResult(executionId, message) {
        return {
            executionId,
            success: false,
            nodeResults: {},
            errors: [message],
            hints: {
                globalContext: '',
                triggerContent: {},
                nodeHints: {},
                warnings: [],
                bypassPinData: {},
            },
        };
    }
};
exports.EvalExecutionService = EvalExecutionService;
exports.EvalExecutionService = EvalExecutionService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [workflow_finder_service_1.WorkflowFinderService,
        node_types_1.NodeTypes,
        backend_common_1.Logger])
], EvalExecutionService);
//# sourceMappingURL=execution.service.js.map