"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { create } from "@bufbuild/protobuf";
import { isNotFound } from "@stigmer/sdk";
import { GetWorkflowVersionInputSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/version_pb";
import { useStigmer } from "../hooks";
import { useFetch } from "../internal/useFetch";
import { useWorkflowExecution } from "./useWorkflowExecution";
import { useWorkflowExecutionEventStream } from "./useWorkflowExecutionEventStream";
import { serializeWorkflowYaml } from "./serialize-workflow-yaml";
import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
import { yamlToGraph, toReactFlowElements } from "./workflow-graph-conversions";
import { cncfYamlToGraph, isCncfWorkflowYaml } from "./cncf-yaml-to-graph";
import { applyDagreLayout } from "./layout";
import { EXECUTION_DAGRE_CONFIG } from "./canvas-constants";
import { deriveEdgeExecutionStates, deriveForkProgress } from "./execution";
const EMPTY_TASK_STATES = new Map();
const TERMINAL_PHASES = new Set([3, 4, 5, 6]);
async function fetchLiveWorkflowFallback(stigmer, workflowId, versionFetchFailed) {
    try {
        const wf = await stigmer.workflow.get(workflowId);
        return { yaml: serializeWorkflowYaml(wf), isVersionPinned: false, versionFetchFailed };
    }
    catch (err) {
        if (isNotFound(err))
            return null;
        throw err;
    }
}
/**
 * Behavior hook that builds a complete read-only execution graph by:
 * 1. Resolving the workflow definition (always fetched — it's the graph source)
 * 2. Serializing to YAML and parsing into a graph model
 * 3. Computing dagre layout
 * 4. Converting to React Flow elements
 * 5. Merging live execution state into node data
 *
 * When `execution` and `taskStates` are provided externally (from a
 * parent that already subscribes), the hook skips its own duplicate
 * subscriptions. When omitted, it falls back to independent fetching
 * for standalone `<WorkflowExecutionGraph executionId="..." />` usage.
 */
export function useWorkflowExecutionGraph(options) {
    const { executionId, onAutoSelectTask, nodesDraggable = false } = options;
    const stigmer = useStigmer();
    const [selectedTaskName, setSelectedTaskName] = useState(null);
    // ── Execution data: use external or fetch own ────────────────────
    const ownExecution = useWorkflowExecution(options.execution !== undefined ? null : executionId);
    const execution = options.execution !== undefined
        ? options.execution
        : ownExecution.execution;
    const isLoadingExecution = options.execution !== undefined
        ? false
        : ownExecution.isLoading;
    const executionError = options.execution !== undefined
        ? null
        : ownExecution.error;
    const phase = execution?.status?.phase;
    // ── Workflow definition fetch (version-aware for graph building) ──
    const workflowId = execution?.spec?.workflowId || null;
    const workflowInstanceId = execution?.spec?.workflowInstanceId || null;
    const versionHash = execution?.status?.workflowVersionHash || null;
    const instanceFetchFn = !workflowId && workflowInstanceId
        ? async () => {
            try {
                const instance = await stigmer.workflowInstance.get(workflowInstanceId);
                return instance.spec?.workflowId ?? null;
            }
            catch {
                return null;
            }
        }
        : null;
    const { data: resolvedWorkflowId } = useFetch(instanceFetchFn, [workflowInstanceId, stigmer], null);
    const effectiveWorkflowId = workflowId || resolvedWorkflowId;
    // Fetch workflow definition — use pinned version if available, otherwise live
    const workflowFetchFn = effectiveWorkflowId
        ? async () => {
            // Path 1: Versioned execution — fetch the specific version entry
            if (versionHash) {
                try {
                    const versionEntry = await stigmer.workflow.getVersion(create(GetWorkflowVersionInputSchema, { workflowId: effectiveWorkflowId, versionHash }));
                    if (versionEntry?.validatedYaml) {
                        return { yaml: versionEntry.validatedYaml, isVersionPinned: true, versionFetchFailed: false };
                    }
                    // Version entry exists but YAML is empty — treat as degraded
                    return await fetchLiveWorkflowFallback(stigmer, effectiveWorkflowId, true);
                }
                catch {
                    // Version lookup failed — explicit fallback with signal
                    return await fetchLiveWorkflowFallback(stigmer, effectiveWorkflowId, true);
                }
            }
            // Path 2: Legacy execution or no hash — use live workflow
            return await fetchLiveWorkflowFallback(stigmer, effectiveWorkflowId, false);
        }
        : null;
    const { data: workflowData, isLoading: isLoadingWorkflow, error: workflowError, } = useFetch(workflowFetchFn, [effectiveWorkflowId, versionHash, stigmer], null);
    // ── Build graph model ────────────────────────────────────────────
    const isVersionPinned = workflowData?.isVersionPinned ?? false;
    const graphBuild = useMemo(() => {
        if (!workflowData?.yaml)
            return null;
        try {
            const graph = isCncfWorkflowYaml(workflowData.yaml)
                ? cncfYamlToGraph(workflowData.yaml)
                : yamlToGraph(workflowData.yaml);
            const laidOut = applyDagreLayout(graph, EXECUTION_DAGRE_CONFIG);
            return { elements: toReactFlowElements(laidOut), graphModel: laidOut };
        }
        catch {
            return null;
        }
    }, [workflowData]);
    const baseElements = graphBuild?.elements ?? null;
    const graphModel = graphBuild?.graphModel ?? null;
    // ── Task states: use external or subscribe own ───────────────────
    const ownStream = useWorkflowExecutionEventStream(options.taskStates !== undefined ? null : executionId, { executionPhase: phase });
    const taskStates = options.taskStates !== undefined
        ? options.taskStates
        : ownStream.taskStates;
    // ── Merge execution state into nodes (T04) + fork progress (T06) + agent activity ──
    const pendingApprovals = execution?.status?.pendingApprovals;
    const nodesWithExecution = useMemo(() => {
        if (!baseElements)
            return [];
        return baseElements.nodes.map((node) => {
            const nodeData = node.data;
            if (nodeData.isSentinel) {
                return nodesDraggable ? { ...node, draggable: false } : node;
            }
            const taskState = taskStates.get(nodeData.taskName);
            const executionState = taskState
                ? {
                    status: taskState.status,
                    durationMs: taskState.durationMs,
                    costMicros: taskState.costMicros,
                    attemptNumber: taskState.attemptNumber,
                    error: taskState.error || undefined,
                }
                : { status: "not_reached" };
            // T06: Derive fork progress for fork nodes.
            const forkProgress = nodeData.kind === WorkflowTaskKind.fork && nodeData.config
                ? deriveForkProgress(nodeData.config, taskStates)
                : null;
            // Agent activity for running agent_call nodes.
            const agentActivity = taskState?.status === "running" &&
                taskState.agentSlug &&
                (taskState.currentToolName || taskState.messagesCount > 0)
                ? {
                    agentSlug: taskState.agentSlug,
                    currentToolName: taskState.currentToolName,
                    messagesCount: taskState.messagesCount,
                    toolCallsCount: taskState.toolCallsCount,
                }
                : undefined;
            // Approval tool name from pending approvals matched by childExecutionId.
            let approvalToolName;
            if (taskState?.status === "waiting_approval" && taskState.childExecutionId && pendingApprovals) {
                const match = pendingApprovals.find((pa) => pa.childAgentExecutionId === taskState.childExecutionId);
                if (match?.approval?.toolName) {
                    approvalToolName = match.approval.toolName;
                }
            }
            return {
                ...node,
                data: {
                    ...nodeData,
                    executionState,
                    ...(forkProgress && { forkProgress }),
                    ...(agentActivity && { agentActivity }),
                    ...(approvalToolName && { approvalToolName }),
                },
                draggable: nodesDraggable,
                connectable: false,
                deletable: false,
            };
        });
    }, [baseElements, taskStates, pendingApprovals, nodesDraggable]);
    // ── Merge execution state into edges (T06) ──────────────────────
    const edgesWithExecution = useMemo(() => {
        if (!baseElements || !graphModel)
            return [];
        const edgeStates = deriveEdgeExecutionStates(graphModel.edges, graphModel.nodes, taskStates);
        return baseElements.edges.map((edge) => {
            const execState = edgeStates.get(edge.id);
            if (!execState || execState === "not_reached")
                return edge;
            return {
                ...edge,
                data: { ...edge.data, executionState: execState },
            };
        });
    }, [baseElements, graphModel, taskStates]);
    // ── Version mismatch detection ───────────────────────────────────
    const versionFetchFailed = workflowData?.versionFetchFailed ?? false;
    const versionMismatch = useMemo(() => {
        // When the graph is rendered from the pinned version, no mismatch is possible.
        if (isVersionPinned)
            return null;
        // When a pinned hash exists but version lookup failed, show a specific message.
        if (versionFetchFailed && versionHash) {
            return "Unable to load the pinned workflow version. Showing the current definition as a fallback.";
        }
        // Legacy path: no version hash on execution, using live workflow.
        if (!execution?.status?.tasks || !baseElements)
            return null;
        const executionTaskNames = new Set(execution.status.tasks.map((t) => t.taskName).filter(Boolean));
        const graphTaskNames = new Set(baseElements.nodes
            .map((n) => n.data.taskName)
            .filter((name) => name !== "Start" && name !== "End"));
        const inExecutionNotGraph = [...executionTaskNames].filter((n) => !graphTaskNames.has(n));
        const inGraphNotExecution = [...graphTaskNames].filter((n) => !executionTaskNames.has(n));
        if (inExecutionNotGraph.length > 0 || inGraphNotExecution.length > 0) {
            return "This execution predates version tracking. The workflow has since been modified.";
        }
        return null;
    }, [execution?.status?.tasks, baseElements, isVersionPinned, versionFetchFailed, versionHash]);
    // ── Loading / error aggregation ──────────────────────────────────
    const isLoading = isLoadingExecution || isLoadingWorkflow;
    const error = executionError?.message
        ?? workflowError?.message
        ?? (!isLoading && !baseElements && effectiveWorkflowId ? "Unable to build workflow graph" : null)
        ?? null;
    // ── Auto-select failed task (fires once on terminal phase) ───────
    const didAutoSelectRef = useRef(false);
    useEffect(() => {
        if (didAutoSelectRef.current)
            return;
        if (!phase || !TERMINAL_PHASES.has(phase))
            return;
        if (selectedTaskName)
            return;
        for (const [name, state] of taskStates) {
            if (state.status === "failed") {
                didAutoSelectRef.current = true;
                setSelectedTaskName(name);
                onAutoSelectTask?.(name);
                return;
            }
        }
    }, [phase, taskStates, selectedTaskName, onAutoSelectTask]);
    return {
        nodes: nodesWithExecution,
        edges: edgesWithExecution,
        executionPhase: phase,
        selectedTaskName,
        setSelectedTaskName,
        isLoading,
        error,
        versionMismatch,
        versionResolutionFailed: versionFetchFailed,
        taskStates,
    };
}
//# sourceMappingURL=useWorkflowExecutionGraph.js.map