import { Metadata, RunView, LogError, LogStatus } from '@memberjunction/core';
import { AgentRunner } from '@memberjunction/ai-agents';
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
export class TaskOrchestrator {
    contextUser;
    pubSub;
    sessionId;
    userPayload;
    createNotifications;
    conversationDetailId;
    JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
    taskTypeId = null;
    constructor(contextUser, pubSub, sessionId, userPayload, createNotifications = false, conversationDetailId) {
        this.contextUser = contextUser;
        this.pubSub = pubSub;
        this.sessionId = sessionId;
        this.userPayload = userPayload;
        this.createNotifications = createNotifications;
        this.conversationDetailId = conversationDetailId;
    }
    async ensureTaskType() {
        if (this.taskTypeId) {
            return this.taskTypeId;
        }
        const rv = new RunView();
        const result = await rv.RunView({
            EntityName: 'MJ: Task Types',
            ExtraFilter: `Name='AI Agent Execution'`,
            ResultType: 'entity_object'
        }, this.contextUser);
        if (result.Success && result.Results && result.Results.length > 0) {
            this.taskTypeId = result.Results[0].ID;
            return this.taskTypeId;
        }
        const md = new Metadata();
        const taskType = await md.GetEntityObject('MJ: Task Types', this.contextUser);
        taskType.Name = 'AI Agent Execution';
        taskType.Description = 'Task executed by an AI agent as part of conversation workflow';
        const saved = await taskType.Save();
        if (!saved) {
            throw new Error('Failed to create AI Agent Execution task type');
        }
        this.taskTypeId = taskType.ID;
        return this.taskTypeId;
    }
    async createTasksFromGraph(taskGraph, conversationDetailId, environmentId) {
        const taskTypeId = await this.ensureTaskType();
        const md = new Metadata();
        const tempIdToRealId = new Map();
        const parentTask = await md.GetEntityObject('MJ: Tasks', this.contextUser);
        parentTask.Name = taskGraph.workflowName;
        parentTask.Description = taskGraph.reasoning || 'AI-orchestrated workflow';
        parentTask.TypeID = taskTypeId;
        parentTask.EnvironmentID = environmentId;
        parentTask.ConversationDetailID = conversationDetailId;
        parentTask.Status = 'In Progress';
        parentTask.PercentComplete = 0;
        const parentSaved = await parentTask.Save();
        if (!parentSaved) {
            throw new Error('Failed to create parent workflow task');
        }
        LogStatus(`Created parent workflow task: ${parentTask.Name} (${parentTask.ID})`);
        const seenTempIds = new Set();
        const uniqueTasks = taskGraph.tasks.filter(task => {
            if (seenTempIds.has(task.tempId)) {
                LogError(`Duplicate tempId detected and ignored: ${task.tempId} (${task.name})`);
                return false;
            }
            seenTempIds.add(task.tempId);
            return true;
        });
        LogStatus(`Creating ${uniqueTasks.length} unique child tasks (${taskGraph.tasks.length - uniqueTasks.length} duplicates filtered)`);
        for (const taskDef of uniqueTasks) {
            const task = await md.GetEntityObject('MJ: Tasks', this.contextUser);
            const agent = await this.findAgentByName(taskDef.agentName);
            if (!agent) {
                LogError(`Agent not found: ${taskDef.agentName}`);
                continue;
            }
            task.Name = taskDef.name;
            task.Description = taskDef.description;
            task.TypeID = taskTypeId;
            task.EnvironmentID = environmentId;
            task.ParentID = parentTask.ID;
            task.ConversationDetailID = conversationDetailId;
            task.AgentID = agent.ID;
            task.Status = 'Pending';
            task.PercentComplete = 0;
            if (taskDef.inputPayload) {
                const metadata = {
                    inputPayload: taskDef.inputPayload,
                    tempId: taskDef.tempId
                };
                task.Description = `${taskDef.description}\n\n__TASK_METADATA__\n${JSON.stringify(metadata)}`;
            }
            const saved = await task.Save();
            if (saved) {
                tempIdToRealId.set(taskDef.tempId, task.ID);
                LogStatus(`Created child task: ${task.Name} (${task.ID}) under parent ${parentTask.ID}`);
            }
        }
        for (const taskDef of uniqueTasks) {
            const taskId = tempIdToRealId.get(taskDef.tempId);
            if (!taskId)
                continue;
            for (const dependsOnTempId of taskDef.dependsOn) {
                const dependsOnId = tempIdToRealId.get(dependsOnTempId);
                if (!dependsOnId) {
                    LogError(`Dependency not found: ${dependsOnTempId}`);
                    continue;
                }
                const dependency = await md.GetEntityObject('MJ: Task Dependencies', this.contextUser);
                dependency.TaskID = taskId;
                dependency.DependsOnTaskID = dependsOnId;
                dependency.DependencyType = 'Prerequisite';
                await dependency.Save();
                LogStatus(`Created dependency: Task ${taskId} depends on ${dependsOnId}`);
            }
        }
        return {
            parentTaskId: parentTask.ID,
            taskIdMap: tempIdToRealId
        };
    }
    publishTaskProgress(taskName, message, percentComplete) {
        if (!this.pubSub || !this.sessionId || !this.userPayload) {
            LogStatus(`⚠️ PubSub not available for progress updates (pubSub: ${!!this.pubSub}, sessionId: ${!!this.sessionId}, userPayload: ${!!this.userPayload})`);
            return;
        }
        const payload = {
            message: JSON.stringify({
                resolver: 'TaskOrchestrator',
                type: 'TaskProgress',
                status: 'ok',
                data: {
                    taskName,
                    message,
                    percentComplete,
                    timestamp: new Date(),
                    conversationDetailId: this.conversationDetailId
                }
            }),
            sessionId: this.userPayload.sessionId
        };
        LogStatus(`📡 Publishing task progress: ${taskName} - ${message} (${percentComplete}%) to session ${this.userPayload.sessionId}`);
        this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
        LogStatus(`[Task: ${taskName}] ${message} (${percentComplete}%)`);
    }
    publishAgentProgress(taskName, agentStep, agentMessage) {
        if (!this.pubSub || !this.sessionId || !this.userPayload) {
            LogStatus(`⚠️ PubSub not available for agent progress (pubSub: ${!!this.pubSub}, sessionId: ${!!this.sessionId}, userPayload: ${!!this.userPayload})`);
            return;
        }
        const payload = {
            message: JSON.stringify({
                resolver: 'TaskOrchestrator',
                type: 'AgentProgress',
                status: 'ok',
                data: {
                    taskName,
                    agentStep,
                    agentMessage,
                    timestamp: new Date(),
                    conversationDetailId: this.conversationDetailId
                }
            }),
            sessionId: this.userPayload.sessionId
        };
        LogStatus(`📡 Publishing agent progress: ${taskName} → ${agentStep} to session ${this.userPayload.sessionId}`);
        this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
        LogStatus(`[Task: ${taskName}] → ${agentStep}: ${agentMessage}`);
    }
    async findAgentByName(agentName) {
        const rv = new RunView();
        const result = await rv.RunView({
            EntityName: 'AI Agents',
            ExtraFilter: `Name='${agentName.replace(/'/g, "''")}'`,
            ResultType: 'entity_object'
        }, this.contextUser);
        if (result.Success && result.Results && result.Results.length > 0) {
            return result.Results[0];
        }
        return null;
    }
    async executeTasksForParent(parentTaskId) {
        const results = [];
        let hasMore = true;
        const md = new Metadata();
        const parentTask = await md.GetEntityObject('MJ: Tasks', this.contextUser);
        await parentTask.Load(parentTaskId);
        this.publishTaskProgress(parentTask.Name, 'Starting workflow execution', 0);
        while (hasMore) {
            const eligibleTasks = await this.findEligibleTasks(parentTaskId);
            if (eligibleTasks.length === 0) {
                hasMore = false;
                break;
            }
            for (const task of eligibleTasks) {
                this.publishTaskProgress(task.Name, 'Starting task', 0);
                const result = await this.executeTask(task);
                results.push(result);
                if (result.success) {
                    this.publishTaskProgress(task.Name, 'Task completed successfully', 100);
                }
                else {
                    this.publishTaskProgress(task.Name, `Task failed: ${result.error}`, 100);
                }
                await this.updateParentTaskProgress(parentTaskId);
            }
        }
        await this.completeParentTask(parentTaskId);
        this.publishTaskProgress(parentTask.Name, 'Workflow completed', 100);
        return results;
    }
    async findEligibleTasks(parentTaskId) {
        const rv = new RunView();
        const tasksResult = await rv.RunView({
            EntityName: 'MJ: Tasks',
            ExtraFilter: `ParentID='${parentTaskId}' AND Status='Pending'`,
            ResultType: 'entity_object'
        }, this.contextUser);
        if (!tasksResult.Success || !tasksResult.Results) {
            return [];
        }
        const eligibleTasks = [];
        for (const task of tasksResult.Results) {
            const hasIncompleteDeps = await this.hasIncompleteDependencies(task.ID);
            if (!hasIncompleteDeps) {
                eligibleTasks.push(task);
            }
        }
        return eligibleTasks;
    }
    async updateParentTaskProgress(parentTaskId) {
        const md = new Metadata();
        const parentTask = await md.GetEntityObject('MJ: Tasks', this.contextUser);
        const loaded = await parentTask.Load(parentTaskId);
        if (!loaded)
            return;
        const rv = new RunView();
        const childrenResult = await rv.RunView({
            EntityName: 'MJ: Tasks',
            ExtraFilter: `ParentID='${parentTaskId}'`,
            ResultType: 'entity_object'
        }, this.contextUser);
        if (!childrenResult.Success || !childrenResult.Results || childrenResult.Results.length === 0) {
            return;
        }
        const children = childrenResult.Results;
        const completedCount = children.filter(t => t.Status === 'Complete').length;
        const totalCount = children.length;
        parentTask.PercentComplete = Math.round((completedCount / totalCount) * 100);
        await parentTask.Save();
        LogStatus(`Parent task ${parentTask.Name} is ${parentTask.PercentComplete}% complete (${completedCount}/${totalCount} tasks)`);
    }
    async completeParentTask(parentTaskId) {
        const md = new Metadata();
        const parentTask = await md.GetEntityObject('MJ: Tasks', this.contextUser);
        const loaded = await parentTask.Load(parentTaskId);
        if (!loaded)
            return;
        parentTask.Status = 'Complete';
        parentTask.PercentComplete = 100;
        parentTask.CompletedAt = new Date();
        const saved = await parentTask.Save();
        LogStatus(`Parent workflow task completed: ${parentTask.Name}`);
        if (this.createNotifications && saved) {
            await this.createTaskGraphCompletionNotification(parentTask);
        }
    }
    async hasIncompleteDependencies(taskId) {
        const rv = new RunView();
        const depsResult = await rv.RunView({
            EntityName: 'MJ: Task Dependencies',
            ExtraFilter: `TaskID='${taskId}'`,
            ResultType: 'entity_object'
        }, this.contextUser);
        if (!depsResult.Success || !depsResult.Results || depsResult.Results.length === 0) {
            return false;
        }
        for (const dep of depsResult.Results) {
            const dependsOnTask = await this.loadTask(dep.DependsOnTaskID);
            if (dependsOnTask && dependsOnTask.Status !== 'Complete') {
                return true;
            }
        }
        return false;
    }
    async loadTask(taskId) {
        const md = new Metadata();
        const task = await md.GetEntityObject('MJ: Tasks', this.contextUser);
        const loaded = await task.Load(taskId);
        return loaded ? task : null;
    }
    async executeTask(task) {
        try {
            LogStatus(`Executing task: ${task.Name} (${task.ID})`);
            task.Status = 'In Progress';
            task.StartedAt = new Date();
            await task.Save();
            const md = new Metadata();
            const agentEntity = await md.GetEntityObject('AI Agents', this.contextUser);
            const loaded = await agentEntity.Load(task.AgentID);
            if (!loaded) {
                throw new Error(`Agent with ID ${task.AgentID} not found`);
            }
            const messages = await this.buildConversationMessages(task);
            const onProgress = (progress) => {
                this.publishAgentProgress(task.Name, progress.step || 'processing', progress.message || '');
            };
            const agentRunner = new AgentRunner();
            const agentResult = await agentRunner.RunAgent({
                agent: agentEntity,
                conversationMessages: messages,
                contextUser: this.contextUser,
                conversationDetailId: task.ConversationDetailID || undefined,
                onProgress: onProgress
            });
            if (agentResult.success) {
                const output = this.extractAgentOutput(agentResult);
                const outputMetadata = {
                    outputType: output.type,
                    output: output.content,
                    agentRunId: agentResult.agentRun?.ID
                };
                task.Status = 'Complete';
                task.CompletedAt = new Date();
                task.PercentComplete = 100;
                task.Description = `${task.Description}\n\n__TASK_OUTPUT__\n${JSON.stringify(outputMetadata)}`;
                await task.Save();
                LogStatus(`Task completed: ${task.Name} (output type: ${output.type})`);
                let conversationDetailId = task.ConversationDetailID;
                if (!conversationDetailId && task.ParentID) {
                    const parentTask = await this.loadTask(task.ParentID);
                    conversationDetailId = parentTask?.ConversationDetailID || null;
                }
                if (conversationDetailId && output.content) {
                    await this.createArtifactFromOutput(output, conversationDetailId, agentEntity, task.Name);
                }
                else if (!conversationDetailId) {
                    LogError(`Cannot create artifact: No conversation detail ID found for task ${task.ID}`);
                }
                return {
                    taskId: task.ID,
                    success: true,
                    output: output.content
                };
            }
            else {
                task.Status = 'Failed';
                task.CompletedAt = new Date();
                await task.Save();
                const errorMsg = agentResult.agentRun?.ErrorMessage || 'Agent execution failed';
                LogError(`Task failed: ${task.Name} - ${errorMsg}`);
                return {
                    taskId: task.ID,
                    success: false,
                    error: errorMsg
                };
            }
        }
        catch (error) {
            LogError(error);
            task.Status = 'Failed';
            task.CompletedAt = new Date();
            await task.Save();
            return {
                taskId: task.ID,
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
            };
        }
    }
    extractInputPayload(task) {
        if (!task.Description)
            return null;
        const metadataMatch = task.Description.match(/__TASK_METADATA__\n(.+?)(?:\n\n|$)/s);
        if (!metadataMatch)
            return null;
        try {
            const metadata = JSON.parse(metadataMatch[1]);
            return metadata.inputPayload || null;
        }
        catch {
            return null;
        }
    }
    async getDependentTaskOutputs(taskId) {
        const outputs = new Map();
        const rv = new RunView();
        const depsResult = await rv.RunView({
            EntityName: 'MJ: Task Dependencies',
            ExtraFilter: `TaskID='${taskId}'`,
            ResultType: 'entity_object'
        }, this.contextUser);
        if (!depsResult.Success || !depsResult.Results) {
            return outputs;
        }
        for (const dep of depsResult.Results) {
            const dependsOnTask = await this.loadTask(dep.DependsOnTaskID);
            if (!dependsOnTask || !dependsOnTask.Description)
                continue;
            const outputMatch = dependsOnTask.Description.match(/__TASK_OUTPUT__\n(.+?)$/s);
            if (outputMatch) {
                try {
                    const outputMetadata = JSON.parse(outputMatch[1]);
                    outputs.set(dep.DependsOnTaskID, outputMetadata.output);
                }
                catch (e) {
                    LogError(`Failed to parse output for task ${dep.DependsOnTaskID}: ${e}`);
                }
            }
        }
        return outputs;
    }
    async buildConversationMessages(task) {
        const messages = [];
        let userContent = task.Description || task.Name;
        const inputPayload = this.extractInputPayload(task);
        const dependentOutputs = await this.getDependentTaskOutputs(task.ID);
        if (dependentOutputs.size > 0) {
            userContent += '\n\n## Results from Dependent Tasks:\n\n';
            for (const [taskId, outputData] of dependentOutputs.entries()) {
                const depTask = await this.loadTask(taskId);
                const taskName = depTask?.Name || taskId;
                userContent += `### ${taskName}\n\`\`\`json\n${JSON.stringify(outputData, null, 2)}\n\`\`\`\n\n`;
            }
        }
        if (inputPayload) {
            userContent += '\n\n## Task Input:\n\`\`\`json\n' + JSON.stringify(inputPayload, null, 2) + '\n\`\`\`';
        }
        messages.push({
            role: 'user',
            content: userContent
        });
        return messages;
    }
    extractAgentOutput(agentResult) {
        if (agentResult.agentRun?.Message) {
            return { type: 'message', content: agentResult.agentRun.Message };
        }
        if (agentResult.payload && Object.keys(agentResult.payload).length > 0) {
            return { type: 'payload', content: agentResult.payload };
        }
        return { type: 'message', content: '' };
    }
    async createArtifactFromOutput(output, conversationDetailId, agent, taskName) {
        try {
            const md = new Metadata();
            const artifact = await md.GetEntityObject('MJ: Artifacts', this.contextUser);
            artifact.Name = `${agent.Name} - ${taskName} - ${new Date().toLocaleString()}`;
            artifact.Description = `Artifact generated by ${agent.Name} for task: ${taskName} (${output.type})`;
            const defaultArtifactTypeId = agent.DefaultArtifactTypeID;
            artifact.TypeID = defaultArtifactTypeId || this.JSON_ARTIFACT_TYPE_ID;
            artifact.UserID = this.contextUser.ID;
            artifact.EnvironmentID = this.contextUser.EnvironmentID || 'F51358F3-9447-4176-B313-BF8025FD8D09';
            const creationMode = agent.ArtifactCreationMode;
            if (creationMode === 'System Only') {
                artifact.Visibility = 'System Only';
                LogStatus(`Task artifact marked as "System Only" per agent configuration`);
            }
            else {
                artifact.Visibility = 'Always';
            }
            const artifactSaved = await artifact.Save();
            if (!artifactSaved) {
                LogError('Failed to save artifact');
                return;
            }
            LogStatus(`Created artifact: ${artifact.Name} (${artifact.ID})`);
            const version = await md.GetEntityObject('MJ: Artifact Versions', this.contextUser);
            version.ArtifactID = artifact.ID;
            version.VersionNumber = 1;
            if (output.type === 'message') {
                version.Content = output.content;
            }
            else {
                version.Content = JSON.stringify(output.content, null, 2);
            }
            version.UserID = this.contextUser.ID;
            const versionSaved = await version.Save();
            if (!versionSaved) {
                LogError('Failed to save artifact version');
                return;
            }
            LogStatus(`Created artifact version: ${version.ID}`);
            const nameAttr = version.Attributes?.find((attr) => attr.StandardProperty === 'name' || attr.Name?.toLowerCase() === 'name');
            let extractedName = nameAttr?.Value?.trim();
            if (extractedName && extractedName.toLowerCase() !== 'null') {
                extractedName = extractedName.replace(/^["']|["']$/g, '');
                artifact.Name = extractedName;
                if (await artifact.Save()) {
                    LogStatus(`✨ Updated artifact name to: ${artifact.Name}`);
                }
            }
            const junction = await md.GetEntityObject('MJ: Conversation Detail Artifacts', this.contextUser);
            junction.ConversationDetailID = conversationDetailId;
            junction.ArtifactVersionID = version.ID;
            junction.Direction = 'Output';
            const junctionSaved = await junction.Save();
            if (!junctionSaved) {
                LogError('Failed to create artifact-conversation association');
                return;
            }
            LogStatus(`Linked artifact ${artifact.ID} to conversation detail ${conversationDetailId}`);
        }
        catch (error) {
            LogError(`Error creating artifact from output: ${error}`);
        }
    }
    async createTaskGraphCompletionNotification(parentTask) {
        try {
            if (!parentTask.ConversationDetailID) {
                LogStatus('Skipping notification - no conversation detail linked');
                return;
            }
            const md = new Metadata();
            const detail = await md.GetEntityObject('Conversation Details', this.contextUser);
            if (!(await detail.Load(parentTask.ConversationDetailID))) {
                throw new Error(`Failed to load conversation detail ${parentTask.ConversationDetailID}`);
            }
            const rv = new RunView();
            const tasksResult = await rv.RunView({
                EntityName: 'MJ: Tasks',
                ExtraFilter: `ParentID='${parentTask.ID}'`,
                ResultType: 'entity_object'
            }, this.contextUser);
            const childTasks = tasksResult.Success ? (tasksResult.Results || []) : [];
            const successCount = childTasks.filter(t => t.Status === 'Complete').length;
            const totalCount = childTasks.length;
            const notification = await md.GetEntityObject('User Notifications', this.contextUser);
            notification.UserID = this.contextUser.ID;
            notification.Title = `Workflow "${parentTask.Name}" completed`;
            notification.Message = `Your ${totalCount}-step workflow has finished. ${successCount} of ${totalCount} tasks completed successfully.`;
            notification.ResourceConfiguration = JSON.stringify({
                type: 'conversation',
                conversationId: detail.ConversationID,
                messageId: parentTask.ConversationDetailID,
                taskId: parentTask.ID
            });
            notification.Unread = true;
            if (!(await notification.Save())) {
                throw new Error('Failed to save notification');
            }
            LogStatus(`📬 Created task graph notification ${notification.ID} for user ${this.contextUser.ID}`);
            if (this.pubSub && this.userPayload) {
                this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
                    userPayload: JSON.stringify(this.userPayload),
                    message: JSON.stringify({
                        type: 'notification',
                        notificationId: notification.ID,
                        action: 'create',
                        title: notification.Title,
                        message: notification.Message
                    })
                });
                LogStatus(`📡 Published task graph notification event to client`);
            }
        }
        catch (error) {
            LogError(`Failed to create task graph notification: ${error.message}`);
        }
    }
}
//# sourceMappingURL=TaskOrchestrator.js.map