import { logger, Service } from '@elizaos/core';
import { N8nApiClient } from '../utils/api';
import { searchNodes, filterNodesByIntegrationSupport } from '../utils/catalog';
import { getUserTagName } from '../utils/context';
import { extractKeywords, generateWorkflow, modifyWorkflow, collectExistingNodeDefinitions, assessFeasibility, correctFieldReferences, correctParameterNames, } from '../utils/generation';
import { positionNodes, validateWorkflow, validateNodeParameters, validateNodeInputs, validateOutputReferences, normalizeTriggerSimpleParam, correctOptionParameters, detectUnknownParameters, ensureExpressionPrefix, } from '../utils/workflow';
import { resolveCredentials } from '../utils/credentialResolver';
import { N8N_CREDENTIAL_STORE_TYPE, N8N_CREDENTIAL_PROVIDER_TYPE, isCredentialProvider, UnsupportedIntegrationError, } from '../types/index';
export const N8N_WORKFLOW_SERVICE_TYPE = 'n8n_workflow';
/**
 * N8n Workflow Service - Orchestrates the RAG pipeline for workflow generation.
 *
 * generateWorkflowDraft(): keywords → node search → LLM generation → validation → positioning
 * deployWorkflow(): credential resolution → n8n Cloud API → tagging
 */
