// packages/server/src/services/workflow.ts
import { v4 as uuid } from 'uuid';
import * as store from '../storage/workflow-store.js';
import { listTemplates } from './agent.js';
function getAgentData(node) {
    if (node.type !== 'agent')
        return null;
    return node.data;
}
function getLabel(node) {
    return node.data.label || node.label;
}
// ---- DAG Validation ----
function topologicalSort(nodes, edges) {
    const adj = new Map();
    const inDegree = new Map();
    for (const node of nodes) {
        adj.set(node.id, []);
        inDegree.set(node.id, 0);
    }
    for (const edge of edges) {
        const source = edge.source;
        const target = edge.target;
        adj.get(source).push(target);
        inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
    }
    const queue = [];
    for (const [id, deg] of inDegree) {
        if (deg === 0)
            queue.push(id);
    }
    const result = [];
    while (queue.length > 0) {
        const id = queue.shift();
        result.push(id);
        for (const neighbor of adj.get(id) ?? []) {
            const newDeg = (inDegree.get(neighbor) ?? 1) - 1;
            inDegree.set(neighbor, newDeg);
            if (newDeg === 0)
                queue.push(neighbor);
        }
    }
    return result.length === nodes.length ? result : null;
}
function hasDuplicateEdges(edges) {
    const seen = new Set();
    for (const e of edges) {
        const sourceHandle = e.sourceHandle ?? '';
        const targetHandle = e.targetHandle ?? '';
        const key = `${e.source}:${sourceHandle}->${e.target}:${targetHandle}`;
        if (seen.has(key))
            return true;
        seen.add(key);
    }
    return false;
}
function hasSelfLoops(edges) {
    return edges.some(e => e.source === e.target);
}
export function validateDAG(template) {
    if (template.nodes.length === 0)
        return 'Workflow must have at least one node';
    if (hasSelfLoops(template.edges))
        return 'Self-loops are not allowed';
    if (hasDuplicateEdges(template.edges))
        return 'Duplicate edges are not allowed';
    const allNodeIds = new Set(template.nodes.map(n => n.id));
    for (const e of template.edges) {
        if (!allNodeIds.has(e.source))
            return `Edge references unknown source node: ${e.source}`;
        if (!allNodeIds.has(e.target))
            return `Edge references unknown target node: ${e.target}`;
    }
    if (topologicalSort(template.nodes, template.edges) === null) {
        return 'Workflow contains a cycle';
    }
    return null;
}
// ---- Role Staleness Resolution ----
function resolveStaleRoles(nodes) {
    const agentMap = new Map(listTemplates().map(a => [a.id, a]));
    const invalidIds = [];
    const resolved = nodes.map(node => {
        if (node.type === 'command')
            return node;
        const agentData = getAgentData(node);
        if (!agentData)
            return node;
        const agent = agentMap.get(agentData.agentConfigId);
        if (!agent) {
            invalidIds.push(node.id);
            return node;
        }
        return {
            ...node,
            data: {
                ...node.data,
                role: agent.role,
                avatarUrl: agent.avatarUrl,
                modelId: agent.modelId,
            },
        };
    });
    return { nodes: resolved, invalidIds };
}
function sanitizeWorkflowAgentValue(value) {
    if (!value || typeof value !== 'object' || Array.isArray(value))
        return value;
    const agent = value;
    return {
        id: agent.id,
        name: agent.name,
        role: agent.role,
        description: agent.description,
        runtimeKind: agent.runtimeKind,
        modelProvider: agent.modelProvider,
        providerId: agent.providerId,
        modelId: agent.modelId,
        avatarUrl: agent.avatarUrl,
        icon: agent.icon,
        enabled: agent.enabled,
    };
}
function sanitizeAgentRunNodes(nodes) {
    return nodes.map((node) => {
        if (node.type !== 'agent_run')
            return node;
        return {
            ...node,
            data: {
                ...node.data,
                agent: sanitizeWorkflowAgentValue(node.data.agent),
            },
        };
    });
}
// ---- Workflow CRUD ----
export function listWorkflows(folderId) {
    return store.listWorkflows(folderId);
}
export function getWorkflow(workflowId) {
    return store.getWorkflow(workflowId);
}
export function createWorkflow(input) {
    const now = Date.now();
    const nodes = input.nodes ?? [];
    const edges = input.edges ?? [];
    const { nodes: staleRoleResolvedNodes, invalidIds } = resolveStaleRoles(nodes);
    if (invalidIds.length > 0) {
        throw new Error(`Invalid agent references in nodes: ${invalidIds.join(', ')}`);
    }
    const resolvedNodes = sanitizeAgentRunNodes(staleRoleResolvedNodes);
    const workflow = {
        id: uuid(),
        name: input.name,
        folderId: input.folderId ?? null,
        icon: input.icon,
        description: input.description,
        tags: input.tags,
        nodes: resolvedNodes,
        edges,
        createdAt: now,
        updatedAt: now,
        triggers: input.triggers,
        groups: input.groups,
    };
    const error = validateDAG(workflow);
    if (error)
        throw new Error(error);
    store.createWorkflow(workflow);
    return workflow;
}
export function updateWorkflow(workflowId, updates) {
    const existing = store.getWorkflow(workflowId);
    if (!existing)
        throw new Error('Workflow not found');
    let nodes = updates.nodes ?? existing.nodes;
    if (updates.nodes) {
        const resolved = resolveStaleRoles(updates.nodes);
        if (resolved.invalidIds.length > 0) {
            throw new Error(`Invalid agent references in nodes: ${resolved.invalidIds.join(', ')}`);
        }
        nodes = resolved.nodes;
    }
    nodes = sanitizeAgentRunNodes(nodes);
    const updated = {
        ...existing,
        ...updates,
        nodes,
        id: existing.id,
        createdAt: existing.createdAt,
        updatedAt: Date.now(),
    };
    if (updates.nodes || updates.edges) {
        const error = validateDAG(updated);
        if (error)
            throw new Error(error);
    }
    store.updateWorkflow(updated);
    return updated;
}
export function deleteWorkflow(workflowId) {
    const existing = store.getWorkflow(workflowId);
    if (!existing)
        throw new Error('Workflow not found');
    store.deleteWorkflow(workflowId);
}
export function duplicateWorkflow(workflowId) {
    const existing = store.getWorkflow(workflowId);
    if (!existing)
        throw new Error('Workflow not found');
    const now = Date.now();
    const duplicated = {
        ...existing,
        id: uuid(),
        name: `${existing.name} (Copy)`,
        nodes: sanitizeAgentRunNodes(existing.nodes),
        createdAt: now,
        updatedAt: now,
    };
    store.createWorkflow(duplicated);
    return duplicated;
}
// ---- Folder CRUD ----
export function listFolders() {
    return store.listWorkflowFolders();
}
export function createFolder(input) {
    const folders = store.listWorkflowFolders();
    const siblings = folders.filter(f => f.parentId === (input.parentId ?? null));
    const folder = {
        id: uuid(),
        name: input.name,
        parentId: input.parentId ?? null,
        order: siblings.length > 0 ? Math.max(...siblings.map(f => f.order)) + 1 : 0,
        createdAt: Date.now(),
    };
    store.createWorkflowFolder(folder);
    return folder;
}
export function updateFolder(id, updates) {
    store.updateWorkflowFolder(id, updates);
}
export function deleteFolder(id) {
    store.deleteWorkflowFolder(id);
}
// ---- Version CRUD ----
export function listVersions(workflowId) {
    return store.listVersions(workflowId);
}
export function createVersion(workflowId, input) {
    const workflow = store.getWorkflow(workflowId);
    if (!workflow)
        throw new Error('Workflow not found');
    const nodes = input?.nodes ?? workflow.nodes;
    const edges = input?.edges ?? workflow.edges;
    const version = {
        id: uuid(),
        workflowId,
        name: input?.name || `v${store.listVersions(workflowId).length + 1}`,
        snapshot: {
            nodes: JSON.parse(JSON.stringify(nodes)),
            edges: JSON.parse(JSON.stringify(edges)),
        },
        createdAt: Date.now(),
    };
    store.addVersion(workflowId, version);
    return version;
}
export function getVersion(workflowId, versionId) {
    return store.getVersion(workflowId, versionId);
}
export function deleteVersion(workflowId, versionId) {
    store.deleteVersion(workflowId, versionId);
}
export function clearVersions(workflowId) {
    store.clearVersions(workflowId);
}
// ---- Execution Log CRUD ----
export function listExecutionLogs(workflowId) {
    return store.listExecutionLogs(workflowId);
}
export function listAllExecutionLogs(limit) {
    return store.listAllExecutionLogs(limit);
}
export function getExecutionLog(workflowId, logId) {
    return store.getExecutionLog(workflowId, logId);
}
export function getExecutionLogPath(workflowId, logId) {
    return store.getExecutionLogPath(workflowId, logId);
}
export function deleteExecutionLog(workflowId, logId) {
    store.deleteExecutionLog(workflowId, logId);
}
export function clearExecutionLogs(workflowId) {
    store.clearExecutionLogs(workflowId);
}
// ---- Staging ----
export function loadStaging(workflowId) {
    return store.loadStaging(workflowId);
}
export function saveStaging(workflowId, nodes) {
    store.saveStaging(workflowId, nodes);
}
export function clearStaging(workflowId) {
    store.clearStaging(workflowId);
}
// ---- Operation History ----
export function loadOperationHistory(workflowId) {
    return store.loadOperationHistory(workflowId);
}
export function saveOperationHistory(workflowId, entries) {
    store.saveOperationHistory(workflowId, entries);
}
export function clearOperationHistory(workflowId) {
    store.clearOperationHistory(workflowId);
}
// ---- Workflow Agent Chat ----
export function loadChat(workflowId) {
    return store.loadChat(workflowId);
}
export function saveChat(workflowId, messages) {
    store.saveChat(workflowId, messages);
}
export function clearChat(workflowId) {
    store.clearChat(workflowId);
}
// ---- Plugin Config Schemes ----
export function listPluginSchemes(workflowId, pluginId) {
    return store.listPluginSchemes(workflowId, pluginId);
}
export function readPluginScheme(workflowId, pluginId, schemeName) {
    return store.readPluginScheme(workflowId, pluginId, schemeName);
}
export function createPluginScheme(workflowId, pluginId, schemeName) {
    store.savePluginScheme(workflowId, pluginId, schemeName, {});
}
export function savePluginScheme(workflowId, pluginId, schemeName, data) {
    store.savePluginScheme(workflowId, pluginId, schemeName, data);
}
export function deletePluginScheme(workflowId, pluginId, schemeName) {
    store.deletePluginScheme(workflowId, pluginId, schemeName);
}
export function mapWorkflowToTaskDrafts(template) {
    const dependsOn = new Map();
    for (const node of template.nodes) {
        dependsOn.set(node.id, []);
    }
    for (const edge of template.edges) {
        dependsOn.get(edge.target)?.push(edge.source);
    }
    return template.nodes.map(node => {
        const label = getLabel(node);
        if (node.type === 'command') {
            return {
                key: node.id,
                title: label,
                description: `Run command: ${label}`,
                agentConfigId: undefined,
                dependsOnKeys: dependsOn.get(node.id)?.length ? dependsOn.get(node.id) : undefined,
                sandboxDirs: undefined,
                commandNode: node,
            };
        }
        const agentData = getAgentData(node);
        const taskTitle = (agentData?.taskTitleTemplate || label);
        const taskDesc = agentData?.taskDescriptionTemplate || `Task assigned to ${label} (${agentData?.role || 'unknown'})`;
        return {
            key: node.id,
            title: taskTitle,
            description: taskDesc,
            agentConfigId: agentData?.agentConfigId,
            dependsOnKeys: dependsOn.get(node.id)?.length ? dependsOn.get(node.id) : undefined,
            sandboxDirs: undefined,
        };
    });
}
// ---- Run-time Validation ----
export function validateWorkflowForRun(_workspaceId, template, memberAgentIds) {
    const agentMap = new Map(listTemplates().map((a) => [a.id, a]));
    for (const node of template.nodes) {
        if (node.type === 'command')
            continue;
        const agentData = getAgentData(node);
        if (!agentData)
            continue;
        const label = getLabel(node);
        const agent = agentMap.get(agentData.agentConfigId);
        if (!agent)
            return `Agent "${label}" (${agentData.agentConfigId}) no longer exists`;
        if (!agent.enabled)
            return `Agent "${agent.name}" is disabled`;
        if (!memberAgentIds.has(agentData.agentConfigId))
            return `Agent "${agent.name}" is not in the issue channel members`;
    }
    return null;
}
// ---- Cron Validation ----
export function validateCron(cronExpr) {
    try {
        const { validate } = require('node-cron');
        if (!validate(cronExpr)) {
            return { valid: false, nextRuns: [], error: 'Invalid cron expression' };
        }
    }
    catch {
        // If node-cron validate fails to load, continue with cron-parser
    }
    try {
        const CronExpressionParser = require('cron-parser');
        const interval = CronExpressionParser.parse(cronExpr);
        const nextRuns = [];
        for (let i = 0; i < 5; i++) {
            const iso = interval.next().toISOString();
            if (iso)
                nextRuns.push(iso);
        }
        return { valid: true, nextRuns };
    }
    catch (err) {
        return { valid: false, nextRuns: [], error: err.message };
    }
}
//# sourceMappingURL=workflow.js.map