import { useMemo } from "react"; import type { DirectiveStep } from "../../lib/api"; import { StepNode } from "./StepNode"; interface DirectiveDAGProps { steps: DirectiveStep[]; onComplete?: (stepId: string) => void; onFail?: (stepId: string) => void; onSkip?: (stepId: string) => void; } interface Layer { steps: DirectiveStep[]; } function topoSort(steps: DirectiveStep[]): Layer[] { if (steps.length === 0) return []; const stepMap = new Map(steps.map((s) => [s.id, s])); const assigned = new Set(); const layers: Layer[] = []; // Iteratively find steps whose dependencies are all assigned let remaining = [...steps]; while (remaining.length > 0) { const layer: DirectiveStep[] = []; for (const step of remaining) { const depsResolved = step.dependsOn.every( (depId) => assigned.has(depId) || !stepMap.has(depId) ); if (depsResolved) { layer.push(step); } } if (layer.length === 0) { // Cycle detected or orphaned — push all remaining layers.push({ steps: remaining }); break; } for (const s of layer) { assigned.add(s.id); } layers.push({ steps: layer.sort((a, b) => a.orderIndex - b.orderIndex) }); remaining = remaining.filter((s) => !assigned.has(s.id)); } return layers; } export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAGProps) { const layers = useMemo(() => topoSort(steps), [steps]); if (steps.length === 0) { return (
No steps yet. Add steps to build the DAG.
); } return (
{layers.map((layer, layerIdx) => (
{layerIdx > 0 && (
)}
{layer.steps.map((step) => ( onComplete(step.id) : undefined} onFail={onFail ? () => onFail(step.id) : undefined} onSkip={onSkip ? () => onSkip(step.id) : undefined} /> ))}
))}
); }