export class N8nWorkflowService extends Service {
    static serviceType = N8N_WORKFLOW_SERVICE_TYPE;
    capabilityDescription = 'Generate and deploy n8n workflows from natural language using RAG pipeline. ' +
        'Supports workflow CRUD, execution management, and credential resolution.';
    apiClient = null;
    serviceConfig = null;
    static async start(runtime) {
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, 'Starting N8n Workflow Service...');
        // Validate configuration
        const apiKey = runtime.getSetting('N8N_API_KEY');
        const host = runtime.getSetting('N8N_HOST');
        if (!apiKey || typeof apiKey !== 'string') {
            throw new Error('N8N_API_KEY is required in settings');
        }
        if (!host || typeof host !== 'string') {
            throw new Error('N8N_HOST is required in settings (e.g., https://your.n8n.cloud)');
        }
        // Get optional pre-configured credentials from character.settings.workflows
        // Note: runtime.getSetting() only returns primitives — nested objects must be read directly
        const workflowSettings = runtime.character?.settings?.workflows;
        const credentials = workflowSettings?.credentials;
        const service = new N8nWorkflowService(runtime);
        service.serviceConfig = {
            apiKey,
            host,
            credentials,
        };
        // Initialize API client
        service.apiClient = new N8nApiClient(host, apiKey);
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `N8n Workflow Service started - connected to ${host}`);
        if (credentials) {
            const configured = Object.entries(credentials)
                .filter(([, v]) => v)
                .map(([k]) => k);
            if (configured.length > 0) {
                logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Pre-configured credentials: ${configured.join(', ')}`);
            }
        }
        return service;
    }
    async stop() {
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, 'Stopping N8n Workflow Service...');
        this.apiClient = null;
        this.serviceConfig = null;
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, 'N8n Workflow Service stopped');
    }
    injectCatalogClarifications(workflow) {
        const paramWarnings = validateNodeParameters(workflow);
        const inputWarnings = validateNodeInputs(workflow);
        const catalogWarnings = [...paramWarnings, ...inputWarnings];
        if (!workflow._meta) {
            workflow._meta = {};
        }
        // Strip previous catalog-derived clarifications to avoid stale duplicates
        // across regeneration cycles (generate → modify → modify).
        const CATALOG_SUFFIX = '— please provide this value or clarify your requirements';
        const nonCatalog = (workflow._meta.requiresClarification || []).filter((c) => !c.endsWith(CATALOG_SUFFIX));
        if (catalogWarnings.length > 0) {
            logger.warn({ src: 'plugin:n8n-workflow:service:main' }, `Catalog validation: ${catalogWarnings.join(', ')}`);
            const clarifications = catalogWarnings.map((w) => `${w} ${CATALOG_SUFFIX}`);
            workflow._meta.requiresClarification = [...nonCatalog, ...clarifications];
        }
        else {
            workflow._meta.requiresClarification = nonCatalog.length > 0 ? nonCatalog : undefined;
        }
    }
    getClient() {
        if (!this.apiClient) {
            throw new Error('N8n Workflow Service not initialized');
        }
        return this.apiClient;
    }
    getConfig() {
        if (!this.serviceConfig) {
            throw new Error('N8n Workflow Service not initialized');
        }
        return this.serviceConfig;
    }
    async generateWorkflowDraft(prompt) {
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, 'Generating workflow draft from prompt');
        const keywords = await extractKeywords(this.runtime, prompt);
        logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Extracted keywords: ${keywords.join(', ')}`);
        let relevantNodes = searchNodes(keywords, 15);
        logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Found ${relevantNodes.length} relevant nodes`);
        if (relevantNodes.length === 0) {
            throw new Error('No relevant n8n nodes found for the given prompt. Please be more specific about the integrations you want to use (e.g., Gmail, Slack, Stripe).');
        }
        // ── Integration availability check ──
        const rawProvider = this.runtime.getService(N8N_CREDENTIAL_PROVIDER_TYPE);
        const credProvider = isCredentialProvider(rawProvider) ? rawProvider : null;
        if (credProvider?.checkCredentialTypes) {
            const credTypes = new Set();
            for (const { node } of relevantNodes) {
                for (const cred of node.credentials ?? []) {
                    credTypes.add(cred.name);
                }
            }
            if (credTypes.size > 0) {
                const checkResult = credProvider.checkCredentialTypes([...credTypes]);
                if (checkResult.unsupported.length > 0) {
                    const supportedSet = new Set(checkResult.supported);
                    const { remaining, removed } = filterNodesByIntegrationSupport(relevantNodes, supportedSet);
                    const remainingServiceNodes = remaining.filter((r) => r.node.credentials?.length);
                    if (remainingServiceNodes.length === 0) {
                        throw new UnsupportedIntegrationError([...new Set(removed.map((r) => r.node.displayName))], []);
                    }
                    const feasibility = await assessFeasibility(this.runtime, prompt, removed, remaining);
                    if (!feasibility.feasible) {
                        throw new UnsupportedIntegrationError([...new Set(removed.map((r) => r.node.displayName))], [...new Set(remainingServiceNodes.map((r) => r.node.displayName))]);
                    }
                    logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Feasibility OK: ${feasibility.reason}. Proceeding with ${remaining.length} nodes.`);
                    relevantNodes = remaining;
                }
            }
        }
        // ── End integration check ──
        let workflow = await generateWorkflow(this.runtime, prompt, relevantNodes.map((r) => r.node));
        logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Generated workflow with ${workflow.nodes?.length || 0} nodes`);
        normalizeTriggerSimpleParam(workflow);
        const optionFixes = correctOptionParameters(workflow);
        if (optionFixes > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Corrected ${optionFixes} invalid option parameter(s)`);
        }
        const unknownParams = detectUnknownParameters(workflow);
        if (unknownParams.length > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Found ${unknownParams.length} node(s) with unknown parameters, auto-correcting...`);
            workflow = await correctParameterNames(this.runtime, workflow, unknownParams);
        }
        const invalidRefs = validateOutputReferences(workflow);
        if (invalidRefs.length > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Found ${invalidRefs.length} invalid field reference(s), auto-correcting...`);
            workflow = await correctFieldReferences(this.runtime, workflow, invalidRefs);
        }
        const exprPrefixed = ensureExpressionPrefix(workflow);
        if (exprPrefixed > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Prefixed ${exprPrefixed} expression value(s) with "="`);
        }
        const validationResult = validateWorkflow(workflow);
        if (!validationResult.valid) {
            logger.error({ src: 'plugin:n8n-workflow:service:main' }, `Validation errors: ${validationResult.errors.join(', ')}`);
            throw new Error(`Generated workflow is invalid: ${validationResult.errors[0]}`);
        }
        if (validationResult.warnings.length > 0) {
            logger.warn({ src: 'plugin:n8n-workflow:service:main' }, `Validation warnings: ${validationResult.warnings.join(', ')}`);
        }
        this.injectCatalogClarifications(workflow);
        return positionNodes(workflow);
    }
    async modifyWorkflowDraft(existingWorkflow, modificationRequest) {
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Modifying workflow draft: ${modificationRequest.slice(0, 100)}`);
        // Get definitions for nodes already in the workflow
        const existingDefs = collectExistingNodeDefinitions(existingWorkflow);
        // Search for new nodes the modification might need
        const keywords = await extractKeywords(this.runtime, modificationRequest);
        const searchResults = searchNodes(keywords, 10);
        const newDefs = searchResults.map((r) => r.node);
        // Deduplicate: merge existing + new, preferring existing (already in workflow)
        const seenNames = new Set(existingDefs.map((d) => d.name));
        const combinedDefs = [...existingDefs];
        for (const def of newDefs) {
            if (!seenNames.has(def.name)) {
                seenNames.add(def.name);
                combinedDefs.push(def);
            }
        }
        logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Modify context: ${existingDefs.length} existing + ${newDefs.length} searched → ${combinedDefs.length} unique node defs`);
        let workflow = await modifyWorkflow(this.runtime, existingWorkflow, modificationRequest, combinedDefs);
        normalizeTriggerSimpleParam(workflow);
        const optionFixes = correctOptionParameters(workflow);
        if (optionFixes > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Corrected ${optionFixes} invalid option parameter(s) in modified workflow`);
        }
        const unknownParams = detectUnknownParameters(workflow);
        if (unknownParams.length > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Found ${unknownParams.length} node(s) with unknown parameters in modified workflow, auto-correcting...`);
            workflow = await correctParameterNames(this.runtime, workflow, unknownParams);
        }
        const invalidRefs = validateOutputReferences(workflow);
        if (invalidRefs.length > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Found ${invalidRefs.length} invalid field reference(s) in modified workflow, auto-correcting...`);
            workflow = await correctFieldReferences(this.runtime, workflow, invalidRefs);
        }
        const exprPrefixed = ensureExpressionPrefix(workflow);
        if (exprPrefixed > 0) {
            logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Prefixed ${exprPrefixed} expression value(s) with "=" in modified workflow`);
        }
        const validationResult = validateWorkflow(workflow);
        if (!validationResult.valid) {
            logger.error({ src: 'plugin:n8n-workflow:service:main' }, `Modified workflow validation errors: ${validationResult.errors.join(', ')}`);
            throw new Error(`Modified workflow is invalid: ${validationResult.errors[0]}`);
        }
        this.injectCatalogClarifications(workflow);
        return positionNodes(workflow);
    }
    async deployWorkflow(workflow, userId) {
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Deploying workflow "${workflow.name}" for user ${userId}`);
        const config = this.getConfig();
        const client = this.getClient();
        const credStore = this.runtime.getService(N8N_CREDENTIAL_STORE_TYPE);
        const rawProvider = this.runtime.getService(N8N_CREDENTIAL_PROVIDER_TYPE);
        const credProvider = isCredentialProvider(rawProvider) ? rawProvider : null;
        // Compute tag name once - reused for credentials and workflow tagging
        const tagName = await getUserTagName(this.runtime, userId);
        const credentialResult = await resolveCredentials(workflow, userId, config, credStore ?? null, credProvider, client, tagName);
        // Block deploy if any credential is unresolved
        if (credentialResult.missingConnections.length > 0) {
            return {
                id: '',
                name: workflow.name,
                active: false,
                nodeCount: workflow.nodes.length,
                missingCredentials: credentialResult.missingConnections,
            };
        }
        // Determine if this is an update (existing workflow) or create (new workflow).
        // If update fails (workflow deleted on n8n), fallback to create.
        let deployedWorkflow;
        let wasUpdate = false;
        if (workflow.id) {
            try {
                deployedWorkflow = await client.updateWorkflow(workflow.id, credentialResult.workflow);
                wasUpdate = true;
            }
            catch {
                logger.warn({ src: 'plugin:n8n-workflow:service:main' }, `Update failed for workflow ${workflow.id}, creating new workflow instead`);
                const { id: _, ...rest } = credentialResult.workflow;
                deployedWorkflow = await client.createWorkflow(rest);
            }
        }
        else {
            deployedWorkflow = await client.createWorkflow(credentialResult.workflow);
        }
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Workflow ${wasUpdate ? 'updated' : 'created'}: ${deployedWorkflow.id}`);
        // Activate (publish) the workflow immediately after creation/update
        let active = false;
        try {
            await client.activateWorkflow(deployedWorkflow.id);
            active = true;
            logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Workflow ${deployedWorkflow.id} activated`);
        }
        catch (error) {
            logger.warn({ src: 'plugin:n8n-workflow:service:main' }, `Failed to activate workflow: ${error instanceof Error ? error.message : String(error)}`);
        }
        // Only tag new workflows (existing ones should already have tags)
        if (userId && !wasUpdate) {
            try {
                const userTag = await client.getOrCreateTag(tagName);
                await client.updateWorkflowTags(deployedWorkflow.id, [userTag.id]);
                logger.debug({ src: 'plugin:n8n-workflow:service:main' }, `Tagged workflow ${deployedWorkflow.id} with "${tagName}"`);
            }
            catch (error) {
                logger.warn({ src: 'plugin:n8n-workflow:service:main' }, `Failed to tag workflow: ${error instanceof Error ? error.message : String(error)}`);
            }
        }
        return {
            id: deployedWorkflow.id,
            name: deployedWorkflow.name,
            active,
            nodeCount: deployedWorkflow.nodes?.length || 0,
            missingCredentials: credentialResult.missingConnections,
        };
    }
    async listWorkflows(userId) {
        const client = this.getClient();
        if (userId) {
            const tagName = await getUserTagName(this.runtime, userId);
            const tagsResponse = await client.listTags();
            const userTag = tagsResponse.data.find((t) => t.name === tagName);
            if (!userTag) {
                return []; // No workflows for this user
            }
            // Get all workflows and filter by tag
            const workflowsResponse = await client.listWorkflows();
            return workflowsResponse.data.filter((w) => w.tags?.some((t) => t.id === userTag.id));
        }
        const response = await client.listWorkflows();
        return response.data;
    }
    async activateWorkflow(workflowId) {
        const client = this.getClient();
        await client.activateWorkflow(workflowId);
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Workflow ${workflowId} activated`);
    }
    async deactivateWorkflow(workflowId) {
        const client = this.getClient();
        await client.deactivateWorkflow(workflowId);
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Workflow ${workflowId} deactivated`);
    }
    async deleteWorkflow(workflowId) {
        const client = this.getClient();
        await client.deleteWorkflow(workflowId);
        logger.info({ src: 'plugin:n8n-workflow:service:main' }, `Workflow ${workflowId} deleted`);
    }
    async getWorkflow(workflowId) {
        const client = this.getClient();
        return client.getWorkflow(workflowId);
    }
    async getWorkflowExecutions(workflowId, limit) {
        const client = this.getClient();
        const response = await client.listExecutions({ workflowId, limit });
        return response.data;
    }
    async listExecutions(params) {
        const client = this.getClient();
        return client.listExecutions(params);
    }
    async getExecutionDetail(executionId) {
        const client = this.getClient();
        return client.getExecution(executionId);
    }
}
//# sourceMappingURL=n8n-workflow-service.js.map