celers_canvas/
lib.rs

1//! Canvas workflow primitives
2//!
3//! This crate provides distributed workflow patterns for task orchestration.
4//!
5//! # Workflow Primitives
6//!
7//! - **Chain**: Execute tasks sequentially, passing results as arguments
8//! - **Group**: Execute tasks in parallel
9//! - **Chord**: Execute tasks in parallel, then run callback with all results
10//! - **Map**: Apply a task to multiple argument sets in parallel
11//!
12//! # Example
13//!
14//! ```ignore
15//! // Chain: task1 -> task2 -> task3
16//! let workflow = Chain::new()
17//!     .then("task1", args1)
18//!     .then("task2", args2)
19//!     .then("task3", args3);
20//!
21//! // Chord: (task1 | task2 | task3) -> callback
22//! let workflow = Chord::new()
23//!     .add("task1", args1)
24//!     .add("task2", args2)
25//!     .add("task3", args3)
26//!     .callback("aggregate_results", callback_args);
27//! ```
28
29use celers_core::{Broker, SerializedTask};
30use serde::{Deserialize, Serialize};
31use std::collections::HashMap;
32use uuid::Uuid;
33
34#[cfg(feature = "backend-redis")]
35use celers_backend_redis::{ChordState, ResultBackend};
36
37#[cfg(feature = "backend-redis")]
38use chrono::Utc;
39
40/// Signature (a task definition with arguments)
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct Signature {
43    /// Task name
44    pub task: String,
45
46    /// Positional arguments
47    #[serde(default)]
48    pub args: Vec<serde_json::Value>,
49
50    /// Keyword arguments
51    #[serde(default)]
52    pub kwargs: HashMap<String, serde_json::Value>,
53
54    /// Task options
55    #[serde(default)]
56    pub options: TaskOptions,
57
58    /// Immutability flag (whether args can be replaced)
59    #[serde(default)]
60    pub immutable: bool,
61}
62
63impl Signature {
64    pub fn new(task: String) -> Self {
65        Self {
66            task,
67            args: Vec::new(),
68            kwargs: HashMap::new(),
69            options: TaskOptions::default(),
70            immutable: false,
71        }
72    }
73
74    pub fn with_args(mut self, args: Vec<serde_json::Value>) -> Self {
75        self.args = args;
76        self
77    }
78
79    pub fn with_kwargs(mut self, kwargs: HashMap<String, serde_json::Value>) -> Self {
80        self.kwargs = kwargs;
81        self
82    }
83
84    pub fn with_priority(mut self, priority: u8) -> Self {
85        self.options.priority = Some(priority);
86        self
87    }
88
89    pub fn with_queue(mut self, queue: String) -> Self {
90        self.options.queue = Some(queue);
91        self
92    }
93
94    pub fn with_task_id(mut self, task_id: Uuid) -> Self {
95        self.options.task_id = Some(task_id);
96        self
97    }
98
99    pub fn with_link(mut self, link: Signature) -> Self {
100        self.options.link = Some(Box::new(link));
101        self
102    }
103
104    pub fn with_link_error(mut self, link_error: Signature) -> Self {
105        self.options.link_error = Some(Box::new(link_error));
106        self
107    }
108
109    /// Add a callback to the success callback chain
110    pub fn add_link(mut self, link: Signature) -> Self {
111        self.options.links.push(link);
112        self
113    }
114
115    /// Add a callback to the error callback chain
116    pub fn add_link_error(mut self, link_error: Signature) -> Self {
117        self.options.link_errors.push(link_error);
118        self
119    }
120
121    /// Set the on_retry callback
122    pub fn with_on_retry(mut self, callback: Signature) -> Self {
123        self.options.on_retry = Some(Box::new(callback));
124        self
125    }
126
127    /// Set soft time limit (warning before kill)
128    pub fn with_soft_time_limit(mut self, seconds: u64) -> Self {
129        self.options.soft_time_limit = Some(seconds);
130        self
131    }
132
133    /// Set hard time limit (force kill)
134    pub fn with_time_limit(mut self, seconds: u64) -> Self {
135        self.options.time_limit = Some(seconds);
136        self
137    }
138
139    /// Set retry delay in seconds
140    pub fn with_retry_delay(mut self, seconds: u64) -> Self {
141        self.options.retry_delay = Some(seconds);
142        self
143    }
144
145    /// Set retry backoff factor (exponential multiplier)
146    pub fn with_retry_backoff(mut self, factor: f64) -> Self {
147        self.options.retry_backoff = Some(factor);
148        self
149    }
150
151    /// Set maximum retry delay
152    pub fn with_retry_backoff_max(mut self, seconds: u64) -> Self {
153        self.options.retry_backoff_max = Some(seconds);
154        self
155    }
156
157    /// Enable/disable retry jitter
158    pub fn with_retry_jitter(mut self, jitter: bool) -> Self {
159        self.options.retry_jitter = Some(jitter);
160        self
161    }
162
163    pub fn immutable(mut self) -> Self {
164        self.immutable = true;
165        self
166    }
167
168    /// Check if task has arguments
169    pub fn has_args(&self) -> bool {
170        !self.args.is_empty()
171    }
172
173    /// Check if task has keyword arguments
174    pub fn has_kwargs(&self) -> bool {
175        !self.kwargs.is_empty()
176    }
177
178    /// Check if task is immutable (args cannot be replaced)
179    pub fn is_immutable(&self) -> bool {
180        self.immutable
181    }
182
183    /// Check if task has a specific kwarg
184    pub fn has_kwarg(&self, key: &str) -> bool {
185        self.kwargs.contains_key(key)
186    }
187
188    /// Get a kwarg value
189    pub fn get_kwarg(&self, key: &str) -> Option<&serde_json::Value> {
190        self.kwargs.get(key)
191    }
192
193    /// Add a single kwarg
194    pub fn add_kwarg(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
195        self.kwargs.insert(key.into(), value);
196        self
197    }
198
199    /// Add a single argument
200    pub fn add_arg(mut self, arg: serde_json::Value) -> Self {
201        self.args.push(arg);
202        self
203    }
204
205    /// Clone the signature
206    pub fn clone_signature(&self) -> Self {
207        self.clone()
208    }
209
210    /// Create an immutable signature (shorthand for `.immutable()`)
211    ///
212    /// This is equivalent to Python Celery's `.si()` method.
213    /// Immutable signatures cannot have their arguments replaced when used in workflows.
214    ///
215    /// # Example
216    /// ```
217    /// use celers_canvas::Signature;
218    ///
219    /// let sig = Signature::new("process".to_string())
220    ///     .with_args(vec![serde_json::json!(1)])
221    ///     .si();
222    ///
223    /// assert!(sig.is_immutable());
224    /// ```
225    pub fn si(self) -> Self {
226        self.immutable()
227    }
228
229    /// Create a partial signature with some arguments pre-filled
230    ///
231    /// The partial signature can have additional arguments added later.
232    /// This is useful for creating task templates with some fixed arguments.
233    ///
234    /// # Example
235    /// ```
236    /// use celers_canvas::Signature;
237    ///
238    /// // Create a partial with first argument fixed
239    /// let partial = Signature::new("add".to_string())
240    ///     .partial(vec![serde_json::json!(10)]);
241    ///
242    /// // Complete with remaining arguments
243    /// let complete = partial.complete(vec![serde_json::json!(5)]);
244    /// assert_eq!(complete.args.len(), 2);
245    /// ```
246    pub fn partial(mut self, args: Vec<serde_json::Value>) -> Self {
247        self.args = args;
248        self
249    }
250
251    /// Complete a partial signature with additional arguments
252    ///
253    /// Appends the provided arguments to the existing arguments.
254    /// If the signature is immutable, returns the signature unchanged.
255    pub fn complete(mut self, additional_args: Vec<serde_json::Value>) -> Self {
256        if self.immutable {
257            return self;
258        }
259        self.args.extend(additional_args);
260        self
261    }
262
263    /// Merge another signature into this one
264    ///
265    /// This combines kwargs from both signatures (the other's kwargs take precedence)
266    /// and inherits options from the other signature if not already set.
267    pub fn merge(mut self, other: Signature) -> Self {
268        // Merge kwargs (other takes precedence)
269        for (key, value) in other.kwargs {
270            self.kwargs.insert(key, value);
271        }
272
273        // Inherit options if not already set
274        if self.options.priority.is_none() {
275            self.options.priority = other.options.priority;
276        }
277        if self.options.queue.is_none() {
278            self.options.queue = other.options.queue;
279        }
280        if self.options.task_id.is_none() {
281            self.options.task_id = other.options.task_id;
282        }
283        if self.options.link.is_none() {
284            self.options.link = other.options.link;
285        }
286        if self.options.link_error.is_none() {
287            self.options.link_error = other.options.link_error;
288        }
289
290        self
291    }
292
293    /// Replace arguments in signature (respects immutability)
294    ///
295    /// If the signature is immutable, returns None.
296    /// Otherwise, returns a new signature with replaced arguments.
297    pub fn replace_args(mut self, args: Vec<serde_json::Value>) -> Option<Self> {
298        if self.immutable {
299            return None;
300        }
301        self.args = args;
302        Some(self)
303    }
304
305    /// Set expiration time in seconds
306    pub fn with_expires(mut self, expires: u64) -> Self {
307        self.options.expires = Some(expires);
308        self
309    }
310
311    /// Set countdown (delay before execution) in seconds
312    pub fn with_countdown(mut self, countdown: u64) -> Self {
313        self.options.countdown = Some(countdown);
314        self
315    }
316
317    /// Set retry policy
318    pub fn with_retries(mut self, max_retries: u32) -> Self {
319        self.options.max_retries = Some(max_retries);
320        self
321    }
322
323    /// Set task routing key
324    pub fn with_routing_key(mut self, routing_key: String) -> Self {
325        self.options.routing_key = Some(routing_key);
326        self
327    }
328
329    /// Set callback argument passing mode
330    ///
331    /// Controls how task result is passed to linked callbacks.
332    ///
333    /// # Example
334    /// ```
335    /// use celers_canvas::{Signature, CallbackArgMode};
336    ///
337    /// let sig = Signature::new("task".to_string())
338    ///     .with_callback_arg_mode(CallbackArgMode::Append);
339    /// ```
340    pub fn with_callback_arg_mode(mut self, mode: CallbackArgMode) -> Self {
341        self.options.callback_arg_mode = mode;
342        self
343    }
344
345    /// Set callback kwarg key (used when CallbackArgMode::Kwarg)
346    ///
347    /// Specifies the keyword argument name for passing the result.
348    /// Defaults to "result" if not set.
349    pub fn with_callback_kwarg_key(mut self, key: impl Into<String>) -> Self {
350        self.options.callback_kwarg_key = Some(key.into());
351        self
352    }
353
354    /// Configure callback to receive result as keyword argument
355    ///
356    /// Shorthand for setting CallbackArgMode::Kwarg with a key.
357    pub fn with_result_as_kwarg(mut self, key: impl Into<String>) -> Self {
358        self.options.callback_arg_mode = CallbackArgMode::Kwarg;
359        self.options.callback_kwarg_key = Some(key.into());
360        self
361    }
362
363    /// Serialize signature to JSON string
364    pub fn to_json(&self) -> Result<String, serde_json::Error> {
365        serde_json::to_string(self)
366    }
367
368    /// Deserialize signature from JSON string
369    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
370        serde_json::from_str(json)
371    }
372
373    /// Serialize signature to JSON bytes
374    pub fn to_json_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
375        serde_json::to_vec(self)
376    }
377
378    /// Deserialize signature from JSON bytes
379    pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
380        serde_json::from_slice(bytes)
381    }
382
383    /// Clear all arguments from the signature
384    ///
385    /// Returns None if the signature is immutable.
386    ///
387    /// # Example
388    /// ```
389    /// use celers_canvas::Signature;
390    ///
391    /// let sig = Signature::new("task".to_string())
392    ///     .with_args(vec![serde_json::json!(1), serde_json::json!(2)]);
393    ///
394    /// let cleared = sig.clear_args().unwrap();
395    /// assert!(cleared.args.is_empty());
396    /// ```
397    pub fn clear_args(mut self) -> Option<Self> {
398        if self.immutable {
399            return None;
400        }
401        self.args.clear();
402        Some(self)
403    }
404
405    /// Clear all keyword arguments from the signature
406    ///
407    /// # Example
408    /// ```
409    /// use celers_canvas::Signature;
410    /// use std::collections::HashMap;
411    ///
412    /// let mut kwargs = HashMap::new();
413    /// kwargs.insert("key".to_string(), serde_json::json!("value"));
414    ///
415    /// let sig = Signature::new("task".to_string()).with_kwargs(kwargs);
416    /// let cleared = sig.clear_kwargs();
417    /// assert!(cleared.kwargs.is_empty());
418    /// ```
419    pub fn clear_kwargs(mut self) -> Self {
420        self.kwargs.clear();
421        self
422    }
423
424    /// Remove a specific keyword argument
425    ///
426    /// # Example
427    /// ```
428    /// use celers_canvas::Signature;
429    ///
430    /// let sig = Signature::new("task".to_string())
431    ///     .add_kwarg("key1", serde_json::json!("value1"))
432    ///     .add_kwarg("key2", serde_json::json!("value2"));
433    ///
434    /// let modified = sig.remove_kwarg("key1");
435    /// assert!(!modified.has_kwarg("key1"));
436    /// assert!(modified.has_kwarg("key2"));
437    /// ```
438    pub fn remove_kwarg(mut self, key: &str) -> Self {
439        self.kwargs.remove(key);
440        self
441    }
442
443    /// Get the number of positional arguments
444    ///
445    /// # Example
446    /// ```
447    /// use celers_canvas::Signature;
448    ///
449    /// let sig = Signature::new("task".to_string())
450    ///     .with_args(vec![serde_json::json!(1), serde_json::json!(2)]);
451    ///
452    /// assert_eq!(sig.args_count(), 2);
453    /// ```
454    pub fn args_count(&self) -> usize {
455        self.args.len()
456    }
457
458    /// Get the number of keyword arguments
459    ///
460    /// # Example
461    /// ```
462    /// use celers_canvas::Signature;
463    ///
464    /// let sig = Signature::new("task".to_string())
465    ///     .add_kwarg("key1", serde_json::json!("value1"))
466    ///     .add_kwarg("key2", serde_json::json!("value2"));
467    ///
468    /// assert_eq!(sig.kwargs_count(), 2);
469    /// ```
470    pub fn kwargs_count(&self) -> usize {
471        self.kwargs.len()
472    }
473
474    /// Get all keyword argument keys
475    ///
476    /// # Example
477    /// ```
478    /// use celers_canvas::Signature;
479    ///
480    /// let sig = Signature::new("task".to_string())
481    ///     .add_kwarg("key1", serde_json::json!("value1"))
482    ///     .add_kwarg("key2", serde_json::json!("value2"));
483    ///
484    /// let keys = sig.kwarg_keys();
485    /// assert_eq!(keys.len(), 2);
486    /// assert!(keys.contains(&"key1"));
487    /// assert!(keys.contains(&"key2"));
488    /// ```
489    pub fn kwarg_keys(&self) -> Vec<&str> {
490        self.kwargs.keys().map(|k| k.as_str()).collect()
491    }
492
493    /// Check if signature has any retry configuration
494    ///
495    /// # Example
496    /// ```
497    /// use celers_canvas::Signature;
498    ///
499    /// let sig1 = Signature::new("task".to_string()).with_retries(3);
500    /// let sig2 = Signature::new("task".to_string());
501    ///
502    /// assert!(sig1.has_retry_config());
503    /// assert!(!sig2.has_retry_config());
504    /// ```
505    pub fn has_retry_config(&self) -> bool {
506        self.options.max_retries.is_some()
507            || self.options.retry_delay.is_some()
508            || self.options.retry_backoff.is_some()
509    }
510
511    /// Check if signature has any time limit configuration
512    ///
513    /// # Example
514    /// ```
515    /// use celers_canvas::Signature;
516    ///
517    /// let sig1 = Signature::new("task".to_string()).with_time_limit(60);
518    /// let sig2 = Signature::new("task".to_string());
519    ///
520    /// assert!(sig1.has_time_limit_config());
521    /// assert!(!sig2.has_time_limit_config());
522    /// ```
523    pub fn has_time_limit_config(&self) -> bool {
524        self.options.time_limit.is_some() || self.options.soft_time_limit.is_some()
525    }
526
527    /// Create a new signature with the same task name but no arguments
528    ///
529    /// # Example
530    /// ```
531    /// use celers_canvas::Signature;
532    ///
533    /// let sig = Signature::new("task".to_string())
534    ///     .with_args(vec![serde_json::json!(1)])
535    ///     .with_priority(5);
536    ///
537    /// let clean = sig.clone_without_args();
538    /// assert_eq!(clean.task, "task");
539    /// assert!(clean.args.is_empty());
540    /// assert_eq!(clean.options.priority, Some(5)); // Options preserved
541    /// ```
542    pub fn clone_without_args(&self) -> Self {
543        Self {
544            task: self.task.clone(),
545            args: Vec::new(),
546            kwargs: HashMap::new(),
547            options: self.options.clone(),
548            immutable: self.immutable,
549        }
550    }
551
552    /// Calculate the estimated serialized size in bytes
553    ///
554    /// This gives a rough estimate of how much space the signature will take when serialized.
555    ///
556    /// # Example
557    /// ```
558    /// use celers_canvas::Signature;
559    ///
560    /// let sig = Signature::new("task".to_string())
561    ///     .with_args(vec![serde_json::json!(1), serde_json::json!(2)]);
562    ///
563    /// let size = sig.estimated_size();
564    /// assert!(size > 0);
565    /// ```
566    pub fn estimated_size(&self) -> usize {
567        self.to_json().map(|s| s.len()).unwrap_or(0)
568    }
569}
570
571impl std::fmt::Display for Signature {
572    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
573        write!(f, "Signature[task={}]", self.task)?;
574        if !self.args.is_empty() {
575            write!(f, " args={}", self.args.len())?;
576        }
577        if !self.kwargs.is_empty() {
578            write!(f, " kwargs={}", self.kwargs.len())?;
579        }
580        if self.immutable {
581            write!(f, " (immutable)")?;
582        }
583        Ok(())
584    }
585}
586
587/// Callback argument passing mode
588#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
589pub enum CallbackArgMode {
590    /// Pass result as first positional argument (default)
591    #[default]
592    Prepend,
593
594    /// Pass result as last positional argument
595    Append,
596
597    /// Pass result as a keyword argument with specified key
598    Kwarg,
599
600    /// Don't pass result to callback (callback uses its own args)
601    None,
602}
603
604impl CallbackArgMode {
605    /// Create a kwarg mode (result passed as "result" kwarg)
606    pub fn kwarg() -> Self {
607        Self::Kwarg
608    }
609
610    /// Check if this mode passes result to callback
611    pub fn passes_result(&self) -> bool {
612        !matches!(self, Self::None)
613    }
614}
615
616impl std::fmt::Display for CallbackArgMode {
617    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618        match self {
619            Self::Prepend => write!(f, "prepend"),
620            Self::Append => write!(f, "append"),
621            Self::Kwarg => write!(f, "kwarg"),
622            Self::None => write!(f, "none"),
623        }
624    }
625}
626
627/// Helper for serde skip_serializing_if
628fn is_default_callback_arg_mode(mode: &CallbackArgMode) -> bool {
629    *mode == CallbackArgMode::Prepend
630}
631
632/// Task options
633#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
634pub struct TaskOptions {
635    /// Task priority (0-9)
636    pub priority: Option<u8>,
637
638    /// Queue name
639    pub queue: Option<String>,
640
641    /// Task ID (for tracking)
642    pub task_id: Option<Uuid>,
643
644    /// Link (callback on success) - single callback for backwards compat
645    pub link: Option<Box<Signature>>,
646
647    /// Link error (callback on failure) - single callback for backwards compat
648    pub link_error: Option<Box<Signature>>,
649
650    /// Multiple success callbacks (executed in order)
651    #[serde(default, skip_serializing_if = "Vec::is_empty")]
652    pub links: Vec<Signature>,
653
654    /// Multiple failure callbacks (executed in order)
655    #[serde(default, skip_serializing_if = "Vec::is_empty")]
656    pub link_errors: Vec<Signature>,
657
658    /// Callback on retry
659    #[serde(skip_serializing_if = "Option::is_none")]
660    pub on_retry: Option<Box<Signature>>,
661
662    /// How to pass result to success callbacks
663    #[serde(default, skip_serializing_if = "is_default_callback_arg_mode")]
664    pub callback_arg_mode: CallbackArgMode,
665
666    /// Key name when using CallbackArgMode::Kwarg
667    #[serde(skip_serializing_if = "Option::is_none")]
668    pub callback_kwarg_key: Option<String>,
669
670    /// Task expiration time in seconds
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub expires: Option<u64>,
673
674    /// Countdown (delay before execution) in seconds
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub countdown: Option<u64>,
677
678    /// Maximum number of retries
679    #[serde(skip_serializing_if = "Option::is_none")]
680    pub max_retries: Option<u32>,
681
682    /// Routing key for task distribution
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub routing_key: Option<String>,
685
686    /// Soft time limit in seconds (warning before kill)
687    #[serde(skip_serializing_if = "Option::is_none")]
688    pub soft_time_limit: Option<u64>,
689
690    /// Hard time limit in seconds (force kill)
691    #[serde(skip_serializing_if = "Option::is_none")]
692    pub time_limit: Option<u64>,
693
694    /// Retry delay in seconds
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub retry_delay: Option<u64>,
697
698    /// Retry backoff factor (exponential backoff multiplier)
699    #[serde(skip_serializing_if = "Option::is_none")]
700    pub retry_backoff: Option<f64>,
701
702    /// Maximum retry delay in seconds
703    #[serde(skip_serializing_if = "Option::is_none")]
704    pub retry_backoff_max: Option<u64>,
705
706    /// Whether to add jitter to retry delays
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub retry_jitter: Option<bool>,
709}
710
711impl TaskOptions {
712    /// Check if priority is set
713    pub fn has_priority(&self) -> bool {
714        self.priority.is_some()
715    }
716
717    /// Check if queue is set
718    pub fn has_queue(&self) -> bool {
719        self.queue.is_some()
720    }
721
722    /// Check if task ID is set
723    pub fn has_task_id(&self) -> bool {
724        self.task_id.is_some()
725    }
726
727    /// Check if any link (success callback) is set
728    pub fn has_link(&self) -> bool {
729        self.link.is_some() || !self.links.is_empty()
730    }
731
732    /// Check if any link_error (failure callback) is set
733    pub fn has_link_error(&self) -> bool {
734        self.link_error.is_some() || !self.link_errors.is_empty()
735    }
736
737    /// Check if on_retry callback is set
738    pub fn has_on_retry(&self) -> bool {
739        self.on_retry.is_some()
740    }
741
742    /// Check if expires is set
743    pub fn has_expires(&self) -> bool {
744        self.expires.is_some()
745    }
746
747    /// Check if countdown is set
748    pub fn has_countdown(&self) -> bool {
749        self.countdown.is_some()
750    }
751
752    /// Check if max_retries is set
753    pub fn has_max_retries(&self) -> bool {
754        self.max_retries.is_some()
755    }
756
757    /// Check if routing_key is set
758    pub fn has_routing_key(&self) -> bool {
759        self.routing_key.is_some()
760    }
761
762    /// Check if soft time limit is set
763    pub fn has_soft_time_limit(&self) -> bool {
764        self.soft_time_limit.is_some()
765    }
766
767    /// Check if hard time limit is set
768    pub fn has_time_limit(&self) -> bool {
769        self.time_limit.is_some()
770    }
771
772    /// Get all success callbacks (both single link and multiple links)
773    pub fn all_links(&self) -> Vec<&Signature> {
774        let mut result = Vec::new();
775        if let Some(ref link) = self.link {
776            result.push(link.as_ref());
777        }
778        for link in &self.links {
779            result.push(link);
780        }
781        result
782    }
783
784    /// Get all error callbacks (both single link_error and multiple link_errors)
785    pub fn all_link_errors(&self) -> Vec<&Signature> {
786        let mut result = Vec::new();
787        if let Some(ref link_error) = self.link_error {
788            result.push(link_error.as_ref());
789        }
790        for link_error in &self.link_errors {
791            result.push(link_error);
792        }
793        result
794    }
795
796    /// Calculate retry delay with backoff
797    pub fn calculate_retry_delay(&self, retry_count: u32) -> u64 {
798        let base_delay = self.retry_delay.unwrap_or(1);
799        let backoff = self.retry_backoff.unwrap_or(2.0);
800        let max_delay = self.retry_backoff_max.unwrap_or(3600);
801
802        let delay = (base_delay as f64 * backoff.powi(retry_count as i32)) as u64;
803        delay.min(max_delay)
804    }
805
806    /// Get the callback argument mode
807    pub fn callback_arg_mode(&self) -> CallbackArgMode {
808        self.callback_arg_mode
809    }
810
811    /// Get the callback kwarg key (defaults to "result")
812    pub fn callback_kwarg_key(&self) -> &str {
813        self.callback_kwarg_key.as_deref().unwrap_or("result")
814    }
815
816    /// Prepare a callback signature with result passed according to callback_arg_mode
817    ///
818    /// This modifies the callback signature to include the result value
819    /// according to the configured callback argument passing mode.
820    ///
821    /// # Arguments
822    /// * `callback` - The callback signature to prepare
823    /// * `result` - The result value to pass to the callback
824    ///
825    /// # Returns
826    /// A new signature with the result incorporated
827    pub fn prepare_callback(
828        &self,
829        mut callback: Signature,
830        result: serde_json::Value,
831    ) -> Signature {
832        // Don't modify immutable signatures
833        if callback.immutable {
834            return callback;
835        }
836
837        match self.callback_arg_mode {
838            CallbackArgMode::Prepend => {
839                callback.args.insert(0, result);
840            }
841            CallbackArgMode::Append => {
842                callback.args.push(result);
843            }
844            CallbackArgMode::Kwarg => {
845                let key = self.callback_kwarg_key().to_string();
846                callback.kwargs.insert(key, result);
847            }
848            CallbackArgMode::None => {
849                // Don't modify the callback
850            }
851        }
852
853        callback
854    }
855}
856
857impl std::fmt::Display for TaskOptions {
858    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
859        let mut parts = Vec::new();
860        if let Some(priority) = self.priority {
861            parts.push(format!("priority={}", priority));
862        }
863        if let Some(ref queue) = self.queue {
864            parts.push(format!("queue={}", queue));
865        }
866        if let Some(task_id) = self.task_id {
867            parts.push(format!("task_id={}", &task_id.to_string()[..8]));
868        }
869        if self.link.is_some() {
870            parts.push("link=yes".to_string());
871        }
872        if self.link_error.is_some() {
873            parts.push("link_error=yes".to_string());
874        }
875        if let Some(expires) = self.expires {
876            parts.push(format!("expires={}s", expires));
877        }
878        if let Some(countdown) = self.countdown {
879            parts.push(format!("countdown={}s", countdown));
880        }
881        if let Some(max_retries) = self.max_retries {
882            parts.push(format!("retries={}", max_retries));
883        }
884        if let Some(ref routing_key) = self.routing_key {
885            parts.push(format!("routing={}", routing_key));
886        }
887        if parts.is_empty() {
888            write!(f, "TaskOptions[default]")
889        } else {
890            write!(f, "TaskOptions[{}]", parts.join(", "))
891        }
892    }
893}
894
895/// Chain: Sequential execution
896///
897/// task1(args1) -> task2(result1) -> task3(result2)
898#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
899pub struct Chain {
900    /// Tasks in the chain
901    pub tasks: Vec<Signature>,
902}
903
904impl Chain {
905    pub fn new() -> Self {
906        Self { tasks: Vec::new() }
907    }
908
909    pub fn then(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
910        self.tasks
911            .push(Signature::new(task.to_string()).with_args(args));
912        self
913    }
914
915    pub fn then_signature(mut self, signature: Signature) -> Self {
916        self.tasks.push(signature);
917        self
918    }
919
920    /// Apply the chain by enqueuing the first task with links to subsequent tasks
921    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
922        if self.tasks.is_empty() {
923            return Err(CanvasError::Invalid("Chain cannot be empty".to_string()));
924        }
925
926        // Build chain backwards: last task -> second-to-last -> ... -> first
927        let mut chain_iter = self.tasks.into_iter().rev();
928        let mut next_sig: Option<Signature> = None;
929
930        // Start from the last task (no link)
931        if let Some(last_task) = chain_iter.next() {
932            // Last task has no link
933            next_sig = Some(last_task);
934
935            // Link remaining tasks backwards
936            for mut task in chain_iter {
937                task.options.link = next_sig.map(Box::new);
938                next_sig = Some(task);
939            }
940        }
941
942        // Enqueue the first task (which is now in next_sig)
943        if let Some(first_sig) = next_sig {
944            let task_id = Self::enqueue_signature(broker, &first_sig).await?;
945            Ok(task_id)
946        } else {
947            Err(CanvasError::Invalid("Failed to build chain".to_string()))
948        }
949    }
950
951    async fn enqueue_signature<B: Broker>(
952        broker: &B,
953        sig: &Signature,
954    ) -> Result<Uuid, CanvasError> {
955        let args_json = serde_json::json!({
956            "args": sig.args,
957            "kwargs": sig.kwargs
958        });
959        let args_bytes = serde_json::to_vec(&args_json)
960            .map_err(|e| CanvasError::Serialization(e.to_string()))?;
961
962        let mut task = SerializedTask::new(sig.task.clone(), args_bytes);
963
964        if let Some(priority) = sig.options.priority {
965            task = task.with_priority(priority.into());
966        }
967
968        let task_id = task.metadata.id;
969        broker
970            .enqueue(task)
971            .await
972            .map_err(|e| CanvasError::Broker(e.to_string()))?;
973
974        Ok(task_id)
975    }
976}
977
978impl Default for Chain {
979    fn default() -> Self {
980        Self::new()
981    }
982}
983
984impl Chain {
985    /// Check if chain is empty
986    pub fn is_empty(&self) -> bool {
987        self.tasks.is_empty()
988    }
989
990    /// Get number of tasks in chain
991    pub fn len(&self) -> usize {
992        self.tasks.len()
993    }
994
995    /// Get the first task in the chain
996    pub fn first(&self) -> Option<&Signature> {
997        self.tasks.first()
998    }
999
1000    /// Get the last task in the chain
1001    pub fn last(&self) -> Option<&Signature> {
1002        self.tasks.last()
1003    }
1004
1005    /// Get an iterator over the tasks
1006    pub fn iter(&self) -> std::slice::Iter<'_, Signature> {
1007        self.tasks.iter()
1008    }
1009
1010    /// Get a mutable iterator over the tasks
1011    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Signature> {
1012        self.tasks.iter_mut()
1013    }
1014
1015    /// Get a task by index
1016    pub fn get(&self, index: usize) -> Option<&Signature> {
1017        self.tasks.get(index)
1018    }
1019
1020    /// Get a mutable task by index
1021    pub fn get_mut(&mut self, index: usize) -> Option<&mut Signature> {
1022        self.tasks.get_mut(index)
1023    }
1024
1025    /// Create a chain with pre-allocated capacity
1026    pub fn with_capacity(capacity: usize) -> Self {
1027        Self {
1028            tasks: Vec::with_capacity(capacity),
1029        }
1030    }
1031
1032    /// Extend the chain with additional tasks
1033    pub fn extend(mut self, tasks: impl IntoIterator<Item = Signature>) -> Self {
1034        self.tasks.extend(tasks);
1035        self
1036    }
1037
1038    /// Reverse the order of tasks in the chain
1039    pub fn reverse(mut self) -> Self {
1040        self.tasks.reverse();
1041        self
1042    }
1043
1044    /// Retain only tasks that satisfy the predicate
1045    pub fn retain<F>(mut self, f: F) -> Self
1046    where
1047        F: FnMut(&Signature) -> bool,
1048    {
1049        self.tasks.retain(f);
1050        self
1051    }
1052
1053    /// Apply the chain with a countdown (delay in seconds)
1054    ///
1055    /// The first task will be delayed by the countdown amount.
1056    /// Subsequent tasks are linked and will execute after the previous completes.
1057    ///
1058    /// # Example
1059    /// ```ignore
1060    /// let chain = Chain::new()
1061    ///     .then("task1", vec![])
1062    ///     .then("task2", vec![]);
1063    ///
1064    /// // Start chain execution after 60 seconds
1065    /// chain.apply_with_countdown(broker, 60).await?;
1066    /// ```
1067    pub async fn apply_with_countdown<B: Broker>(
1068        mut self,
1069        broker: &B,
1070        countdown: u64,
1071    ) -> Result<Uuid, CanvasError> {
1072        if self.tasks.is_empty() {
1073            return Err(CanvasError::Invalid("Chain cannot be empty".to_string()));
1074        }
1075
1076        // Set countdown on the first task
1077        if let Some(first) = self.tasks.first_mut() {
1078            first.options.countdown = Some(countdown);
1079        }
1080
1081        // Use regular apply to handle the chain
1082        self.apply(broker).await
1083    }
1084
1085    /// Apply the chain with an ETA (execution time as Unix timestamp)
1086    ///
1087    /// The first task will be scheduled for execution at the specified ETA.
1088    /// Subsequent tasks are linked and will execute after the previous completes.
1089    ///
1090    /// # Example
1091    /// ```ignore
1092    /// use std::time::{SystemTime, UNIX_EPOCH, Duration};
1093    ///
1094    /// let chain = Chain::new()
1095    ///     .then("task1", vec![])
1096    ///     .then("task2", vec![]);
1097    ///
1098    /// // Schedule chain for 1 hour from now
1099    /// let eta = SystemTime::now()
1100    ///     .duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600;
1101    /// chain.apply_with_eta(broker, eta).await?;
1102    /// ```
1103    pub async fn apply_with_eta<B: Broker>(
1104        mut self,
1105        broker: &B,
1106        eta: u64,
1107    ) -> Result<Uuid, CanvasError> {
1108        if self.tasks.is_empty() {
1109            return Err(CanvasError::Invalid("Chain cannot be empty".to_string()));
1110        }
1111
1112        // Calculate countdown from ETA
1113        let now = std::time::SystemTime::now()
1114            .duration_since(std::time::UNIX_EPOCH)
1115            .unwrap_or_default()
1116            .as_secs();
1117
1118        let countdown = eta.saturating_sub(now);
1119
1120        // Set countdown on the first task
1121        if let Some(first) = self.tasks.first_mut() {
1122            first.options.countdown = Some(countdown);
1123        }
1124
1125        self.apply(broker).await
1126    }
1127
1128    /// Set countdown on all tasks in the chain (staggered execution)
1129    ///
1130    /// Each task gets a progressively larger countdown.
1131    ///
1132    /// # Arguments
1133    /// * `start` - Initial countdown for first task
1134    /// * `step` - Additional delay added for each subsequent task
1135    pub fn with_staggered_countdown(mut self, start: u64, step: u64) -> Self {
1136        let mut countdown = start;
1137        for task in &mut self.tasks {
1138            task.options.countdown = Some(countdown);
1139            countdown += step;
1140        }
1141        self
1142    }
1143
1144    /// Append another chain to this chain
1145    ///
1146    /// # Example
1147    /// ```
1148    /// use celers_canvas::{Chain, Signature};
1149    ///
1150    /// let chain1 = Chain::new()
1151    ///     .then("task1", vec![])
1152    ///     .then("task2", vec![]);
1153    ///
1154    /// let chain2 = Chain::new()
1155    ///     .then("task3", vec![])
1156    ///     .then("task4", vec![]);
1157    ///
1158    /// let combined = chain1.append(chain2);
1159    /// assert_eq!(combined.len(), 4);
1160    /// ```
1161    pub fn append(mut self, other: Chain) -> Self {
1162        self.tasks.extend(other.tasks);
1163        self
1164    }
1165
1166    /// Prepend another chain to this chain
1167    ///
1168    /// # Example
1169    /// ```
1170    /// use celers_canvas::{Chain, Signature};
1171    ///
1172    /// let chain1 = Chain::new()
1173    ///     .then("task1", vec![])
1174    ///     .then("task2", vec![]);
1175    ///
1176    /// let chain2 = Chain::new()
1177    ///     .then("task3", vec![])
1178    ///     .then("task4", vec![]);
1179    ///
1180    /// let combined = chain1.prepend(chain2);
1181    /// assert_eq!(combined.len(), 4);
1182    /// assert_eq!(combined.first().unwrap().task, "task3");
1183    /// ```
1184    pub fn prepend(mut self, other: Chain) -> Self {
1185        let mut new_tasks = other.tasks;
1186        new_tasks.extend(self.tasks);
1187        self.tasks = new_tasks;
1188        self
1189    }
1190
1191    /// Split chain at the specified index
1192    ///
1193    /// Returns a tuple of (before, after) chains.
1194    /// The task at `index` will be the first task in the second chain.
1195    ///
1196    /// # Example
1197    /// ```
1198    /// use celers_canvas::Chain;
1199    ///
1200    /// let chain = Chain::new()
1201    ///     .then("task1", vec![])
1202    ///     .then("task2", vec![])
1203    ///     .then("task3", vec![])
1204    ///     .then("task4", vec![]);
1205    ///
1206    /// let (before, after) = chain.split_at(2);
1207    /// assert_eq!(before.len(), 2);
1208    /// assert_eq!(after.len(), 2);
1209    /// ```
1210    pub fn split_at(self, index: usize) -> (Chain, Chain) {
1211        let (before, after) = self.tasks.split_at(index.min(self.tasks.len()));
1212        (
1213            Chain {
1214                tasks: before.to_vec(),
1215            },
1216            Chain {
1217                tasks: after.to_vec(),
1218            },
1219        )
1220    }
1221
1222    /// Concatenate multiple chains into a single chain
1223    ///
1224    /// # Example
1225    /// ```
1226    /// use celers_canvas::Chain;
1227    ///
1228    /// let chains = vec![
1229    ///     Chain::new().then("task1", vec![]),
1230    ///     Chain::new().then("task2", vec![]),
1231    ///     Chain::new().then("task3", vec![]),
1232    /// ];
1233    ///
1234    /// let combined = Chain::concat(chains);
1235    /// assert_eq!(combined.len(), 3);
1236    /// ```
1237    pub fn concat<I>(chains: I) -> Self
1238    where
1239        I: IntoIterator<Item = Chain>,
1240    {
1241        let mut result = Chain::new();
1242        for chain in chains {
1243            result.tasks.extend(chain.tasks);
1244        }
1245        result
1246    }
1247
1248    /// Clone all tasks in the chain with a new task name prefix
1249    ///
1250    /// Useful for creating workflow variants.
1251    ///
1252    /// # Example
1253    /// ```
1254    /// use celers_canvas::Chain;
1255    ///
1256    /// let chain = Chain::new()
1257    ///     .then("process", vec![])
1258    ///     .then("validate", vec![]);
1259    ///
1260    /// let prefixed = chain.with_task_prefix("batch_");
1261    /// assert_eq!(prefixed.first().unwrap().task, "batch_process");
1262    /// ```
1263    pub fn with_task_prefix(mut self, prefix: &str) -> Self {
1264        for task in &mut self.tasks {
1265            task.task = format!("{}{}", prefix, task.task);
1266        }
1267        self
1268    }
1269
1270    /// Clone all tasks in the chain with a new task name suffix
1271    ///
1272    /// # Example
1273    /// ```
1274    /// use celers_canvas::Chain;
1275    ///
1276    /// let chain = Chain::new()
1277    ///     .then("process", vec![])
1278    ///     .then("validate", vec![]);
1279    ///
1280    /// let suffixed = chain.with_task_suffix("_v2");
1281    /// assert_eq!(suffixed.first().unwrap().task, "process_v2");
1282    /// ```
1283    pub fn with_task_suffix(mut self, suffix: &str) -> Self {
1284        for task in &mut self.tasks {
1285            task.task = format!("{}{}", task.task, suffix);
1286        }
1287        self
1288    }
1289
1290    /// Validate that all tasks in the chain have non-empty names
1291    ///
1292    /// Returns true if all tasks are valid, false otherwise.
1293    ///
1294    /// # Example
1295    /// ```
1296    /// use celers_canvas::Chain;
1297    ///
1298    /// let valid = Chain::new()
1299    ///     .then("task1", vec![])
1300    ///     .then("task2", vec![]);
1301    /// assert!(valid.is_valid());
1302    ///
1303    /// let invalid = Chain { tasks: vec![] };
1304    /// assert!(!invalid.is_valid());
1305    /// ```
1306    pub fn is_valid(&self) -> bool {
1307        !self.tasks.is_empty() && self.tasks.iter().all(|t| !t.task.is_empty())
1308    }
1309
1310    /// Count tasks that match a predicate
1311    ///
1312    /// # Example
1313    /// ```
1314    /// use celers_canvas::{Chain, Signature};
1315    ///
1316    /// let chain = Chain::new()
1317    ///     .then_signature(Signature::new("high".to_string()).with_priority(9))
1318    ///     .then_signature(Signature::new("low".to_string()).with_priority(1))
1319    ///     .then_signature(Signature::new("urgent".to_string()).with_priority(9));
1320    ///
1321    /// let high_priority = chain.count_matching(|sig| sig.options.priority.unwrap_or(0) >= 9);
1322    /// assert_eq!(high_priority, 2);
1323    /// ```
1324    pub fn count_matching<F>(&self, predicate: F) -> usize
1325    where
1326        F: Fn(&Signature) -> bool,
1327    {
1328        self.tasks.iter().filter(|t| predicate(t)).count()
1329    }
1330
1331    /// Check if any task matches a predicate
1332    ///
1333    /// # Example
1334    /// ```
1335    /// use celers_canvas::Chain;
1336    ///
1337    /// let chain = Chain::new()
1338    ///     .then("process", vec![])
1339    ///     .then("validate", vec![]);
1340    ///
1341    /// assert!(chain.any(|sig| sig.task == "validate"));
1342    /// assert!(!chain.any(|sig| sig.task == "missing"));
1343    /// ```
1344    pub fn any<F>(&self, predicate: F) -> bool
1345    where
1346        F: Fn(&Signature) -> bool,
1347    {
1348        self.tasks.iter().any(predicate)
1349    }
1350
1351    /// Check if all tasks match a predicate
1352    ///
1353    /// # Example
1354    /// ```
1355    /// use celers_canvas::Chain;
1356    ///
1357    /// let chain = Chain::new()
1358    ///     .then("process", vec![])
1359    ///     .then("validate", vec![]);
1360    ///
1361    /// assert!(chain.all(|sig| !sig.task.is_empty()));
1362    /// ```
1363    pub fn all<F>(&self, predicate: F) -> bool
1364    where
1365        F: Fn(&Signature) -> bool,
1366    {
1367        self.tasks.iter().all(predicate)
1368    }
1369
1370    /// Map over all tasks, transforming each signature
1371    ///
1372    /// # Example
1373    /// ```
1374    /// use celers_canvas::{Chain, Signature};
1375    ///
1376    /// let chain = Chain::new()
1377    ///     .then("task1", vec![])
1378    ///     .then("task2", vec![]);
1379    ///
1380    /// let modified = chain.map_tasks(|sig| {
1381    ///     Signature::new(format!("modified_{}", sig.task))
1382    /// });
1383    ///
1384    /// assert_eq!(modified.first().unwrap().task, "modified_task1");
1385    /// ```
1386    pub fn map_tasks<F>(mut self, f: F) -> Self
1387    where
1388        F: FnMut(Signature) -> Signature,
1389    {
1390        self.tasks = self.tasks.into_iter().map(f).collect();
1391        self
1392    }
1393
1394    /// Filter and map tasks in one operation
1395    ///
1396    /// # Example
1397    /// ```
1398    /// use celers_canvas::{Chain, Signature};
1399    ///
1400    /// let chain = Chain::new()
1401    ///     .then_signature(Signature::new("high".to_string()).with_priority(9))
1402    ///     .then_signature(Signature::new("low".to_string()).with_priority(1))
1403    ///     .then_signature(Signature::new("urgent".to_string()).with_priority(9));
1404    ///
1405    /// let high_priority = chain.filter_map(|sig| {
1406    ///     if sig.options.priority.unwrap_or(0) >= 9 {
1407    ///         Some(sig)
1408    ///     } else {
1409    ///         None
1410    ///     }
1411    /// });
1412    ///
1413    /// assert_eq!(high_priority.len(), 2);
1414    /// ```
1415    pub fn filter_map<F>(mut self, f: F) -> Self
1416    where
1417        F: FnMut(Signature) -> Option<Signature>,
1418    {
1419        self.tasks = self.tasks.into_iter().filter_map(f).collect();
1420        self
1421    }
1422
1423    /// Take the first n tasks from the chain
1424    ///
1425    /// # Example
1426    /// ```
1427    /// use celers_canvas::Chain;
1428    ///
1429    /// let chain = Chain::new()
1430    ///     .then("task1", vec![])
1431    ///     .then("task2", vec![])
1432    ///     .then("task3", vec![])
1433    ///     .then("task4", vec![]);
1434    ///
1435    /// let first_two = chain.take(2);
1436    /// assert_eq!(first_two.len(), 2);
1437    /// ```
1438    pub fn take(mut self, n: usize) -> Self {
1439        self.tasks.truncate(n);
1440        self
1441    }
1442
1443    /// Skip the first n tasks from the chain
1444    ///
1445    /// # Example
1446    /// ```
1447    /// use celers_canvas::Chain;
1448    ///
1449    /// let chain = Chain::new()
1450    ///     .then("task1", vec![])
1451    ///     .then("task2", vec![])
1452    ///     .then("task3", vec![])
1453    ///     .then("task4", vec![]);
1454    ///
1455    /// let skipped = chain.skip(2);
1456    /// assert_eq!(skipped.len(), 2);
1457    /// assert_eq!(skipped.first().unwrap().task, "task3");
1458    /// ```
1459    pub fn skip(mut self, n: usize) -> Self {
1460        self.tasks = self.tasks.into_iter().skip(n).collect();
1461        self
1462    }
1463
1464    /// Find the index of the first task with the given name
1465    ///
1466    /// # Example
1467    /// ```
1468    /// use celers_canvas::Chain;
1469    ///
1470    /// let chain = Chain::new()
1471    ///     .then("task1", vec![])
1472    ///     .then("task2", vec![])
1473    ///     .then("task1", vec![]);
1474    ///
1475    /// assert_eq!(chain.find_task("task1"), Some(0));
1476    /// assert_eq!(chain.find_task("task2"), Some(1));
1477    /// assert_eq!(chain.find_task("task3"), None);
1478    /// ```
1479    pub fn find_task(&self, task_name: &str) -> Option<usize> {
1480        self.tasks.iter().position(|t| t.task == task_name)
1481    }
1482
1483    /// Find all indices of tasks with the given name
1484    ///
1485    /// # Example
1486    /// ```
1487    /// use celers_canvas::Chain;
1488    ///
1489    /// let chain = Chain::new()
1490    ///     .then("task1", vec![])
1491    ///     .then("task2", vec![])
1492    ///     .then("task1", vec![]);
1493    ///
1494    /// assert_eq!(chain.find_all_tasks("task1"), vec![0, 2]);
1495    /// assert_eq!(chain.find_all_tasks("task2"), vec![1]);
1496    /// ```
1497    pub fn find_all_tasks(&self, task_name: &str) -> Vec<usize> {
1498        self.tasks
1499            .iter()
1500            .enumerate()
1501            .filter(|(_, t)| t.task == task_name)
1502            .map(|(i, _)| i)
1503            .collect()
1504    }
1505
1506    /// Check if the chain contains a task with the given name
1507    ///
1508    /// # Example
1509    /// ```
1510    /// use celers_canvas::Chain;
1511    ///
1512    /// let chain = Chain::new()
1513    ///     .then("task1", vec![])
1514    ///     .then("task2", vec![]);
1515    ///
1516    /// assert!(chain.contains_task("task1"));
1517    /// assert!(!chain.contains_task("task3"));
1518    /// ```
1519    pub fn contains_task(&self, task_name: &str) -> bool {
1520        self.tasks.iter().any(|t| t.task == task_name)
1521    }
1522
1523    /// Get the total estimated duration in seconds based on task time limits
1524    ///
1525    /// This sums up all task time limits (or soft_time_limit if time_limit is not set).
1526    /// Returns None if no tasks have time limits set.
1527    ///
1528    /// # Example
1529    /// ```
1530    /// use celers_canvas::{Chain, Signature};
1531    ///
1532    /// let chain = Chain::new()
1533    ///     .then_signature(Signature::new("task1".to_string()).with_time_limit(10))
1534    ///     .then_signature(Signature::new("task2".to_string()).with_time_limit(20));
1535    ///
1536    /// assert_eq!(chain.estimated_duration(), Some(30));
1537    /// ```
1538    pub fn estimated_duration(&self) -> Option<u64> {
1539        let mut total = 0u64;
1540        let mut found_any = false;
1541
1542        for task in &self.tasks {
1543            if let Some(limit) = task.options.time_limit.or(task.options.soft_time_limit) {
1544                total += limit;
1545                found_any = true;
1546            }
1547        }
1548
1549        if found_any {
1550            Some(total)
1551        } else {
1552            None
1553        }
1554    }
1555
1556    /// Get a summary of all task names in the chain
1557    ///
1558    /// # Example
1559    /// ```
1560    /// use celers_canvas::Chain;
1561    ///
1562    /// let chain = Chain::new()
1563    ///     .then("fetch", vec![])
1564    ///     .then("process", vec![])
1565    ///     .then("save", vec![]);
1566    ///
1567    /// assert_eq!(chain.task_names(), vec!["fetch", "process", "save"]);
1568    /// ```
1569    pub fn task_names(&self) -> Vec<&str> {
1570        self.tasks.iter().map(|t| t.task.as_str()).collect()
1571    }
1572
1573    /// Get all unique task names in the chain
1574    ///
1575    /// # Example
1576    /// ```
1577    /// use celers_canvas::Chain;
1578    ///
1579    /// let chain = Chain::new()
1580    ///     .then("task1", vec![])
1581    ///     .then("task2", vec![])
1582    ///     .then("task1", vec![]);
1583    ///
1584    /// let unique = chain.unique_task_names();
1585    /// assert_eq!(unique.len(), 2);
1586    /// assert!(unique.contains(&"task1"));
1587    /// assert!(unique.contains(&"task2"));
1588    /// ```
1589    pub fn unique_task_names(&self) -> std::collections::HashSet<&str> {
1590        self.tasks.iter().map(|t| t.task.as_str()).collect()
1591    }
1592
1593    /// Clone the chain with a transformation applied to each task
1594    ///
1595    /// # Example
1596    /// ```
1597    /// use celers_canvas::Chain;
1598    ///
1599    /// let chain = Chain::new()
1600    ///     .then("task1", vec![])
1601    ///     .then("task2", vec![]);
1602    ///
1603    /// let prioritized = chain.clone_with_transform(|sig| {
1604    ///     sig.clone().with_priority(5)
1605    /// });
1606    ///
1607    /// assert!(prioritized.tasks.iter().all(|t| t.options.priority == Some(5)));
1608    /// ```
1609    pub fn clone_with_transform<F>(&self, mut transform: F) -> Self
1610    where
1611        F: FnMut(&Signature) -> Signature,
1612    {
1613        Self {
1614            tasks: self.tasks.iter().map(&mut transform).collect(),
1615        }
1616    }
1617}
1618
1619impl std::fmt::Display for Chain {
1620    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1621        write!(f, "Chain[{} tasks]", self.tasks.len())?;
1622        if !self.tasks.is_empty() {
1623            write!(
1624                f,
1625                " {} -> ... -> {}",
1626                self.tasks.first().unwrap().task,
1627                self.tasks.last().unwrap().task
1628            )?;
1629        }
1630        Ok(())
1631    }
1632}
1633
1634impl IntoIterator for Chain {
1635    type Item = Signature;
1636    type IntoIter = std::vec::IntoIter<Signature>;
1637
1638    fn into_iter(self) -> Self::IntoIter {
1639        self.tasks.into_iter()
1640    }
1641}
1642
1643impl<'a> IntoIterator for &'a Chain {
1644    type Item = &'a Signature;
1645    type IntoIter = std::slice::Iter<'a, Signature>;
1646
1647    fn into_iter(self) -> Self::IntoIter {
1648        self.tasks.iter()
1649    }
1650}
1651
1652impl From<Vec<Signature>> for Chain {
1653    fn from(tasks: Vec<Signature>) -> Self {
1654        Self { tasks }
1655    }
1656}
1657
1658impl FromIterator<Signature> for Chain {
1659    fn from_iter<T: IntoIterator<Item = Signature>>(iter: T) -> Self {
1660        Self {
1661            tasks: iter.into_iter().collect(),
1662        }
1663    }
1664}
1665
1666/// Group: Parallel execution
1667///
1668/// (task1 | task2 | task3)
1669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1670pub struct Group {
1671    /// Tasks in the group
1672    pub tasks: Vec<Signature>,
1673
1674    /// Group ID
1675    pub group_id: Option<Uuid>,
1676}
1677
1678impl Group {
1679    pub fn new() -> Self {
1680        Self {
1681            tasks: Vec::new(),
1682            group_id: Some(Uuid::new_v4()),
1683        }
1684    }
1685
1686    pub fn add(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
1687        self.tasks
1688            .push(Signature::new(task.to_string()).with_args(args));
1689        self
1690    }
1691
1692    pub fn add_signature(mut self, signature: Signature) -> Self {
1693        self.tasks.push(signature);
1694        self
1695    }
1696
1697    /// Apply the group by enqueuing all tasks to the broker
1698    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
1699        if self.tasks.is_empty() {
1700            return Err(CanvasError::Invalid("Group cannot be empty".to_string()));
1701        }
1702
1703        let group_id = self.group_id.unwrap_or_else(Uuid::new_v4);
1704
1705        // Enqueue all tasks in parallel
1706        for sig in self.tasks {
1707            // Convert signature to SerializedTask
1708            let args_json = serde_json::json!({
1709                "args": sig.args,
1710                "kwargs": sig.kwargs
1711            });
1712            let args_bytes = serde_json::to_vec(&args_json)
1713                .map_err(|e| CanvasError::Serialization(e.to_string()))?;
1714
1715            let mut task = SerializedTask::new(sig.task.clone(), args_bytes);
1716
1717            // Set priority if specified
1718            if let Some(priority) = sig.options.priority {
1719                task = task.with_priority(priority.into());
1720            }
1721
1722            // Set group_id in metadata (for tracking)
1723            task.metadata.group_id = Some(group_id);
1724
1725            // Enqueue the task
1726            broker
1727                .enqueue(task)
1728                .await
1729                .map_err(|e| CanvasError::Broker(e.to_string()))?;
1730        }
1731
1732        Ok(group_id)
1733    }
1734}
1735
1736impl Default for Group {
1737    fn default() -> Self {
1738        Self::new()
1739    }
1740}
1741
1742impl Group {
1743    /// Check if group is empty
1744    pub fn is_empty(&self) -> bool {
1745        self.tasks.is_empty()
1746    }
1747
1748    /// Get the first task in the group
1749    pub fn first(&self) -> Option<&Signature> {
1750        self.tasks.first()
1751    }
1752
1753    /// Get the last task in the group
1754    pub fn last(&self) -> Option<&Signature> {
1755        self.tasks.last()
1756    }
1757
1758    /// Get an iterator over the tasks
1759    pub fn iter(&self) -> std::slice::Iter<'_, Signature> {
1760        self.tasks.iter()
1761    }
1762
1763    /// Get a mutable iterator over the tasks
1764    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Signature> {
1765        self.tasks.iter_mut()
1766    }
1767
1768    /// Get a task by index
1769    pub fn get(&self, index: usize) -> Option<&Signature> {
1770        self.tasks.get(index)
1771    }
1772
1773    /// Get a mutable task by index
1774    pub fn get_mut(&mut self, index: usize) -> Option<&mut Signature> {
1775        self.tasks.get_mut(index)
1776    }
1777
1778    /// Create a group with pre-allocated capacity
1779    pub fn with_capacity(capacity: usize) -> Self {
1780        Self {
1781            tasks: Vec::with_capacity(capacity),
1782            group_id: Some(Uuid::new_v4()),
1783        }
1784    }
1785
1786    /// Extend the group with additional tasks
1787    pub fn extend(mut self, tasks: impl IntoIterator<Item = Signature>) -> Self {
1788        self.tasks.extend(tasks);
1789        self
1790    }
1791
1792    /// Retain only tasks that satisfy the predicate
1793    pub fn retain<F>(mut self, f: F) -> Self
1794    where
1795        F: FnMut(&Signature) -> bool,
1796    {
1797        self.tasks.retain(f);
1798        self
1799    }
1800
1801    /// Find a task by predicate
1802    pub fn find<F>(&self, predicate: F) -> Option<&Signature>
1803    where
1804        F: Fn(&Signature) -> bool,
1805    {
1806        self.tasks.iter().find(|sig| predicate(sig))
1807    }
1808
1809    /// Filter tasks by predicate and return a new group
1810    pub fn filter<F>(mut self, predicate: F) -> Self
1811    where
1812        F: FnMut(&Signature) -> bool,
1813    {
1814        self.tasks.retain(predicate);
1815        self
1816    }
1817
1818    /// Stagger task execution with countdown delays
1819    ///
1820    /// Each task gets a countdown that increases by the specified interval.
1821    /// This helps prevent thundering herd problems when launching many tasks.
1822    ///
1823    /// # Arguments
1824    /// * `start` - Initial countdown in seconds for the first task
1825    /// * `step` - Increment in seconds for each subsequent task
1826    ///
1827    /// # Example
1828    /// ```
1829    /// use celers_canvas::{Group, Signature};
1830    ///
1831    /// let group = Group::new()
1832    ///     .add("task1", vec![])
1833    ///     .add("task2", vec![])
1834    ///     .add("task3", vec![])
1835    ///     .skew(0.0, 1.0); // task1: 0s, task2: 1s, task3: 2s
1836    ///
1837    /// assert_eq!(group.tasks[0].options.countdown, Some(0));
1838    /// assert_eq!(group.tasks[1].options.countdown, Some(1));
1839    /// assert_eq!(group.tasks[2].options.countdown, Some(2));
1840    /// ```
1841    pub fn skew(mut self, start: f64, step: f64) -> Self {
1842        let mut countdown = start;
1843        for task in &mut self.tasks {
1844            task.options.countdown = Some(countdown as u64);
1845            countdown += step;
1846        }
1847        self
1848    }
1849
1850    /// Stagger task execution with random jitter
1851    ///
1852    /// Each task gets a random countdown between 0 and max_delay.
1853    /// This provides more even load distribution than linear skew.
1854    ///
1855    /// # Arguments
1856    /// * `max_delay` - Maximum countdown in seconds
1857    pub fn jitter(mut self, max_delay: u64) -> Self {
1858        use std::collections::hash_map::DefaultHasher;
1859        use std::hash::{Hash, Hasher};
1860
1861        for (i, task) in self.tasks.iter_mut().enumerate() {
1862            // Use a deterministic "random" based on task index and name
1863            let mut hasher = DefaultHasher::new();
1864            i.hash(&mut hasher);
1865            task.task.hash(&mut hasher);
1866            let hash = hasher.finish();
1867            let delay = hash % (max_delay + 1);
1868            task.options.countdown = Some(delay);
1869        }
1870        self
1871    }
1872
1873    /// Get number of tasks in group
1874    pub fn len(&self) -> usize {
1875        self.tasks.len()
1876    }
1877
1878    /// Check if group ID is set
1879    pub fn has_group_id(&self) -> bool {
1880        self.group_id.is_some()
1881    }
1882
1883    /// Merge another group into this group
1884    ///
1885    /// All tasks from the other group are added to this group.
1886    ///
1887    /// # Example
1888    /// ```
1889    /// use celers_canvas::Group;
1890    ///
1891    /// let group1 = Group::new()
1892    ///     .add("task1", vec![])
1893    ///     .add("task2", vec![]);
1894    ///
1895    /// let group2 = Group::new()
1896    ///     .add("task3", vec![])
1897    ///     .add("task4", vec![]);
1898    ///
1899    /// let merged = group1.merge(group2);
1900    /// assert_eq!(merged.len(), 4);
1901    /// ```
1902    pub fn merge(mut self, other: Group) -> Self {
1903        self.tasks.extend(other.tasks);
1904        self
1905    }
1906
1907    /// Partition tasks into multiple groups based on a predicate
1908    ///
1909    /// Returns a tuple of (matching, non_matching) groups.
1910    ///
1911    /// # Example
1912    /// ```
1913    /// use celers_canvas::{Group, Signature};
1914    ///
1915    /// let group = Group::new()
1916    ///     .add_signature(Signature::new("high_priority".to_string()).with_priority(9))
1917    ///     .add_signature(Signature::new("normal".to_string()).with_priority(5))
1918    ///     .add_signature(Signature::new("urgent".to_string()).with_priority(9))
1919    ///     .add_signature(Signature::new("low".to_string()).with_priority(1));
1920    ///
1921    /// let (high, low) = group.partition(|sig| sig.options.priority.unwrap_or(0) >= 9);
1922    /// assert_eq!(high.len(), 2);
1923    /// assert_eq!(low.len(), 2);
1924    /// ```
1925    pub fn partition<F>(self, mut predicate: F) -> (Group, Group)
1926    where
1927        F: FnMut(&Signature) -> bool,
1928    {
1929        let (matching, non_matching): (Vec<_>, Vec<_>) =
1930            self.tasks.into_iter().partition(|sig| predicate(sig));
1931
1932        (
1933            Group {
1934                tasks: matching,
1935                group_id: self.group_id,
1936            },
1937            Group {
1938                tasks: non_matching,
1939                group_id: None, // Different group
1940            },
1941        )
1942    }
1943
1944    /// Add a task name prefix to all tasks in the group
1945    ///
1946    /// # Example
1947    /// ```
1948    /// use celers_canvas::Group;
1949    ///
1950    /// let group = Group::new()
1951    ///     .add("process", vec![])
1952    ///     .add("validate", vec![]);
1953    ///
1954    /// let prefixed = group.with_task_prefix("batch_");
1955    /// assert_eq!(prefixed.first().unwrap().task, "batch_process");
1956    /// ```
1957    pub fn with_task_prefix(mut self, prefix: &str) -> Self {
1958        for task in &mut self.tasks {
1959            task.task = format!("{}{}", prefix, task.task);
1960        }
1961        self
1962    }
1963
1964    /// Add a task name suffix to all tasks in the group
1965    ///
1966    /// # Example
1967    /// ```
1968    /// use celers_canvas::Group;
1969    ///
1970    /// let group = Group::new()
1971    ///     .add("process", vec![])
1972    ///     .add("validate", vec![]);
1973    ///
1974    /// let suffixed = group.with_task_suffix("_v2");
1975    /// assert_eq!(suffixed.first().unwrap().task, "process_v2");
1976    /// ```
1977    pub fn with_task_suffix(mut self, suffix: &str) -> Self {
1978        for task in &mut self.tasks {
1979            task.task = format!("{}{}", task.task, suffix);
1980        }
1981        self
1982    }
1983
1984    /// Set priority on all tasks in the group
1985    ///
1986    /// # Example
1987    /// ```
1988    /// use celers_canvas::Group;
1989    ///
1990    /// let group = Group::new()
1991    ///     .add("task1", vec![])
1992    ///     .add("task2", vec![])
1993    ///     .with_priority(9);
1994    ///
1995    /// assert_eq!(group.first().unwrap().options.priority, Some(9));
1996    /// ```
1997    pub fn with_priority(mut self, priority: u8) -> Self {
1998        for task in &mut self.tasks {
1999            task.options.priority = Some(priority);
2000        }
2001        self
2002    }
2003
2004    /// Set queue on all tasks in the group
2005    ///
2006    /// # Example
2007    /// ```
2008    /// use celers_canvas::Group;
2009    ///
2010    /// let group = Group::new()
2011    ///     .add("task1", vec![])
2012    ///     .add("task2", vec![])
2013    ///     .with_queue("high_priority".to_string());
2014    ///
2015    /// assert_eq!(group.first().unwrap().options.queue, Some("high_priority".to_string()));
2016    /// ```
2017    pub fn with_queue(mut self, queue: String) -> Self {
2018        for task in &mut self.tasks {
2019            task.options.queue = Some(queue.clone());
2020        }
2021        self
2022    }
2023
2024    /// Validate that all tasks in the group have non-empty names
2025    ///
2026    /// Returns true if all tasks are valid, false otherwise.
2027    ///
2028    /// # Example
2029    /// ```
2030    /// use celers_canvas::Group;
2031    ///
2032    /// let valid = Group::new()
2033    ///     .add("task1", vec![])
2034    ///     .add("task2", vec![]);
2035    /// assert!(valid.is_valid());
2036    ///
2037    /// let invalid = Group { tasks: vec![], group_id: None };
2038    /// assert!(!invalid.is_valid());
2039    /// ```
2040    pub fn is_valid(&self) -> bool {
2041        !self.tasks.is_empty() && self.tasks.iter().all(|t| !t.task.is_empty())
2042    }
2043
2044    /// Count tasks that match a predicate
2045    ///
2046    /// # Example
2047    /// ```
2048    /// use celers_canvas::{Group, Signature};
2049    ///
2050    /// let group = Group::new()
2051    ///     .add_signature(Signature::new("high".to_string()).with_priority(9))
2052    ///     .add_signature(Signature::new("low".to_string()).with_priority(1))
2053    ///     .add_signature(Signature::new("urgent".to_string()).with_priority(9));
2054    ///
2055    /// let high_priority = group.count_matching(|sig| sig.options.priority.unwrap_or(0) >= 9);
2056    /// assert_eq!(high_priority, 2);
2057    /// ```
2058    pub fn count_matching<F>(&self, predicate: F) -> usize
2059    where
2060        F: Fn(&Signature) -> bool,
2061    {
2062        self.tasks.iter().filter(|t| predicate(t)).count()
2063    }
2064
2065    /// Check if any task matches a predicate
2066    ///
2067    /// # Example
2068    /// ```
2069    /// use celers_canvas::Group;
2070    ///
2071    /// let group = Group::new()
2072    ///     .add("process", vec![])
2073    ///     .add("validate", vec![]);
2074    ///
2075    /// assert!(group.any(|sig| sig.task == "validate"));
2076    /// assert!(!group.any(|sig| sig.task == "missing"));
2077    /// ```
2078    pub fn any<F>(&self, predicate: F) -> bool
2079    where
2080        F: Fn(&Signature) -> bool,
2081    {
2082        self.tasks.iter().any(predicate)
2083    }
2084
2085    /// Check if all tasks match a predicate
2086    ///
2087    /// # Example
2088    /// ```
2089    /// use celers_canvas::Group;
2090    ///
2091    /// let group = Group::new()
2092    ///     .add("process", vec![])
2093    ///     .add("validate", vec![]);
2094    ///
2095    /// assert!(group.all(|sig| !sig.task.is_empty()));
2096    /// ```
2097    pub fn all<F>(&self, predicate: F) -> bool
2098    where
2099        F: Fn(&Signature) -> bool,
2100    {
2101        self.tasks.iter().all(predicate)
2102    }
2103
2104    /// Map over all tasks, transforming each signature
2105    ///
2106    /// # Example
2107    /// ```
2108    /// use celers_canvas::{Group, Signature};
2109    ///
2110    /// let group = Group::new()
2111    ///     .add("task1", vec![])
2112    ///     .add("task2", vec![]);
2113    ///
2114    /// let modified = group.map_tasks(|sig| {
2115    ///     Signature::new(format!("parallel_{}", sig.task))
2116    /// });
2117    ///
2118    /// assert_eq!(modified.first().unwrap().task, "parallel_task1");
2119    /// ```
2120    pub fn map_tasks<F>(mut self, f: F) -> Self
2121    where
2122        F: FnMut(Signature) -> Signature,
2123    {
2124        self.tasks = self.tasks.into_iter().map(f).collect();
2125        self
2126    }
2127
2128    /// Filter and map tasks in one operation
2129    ///
2130    /// # Example
2131    /// ```
2132    /// use celers_canvas::{Group, Signature};
2133    ///
2134    /// let group = Group::new()
2135    ///     .add_signature(Signature::new("high".to_string()).with_priority(9))
2136    ///     .add_signature(Signature::new("low".to_string()).with_priority(1))
2137    ///     .add_signature(Signature::new("urgent".to_string()).with_priority(9));
2138    ///
2139    /// let high_priority = group.filter_map(|sig| {
2140    ///     if sig.options.priority.unwrap_or(0) >= 9 {
2141    ///         Some(sig)
2142    ///     } else {
2143    ///         None
2144    ///     }
2145    /// });
2146    ///
2147    /// assert_eq!(high_priority.len(), 2);
2148    /// ```
2149    pub fn filter_map<F>(mut self, f: F) -> Self
2150    where
2151        F: FnMut(Signature) -> Option<Signature>,
2152    {
2153        self.tasks = self.tasks.into_iter().filter_map(f).collect();
2154        self
2155    }
2156
2157    /// Take the first n tasks from the group
2158    ///
2159    /// # Example
2160    /// ```
2161    /// use celers_canvas::Group;
2162    ///
2163    /// let group = Group::new()
2164    ///     .add("task1", vec![])
2165    ///     .add("task2", vec![])
2166    ///     .add("task3", vec![])
2167    ///     .add("task4", vec![]);
2168    ///
2169    /// let first_two = group.take(2);
2170    /// assert_eq!(first_two.len(), 2);
2171    /// ```
2172    pub fn take(mut self, n: usize) -> Self {
2173        self.tasks.truncate(n);
2174        self
2175    }
2176
2177    /// Skip the first n tasks from the group
2178    ///
2179    /// # Example
2180    /// ```
2181    /// use celers_canvas::Group;
2182    ///
2183    /// let group = Group::new()
2184    ///     .add("task1", vec![])
2185    ///     .add("task2", vec![])
2186    ///     .add("task3", vec![])
2187    ///     .add("task4", vec![]);
2188    ///
2189    /// let skipped = group.skip(2);
2190    /// assert_eq!(skipped.len(), 2);
2191    /// assert_eq!(skipped.first().unwrap().task, "task3");
2192    /// ```
2193    pub fn skip(mut self, n: usize) -> Self {
2194        self.tasks = self.tasks.into_iter().skip(n).collect();
2195        self
2196    }
2197
2198    /// Find the index of the first task with the given name
2199    ///
2200    /// # Example
2201    /// ```
2202    /// use celers_canvas::Group;
2203    ///
2204    /// let group = Group::new()
2205    ///     .add("task1", vec![])
2206    ///     .add("task2", vec![])
2207    ///     .add("task1", vec![]);
2208    ///
2209    /// assert_eq!(group.find_task("task1"), Some(0));
2210    /// assert_eq!(group.find_task("task2"), Some(1));
2211    /// assert_eq!(group.find_task("task3"), None);
2212    /// ```
2213    pub fn find_task(&self, task_name: &str) -> Option<usize> {
2214        self.tasks.iter().position(|t| t.task == task_name)
2215    }
2216
2217    /// Find all indices of tasks with the given name
2218    ///
2219    /// # Example
2220    /// ```
2221    /// use celers_canvas::Group;
2222    ///
2223    /// let group = Group::new()
2224    ///     .add("task1", vec![])
2225    ///     .add("task2", vec![])
2226    ///     .add("task1", vec![]);
2227    ///
2228    /// assert_eq!(group.find_all_tasks("task1"), vec![0, 2]);
2229    /// assert_eq!(group.find_all_tasks("task2"), vec![1]);
2230    /// ```
2231    pub fn find_all_tasks(&self, task_name: &str) -> Vec<usize> {
2232        self.tasks
2233            .iter()
2234            .enumerate()
2235            .filter(|(_, t)| t.task == task_name)
2236            .map(|(i, _)| i)
2237            .collect()
2238    }
2239
2240    /// Check if the group contains a task with the given name
2241    ///
2242    /// # Example
2243    /// ```
2244    /// use celers_canvas::Group;
2245    ///
2246    /// let group = Group::new()
2247    ///     .add("task1", vec![])
2248    ///     .add("task2", vec![]);
2249    ///
2250    /// assert!(group.contains_task("task1"));
2251    /// assert!(!group.contains_task("task3"));
2252    /// ```
2253    pub fn contains_task(&self, task_name: &str) -> bool {
2254        self.tasks.iter().any(|t| t.task == task_name)
2255    }
2256
2257    /// Get the maximum estimated duration in seconds based on task time limits
2258    ///
2259    /// Since tasks run in parallel, the group duration is the maximum of all task durations.
2260    /// Returns None if no tasks have time limits set.
2261    ///
2262    /// # Example
2263    /// ```
2264    /// use celers_canvas::{Group, Signature};
2265    ///
2266    /// let group = Group::new()
2267    ///     .add_signature(Signature::new("task1".to_string()).with_time_limit(10))
2268    ///     .add_signature(Signature::new("task2".to_string()).with_time_limit(20));
2269    ///
2270    /// assert_eq!(group.estimated_duration(), Some(20)); // Max of 10 and 20
2271    /// ```
2272    pub fn estimated_duration(&self) -> Option<u64> {
2273        self.tasks
2274            .iter()
2275            .filter_map(|t| t.options.time_limit.or(t.options.soft_time_limit))
2276            .max()
2277    }
2278
2279    /// Get a summary of all task names in the group
2280    ///
2281    /// # Example
2282    /// ```
2283    /// use celers_canvas::Group;
2284    ///
2285    /// let group = Group::new()
2286    ///     .add("fetch", vec![])
2287    ///     .add("process", vec![])
2288    ///     .add("save", vec![]);
2289    ///
2290    /// assert_eq!(group.task_names(), vec!["fetch", "process", "save"]);
2291    /// ```
2292    pub fn task_names(&self) -> Vec<&str> {
2293        self.tasks.iter().map(|t| t.task.as_str()).collect()
2294    }
2295
2296    /// Get all unique task names in the group
2297    ///
2298    /// # Example
2299    /// ```
2300    /// use celers_canvas::Group;
2301    ///
2302    /// let group = Group::new()
2303    ///     .add("task1", vec![])
2304    ///     .add("task2", vec![])
2305    ///     .add("task1", vec![]);
2306    ///
2307    /// let unique = group.unique_task_names();
2308    /// assert_eq!(unique.len(), 2);
2309    /// assert!(unique.contains(&"task1"));
2310    /// assert!(unique.contains(&"task2"));
2311    /// ```
2312    pub fn unique_task_names(&self) -> std::collections::HashSet<&str> {
2313        self.tasks.iter().map(|t| t.task.as_str()).collect()
2314    }
2315
2316    /// Clone the group with a transformation applied to each task
2317    ///
2318    /// # Example
2319    /// ```
2320    /// use celers_canvas::Group;
2321    ///
2322    /// let group = Group::new()
2323    ///     .add("task1", vec![])
2324    ///     .add("task2", vec![]);
2325    ///
2326    /// let prioritized = group.clone_with_transform(|sig| {
2327    ///     sig.clone().with_priority(5)
2328    /// });
2329    ///
2330    /// assert!(prioritized.tasks.iter().all(|t| t.options.priority == Some(5)));
2331    /// ```
2332    pub fn clone_with_transform<F>(&self, mut transform: F) -> Self
2333    where
2334        F: FnMut(&Signature) -> Signature,
2335    {
2336        Self {
2337            tasks: self.tasks.iter().map(&mut transform).collect(),
2338            group_id: self.group_id,
2339        }
2340    }
2341
2342    /// Count tasks by priority level
2343    ///
2344    /// Returns a map of priority values to the count of tasks at that priority.
2345    ///
2346    /// # Example
2347    /// ```
2348    /// use celers_canvas::{Group, Signature};
2349    ///
2350    /// let group = Group::new()
2351    ///     .add_signature(Signature::new("task1".to_string()).with_priority(1))
2352    ///     .add_signature(Signature::new("task2".to_string()).with_priority(5))
2353    ///     .add_signature(Signature::new("task3".to_string()).with_priority(1));
2354    ///
2355    /// let counts = group.count_by_priority();
2356    /// assert_eq!(counts.get(&1), Some(&2));
2357    /// assert_eq!(counts.get(&5), Some(&1));
2358    /// ```
2359    pub fn count_by_priority(&self) -> std::collections::HashMap<u8, usize> {
2360        let mut counts = std::collections::HashMap::new();
2361        for task in &self.tasks {
2362            if let Some(priority) = task.options.priority {
2363                *counts.entry(priority).or_insert(0) += 1;
2364            }
2365        }
2366        counts
2367    }
2368
2369    /// Count tasks by queue name
2370    ///
2371    /// Returns a map of queue names to the count of tasks targeting that queue.
2372    ///
2373    /// # Example
2374    /// ```
2375    /// use celers_canvas::{Group, Signature};
2376    ///
2377    /// let group = Group::new()
2378    ///     .add_signature(Signature::new("task1".to_string()).with_queue("queue_a".to_string()))
2379    ///     .add_signature(Signature::new("task2".to_string()).with_queue("queue_b".to_string()))
2380    ///     .add_signature(Signature::new("task3".to_string()).with_queue("queue_a".to_string()));
2381    ///
2382    /// let counts = group.count_by_queue();
2383    /// assert_eq!(counts.get("queue_a"), Some(&2));
2384    /// assert_eq!(counts.get("queue_b"), Some(&1));
2385    /// ```
2386    pub fn count_by_queue(&self) -> std::collections::HashMap<String, usize> {
2387        let mut counts = std::collections::HashMap::new();
2388        for task in &self.tasks {
2389            if let Some(ref queue) = task.options.queue {
2390                *counts.entry(queue.clone()).or_insert(0) += 1;
2391            }
2392        }
2393        counts
2394    }
2395}
2396
2397impl std::fmt::Display for Group {
2398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2399        write!(f, "Group[{} tasks]", self.tasks.len())?;
2400        if let Some(group_id) = self.group_id {
2401            write!(f, " id={}", &group_id.to_string()[..8])?;
2402        }
2403        Ok(())
2404    }
2405}
2406
2407impl IntoIterator for Group {
2408    type Item = Signature;
2409    type IntoIter = std::vec::IntoIter<Signature>;
2410
2411    fn into_iter(self) -> Self::IntoIter {
2412        self.tasks.into_iter()
2413    }
2414}
2415
2416impl<'a> IntoIterator for &'a Group {
2417    type Item = &'a Signature;
2418    type IntoIter = std::slice::Iter<'a, Signature>;
2419
2420    fn into_iter(self) -> Self::IntoIter {
2421        self.tasks.iter()
2422    }
2423}
2424
2425impl From<Vec<Signature>> for Group {
2426    fn from(tasks: Vec<Signature>) -> Self {
2427        Self {
2428            tasks,
2429            group_id: Some(Uuid::new_v4()),
2430        }
2431    }
2432}
2433
2434impl FromIterator<Signature> for Group {
2435    fn from_iter<T: IntoIterator<Item = Signature>>(iter: T) -> Self {
2436        Self {
2437            tasks: iter.into_iter().collect(),
2438            group_id: Some(Uuid::new_v4()),
2439        }
2440    }
2441}
2442
2443/// Chord: Parallel execution with callback
2444///
2445/// (task1 | task2 | task3) -> callback([result1, result2, result3])
2446#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2447pub struct Chord {
2448    /// Header (parallel tasks)
2449    pub header: Group,
2450
2451    /// Body (callback task)
2452    pub body: Signature,
2453}
2454
2455impl Chord {
2456    pub fn new(header: Group, body: Signature) -> Self {
2457        Self { header, body }
2458    }
2459
2460    /// Apply the chord by initializing state and enqueuing header tasks
2461    #[cfg(feature = "backend-redis")]
2462    pub async fn apply<B: Broker, R: ResultBackend>(
2463        mut self,
2464        broker: &B,
2465        backend: &mut R,
2466    ) -> Result<Uuid, CanvasError> {
2467        if self.header.tasks.is_empty() {
2468            return Err(CanvasError::Invalid(
2469                "Chord header cannot be empty".to_string(),
2470            ));
2471        }
2472
2473        let chord_id = Uuid::new_v4();
2474        let total = self.header.tasks.len();
2475
2476        // Initialize chord state in backend
2477        let chord_state = ChordState {
2478            chord_id,
2479            total,
2480            completed: 0,
2481            callback: Some(self.body.task.clone()),
2482            task_ids: Vec::new(),
2483            created_at: Utc::now(),
2484            timeout: None,
2485            cancelled: false,
2486            cancellation_reason: None,
2487            retry_count: 0,
2488            max_retries: None,
2489        };
2490
2491        backend
2492            .chord_init(chord_state)
2493            .await
2494            .map_err(|e| CanvasError::Broker(format!("Failed to initialize chord: {}", e)))?;
2495
2496        // Enqueue all header tasks with chord_id
2497        for sig in &mut self.header.tasks {
2498            let args_json = serde_json::json!({
2499                "args": sig.args,
2500                "kwargs": sig.kwargs
2501            });
2502            let args_bytes = serde_json::to_vec(&args_json)
2503                .map_err(|e| CanvasError::Serialization(e.to_string()))?;
2504
2505            let mut task = SerializedTask::new(sig.task.clone(), args_bytes);
2506
2507            if let Some(priority) = sig.options.priority {
2508                task = task.with_priority(priority.into());
2509            }
2510
2511            // Set chord_id so worker knows to update chord state on completion
2512            task.metadata.chord_id = Some(chord_id);
2513
2514            broker
2515                .enqueue(task)
2516                .await
2517                .map_err(|e| CanvasError::Broker(e.to_string()))?;
2518        }
2519
2520        Ok(chord_id)
2521    }
2522
2523    /// Apply the chord without a result backend (simpler version)
2524    #[cfg(not(feature = "backend-redis"))]
2525    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2526        // Without backend, we can only enqueue header tasks
2527        // Manual coordination required
2528        self.header.apply(broker).await
2529    }
2530}
2531
2532impl std::fmt::Display for Chord {
2533    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2534        write!(
2535            f,
2536            "Chord[{} tasks] -> callback({})",
2537            self.header.tasks.len(),
2538            self.body.task
2539        )
2540    }
2541}
2542
2543/// Map: Apply task to multiple arguments
2544///
2545/// map(task, [args1, args2, args3]) -> [result1, result2, result3]
2546#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2547pub struct Map {
2548    /// Task to apply
2549    pub task: Signature,
2550
2551    /// List of argument sets
2552    pub argsets: Vec<Vec<serde_json::Value>>,
2553}
2554
2555impl Map {
2556    pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2557        Self { task, argsets }
2558    }
2559
2560    /// Apply the map by creating a group of tasks with different arguments
2561    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2562        let mut group = Group::new();
2563
2564        for args in self.argsets {
2565            let mut sig = self.task.clone();
2566            sig.args = args;
2567            group = group.add_signature(sig);
2568        }
2569
2570        group.apply(broker).await
2571    }
2572
2573    /// Check if map is empty
2574    pub fn is_empty(&self) -> bool {
2575        self.argsets.is_empty()
2576    }
2577
2578    /// Get number of argument sets (and thus tasks)
2579    pub fn len(&self) -> usize {
2580        self.argsets.len()
2581    }
2582}
2583
2584impl std::fmt::Display for Map {
2585    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2586        write!(
2587            f,
2588            "Map[task={}, {} argsets]",
2589            self.task.task,
2590            self.argsets.len()
2591        )
2592    }
2593}
2594
2595/// Starmap: Like map but unpacks arguments
2596///
2597/// starmap(task, [(a1, b1), (a2, b2)]) -> [task(a1, b1), task(a2, b2)]
2598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2599pub struct Starmap {
2600    /// Task to apply
2601    pub task: Signature,
2602
2603    /// List of argument tuples
2604    pub argsets: Vec<Vec<serde_json::Value>>,
2605}
2606
2607impl Starmap {
2608    pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2609        Self { task, argsets }
2610    }
2611
2612    /// Apply the starmap by creating a group of tasks with unpacked arguments
2613    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2614        // Starmap is the same as Map - the unpacking happens in task execution
2615        let map = Map::new(self.task, self.argsets);
2616        map.apply(broker).await
2617    }
2618
2619    /// Check if starmap is empty
2620    pub fn is_empty(&self) -> bool {
2621        self.argsets.is_empty()
2622    }
2623
2624    /// Get number of argument sets (and thus tasks)
2625    pub fn len(&self) -> usize {
2626        self.argsets.len()
2627    }
2628}
2629
2630impl std::fmt::Display for Starmap {
2631    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2632        write!(
2633            f,
2634            "Starmap[task={}, {} argsets]",
2635            self.task.task,
2636            self.argsets.len()
2637        )
2638    }
2639}
2640
2641/// Chunks: Split iterable into chunks for parallel processing
2642///
2643/// chunks(task, items, chunk_size) -> Group of tasks, each processing a chunk
2644///
2645/// # Example
2646/// ```
2647/// use celers_canvas::{Chunks, Signature};
2648///
2649/// let task = Signature::new("process_batch".to_string());
2650/// let items: Vec<serde_json::Value> = (0..100).map(|i| serde_json::json!(i)).collect();
2651///
2652/// // Process 100 items in chunks of 10 (creates 10 parallel tasks)
2653/// let chunks = Chunks::new(task, items, 10);
2654/// assert_eq!(chunks.num_chunks(), 10);
2655/// ```
2656#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2657pub struct Chunks {
2658    /// Task to apply to each chunk
2659    pub task: Signature,
2660
2661    /// Items to split into chunks
2662    pub items: Vec<serde_json::Value>,
2663
2664    /// Size of each chunk
2665    pub chunk_size: usize,
2666}
2667
2668impl Chunks {
2669    /// Create a new Chunks workflow
2670    ///
2671    /// # Arguments
2672    /// * `task` - The task signature to apply to each chunk
2673    /// * `items` - Items to split into chunks
2674    /// * `chunk_size` - Number of items per chunk
2675    pub fn new(task: Signature, items: Vec<serde_json::Value>, chunk_size: usize) -> Self {
2676        Self {
2677            task,
2678            items,
2679            chunk_size: chunk_size.max(1), // Minimum chunk size of 1
2680        }
2681    }
2682
2683    /// Get the number of chunks that will be created
2684    pub fn num_chunks(&self) -> usize {
2685        if self.items.is_empty() {
2686            0
2687        } else {
2688            self.items.len().div_ceil(self.chunk_size)
2689        }
2690    }
2691
2692    /// Check if chunks is empty
2693    pub fn is_empty(&self) -> bool {
2694        self.items.is_empty()
2695    }
2696
2697    /// Get total number of items
2698    pub fn len(&self) -> usize {
2699        self.items.len()
2700    }
2701
2702    /// Convert to a Group for execution
2703    pub fn to_group(&self) -> Group {
2704        let mut group = Group::new();
2705
2706        for chunk in self.items.chunks(self.chunk_size) {
2707            let mut sig = self.task.clone();
2708            sig.args = vec![serde_json::json!(chunk)];
2709            group = group.add_signature(sig);
2710        }
2711
2712        group
2713    }
2714
2715    /// Apply the chunks by creating a group of tasks
2716    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2717        if self.items.is_empty() {
2718            return Err(CanvasError::Invalid("Chunks cannot be empty".to_string()));
2719        }
2720
2721        self.to_group().apply(broker).await
2722    }
2723}
2724
2725impl std::fmt::Display for Chunks {
2726    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2727        write!(
2728            f,
2729            "Chunks[task={}, {} items, chunk_size={}, {} chunks]",
2730            self.task.task,
2731            self.items.len(),
2732            self.chunk_size,
2733            self.num_chunks()
2734        )
2735    }
2736}
2737
2738/// XMap: Map with exception handling
2739///
2740/// Like Map, but continues processing even if some tasks fail.
2741/// Failed tasks are tracked separately.
2742#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2743pub struct XMap {
2744    /// Task to apply
2745    pub task: Signature,
2746
2747    /// List of argument sets
2748    pub argsets: Vec<Vec<serde_json::Value>>,
2749
2750    /// Whether to stop on first error
2751    pub fail_fast: bool,
2752}
2753
2754impl XMap {
2755    /// Create a new XMap workflow
2756    pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2757        Self {
2758            task,
2759            argsets,
2760            fail_fast: false,
2761        }
2762    }
2763
2764    /// Set fail-fast behavior (stop on first error)
2765    pub fn fail_fast(mut self, fail_fast: bool) -> Self {
2766        self.fail_fast = fail_fast;
2767        self
2768    }
2769
2770    /// Check if empty
2771    pub fn is_empty(&self) -> bool {
2772        self.argsets.is_empty()
2773    }
2774
2775    /// Get number of argument sets
2776    pub fn len(&self) -> usize {
2777        self.argsets.len()
2778    }
2779
2780    /// Apply the xmap by creating a group of tasks
2781    ///
2782    /// Note: Exception handling is done at the result collection level,
2783    /// not during task submission.
2784    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2785        let map = Map::new(self.task, self.argsets);
2786        map.apply(broker).await
2787    }
2788}
2789
2790impl std::fmt::Display for XMap {
2791    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2792        write!(
2793            f,
2794            "XMap[task={}, {} argsets, fail_fast={}]",
2795            self.task.task,
2796            self.argsets.len(),
2797            self.fail_fast
2798        )
2799    }
2800}
2801
2802/// XStarmap: Starmap with exception handling
2803///
2804/// Like Starmap, but continues processing even if some tasks fail.
2805#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2806pub struct XStarmap {
2807    /// Task to apply
2808    pub task: Signature,
2809
2810    /// List of argument tuples
2811    pub argsets: Vec<Vec<serde_json::Value>>,
2812
2813    /// Whether to stop on first error
2814    pub fail_fast: bool,
2815}
2816
2817impl XStarmap {
2818    /// Create a new XStarmap workflow
2819    pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2820        Self {
2821            task,
2822            argsets,
2823            fail_fast: false,
2824        }
2825    }
2826
2827    /// Set fail-fast behavior (stop on first error)
2828    pub fn fail_fast(mut self, fail_fast: bool) -> Self {
2829        self.fail_fast = fail_fast;
2830        self
2831    }
2832
2833    /// Check if empty
2834    pub fn is_empty(&self) -> bool {
2835        self.argsets.is_empty()
2836    }
2837
2838    /// Get number of argument sets
2839    pub fn len(&self) -> usize {
2840        self.argsets.len()
2841    }
2842
2843    /// Apply the xstarmap by creating a group of tasks
2844    pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2845        let starmap = Starmap::new(self.task, self.argsets);
2846        starmap.apply(broker).await
2847    }
2848}
2849
2850impl std::fmt::Display for XStarmap {
2851    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2852        write!(
2853            f,
2854            "XStarmap[task={}, {} argsets, fail_fast={}]",
2855            self.task.task,
2856            self.argsets.len(),
2857            self.fail_fast
2858        )
2859    }
2860}
2861
2862// ============================================================================
2863// Conditional Workflow Primitives
2864// ============================================================================
2865
2866/// Condition for conditional workflow branching
2867///
2868/// Represents a condition that determines which branch of a workflow to execute.
2869/// Conditions can be evaluated against the result of a previous task or against
2870/// static values.
2871#[derive(Debug, Clone, Serialize, Deserialize)]
2872pub enum Condition {
2873    /// Always true - execute the then branch
2874    Always,
2875
2876    /// Always false - execute the else branch
2877    Never,
2878
2879    /// Check if a value equals the expected value
2880    Equals {
2881        /// Field path to extract from result (e.g., "status" or "data.count")
2882        field: Option<String>,
2883        /// Expected value
2884        value: serde_json::Value,
2885    },
2886
2887    /// Check if a value is not equal to the expected value
2888    NotEquals {
2889        /// Field path to extract from result
2890        field: Option<String>,
2891        /// Value to compare against
2892        value: serde_json::Value,
2893    },
2894
2895    /// Check if a numeric value is greater than threshold
2896    GreaterThan {
2897        /// Field path to extract from result
2898        field: Option<String>,
2899        /// Threshold value
2900        threshold: f64,
2901    },
2902
2903    /// Check if a numeric value is less than threshold
2904    LessThan {
2905        /// Field path to extract from result
2906        field: Option<String>,
2907        /// Threshold value
2908        threshold: f64,
2909    },
2910
2911    /// Check if a value is truthy (not null, not false, not 0, not empty)
2912    Truthy {
2913        /// Field path to extract from result
2914        field: Option<String>,
2915    },
2916
2917    /// Check if a value is falsy (null, false, 0, or empty)
2918    Falsy {
2919        /// Field path to extract from result
2920        field: Option<String>,
2921    },
2922
2923    /// Check if a value contains a substring or element
2924    Contains {
2925        /// Field path to extract from result
2926        field: Option<String>,
2927        /// Value to search for
2928        value: serde_json::Value,
2929    },
2930
2931    /// Check if a value matches a regex pattern
2932    Matches {
2933        /// Field path to extract from result
2934        field: Option<String>,
2935        /// Regex pattern
2936        pattern: String,
2937    },
2938
2939    /// Logical AND of multiple conditions
2940    And(Vec<Condition>),
2941
2942    /// Logical OR of multiple conditions
2943    Or(Vec<Condition>),
2944
2945    /// Logical NOT of a condition
2946    Not(Box<Condition>),
2947
2948    /// Custom condition evaluated by a task
2949    /// The task should return a boolean result
2950    Custom {
2951        /// Task name that evaluates the condition
2952        task: String,
2953        /// Arguments for the condition task
2954        args: Vec<serde_json::Value>,
2955    },
2956}
2957
2958impl Condition {
2959    /// Create an always-true condition
2960    pub fn always() -> Self {
2961        Self::Always
2962    }
2963
2964    /// Create an always-false condition
2965    pub fn never() -> Self {
2966        Self::Never
2967    }
2968
2969    /// Create an equals condition
2970    pub fn equals(value: serde_json::Value) -> Self {
2971        Self::Equals { field: None, value }
2972    }
2973
2974    /// Create an equals condition on a specific field
2975    pub fn field_equals(field: impl Into<String>, value: serde_json::Value) -> Self {
2976        Self::Equals {
2977            field: Some(field.into()),
2978            value,
2979        }
2980    }
2981
2982    /// Create a not-equals condition
2983    pub fn not_equals(value: serde_json::Value) -> Self {
2984        Self::NotEquals { field: None, value }
2985    }
2986
2987    /// Create a greater-than condition
2988    pub fn greater_than(threshold: f64) -> Self {
2989        Self::GreaterThan {
2990            field: None,
2991            threshold,
2992        }
2993    }
2994
2995    /// Create a greater-than condition on a specific field
2996    pub fn field_greater_than(field: impl Into<String>, threshold: f64) -> Self {
2997        Self::GreaterThan {
2998            field: Some(field.into()),
2999            threshold,
3000        }
3001    }
3002
3003    /// Create a less-than condition
3004    pub fn less_than(threshold: f64) -> Self {
3005        Self::LessThan {
3006            field: None,
3007            threshold,
3008        }
3009    }
3010
3011    /// Create a truthy condition
3012    pub fn truthy() -> Self {
3013        Self::Truthy { field: None }
3014    }
3015
3016    /// Create a truthy condition on a specific field
3017    pub fn field_truthy(field: impl Into<String>) -> Self {
3018        Self::Truthy {
3019            field: Some(field.into()),
3020        }
3021    }
3022
3023    /// Create a falsy condition
3024    pub fn falsy() -> Self {
3025        Self::Falsy { field: None }
3026    }
3027
3028    /// Create a contains condition
3029    pub fn contains(value: serde_json::Value) -> Self {
3030        Self::Contains { field: None, value }
3031    }
3032
3033    /// Create a regex match condition
3034    pub fn matches(pattern: impl Into<String>) -> Self {
3035        Self::Matches {
3036            field: None,
3037            pattern: pattern.into(),
3038        }
3039    }
3040
3041    /// Create a custom task-based condition
3042    pub fn custom(task: impl Into<String>, args: Vec<serde_json::Value>) -> Self {
3043        Self::Custom {
3044            task: task.into(),
3045            args,
3046        }
3047    }
3048
3049    /// Combine with AND
3050    pub fn and(self, other: Condition) -> Self {
3051        match self {
3052            Self::And(mut conditions) => {
3053                conditions.push(other);
3054                Self::And(conditions)
3055            }
3056            _ => Self::And(vec![self, other]),
3057        }
3058    }
3059
3060    /// Combine with OR
3061    pub fn or(self, other: Condition) -> Self {
3062        match self {
3063            Self::Or(mut conditions) => {
3064                conditions.push(other);
3065                Self::Or(conditions)
3066            }
3067            _ => Self::Or(vec![self, other]),
3068        }
3069    }
3070
3071    /// Negate the condition
3072    pub fn negate(self) -> Self {
3073        Self::Not(Box::new(self))
3074    }
3075
3076    /// Evaluate the condition against a JSON value
3077    pub fn evaluate(&self, value: &serde_json::Value) -> bool {
3078        match self {
3079            Self::Always => true,
3080            Self::Never => false,
3081            Self::Equals {
3082                field,
3083                value: expected,
3084            } => {
3085                let actual = extract_field(value, field.as_deref());
3086                actual == *expected
3087            }
3088            Self::NotEquals {
3089                field,
3090                value: expected,
3091            } => {
3092                let actual = extract_field(value, field.as_deref());
3093                actual != *expected
3094            }
3095            Self::GreaterThan { field, threshold } => {
3096                let actual = extract_field(value, field.as_deref());
3097                actual.as_f64().is_some_and(|v| v > *threshold)
3098            }
3099            Self::LessThan { field, threshold } => {
3100                let actual = extract_field(value, field.as_deref());
3101                actual.as_f64().is_some_and(|v| v < *threshold)
3102            }
3103            Self::Truthy { field } => {
3104                let actual = extract_field(value, field.as_deref());
3105                is_truthy(&actual)
3106            }
3107            Self::Falsy { field } => {
3108                let actual = extract_field(value, field.as_deref());
3109                !is_truthy(&actual)
3110            }
3111            Self::Contains {
3112                field,
3113                value: needle,
3114            } => {
3115                let actual = extract_field(value, field.as_deref());
3116                contains_value(&actual, needle)
3117            }
3118            Self::Matches { field, pattern } => {
3119                let actual = extract_field(value, field.as_deref());
3120                if let Some(s) = actual.as_str() {
3121                    regex::Regex::new(pattern)
3122                        .map(|re| re.is_match(s))
3123                        .unwrap_or(false)
3124                } else {
3125                    false
3126                }
3127            }
3128            Self::And(conditions) => conditions.iter().all(|c| c.evaluate(value)),
3129            Self::Or(conditions) => conditions.iter().any(|c| c.evaluate(value)),
3130            Self::Not(condition) => !condition.evaluate(value),
3131            Self::Custom { .. } => {
3132                // Custom conditions cannot be evaluated synchronously
3133                // They need to be evaluated by executing a task
3134                false
3135            }
3136        }
3137    }
3138
3139    /// Check if this is a custom condition that requires task execution
3140    pub fn is_custom(&self) -> bool {
3141        match self {
3142            Self::Custom { .. } => true,
3143            Self::And(conditions) => conditions.iter().any(|c| c.is_custom()),
3144            Self::Or(conditions) => conditions.iter().any(|c| c.is_custom()),
3145            Self::Not(condition) => condition.is_custom(),
3146            _ => false,
3147        }
3148    }
3149}
3150
3151/// Extract a field from a JSON value using dot notation
3152fn extract_field(value: &serde_json::Value, field: Option<&str>) -> serde_json::Value {
3153    match field {
3154        None => value.clone(),
3155        Some(path) => {
3156            let mut current = value;
3157            for part in path.split('.') {
3158                current = match current {
3159                    serde_json::Value::Object(map) => {
3160                        map.get(part).unwrap_or(&serde_json::Value::Null)
3161                    }
3162                    serde_json::Value::Array(arr) => {
3163                        if let Ok(idx) = part.parse::<usize>() {
3164                            arr.get(idx).unwrap_or(&serde_json::Value::Null)
3165                        } else {
3166                            &serde_json::Value::Null
3167                        }
3168                    }
3169                    _ => &serde_json::Value::Null,
3170                };
3171            }
3172            current.clone()
3173        }
3174    }
3175}
3176
3177/// Check if a JSON value is truthy
3178fn is_truthy(value: &serde_json::Value) -> bool {
3179    match value {
3180        serde_json::Value::Null => false,
3181        serde_json::Value::Bool(b) => *b,
3182        serde_json::Value::Number(n) => n.as_f64().is_some_and(|v| v != 0.0),
3183        serde_json::Value::String(s) => !s.is_empty(),
3184        serde_json::Value::Array(a) => !a.is_empty(),
3185        serde_json::Value::Object(o) => !o.is_empty(),
3186    }
3187}
3188
3189/// Check if a JSON value contains another value
3190fn contains_value(haystack: &serde_json::Value, needle: &serde_json::Value) -> bool {
3191    match haystack {
3192        serde_json::Value::String(s) => {
3193            if let Some(needle_str) = needle.as_str() {
3194                s.contains(needle_str)
3195            } else {
3196                false
3197            }
3198        }
3199        serde_json::Value::Array(arr) => arr.contains(needle),
3200        serde_json::Value::Object(map) => {
3201            if let Some(key) = needle.as_str() {
3202                map.contains_key(key)
3203            } else {
3204                false
3205            }
3206        }
3207        _ => false,
3208    }
3209}
3210
3211impl std::fmt::Display for Condition {
3212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3213        match self {
3214            Self::Always => write!(f, "always"),
3215            Self::Never => write!(f, "never"),
3216            Self::Equals { field, value } => {
3217                if let Some(field) = field {
3218                    write!(f, "{} == {}", field, value)
3219                } else {
3220                    write!(f, "result == {}", value)
3221                }
3222            }
3223            Self::NotEquals { field, value } => {
3224                if let Some(field) = field {
3225                    write!(f, "{} != {}", field, value)
3226                } else {
3227                    write!(f, "result != {}", value)
3228                }
3229            }
3230            Self::GreaterThan { field, threshold } => {
3231                if let Some(field) = field {
3232                    write!(f, "{} > {}", field, threshold)
3233                } else {
3234                    write!(f, "result > {}", threshold)
3235                }
3236            }
3237            Self::LessThan { field, threshold } => {
3238                if let Some(field) = field {
3239                    write!(f, "{} < {}", field, threshold)
3240                } else {
3241                    write!(f, "result < {}", threshold)
3242                }
3243            }
3244            Self::Truthy { field } => {
3245                if let Some(field) = field {
3246                    write!(f, "truthy({})", field)
3247                } else {
3248                    write!(f, "truthy(result)")
3249                }
3250            }
3251            Self::Falsy { field } => {
3252                if let Some(field) = field {
3253                    write!(f, "falsy({})", field)
3254                } else {
3255                    write!(f, "falsy(result)")
3256                }
3257            }
3258            Self::Contains { field, value } => {
3259                if let Some(field) = field {
3260                    write!(f, "{} contains {}", field, value)
3261                } else {
3262                    write!(f, "result contains {}", value)
3263                }
3264            }
3265            Self::Matches { field, pattern } => {
3266                if let Some(field) = field {
3267                    write!(f, "{} matches /{}/", field, pattern)
3268                } else {
3269                    write!(f, "result matches /{}/", pattern)
3270                }
3271            }
3272            Self::And(conditions) => {
3273                let parts: Vec<String> = conditions.iter().map(|c| format!("{}", c)).collect();
3274                write!(f, "({})", parts.join(" AND "))
3275            }
3276            Self::Or(conditions) => {
3277                let parts: Vec<String> = conditions.iter().map(|c| format!("{}", c)).collect();
3278                write!(f, "({})", parts.join(" OR "))
3279            }
3280            Self::Not(condition) => write!(f, "NOT ({})", condition),
3281            Self::Custom { task, .. } => write!(f, "custom({})", task),
3282        }
3283    }
3284}
3285
3286/// Branch: Conditional workflow execution (if/else)
3287///
3288/// Executes different tasks/workflows based on a condition evaluated against
3289/// the result of a previous task.
3290///
3291/// # Example
3292/// ```
3293/// use celers_canvas::{Branch, Condition, Signature};
3294///
3295/// // Execute different tasks based on result value
3296/// let branch = Branch::new(
3297///     Condition::field_greater_than("count", 100.0),
3298///     Signature::new("process_large_batch".to_string()),
3299/// )
3300/// .otherwise(Signature::new("process_small_batch".to_string()));
3301/// ```
3302#[derive(Debug, Clone, Serialize, Deserialize)]
3303pub struct Branch {
3304    /// Condition to evaluate
3305    pub condition: Condition,
3306
3307    /// Task/workflow to execute if condition is true
3308    pub then_branch: Box<Signature>,
3309
3310    /// Optional task/workflow to execute if condition is false
3311    pub else_branch: Option<Box<Signature>>,
3312
3313    /// Pass the condition input to the branch tasks
3314    pub pass_result: bool,
3315}
3316
3317impl Branch {
3318    /// Create a new Branch workflow
3319    ///
3320    /// # Arguments
3321    /// * `condition` - The condition to evaluate
3322    /// * `then_sig` - The signature to execute if condition is true
3323    pub fn new(condition: Condition, then_sig: Signature) -> Self {
3324        Self {
3325            condition,
3326            then_branch: Box::new(then_sig),
3327            else_branch: None,
3328            pass_result: true,
3329        }
3330    }
3331
3332    /// Set the else branch
3333    pub fn otherwise(mut self, else_sig: Signature) -> Self {
3334        self.else_branch = Some(Box::new(else_sig));
3335        self
3336    }
3337
3338    /// Alias for `otherwise`
3339    pub fn else_do(self, else_sig: Signature) -> Self {
3340        self.otherwise(else_sig)
3341    }
3342
3343    /// Set whether to pass the input result to branch tasks
3344    pub fn with_pass_result(mut self, pass: bool) -> Self {
3345        self.pass_result = pass;
3346        self
3347    }
3348
3349    /// Check if there's an else branch
3350    pub fn has_else(&self) -> bool {
3351        self.else_branch.is_some()
3352    }
3353
3354    /// Evaluate the branch condition and return the appropriate signature
3355    ///
3356    /// Returns Some(signature) for the branch to execute, or None if condition
3357    /// is false and there's no else branch.
3358    pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
3359        let should_then = self.condition.evaluate(result);
3360
3361        let sig = if should_then {
3362            Some((*self.then_branch).clone())
3363        } else {
3364            self.else_branch.as_ref().map(|s| (**s).clone())
3365        };
3366
3367        // Pass the result as the first argument if enabled
3368        if let Some(mut sig) = sig {
3369            if self.pass_result && !sig.immutable {
3370                sig.args.insert(0, result.clone());
3371            }
3372            Some(sig)
3373        } else {
3374            None
3375        }
3376    }
3377}
3378
3379impl std::fmt::Display for Branch {
3380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3381        if let Some(else_branch) = &self.else_branch {
3382            write!(
3383                f,
3384                "Branch[if {} then {} else {}]",
3385                self.condition, self.then_branch.task, else_branch.task
3386            )
3387        } else {
3388            write!(
3389                f,
3390                "Branch[if {} then {}]",
3391                self.condition, self.then_branch.task
3392            )
3393        }
3394    }
3395}
3396
3397/// Maybe: Optional task execution based on condition
3398///
3399/// A simplified Branch that either executes a task or does nothing.
3400/// This is essentially `Branch` without an else clause.
3401///
3402/// # Example
3403/// ```
3404/// use celers_canvas::{Maybe, Condition, Signature};
3405///
3406/// // Only send notification if count > 0
3407/// let maybe = Maybe::new(
3408///     Condition::field_greater_than("count", 0.0),
3409///     Signature::new("send_notification".to_string()),
3410/// );
3411/// ```
3412#[derive(Debug, Clone, Serialize, Deserialize)]
3413pub struct Maybe {
3414    /// Condition to evaluate
3415    pub condition: Condition,
3416
3417    /// Task to execute if condition is true
3418    pub task: Signature,
3419
3420    /// Pass the condition input to the task
3421    pub pass_result: bool,
3422}
3423
3424impl Maybe {
3425    /// Create a new Maybe workflow
3426    pub fn new(condition: Condition, task: Signature) -> Self {
3427        Self {
3428            condition,
3429            task,
3430            pass_result: true,
3431        }
3432    }
3433
3434    /// Set whether to pass the input result to the task
3435    pub fn with_pass_result(mut self, pass: bool) -> Self {
3436        self.pass_result = pass;
3437        self
3438    }
3439
3440    /// Evaluate and return the task if condition is met
3441    pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
3442        if self.condition.evaluate(result) {
3443            let mut task = self.task.clone();
3444            if self.pass_result && !task.immutable {
3445                task.args.insert(0, result.clone());
3446            }
3447            Some(task)
3448        } else {
3449            None
3450        }
3451    }
3452
3453    /// Convert to a Branch (for unified handling)
3454    pub fn to_branch(self) -> Branch {
3455        Branch {
3456            condition: self.condition,
3457            then_branch: Box::new(self.task),
3458            else_branch: None,
3459            pass_result: self.pass_result,
3460        }
3461    }
3462}
3463
3464impl std::fmt::Display for Maybe {
3465    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3466        write!(f, "Maybe[if {} then {}]", self.condition, self.task.task)
3467    }
3468}
3469
3470/// Switch: Multi-way conditional branching
3471///
3472/// Like a switch/case statement - evaluates multiple conditions and executes
3473/// the first matching branch.
3474///
3475/// # Example
3476/// ```
3477/// use celers_canvas::{Switch, Condition, Signature};
3478///
3479/// let switch = Switch::new()
3480///     .case(
3481///         Condition::field_equals("status", serde_json::json!("pending")),
3482///         Signature::new("process_pending".to_string()),
3483///     )
3484///     .case(
3485///         Condition::field_equals("status", serde_json::json!("approved")),
3486///         Signature::new("process_approved".to_string()),
3487///     )
3488///     .default(Signature::new("process_unknown".to_string()));
3489/// ```
3490#[derive(Debug, Clone, Serialize, Deserialize)]
3491pub struct Switch {
3492    /// List of (condition, signature) pairs
3493    pub cases: Vec<(Condition, Signature)>,
3494
3495    /// Default signature if no conditions match
3496    pub default: Option<Signature>,
3497
3498    /// Pass the result to branch tasks
3499    pub pass_result: bool,
3500}
3501
3502impl Switch {
3503    /// Create a new empty Switch
3504    pub fn new() -> Self {
3505        Self {
3506            cases: Vec::new(),
3507            default: None,
3508            pass_result: true,
3509        }
3510    }
3511
3512    /// Add a case to the switch
3513    pub fn case(mut self, condition: Condition, task: Signature) -> Self {
3514        self.cases.push((condition, task));
3515        self
3516    }
3517
3518    /// Set the default case
3519    pub fn default(mut self, task: Signature) -> Self {
3520        self.default = Some(task);
3521        self
3522    }
3523
3524    /// Set whether to pass result to branch tasks
3525    pub fn with_pass_result(mut self, pass: bool) -> Self {
3526        self.pass_result = pass;
3527        self
3528    }
3529
3530    /// Check if switch is empty (no cases)
3531    pub fn is_empty(&self) -> bool {
3532        self.cases.is_empty()
3533    }
3534
3535    /// Get number of cases
3536    pub fn len(&self) -> usize {
3537        self.cases.len()
3538    }
3539
3540    /// Evaluate and return the matching task
3541    pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
3542        // Find first matching case
3543        for (condition, task) in &self.cases {
3544            if condition.evaluate(result) {
3545                let mut task = task.clone();
3546                if self.pass_result && !task.immutable {
3547                    task.args.insert(0, result.clone());
3548                }
3549                return Some(task);
3550            }
3551        }
3552
3553        // Return default if no match
3554        if let Some(default) = &self.default {
3555            let mut task = default.clone();
3556            if self.pass_result && !task.immutable {
3557                task.args.insert(0, result.clone());
3558            }
3559            Some(task)
3560        } else {
3561            None
3562        }
3563    }
3564}
3565
3566impl Default for Switch {
3567    fn default() -> Self {
3568        Self::new()
3569    }
3570}
3571
3572impl std::fmt::Display for Switch {
3573    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3574        let case_strs: Vec<String> = self
3575            .cases
3576            .iter()
3577            .map(|(c, t)| format!("{} => {}", c, t.task))
3578            .collect();
3579
3580        if let Some(default) = &self.default {
3581            write!(
3582                f,
3583                "Switch[{}, default => {}]",
3584                case_strs.join(", "),
3585                default.task
3586            )
3587        } else {
3588            write!(f, "Switch[{}]", case_strs.join(", "))
3589        }
3590    }
3591}
3592
3593// ============================================================================
3594// Nested Workflows
3595// ============================================================================
3596
3597/// A canvas element that can be either a simple signature or a nested workflow
3598///
3599/// This enables composing complex workflows where any step can be another workflow.
3600///
3601/// # Example
3602/// ```
3603/// use celers_canvas::{CanvasElement, Chain, Group, Signature, Chord};
3604///
3605/// // Create a chain where one element is a group (parallel tasks)
3606/// let element = CanvasElement::group(
3607///     Group::new()
3608///         .add("task1", vec![])
3609///         .add("task2", vec![])
3610/// );
3611///
3612/// // Create a nested chain of chords
3613/// let nested = CanvasElement::chain(
3614///     Chain::new()
3615///         .then("step1", vec![])
3616///         .then("step2", vec![])
3617/// );
3618/// ```
3619#[derive(Debug, Clone, Serialize, Deserialize)]
3620#[serde(tag = "element_type")]
3621pub enum CanvasElement {
3622    /// A simple task signature
3623    Signature(Signature),
3624
3625    /// A chain of tasks
3626    Chain(Chain),
3627
3628    /// A group of parallel tasks
3629    Group(Group),
3630
3631    /// A chord (group + callback)
3632    Chord {
3633        /// Header group
3634        header: Group,
3635        /// Callback signature
3636        body: Signature,
3637    },
3638
3639    /// A map operation
3640    Map {
3641        /// Task to apply
3642        task: Signature,
3643        /// Argument sets
3644        argsets: Vec<Vec<serde_json::Value>>,
3645    },
3646
3647    /// A conditional branch
3648    Branch(Branch),
3649
3650    /// A switch statement
3651    Switch(Switch),
3652}
3653
3654impl CanvasElement {
3655    /// Create a signature element
3656    pub fn signature(sig: Signature) -> Self {
3657        Self::Signature(sig)
3658    }
3659
3660    /// Create a task element (shorthand for signature)
3661    pub fn task(name: impl Into<String>, args: Vec<serde_json::Value>) -> Self {
3662        Self::Signature(Signature::new(name.into()).with_args(args))
3663    }
3664
3665    /// Create a chain element
3666    pub fn chain(chain: Chain) -> Self {
3667        Self::Chain(chain)
3668    }
3669
3670    /// Create a group element
3671    pub fn group(group: Group) -> Self {
3672        Self::Group(group)
3673    }
3674
3675    /// Create a chord element
3676    pub fn chord(header: Group, body: Signature) -> Self {
3677        Self::Chord { header, body }
3678    }
3679
3680    /// Create a map element
3681    pub fn map(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
3682        Self::Map { task, argsets }
3683    }
3684
3685    /// Create a branch element
3686    pub fn branch(branch: Branch) -> Self {
3687        Self::Branch(branch)
3688    }
3689
3690    /// Create a switch element
3691    pub fn switch(switch: Switch) -> Self {
3692        Self::Switch(switch)
3693    }
3694
3695    /// Check if this is a simple signature
3696    pub fn is_signature(&self) -> bool {
3697        matches!(self, Self::Signature(_))
3698    }
3699
3700    /// Check if this is a chain
3701    pub fn is_chain(&self) -> bool {
3702        matches!(self, Self::Chain(_))
3703    }
3704
3705    /// Check if this is a group
3706    pub fn is_group(&self) -> bool {
3707        matches!(self, Self::Group(_))
3708    }
3709
3710    /// Check if this is a chord
3711    pub fn is_chord(&self) -> bool {
3712        matches!(self, Self::Chord { .. })
3713    }
3714
3715    /// Get the element type as a string
3716    pub fn element_type(&self) -> &'static str {
3717        match self {
3718            Self::Signature(_) => "signature",
3719            Self::Chain(_) => "chain",
3720            Self::Group(_) => "group",
3721            Self::Chord { .. } => "chord",
3722            Self::Map { .. } => "map",
3723            Self::Branch(_) => "branch",
3724            Self::Switch(_) => "switch",
3725        }
3726    }
3727}
3728
3729impl std::fmt::Display for CanvasElement {
3730    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3731        match self {
3732            Self::Signature(sig) => write!(f, "Signature[{}]", sig.task),
3733            Self::Chain(chain) => write!(f, "{}", chain),
3734            Self::Group(group) => write!(f, "{}", group),
3735            Self::Chord { header, body } => {
3736                write!(f, "Chord[header={}, body={}]", header, body.task)
3737            }
3738            Self::Map { task, argsets } => {
3739                write!(f, "Map[task={}, {} argsets]", task.task, argsets.len())
3740            }
3741            Self::Branch(branch) => write!(f, "{}", branch),
3742            Self::Switch(switch) => write!(f, "{}", switch),
3743        }
3744    }
3745}
3746
3747impl From<Signature> for CanvasElement {
3748    fn from(sig: Signature) -> Self {
3749        Self::Signature(sig)
3750    }
3751}
3752
3753impl From<Chain> for CanvasElement {
3754    fn from(chain: Chain) -> Self {
3755        Self::Chain(chain)
3756    }
3757}
3758
3759impl From<Group> for CanvasElement {
3760    fn from(group: Group) -> Self {
3761        Self::Group(group)
3762    }
3763}
3764
3765impl From<Branch> for CanvasElement {
3766    fn from(branch: Branch) -> Self {
3767        Self::Branch(branch)
3768    }
3769}
3770
3771impl From<Switch> for CanvasElement {
3772    fn from(switch: Switch) -> Self {
3773        Self::Switch(switch)
3774    }
3775}
3776
3777/// A nested chain that can contain any canvas element
3778///
3779/// Unlike the basic Chain that only contains Signatures, NestedChain
3780/// can contain Groups, Chords, or other Chains as steps.
3781///
3782/// # Example
3783/// ```
3784/// use celers_canvas::{NestedChain, CanvasElement, Group, Signature};
3785///
3786/// let workflow = NestedChain::new()
3787///     .then_element(CanvasElement::task("step1".to_string(), vec![]))
3788///     .then_element(CanvasElement::group(
3789///         Group::new()
3790///             .add("parallel_a", vec![])
3791///             .add("parallel_b", vec![])
3792///     ))
3793///     .then_element(CanvasElement::task("step2".to_string(), vec![]));
3794/// ```
3795#[derive(Debug, Clone, Serialize, Deserialize)]
3796pub struct NestedChain {
3797    /// Elements in the chain
3798    pub elements: Vec<CanvasElement>,
3799}
3800
3801impl NestedChain {
3802    /// Create a new empty nested chain
3803    pub fn new() -> Self {
3804        Self {
3805            elements: Vec::new(),
3806        }
3807    }
3808
3809    /// Add an element to the chain
3810    pub fn then_element(mut self, element: CanvasElement) -> Self {
3811        self.elements.push(element);
3812        self
3813    }
3814
3815    /// Add a signature to the chain
3816    pub fn then_signature(mut self, sig: Signature) -> Self {
3817        self.elements.push(CanvasElement::Signature(sig));
3818        self
3819    }
3820
3821    /// Add a simple task to the chain
3822    pub fn then(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
3823        self.elements.push(CanvasElement::task(task, args));
3824        self
3825    }
3826
3827    /// Add a group to the chain (parallel execution point)
3828    pub fn then_group(mut self, group: Group) -> Self {
3829        self.elements.push(CanvasElement::Group(group));
3830        self
3831    }
3832
3833    /// Add a chord to the chain
3834    pub fn then_chord(mut self, header: Group, body: Signature) -> Self {
3835        self.elements.push(CanvasElement::Chord { header, body });
3836        self
3837    }
3838
3839    /// Add a branch to the chain
3840    pub fn then_branch(mut self, branch: Branch) -> Self {
3841        self.elements.push(CanvasElement::Branch(branch));
3842        self
3843    }
3844
3845    /// Add another chain as a nested element
3846    pub fn then_chain(mut self, chain: Chain) -> Self {
3847        self.elements.push(CanvasElement::Chain(chain));
3848        self
3849    }
3850
3851    /// Check if the chain is empty
3852    pub fn is_empty(&self) -> bool {
3853        self.elements.is_empty()
3854    }
3855
3856    /// Get the number of elements
3857    pub fn len(&self) -> usize {
3858        self.elements.len()
3859    }
3860
3861    /// Flatten the nested chain into a sequence of signatures where possible
3862    ///
3863    /// This is useful for simpler execution when nested workflows aren't needed.
3864    /// Note: This will return None if the chain contains elements that can't be
3865    /// flattened to signatures (groups, chords, etc.)
3866    pub fn flatten_signatures(&self) -> Option<Vec<Signature>> {
3867        let mut result = Vec::new();
3868
3869        for element in &self.elements {
3870            match element {
3871                CanvasElement::Signature(sig) => result.push(sig.clone()),
3872                CanvasElement::Chain(chain) => {
3873                    result.extend(chain.tasks.clone());
3874                }
3875                _ => return None, // Can't flatten non-signature elements
3876            }
3877        }
3878
3879        Some(result)
3880    }
3881
3882    /// Execute the nested chain sequentially
3883    ///
3884    /// Each element is executed in order. For complex elements (Groups, Chords),
3885    /// they are executed and we wait for them to start before continuing.
3886    /// Note: This executes elements sequentially but doesn't wait for completion,
3887    /// following Celery's async execution model.
3888    pub async fn apply<B: Broker>(&self, broker: &B) -> Result<Uuid, CanvasError> {
3889        if self.elements.is_empty() {
3890            return Err(CanvasError::Invalid(
3891                "NestedChain cannot be empty".to_string(),
3892            ));
3893        }
3894
3895        // Execute each element in sequence
3896        let mut last_id = None;
3897        for element in &self.elements {
3898            match element {
3899                CanvasElement::Signature(sig) => {
3900                    // Convert to Chain for sequential execution
3901                    let chain = Chain {
3902                        tasks: vec![sig.clone()],
3903                    };
3904                    last_id = Some(chain.apply(broker).await?);
3905                }
3906                CanvasElement::Chain(chain) => {
3907                    last_id = Some(chain.clone().apply(broker).await?);
3908                }
3909                CanvasElement::Group(group) => {
3910                    last_id = Some(group.clone().apply(broker).await?);
3911                }
3912                CanvasElement::Chord { header, body } => {
3913                    #[cfg(feature = "backend-redis")]
3914                    {
3915                        // Note: Chord requires backend, but we can't pass it here
3916                        // For now, fall back to just executing the group
3917                        last_id = Some(header.clone().apply(broker).await?);
3918                        // Callback would need to be manually triggered
3919                        let _ = body; // Silence unused warning
3920                    }
3921                    #[cfg(not(feature = "backend-redis"))]
3922                    {
3923                        last_id = Some(header.clone().apply(broker).await?);
3924                        let _ = body; // Silence unused warning
3925                    }
3926                }
3927                CanvasElement::Map { task, argsets } => {
3928                    let map = Map::new(task.clone(), argsets.clone());
3929                    last_id = Some(map.apply(broker).await?);
3930                }
3931                CanvasElement::Branch(_branch) => {
3932                    // Branches require runtime evaluation, skip for now
3933                    return Err(CanvasError::Invalid(
3934                        "Branch elements not supported in NestedChain.apply()".to_string(),
3935                    ));
3936                }
3937                CanvasElement::Switch(_switch) => {
3938                    // Switch requires runtime evaluation, skip for now
3939                    return Err(CanvasError::Invalid(
3940                        "Switch elements not supported in NestedChain.apply()".to_string(),
3941                    ));
3942                }
3943            }
3944        }
3945
3946        last_id.ok_or_else(|| CanvasError::Invalid("No elements executed".to_string()))
3947    }
3948}
3949
3950impl Default for NestedChain {
3951    fn default() -> Self {
3952        Self::new()
3953    }
3954}
3955
3956impl std::fmt::Display for NestedChain {
3957    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3958        let element_strs: Vec<String> = self.elements.iter().map(|e| format!("{}", e)).collect();
3959        write!(f, "NestedChain[{}]", element_strs.join(" -> "))
3960    }
3961}
3962
3963/// A nested group that can contain any canvas element
3964///
3965/// Unlike the basic Group that only contains Signatures, NestedGroup
3966/// can contain Chains, other Groups, or Chords as parallel tasks.
3967///
3968/// # Example
3969/// ```
3970/// use celers_canvas::{NestedGroup, CanvasElement, Chain, Signature};
3971///
3972/// let workflow = NestedGroup::new()
3973///     .add_element(CanvasElement::chain(
3974///         Chain::new().then("step1", vec![]).then("step2", vec![])
3975///     ))
3976///     .add_element(CanvasElement::chain(
3977///         Chain::new().then("step3", vec![]).then("step4", vec![])
3978///     ));
3979/// ```
3980#[derive(Debug, Clone, Serialize, Deserialize)]
3981pub struct NestedGroup {
3982    /// Elements in the group (executed in parallel)
3983    pub elements: Vec<CanvasElement>,
3984}
3985
3986impl NestedGroup {
3987    /// Create a new empty nested group
3988    pub fn new() -> Self {
3989        Self {
3990            elements: Vec::new(),
3991        }
3992    }
3993
3994    /// Add an element to the group
3995    pub fn add_element(mut self, element: CanvasElement) -> Self {
3996        self.elements.push(element);
3997        self
3998    }
3999
4000    /// Add a signature to the group
4001    pub fn add_signature(mut self, sig: Signature) -> Self {
4002        self.elements.push(CanvasElement::Signature(sig));
4003        self
4004    }
4005
4006    /// Add a simple task to the group
4007    pub fn add(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
4008        self.elements.push(CanvasElement::task(task, args));
4009        self
4010    }
4011
4012    /// Add a chain to the group
4013    pub fn add_chain(mut self, chain: Chain) -> Self {
4014        self.elements.push(CanvasElement::Chain(chain));
4015        self
4016    }
4017
4018    /// Check if the group is empty
4019    pub fn is_empty(&self) -> bool {
4020        self.elements.is_empty()
4021    }
4022
4023    /// Get the number of elements
4024    pub fn len(&self) -> usize {
4025        self.elements.len()
4026    }
4027
4028    /// Flatten to signatures if possible
4029    pub fn flatten_signatures(&self) -> Option<Vec<Signature>> {
4030        let mut result = Vec::new();
4031
4032        for element in &self.elements {
4033            match element {
4034                CanvasElement::Signature(sig) => result.push(sig.clone()),
4035                _ => return None,
4036            }
4037        }
4038
4039        Some(result)
4040    }
4041
4042    /// Execute all elements in parallel
4043    ///
4044    /// All elements in the group are started concurrently.
4045    /// Returns a group ID that can be used to track the parallel execution.
4046    pub async fn apply<B: Broker>(&self, broker: &B) -> Result<Uuid, CanvasError> {
4047        if self.elements.is_empty() {
4048            return Err(CanvasError::Invalid(
4049                "NestedGroup cannot be empty".to_string(),
4050            ));
4051        }
4052
4053        // Generate a group ID for tracking
4054        let group_id = Uuid::new_v4();
4055
4056        // Execute all elements in parallel
4057        for element in &self.elements {
4058            match element {
4059                CanvasElement::Signature(sig) => {
4060                    let chain = Chain {
4061                        tasks: vec![sig.clone()],
4062                    };
4063                    chain.apply(broker).await?;
4064                }
4065                CanvasElement::Chain(chain) => {
4066                    chain.clone().apply(broker).await?;
4067                }
4068                CanvasElement::Group(group) => {
4069                    group.clone().apply(broker).await?;
4070                }
4071                CanvasElement::Chord { header, body } => {
4072                    #[cfg(feature = "backend-redis")]
4073                    {
4074                        // Note: Chord requires backend, but we can't pass it here
4075                        // For now, fall back to just executing the group
4076                        header.clone().apply(broker).await?;
4077                        let _ = body; // Silence unused warning
4078                    }
4079                    #[cfg(not(feature = "backend-redis"))]
4080                    {
4081                        header.clone().apply(broker).await?;
4082                        let _ = body; // Silence unused warning
4083                    }
4084                }
4085                CanvasElement::Map { task, argsets } => {
4086                    let map = Map::new(task.clone(), argsets.clone());
4087                    map.apply(broker).await?;
4088                }
4089                CanvasElement::Branch(_branch) => {
4090                    // Branches require runtime evaluation, skip for now
4091                    return Err(CanvasError::Invalid(
4092                        "Branch elements not supported in NestedGroup.apply()".to_string(),
4093                    ));
4094                }
4095                CanvasElement::Switch(_switch) => {
4096                    // Switch requires runtime evaluation, skip for now
4097                    return Err(CanvasError::Invalid(
4098                        "Switch elements not supported in NestedGroup.apply()".to_string(),
4099                    ));
4100                }
4101            }
4102        }
4103
4104        Ok(group_id)
4105    }
4106}
4107
4108impl Default for NestedGroup {
4109    fn default() -> Self {
4110        Self::new()
4111    }
4112}
4113
4114impl std::fmt::Display for NestedGroup {
4115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4116        let element_strs: Vec<String> = self.elements.iter().map(|e| format!("{}", e)).collect();
4117        write!(f, "NestedGroup[{}]", element_strs.join(" | "))
4118    }
4119}
4120
4121/// Error handling strategy for workflows
4122///
4123/// Determines how the workflow should behave when a task fails.
4124#[derive(Debug, Clone, Serialize, Deserialize, Default)]
4125pub enum ErrorStrategy {
4126    /// Stop the workflow on first error (default)
4127    #[default]
4128    StopOnError,
4129
4130    /// Continue even if some tasks fail
4131    ContinueOnError,
4132
4133    /// Retry the failed task a number of times before failing
4134    RetryOnError {
4135        /// Maximum number of retries
4136        max_retries: u32,
4137        /// Delay between retries in seconds
4138        delay: Option<u64>,
4139    },
4140
4141    /// Execute a fallback task on error
4142    Fallback {
4143        /// Fallback task to execute
4144        fallback: Signature,
4145    },
4146
4147    /// Execute an error handler task that receives the error info
4148    ErrorHandler {
4149        /// Error handler task name
4150        handler: Signature,
4151    },
4152}
4153
4154impl ErrorStrategy {
4155    /// Create a stop-on-error strategy
4156    pub fn stop() -> Self {
4157        Self::StopOnError
4158    }
4159
4160    /// Create a continue-on-error strategy
4161    pub fn continue_on_error() -> Self {
4162        Self::ContinueOnError
4163    }
4164
4165    /// Create a retry strategy
4166    pub fn retry(max_retries: u32) -> Self {
4167        Self::RetryOnError {
4168            max_retries,
4169            delay: None,
4170        }
4171    }
4172
4173    /// Create a retry strategy with delay
4174    pub fn retry_with_delay(max_retries: u32, delay: u64) -> Self {
4175        Self::RetryOnError {
4176            max_retries,
4177            delay: Some(delay),
4178        }
4179    }
4180
4181    /// Create a fallback strategy
4182    pub fn fallback(task: Signature) -> Self {
4183        Self::Fallback { fallback: task }
4184    }
4185
4186    /// Create an error handler strategy
4187    pub fn error_handler(handler: Signature) -> Self {
4188        Self::ErrorHandler { handler }
4189    }
4190
4191    /// Check if this strategy allows continuing on error
4192    pub fn allows_continue(&self) -> bool {
4193        !matches!(self, Self::StopOnError)
4194    }
4195}
4196
4197impl std::fmt::Display for ErrorStrategy {
4198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4199        match self {
4200            Self::StopOnError => write!(f, "StopOnError"),
4201            Self::ContinueOnError => write!(f, "ContinueOnError"),
4202            Self::RetryOnError { max_retries, delay } => {
4203                if let Some(d) = delay {
4204                    write!(f, "RetryOnError({} times, {}s delay)", max_retries, d)
4205                } else {
4206                    write!(f, "RetryOnError({} times)", max_retries)
4207                }
4208            }
4209            Self::Fallback { fallback } => write!(f, "Fallback({})", fallback.task),
4210            Self::ErrorHandler { handler } => write!(f, "ErrorHandler({})", handler.task),
4211        }
4212    }
4213}
4214
4215// ============================================================================
4216// Workflow Cancellation
4217// ============================================================================
4218
4219/// Cancellation token for workflow cancellation
4220#[derive(Debug, Clone, Serialize, Deserialize)]
4221pub struct CancellationToken {
4222    /// Workflow ID
4223    pub workflow_id: Uuid,
4224    /// Cancellation reason
4225    pub reason: Option<String>,
4226    /// Whether to cancel entire tree (including sub-workflows)
4227    pub cancel_tree: bool,
4228    /// Whether to cancel only specific branch
4229    pub branch_id: Option<Uuid>,
4230}
4231
4232impl CancellationToken {
4233    /// Create a new cancellation token for a workflow
4234    pub fn new(workflow_id: Uuid) -> Self {
4235        Self {
4236            workflow_id,
4237            reason: None,
4238            cancel_tree: false,
4239            branch_id: None,
4240        }
4241    }
4242
4243    /// Set cancellation reason
4244    pub fn with_reason(mut self, reason: String) -> Self {
4245        self.reason = Some(reason);
4246        self
4247    }
4248
4249    /// Cancel entire workflow tree
4250    pub fn cancel_tree(mut self) -> Self {
4251        self.cancel_tree = true;
4252        self
4253    }
4254
4255    /// Cancel only specific branch
4256    pub fn cancel_branch(mut self, branch_id: Uuid) -> Self {
4257        self.branch_id = Some(branch_id);
4258        self
4259    }
4260}
4261
4262impl std::fmt::Display for CancellationToken {
4263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4264        write!(f, "CancellationToken[workflow={}]", self.workflow_id)?;
4265        if let Some(ref reason) = self.reason {
4266            write!(f, " reason={}", reason)?;
4267        }
4268        if self.cancel_tree {
4269            write!(f, " (tree)")?;
4270        }
4271        if let Some(branch) = self.branch_id {
4272            write!(f, " branch={}", branch)?;
4273        }
4274        Ok(())
4275    }
4276}
4277
4278// ============================================================================
4279// Workflow Retry Policies
4280// ============================================================================
4281
4282/// Workflow-level retry policy
4283#[derive(Debug, Clone, Serialize, Deserialize)]
4284pub struct WorkflowRetryPolicy {
4285    /// Maximum number of retries for entire workflow
4286    pub max_retries: u32,
4287    /// Retry only failed branches
4288    pub retry_failed_only: bool,
4289    /// Exponential backoff factor
4290    pub backoff_factor: Option<f64>,
4291    /// Maximum backoff delay in seconds
4292    pub max_backoff: Option<u64>,
4293    /// Initial retry delay in seconds
4294    pub initial_delay: Option<u64>,
4295}
4296
4297impl WorkflowRetryPolicy {
4298    /// Create a new retry policy
4299    pub fn new(max_retries: u32) -> Self {
4300        Self {
4301            max_retries,
4302            retry_failed_only: false,
4303            backoff_factor: None,
4304            max_backoff: None,
4305            initial_delay: None,
4306        }
4307    }
4308
4309    /// Retry only failed branches
4310    pub fn failed_only(mut self) -> Self {
4311        self.retry_failed_only = true;
4312        self
4313    }
4314
4315    /// Set exponential backoff
4316    pub fn with_backoff(mut self, factor: f64, max_delay: u64) -> Self {
4317        self.backoff_factor = Some(factor);
4318        self.max_backoff = Some(max_delay);
4319        self
4320    }
4321
4322    /// Set initial delay
4323    pub fn with_initial_delay(mut self, delay: u64) -> Self {
4324        self.initial_delay = Some(delay);
4325        self
4326    }
4327
4328    /// Calculate delay for retry attempt
4329    pub fn calculate_delay(&self, attempt: u32) -> u64 {
4330        let base_delay = self.initial_delay.unwrap_or(1);
4331
4332        if let Some(factor) = self.backoff_factor {
4333            let delay = (base_delay as f64) * factor.powi(attempt as i32);
4334            let max = self.max_backoff.unwrap_or(300);
4335            delay.min(max as f64) as u64
4336        } else {
4337            base_delay
4338        }
4339    }
4340}
4341
4342impl std::fmt::Display for WorkflowRetryPolicy {
4343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4344        write!(f, "WorkflowRetryPolicy[max_retries={}]", self.max_retries)?;
4345        if self.retry_failed_only {
4346            write!(f, " (failed_only)")?;
4347        }
4348        if let Some(factor) = self.backoff_factor {
4349            write!(f, " backoff={}", factor)?;
4350        }
4351        Ok(())
4352    }
4353}
4354
4355// ============================================================================
4356// Workflow Timeout
4357// ============================================================================
4358
4359/// Workflow-level timeout configuration
4360#[derive(Debug, Clone, Serialize, Deserialize)]
4361pub struct WorkflowTimeout {
4362    /// Global workflow timeout in seconds
4363    pub total_timeout: Option<u64>,
4364    /// Per-stage timeout in seconds
4365    pub stage_timeout: Option<u64>,
4366    /// Timeout escalation - what to do on timeout
4367    pub escalation: TimeoutEscalation,
4368}
4369
4370/// Timeout escalation strategy
4371#[derive(Debug, Clone, Serialize, Deserialize)]
4372pub enum TimeoutEscalation {
4373    /// Cancel the workflow
4374    Cancel,
4375    /// Fail the workflow
4376    Fail,
4377    /// Continue with partial results
4378    ContinuePartial,
4379}
4380
4381impl WorkflowTimeout {
4382    /// Create a new timeout configuration
4383    pub fn new(total_timeout: u64) -> Self {
4384        Self {
4385            total_timeout: Some(total_timeout),
4386            stage_timeout: None,
4387            escalation: TimeoutEscalation::Cancel,
4388        }
4389    }
4390
4391    /// Set per-stage timeout
4392    pub fn with_stage_timeout(mut self, timeout: u64) -> Self {
4393        self.stage_timeout = Some(timeout);
4394        self
4395    }
4396
4397    /// Set escalation strategy
4398    pub fn with_escalation(mut self, escalation: TimeoutEscalation) -> Self {
4399        self.escalation = escalation;
4400        self
4401    }
4402}
4403
4404impl std::fmt::Display for WorkflowTimeout {
4405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4406        write!(f, "WorkflowTimeout[")?;
4407        if let Some(total) = self.total_timeout {
4408            write!(f, "total={}s", total)?;
4409        }
4410        if let Some(stage) = self.stage_timeout {
4411            write!(f, " stage={}s", stage)?;
4412        }
4413        write!(f, " escalation={:?}]", self.escalation)
4414    }
4415}
4416
4417// ============================================================================
4418// Workflow Loops
4419// ============================================================================
4420
4421/// For-each loop over a collection
4422#[derive(Debug, Clone, Serialize, Deserialize)]
4423pub struct ForEach {
4424    /// Task to execute for each item
4425    pub task: Signature,
4426    /// Collection to iterate over
4427    pub items: Vec<serde_json::Value>,
4428    /// Maximum parallel execution
4429    pub concurrency: Option<usize>,
4430}
4431
4432impl ForEach {
4433    /// Create a new for-each loop
4434    pub fn new(task: Signature, items: Vec<serde_json::Value>) -> Self {
4435        Self {
4436            task,
4437            items,
4438            concurrency: None,
4439        }
4440    }
4441
4442    /// Set maximum concurrent executions
4443    pub fn with_concurrency(mut self, concurrency: usize) -> Self {
4444        self.concurrency = Some(concurrency);
4445        self
4446    }
4447
4448    /// Check if loop is empty
4449    pub fn is_empty(&self) -> bool {
4450        self.items.is_empty()
4451    }
4452
4453    /// Get number of iterations
4454    pub fn len(&self) -> usize {
4455        self.items.len()
4456    }
4457}
4458
4459impl std::fmt::Display for ForEach {
4460    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4461        write!(f, "ForEach[task={}, {} items]", self.task.task, self.len())?;
4462        if let Some(conc) = self.concurrency {
4463            write!(f, " concurrency={}", conc)?;
4464        }
4465        Ok(())
4466    }
4467}
4468
4469/// While loop with condition
4470#[derive(Debug, Clone, Serialize, Deserialize)]
4471pub struct WhileLoop {
4472    /// Condition to evaluate
4473    pub condition: Condition,
4474    /// Task to execute while condition is true
4475    pub body: Signature,
4476    /// Maximum iterations (safety limit)
4477    pub max_iterations: Option<u32>,
4478}
4479
4480impl WhileLoop {
4481    /// Create a new while loop
4482    pub fn new(condition: Condition, body: Signature) -> Self {
4483        Self {
4484            condition,
4485            body,
4486            max_iterations: Some(1000), // Default safety limit
4487        }
4488    }
4489
4490    /// Set maximum iterations
4491    pub fn with_max_iterations(mut self, max: u32) -> Self {
4492        self.max_iterations = Some(max);
4493        self
4494    }
4495
4496    /// Remove iteration limit (use with caution!)
4497    pub fn unlimited(mut self) -> Self {
4498        self.max_iterations = None;
4499        self
4500    }
4501}
4502
4503impl std::fmt::Display for WhileLoop {
4504    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4505        write!(f, "While[{} -> {}]", self.condition, self.body.task)?;
4506        if let Some(max) = self.max_iterations {
4507            write!(f, " max={}", max)?;
4508        }
4509        Ok(())
4510    }
4511}
4512
4513// ============================================================================
4514// Workflow State Tracking
4515// ============================================================================
4516
4517/// Workflow execution state
4518#[derive(Debug, Clone, Serialize, Deserialize)]
4519pub struct WorkflowState {
4520    /// Workflow ID
4521    pub workflow_id: Uuid,
4522    /// Current status
4523    pub status: WorkflowStatus,
4524    /// Total tasks in workflow
4525    pub total_tasks: usize,
4526    /// Completed tasks
4527    pub completed_tasks: usize,
4528    /// Failed tasks
4529    pub failed_tasks: usize,
4530    /// Start time (Unix timestamp)
4531    pub start_time: Option<u64>,
4532    /// End time (Unix timestamp)
4533    pub end_time: Option<u64>,
4534    /// Current stage
4535    pub current_stage: Option<String>,
4536    /// Intermediate results
4537    pub intermediate_results: HashMap<String, serde_json::Value>,
4538}
4539
4540/// Workflow status
4541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4542pub enum WorkflowStatus {
4543    /// Workflow is pending
4544    Pending,
4545    /// Workflow is running
4546    Running,
4547    /// Workflow completed successfully
4548    Success,
4549    /// Workflow failed
4550    Failed,
4551    /// Workflow was cancelled
4552    Cancelled,
4553    /// Workflow is paused
4554    Paused,
4555}
4556
4557impl WorkflowState {
4558    /// Create a new workflow state
4559    pub fn new(workflow_id: Uuid, total_tasks: usize) -> Self {
4560        Self {
4561            workflow_id,
4562            status: WorkflowStatus::Pending,
4563            total_tasks,
4564            completed_tasks: 0,
4565            failed_tasks: 0,
4566            start_time: None,
4567            end_time: None,
4568            current_stage: None,
4569            intermediate_results: HashMap::new(),
4570        }
4571    }
4572
4573    /// Calculate progress percentage (0-100)
4574    pub fn progress(&self) -> f64 {
4575        if self.total_tasks == 0 {
4576            return 100.0;
4577        }
4578        (self.completed_tasks as f64 / self.total_tasks as f64) * 100.0
4579    }
4580
4581    /// Check if workflow is complete
4582    pub fn is_complete(&self) -> bool {
4583        matches!(
4584            self.status,
4585            WorkflowStatus::Success | WorkflowStatus::Failed | WorkflowStatus::Cancelled
4586        )
4587    }
4588
4589    /// Mark task as completed
4590    pub fn mark_completed(&mut self) {
4591        self.completed_tasks += 1;
4592    }
4593
4594    /// Mark task as failed
4595    pub fn mark_failed(&mut self) {
4596        self.failed_tasks += 1;
4597    }
4598
4599    /// Set intermediate result
4600    pub fn set_result(&mut self, key: String, value: serde_json::Value) {
4601        self.intermediate_results.insert(key, value);
4602    }
4603
4604    /// Get intermediate result
4605    pub fn get_result(&self, key: &str) -> Option<&serde_json::Value> {
4606        self.intermediate_results.get(key)
4607    }
4608}
4609
4610impl std::fmt::Display for WorkflowState {
4611    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4612        write!(
4613            f,
4614            "WorkflowState[id={}, status={:?}, progress={:.1}%]",
4615            self.workflow_id,
4616            self.status,
4617            self.progress()
4618        )?;
4619        if self.failed_tasks > 0 {
4620            write!(f, " failed={}", self.failed_tasks)?;
4621        }
4622        Ok(())
4623    }
4624}
4625
4626// ============================================================================
4627// DAG Export
4628// ============================================================================
4629
4630/// DAG export format
4631#[derive(Debug, Clone, Copy)]
4632pub enum DagFormat {
4633    /// GraphViz DOT format
4634    Dot,
4635    /// Mermaid diagram format
4636    Mermaid,
4637    /// JSON representation
4638    Json,
4639    /// SVG format (rendered from DOT)
4640    Svg,
4641    /// PNG format (rendered from DOT)
4642    Png,
4643}
4644
4645/// Render DOT format to SVG using GraphViz dot command
4646///
4647/// Requires GraphViz to be installed on the system.
4648/// Executes: `dot -Tsvg`
4649///
4650/// # Errors
4651/// Returns error if GraphViz is not installed or execution fails
4652fn render_dot_to_svg(dot: &str) -> Result<String, CanvasError> {
4653    use std::io::Write;
4654    use std::process::{Command, Stdio};
4655
4656    let mut child = Command::new("dot")
4657        .arg("-Tsvg")
4658        .stdin(Stdio::piped())
4659        .stdout(Stdio::piped())
4660        .stderr(Stdio::piped())
4661        .spawn()
4662        .map_err(|e| {
4663            CanvasError::Invalid(format!(
4664                "Failed to execute 'dot' command. Is GraphViz installed? Error: {}",
4665                e
4666            ))
4667        })?;
4668
4669    if let Some(mut stdin) = child.stdin.take() {
4670        stdin
4671            .write_all(dot.as_bytes())
4672            .map_err(|e| CanvasError::Invalid(format!("Failed to write DOT to stdin: {}", e)))?;
4673    }
4674
4675    let output = child
4676        .wait_with_output()
4677        .map_err(|e| CanvasError::Invalid(format!("Failed to wait for dot process: {}", e)))?;
4678
4679    if !output.status.success() {
4680        let stderr = String::from_utf8_lossy(&output.stderr);
4681        return Err(CanvasError::Invalid(format!(
4682            "dot command failed: {}",
4683            stderr
4684        )));
4685    }
4686
4687    String::from_utf8(output.stdout)
4688        .map_err(|e| CanvasError::Invalid(format!("Invalid UTF-8 in SVG output: {}", e)))
4689}
4690
4691/// Render DOT format to PNG using GraphViz dot command
4692///
4693/// Requires GraphViz to be installed on the system.
4694/// Executes: `dot -Tpng`
4695///
4696/// # Errors
4697/// Returns error if GraphViz is not installed or execution fails
4698fn render_dot_to_png(dot: &str) -> Result<Vec<u8>, CanvasError> {
4699    use std::io::Write;
4700    use std::process::{Command, Stdio};
4701
4702    let mut child = Command::new("dot")
4703        .arg("-Tpng")
4704        .stdin(Stdio::piped())
4705        .stdout(Stdio::piped())
4706        .stderr(Stdio::piped())
4707        .spawn()
4708        .map_err(|e| {
4709            CanvasError::Invalid(format!(
4710                "Failed to execute 'dot' command. Is GraphViz installed? Error: {}",
4711                e
4712            ))
4713        })?;
4714
4715    if let Some(mut stdin) = child.stdin.take() {
4716        stdin
4717            .write_all(dot.as_bytes())
4718            .map_err(|e| CanvasError::Invalid(format!("Failed to write DOT to stdin: {}", e)))?;
4719    }
4720
4721    let output = child
4722        .wait_with_output()
4723        .map_err(|e| CanvasError::Invalid(format!("Failed to wait for dot process: {}", e)))?;
4724
4725    if !output.status.success() {
4726        let stderr = String::from_utf8_lossy(&output.stderr);
4727        return Err(CanvasError::Invalid(format!(
4728            "dot command failed: {}",
4729            stderr
4730        )));
4731    }
4732
4733    Ok(output.stdout)
4734}
4735
4736/// Check if GraphViz dot command is available
4737///
4738/// # Example
4739/// ```no_run
4740/// if celers_canvas::is_graphviz_available() {
4741///     println!("GraphViz is installed");
4742/// } else {
4743///     println!("GraphViz is not installed");
4744/// }
4745/// ```
4746#[allow(dead_code)]
4747pub fn is_graphviz_available() -> bool {
4748    use std::process::Command;
4749
4750    Command::new("dot")
4751        .arg("-V")
4752        .output()
4753        .map(|output| output.status.success())
4754        .unwrap_or(false)
4755}
4756
4757/// Trait for exporting workflow as DAG
4758pub trait DagExport {
4759    /// Export workflow as GraphViz DOT
4760    fn to_dot(&self) -> String;
4761
4762    /// Export workflow as Mermaid diagram
4763    fn to_mermaid(&self) -> String;
4764
4765    /// Export workflow as JSON
4766    fn to_json(&self) -> Result<String, serde_json::Error>;
4767
4768    /// Export workflow as SVG using GraphViz dot command
4769    ///
4770    /// This method generates SVG by executing the `dot` command.
4771    /// Requires GraphViz to be installed on the system.
4772    ///
4773    /// # Errors
4774    /// Returns error if GraphViz is not installed or execution fails
4775    fn to_svg(&self) -> Result<String, CanvasError> {
4776        let dot = self.to_dot();
4777        render_dot_to_svg(&dot)
4778    }
4779
4780    /// Export workflow as PNG using GraphViz dot command
4781    ///
4782    /// This method generates PNG by executing the `dot` command.
4783    /// Requires GraphViz to be installed on the system.
4784    ///
4785    /// # Errors
4786    /// Returns error if GraphViz is not installed or execution fails
4787    fn to_png(&self) -> Result<Vec<u8>, CanvasError> {
4788        let dot = self.to_dot();
4789        render_dot_to_png(&dot)
4790    }
4791
4792    /// Get command to render DOT to SVG
4793    ///
4794    /// Returns the shell command that can be used to convert
4795    /// the DOT format to SVG. Useful for manual rendering.
4796    fn svg_render_command(&self) -> String {
4797        "dot -Tsvg -o output.svg input.dot".to_string()
4798    }
4799
4800    /// Get command to render DOT to PNG
4801    ///
4802    /// Returns the shell command that can be used to convert
4803    /// the DOT format to PNG. Useful for manual rendering.
4804    fn png_render_command(&self) -> String {
4805        "dot -Tpng -o output.png input.dot".to_string()
4806    }
4807}
4808
4809impl DagExport for Chain {
4810    fn to_dot(&self) -> String {
4811        let mut dot = String::from("digraph Chain {\n");
4812        dot.push_str("  rankdir=LR;\n");
4813        dot.push_str("  node [shape=box];\n\n");
4814
4815        for (i, task) in self.tasks.iter().enumerate() {
4816            dot.push_str(&format!("  n{} [label=\"{}\"];\n", i, task.task));
4817            if i > 0 {
4818                dot.push_str(&format!("  n{} -> n{};\n", i - 1, i));
4819            }
4820        }
4821
4822        dot.push_str("}\n");
4823        dot
4824    }
4825
4826    fn to_mermaid(&self) -> String {
4827        let mut mmd = String::from("graph LR\n");
4828
4829        for (i, task) in self.tasks.iter().enumerate() {
4830            let node_id = format!("n{}", i);
4831            mmd.push_str(&format!("  {}[\"{}\"]\n", node_id, task.task));
4832            if i > 0 {
4833                mmd.push_str(&format!("  n{} --> n{}\n", i - 1, i));
4834            }
4835        }
4836
4837        mmd
4838    }
4839
4840    fn to_json(&self) -> Result<String, serde_json::Error> {
4841        serde_json::to_string_pretty(self)
4842    }
4843}
4844
4845impl DagExport for Group {
4846    fn to_dot(&self) -> String {
4847        let mut dot = String::from("digraph Group {\n");
4848        dot.push_str("  rankdir=TB;\n");
4849        dot.push_str("  node [shape=box];\n\n");
4850        dot.push_str("  start [shape=circle, label=\"start\"];\n");
4851
4852        for (i, task) in self.tasks.iter().enumerate() {
4853            dot.push_str(&format!("  n{} [label=\"{}\"];\n", i, task.task));
4854            dot.push_str(&format!("  start -> n{};\n", i));
4855        }
4856
4857        dot.push_str("}\n");
4858        dot
4859    }
4860
4861    fn to_mermaid(&self) -> String {
4862        let mut mmd = String::from("graph TB\n");
4863        mmd.push_str("  start((start))\n");
4864
4865        for (i, task) in self.tasks.iter().enumerate() {
4866            mmd.push_str(&format!("  n{}[\"{}\"]\n", i, task.task));
4867            mmd.push_str(&format!("  start --> n{}\n", i));
4868        }
4869
4870        mmd
4871    }
4872
4873    fn to_json(&self) -> Result<String, serde_json::Error> {
4874        serde_json::to_string_pretty(self)
4875    }
4876}
4877
4878impl DagExport for Chord {
4879    fn to_dot(&self) -> String {
4880        let mut dot = String::from("digraph Chord {\n");
4881        dot.push_str("  rankdir=TB;\n");
4882        dot.push_str("  node [shape=box];\n\n");
4883        dot.push_str("  start [shape=circle, label=\"start\"];\n");
4884        dot.push_str(&format!(
4885            "  callback [label=\"{}\", style=filled, fillcolor=lightblue];\n",
4886            self.body.task
4887        ));
4888
4889        for (i, task) in self.header.tasks.iter().enumerate() {
4890            dot.push_str(&format!("  n{} [label=\"{}\"];\n", i, task.task));
4891            dot.push_str(&format!("  start -> n{};\n", i));
4892            dot.push_str(&format!("  n{} -> callback;\n", i));
4893        }
4894
4895        dot.push_str("}\n");
4896        dot
4897    }
4898
4899    fn to_mermaid(&self) -> String {
4900        let mut mmd = String::from("graph TB\n");
4901        mmd.push_str("  start((start))\n");
4902        mmd.push_str(&format!("  callback[\"{}\"]\n", self.body.task));
4903        mmd.push_str("  style callback fill:#add8e6\n");
4904
4905        for (i, task) in self.header.tasks.iter().enumerate() {
4906            mmd.push_str(&format!("  n{}[\"{}\"]\n", i, task.task));
4907            mmd.push_str(&format!("  start --> n{}\n", i));
4908            mmd.push_str(&format!("  n{} --> callback\n", i));
4909        }
4910
4911        mmd
4912    }
4913
4914    fn to_json(&self) -> Result<String, serde_json::Error> {
4915        serde_json::to_string_pretty(self)
4916    }
4917}
4918
4919// ============================================================================
4920// Advanced Result Passing
4921// ============================================================================
4922
4923/// Named output for result passing
4924#[derive(Debug, Clone, Serialize, Deserialize)]
4925pub struct NamedOutput {
4926    /// Output name
4927    pub name: String,
4928    /// Output value
4929    pub value: serde_json::Value,
4930    /// Source task
4931    pub source: Option<String>,
4932}
4933
4934impl NamedOutput {
4935    /// Create a new named output
4936    pub fn new(name: impl Into<String>, value: serde_json::Value) -> Self {
4937        Self {
4938            name: name.into(),
4939            value,
4940            source: None,
4941        }
4942    }
4943
4944    /// Set source task
4945    pub fn with_source(mut self, source: impl Into<String>) -> Self {
4946        self.source = Some(source.into());
4947        self
4948    }
4949}
4950
4951/// Result transformation function
4952#[derive(Debug, Clone, Serialize, Deserialize)]
4953pub enum ResultTransform {
4954    /// Extract a field from result
4955    Extract { field: String },
4956    /// Map result through a task
4957    Map { task: Box<Signature> },
4958    /// Filter result based on condition
4959    Filter { condition: Condition },
4960    /// Aggregate multiple results
4961    Aggregate { strategy: AggregationStrategy },
4962}
4963
4964/// Aggregation strategy for combining results
4965#[derive(Debug, Clone, Serialize, Deserialize)]
4966pub enum AggregationStrategy {
4967    /// Sum numeric results
4968    Sum,
4969    /// Average numeric results
4970    Average,
4971    /// Concatenate arrays
4972    Concat,
4973    /// Merge objects
4974    Merge,
4975    /// Take first non-null result
4976    Coalesce,
4977    /// Custom aggregation task
4978    Custom { task: Box<Signature> },
4979}
4980
4981impl std::fmt::Display for ResultTransform {
4982    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4983        match self {
4984            Self::Extract { field } => write!(f, "Extract[{}]", field),
4985            Self::Map { task } => write!(f, "Map[{}]", task.task),
4986            Self::Filter { condition } => write!(f, "Filter[{}]", condition),
4987            Self::Aggregate { strategy } => write!(f, "Aggregate[{:?}]", strategy),
4988        }
4989    }
4990}
4991
4992// ============================================================================
4993// Result Caching
4994// ============================================================================
4995
4996/// Result cache configuration
4997#[derive(Debug, Clone, Serialize, Deserialize)]
4998pub struct ResultCache {
4999    /// Cache key
5000    pub key: String,
5001    /// Cache policy
5002    pub policy: CachePolicy,
5003    /// Time-to-live in seconds
5004    pub ttl: Option<u64>,
5005}
5006
5007/// Cache policy
5008#[derive(Debug, Clone, Serialize, Deserialize)]
5009pub enum CachePolicy {
5010    /// Always cache results
5011    Always,
5012    /// Cache only successful results
5013    OnSuccess,
5014    /// Cache based on custom condition
5015    Conditional { condition: Condition },
5016    /// Never cache (useful for overriding)
5017    Never,
5018}
5019
5020impl ResultCache {
5021    /// Create a new cache configuration
5022    pub fn new(key: impl Into<String>) -> Self {
5023        Self {
5024            key: key.into(),
5025            policy: CachePolicy::OnSuccess,
5026            ttl: None,
5027        }
5028    }
5029
5030    /// Set cache policy
5031    pub fn with_policy(mut self, policy: CachePolicy) -> Self {
5032        self.policy = policy;
5033        self
5034    }
5035
5036    /// Set TTL in seconds
5037    pub fn with_ttl(mut self, ttl: u64) -> Self {
5038        self.ttl = Some(ttl);
5039        self
5040    }
5041}
5042
5043impl std::fmt::Display for ResultCache {
5044    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5045        write!(f, "Cache[key={}]", self.key)?;
5046        if let Some(ttl) = self.ttl {
5047            write!(f, " ttl={}s", ttl)?;
5048        }
5049        Ok(())
5050    }
5051}
5052
5053// ============================================================================
5054// Workflow Error Handlers
5055// ============================================================================
5056
5057/// Workflow-level error handler
5058#[derive(Debug, Clone, Serialize, Deserialize)]
5059pub struct WorkflowErrorHandler {
5060    /// Error handler task
5061    pub handler: Signature,
5062    /// Error types to handle (empty = handle all)
5063    pub error_types: Vec<String>,
5064    /// Whether to suppress the error after handling
5065    pub suppress: bool,
5066}
5067
5068impl WorkflowErrorHandler {
5069    /// Create a new error handler
5070    pub fn new(handler: Signature) -> Self {
5071        Self {
5072            handler,
5073            error_types: Vec::new(),
5074            suppress: false,
5075        }
5076    }
5077
5078    /// Handle specific error types
5079    pub fn for_errors(mut self, error_types: Vec<String>) -> Self {
5080        self.error_types = error_types;
5081        self
5082    }
5083
5084    /// Suppress error after handling
5085    pub fn suppress_error(mut self) -> Self {
5086        self.suppress = true;
5087        self
5088    }
5089}
5090
5091impl std::fmt::Display for WorkflowErrorHandler {
5092    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5093        write!(f, "ErrorHandler[{}]", self.handler.task)?;
5094        if self.suppress {
5095            write!(f, " (suppress)")?;
5096        }
5097        Ok(())
5098    }
5099}
5100
5101// ============================================================================
5102// Compensation Workflows (Saga Pattern)
5103// ============================================================================
5104
5105/// Compensation workflow for rollback
5106#[derive(Debug, Clone, Serialize, Deserialize)]
5107pub struct CompensationWorkflow {
5108    /// Forward actions
5109    pub forward: Vec<Signature>,
5110    /// Compensation actions (run in reverse order on failure)
5111    pub compensations: Vec<Signature>,
5112}
5113
5114impl CompensationWorkflow {
5115    /// Create a new compensation workflow
5116    pub fn new() -> Self {
5117        Self {
5118            forward: Vec::new(),
5119            compensations: Vec::new(),
5120        }
5121    }
5122
5123    /// Add a step with compensation
5124    pub fn step(mut self, forward: Signature, compensation: Signature) -> Self {
5125        self.forward.push(forward);
5126        self.compensations.push(compensation);
5127        self
5128    }
5129
5130    /// Check if empty
5131    pub fn is_empty(&self) -> bool {
5132        self.forward.is_empty()
5133    }
5134
5135    /// Get number of steps
5136    pub fn len(&self) -> usize {
5137        self.forward.len()
5138    }
5139}
5140
5141impl Default for CompensationWorkflow {
5142    fn default() -> Self {
5143        Self::new()
5144    }
5145}
5146
5147impl std::fmt::Display for CompensationWorkflow {
5148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5149        write!(
5150            f,
5151            "Compensation[{} steps, {} compensations]",
5152            self.forward.len(),
5153            self.compensations.len()
5154        )
5155    }
5156}
5157
5158/// Saga pattern workflow
5159#[derive(Debug, Clone, Serialize, Deserialize)]
5160pub struct Saga {
5161    /// Compensation workflow
5162    pub workflow: CompensationWorkflow,
5163    /// Isolation level
5164    pub isolation: SagaIsolation,
5165}
5166
5167/// Saga isolation level
5168#[derive(Debug, Clone, Serialize, Deserialize)]
5169pub enum SagaIsolation {
5170    /// Read uncommitted (no isolation)
5171    ReadUncommitted,
5172    /// Read committed (default)
5173    ReadCommitted,
5174    /// Serializable (full isolation)
5175    Serializable,
5176}
5177
5178impl Saga {
5179    /// Create a new saga
5180    pub fn new(workflow: CompensationWorkflow) -> Self {
5181        Self {
5182            workflow,
5183            isolation: SagaIsolation::ReadCommitted,
5184        }
5185    }
5186
5187    /// Set isolation level
5188    pub fn with_isolation(mut self, isolation: SagaIsolation) -> Self {
5189        self.isolation = isolation;
5190        self
5191    }
5192}
5193
5194impl std::fmt::Display for Saga {
5195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5196        write!(
5197            f,
5198            "Saga[{} steps, isolation={:?}]",
5199            self.workflow.len(),
5200            self.isolation
5201        )
5202    }
5203}
5204
5205// ============================================================================
5206// Advanced Workflow Patterns
5207// ============================================================================
5208
5209/// Scatter-gather pattern: distribute work, collect results
5210#[derive(Debug, Clone, Serialize, Deserialize)]
5211pub struct ScatterGather {
5212    /// Scatter task (distributes work)
5213    pub scatter: Signature,
5214    /// Worker tasks (process items)
5215    pub workers: Vec<Signature>,
5216    /// Gather task (collects results)
5217    pub gather: Signature,
5218    /// Timeout for gathering
5219    pub timeout: Option<u64>,
5220}
5221
5222impl ScatterGather {
5223    /// Create a new scatter-gather pattern
5224    pub fn new(scatter: Signature, workers: Vec<Signature>, gather: Signature) -> Self {
5225        Self {
5226            scatter,
5227            workers,
5228            gather,
5229            timeout: None,
5230        }
5231    }
5232
5233    /// Set gather timeout
5234    pub fn with_timeout(mut self, timeout: u64) -> Self {
5235        self.timeout = Some(timeout);
5236        self
5237    }
5238}
5239
5240impl std::fmt::Display for ScatterGather {
5241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5242        write!(
5243            f,
5244            "ScatterGather[scatter={}, {} workers, gather={}]",
5245            self.scatter.task,
5246            self.workers.len(),
5247            self.gather.task
5248        )
5249    }
5250}
5251
5252/// Pipeline pattern: streaming data through stages
5253#[derive(Debug, Clone, Serialize, Deserialize)]
5254pub struct Pipeline {
5255    /// Pipeline stages
5256    pub stages: Vec<Signature>,
5257    /// Buffer size between stages
5258    pub buffer_size: Option<usize>,
5259}
5260
5261impl Pipeline {
5262    /// Create a new pipeline
5263    pub fn new() -> Self {
5264        Self {
5265            stages: Vec::new(),
5266            buffer_size: None,
5267        }
5268    }
5269
5270    /// Add a stage
5271    pub fn stage(mut self, stage: Signature) -> Self {
5272        self.stages.push(stage);
5273        self
5274    }
5275
5276    /// Set buffer size
5277    pub fn with_buffer_size(mut self, size: usize) -> Self {
5278        self.buffer_size = Some(size);
5279        self
5280    }
5281
5282    /// Check if empty
5283    pub fn is_empty(&self) -> bool {
5284        self.stages.is_empty()
5285    }
5286
5287    /// Get number of stages
5288    pub fn len(&self) -> usize {
5289        self.stages.len()
5290    }
5291}
5292
5293impl Default for Pipeline {
5294    fn default() -> Self {
5295        Self::new()
5296    }
5297}
5298
5299impl std::fmt::Display for Pipeline {
5300    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5301        write!(f, "Pipeline[{} stages]", self.stages.len())?;
5302        if let Some(buf) = self.buffer_size {
5303            write!(f, " buffer={}", buf)?;
5304        }
5305        Ok(())
5306    }
5307}
5308
5309/// Fan-out pattern: broadcast to multiple consumers
5310#[derive(Debug, Clone, Serialize, Deserialize)]
5311pub struct FanOut {
5312    /// Source task
5313    pub source: Signature,
5314    /// Consumer tasks
5315    pub consumers: Vec<Signature>,
5316}
5317
5318impl FanOut {
5319    /// Create a new fan-out pattern
5320    pub fn new(source: Signature) -> Self {
5321        Self {
5322            source,
5323            consumers: Vec::new(),
5324        }
5325    }
5326
5327    /// Add a consumer
5328    pub fn consumer(mut self, consumer: Signature) -> Self {
5329        self.consumers.push(consumer);
5330        self
5331    }
5332
5333    /// Get number of consumers
5334    pub fn len(&self) -> usize {
5335        self.consumers.len()
5336    }
5337
5338    /// Check if empty
5339    pub fn is_empty(&self) -> bool {
5340        self.consumers.is_empty()
5341    }
5342}
5343
5344impl std::fmt::Display for FanOut {
5345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5346        write!(
5347            f,
5348            "FanOut[source={}, {} consumers]",
5349            self.source.task,
5350            self.consumers.len()
5351        )
5352    }
5353}
5354
5355/// Fan-in pattern: collect from multiple sources
5356#[derive(Debug, Clone, Serialize, Deserialize)]
5357pub struct FanIn {
5358    /// Source tasks
5359    pub sources: Vec<Signature>,
5360    /// Aggregator task
5361    pub aggregator: Signature,
5362}
5363
5364impl FanIn {
5365    /// Create a new fan-in pattern
5366    pub fn new(aggregator: Signature) -> Self {
5367        Self {
5368            sources: Vec::new(),
5369            aggregator,
5370        }
5371    }
5372
5373    /// Add a source
5374    pub fn source(mut self, source: Signature) -> Self {
5375        self.sources.push(source);
5376        self
5377    }
5378
5379    /// Get number of sources
5380    pub fn len(&self) -> usize {
5381        self.sources.len()
5382    }
5383
5384    /// Check if empty
5385    pub fn is_empty(&self) -> bool {
5386        self.sources.is_empty()
5387    }
5388}
5389
5390impl std::fmt::Display for FanIn {
5391    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5392        write!(
5393            f,
5394            "FanIn[{} sources, aggregator={}]",
5395            self.sources.len(),
5396            self.aggregator.task
5397        )
5398    }
5399}
5400
5401// ============================================================================
5402// Workflow Validation and Dry-Run
5403// ============================================================================
5404
5405/// Workflow validation result
5406#[derive(Debug, Clone)]
5407pub struct ValidationResult {
5408    /// Whether workflow is valid
5409    pub valid: bool,
5410    /// Validation errors
5411    pub errors: Vec<String>,
5412    /// Validation warnings
5413    pub warnings: Vec<String>,
5414}
5415
5416impl ValidationResult {
5417    /// Create a valid result
5418    pub fn valid() -> Self {
5419        Self {
5420            valid: true,
5421            errors: Vec::new(),
5422            warnings: Vec::new(),
5423        }
5424    }
5425
5426    /// Create an invalid result
5427    pub fn invalid(error: impl Into<String>) -> Self {
5428        Self {
5429            valid: false,
5430            errors: vec![error.into()],
5431            warnings: Vec::new(),
5432        }
5433    }
5434
5435    /// Add an error
5436    pub fn add_error(&mut self, error: impl Into<String>) {
5437        self.errors.push(error.into());
5438        self.valid = false;
5439    }
5440
5441    /// Add a warning
5442    pub fn add_warning(&mut self, warning: impl Into<String>) {
5443        self.warnings.push(warning.into());
5444    }
5445}
5446
5447impl std::fmt::Display for ValidationResult {
5448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5449        if self.valid {
5450            write!(f, "Valid")?;
5451            if !self.warnings.is_empty() {
5452                write!(f, " ({} warnings)", self.warnings.len())?;
5453            }
5454        } else {
5455            write!(f, "Invalid ({} errors)", self.errors.len())?;
5456        }
5457        Ok(())
5458    }
5459}
5460
5461/// Workflow validator trait
5462pub trait WorkflowValidator {
5463    /// Validate workflow structure
5464    fn validate(&self) -> ValidationResult;
5465}
5466
5467impl WorkflowValidator for Chain {
5468    fn validate(&self) -> ValidationResult {
5469        let mut result = ValidationResult::valid();
5470
5471        if self.is_empty() {
5472            result.add_error("Chain cannot be empty");
5473        }
5474
5475        if self.len() > 100 {
5476            result.add_warning(format!(
5477                "Chain has {} tasks, which may be inefficient",
5478                self.len()
5479            ));
5480        }
5481
5482        result
5483    }
5484}
5485
5486impl WorkflowValidator for Group {
5487    fn validate(&self) -> ValidationResult {
5488        let mut result = ValidationResult::valid();
5489
5490        if self.is_empty() {
5491            result.add_error("Group cannot be empty");
5492        }
5493
5494        if self.len() > 1000 {
5495            result.add_warning(format!(
5496                "Group has {} tasks, which may overwhelm workers",
5497                self.len()
5498            ));
5499        }
5500
5501        result
5502    }
5503}
5504
5505impl WorkflowValidator for Chord {
5506    fn validate(&self) -> ValidationResult {
5507        let mut result = ValidationResult::valid();
5508
5509        if self.header.is_empty() {
5510            result.add_error("Chord header cannot be empty");
5511        }
5512
5513        result
5514    }
5515}
5516
5517// ============================================================================
5518// Loop Control
5519// ============================================================================
5520
5521/// Loop control for break/continue operations
5522#[derive(Debug, Clone, Serialize, Deserialize)]
5523pub enum LoopControl {
5524    /// Continue to next iteration
5525    Continue,
5526    /// Break out of loop
5527    Break,
5528    /// Break with result value
5529    BreakWith { value: serde_json::Value },
5530}
5531
5532impl LoopControl {
5533    /// Create a continue control
5534    pub fn continue_loop() -> Self {
5535        Self::Continue
5536    }
5537
5538    /// Create a break control
5539    pub fn break_loop() -> Self {
5540        Self::Break
5541    }
5542
5543    /// Create a break with value
5544    pub fn break_with(value: serde_json::Value) -> Self {
5545        Self::BreakWith { value }
5546    }
5547}
5548
5549impl std::fmt::Display for LoopControl {
5550    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5551        match self {
5552            Self::Continue => write!(f, "Continue"),
5553            Self::Break => write!(f, "Break"),
5554            Self::BreakWith { .. } => write!(f, "BreakWith"),
5555        }
5556    }
5557}
5558
5559// ============================================================================
5560// Error Propagation Control
5561// ============================================================================
5562
5563/// Error propagation mode for workflows
5564///
5565/// Controls how errors are handled and propagated in workflow execution.
5566#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
5567pub enum ErrorPropagationMode {
5568    /// Stop on first error (default)
5569    #[default]
5570    StopOnFirstError,
5571
5572    /// Continue execution, collect all errors
5573    ContinueOnError,
5574
5575    /// Partial failure handling - continue if threshold not exceeded
5576    PartialFailure {
5577        /// Maximum number of failed tasks before stopping
5578        max_failures: usize,
5579        /// Maximum failure percentage (0.0-1.0) before stopping
5580        max_failure_rate: Option<f64>,
5581    },
5582}
5583
5584impl ErrorPropagationMode {
5585    /// Create a partial failure mode
5586    pub fn partial_failure(max_failures: usize) -> Self {
5587        Self::PartialFailure {
5588            max_failures,
5589            max_failure_rate: None,
5590        }
5591    }
5592
5593    /// Create a partial failure mode with rate threshold
5594    pub fn partial_failure_with_rate(max_failures: usize, max_rate: f64) -> Self {
5595        Self::PartialFailure {
5596            max_failures,
5597            max_failure_rate: Some(max_rate),
5598        }
5599    }
5600
5601    /// Check if mode allows continuing after error
5602    pub fn allows_continue(&self) -> bool {
5603        !matches!(self, Self::StopOnFirstError)
5604    }
5605}
5606
5607impl std::fmt::Display for ErrorPropagationMode {
5608    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5609        match self {
5610            Self::StopOnFirstError => write!(f, "StopOnFirstError"),
5611            Self::ContinueOnError => write!(f, "ContinueOnError"),
5612            Self::PartialFailure {
5613                max_failures,
5614                max_failure_rate,
5615            } => {
5616                write!(f, "PartialFailure(max={})", max_failures)?;
5617                if let Some(rate) = max_failure_rate {
5618                    write!(f, " rate={:.1}%", rate * 100.0)?;
5619                }
5620                Ok(())
5621            }
5622        }
5623    }
5624}
5625
5626/// Tracks partial failure information for workflows
5627#[derive(Debug, Clone, Serialize, Deserialize)]
5628pub struct PartialFailureTracker {
5629    /// Total number of tasks
5630    pub total_tasks: usize,
5631    /// Number of successful tasks
5632    pub successful_tasks: usize,
5633    /// Number of failed tasks
5634    pub failed_tasks: usize,
5635    /// Task IDs that succeeded
5636    pub successful_task_ids: Vec<Uuid>,
5637    /// Task IDs that failed with error messages
5638    pub failed_task_ids: Vec<(Uuid, String)>,
5639}
5640
5641impl PartialFailureTracker {
5642    /// Create a new partial failure tracker
5643    pub fn new(total_tasks: usize) -> Self {
5644        Self {
5645            total_tasks,
5646            successful_tasks: 0,
5647            failed_tasks: 0,
5648            successful_task_ids: Vec::new(),
5649            failed_task_ids: Vec::new(),
5650        }
5651    }
5652
5653    /// Record a successful task
5654    pub fn record_success(&mut self, task_id: Uuid) {
5655        self.successful_tasks += 1;
5656        self.successful_task_ids.push(task_id);
5657    }
5658
5659    /// Record a failed task
5660    pub fn record_failure(&mut self, task_id: Uuid, error: String) {
5661        self.failed_tasks += 1;
5662        self.failed_task_ids.push((task_id, error));
5663    }
5664
5665    /// Calculate failure rate (0.0-1.0)
5666    pub fn failure_rate(&self) -> f64 {
5667        if self.total_tasks == 0 {
5668            return 0.0;
5669        }
5670        self.failed_tasks as f64 / self.total_tasks as f64
5671    }
5672
5673    /// Calculate success rate (0.0-1.0)
5674    pub fn success_rate(&self) -> f64 {
5675        if self.total_tasks == 0 {
5676            return 1.0;
5677        }
5678        self.successful_tasks as f64 / self.total_tasks as f64
5679    }
5680
5681    /// Check if failure threshold exceeded
5682    pub fn exceeds_threshold(&self, mode: &ErrorPropagationMode) -> bool {
5683        match mode {
5684            ErrorPropagationMode::StopOnFirstError => self.failed_tasks > 0,
5685            ErrorPropagationMode::ContinueOnError => false,
5686            ErrorPropagationMode::PartialFailure {
5687                max_failures,
5688                max_failure_rate,
5689            } => {
5690                if self.failed_tasks >= *max_failures {
5691                    return true;
5692                }
5693                if let Some(rate) = max_failure_rate {
5694                    if self.failure_rate() > *rate {
5695                        return true;
5696                    }
5697                }
5698                false
5699            }
5700        }
5701    }
5702
5703    /// Check if workflow should continue
5704    pub fn should_continue(&self, mode: &ErrorPropagationMode) -> bool {
5705        !self.exceeds_threshold(mode)
5706    }
5707}
5708
5709impl std::fmt::Display for PartialFailureTracker {
5710    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5711        write!(
5712            f,
5713            "PartialFailureTracker[success={}/{}, failed={}, rate={:.1}%]",
5714            self.successful_tasks,
5715            self.total_tasks,
5716            self.failed_tasks,
5717            self.failure_rate() * 100.0
5718        )
5719    }
5720}
5721
5722// ============================================================================
5723// Sub-Workflow Isolation
5724// ============================================================================
5725
5726/// Isolation level for sub-workflows
5727#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5728pub enum IsolationLevel {
5729    /// No isolation - sub-workflow shares parent context
5730    #[default]
5731    None,
5732
5733    /// Resource isolation - separate resource limits
5734    Resource {
5735        /// Maximum memory in MB
5736        max_memory_mb: Option<u64>,
5737        /// Maximum CPU percentage
5738        max_cpu_percent: Option<u8>,
5739    },
5740
5741    /// Error isolation - errors don't propagate to parent
5742    Error,
5743
5744    /// Full isolation - separate context, resources, and errors
5745    Full {
5746        /// Maximum memory in MB
5747        max_memory_mb: Option<u64>,
5748        /// Maximum CPU percentage
5749        max_cpu_percent: Option<u8>,
5750    },
5751}
5752
5753impl IsolationLevel {
5754    /// Create resource isolation
5755    pub fn resource(max_memory_mb: u64) -> Self {
5756        Self::Resource {
5757            max_memory_mb: Some(max_memory_mb),
5758            max_cpu_percent: None,
5759        }
5760    }
5761
5762    /// Create full isolation
5763    pub fn full(max_memory_mb: u64) -> Self {
5764        Self::Full {
5765            max_memory_mb: Some(max_memory_mb),
5766            max_cpu_percent: None,
5767        }
5768    }
5769
5770    /// Check if isolation includes resource limits
5771    pub fn has_resource_limits(&self) -> bool {
5772        matches!(self, Self::Resource { .. } | Self::Full { .. })
5773    }
5774
5775    /// Check if isolation includes error boundaries
5776    pub fn has_error_isolation(&self) -> bool {
5777        matches!(self, Self::Error | Self::Full { .. })
5778    }
5779}
5780
5781impl std::fmt::Display for IsolationLevel {
5782    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5783        match self {
5784            Self::None => write!(f, "None"),
5785            Self::Resource {
5786                max_memory_mb,
5787                max_cpu_percent,
5788            } => {
5789                write!(f, "Resource(")?;
5790                if let Some(mem) = max_memory_mb {
5791                    write!(f, "mem={}MB", mem)?;
5792                }
5793                if let Some(cpu) = max_cpu_percent {
5794                    write!(f, " cpu={}%", cpu)?;
5795                }
5796                write!(f, ")")
5797            }
5798            Self::Error => write!(f, "Error"),
5799            Self::Full {
5800                max_memory_mb,
5801                max_cpu_percent,
5802            } => {
5803                write!(f, "Full(")?;
5804                if let Some(mem) = max_memory_mb {
5805                    write!(f, "mem={}MB", mem)?;
5806                }
5807                if let Some(cpu) = max_cpu_percent {
5808                    write!(f, " cpu={}%", cpu)?;
5809                }
5810                write!(f, ")")
5811            }
5812        }
5813    }
5814}
5815
5816/// Sub-workflow isolation context
5817#[derive(Debug, Clone, Serialize, Deserialize)]
5818pub struct SubWorkflowIsolation {
5819    /// Sub-workflow ID
5820    pub workflow_id: Uuid,
5821    /// Parent workflow ID
5822    pub parent_workflow_id: Option<Uuid>,
5823    /// Isolation level
5824    pub isolation_level: IsolationLevel,
5825    /// Whether errors should propagate to parent
5826    pub propagate_errors: bool,
5827    /// Whether cancellation should propagate to parent
5828    pub propagate_cancellation: bool,
5829}
5830
5831impl SubWorkflowIsolation {
5832    /// Create a new sub-workflow isolation context
5833    pub fn new(workflow_id: Uuid, isolation_level: IsolationLevel) -> Self {
5834        Self {
5835            workflow_id,
5836            parent_workflow_id: None,
5837            isolation_level,
5838            propagate_errors: true,
5839            propagate_cancellation: true,
5840        }
5841    }
5842
5843    /// Set parent workflow ID
5844    pub fn with_parent(mut self, parent_id: Uuid) -> Self {
5845        self.parent_workflow_id = Some(parent_id);
5846        self
5847    }
5848
5849    /// Disable error propagation
5850    pub fn no_error_propagation(mut self) -> Self {
5851        self.propagate_errors = false;
5852        self
5853    }
5854
5855    /// Disable cancellation propagation
5856    pub fn no_cancellation_propagation(mut self) -> Self {
5857        self.propagate_cancellation = false;
5858        self
5859    }
5860}
5861
5862impl std::fmt::Display for SubWorkflowIsolation {
5863    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5864        write!(
5865            f,
5866            "SubWorkflowIsolation[id={}, level={}]",
5867            self.workflow_id, self.isolation_level
5868        )?;
5869        if let Some(parent) = self.parent_workflow_id {
5870            write!(f, " parent={}", parent)?;
5871        }
5872        Ok(())
5873    }
5874}
5875
5876// ============================================================================
5877// Workflow Checkpointing and Recovery
5878// ============================================================================
5879
5880/// Workflow checkpoint for crash recovery
5881#[derive(Debug, Clone, Serialize, Deserialize)]
5882pub struct WorkflowCheckpoint {
5883    /// Workflow ID
5884    pub workflow_id: Uuid,
5885    /// Checkpoint timestamp (Unix timestamp)
5886    pub timestamp: u64,
5887    /// Completed task IDs
5888    pub completed_tasks: Vec<Uuid>,
5889    /// Failed task IDs with errors
5890    pub failed_tasks: Vec<(Uuid, String)>,
5891    /// In-progress task IDs
5892    pub in_progress_tasks: Vec<Uuid>,
5893    /// Workflow state snapshot
5894    pub state: WorkflowState,
5895    /// Checkpoint version (for compatibility)
5896    pub version: u32,
5897}
5898
5899impl WorkflowCheckpoint {
5900    /// Create a new checkpoint
5901    pub fn new(workflow_id: Uuid, state: WorkflowState) -> Self {
5902        Self {
5903            workflow_id,
5904            timestamp: std::time::SystemTime::now()
5905                .duration_since(std::time::UNIX_EPOCH)
5906                .unwrap()
5907                .as_secs(),
5908            completed_tasks: Vec::new(),
5909            failed_tasks: Vec::new(),
5910            in_progress_tasks: Vec::new(),
5911            state,
5912            version: 1,
5913        }
5914    }
5915
5916    /// Record completed task
5917    pub fn record_completed(&mut self, task_id: Uuid) {
5918        self.completed_tasks.push(task_id);
5919        // Remove from in-progress if present
5920        self.in_progress_tasks.retain(|&id| id != task_id);
5921    }
5922
5923    /// Record failed task
5924    pub fn record_failed(&mut self, task_id: Uuid, error: String) {
5925        self.failed_tasks.push((task_id, error));
5926        // Remove from in-progress if present
5927        self.in_progress_tasks.retain(|&id| id != task_id);
5928    }
5929
5930    /// Record in-progress task
5931    pub fn record_in_progress(&mut self, task_id: Uuid) {
5932        if !self.in_progress_tasks.contains(&task_id) {
5933            self.in_progress_tasks.push(task_id);
5934        }
5935    }
5936
5937    /// Check if task is completed
5938    pub fn is_completed(&self, task_id: &Uuid) -> bool {
5939        self.completed_tasks.contains(task_id)
5940    }
5941
5942    /// Check if task failed
5943    pub fn is_failed(&self, task_id: &Uuid) -> bool {
5944        self.failed_tasks.iter().any(|(id, _)| id == task_id)
5945    }
5946
5947    /// Get tasks that need to be retried (in-progress at checkpoint)
5948    pub fn tasks_to_retry(&self) -> &[Uuid] {
5949        &self.in_progress_tasks
5950    }
5951
5952    /// Serialize checkpoint to JSON
5953    pub fn to_json(&self) -> Result<String, serde_json::Error> {
5954        serde_json::to_string(self)
5955    }
5956
5957    /// Deserialize checkpoint from JSON
5958    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
5959        serde_json::from_str(json)
5960    }
5961}
5962
5963impl std::fmt::Display for WorkflowCheckpoint {
5964    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5965        write!(
5966            f,
5967            "WorkflowCheckpoint[id={}, completed={}, failed={}, in_progress={}]",
5968            self.workflow_id,
5969            self.completed_tasks.len(),
5970            self.failed_tasks.len(),
5971            self.in_progress_tasks.len()
5972        )
5973    }
5974}
5975
5976/// Workflow recovery policy
5977#[derive(Debug, Clone, Serialize, Deserialize)]
5978pub struct WorkflowRecoveryPolicy {
5979    /// Whether to enable automatic recovery
5980    pub auto_recovery: bool,
5981    /// Whether to resume from last checkpoint
5982    pub resume_from_checkpoint: bool,
5983    /// Whether to replay failed stages
5984    pub replay_failed: bool,
5985    /// Maximum age of checkpoint to use (seconds)
5986    pub max_checkpoint_age: Option<u64>,
5987    /// Retry policy for recovered tasks
5988    pub retry_policy: Option<WorkflowRetryPolicy>,
5989}
5990
5991impl WorkflowRecoveryPolicy {
5992    /// Create a new recovery policy with auto-recovery enabled
5993    pub fn auto_recover() -> Self {
5994        Self {
5995            auto_recovery: true,
5996            resume_from_checkpoint: true,
5997            replay_failed: true,
5998            max_checkpoint_age: Some(3600), // 1 hour
5999            retry_policy: None,
6000        }
6001    }
6002
6003    /// Disable auto-recovery
6004    pub fn manual() -> Self {
6005        Self {
6006            auto_recovery: false,
6007            resume_from_checkpoint: true,
6008            replay_failed: false,
6009            max_checkpoint_age: None,
6010            retry_policy: None,
6011        }
6012    }
6013
6014    /// Set maximum checkpoint age
6015    pub fn with_max_checkpoint_age(mut self, seconds: u64) -> Self {
6016        self.max_checkpoint_age = Some(seconds);
6017        self
6018    }
6019
6020    /// Set retry policy for recovered tasks
6021    pub fn with_retry_policy(mut self, policy: WorkflowRetryPolicy) -> Self {
6022        self.retry_policy = Some(policy);
6023        self
6024    }
6025
6026    /// Check if checkpoint is valid based on age
6027    pub fn is_checkpoint_valid(&self, checkpoint: &WorkflowCheckpoint) -> bool {
6028        if let Some(max_age) = self.max_checkpoint_age {
6029            let now = std::time::SystemTime::now()
6030                .duration_since(std::time::UNIX_EPOCH)
6031                .unwrap()
6032                .as_secs();
6033            let age = now.saturating_sub(checkpoint.timestamp);
6034            age <= max_age
6035        } else {
6036            true
6037        }
6038    }
6039}
6040
6041impl std::fmt::Display for WorkflowRecoveryPolicy {
6042    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6043        write!(f, "WorkflowRecoveryPolicy[")?;
6044        if self.auto_recovery {
6045            write!(f, "auto")?;
6046        } else {
6047            write!(f, "manual")?;
6048        }
6049        if self.resume_from_checkpoint {
6050            write!(f, " resume")?;
6051        }
6052        if self.replay_failed {
6053            write!(f, " replay_failed")?;
6054        }
6055        write!(f, "]")
6056    }
6057}
6058
6059// ============================================================================
6060// State Versioning
6061// ============================================================================
6062
6063/// State version for workflow state evolution
6064#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6065pub struct StateVersion {
6066    /// Major version (breaking changes)
6067    pub major: u32,
6068    /// Minor version (backward-compatible changes)
6069    pub minor: u32,
6070    /// Patch version (bug fixes)
6071    pub patch: u32,
6072}
6073
6074impl StateVersion {
6075    /// Create a new state version
6076    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
6077        Self {
6078            major,
6079            minor,
6080            patch,
6081        }
6082    }
6083
6084    /// Current version
6085    pub fn current() -> Self {
6086        Self {
6087            major: 1,
6088            minor: 0,
6089            patch: 0,
6090        }
6091    }
6092
6093    /// Check if this version can be migrated to another version
6094    /// (same major version, this minor version <= target minor version)
6095    pub fn is_compatible(&self, other: &StateVersion) -> bool {
6096        self.major == other.major && self.minor <= other.minor
6097    }
6098}
6099
6100impl std::fmt::Display for StateVersion {
6101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6102        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
6103    }
6104}
6105
6106/// State migration error
6107#[derive(Debug, Clone)]
6108pub enum StateMigrationError {
6109    /// Incompatible version
6110    IncompatibleVersion {
6111        from: StateVersion,
6112        to: StateVersion,
6113    },
6114    /// Migration failed
6115    MigrationFailed(String),
6116    /// Unsupported version
6117    UnsupportedVersion(StateVersion),
6118}
6119
6120impl std::fmt::Display for StateMigrationError {
6121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6122        match self {
6123            Self::IncompatibleVersion { from, to } => {
6124                write!(f, "Incompatible state version: {} -> {}", from, to)
6125            }
6126            Self::MigrationFailed(msg) => write!(f, "State migration failed: {}", msg),
6127            Self::UnsupportedVersion(version) => {
6128                write!(f, "Unsupported state version: {}", version)
6129            }
6130        }
6131    }
6132}
6133
6134impl std::error::Error for StateMigrationError {}
6135
6136/// State migration strategy
6137pub trait StateMigration {
6138    /// Migrate state from one version to another
6139    fn migrate(&self, from: StateVersion, to: StateVersion) -> Result<(), StateMigrationError>;
6140}
6141
6142/// Versioned workflow state with migration support
6143#[derive(Debug, Clone, Serialize, Deserialize)]
6144pub struct VersionedWorkflowState {
6145    /// State version
6146    pub version: StateVersion,
6147    /// Workflow state
6148    pub state: WorkflowState,
6149    /// Migration history
6150    pub migration_history: Vec<(StateVersion, StateVersion, u64)>, // (from, to, timestamp)
6151}
6152
6153impl VersionedWorkflowState {
6154    /// Create a new versioned state
6155    pub fn new(state: WorkflowState) -> Self {
6156        Self {
6157            version: StateVersion::current(),
6158            state,
6159            migration_history: Vec::new(),
6160        }
6161    }
6162
6163    /// Migrate to a new version
6164    pub fn migrate_to(&mut self, target: StateVersion) -> Result<(), StateMigrationError> {
6165        if self.version == target {
6166            return Ok(());
6167        }
6168
6169        if !self.version.is_compatible(&target) {
6170            return Err(StateMigrationError::IncompatibleVersion {
6171                from: self.version,
6172                to: target,
6173            });
6174        }
6175
6176        // Record migration
6177        let timestamp = std::time::SystemTime::now()
6178            .duration_since(std::time::UNIX_EPOCH)
6179            .unwrap()
6180            .as_secs();
6181        self.migration_history
6182            .push((self.version, target, timestamp));
6183        self.version = target;
6184
6185        Ok(())
6186    }
6187
6188    /// Check if state can be migrated to target version
6189    pub fn can_migrate_to(&self, target: &StateVersion) -> bool {
6190        self.version.is_compatible(target)
6191    }
6192
6193    /// Get migration history
6194    pub fn get_migration_history(&self) -> &[(StateVersion, StateVersion, u64)] {
6195        &self.migration_history
6196    }
6197}
6198
6199// ============================================================================
6200// Workflow Compilation and Optimization
6201// ============================================================================
6202
6203/// Workflow optimization pass
6204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6205pub enum OptimizationPass {
6206    /// Common subexpression elimination
6207    CommonSubexpressionElimination,
6208    /// Dead code elimination
6209    DeadCodeElimination,
6210    /// Task fusion (combine sequential tasks)
6211    TaskFusion,
6212    /// Parallel task scheduling optimization
6213    ParallelScheduling,
6214    /// Resource allocation optimization
6215    ResourceOptimization,
6216}
6217
6218impl std::fmt::Display for OptimizationPass {
6219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6220        match self {
6221            Self::CommonSubexpressionElimination => write!(f, "CSE"),
6222            Self::DeadCodeElimination => write!(f, "DCE"),
6223            Self::TaskFusion => write!(f, "TaskFusion"),
6224            Self::ParallelScheduling => write!(f, "ParallelScheduling"),
6225            Self::ResourceOptimization => write!(f, "ResourceOptimization"),
6226        }
6227    }
6228}
6229
6230/// Workflow compiler for optimization
6231#[derive(Debug, Clone)]
6232pub struct WorkflowCompiler {
6233    /// Optimization passes to apply
6234    pub passes: Vec<OptimizationPass>,
6235    /// Whether to enable aggressive optimizations
6236    pub aggressive: bool,
6237}
6238
6239impl WorkflowCompiler {
6240    /// Create a new workflow compiler
6241    pub fn new() -> Self {
6242        Self {
6243            passes: vec![
6244                OptimizationPass::DeadCodeElimination,
6245                OptimizationPass::CommonSubexpressionElimination,
6246            ],
6247            aggressive: false,
6248        }
6249    }
6250
6251    /// Enable aggressive optimizations
6252    pub fn aggressive(mut self) -> Self {
6253        self.aggressive = true;
6254        self.passes.push(OptimizationPass::TaskFusion);
6255        self.passes.push(OptimizationPass::ParallelScheduling);
6256        self.passes.push(OptimizationPass::ResourceOptimization);
6257        self
6258    }
6259
6260    /// Add optimization pass
6261    pub fn add_pass(mut self, pass: OptimizationPass) -> Self {
6262        if !self.passes.contains(&pass) {
6263            self.passes.push(pass);
6264        }
6265        self
6266    }
6267
6268    /// Optimize a chain by applying configured optimization passes
6269    pub fn optimize_chain(&self, chain: &Chain) -> Chain {
6270        let mut optimized = chain.clone();
6271
6272        for pass in &self.passes {
6273            optimized = match pass {
6274                OptimizationPass::CommonSubexpressionElimination => {
6275                    self.apply_cse_chain(&optimized)
6276                }
6277                OptimizationPass::DeadCodeElimination => self.apply_dce_chain(&optimized),
6278                OptimizationPass::TaskFusion => self.apply_task_fusion(&optimized),
6279                OptimizationPass::ParallelScheduling => {
6280                    // Chains are sequential, no parallel scheduling
6281                    optimized
6282                }
6283                OptimizationPass::ResourceOptimization => {
6284                    self.apply_resource_optimization_chain(&optimized)
6285                }
6286            };
6287        }
6288
6289        optimized
6290    }
6291
6292    /// Optimize a group by applying configured optimization passes
6293    pub fn optimize_group(&self, group: &Group) -> Group {
6294        let mut optimized = group.clone();
6295
6296        for pass in &self.passes {
6297            optimized = match pass {
6298                OptimizationPass::CommonSubexpressionElimination => {
6299                    self.apply_cse_group(&optimized)
6300                }
6301                OptimizationPass::DeadCodeElimination => self.apply_dce_group(&optimized),
6302                OptimizationPass::TaskFusion => {
6303                    // Groups are parallel, no task fusion
6304                    optimized
6305                }
6306                OptimizationPass::ParallelScheduling => self.apply_parallel_scheduling(&optimized),
6307                OptimizationPass::ResourceOptimization => {
6308                    self.apply_resource_optimization_group(&optimized)
6309                }
6310            };
6311        }
6312
6313        optimized
6314    }
6315
6316    /// Optimize a chord by applying configured optimization passes
6317    pub fn optimize_chord(&self, chord: &Chord) -> Chord {
6318        let optimized_group = self.optimize_group(&chord.header);
6319        Chord {
6320            header: optimized_group,
6321            body: chord.body.clone(),
6322        }
6323    }
6324
6325    // Helper methods for optimization passes
6326
6327    /// Common Subexpression Elimination for chains
6328    fn apply_cse_chain(&self, chain: &Chain) -> Chain {
6329        let mut seen = HashMap::new();
6330        let mut optimized_tasks = Vec::new();
6331
6332        for (idx, task) in chain.tasks.iter().enumerate() {
6333            // Create a key for deduplication (task name + args)
6334            let key = format!(
6335                "{}:{}:{}",
6336                task.task,
6337                serde_json::to_string(&task.args).unwrap_or_default(),
6338                serde_json::to_string(&task.kwargs).unwrap_or_default()
6339            );
6340
6341            if let Some(&prev_idx) = seen.get(&key) {
6342                // Skip duplicate if aggressive mode
6343                if self.aggressive && prev_idx < idx {
6344                    continue;
6345                }
6346            } else {
6347                seen.insert(key, idx);
6348            }
6349
6350            optimized_tasks.push(task.clone());
6351        }
6352
6353        Chain {
6354            tasks: optimized_tasks,
6355        }
6356    }
6357
6358    /// Common Subexpression Elimination for groups
6359    fn apply_cse_group(&self, group: &Group) -> Group {
6360        let mut seen = HashMap::new();
6361        let mut optimized_tasks = Vec::new();
6362
6363        for task in &group.tasks {
6364            // Create a key for deduplication (task name + args)
6365            let key = format!(
6366                "{}:{}:{}",
6367                task.task,
6368                serde_json::to_string(&task.args).unwrap_or_default(),
6369                serde_json::to_string(&task.kwargs).unwrap_or_default()
6370            );
6371
6372            if let std::collections::hash_map::Entry::Vacant(e) = seen.entry(key) {
6373                e.insert(true);
6374                optimized_tasks.push(task.clone());
6375            } else {
6376                // Skip duplicate in aggressive mode
6377                if !self.aggressive {
6378                    optimized_tasks.push(task.clone());
6379                }
6380            }
6381        }
6382
6383        Group {
6384            tasks: optimized_tasks,
6385            group_id: group.group_id,
6386        }
6387    }
6388
6389    /// Dead Code Elimination for chains
6390    fn apply_dce_chain(&self, chain: &Chain) -> Chain {
6391        // Remove tasks that have no effect (e.g., empty task names)
6392        let optimized_tasks: Vec<_> = chain
6393            .tasks
6394            .iter()
6395            .filter(|task| !task.task.is_empty())
6396            .cloned()
6397            .collect();
6398
6399        Chain {
6400            tasks: optimized_tasks,
6401        }
6402    }
6403
6404    /// Dead Code Elimination for groups
6405    fn apply_dce_group(&self, group: &Group) -> Group {
6406        // Remove tasks that have no effect (e.g., empty task names)
6407        let optimized_tasks: Vec<_> = group
6408            .tasks
6409            .iter()
6410            .filter(|task| !task.task.is_empty())
6411            .cloned()
6412            .collect();
6413
6414        Group {
6415            tasks: optimized_tasks,
6416            group_id: group.group_id,
6417        }
6418    }
6419
6420    /// Task Fusion for chains (combine similar sequential tasks)
6421    fn apply_task_fusion(&self, chain: &Chain) -> Chain {
6422        if !self.aggressive || chain.tasks.len() < 2 {
6423            return chain.clone();
6424        }
6425
6426        let mut optimized_tasks = Vec::new();
6427        let mut i = 0;
6428
6429        while i < chain.tasks.len() {
6430            let current = &chain.tasks[i];
6431
6432            // Check if next task can be fused (same task name, immutable)
6433            if i + 1 < chain.tasks.len() {
6434                let next = &chain.tasks[i + 1];
6435
6436                if current.task == next.task
6437                    && current.immutable
6438                    && next.immutable
6439                    && current.options.priority == next.options.priority
6440                {
6441                    // Fuse tasks: combine args
6442                    let mut fused = current.clone();
6443                    fused.args.extend(next.args.clone());
6444                    optimized_tasks.push(fused);
6445                    i += 2; // Skip both tasks
6446                    continue;
6447                }
6448            }
6449
6450            optimized_tasks.push(current.clone());
6451            i += 1;
6452        }
6453
6454        Chain {
6455            tasks: optimized_tasks,
6456        }
6457    }
6458
6459    /// Parallel Scheduling optimization for groups
6460    fn apply_parallel_scheduling(&self, group: &Group) -> Group {
6461        let mut optimized_tasks = group.tasks.clone();
6462
6463        // Sort tasks by priority (higher priority first)
6464        optimized_tasks.sort_by(|a, b| {
6465            let a_priority = a.options.priority.unwrap_or(0);
6466            let b_priority = b.options.priority.unwrap_or(0);
6467            b_priority.cmp(&a_priority)
6468        });
6469
6470        Group {
6471            tasks: optimized_tasks,
6472            group_id: group.group_id,
6473        }
6474    }
6475
6476    /// Resource Optimization for chains
6477    fn apply_resource_optimization_chain(&self, chain: &Chain) -> Chain {
6478        // Group tasks by queue to improve resource utilization
6479        let mut optimized_tasks = chain.tasks.clone();
6480
6481        if self.aggressive {
6482            // Reorder tasks to group by queue while maintaining dependencies
6483            optimized_tasks.sort_by(|a, b| {
6484                let a_queue = a.options.queue.as_deref().unwrap_or("");
6485                let b_queue = b.options.queue.as_deref().unwrap_or("");
6486                a_queue.cmp(b_queue)
6487            });
6488        }
6489
6490        Chain {
6491            tasks: optimized_tasks,
6492        }
6493    }
6494
6495    /// Resource Optimization for groups
6496    fn apply_resource_optimization_group(&self, group: &Group) -> Group {
6497        // Balance tasks across queues
6498        let mut optimized_tasks = group.tasks.clone();
6499
6500        // Sort by queue to improve locality
6501        optimized_tasks.sort_by(|a, b| {
6502            let a_queue = a.options.queue.as_deref().unwrap_or("");
6503            let b_queue = b.options.queue.as_deref().unwrap_or("");
6504            a_queue.cmp(b_queue)
6505        });
6506
6507        Group {
6508            tasks: optimized_tasks,
6509            group_id: group.group_id,
6510        }
6511    }
6512}
6513
6514impl Default for WorkflowCompiler {
6515    fn default() -> Self {
6516        Self::new()
6517    }
6518}
6519
6520impl std::fmt::Display for WorkflowCompiler {
6521    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6522        write!(f, "WorkflowCompiler[")?;
6523        for (i, pass) in self.passes.iter().enumerate() {
6524            if i > 0 {
6525                write!(f, ", ")?;
6526            }
6527            write!(f, "{}", pass)?;
6528        }
6529        if self.aggressive {
6530            write!(f, " aggressive")?;
6531        }
6532        write!(f, "]")
6533    }
6534}
6535
6536// ============================================================================
6537// Type-Safe Result Passing
6538// ============================================================================
6539
6540/// Type-safe result wrapper for workflow results
6541#[derive(Debug, Clone, Serialize, Deserialize)]
6542pub struct TypedResult<T> {
6543    /// Result value
6544    pub value: T,
6545    /// Result type name for validation
6546    pub type_name: String,
6547    /// Result metadata
6548    #[serde(default)]
6549    pub metadata: HashMap<String, serde_json::Value>,
6550}
6551
6552impl<T: Serialize> TypedResult<T> {
6553    /// Create a new typed result
6554    pub fn new(value: T) -> Self {
6555        Self {
6556            value,
6557            type_name: std::any::type_name::<T>().to_string(),
6558            metadata: HashMap::new(),
6559        }
6560    }
6561
6562    /// Add metadata to the result
6563    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
6564        self.metadata.insert(key.into(), value);
6565        self
6566    }
6567
6568    /// Get the type name
6569    pub fn type_name(&self) -> &str {
6570        &self.type_name
6571    }
6572}
6573
6574impl<T: std::fmt::Display> std::fmt::Display for TypedResult<T> {
6575    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6576        write!(
6577            f,
6578            "TypedResult[type={}, value={}]",
6579            self.type_name, self.value
6580        )
6581    }
6582}
6583
6584/// Type validator for result passing
6585#[derive(Debug, Clone)]
6586pub struct TypeValidator {
6587    /// Expected type name
6588    pub expected_type: String,
6589    /// Whether to allow compatible types
6590    pub allow_compatible: bool,
6591}
6592
6593impl TypeValidator {
6594    /// Create a new type validator
6595    pub fn new(expected_type: impl Into<String>) -> Self {
6596        Self {
6597            expected_type: expected_type.into(),
6598            allow_compatible: false,
6599        }
6600    }
6601
6602    /// Allow compatible types
6603    pub fn allow_compatible(mut self) -> Self {
6604        self.allow_compatible = true;
6605        self
6606    }
6607
6608    /// Validate a type name
6609    pub fn validate(&self, actual_type: &str) -> bool {
6610        if actual_type == self.expected_type {
6611            return true;
6612        }
6613        if self.allow_compatible {
6614            self.is_compatible(actual_type)
6615        } else {
6616            false
6617        }
6618    }
6619
6620    /// Check if types are compatible
6621    fn is_compatible(&self, actual_type: &str) -> bool {
6622        // Simple compatibility check (can be extended)
6623        if self.expected_type.contains("Option") && actual_type != "None" {
6624            return true;
6625        }
6626        if self.expected_type == "serde_json::Value" {
6627            return true;
6628        }
6629        false
6630    }
6631}
6632
6633impl std::fmt::Display for TypeValidator {
6634    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6635        write!(f, "TypeValidator[expected={}]", self.expected_type)?;
6636        if self.allow_compatible {
6637            write!(f, " (allow_compatible)")?;
6638        }
6639        Ok(())
6640    }
6641}
6642
6643// ============================================================================
6644// Data Dependencies
6645// ============================================================================
6646
6647/// Task dependency specification
6648#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
6649pub struct TaskDependency {
6650    /// Task ID that this task depends on
6651    pub task_id: Uuid,
6652    /// Output key to use from the dependency (optional)
6653    pub output_key: Option<String>,
6654    /// Whether this dependency is optional
6655    #[serde(default)]
6656    pub optional: bool,
6657}
6658
6659impl TaskDependency {
6660    /// Create a new task dependency
6661    pub fn new(task_id: Uuid) -> Self {
6662        Self {
6663            task_id,
6664            output_key: None,
6665            optional: false,
6666        }
6667    }
6668
6669    /// Set output key
6670    pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
6671        self.output_key = Some(key.into());
6672        self
6673    }
6674
6675    /// Mark as optional
6676    pub fn optional(mut self) -> Self {
6677        self.optional = true;
6678        self
6679    }
6680}
6681
6682impl std::fmt::Display for TaskDependency {
6683    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6684        write!(f, "TaskDependency[{}]", self.task_id)?;
6685        if let Some(ref key) = self.output_key {
6686            write!(f, " output={}", key)?;
6687        }
6688        if self.optional {
6689            write!(f, " (optional)")?;
6690        }
6691        Ok(())
6692    }
6693}
6694
6695/// Dependency graph for workflow tasks
6696#[derive(Debug, Clone, Serialize, Deserialize)]
6697pub struct DependencyGraph {
6698    /// Map of task ID to its dependencies
6699    pub dependencies: HashMap<Uuid, Vec<TaskDependency>>,
6700    /// Reverse map for quick lookup
6701    #[serde(skip)]
6702    pub dependents: HashMap<Uuid, Vec<Uuid>>,
6703}
6704
6705impl DependencyGraph {
6706    /// Create a new dependency graph
6707    pub fn new() -> Self {
6708        Self {
6709            dependencies: HashMap::new(),
6710            dependents: HashMap::new(),
6711        }
6712    }
6713
6714    /// Add a dependency
6715    pub fn add_dependency(&mut self, task_id: Uuid, dependency: TaskDependency) {
6716        self.dependencies
6717            .entry(task_id)
6718            .or_default()
6719            .push(dependency.clone());
6720
6721        // Update reverse map
6722        self.dependents
6723            .entry(dependency.task_id)
6724            .or_default()
6725            .push(task_id);
6726    }
6727
6728    /// Get dependencies for a task
6729    pub fn get_dependencies(&self, task_id: &Uuid) -> Vec<&TaskDependency> {
6730        self.dependencies
6731            .get(task_id)
6732            .map(|deps| deps.iter().collect())
6733            .unwrap_or_default()
6734    }
6735
6736    /// Get tasks that depend on this task
6737    pub fn get_dependents(&self, task_id: &Uuid) -> Vec<Uuid> {
6738        self.dependents.get(task_id).cloned().unwrap_or_default()
6739    }
6740
6741    /// Check for circular dependencies
6742    pub fn has_circular_dependency(&self) -> bool {
6743        let mut visited = std::collections::HashSet::new();
6744        let mut rec_stack = std::collections::HashSet::new();
6745
6746        for task_id in self.dependencies.keys() {
6747            if self.is_cyclic(*task_id, &mut visited, &mut rec_stack) {
6748                return true;
6749            }
6750        }
6751        false
6752    }
6753
6754    fn is_cyclic(
6755        &self,
6756        task_id: Uuid,
6757        visited: &mut std::collections::HashSet<Uuid>,
6758        rec_stack: &mut std::collections::HashSet<Uuid>,
6759    ) -> bool {
6760        if rec_stack.contains(&task_id) {
6761            return true;
6762        }
6763        if visited.contains(&task_id) {
6764            return false;
6765        }
6766
6767        visited.insert(task_id);
6768        rec_stack.insert(task_id);
6769
6770        if let Some(deps) = self.dependencies.get(&task_id) {
6771            for dep in deps {
6772                if self.is_cyclic(dep.task_id, visited, rec_stack) {
6773                    return true;
6774                }
6775            }
6776        }
6777
6778        rec_stack.remove(&task_id);
6779        false
6780    }
6781
6782    /// Get topological order of tasks
6783    pub fn topological_sort(&self) -> Result<Vec<Uuid>, String> {
6784        if self.has_circular_dependency() {
6785            return Err("Circular dependency detected".to_string());
6786        }
6787
6788        let mut in_degree: HashMap<Uuid, usize> = HashMap::new();
6789        let mut queue: Vec<Uuid> = Vec::new();
6790        let mut result: Vec<Uuid> = Vec::new();
6791
6792        // Calculate in-degrees
6793        for (task_id, deps) in &self.dependencies {
6794            in_degree.entry(*task_id).or_insert(deps.len());
6795            for dep in deps {
6796                in_degree.entry(dep.task_id).or_insert(0);
6797            }
6798        }
6799
6800        // Find tasks with no dependencies
6801        for (task_id, &degree) in &in_degree {
6802            if degree == 0 {
6803                queue.push(*task_id);
6804            }
6805        }
6806
6807        // Process queue
6808        while let Some(task_id) = queue.pop() {
6809            result.push(task_id);
6810
6811            if let Some(dependents) = self.dependents.get(&task_id) {
6812                for &dependent in dependents {
6813                    if let Some(degree) = in_degree.get_mut(&dependent) {
6814                        if *degree > 0 {
6815                            *degree -= 1;
6816                            if *degree == 0 {
6817                                queue.push(dependent);
6818                            }
6819                        }
6820                    }
6821                }
6822            }
6823        }
6824
6825        if result.len() == in_degree.len() {
6826            Ok(result)
6827        } else {
6828            Err("Failed to compute topological sort".to_string())
6829        }
6830    }
6831}
6832
6833impl Default for DependencyGraph {
6834    fn default() -> Self {
6835        Self::new()
6836    }
6837}
6838
6839impl std::fmt::Display for DependencyGraph {
6840    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6841        write!(f, "DependencyGraph[{} tasks]", self.dependencies.len())
6842    }
6843}
6844
6845// ============================================================================
6846// Parallel Reduce
6847// ============================================================================
6848
6849/// Parallel reduce configuration
6850#[derive(Debug, Clone, Serialize, Deserialize)]
6851pub struct ParallelReduce {
6852    /// Task to map over inputs
6853    pub map_task: Signature,
6854    /// Task to reduce pairs of results
6855    pub reduce_task: Signature,
6856    /// Input values to map over
6857    pub inputs: Vec<serde_json::Value>,
6858    /// Number of parallel workers
6859    pub parallelism: usize,
6860    /// Initial value for reduction
6861    pub initial_value: Option<serde_json::Value>,
6862}
6863
6864impl ParallelReduce {
6865    /// Create a new parallel reduce
6866    pub fn new(
6867        map_task: Signature,
6868        reduce_task: Signature,
6869        inputs: Vec<serde_json::Value>,
6870    ) -> Self {
6871        Self {
6872            map_task,
6873            reduce_task,
6874            inputs,
6875            parallelism: 4,
6876            initial_value: None,
6877        }
6878    }
6879
6880    /// Set parallelism level
6881    pub fn with_parallelism(mut self, parallelism: usize) -> Self {
6882        self.parallelism = parallelism;
6883        self
6884    }
6885
6886    /// Set initial value
6887    pub fn with_initial_value(mut self, value: serde_json::Value) -> Self {
6888        self.initial_value = Some(value);
6889        self
6890    }
6891
6892    /// Get the number of inputs
6893    pub fn input_count(&self) -> usize {
6894        self.inputs.len()
6895    }
6896
6897    /// Check if empty
6898    pub fn is_empty(&self) -> bool {
6899        self.inputs.is_empty()
6900    }
6901}
6902
6903impl std::fmt::Display for ParallelReduce {
6904    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6905        write!(
6906            f,
6907            "ParallelReduce[map={}, reduce={}, inputs={}, parallelism={}]",
6908            self.map_task.task,
6909            self.reduce_task.task,
6910            self.inputs.len(),
6911            self.parallelism
6912        )
6913    }
6914}
6915
6916// ============================================================================
6917// Workflow Templates
6918// ============================================================================
6919
6920/// Workflow template parameter
6921#[derive(Debug, Clone, Serialize, Deserialize)]
6922pub struct TemplateParameter {
6923    /// Parameter name
6924    pub name: String,
6925    /// Parameter type
6926    pub param_type: String,
6927    /// Default value
6928    pub default: Option<serde_json::Value>,
6929    /// Whether parameter is required
6930    #[serde(default = "default_true")]
6931    pub required: bool,
6932    /// Parameter description
6933    pub description: Option<String>,
6934}
6935
6936fn default_true() -> bool {
6937    true
6938}
6939
6940impl TemplateParameter {
6941    /// Create a new template parameter
6942    pub fn new(name: impl Into<String>, param_type: impl Into<String>) -> Self {
6943        Self {
6944            name: name.into(),
6945            param_type: param_type.into(),
6946            default: None,
6947            required: true,
6948            description: None,
6949        }
6950    }
6951
6952    /// Set default value
6953    pub fn with_default(mut self, value: serde_json::Value) -> Self {
6954        self.default = Some(value);
6955        self.required = false;
6956        self
6957    }
6958
6959    /// Set description
6960    pub fn with_description(mut self, description: impl Into<String>) -> Self {
6961        self.description = Some(description.into());
6962        self
6963    }
6964
6965    /// Make optional
6966    pub fn optional(mut self) -> Self {
6967        self.required = false;
6968        self
6969    }
6970}
6971
6972impl std::fmt::Display for TemplateParameter {
6973    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6974        write!(f, "{}:{}", self.name, self.param_type)?;
6975        if !self.required {
6976            write!(f, " (optional)")?;
6977        }
6978        Ok(())
6979    }
6980}
6981
6982/// Workflow template for reusable patterns
6983#[derive(Debug, Clone, Serialize, Deserialize)]
6984pub struct WorkflowTemplate {
6985    /// Template name
6986    pub name: String,
6987    /// Template version
6988    pub version: String,
6989    /// Template parameters
6990    pub parameters: Vec<TemplateParameter>,
6991    /// Template chain (if any)
6992    pub chain: Option<Chain>,
6993    /// Template group (if any)
6994    pub group: Option<Group>,
6995    /// Template description
6996    pub description: Option<String>,
6997}
6998
6999impl WorkflowTemplate {
7000    /// Create a new workflow template
7001    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
7002        Self {
7003            name: name.into(),
7004            version: version.into(),
7005            parameters: Vec::new(),
7006            chain: None,
7007            group: None,
7008            description: None,
7009        }
7010    }
7011
7012    /// Add a parameter
7013    pub fn add_parameter(mut self, param: TemplateParameter) -> Self {
7014        self.parameters.push(param);
7015        self
7016    }
7017
7018    /// Set template chain
7019    pub fn with_chain(mut self, chain: Chain) -> Self {
7020        self.chain = Some(chain);
7021        self
7022    }
7023
7024    /// Set template group
7025    pub fn with_group(mut self, group: Group) -> Self {
7026        self.group = Some(group);
7027        self
7028    }
7029
7030    /// Set description
7031    pub fn with_description(mut self, description: impl Into<String>) -> Self {
7032        self.description = Some(description.into());
7033        self
7034    }
7035
7036    /// Instantiate template with parameters
7037    pub fn instantiate(&self, params: HashMap<String, serde_json::Value>) -> Result<Self, String> {
7038        // Validate required parameters
7039        for param in &self.parameters {
7040            if param.required && !params.contains_key(&param.name) && param.default.is_none() {
7041                return Err(format!("Missing required parameter: {}", param.name));
7042            }
7043        }
7044
7045        // Create instance with parameters applied
7046        Ok(self.clone())
7047    }
7048}
7049
7050impl std::fmt::Display for WorkflowTemplate {
7051    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7052        write!(f, "WorkflowTemplate[{}@{}]", self.name, self.version)?;
7053        if !self.parameters.is_empty() {
7054            write!(f, " params={}", self.parameters.len())?;
7055        }
7056        Ok(())
7057    }
7058}
7059
7060// ============================================================================
7061// Event-Driven Workflows
7062// ============================================================================
7063
7064/// Workflow event types
7065#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7066pub enum WorkflowEvent {
7067    /// Task completed
7068    TaskCompleted { task_id: Uuid },
7069    /// Task failed
7070    TaskFailed { task_id: Uuid, error: String },
7071    /// Workflow started
7072    WorkflowStarted { workflow_id: Uuid },
7073    /// Workflow completed
7074    WorkflowCompleted { workflow_id: Uuid },
7075    /// Workflow failed
7076    WorkflowFailed { workflow_id: Uuid, error: String },
7077    /// Custom event
7078    Custom { event_type: String, data: String },
7079}
7080
7081impl std::fmt::Display for WorkflowEvent {
7082    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7083        match self {
7084            Self::TaskCompleted { task_id } => write!(f, "TaskCompleted[{}]", task_id),
7085            Self::TaskFailed { task_id, .. } => write!(f, "TaskFailed[{}]", task_id),
7086            Self::WorkflowStarted { workflow_id } => write!(f, "WorkflowStarted[{}]", workflow_id),
7087            Self::WorkflowCompleted { workflow_id } => {
7088                write!(f, "WorkflowCompleted[{}]", workflow_id)
7089            }
7090            Self::WorkflowFailed { workflow_id, .. } => {
7091                write!(f, "WorkflowFailed[{}]", workflow_id)
7092            }
7093            Self::Custom { event_type, .. } => write!(f, "Custom[{}]", event_type),
7094        }
7095    }
7096}
7097
7098/// Event handler configuration
7099#[derive(Debug, Clone, Serialize, Deserialize)]
7100pub struct EventHandler {
7101    /// Event type to handle
7102    pub event_type: String,
7103    /// Task to execute on event
7104    pub handler_task: Signature,
7105    /// Event filter (optional)
7106    pub filter: Option<String>,
7107}
7108
7109impl EventHandler {
7110    /// Create a new event handler
7111    pub fn new(event_type: impl Into<String>, handler_task: Signature) -> Self {
7112        Self {
7113            event_type: event_type.into(),
7114            handler_task,
7115            filter: None,
7116        }
7117    }
7118
7119    /// Set event filter
7120    pub fn with_filter(mut self, filter: impl Into<String>) -> Self {
7121        self.filter = Some(filter.into());
7122        self
7123    }
7124}
7125
7126impl std::fmt::Display for EventHandler {
7127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7128        write!(
7129            f,
7130            "EventHandler[event={}, handler={}]",
7131            self.event_type, self.handler_task.task
7132        )
7133    }
7134}
7135
7136/// Event-driven workflow
7137#[derive(Debug, Clone, Serialize, Deserialize)]
7138pub struct EventDrivenWorkflow {
7139    /// Workflow ID
7140    pub workflow_id: Uuid,
7141    /// Event handlers
7142    pub handlers: Vec<EventHandler>,
7143    /// Whether workflow is active
7144    pub active: bool,
7145}
7146
7147impl EventDrivenWorkflow {
7148    /// Create a new event-driven workflow
7149    pub fn new() -> Self {
7150        Self {
7151            workflow_id: Uuid::new_v4(),
7152            handlers: Vec::new(),
7153            active: true,
7154        }
7155    }
7156
7157    /// Add an event handler
7158    pub fn on_event(mut self, handler: EventHandler) -> Self {
7159        self.handlers.push(handler);
7160        self
7161    }
7162
7163    /// Add handler for task completion
7164    pub fn on_task_completed(self, task: Signature) -> Self {
7165        self.on_event(EventHandler::new("TaskCompleted", task))
7166    }
7167
7168    /// Add handler for task failure
7169    pub fn on_task_failed(self, task: Signature) -> Self {
7170        self.on_event(EventHandler::new("TaskFailed", task))
7171    }
7172
7173    /// Activate workflow
7174    pub fn activate(mut self) -> Self {
7175        self.active = true;
7176        self
7177    }
7178
7179    /// Deactivate workflow
7180    pub fn deactivate(mut self) -> Self {
7181        self.active = false;
7182        self
7183    }
7184
7185    /// Check if workflow has handlers
7186    pub fn has_handlers(&self) -> bool {
7187        !self.handlers.is_empty()
7188    }
7189}
7190
7191impl Default for EventDrivenWorkflow {
7192    fn default() -> Self {
7193        Self::new()
7194    }
7195}
7196
7197impl std::fmt::Display for EventDrivenWorkflow {
7198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7199        write!(
7200            f,
7201            "EventDrivenWorkflow[id={}, handlers={}]",
7202            self.workflow_id,
7203            self.handlers.len()
7204        )?;
7205        if !self.active {
7206            write!(f, " (inactive)")?;
7207        }
7208        Ok(())
7209    }
7210}
7211
7212/// Canvas errors
7213#[derive(Debug, thiserror::Error)]
7214pub enum CanvasError {
7215    #[error("Invalid workflow: {0}")]
7216    Invalid(String),
7217
7218    #[error("Broker error: {0}")]
7219    Broker(String),
7220
7221    #[error("Serialization error: {0}")]
7222    Serialization(String),
7223
7224    #[error("Workflow cancelled: {0}")]
7225    Cancelled(String),
7226
7227    #[error("Workflow timeout: {0}")]
7228    Timeout(String),
7229}
7230
7231impl CanvasError {
7232    /// Check if error is invalid workflow
7233    pub fn is_invalid(&self) -> bool {
7234        matches!(self, CanvasError::Invalid(_))
7235    }
7236
7237    /// Check if error is broker-related
7238    pub fn is_broker(&self) -> bool {
7239        matches!(self, CanvasError::Broker(_))
7240    }
7241
7242    /// Check if error is serialization-related
7243    pub fn is_serialization(&self) -> bool {
7244        matches!(self, CanvasError::Serialization(_))
7245    }
7246
7247    /// Check if error is cancellation-related
7248    pub fn is_cancelled(&self) -> bool {
7249        matches!(self, CanvasError::Cancelled(_))
7250    }
7251
7252    /// Check if error is timeout-related
7253    pub fn is_timeout(&self) -> bool {
7254        matches!(self, CanvasError::Timeout(_))
7255    }
7256
7257    /// Check if this error is retryable
7258    ///
7259    /// Broker errors are typically retryable (transient network issues).
7260    /// Invalid workflow, serialization, cancellation, and timeout errors are not retryable.
7261    pub fn is_retryable(&self) -> bool {
7262        matches!(self, CanvasError::Broker(_))
7263    }
7264
7265    /// Get the error category as a string
7266    pub fn category(&self) -> &'static str {
7267        match self {
7268            CanvasError::Invalid(_) => "invalid",
7269            CanvasError::Broker(_) => "broker",
7270            CanvasError::Serialization(_) => "serialization",
7271            CanvasError::Cancelled(_) => "cancelled",
7272            CanvasError::Timeout(_) => "timeout",
7273        }
7274    }
7275}
7276
7277// ============================================================================
7278// Parallel Workflow Scheduling
7279// ============================================================================
7280
7281/// Task priority for scheduling
7282#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
7283pub enum TaskPriority {
7284    /// Low priority (value: 0)
7285    Low = 0,
7286    /// Normal priority (value: 5)
7287    #[default]
7288    Normal = 5,
7289    /// High priority (value: 10)
7290    High = 10,
7291    /// Critical priority (value: 15)
7292    Critical = 15,
7293}
7294
7295impl std::fmt::Display for TaskPriority {
7296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7297        match self {
7298            Self::Low => write!(f, "Low"),
7299            Self::Normal => write!(f, "Normal"),
7300            Self::High => write!(f, "High"),
7301            Self::Critical => write!(f, "Critical"),
7302        }
7303    }
7304}
7305
7306/// Worker resource capacity
7307#[derive(Debug, Clone, Serialize, Deserialize)]
7308pub struct WorkerCapacity {
7309    /// Worker ID
7310    pub worker_id: String,
7311    /// CPU cores available
7312    pub cpu_cores: u32,
7313    /// Memory available (MB)
7314    pub memory_mb: u64,
7315    /// Current load (0.0 to 1.0)
7316    pub current_load: f64,
7317    /// Active tasks count
7318    pub active_tasks: usize,
7319}
7320
7321impl WorkerCapacity {
7322    /// Create a new worker capacity
7323    pub fn new(worker_id: impl Into<String>, cpu_cores: u32, memory_mb: u64) -> Self {
7324        Self {
7325            worker_id: worker_id.into(),
7326            cpu_cores,
7327            memory_mb,
7328            current_load: 0.0,
7329            active_tasks: 0,
7330        }
7331    }
7332
7333    /// Check if worker has capacity for a task
7334    pub fn has_capacity(&self, required_load: f64) -> bool {
7335        self.current_load + required_load <= 1.0
7336    }
7337
7338    /// Get available capacity
7339    pub fn available_capacity(&self) -> f64 {
7340        (1.0 - self.current_load).max(0.0)
7341    }
7342}
7343
7344/// Task scheduling decision
7345#[derive(Debug, Clone, Serialize, Deserialize)]
7346pub struct SchedulingDecision {
7347    /// Task ID
7348    pub task_id: Uuid,
7349    /// Assigned worker ID
7350    pub worker_id: String,
7351    /// Priority
7352    pub priority: TaskPriority,
7353    /// Estimated execution time (seconds)
7354    pub estimated_time: Option<u64>,
7355}
7356
7357impl SchedulingDecision {
7358    /// Create a new scheduling decision
7359    pub fn new(task_id: Uuid, worker_id: impl Into<String>, priority: TaskPriority) -> Self {
7360        Self {
7361            task_id,
7362            worker_id: worker_id.into(),
7363            priority,
7364            estimated_time: None,
7365        }
7366    }
7367
7368    /// Set estimated execution time
7369    pub fn with_estimated_time(mut self, seconds: u64) -> Self {
7370        self.estimated_time = Some(seconds);
7371        self
7372    }
7373}
7374
7375/// Scheduling strategy for task distribution
7376#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
7377pub enum SchedulingStrategy {
7378    /// Round-robin distribution
7379    RoundRobin,
7380    /// Assign to worker with lowest load
7381    #[default]
7382    LeastLoaded,
7383    /// Priority-based scheduling
7384    PriorityBased,
7385    /// Resource-aware scheduling
7386    ResourceAware,
7387}
7388
7389impl std::fmt::Display for SchedulingStrategy {
7390    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7391        match self {
7392            Self::RoundRobin => write!(f, "RoundRobin"),
7393            Self::LeastLoaded => write!(f, "LeastLoaded"),
7394            Self::PriorityBased => write!(f, "PriorityBased"),
7395            Self::ResourceAware => write!(f, "ResourceAware"),
7396        }
7397    }
7398}
7399
7400/// Parallel workflow scheduler for task distribution
7401#[derive(Debug, Clone)]
7402pub struct ParallelScheduler {
7403    /// Scheduling strategy
7404    pub strategy: SchedulingStrategy,
7405    /// Worker capacities
7406    pub workers: Vec<WorkerCapacity>,
7407    /// Enable load balancing
7408    pub load_balancing: bool,
7409    /// Maximum tasks per worker
7410    pub max_tasks_per_worker: Option<usize>,
7411}
7412
7413impl ParallelScheduler {
7414    /// Create a new parallel scheduler
7415    pub fn new(strategy: SchedulingStrategy) -> Self {
7416        Self {
7417            strategy,
7418            workers: Vec::new(),
7419            load_balancing: true,
7420            max_tasks_per_worker: None,
7421        }
7422    }
7423
7424    /// Add a worker to the scheduler
7425    pub fn add_worker(&mut self, worker: WorkerCapacity) {
7426        self.workers.push(worker);
7427    }
7428
7429    /// Enable load balancing
7430    pub fn with_load_balancing(mut self, enabled: bool) -> Self {
7431        self.load_balancing = enabled;
7432        self
7433    }
7434
7435    /// Set maximum tasks per worker
7436    pub fn with_max_tasks_per_worker(mut self, max: usize) -> Self {
7437        self.max_tasks_per_worker = Some(max);
7438        self
7439    }
7440
7441    /// Schedule a task to a worker
7442    pub fn schedule_task(
7443        &self,
7444        task_id: Uuid,
7445        priority: TaskPriority,
7446    ) -> Option<SchedulingDecision> {
7447        if self.workers.is_empty() {
7448            return None;
7449        }
7450
7451        let worker_id = match self.strategy {
7452            SchedulingStrategy::RoundRobin => {
7453                // Simple round-robin based on task count
7454                self.workers
7455                    .iter()
7456                    .min_by_key(|w| w.active_tasks)
7457                    .map(|w| w.worker_id.clone())
7458            }
7459            SchedulingStrategy::LeastLoaded => {
7460                // Assign to worker with lowest load
7461                self.workers
7462                    .iter()
7463                    .filter(|w| {
7464                        if let Some(max) = self.max_tasks_per_worker {
7465                            w.active_tasks < max
7466                        } else {
7467                            true
7468                        }
7469                    })
7470                    .min_by(|a, b| {
7471                        a.current_load
7472                            .partial_cmp(&b.current_load)
7473                            .unwrap_or(std::cmp::Ordering::Equal)
7474                    })
7475                    .map(|w| w.worker_id.clone())
7476            }
7477            SchedulingStrategy::PriorityBased => {
7478                // Higher priority tasks go to less loaded workers
7479                let priority_weight = priority as u8 as f64 / 15.0;
7480                self.workers
7481                    .iter()
7482                    .filter(|w| {
7483                        if let Some(max) = self.max_tasks_per_worker {
7484                            w.active_tasks < max
7485                        } else {
7486                            true
7487                        }
7488                    })
7489                    .min_by(|a, b| {
7490                        let a_score = a.current_load * (1.0 - priority_weight);
7491                        let b_score = b.current_load * (1.0 - priority_weight);
7492                        a_score
7493                            .partial_cmp(&b_score)
7494                            .unwrap_or(std::cmp::Ordering::Equal)
7495                    })
7496                    .map(|w| w.worker_id.clone())
7497            }
7498            SchedulingStrategy::ResourceAware => {
7499                // Consider both CPU and memory availability
7500                self.workers
7501                    .iter()
7502                    .filter(|w| {
7503                        if let Some(max) = self.max_tasks_per_worker {
7504                            w.active_tasks < max
7505                        } else {
7506                            true
7507                        }
7508                    })
7509                    .max_by(|a, b| {
7510                        let a_score = a.available_capacity()
7511                            * (a.cpu_cores as f64 / 100.0)
7512                            * (a.memory_mb as f64 / 1_000_000.0);
7513                        let b_score = b.available_capacity()
7514                            * (b.cpu_cores as f64 / 100.0)
7515                            * (b.memory_mb as f64 / 1_000_000.0);
7516                        a_score
7517                            .partial_cmp(&b_score)
7518                            .unwrap_or(std::cmp::Ordering::Equal)
7519                    })
7520                    .map(|w| w.worker_id.clone())
7521            }
7522        };
7523
7524        worker_id.map(|id| SchedulingDecision::new(task_id, id, priority))
7525    }
7526
7527    /// Get worker count
7528    pub fn worker_count(&self) -> usize {
7529        self.workers.len()
7530    }
7531
7532    /// Get total capacity across all workers
7533    pub fn total_capacity(&self) -> f64 {
7534        self.workers.iter().map(|w| w.available_capacity()).sum()
7535    }
7536
7537    /// Get average load across all workers
7538    pub fn average_load(&self) -> f64 {
7539        if self.workers.is_empty() {
7540            return 0.0;
7541        }
7542        let total_load: f64 = self.workers.iter().map(|w| w.current_load).sum();
7543        total_load / self.workers.len() as f64
7544    }
7545}
7546
7547impl Default for ParallelScheduler {
7548    fn default() -> Self {
7549        Self::new(SchedulingStrategy::default())
7550    }
7551}
7552
7553impl std::fmt::Display for ParallelScheduler {
7554    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7555        write!(
7556            f,
7557            "ParallelScheduler[strategy={}, workers={}, avg_load={:.2}]",
7558            self.strategy,
7559            self.workers.len(),
7560            self.average_load()
7561        )
7562    }
7563}
7564
7565// ============================================================================
7566// Workflow Batching
7567// ============================================================================
7568
7569/// Workflow batch for grouping similar workflows
7570#[derive(Debug, Clone, Serialize, Deserialize)]
7571pub struct WorkflowBatch {
7572    /// Batch ID
7573    pub batch_id: Uuid,
7574    /// Workflow IDs in this batch
7575    pub workflow_ids: Vec<Uuid>,
7576    /// Batch priority
7577    pub priority: TaskPriority,
7578    /// Maximum batch size
7579    pub max_size: usize,
7580    /// Batch timeout (seconds)
7581    pub timeout: Option<u64>,
7582    /// Creation timestamp
7583    pub created_at: u64,
7584}
7585
7586impl WorkflowBatch {
7587    /// Create a new workflow batch
7588    pub fn new(max_size: usize) -> Self {
7589        Self {
7590            batch_id: Uuid::new_v4(),
7591            workflow_ids: Vec::new(),
7592            priority: TaskPriority::Normal,
7593            max_size,
7594            timeout: None,
7595            created_at: std::time::SystemTime::now()
7596                .duration_since(std::time::UNIX_EPOCH)
7597                .unwrap()
7598                .as_secs(),
7599        }
7600    }
7601
7602    /// Add a workflow to the batch
7603    pub fn add_workflow(&mut self, workflow_id: Uuid) -> bool {
7604        if self.workflow_ids.len() < self.max_size {
7605            self.workflow_ids.push(workflow_id);
7606            true
7607        } else {
7608            false
7609        }
7610    }
7611
7612    /// Check if batch is full
7613    pub fn is_full(&self) -> bool {
7614        self.workflow_ids.len() >= self.max_size
7615    }
7616
7617    /// Check if batch is empty
7618    pub fn is_empty(&self) -> bool {
7619        self.workflow_ids.is_empty()
7620    }
7621
7622    /// Get batch size
7623    pub fn size(&self) -> usize {
7624        self.workflow_ids.len()
7625    }
7626
7627    /// Check if batch has timed out
7628    pub fn is_timed_out(&self) -> bool {
7629        if let Some(timeout) = self.timeout {
7630            let now = std::time::SystemTime::now()
7631                .duration_since(std::time::UNIX_EPOCH)
7632                .unwrap()
7633                .as_secs();
7634            let age = now.saturating_sub(self.created_at);
7635            age >= timeout
7636        } else {
7637            false
7638        }
7639    }
7640
7641    /// Set priority
7642    pub fn with_priority(mut self, priority: TaskPriority) -> Self {
7643        self.priority = priority;
7644        self
7645    }
7646
7647    /// Set timeout
7648    pub fn with_timeout(mut self, seconds: u64) -> Self {
7649        self.timeout = Some(seconds);
7650        self
7651    }
7652}
7653
7654impl std::fmt::Display for WorkflowBatch {
7655    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7656        write!(
7657            f,
7658            "WorkflowBatch[id={}, size={}/{}, priority={}]",
7659            self.batch_id,
7660            self.size(),
7661            self.max_size,
7662            self.priority
7663        )
7664    }
7665}
7666
7667/// Batching strategy for workflow grouping
7668#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
7669pub enum BatchingStrategy {
7670    /// Batch by workflow type
7671    #[default]
7672    ByType,
7673    /// Batch by priority
7674    ByPriority,
7675    /// Batch by size (group similar-sized workflows)
7676    BySize,
7677    /// Batch by time window
7678    ByTimeWindow,
7679}
7680
7681impl std::fmt::Display for BatchingStrategy {
7682    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7683        match self {
7684            Self::ByType => write!(f, "ByType"),
7685            Self::ByPriority => write!(f, "ByPriority"),
7686            Self::BySize => write!(f, "BySize"),
7687            Self::ByTimeWindow => write!(f, "ByTimeWindow"),
7688        }
7689    }
7690}
7691
7692/// Workflow batcher for grouping similar workflows
7693#[derive(Debug, Clone)]
7694pub struct WorkflowBatcher {
7695    /// Batching strategy
7696    pub strategy: BatchingStrategy,
7697    /// Active batches
7698    pub batches: Vec<WorkflowBatch>,
7699    /// Default batch size
7700    pub default_batch_size: usize,
7701    /// Default batch timeout (seconds)
7702    pub default_timeout: Option<u64>,
7703}
7704
7705impl WorkflowBatcher {
7706    /// Create a new workflow batcher
7707    pub fn new(strategy: BatchingStrategy) -> Self {
7708        Self {
7709            strategy,
7710            batches: Vec::new(),
7711            default_batch_size: 10,
7712            default_timeout: Some(60), // 1 minute default
7713        }
7714    }
7715
7716    /// Set default batch size
7717    pub fn with_batch_size(mut self, size: usize) -> Self {
7718        self.default_batch_size = size;
7719        self
7720    }
7721
7722    /// Set default batch timeout
7723    pub fn with_timeout(mut self, seconds: u64) -> Self {
7724        self.default_timeout = Some(seconds);
7725        self
7726    }
7727
7728    /// Add a workflow to a batch
7729    pub fn add_workflow(&mut self, workflow_id: Uuid, priority: TaskPriority) -> Uuid {
7730        // Find or create appropriate batch
7731        let batch_id = match self.strategy {
7732            BatchingStrategy::ByPriority => {
7733                // Find batch with matching priority
7734                let batch = self
7735                    .batches
7736                    .iter_mut()
7737                    .find(|b| b.priority == priority && !b.is_full() && !b.is_timed_out());
7738
7739                if let Some(batch) = batch {
7740                    batch.add_workflow(workflow_id);
7741                    batch.batch_id
7742                } else {
7743                    // Create new batch
7744                    let mut new_batch =
7745                        WorkflowBatch::new(self.default_batch_size).with_priority(priority);
7746                    if let Some(timeout) = self.default_timeout {
7747                        new_batch = new_batch.with_timeout(timeout);
7748                    }
7749                    new_batch.add_workflow(workflow_id);
7750                    let batch_id = new_batch.batch_id;
7751                    self.batches.push(new_batch);
7752                    batch_id
7753                }
7754            }
7755            _ => {
7756                // For other strategies, use first available batch
7757                let batch = self
7758                    .batches
7759                    .iter_mut()
7760                    .find(|b| !b.is_full() && !b.is_timed_out());
7761
7762                if let Some(batch) = batch {
7763                    batch.add_workflow(workflow_id);
7764                    batch.batch_id
7765                } else {
7766                    // Create new batch
7767                    let mut new_batch = WorkflowBatch::new(self.default_batch_size);
7768                    if let Some(timeout) = self.default_timeout {
7769                        new_batch = new_batch.with_timeout(timeout);
7770                    }
7771                    new_batch.add_workflow(workflow_id);
7772                    let batch_id = new_batch.batch_id;
7773                    self.batches.push(new_batch);
7774                    batch_id
7775                }
7776            }
7777        };
7778
7779        batch_id
7780    }
7781
7782    /// Get ready batches (full or timed out)
7783    pub fn get_ready_batches(&self) -> Vec<&WorkflowBatch> {
7784        self.batches
7785            .iter()
7786            .filter(|b| b.is_full() || b.is_timed_out())
7787            .collect()
7788    }
7789
7790    /// Remove ready batches
7791    pub fn remove_ready_batches(&mut self) -> Vec<WorkflowBatch> {
7792        let (ready, pending): (Vec<_>, Vec<_>) = self
7793            .batches
7794            .drain(..)
7795            .partition(|b| b.is_full() || b.is_timed_out());
7796        self.batches = pending;
7797        ready
7798    }
7799
7800    /// Get batch count
7801    pub fn batch_count(&self) -> usize {
7802        self.batches.len()
7803    }
7804
7805    /// Get total workflow count across all batches
7806    pub fn total_workflow_count(&self) -> usize {
7807        self.batches.iter().map(|b| b.size()).sum()
7808    }
7809}
7810
7811impl Default for WorkflowBatcher {
7812    fn default() -> Self {
7813        Self::new(BatchingStrategy::default())
7814    }
7815}
7816
7817impl std::fmt::Display for WorkflowBatcher {
7818    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7819        write!(
7820            f,
7821            "WorkflowBatcher[strategy={}, batches={}, workflows={}]",
7822            self.strategy,
7823            self.batch_count(),
7824            self.total_workflow_count()
7825        )
7826    }
7827}
7828
7829// ============================================================================
7830// Streaming Map-Reduce
7831// ============================================================================
7832
7833/// Streaming map-reduce for processing large datasets
7834#[derive(Debug, Clone, Serialize, Deserialize)]
7835pub struct StreamingMapReduce {
7836    /// Map task signature
7837    pub map_task: Signature,
7838    /// Reduce task signature
7839    pub reduce_task: Signature,
7840    /// Chunk size for streaming
7841    pub chunk_size: usize,
7842    /// Buffer size for intermediate results
7843    pub buffer_size: usize,
7844    /// Enable backpressure control
7845    pub backpressure: bool,
7846}
7847
7848impl StreamingMapReduce {
7849    /// Create a new streaming map-reduce
7850    pub fn new(map_task: Signature, reduce_task: Signature) -> Self {
7851        Self {
7852            map_task,
7853            reduce_task,
7854            chunk_size: 100,
7855            buffer_size: 1000,
7856            backpressure: true,
7857        }
7858    }
7859
7860    /// Set chunk size
7861    pub fn with_chunk_size(mut self, size: usize) -> Self {
7862        self.chunk_size = size;
7863        self
7864    }
7865
7866    /// Set buffer size
7867    pub fn with_buffer_size(mut self, size: usize) -> Self {
7868        self.buffer_size = size;
7869        self
7870    }
7871
7872    /// Enable/disable backpressure
7873    pub fn with_backpressure(mut self, enabled: bool) -> Self {
7874        self.backpressure = enabled;
7875        self
7876    }
7877}
7878
7879impl std::fmt::Display for StreamingMapReduce {
7880    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7881        write!(
7882            f,
7883            "StreamingMapReduce[map={}, reduce={}, chunk_size={}, buffer_size={}]",
7884            self.map_task.task, self.reduce_task.task, self.chunk_size, self.buffer_size
7885        )
7886    }
7887}
7888
7889// ============================================================================
7890// Reactive Workflows
7891// ============================================================================
7892
7893/// Observable value that can trigger reactive workflows
7894#[derive(Debug, Clone, Serialize, Deserialize)]
7895pub struct Observable<T> {
7896    /// Current value
7897    pub value: T,
7898    /// Subscribers (workflow IDs)
7899    #[serde(skip)]
7900    pub subscribers: Vec<Uuid>,
7901    /// Value change history
7902    pub history: Vec<(T, u64)>, // (value, timestamp)
7903}
7904
7905impl<T: Clone> Observable<T> {
7906    /// Create a new observable with initial value
7907    pub fn new(value: T) -> Self {
7908        Self {
7909            value,
7910            subscribers: Vec::new(),
7911            history: Vec::new(),
7912        }
7913    }
7914
7915    /// Subscribe a workflow to this observable
7916    pub fn subscribe(&mut self, workflow_id: Uuid) {
7917        if !self.subscribers.contains(&workflow_id) {
7918            self.subscribers.push(workflow_id);
7919        }
7920    }
7921
7922    /// Unsubscribe a workflow
7923    pub fn unsubscribe(&mut self, workflow_id: &Uuid) {
7924        self.subscribers.retain(|id| id != workflow_id);
7925    }
7926
7927    /// Update the value and notify subscribers
7928    pub fn set(&mut self, value: T) {
7929        let timestamp = std::time::SystemTime::now()
7930            .duration_since(std::time::UNIX_EPOCH)
7931            .unwrap()
7932            .as_secs();
7933        self.history.push((self.value.clone(), timestamp));
7934        self.value = value;
7935    }
7936
7937    /// Get current value
7938    pub fn get(&self) -> &T {
7939        &self.value
7940    }
7941
7942    /// Get subscriber count
7943    pub fn subscriber_count(&self) -> usize {
7944        self.subscribers.len()
7945    }
7946}
7947
7948/// Reactive workflow that responds to observable changes
7949#[derive(Debug, Clone, Serialize, Deserialize)]
7950pub struct ReactiveWorkflow {
7951    /// Workflow ID
7952    pub workflow_id: Uuid,
7953    /// Observable IDs being watched
7954    pub watched_observables: Vec<String>,
7955    /// Reaction task
7956    pub reaction_task: Signature,
7957    /// Debounce time (milliseconds)
7958    pub debounce_ms: Option<u64>,
7959    /// Throttle time (milliseconds)
7960    pub throttle_ms: Option<u64>,
7961    /// Filter condition (optional)
7962    pub filter: Option<String>,
7963}
7964
7965impl ReactiveWorkflow {
7966    /// Create a new reactive workflow
7967    pub fn new(reaction_task: Signature) -> Self {
7968        Self {
7969            workflow_id: Uuid::new_v4(),
7970            watched_observables: Vec::new(),
7971            reaction_task,
7972            debounce_ms: None,
7973            throttle_ms: None,
7974            filter: None,
7975        }
7976    }
7977
7978    /// Watch an observable
7979    pub fn watch(mut self, observable_id: impl Into<String>) -> Self {
7980        self.watched_observables.push(observable_id.into());
7981        self
7982    }
7983
7984    /// Set debounce time (delay reaction until changes stop)
7985    pub fn with_debounce(mut self, milliseconds: u64) -> Self {
7986        self.debounce_ms = Some(milliseconds);
7987        self
7988    }
7989
7990    /// Set throttle time (limit reaction frequency)
7991    pub fn with_throttle(mut self, milliseconds: u64) -> Self {
7992        self.throttle_ms = Some(milliseconds);
7993        self
7994    }
7995
7996    /// Set filter condition
7997    pub fn with_filter(mut self, condition: impl Into<String>) -> Self {
7998        self.filter = Some(condition.into());
7999        self
8000    }
8001}
8002
8003impl std::fmt::Display for ReactiveWorkflow {
8004    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8005        write!(
8006            f,
8007            "ReactiveWorkflow[id={}, watching={}, reaction={}]",
8008            self.workflow_id,
8009            self.watched_observables.len(),
8010            self.reaction_task.task
8011        )
8012    }
8013}
8014
8015/// Stream operator for reactive data processing
8016#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8017pub enum StreamOperator {
8018    /// Map values
8019    Map,
8020    /// Filter values
8021    Filter,
8022    /// Reduce values
8023    Reduce,
8024    /// Scan (accumulate)
8025    Scan,
8026    /// Take first N values
8027    Take,
8028    /// Skip first N values
8029    Skip,
8030    /// Debounce
8031    Debounce,
8032    /// Throttle
8033    Throttle,
8034}
8035
8036impl std::fmt::Display for StreamOperator {
8037    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8038        match self {
8039            Self::Map => write!(f, "Map"),
8040            Self::Filter => write!(f, "Filter"),
8041            Self::Reduce => write!(f, "Reduce"),
8042            Self::Scan => write!(f, "Scan"),
8043            Self::Take => write!(f, "Take"),
8044            Self::Skip => write!(f, "Skip"),
8045            Self::Debounce => write!(f, "Debounce"),
8046            Self::Throttle => write!(f, "Throttle"),
8047        }
8048    }
8049}
8050
8051/// Reactive stream for data flow processing
8052#[derive(Debug, Clone, Serialize, Deserialize)]
8053pub struct ReactiveStream {
8054    /// Stream ID
8055    pub stream_id: Uuid,
8056    /// Source observable ID
8057    pub source_id: String,
8058    /// Stream operators
8059    pub operators: Vec<(StreamOperator, serde_json::Value)>,
8060    /// Subscriber workflows
8061    #[serde(skip)]
8062    pub subscribers: Vec<Uuid>,
8063}
8064
8065impl ReactiveStream {
8066    /// Create a new reactive stream
8067    pub fn new(source_id: impl Into<String>) -> Self {
8068        Self {
8069            stream_id: Uuid::new_v4(),
8070            source_id: source_id.into(),
8071            operators: Vec::new(),
8072            subscribers: Vec::new(),
8073        }
8074    }
8075
8076    /// Add a map operator
8077    pub fn map(mut self, transform: serde_json::Value) -> Self {
8078        self.operators.push((StreamOperator::Map, transform));
8079        self
8080    }
8081
8082    /// Add a filter operator
8083    pub fn filter(mut self, condition: serde_json::Value) -> Self {
8084        self.operators.push((StreamOperator::Filter, condition));
8085        self
8086    }
8087
8088    /// Add a take operator
8089    pub fn take(mut self, count: usize) -> Self {
8090        self.operators
8091            .push((StreamOperator::Take, serde_json::json!(count)));
8092        self
8093    }
8094
8095    /// Add a skip operator
8096    pub fn skip(mut self, count: usize) -> Self {
8097        self.operators
8098            .push((StreamOperator::Skip, serde_json::json!(count)));
8099        self
8100    }
8101
8102    /// Add a debounce operator
8103    pub fn debounce(mut self, milliseconds: u64) -> Self {
8104        self.operators
8105            .push((StreamOperator::Debounce, serde_json::json!(milliseconds)));
8106        self
8107    }
8108
8109    /// Add a throttle operator
8110    pub fn throttle(mut self, milliseconds: u64) -> Self {
8111        self.operators
8112            .push((StreamOperator::Throttle, serde_json::json!(milliseconds)));
8113        self
8114    }
8115
8116    /// Subscribe a workflow to this stream
8117    pub fn subscribe(&mut self, workflow_id: Uuid) {
8118        if !self.subscribers.contains(&workflow_id) {
8119            self.subscribers.push(workflow_id);
8120        }
8121    }
8122}
8123
8124impl std::fmt::Display for ReactiveStream {
8125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8126        write!(
8127            f,
8128            "ReactiveStream[id={}, source={}, operators={}]",
8129            self.stream_id,
8130            self.source_id,
8131            self.operators.len()
8132        )
8133    }
8134}
8135
8136// ============================================================================
8137// Resource Utilization Monitoring
8138// ============================================================================
8139
8140/// Resource utilization metrics
8141#[derive(Debug, Clone, Serialize, Deserialize)]
8142pub struct ResourceUtilization {
8143    /// CPU utilization (0.0 to 1.0)
8144    pub cpu: f64,
8145    /// Memory utilization (0.0 to 1.0)
8146    pub memory: f64,
8147    /// Disk I/O utilization (0.0 to 1.0)
8148    pub disk_io: f64,
8149    /// Network I/O utilization (0.0 to 1.0)
8150    pub network_io: f64,
8151    /// Timestamp
8152    pub timestamp: u64,
8153}
8154
8155impl ResourceUtilization {
8156    /// Create a new resource utilization snapshot
8157    pub fn new(cpu: f64, memory: f64, disk_io: f64, network_io: f64) -> Self {
8158        Self {
8159            cpu: cpu.clamp(0.0, 1.0),
8160            memory: memory.clamp(0.0, 1.0),
8161            disk_io: disk_io.clamp(0.0, 1.0),
8162            network_io: network_io.clamp(0.0, 1.0),
8163            timestamp: std::time::SystemTime::now()
8164                .duration_since(std::time::UNIX_EPOCH)
8165                .unwrap()
8166                .as_secs(),
8167        }
8168    }
8169
8170    /// Get overall utilization (average of all metrics)
8171    pub fn overall(&self) -> f64 {
8172        (self.cpu + self.memory + self.disk_io + self.network_io) / 4.0
8173    }
8174
8175    /// Check if any resource is over threshold
8176    pub fn is_overloaded(&self, threshold: f64) -> bool {
8177        self.cpu > threshold
8178            || self.memory > threshold
8179            || self.disk_io > threshold
8180            || self.network_io > threshold
8181    }
8182
8183    /// Get the most utilized resource
8184    pub fn bottleneck(&self) -> &'static str {
8185        let max = self
8186            .cpu
8187            .max(self.memory)
8188            .max(self.disk_io)
8189            .max(self.network_io);
8190        if (max - self.cpu).abs() < f64::EPSILON {
8191            "cpu"
8192        } else if (max - self.memory).abs() < f64::EPSILON {
8193            "memory"
8194        } else if (max - self.disk_io).abs() < f64::EPSILON {
8195            "disk_io"
8196        } else {
8197            "network_io"
8198        }
8199    }
8200}
8201
8202impl std::fmt::Display for ResourceUtilization {
8203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8204        write!(
8205            f,
8206            "ResourceUtilization[cpu={:.2}, mem={:.2}, disk={:.2}, net={:.2}, overall={:.2}]",
8207            self.cpu,
8208            self.memory,
8209            self.disk_io,
8210            self.network_io,
8211            self.overall()
8212        )
8213    }
8214}
8215
8216/// Workflow resource monitor
8217#[derive(Debug, Clone)]
8218pub struct WorkflowResourceMonitor {
8219    /// Workflow ID
8220    pub workflow_id: Uuid,
8221    /// Resource utilization history
8222    pub history: Vec<ResourceUtilization>,
8223    /// Maximum history size
8224    pub max_history: usize,
8225    /// Sampling interval (seconds)
8226    pub sampling_interval: u64,
8227}
8228
8229impl WorkflowResourceMonitor {
8230    /// Create a new resource monitor
8231    pub fn new(workflow_id: Uuid) -> Self {
8232        Self {
8233            workflow_id,
8234            history: Vec::new(),
8235            max_history: 1000,
8236            sampling_interval: 5,
8237        }
8238    }
8239
8240    /// Set maximum history size
8241    pub fn with_max_history(mut self, max: usize) -> Self {
8242        self.max_history = max;
8243        self
8244    }
8245
8246    /// Set sampling interval
8247    pub fn with_sampling_interval(mut self, seconds: u64) -> Self {
8248        self.sampling_interval = seconds;
8249        self
8250    }
8251
8252    /// Record resource utilization
8253    pub fn record(&mut self, utilization: ResourceUtilization) {
8254        self.history.push(utilization);
8255        // Trim history if needed
8256        if self.history.len() > self.max_history {
8257            self.history
8258                .drain(0..(self.history.len() - self.max_history));
8259        }
8260    }
8261
8262    /// Get average utilization over time window (seconds)
8263    pub fn average_utilization(&self, window_seconds: u64) -> Option<ResourceUtilization> {
8264        if self.history.is_empty() {
8265            return None;
8266        }
8267
8268        let now = std::time::SystemTime::now()
8269            .duration_since(std::time::UNIX_EPOCH)
8270            .unwrap()
8271            .as_secs();
8272        let cutoff = now.saturating_sub(window_seconds);
8273
8274        let recent: Vec<_> = self
8275            .history
8276            .iter()
8277            .filter(|u| u.timestamp >= cutoff)
8278            .collect();
8279
8280        if recent.is_empty() {
8281            return None;
8282        }
8283
8284        let sum_cpu: f64 = recent.iter().map(|u| u.cpu).sum();
8285        let sum_memory: f64 = recent.iter().map(|u| u.memory).sum();
8286        let sum_disk: f64 = recent.iter().map(|u| u.disk_io).sum();
8287        let sum_network: f64 = recent.iter().map(|u| u.network_io).sum();
8288        let count = recent.len() as f64;
8289
8290        Some(ResourceUtilization::new(
8291            sum_cpu / count,
8292            sum_memory / count,
8293            sum_disk / count,
8294            sum_network / count,
8295        ))
8296    }
8297
8298    /// Get peak utilization
8299    pub fn peak_utilization(&self) -> Option<&ResourceUtilization> {
8300        self.history.iter().max_by(|a, b| {
8301            a.overall()
8302                .partial_cmp(&b.overall())
8303                .unwrap_or(std::cmp::Ordering::Equal)
8304        })
8305    }
8306
8307    /// Clear history
8308    pub fn clear(&mut self) {
8309        self.history.clear();
8310    }
8311}
8312
8313impl std::fmt::Display for WorkflowResourceMonitor {
8314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8315        write!(
8316            f,
8317            "WorkflowResourceMonitor[workflow={}, samples={}]",
8318            self.workflow_id,
8319            self.history.len()
8320        )
8321    }
8322}
8323
8324// ============================================================================
8325// Workflow Testing Framework
8326// ============================================================================
8327
8328/// Mock task result for testing
8329#[derive(Debug, Clone, Serialize, Deserialize)]
8330pub struct MockTaskResult {
8331    /// Task name
8332    pub task_name: String,
8333    /// Result value
8334    pub result: serde_json::Value,
8335    /// Execution delay (milliseconds)
8336    pub delay_ms: u64,
8337    /// Whether to fail
8338    pub should_fail: bool,
8339    /// Failure message
8340    pub failure_message: Option<String>,
8341}
8342
8343impl MockTaskResult {
8344    /// Create a successful mock result
8345    pub fn success(task_name: impl Into<String>, result: serde_json::Value) -> Self {
8346        Self {
8347            task_name: task_name.into(),
8348            result,
8349            delay_ms: 0,
8350            should_fail: false,
8351            failure_message: None,
8352        }
8353    }
8354
8355    /// Create a failing mock result
8356    pub fn failure(task_name: impl Into<String>, message: impl Into<String>) -> Self {
8357        Self {
8358            task_name: task_name.into(),
8359            result: serde_json::Value::Null,
8360            delay_ms: 0,
8361            should_fail: true,
8362            failure_message: Some(message.into()),
8363        }
8364    }
8365
8366    /// Set execution delay
8367    pub fn with_delay(mut self, milliseconds: u64) -> Self {
8368        self.delay_ms = milliseconds;
8369        self
8370    }
8371}
8372
8373/// Mock task executor for testing workflows
8374#[derive(Debug, Clone)]
8375pub struct MockTaskExecutor {
8376    /// Mock results by task name
8377    pub mock_results: HashMap<String, MockTaskResult>,
8378    /// Execution history
8379    pub execution_history: Vec<(String, u64)>, // (task_name, timestamp)
8380}
8381
8382impl MockTaskExecutor {
8383    /// Create a new mock executor
8384    pub fn new() -> Self {
8385        Self {
8386            mock_results: HashMap::new(),
8387            execution_history: Vec::new(),
8388        }
8389    }
8390
8391    /// Register a mock result for a task
8392    pub fn register(&mut self, result: MockTaskResult) {
8393        self.mock_results.insert(result.task_name.clone(), result);
8394    }
8395
8396    /// Execute a mock task
8397    pub fn execute(&mut self, task_name: &str) -> Result<serde_json::Value, String> {
8398        let timestamp = std::time::SystemTime::now()
8399            .duration_since(std::time::UNIX_EPOCH)
8400            .unwrap()
8401            .as_secs();
8402        self.execution_history
8403            .push((task_name.to_string(), timestamp));
8404
8405        if let Some(mock_result) = self.mock_results.get(task_name) {
8406            // Simulate delay
8407            if mock_result.delay_ms > 0 {
8408                std::thread::sleep(std::time::Duration::from_millis(mock_result.delay_ms));
8409            }
8410
8411            if mock_result.should_fail {
8412                Err(mock_result
8413                    .failure_message
8414                    .clone()
8415                    .unwrap_or_else(|| "Mock task failed".to_string()))
8416            } else {
8417                Ok(mock_result.result.clone())
8418            }
8419        } else {
8420            Err(format!("No mock result registered for task: {}", task_name))
8421        }
8422    }
8423
8424    /// Get execution count for a task
8425    pub fn execution_count(&self, task_name: &str) -> usize {
8426        self.execution_history
8427            .iter()
8428            .filter(|(name, _)| name == task_name)
8429            .count()
8430    }
8431
8432    /// Clear execution history
8433    pub fn clear_history(&mut self) {
8434        self.execution_history.clear();
8435    }
8436}
8437
8438impl Default for MockTaskExecutor {
8439    fn default() -> Self {
8440        Self::new()
8441    }
8442}
8443
8444/// Test data injector for workflow testing
8445#[derive(Debug, Clone)]
8446pub struct TestDataInjector {
8447    /// Injected data by key
8448    pub data: HashMap<String, serde_json::Value>,
8449}
8450
8451impl TestDataInjector {
8452    /// Create a new test data injector
8453    pub fn new() -> Self {
8454        Self {
8455            data: HashMap::new(),
8456        }
8457    }
8458
8459    /// Inject test data
8460    pub fn inject(&mut self, key: impl Into<String>, value: serde_json::Value) {
8461        self.data.insert(key.into(), value);
8462    }
8463
8464    /// Get injected data
8465    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
8466        self.data.get(key)
8467    }
8468
8469    /// Clear all injected data
8470    pub fn clear(&mut self) {
8471        self.data.clear();
8472    }
8473}
8474
8475impl Default for TestDataInjector {
8476    fn default() -> Self {
8477        Self::new()
8478    }
8479}
8480
8481// ============================================================================
8482// Time-Travel Debugging
8483// ============================================================================
8484
8485/// Workflow execution snapshot for time-travel debugging
8486#[derive(Debug, Clone, Serialize, Deserialize)]
8487pub struct WorkflowSnapshot {
8488    /// Snapshot ID
8489    pub snapshot_id: Uuid,
8490    /// Workflow ID
8491    pub workflow_id: Uuid,
8492    /// Snapshot timestamp
8493    pub timestamp: u64,
8494    /// Workflow state at snapshot
8495    pub state: WorkflowState,
8496    /// Completed task IDs
8497    pub completed_tasks: Vec<Uuid>,
8498    /// Task results
8499    pub task_results: HashMap<Uuid, serde_json::Value>,
8500    /// Checkpoint data
8501    pub checkpoint: Option<WorkflowCheckpoint>,
8502}
8503
8504impl WorkflowSnapshot {
8505    /// Create a new workflow snapshot
8506    pub fn new(workflow_id: Uuid, state: WorkflowState) -> Self {
8507        Self {
8508            snapshot_id: Uuid::new_v4(),
8509            workflow_id,
8510            timestamp: std::time::SystemTime::now()
8511                .duration_since(std::time::UNIX_EPOCH)
8512                .unwrap()
8513                .as_secs(),
8514            state,
8515            completed_tasks: Vec::new(),
8516            task_results: HashMap::new(),
8517            checkpoint: None,
8518        }
8519    }
8520
8521    /// Record task completion
8522    pub fn record_task(&mut self, task_id: Uuid, result: serde_json::Value) {
8523        self.completed_tasks.push(task_id);
8524        self.task_results.insert(task_id, result);
8525    }
8526
8527    /// Attach checkpoint
8528    pub fn with_checkpoint(mut self, checkpoint: WorkflowCheckpoint) -> Self {
8529        self.checkpoint = Some(checkpoint);
8530        self
8531    }
8532}
8533
8534/// Time-travel debugger for workflow replay
8535#[derive(Debug, Clone)]
8536pub struct TimeTravelDebugger {
8537    /// Workflow ID
8538    pub workflow_id: Uuid,
8539    /// Snapshots history
8540    pub snapshots: Vec<WorkflowSnapshot>,
8541    /// Current snapshot index
8542    pub current_index: usize,
8543    /// Step mode enabled
8544    pub step_mode: bool,
8545}
8546
8547impl TimeTravelDebugger {
8548    /// Create a new time-travel debugger
8549    pub fn new(workflow_id: Uuid) -> Self {
8550        Self {
8551            workflow_id,
8552            snapshots: Vec::new(),
8553            current_index: 0,
8554            step_mode: false,
8555        }
8556    }
8557
8558    /// Record a snapshot
8559    pub fn record_snapshot(&mut self, snapshot: WorkflowSnapshot) {
8560        self.snapshots.push(snapshot);
8561        self.current_index = self.snapshots.len() - 1;
8562    }
8563
8564    /// Replay from a specific snapshot
8565    pub fn replay_from(&mut self, snapshot_index: usize) -> Option<&WorkflowSnapshot> {
8566        if snapshot_index < self.snapshots.len() {
8567            self.current_index = snapshot_index;
8568            self.snapshots.get(snapshot_index)
8569        } else {
8570            None
8571        }
8572    }
8573
8574    /// Step forward one snapshot
8575    pub fn step_forward(&mut self) -> Option<&WorkflowSnapshot> {
8576        if self.current_index + 1 < self.snapshots.len() {
8577            self.current_index += 1;
8578            self.snapshots.get(self.current_index)
8579        } else {
8580            None
8581        }
8582    }
8583
8584    /// Step backward one snapshot
8585    pub fn step_backward(&mut self) -> Option<&WorkflowSnapshot> {
8586        if self.current_index > 0 {
8587            self.current_index -= 1;
8588            self.snapshots.get(self.current_index)
8589        } else {
8590            None
8591        }
8592    }
8593
8594    /// Get current snapshot
8595    pub fn current_snapshot(&self) -> Option<&WorkflowSnapshot> {
8596        self.snapshots.get(self.current_index)
8597    }
8598
8599    /// Enable step-by-step execution mode
8600    pub fn enable_step_mode(&mut self) {
8601        self.step_mode = true;
8602    }
8603
8604    /// Disable step-by-step execution mode
8605    pub fn disable_step_mode(&mut self) {
8606        self.step_mode = false;
8607    }
8608
8609    /// Get snapshot count
8610    pub fn snapshot_count(&self) -> usize {
8611        self.snapshots.len()
8612    }
8613
8614    /// Clear all snapshots
8615    pub fn clear(&mut self) {
8616        self.snapshots.clear();
8617        self.current_index = 0;
8618    }
8619}
8620
8621impl std::fmt::Display for TimeTravelDebugger {
8622    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8623        write!(
8624            f,
8625            "TimeTravelDebugger[workflow={}, snapshots={}, current={}]",
8626            self.workflow_id,
8627            self.snapshots.len(),
8628            self.current_index
8629        )
8630    }
8631}
8632
8633// ============================================================================
8634// Workflow Visualization Support
8635// ============================================================================
8636
8637/// Visual theme for workflow visualization
8638#[derive(Debug, Clone, Serialize, Deserialize)]
8639pub struct VisualTheme {
8640    /// Theme name
8641    pub name: String,
8642    /// Colors for task states
8643    pub colors: HashMap<String, String>,
8644    /// Node shapes by task type
8645    pub shapes: HashMap<String, String>,
8646    /// Edge styles
8647    pub edge_styles: HashMap<String, String>,
8648    /// Font settings
8649    pub font_family: String,
8650    pub font_size: u8,
8651}
8652
8653impl VisualTheme {
8654    /// Create default light theme
8655    pub fn light() -> Self {
8656        let mut colors = HashMap::new();
8657        colors.insert("pending".to_string(), "#E0E0E0".to_string());
8658        colors.insert("running".to_string(), "#2196F3".to_string());
8659        colors.insert("completed".to_string(), "#4CAF50".to_string());
8660        colors.insert("failed".to_string(), "#F44336".to_string());
8661        colors.insert("cancelled".to_string(), "#FF9800".to_string());
8662
8663        let mut shapes = HashMap::new();
8664        shapes.insert("task".to_string(), "box".to_string());
8665        shapes.insert("group".to_string(), "ellipse".to_string());
8666        shapes.insert("chord".to_string(), "diamond".to_string());
8667
8668        let mut edge_styles = HashMap::new();
8669        edge_styles.insert("chain".to_string(), "solid".to_string());
8670        edge_styles.insert("callback".to_string(), "dashed".to_string());
8671        edge_styles.insert("error".to_string(), "dotted".to_string());
8672
8673        Self {
8674            name: "light".to_string(),
8675            colors,
8676            shapes,
8677            edge_styles,
8678            font_family: "Arial".to_string(),
8679            font_size: 12,
8680        }
8681    }
8682
8683    /// Create dark theme
8684    pub fn dark() -> Self {
8685        let mut colors = HashMap::new();
8686        colors.insert("pending".to_string(), "#424242".to_string());
8687        colors.insert("running".to_string(), "#1976D2".to_string());
8688        colors.insert("completed".to_string(), "#388E3C".to_string());
8689        colors.insert("failed".to_string(), "#D32F2F".to_string());
8690        colors.insert("cancelled".to_string(), "#F57C00".to_string());
8691
8692        let mut shapes = HashMap::new();
8693        shapes.insert("task".to_string(), "box".to_string());
8694        shapes.insert("group".to_string(), "ellipse".to_string());
8695        shapes.insert("chord".to_string(), "diamond".to_string());
8696
8697        let mut edge_styles = HashMap::new();
8698        edge_styles.insert("chain".to_string(), "solid".to_string());
8699        edge_styles.insert("callback".to_string(), "dashed".to_string());
8700        edge_styles.insert("error".to_string(), "dotted".to_string());
8701
8702        Self {
8703            name: "dark".to_string(),
8704            colors,
8705            shapes,
8706            edge_styles,
8707            font_family: "Arial".to_string(),
8708            font_size: 12,
8709        }
8710    }
8711
8712    /// Get color for state
8713    pub fn color_for_state(&self, state: &str) -> Option<&str> {
8714        self.colors.get(state).map(|s| s.as_str())
8715    }
8716
8717    /// Get shape for task type
8718    pub fn shape_for_type(&self, task_type: &str) -> Option<&str> {
8719        self.shapes.get(task_type).map(|s| s.as_str())
8720    }
8721}
8722
8723impl Default for VisualTheme {
8724    fn default() -> Self {
8725        Self::light()
8726    }
8727}
8728
8729/// Task visual metadata for UI rendering
8730#[derive(Debug, Clone, Serialize, Deserialize)]
8731pub struct TaskVisualMetadata {
8732    /// Task ID
8733    pub task_id: Uuid,
8734    /// Task name
8735    pub task_name: String,
8736    /// Current state (pending, running, completed, failed, cancelled)
8737    pub state: String,
8738    /// Progress percentage (0-100)
8739    pub progress: f64,
8740    /// Visual position (x, y) for layout
8741    pub position: Option<(f64, f64)>,
8742    /// Node color (CSS color)
8743    pub color: String,
8744    /// Node shape
8745    pub shape: String,
8746    /// CSS classes for styling
8747    pub css_classes: Vec<String>,
8748    /// Additional metadata
8749    pub metadata: HashMap<String, serde_json::Value>,
8750}
8751
8752impl TaskVisualMetadata {
8753    /// Create new task visual metadata
8754    pub fn new(task_id: Uuid, task_name: String, state: String) -> Self {
8755        Self {
8756            task_id,
8757            task_name,
8758            state: state.clone(),
8759            progress: 0.0,
8760            position: None,
8761            color: Self::default_color_for_state(&state),
8762            shape: "box".to_string(),
8763            css_classes: vec![format!("task-{}", state)],
8764            metadata: HashMap::new(),
8765        }
8766    }
8767
8768    /// Default color for state
8769    fn default_color_for_state(state: &str) -> String {
8770        match state {
8771            "pending" => "#E0E0E0",
8772            "running" => "#2196F3",
8773            "completed" => "#4CAF50",
8774            "failed" => "#F44336",
8775            "cancelled" => "#FF9800",
8776            _ => "#9E9E9E",
8777        }
8778        .to_string()
8779    }
8780
8781    /// Set progress
8782    pub fn with_progress(mut self, progress: f64) -> Self {
8783        self.progress = progress.clamp(0.0, 100.0);
8784        self
8785    }
8786
8787    /// Set position
8788    pub fn with_position(mut self, x: f64, y: f64) -> Self {
8789        self.position = Some((x, y));
8790        self
8791    }
8792
8793    /// Set color
8794    pub fn with_color(mut self, color: String) -> Self {
8795        self.color = color;
8796        self
8797    }
8798
8799    /// Add CSS class
8800    pub fn add_css_class(&mut self, class: String) {
8801        if !self.css_classes.contains(&class) {
8802            self.css_classes.push(class);
8803        }
8804    }
8805
8806    /// Add metadata
8807    pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
8808        self.metadata.insert(key, value);
8809    }
8810}
8811
8812/// Workflow visualization data with rich metadata
8813#[derive(Debug, Clone, Serialize, Deserialize)]
8814pub struct WorkflowVisualizationData {
8815    /// Workflow ID
8816    pub workflow_id: Uuid,
8817    /// Workflow name
8818    pub workflow_name: String,
8819    /// Workflow state
8820    pub state: WorkflowState,
8821    /// Task visual metadata
8822    pub tasks: Vec<TaskVisualMetadata>,
8823    /// Edge connections (from_task_id, to_task_id, edge_type)
8824    pub edges: Vec<(Uuid, Uuid, String)>,
8825    /// Visual theme
8826    pub theme: VisualTheme,
8827    /// Layout algorithm hint (hierarchical, force, circular, etc.)
8828    pub layout_hint: String,
8829    /// Viewport dimensions (width, height)
8830    pub viewport: (f64, f64),
8831}
8832
8833impl WorkflowVisualizationData {
8834    /// Create new visualization data
8835    pub fn new(workflow_id: Uuid, workflow_name: String, state: WorkflowState) -> Self {
8836        Self {
8837            workflow_id,
8838            workflow_name,
8839            state,
8840            tasks: Vec::new(),
8841            edges: Vec::new(),
8842            theme: VisualTheme::default(),
8843            layout_hint: "hierarchical".to_string(),
8844            viewport: (1000.0, 600.0),
8845        }
8846    }
8847
8848    /// Add task metadata
8849    pub fn add_task(&mut self, task: TaskVisualMetadata) {
8850        self.tasks.push(task);
8851    }
8852
8853    /// Add edge
8854    pub fn add_edge(&mut self, from: Uuid, to: Uuid, edge_type: String) {
8855        self.edges.push((from, to, edge_type));
8856    }
8857
8858    /// Set theme
8859    pub fn with_theme(mut self, theme: VisualTheme) -> Self {
8860        self.theme = theme;
8861        self
8862    }
8863
8864    /// Set layout hint
8865    pub fn with_layout(mut self, layout_hint: String) -> Self {
8866        self.layout_hint = layout_hint;
8867        self
8868    }
8869
8870    /// Export to JSON for frontend
8871    pub fn to_json(&self) -> Result<String, serde_json::Error> {
8872        serde_json::to_string_pretty(self)
8873    }
8874
8875    /// Export to vis.js format
8876    pub fn to_visjs_format(&self) -> serde_json::Value {
8877        let nodes: Vec<serde_json::Value> = self
8878            .tasks
8879            .iter()
8880            .map(|task| {
8881                serde_json::json!({
8882                    "id": task.task_id.to_string(),
8883                    "label": task.task_name,
8884                    "color": task.color,
8885                    "shape": task.shape,
8886                    "title": format!("{} ({})", task.task_name, task.state),
8887                    "value": task.progress,
8888                })
8889            })
8890            .collect();
8891
8892        let edges: Vec<serde_json::Value> = self
8893            .edges
8894            .iter()
8895            .map(|(from, to, edge_type)| {
8896                serde_json::json!({
8897                    "from": from.to_string(),
8898                    "to": to.to_string(),
8899                    "arrows": "to",
8900                    "dashes": edge_type == "callback",
8901                })
8902            })
8903            .collect();
8904
8905        serde_json::json!({
8906            "nodes": nodes,
8907            "edges": edges,
8908        })
8909    }
8910}
8911
8912/// Execution timeline entry for Gantt chart visualization
8913#[derive(Debug, Clone, Serialize, Deserialize)]
8914pub struct TimelineEntry {
8915    /// Task ID
8916    pub task_id: Uuid,
8917    /// Task name
8918    pub task_name: String,
8919    /// Start time (Unix timestamp in milliseconds)
8920    pub start_time: u64,
8921    /// End time (Unix timestamp in milliseconds)
8922    pub end_time: Option<u64>,
8923    /// Duration in milliseconds
8924    pub duration: Option<u64>,
8925    /// Task state
8926    pub state: String,
8927    /// Worker ID that executed the task
8928    pub worker_id: Option<String>,
8929    /// Parent task ID (for nested workflows)
8930    pub parent_id: Option<Uuid>,
8931    /// Color for visualization
8932    pub color: String,
8933}
8934
8935impl TimelineEntry {
8936    /// Create new timeline entry
8937    pub fn new(task_id: Uuid, task_name: String, start_time: u64) -> Self {
8938        Self {
8939            task_id,
8940            task_name,
8941            start_time,
8942            end_time: None,
8943            duration: None,
8944            state: "running".to_string(),
8945            worker_id: None,
8946            parent_id: None,
8947            color: "#2196F3".to_string(),
8948        }
8949    }
8950
8951    /// Mark task as completed
8952    pub fn complete(&mut self, end_time: u64) {
8953        self.end_time = Some(end_time);
8954        self.duration = Some(end_time.saturating_sub(self.start_time));
8955        self.state = "completed".to_string();
8956        self.color = "#4CAF50".to_string();
8957    }
8958
8959    /// Mark task as failed
8960    pub fn fail(&mut self, end_time: u64) {
8961        self.end_time = Some(end_time);
8962        self.duration = Some(end_time.saturating_sub(self.start_time));
8963        self.state = "failed".to_string();
8964        self.color = "#F44336".to_string();
8965    }
8966
8967    /// Set worker ID
8968    pub fn with_worker(mut self, worker_id: String) -> Self {
8969        self.worker_id = Some(worker_id);
8970        self
8971    }
8972
8973    /// Set parent ID
8974    pub fn with_parent(mut self, parent_id: Uuid) -> Self {
8975        self.parent_id = Some(parent_id);
8976        self
8977    }
8978}
8979
8980/// Execution timeline for workflow visualization
8981#[derive(Debug, Clone, Serialize, Deserialize)]
8982pub struct ExecutionTimeline {
8983    /// Workflow ID
8984    pub workflow_id: Uuid,
8985    /// Timeline entries
8986    pub entries: Vec<TimelineEntry>,
8987    /// Workflow start time
8988    pub workflow_start: u64,
8989    /// Workflow end time
8990    pub workflow_end: Option<u64>,
8991}
8992
8993impl ExecutionTimeline {
8994    /// Create new execution timeline
8995    pub fn new(workflow_id: Uuid) -> Self {
8996        Self {
8997            workflow_id,
8998            entries: Vec::new(),
8999            workflow_start: std::time::SystemTime::now()
9000                .duration_since(std::time::UNIX_EPOCH)
9001                .unwrap()
9002                .as_millis() as u64,
9003            workflow_end: None,
9004        }
9005    }
9006
9007    /// Add timeline entry
9008    pub fn add_entry(&mut self, entry: TimelineEntry) {
9009        self.entries.push(entry);
9010    }
9011
9012    /// Start task
9013    pub fn start_task(&mut self, task_id: Uuid, task_name: String) -> usize {
9014        let now = std::time::SystemTime::now()
9015            .duration_since(std::time::UNIX_EPOCH)
9016            .unwrap()
9017            .as_millis() as u64;
9018        let entry = TimelineEntry::new(task_id, task_name, now);
9019        self.entries.push(entry);
9020        self.entries.len() - 1
9021    }
9022
9023    /// Complete task
9024    pub fn complete_task(&mut self, index: usize) {
9025        if let Some(entry) = self.entries.get_mut(index) {
9026            let now = std::time::SystemTime::now()
9027                .duration_since(std::time::UNIX_EPOCH)
9028                .unwrap()
9029                .as_millis() as u64;
9030            entry.complete(now);
9031        }
9032    }
9033
9034    /// Fail task
9035    pub fn fail_task(&mut self, index: usize) {
9036        if let Some(entry) = self.entries.get_mut(index) {
9037            let now = std::time::SystemTime::now()
9038                .duration_since(std::time::UNIX_EPOCH)
9039                .unwrap()
9040                .as_millis() as u64;
9041            entry.fail(now);
9042        }
9043    }
9044
9045    /// Mark workflow as complete
9046    pub fn complete_workflow(&mut self) {
9047        self.workflow_end = Some(
9048            std::time::SystemTime::now()
9049                .duration_since(std::time::UNIX_EPOCH)
9050                .unwrap()
9051                .as_millis() as u64,
9052        );
9053    }
9054
9055    /// Export to JSON
9056    pub fn to_json(&self) -> Result<String, serde_json::Error> {
9057        serde_json::to_string_pretty(self)
9058    }
9059
9060    /// Export to Google Charts Timeline format
9061    pub fn to_google_charts_format(&self) -> serde_json::Value {
9062        let rows: Vec<serde_json::Value> = self
9063            .entries
9064            .iter()
9065            .map(|entry| {
9066                serde_json::json!([
9067                    entry.task_name,
9068                    entry.task_name,
9069                    entry.start_time,
9070                    entry.end_time.unwrap_or(entry.start_time),
9071                ])
9072            })
9073            .collect();
9074
9075        serde_json::json!({
9076            "cols": [
9077                {"id": "", "label": "Task ID", "type": "string"},
9078                {"id": "", "label": "Task Name", "type": "string"},
9079                {"id": "", "label": "Start", "type": "number"},
9080                {"id": "", "label": "End", "type": "number"}
9081            ],
9082            "rows": rows.iter().map(|row| serde_json::json!({"c": row})).collect::<Vec<_>>()
9083        })
9084    }
9085}
9086
9087/// Animation frame for execution visualization
9088#[derive(Debug, Clone, Serialize, Deserialize)]
9089pub struct AnimationFrame {
9090    /// Frame number
9091    pub frame_number: usize,
9092    /// Timestamp
9093    pub timestamp: u64,
9094    /// Workflow state at this frame
9095    pub workflow_state: WorkflowState,
9096    /// Task states (task_id -> state)
9097    pub task_states: HashMap<Uuid, String>,
9098    /// Active tasks at this frame
9099    pub active_tasks: Vec<Uuid>,
9100    /// Completed tasks at this frame
9101    pub completed_tasks: Vec<Uuid>,
9102    /// Events that occurred in this frame
9103    pub events: Vec<WorkflowEvent>,
9104}
9105
9106impl AnimationFrame {
9107    /// Create new animation frame
9108    pub fn new(frame_number: usize, workflow_state: WorkflowState) -> Self {
9109        Self {
9110            frame_number,
9111            timestamp: std::time::SystemTime::now()
9112                .duration_since(std::time::UNIX_EPOCH)
9113                .unwrap()
9114                .as_millis() as u64,
9115            workflow_state,
9116            task_states: HashMap::new(),
9117            active_tasks: Vec::new(),
9118            completed_tasks: Vec::new(),
9119            events: Vec::new(),
9120        }
9121    }
9122
9123    /// Set task state
9124    pub fn set_task_state(&mut self, task_id: Uuid, state: String) {
9125        self.task_states.insert(task_id, state);
9126    }
9127
9128    /// Add active task
9129    pub fn add_active_task(&mut self, task_id: Uuid) {
9130        if !self.active_tasks.contains(&task_id) {
9131            self.active_tasks.push(task_id);
9132        }
9133    }
9134
9135    /// Add completed task
9136    pub fn add_completed_task(&mut self, task_id: Uuid) {
9137        if !self.completed_tasks.contains(&task_id) {
9138            self.completed_tasks.push(task_id);
9139        }
9140        // Remove from active tasks
9141        self.active_tasks.retain(|id| id != &task_id);
9142    }
9143
9144    /// Add event
9145    pub fn add_event(&mut self, event: WorkflowEvent) {
9146        self.events.push(event);
9147    }
9148}
9149
9150/// Workflow animation sequence
9151#[derive(Debug, Clone, Serialize, Deserialize)]
9152pub struct WorkflowAnimation {
9153    /// Workflow ID
9154    pub workflow_id: Uuid,
9155    /// Animation frames
9156    pub frames: Vec<AnimationFrame>,
9157    /// Frame duration in milliseconds
9158    pub frame_duration: u64,
9159    /// Total duration
9160    pub total_duration: u64,
9161}
9162
9163impl WorkflowAnimation {
9164    /// Create new workflow animation
9165    pub fn new(workflow_id: Uuid, frame_duration: u64) -> Self {
9166        Self {
9167            workflow_id,
9168            frames: Vec::new(),
9169            frame_duration,
9170            total_duration: 0,
9171        }
9172    }
9173
9174    /// Add frame
9175    pub fn add_frame(&mut self, frame: AnimationFrame) {
9176        self.frames.push(frame);
9177        self.total_duration = self.frames.len() as u64 * self.frame_duration;
9178    }
9179
9180    /// Get frame at index
9181    pub fn get_frame(&self, index: usize) -> Option<&AnimationFrame> {
9182        self.frames.get(index)
9183    }
9184
9185    /// Get frame count
9186    pub fn frame_count(&self) -> usize {
9187        self.frames.len()
9188    }
9189
9190    /// Export to JSON
9191    pub fn to_json(&self) -> Result<String, serde_json::Error> {
9192        serde_json::to_string_pretty(self)
9193    }
9194}
9195
9196/// DAG export with execution state overlay
9197pub trait DagExportWithState {
9198    /// Export DAG with current execution state
9199    fn to_dot_with_state(
9200        &self,
9201        state: &WorkflowState,
9202        task_states: &HashMap<Uuid, String>,
9203    ) -> String;
9204
9205    /// Export Mermaid diagram with execution state
9206    fn to_mermaid_with_state(
9207        &self,
9208        state: &WorkflowState,
9209        task_states: &HashMap<Uuid, String>,
9210    ) -> String;
9211
9212    /// Export to JSON with execution state
9213    fn to_json_with_state(
9214        &self,
9215        state: &WorkflowState,
9216        task_states: &HashMap<Uuid, String>,
9217    ) -> Result<String, serde_json::Error>;
9218}
9219
9220impl DagExportWithState for Chain {
9221    fn to_dot_with_state(
9222        &self,
9223        _state: &WorkflowState,
9224        task_states: &HashMap<Uuid, String>,
9225    ) -> String {
9226        let mut dot = String::from("digraph Chain {\n");
9227        dot.push_str("  rankdir=LR;\n");
9228
9229        for (i, sig) in self.tasks.iter().enumerate() {
9230            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9231            let state = task_states
9232                .get(&task_id)
9233                .map(|s| s.as_str())
9234                .unwrap_or("pending");
9235            let color = match state {
9236                "completed" => "#4CAF50",
9237                "running" => "#2196F3",
9238                "failed" => "#F44336",
9239                _ => "#E0E0E0",
9240            };
9241
9242            dot.push_str(&format!(
9243                "  task{} [label=\"{}\" style=filled fillcolor=\"{}\"];\n",
9244                i, sig.task, color
9245            ));
9246
9247            if i > 0 {
9248                dot.push_str(&format!("  task{} -> task{};\n", i - 1, i));
9249            }
9250        }
9251
9252        dot.push('}');
9253        dot
9254    }
9255
9256    fn to_mermaid_with_state(
9257        &self,
9258        _state: &WorkflowState,
9259        task_states: &HashMap<Uuid, String>,
9260    ) -> String {
9261        let mut mmd = String::from("graph LR\n");
9262
9263        for (i, sig) in self.tasks.iter().enumerate() {
9264            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9265            let state = task_states
9266                .get(&task_id)
9267                .map(|s| s.as_str())
9268                .unwrap_or("pending");
9269            let style_class = match state {
9270                "completed" => "completed",
9271                "running" => "running",
9272                "failed" => "failed",
9273                _ => "pending",
9274            };
9275
9276            mmd.push_str(&format!(
9277                "  task{}[\"{}\"]:::{}\n",
9278                i, sig.task, style_class
9279            ));
9280
9281            if i > 0 {
9282                mmd.push_str(&format!("  task{} --> task{}\n", i - 1, i));
9283            }
9284        }
9285
9286        // Add style definitions
9287        mmd.push_str("\n  classDef completed fill:#4CAF50,stroke:#333,stroke-width:2px\n");
9288        mmd.push_str("  classDef running fill:#2196F3,stroke:#333,stroke-width:2px\n");
9289        mmd.push_str("  classDef failed fill:#F44336,stroke:#333,stroke-width:2px\n");
9290        mmd.push_str("  classDef pending fill:#E0E0E0,stroke:#333,stroke-width:2px\n");
9291
9292        mmd
9293    }
9294
9295    fn to_json_with_state(
9296        &self,
9297        state: &WorkflowState,
9298        task_states: &HashMap<Uuid, String>,
9299    ) -> Result<String, serde_json::Error> {
9300        let mut nodes = Vec::new();
9301        let mut edges = Vec::new();
9302
9303        for (i, sig) in self.tasks.iter().enumerate() {
9304            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9305            let task_state = task_states
9306                .get(&task_id)
9307                .map(|s| s.as_str())
9308                .unwrap_or("pending");
9309
9310            nodes.push(serde_json::json!({
9311                "id": format!("task{}", i),
9312                "label": sig.task,
9313                "state": task_state,
9314                "task_id": task_id,
9315            }));
9316
9317            if i > 0 {
9318                edges.push(serde_json::json!({
9319                    "from": format!("task{}", i - 1),
9320                    "to": format!("task{}", i),
9321                }));
9322            }
9323        }
9324
9325        let result = serde_json::json!({
9326            "type": "chain",
9327            "workflow_state": state,
9328            "nodes": nodes,
9329            "edges": edges,
9330        });
9331
9332        serde_json::to_string_pretty(&result)
9333    }
9334}
9335
9336impl DagExportWithState for Group {
9337    fn to_dot_with_state(
9338        &self,
9339        _state: &WorkflowState,
9340        task_states: &HashMap<Uuid, String>,
9341    ) -> String {
9342        let mut dot = String::from("digraph Group {\n");
9343
9344        for (i, sig) in self.tasks.iter().enumerate() {
9345            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9346            let state = task_states
9347                .get(&task_id)
9348                .map(|s| s.as_str())
9349                .unwrap_or("pending");
9350            let color = match state {
9351                "completed" => "#4CAF50",
9352                "running" => "#2196F3",
9353                "failed" => "#F44336",
9354                _ => "#E0E0E0",
9355            };
9356
9357            dot.push_str(&format!(
9358                "  task{} [label=\"{}\" style=filled fillcolor=\"{}\"];\n",
9359                i, sig.task, color
9360            ));
9361        }
9362
9363        dot.push('}');
9364        dot
9365    }
9366
9367    fn to_mermaid_with_state(
9368        &self,
9369        _state: &WorkflowState,
9370        task_states: &HashMap<Uuid, String>,
9371    ) -> String {
9372        let mut mmd = String::from("graph TB\n");
9373
9374        for (i, sig) in self.tasks.iter().enumerate() {
9375            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9376            let state = task_states
9377                .get(&task_id)
9378                .map(|s| s.as_str())
9379                .unwrap_or("pending");
9380            let style_class = match state {
9381                "completed" => "completed",
9382                "running" => "running",
9383                "failed" => "failed",
9384                _ => "pending",
9385            };
9386
9387            mmd.push_str(&format!(
9388                "  task{}[\"{}\"]:::{}\n",
9389                i, sig.task, style_class
9390            ));
9391        }
9392
9393        // Add style definitions
9394        mmd.push_str("\n  classDef completed fill:#4CAF50,stroke:#333,stroke-width:2px\n");
9395        mmd.push_str("  classDef running fill:#2196F3,stroke:#333,stroke-width:2px\n");
9396        mmd.push_str("  classDef failed fill:#F44336,stroke:#333,stroke-width:2px\n");
9397        mmd.push_str("  classDef pending fill:#E0E0E0,stroke:#333,stroke-width:2px\n");
9398
9399        mmd
9400    }
9401
9402    fn to_json_with_state(
9403        &self,
9404        state: &WorkflowState,
9405        task_states: &HashMap<Uuid, String>,
9406    ) -> Result<String, serde_json::Error> {
9407        let mut nodes = Vec::new();
9408
9409        for (i, sig) in self.tasks.iter().enumerate() {
9410            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9411            let task_state = task_states
9412                .get(&task_id)
9413                .map(|s| s.as_str())
9414                .unwrap_or("pending");
9415
9416            nodes.push(serde_json::json!({
9417                "id": format!("task{}", i),
9418                "label": sig.task,
9419                "state": task_state,
9420                "task_id": task_id,
9421            }));
9422        }
9423
9424        let result = serde_json::json!({
9425            "type": "group",
9426            "workflow_state": state,
9427            "nodes": nodes,
9428            "edges": [],
9429        });
9430
9431        serde_json::to_string_pretty(&result)
9432    }
9433}
9434
9435impl DagExportWithState for Chord {
9436    fn to_dot_with_state(
9437        &self,
9438        _state: &WorkflowState,
9439        task_states: &HashMap<Uuid, String>,
9440    ) -> String {
9441        let mut dot = String::from("digraph Chord {\n");
9442        dot.push_str("  rankdir=LR;\n");
9443
9444        // Header tasks
9445        for (i, sig) in self.header.tasks.iter().enumerate() {
9446            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9447            let state = task_states
9448                .get(&task_id)
9449                .map(|s| s.as_str())
9450                .unwrap_or("pending");
9451            let color = match state {
9452                "completed" => "#4CAF50",
9453                "running" => "#2196F3",
9454                "failed" => "#F44336",
9455                _ => "#E0E0E0",
9456            };
9457
9458            dot.push_str(&format!(
9459                "  task{} [label=\"{}\" style=filled fillcolor=\"{}\"];\n",
9460                i, sig.task, color
9461            ));
9462        }
9463
9464        // Body (callback)
9465        let task_id = self.body.options.task_id.unwrap_or_else(Uuid::new_v4);
9466        let state = task_states
9467            .get(&task_id)
9468            .map(|s| s.as_str())
9469            .unwrap_or("pending");
9470        let color = match state {
9471            "completed" => "#4CAF50",
9472            "running" => "#2196F3",
9473            "failed" => "#F44336",
9474            _ => "#E0E0E0",
9475        };
9476
9477        dot.push_str(&format!(
9478            "  callback [label=\"{}\" shape=diamond style=filled fillcolor=\"{}\"];\n",
9479            self.body.task, color
9480        ));
9481
9482        for i in 0..self.header.tasks.len() {
9483            dot.push_str(&format!("  task{} -> callback;\n", i));
9484        }
9485
9486        dot.push('}');
9487        dot
9488    }
9489
9490    fn to_mermaid_with_state(
9491        &self,
9492        _state: &WorkflowState,
9493        task_states: &HashMap<Uuid, String>,
9494    ) -> String {
9495        let mut mmd = String::from("graph TB\n");
9496
9497        // Header tasks
9498        for (i, sig) in self.header.tasks.iter().enumerate() {
9499            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9500            let state = task_states
9501                .get(&task_id)
9502                .map(|s| s.as_str())
9503                .unwrap_or("pending");
9504            let style_class = match state {
9505                "completed" => "completed",
9506                "running" => "running",
9507                "failed" => "failed",
9508                _ => "pending",
9509            };
9510
9511            mmd.push_str(&format!(
9512                "  task{}[\"{}\"]:::{}\n",
9513                i, sig.task, style_class
9514            ));
9515        }
9516
9517        // Body (callback)
9518        let task_id = self.body.options.task_id.unwrap_or_else(Uuid::new_v4);
9519        let state = task_states
9520            .get(&task_id)
9521            .map(|s| s.as_str())
9522            .unwrap_or("pending");
9523        let style_class = match state {
9524            "completed" => "completed",
9525            "running" => "running",
9526            "failed" => "failed",
9527            _ => "pending",
9528        };
9529
9530        mmd.push_str(&format!(
9531            "  callback{{\"{}\"}}:::{}\n",
9532            self.body.task, style_class
9533        ));
9534
9535        for i in 0..self.header.tasks.len() {
9536            mmd.push_str(&format!("  task{} --> callback\n", i));
9537        }
9538
9539        // Add style definitions
9540        mmd.push_str("\n  classDef completed fill:#4CAF50,stroke:#333,stroke-width:2px\n");
9541        mmd.push_str("  classDef running fill:#2196F3,stroke:#333,stroke-width:2px\n");
9542        mmd.push_str("  classDef failed fill:#F44336,stroke:#333,stroke-width:2px\n");
9543        mmd.push_str("  classDef pending fill:#E0E0E0,stroke:#333,stroke-width:2px\n");
9544
9545        mmd
9546    }
9547
9548    fn to_json_with_state(
9549        &self,
9550        state: &WorkflowState,
9551        task_states: &HashMap<Uuid, String>,
9552    ) -> Result<String, serde_json::Error> {
9553        let mut nodes = Vec::new();
9554        let mut edges = Vec::new();
9555
9556        // Header tasks
9557        for (i, sig) in self.header.tasks.iter().enumerate() {
9558            let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9559            let task_state = task_states
9560                .get(&task_id)
9561                .map(|s| s.as_str())
9562                .unwrap_or("pending");
9563
9564            nodes.push(serde_json::json!({
9565                "id": format!("task{}", i),
9566                "label": sig.task,
9567                "state": task_state,
9568                "task_id": task_id,
9569            }));
9570        }
9571
9572        // Body (callback)
9573        let task_id = self.body.options.task_id.unwrap_or_else(Uuid::new_v4);
9574        let task_state = task_states
9575            .get(&task_id)
9576            .map(|s| s.as_str())
9577            .unwrap_or("pending");
9578
9579        nodes.push(serde_json::json!({
9580            "id": "callback",
9581            "label": self.body.task,
9582            "state": task_state,
9583            "task_id": task_id,
9584            "shape": "diamond",
9585        }));
9586
9587        for i in 0..self.header.tasks.len() {
9588            edges.push(serde_json::json!({
9589                "from": format!("task{}", i),
9590                "to": "callback",
9591            }));
9592        }
9593
9594        let result = serde_json::json!({
9595            "type": "chord",
9596            "workflow_state": state,
9597            "nodes": nodes,
9598            "edges": edges,
9599        });
9600
9601        serde_json::to_string_pretty(&result)
9602    }
9603}
9604
9605/// Real-time workflow event stream
9606#[derive(Debug, Clone)]
9607pub struct WorkflowEventStream {
9608    /// Workflow ID
9609    pub workflow_id: Uuid,
9610    /// Event buffer
9611    pub events: Vec<(u64, WorkflowEvent)>,
9612    /// Maximum buffer size
9613    pub max_buffer_size: usize,
9614}
9615
9616impl WorkflowEventStream {
9617    /// Create new event stream
9618    pub fn new(workflow_id: Uuid) -> Self {
9619        Self {
9620            workflow_id,
9621            events: Vec::new(),
9622            max_buffer_size: 1000,
9623        }
9624    }
9625
9626    /// Set maximum buffer size
9627    pub fn with_max_buffer_size(mut self, size: usize) -> Self {
9628        self.max_buffer_size = size;
9629        self
9630    }
9631
9632    /// Push event
9633    pub fn push(&mut self, event: WorkflowEvent) {
9634        let timestamp = std::time::SystemTime::now()
9635            .duration_since(std::time::UNIX_EPOCH)
9636            .unwrap()
9637            .as_millis() as u64;
9638
9639        self.events.push((timestamp, event));
9640
9641        // Trim buffer if needed
9642        if self.events.len() > self.max_buffer_size {
9643            self.events.remove(0);
9644        }
9645    }
9646
9647    /// Get events since timestamp
9648    pub fn events_since(&self, timestamp: u64) -> Vec<&(u64, WorkflowEvent)> {
9649        self.events
9650            .iter()
9651            .filter(|(ts, _)| *ts > timestamp)
9652            .collect()
9653    }
9654
9655    /// Get all events
9656    pub fn all_events(&self) -> &[(u64, WorkflowEvent)] {
9657        &self.events
9658    }
9659
9660    /// Clear events
9661    pub fn clear(&mut self) {
9662        self.events.clear();
9663    }
9664
9665    /// Export to JSON for Server-Sent Events (SSE)
9666    pub fn to_sse_format(&self) -> Vec<String> {
9667        self.events
9668            .iter()
9669            .map(|(ts, event)| {
9670                format!(
9671                    "event: workflow\ndata: {{\"timestamp\": {}, \"event\": \"{}\"}}\n\n",
9672                    ts, event
9673                )
9674            })
9675            .collect()
9676    }
9677}
9678
9679// ============================================================================
9680// Production-Ready Enhancements
9681// ============================================================================
9682
9683/// Workflow metrics collector for automatic metrics gathering
9684#[derive(Debug, Clone, Serialize, Deserialize)]
9685pub struct WorkflowMetricsCollector {
9686    /// Workflow ID
9687    pub workflow_id: Uuid,
9688    /// Start time
9689    pub start_time: u64,
9690    /// End time
9691    pub end_time: Option<u64>,
9692    /// Total tasks
9693    pub total_tasks: usize,
9694    /// Completed tasks
9695    pub completed_tasks: usize,
9696    /// Failed tasks
9697    pub failed_tasks: usize,
9698    /// Task execution times (task_id -> duration_ms)
9699    pub task_durations: HashMap<Uuid, u64>,
9700    /// Task retry counts (task_id -> retry_count)
9701    pub task_retries: HashMap<Uuid, usize>,
9702    /// Total workflow duration in milliseconds
9703    pub total_duration: Option<u64>,
9704    /// Average task duration
9705    pub avg_task_duration: Option<f64>,
9706    /// Success rate (0.0 to 1.0)
9707    pub success_rate: Option<f64>,
9708}
9709
9710impl WorkflowMetricsCollector {
9711    /// Create new metrics collector
9712    pub fn new(workflow_id: Uuid) -> Self {
9713        Self {
9714            workflow_id,
9715            start_time: std::time::SystemTime::now()
9716                .duration_since(std::time::UNIX_EPOCH)
9717                .unwrap()
9718                .as_millis() as u64,
9719            end_time: None,
9720            total_tasks: 0,
9721            completed_tasks: 0,
9722            failed_tasks: 0,
9723            task_durations: HashMap::new(),
9724            task_retries: HashMap::new(),
9725            total_duration: None,
9726            avg_task_duration: None,
9727            success_rate: None,
9728        }
9729    }
9730
9731    /// Record task start
9732    pub fn record_task_start(&mut self, task_id: Uuid) {
9733        self.total_tasks += 1;
9734        self.task_durations.insert(task_id, 0);
9735    }
9736
9737    /// Record task completion
9738    pub fn record_task_complete(&mut self, task_id: Uuid, duration_ms: u64) {
9739        self.completed_tasks += 1;
9740        self.task_durations.insert(task_id, duration_ms);
9741    }
9742
9743    /// Record task failure
9744    pub fn record_task_failure(&mut self, task_id: Uuid, duration_ms: u64) {
9745        self.failed_tasks += 1;
9746        self.task_durations.insert(task_id, duration_ms);
9747    }
9748
9749    /// Record task retry
9750    pub fn record_task_retry(&mut self, task_id: Uuid) {
9751        *self.task_retries.entry(task_id).or_insert(0) += 1;
9752    }
9753
9754    /// Finalize metrics
9755    pub fn finalize(&mut self) {
9756        let now = std::time::SystemTime::now()
9757            .duration_since(std::time::UNIX_EPOCH)
9758            .unwrap()
9759            .as_millis() as u64;
9760
9761        self.end_time = Some(now);
9762        self.total_duration = Some(now.saturating_sub(self.start_time));
9763
9764        // Calculate average task duration
9765        if !self.task_durations.is_empty() {
9766            let sum: u64 = self.task_durations.values().sum();
9767            self.avg_task_duration = Some(sum as f64 / self.task_durations.len() as f64);
9768        }
9769
9770        // Calculate success rate
9771        if self.total_tasks > 0 {
9772            self.success_rate = Some(self.completed_tasks as f64 / self.total_tasks as f64);
9773        }
9774    }
9775
9776    /// Get metrics summary
9777    pub fn summary(&self) -> String {
9778        format!(
9779            "WorkflowMetrics[id={}, total={}, completed={}, failed={}, success_rate={:.2}%, avg_duration={:.2}ms]",
9780            self.workflow_id,
9781            self.total_tasks,
9782            self.completed_tasks,
9783            self.failed_tasks,
9784            self.success_rate.unwrap_or(0.0) * 100.0,
9785            self.avg_task_duration.unwrap_or(0.0)
9786        )
9787    }
9788}
9789
9790impl std::fmt::Display for WorkflowMetricsCollector {
9791    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9792        write!(f, "{}", self.summary())
9793    }
9794}
9795
9796/// Workflow rate limiter for controlling execution rate
9797#[derive(Debug, Clone)]
9798pub struct WorkflowRateLimiter {
9799    /// Maximum workflows per time window
9800    pub max_workflows: usize,
9801    /// Time window in milliseconds
9802    pub window_ms: u64,
9803    /// Workflow timestamps
9804    pub workflow_timestamps: Vec<u64>,
9805    /// Total workflows processed
9806    pub total_workflows: usize,
9807    /// Total workflows rejected
9808    pub rejected_workflows: usize,
9809}
9810
9811impl WorkflowRateLimiter {
9812    /// Create new rate limiter
9813    pub fn new(max_workflows: usize, window_ms: u64) -> Self {
9814        Self {
9815            max_workflows,
9816            window_ms,
9817            workflow_timestamps: Vec::new(),
9818            total_workflows: 0,
9819            rejected_workflows: 0,
9820        }
9821    }
9822
9823    /// Check if workflow can be executed
9824    pub fn allow_workflow(&mut self) -> bool {
9825        let now = std::time::SystemTime::now()
9826            .duration_since(std::time::UNIX_EPOCH)
9827            .unwrap()
9828            .as_millis() as u64;
9829
9830        // Remove old timestamps outside the window
9831        self.workflow_timestamps
9832            .retain(|&ts| now.saturating_sub(ts) < self.window_ms);
9833
9834        // Check if we can allow this workflow
9835        if self.workflow_timestamps.len() < self.max_workflows {
9836            self.workflow_timestamps.push(now);
9837            self.total_workflows += 1;
9838            true
9839        } else {
9840            self.rejected_workflows += 1;
9841            false
9842        }
9843    }
9844
9845    /// Get current rate (workflows per second)
9846    pub fn current_rate(&self) -> f64 {
9847        if self.workflow_timestamps.is_empty() {
9848            return 0.0;
9849        }
9850
9851        let now = std::time::SystemTime::now()
9852            .duration_since(std::time::UNIX_EPOCH)
9853            .unwrap()
9854            .as_millis() as u64;
9855
9856        let active_timestamps: Vec<_> = self
9857            .workflow_timestamps
9858            .iter()
9859            .filter(|&&ts| now.saturating_sub(ts) < self.window_ms)
9860            .collect();
9861
9862        if active_timestamps.is_empty() {
9863            return 0.0;
9864        }
9865
9866        active_timestamps.len() as f64 / (self.window_ms as f64 / 1000.0)
9867    }
9868
9869    /// Reset rate limiter
9870    pub fn reset(&mut self) {
9871        self.workflow_timestamps.clear();
9872    }
9873
9874    /// Get rejection rate
9875    pub fn rejection_rate(&self) -> f64 {
9876        if self.total_workflows == 0 {
9877            0.0
9878        } else {
9879            self.rejected_workflows as f64 / self.total_workflows as f64
9880        }
9881    }
9882}
9883
9884impl std::fmt::Display for WorkflowRateLimiter {
9885    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9886        write!(
9887            f,
9888            "RateLimiter[max={}/{}ms, current_rate={:.2}/s, rejected={}]",
9889            self.max_workflows,
9890            self.window_ms,
9891            self.current_rate(),
9892            self.rejected_workflows
9893        )
9894    }
9895}
9896
9897/// Workflow concurrency control for limiting concurrent workflows
9898#[derive(Debug, Clone)]
9899pub struct WorkflowConcurrencyControl {
9900    /// Maximum concurrent workflows
9901    pub max_concurrent: usize,
9902    /// Currently active workflows
9903    pub active_workflows: HashMap<Uuid, u64>,
9904    /// Total workflows started
9905    pub total_started: usize,
9906    /// Total workflows completed
9907    pub total_completed: usize,
9908    /// Peak concurrency reached
9909    pub peak_concurrency: usize,
9910}
9911
9912impl WorkflowConcurrencyControl {
9913    /// Create new concurrency control
9914    pub fn new(max_concurrent: usize) -> Self {
9915        Self {
9916            max_concurrent,
9917            active_workflows: HashMap::new(),
9918            total_started: 0,
9919            total_completed: 0,
9920            peak_concurrency: 0,
9921        }
9922    }
9923
9924    /// Try to start a workflow
9925    pub fn try_start(&mut self, workflow_id: Uuid) -> bool {
9926        if self.active_workflows.len() >= self.max_concurrent {
9927            return false;
9928        }
9929
9930        let now = std::time::SystemTime::now()
9931            .duration_since(std::time::UNIX_EPOCH)
9932            .unwrap()
9933            .as_millis() as u64;
9934
9935        self.active_workflows.insert(workflow_id, now);
9936        self.total_started += 1;
9937
9938        // Update peak concurrency
9939        if self.active_workflows.len() > self.peak_concurrency {
9940            self.peak_concurrency = self.active_workflows.len();
9941        }
9942
9943        true
9944    }
9945
9946    /// Complete a workflow
9947    pub fn complete(&mut self, workflow_id: Uuid) -> bool {
9948        if self.active_workflows.remove(&workflow_id).is_some() {
9949            self.total_completed += 1;
9950            true
9951        } else {
9952            false
9953        }
9954    }
9955
9956    /// Get current concurrency
9957    pub fn current_concurrency(&self) -> usize {
9958        self.active_workflows.len()
9959    }
9960
9961    /// Get available slots
9962    pub fn available_slots(&self) -> usize {
9963        self.max_concurrent
9964            .saturating_sub(self.active_workflows.len())
9965    }
9966
9967    /// Check if at capacity
9968    pub fn is_at_capacity(&self) -> bool {
9969        self.active_workflows.len() >= self.max_concurrent
9970    }
9971
9972    /// Get average workflow duration
9973    pub fn avg_workflow_duration(&self) -> Option<f64> {
9974        if self.total_completed == 0 {
9975            return None;
9976        }
9977
9978        let now = std::time::SystemTime::now()
9979            .duration_since(std::time::UNIX_EPOCH)
9980            .unwrap()
9981            .as_millis() as u64;
9982
9983        let total_duration: u64 = self
9984            .active_workflows
9985            .values()
9986            .map(|&start_time| now.saturating_sub(start_time))
9987            .sum();
9988
9989        Some(total_duration as f64 / self.total_completed as f64)
9990    }
9991}
9992
9993impl std::fmt::Display for WorkflowConcurrencyControl {
9994    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9995        write!(
9996            f,
9997            "ConcurrencyControl[max={}, active={}, peak={}, available={}]",
9998            self.max_concurrent,
9999            self.current_concurrency(),
10000            self.peak_concurrency,
10001            self.available_slots()
10002        )
10003    }
10004}
10005
10006/// Workflow composition helpers for easier workflow building
10007#[derive(Debug, Clone)]
10008pub struct WorkflowBuilder {
10009    /// Workflow name
10010    pub name: String,
10011    /// Workflow description
10012    pub description: Option<String>,
10013    /// Workflow tags
10014    pub tags: Vec<String>,
10015    /// Workflow metadata
10016    pub metadata: HashMap<String, serde_json::Value>,
10017}
10018
10019impl WorkflowBuilder {
10020    /// Create new workflow builder
10021    pub fn new(name: impl Into<String>) -> Self {
10022        Self {
10023            name: name.into(),
10024            description: None,
10025            tags: Vec::new(),
10026            metadata: HashMap::new(),
10027        }
10028    }
10029
10030    /// Set description
10031    pub fn with_description(mut self, description: impl Into<String>) -> Self {
10032        self.description = Some(description.into());
10033        self
10034    }
10035
10036    /// Add tag
10037    pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
10038        self.tags.push(tag.into());
10039        self
10040    }
10041
10042    /// Add metadata
10043    pub fn add_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
10044        self.metadata.insert(key.into(), value);
10045        self
10046    }
10047
10048    /// Build a chain workflow
10049    pub fn chain(self) -> Chain {
10050        Chain::new()
10051    }
10052
10053    /// Build a group workflow
10054    pub fn group(self) -> Group {
10055        Group::new()
10056    }
10057
10058    /// Build a map workflow
10059    pub fn map(self, task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Map {
10060        Map::new(task, argsets)
10061    }
10062}
10063
10064/// Workflow registry for tracking and managing workflows
10065#[derive(Debug, Clone)]
10066pub struct WorkflowRegistry {
10067    /// Registered workflows (workflow_id -> workflow_name)
10068    pub workflows: HashMap<Uuid, String>,
10069    /// Workflow metadata
10070    pub metadata: HashMap<Uuid, HashMap<String, serde_json::Value>>,
10071    /// Workflow states
10072    pub states: HashMap<Uuid, WorkflowStatus>,
10073    /// Workflow start times
10074    pub start_times: HashMap<Uuid, u64>,
10075    /// Workflow tags
10076    pub tags: HashMap<String, Vec<Uuid>>,
10077}
10078
10079impl WorkflowRegistry {
10080    /// Create new workflow registry
10081    pub fn new() -> Self {
10082        Self {
10083            workflows: HashMap::new(),
10084            metadata: HashMap::new(),
10085            states: HashMap::new(),
10086            start_times: HashMap::new(),
10087            tags: HashMap::new(),
10088        }
10089    }
10090
10091    /// Register a workflow
10092    pub fn register(
10093        &mut self,
10094        workflow_id: Uuid,
10095        name: String,
10096        metadata: HashMap<String, serde_json::Value>,
10097    ) {
10098        self.workflows.insert(workflow_id, name);
10099        self.metadata.insert(workflow_id, metadata);
10100        self.states.insert(workflow_id, WorkflowStatus::Pending);
10101
10102        let now = std::time::SystemTime::now()
10103            .duration_since(std::time::UNIX_EPOCH)
10104            .unwrap()
10105            .as_millis() as u64;
10106        self.start_times.insert(workflow_id, now);
10107    }
10108
10109    /// Update workflow state
10110    pub fn update_state(&mut self, workflow_id: Uuid, state: WorkflowStatus) {
10111        self.states.insert(workflow_id, state);
10112    }
10113
10114    /// Add tag to workflow
10115    pub fn add_tag(&mut self, workflow_id: Uuid, tag: String) {
10116        self.tags.entry(tag).or_default().push(workflow_id);
10117    }
10118
10119    /// Get workflows by tag
10120    pub fn get_by_tag(&self, tag: &str) -> Vec<Uuid> {
10121        self.tags.get(tag).cloned().unwrap_or_default()
10122    }
10123
10124    /// Get workflow state
10125    pub fn get_state(&self, workflow_id: &Uuid) -> Option<&WorkflowStatus> {
10126        self.states.get(workflow_id)
10127    }
10128
10129    /// Get workflow name
10130    pub fn get_name(&self, workflow_id: &Uuid) -> Option<&str> {
10131        self.workflows.get(workflow_id).map(|s| s.as_str())
10132    }
10133
10134    /// Get workflow metadata
10135    pub fn get_metadata(&self, workflow_id: &Uuid) -> Option<&HashMap<String, serde_json::Value>> {
10136        self.metadata.get(workflow_id)
10137    }
10138
10139    /// Remove workflow
10140    pub fn remove(&mut self, workflow_id: &Uuid) -> bool {
10141        let removed = self.workflows.remove(workflow_id).is_some();
10142        self.metadata.remove(workflow_id);
10143        self.states.remove(workflow_id);
10144        self.start_times.remove(workflow_id);
10145
10146        // Remove from tags
10147        for workflows in self.tags.values_mut() {
10148            workflows.retain(|id| id != workflow_id);
10149        }
10150
10151        removed
10152    }
10153
10154    /// Get workflow count
10155    pub fn count(&self) -> usize {
10156        self.workflows.len()
10157    }
10158
10159    /// Get workflows by state
10160    pub fn get_by_state(&self, state: &WorkflowStatus) -> Vec<Uuid> {
10161        self.states
10162            .iter()
10163            .filter(|(_, s)| *s == state)
10164            .map(|(id, _)| *id)
10165            .collect()
10166    }
10167
10168    /// Clear all workflows
10169    pub fn clear(&mut self) {
10170        self.workflows.clear();
10171        self.metadata.clear();
10172        self.states.clear();
10173        self.start_times.clear();
10174        self.tags.clear();
10175    }
10176
10177    /// Get all workflow IDs
10178    pub fn all_workflow_ids(&self) -> Vec<Uuid> {
10179        self.workflows.keys().copied().collect()
10180    }
10181
10182    /// Get workflows by name pattern (contains)
10183    pub fn find_by_name(&self, pattern: &str) -> Vec<Uuid> {
10184        self.workflows
10185            .iter()
10186            .filter(|(_, name)| name.contains(pattern))
10187            .map(|(id, _)| *id)
10188            .collect()
10189    }
10190
10191    /// Get workflows older than duration (in milliseconds)
10192    pub fn get_older_than(&self, duration_ms: u64) -> Vec<Uuid> {
10193        let now = std::time::SystemTime::now()
10194            .duration_since(std::time::UNIX_EPOCH)
10195            .unwrap()
10196            .as_millis() as u64;
10197
10198        self.start_times
10199            .iter()
10200            .filter(|(_, &start_time)| now.saturating_sub(start_time) > duration_ms)
10201            .map(|(id, _)| *id)
10202            .collect()
10203    }
10204
10205    /// Get workflow age in milliseconds
10206    pub fn get_age(&self, workflow_id: &Uuid) -> Option<u64> {
10207        self.start_times.get(workflow_id).map(|&start_time| {
10208            let now = std::time::SystemTime::now()
10209                .duration_since(std::time::UNIX_EPOCH)
10210                .unwrap()
10211                .as_millis() as u64;
10212            now.saturating_sub(start_time)
10213        })
10214    }
10215
10216    /// Check if workflow exists
10217    pub fn contains(&self, workflow_id: &Uuid) -> bool {
10218        self.workflows.contains_key(workflow_id)
10219    }
10220
10221    /// Get all tags
10222    pub fn all_tags(&self) -> Vec<String> {
10223        self.tags.keys().cloned().collect()
10224    }
10225
10226    /// Get workflows with multiple tags (all tags must match)
10227    pub fn get_by_tags_all(&self, tags: &[&str]) -> Vec<Uuid> {
10228        if tags.is_empty() {
10229            return Vec::new();
10230        }
10231
10232        let mut result: Option<Vec<Uuid>> = None;
10233
10234        for tag in tags {
10235            let tagged = self.get_by_tag(tag);
10236            result = match result {
10237                None => Some(tagged),
10238                Some(current) => {
10239                    // Intersection
10240                    Some(
10241                        current
10242                            .into_iter()
10243                            .filter(|id| tagged.contains(id))
10244                            .collect(),
10245                    )
10246                }
10247            };
10248        }
10249
10250        result.unwrap_or_default()
10251    }
10252
10253    /// Get workflows with any of the tags (OR operation)
10254    pub fn get_by_tags_any(&self, tags: &[&str]) -> Vec<Uuid> {
10255        let mut result = Vec::new();
10256        for tag in tags {
10257            result.extend(self.get_by_tag(tag));
10258        }
10259        // Remove duplicates
10260        result.sort();
10261        result.dedup();
10262        result
10263    }
10264
10265    /// Get count by state
10266    pub fn count_by_state(&self, state: &WorkflowStatus) -> usize {
10267        self.states.iter().filter(|(_, s)| *s == state).count()
10268    }
10269
10270    /// Get running workflows count
10271    pub fn running_count(&self) -> usize {
10272        self.count_by_state(&WorkflowStatus::Running)
10273    }
10274
10275    /// Get pending workflows count
10276    pub fn pending_count(&self) -> usize {
10277        self.count_by_state(&WorkflowStatus::Pending)
10278    }
10279
10280    /// Get completed workflows count (success + failed)
10281    pub fn completed_count(&self) -> usize {
10282        self.count_by_state(&WorkflowStatus::Success) + self.count_by_state(&WorkflowStatus::Failed)
10283    }
10284}
10285
10286impl Default for WorkflowRegistry {
10287    fn default() -> Self {
10288        Self::new()
10289    }
10290}
10291
10292impl std::fmt::Display for WorkflowRegistry {
10293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10294        write!(
10295            f,
10296            "WorkflowRegistry[total={}, pending={}, running={}, success={}, failed={}]",
10297            self.count(),
10298            self.get_by_state(&WorkflowStatus::Pending).len(),
10299            self.get_by_state(&WorkflowStatus::Running).len(),
10300            self.get_by_state(&WorkflowStatus::Success).len(),
10301            self.get_by_state(&WorkflowStatus::Failed).len()
10302        )
10303    }
10304}
10305
10306#[cfg(test)]
10307mod tests {
10308    use super::*;
10309
10310    #[test]
10311    fn test_signature_creation() {
10312        let sig = Signature::new("test_task".to_string())
10313            .with_args(vec![serde_json::json!(1), serde_json::json!(2)])
10314            .with_priority(9);
10315
10316        assert_eq!(sig.task, "test_task");
10317        assert_eq!(sig.args.len(), 2);
10318        assert_eq!(sig.options.priority, Some(9));
10319    }
10320
10321    #[test]
10322    fn test_signature_predicates() {
10323        let sig = Signature::new("task".to_string())
10324            .with_args(vec![serde_json::json!(1)])
10325            .with_kwargs({
10326                let mut map = HashMap::new();
10327                map.insert("key".to_string(), serde_json::json!("value"));
10328                map
10329            })
10330            .immutable();
10331
10332        assert!(sig.has_args());
10333        assert!(sig.has_kwargs());
10334        assert!(sig.is_immutable());
10335    }
10336
10337    #[test]
10338    fn test_signature_display() {
10339        let sig = Signature::new("my_task".to_string())
10340            .with_args(vec![serde_json::json!(1), serde_json::json!(2)])
10341            .immutable();
10342
10343        let display = format!("{}", sig);
10344        assert!(display.contains("Signature[task=my_task]"));
10345        assert!(display.contains("args=2"));
10346        assert!(display.contains("(immutable)"));
10347    }
10348
10349    #[test]
10350    fn test_task_options_predicates() {
10351        let mut opts = TaskOptions::default();
10352        assert!(!opts.has_priority());
10353        assert!(!opts.has_queue());
10354        assert!(!opts.has_task_id());
10355        assert!(!opts.has_link());
10356        assert!(!opts.has_link_error());
10357
10358        opts.priority = Some(5);
10359        opts.queue = Some("celery".to_string());
10360        opts.task_id = Some(Uuid::new_v4());
10361        opts.link = Some(Box::new(Signature::new("link_task".to_string())));
10362        opts.link_error = Some(Box::new(Signature::new("error_task".to_string())));
10363
10364        assert!(opts.has_priority());
10365        assert!(opts.has_queue());
10366        assert!(opts.has_task_id());
10367        assert!(opts.has_link());
10368        assert!(opts.has_link_error());
10369    }
10370
10371    #[test]
10372    fn test_task_options_display() {
10373        let task_id = Uuid::new_v4();
10374        let opts = TaskOptions {
10375            priority: Some(9),
10376            queue: Some("high_priority".to_string()),
10377            task_id: Some(task_id),
10378            link: Some(Box::new(Signature::new("success".to_string()))),
10379            link_error: Some(Box::new(Signature::new("failure".to_string()))),
10380            ..Default::default()
10381        };
10382
10383        let display = format!("{}", opts);
10384        assert!(display.contains("TaskOptions"));
10385        assert!(display.contains("priority=9"));
10386        assert!(display.contains("queue=high_priority"));
10387        assert!(display.contains("task_id="));
10388        assert!(display.contains("link=yes"));
10389        assert!(display.contains("link_error=yes"));
10390    }
10391
10392    #[test]
10393    fn test_task_options_display_default() {
10394        let opts = TaskOptions::default();
10395        let display = format!("{}", opts);
10396        assert_eq!(display, "TaskOptions[default]");
10397    }
10398
10399    #[test]
10400    fn test_chain_builder() {
10401        let chain = Chain::new()
10402            .then("task1", vec![serde_json::json!(1)])
10403            .then("task2", vec![serde_json::json!(2)])
10404            .then("task3", vec![serde_json::json!(3)]);
10405
10406        assert_eq!(chain.tasks.len(), 3);
10407        assert_eq!(chain.tasks[0].task, "task1");
10408        assert_eq!(chain.tasks[2].task, "task3");
10409    }
10410
10411    #[test]
10412    fn test_chain_predicates() {
10413        let chain = Chain::new();
10414        assert!(chain.is_empty());
10415        assert_eq!(chain.len(), 0);
10416
10417        let chain = chain.then("task1", vec![]).then("task2", vec![]);
10418        assert!(!chain.is_empty());
10419        assert_eq!(chain.len(), 2);
10420    }
10421
10422    #[test]
10423    fn test_chain_display() {
10424        let chain = Chain::new()
10425            .then("first", vec![])
10426            .then("middle", vec![])
10427            .then("last", vec![]);
10428
10429        let display = format!("{}", chain);
10430        assert!(display.contains("Chain[3 tasks]"));
10431        assert!(display.contains("first -> ... -> last"));
10432    }
10433
10434    #[test]
10435    fn test_chain_display_empty() {
10436        let chain = Chain::new();
10437        let display = format!("{}", chain);
10438        assert_eq!(display, "Chain[0 tasks]");
10439    }
10440
10441    #[test]
10442    fn test_group_builder() {
10443        let group = Group::new()
10444            .add("task1", vec![])
10445            .add("task2", vec![])
10446            .add("task3", vec![]);
10447
10448        assert_eq!(group.tasks.len(), 3);
10449    }
10450
10451    #[test]
10452    fn test_group_predicates() {
10453        let group = Group::new();
10454        assert!(group.is_empty());
10455        assert_eq!(group.len(), 0);
10456        assert!(group.has_group_id());
10457
10458        let group = group.add("task1", vec![]).add("task2", vec![]);
10459        assert!(!group.is_empty());
10460        assert_eq!(group.len(), 2);
10461    }
10462
10463    #[test]
10464    fn test_group_display() {
10465        let group = Group::new()
10466            .add("task1", vec![])
10467            .add("task2", vec![])
10468            .add("task3", vec![]);
10469
10470        let display = format!("{}", group);
10471        assert!(display.contains("Group[3 tasks]"));
10472        assert!(display.contains("id="));
10473    }
10474
10475    #[test]
10476    fn test_chord_creation() {
10477        let header = Group::new().add("task1", vec![]).add("task2", vec![]);
10478
10479        let body = Signature::new("callback".to_string());
10480
10481        let chord = Chord::new(header, body);
10482
10483        assert_eq!(chord.header.tasks.len(), 2);
10484        assert_eq!(chord.body.task, "callback");
10485    }
10486
10487    #[test]
10488    fn test_chord_display() {
10489        let header = Group::new()
10490            .add("task1", vec![])
10491            .add("task2", vec![])
10492            .add("task3", vec![]);
10493        let body = Signature::new("aggregate".to_string());
10494        let chord = Chord::new(header, body);
10495
10496        let display = format!("{}", chord);
10497        assert!(display.contains("Chord[3 tasks] -> callback(aggregate)"));
10498    }
10499
10500    #[test]
10501    fn test_map_creation() {
10502        let task = Signature::new("process".to_string());
10503        let argsets = vec![
10504            vec![serde_json::json!(1)],
10505            vec![serde_json::json!(2)],
10506            vec![serde_json::json!(3)],
10507        ];
10508
10509        let map = Map::new(task, argsets);
10510
10511        assert_eq!(map.argsets.len(), 3);
10512    }
10513
10514    #[test]
10515    fn test_map_predicates() {
10516        let task = Signature::new("process".to_string());
10517        let empty_map = Map::new(task.clone(), vec![]);
10518        assert!(empty_map.is_empty());
10519        assert_eq!(empty_map.len(), 0);
10520
10521        let map = Map::new(
10522            task,
10523            vec![vec![serde_json::json!(1)], vec![serde_json::json!(2)]],
10524        );
10525        assert!(!map.is_empty());
10526        assert_eq!(map.len(), 2);
10527    }
10528
10529    #[test]
10530    fn test_map_display() {
10531        let task = Signature::new("process".to_string());
10532        let argsets = vec![
10533            vec![serde_json::json!(1)],
10534            vec![serde_json::json!(2)],
10535            vec![serde_json::json!(3)],
10536        ];
10537        let map = Map::new(task, argsets);
10538
10539        let display = format!("{}", map);
10540        assert!(display.contains("Map[task=process, 3 argsets]"));
10541    }
10542
10543    #[test]
10544    fn test_starmap_predicates() {
10545        let task = Signature::new("add".to_string());
10546        let empty_starmap = Starmap::new(task.clone(), vec![]);
10547        assert!(empty_starmap.is_empty());
10548        assert_eq!(empty_starmap.len(), 0);
10549
10550        let starmap = Starmap::new(
10551            task,
10552            vec![
10553                vec![serde_json::json!(1), serde_json::json!(2)],
10554                vec![serde_json::json!(3), serde_json::json!(4)],
10555            ],
10556        );
10557        assert!(!starmap.is_empty());
10558        assert_eq!(starmap.len(), 2);
10559    }
10560
10561    #[test]
10562    fn test_starmap_display() {
10563        let task = Signature::new("multiply".to_string());
10564        let argsets = vec![
10565            vec![serde_json::json!(2), serde_json::json!(3)],
10566            vec![serde_json::json!(4), serde_json::json!(5)],
10567        ];
10568        let starmap = Starmap::new(task, argsets);
10569
10570        let display = format!("{}", starmap);
10571        assert!(display.contains("Starmap[task=multiply, 2 argsets]"));
10572    }
10573
10574    #[test]
10575    fn test_canvas_error_predicates() {
10576        let invalid_err = CanvasError::Invalid("bad workflow".to_string());
10577        assert!(invalid_err.is_invalid());
10578        assert!(!invalid_err.is_broker());
10579        assert!(!invalid_err.is_serialization());
10580        assert!(!invalid_err.is_retryable());
10581
10582        let broker_err = CanvasError::Broker("connection failed".to_string());
10583        assert!(!broker_err.is_invalid());
10584        assert!(broker_err.is_broker());
10585        assert!(!broker_err.is_serialization());
10586        assert!(broker_err.is_retryable());
10587
10588        let ser_err = CanvasError::Serialization("bad json".to_string());
10589        assert!(!ser_err.is_invalid());
10590        assert!(!ser_err.is_broker());
10591        assert!(ser_err.is_serialization());
10592        assert!(!ser_err.is_retryable());
10593    }
10594
10595    #[test]
10596    fn test_canvas_error_category() {
10597        let invalid_err = CanvasError::Invalid("test".to_string());
10598        assert_eq!(invalid_err.category(), "invalid");
10599
10600        let broker_err = CanvasError::Broker("test".to_string());
10601        assert_eq!(broker_err.category(), "broker");
10602
10603        let ser_err = CanvasError::Serialization("test".to_string());
10604        assert_eq!(ser_err.category(), "serialization");
10605
10606        let cancelled_err = CanvasError::Cancelled("test".to_string());
10607        assert_eq!(cancelled_err.category(), "cancelled");
10608
10609        let timeout_err = CanvasError::Timeout("test".to_string());
10610        assert_eq!(timeout_err.category(), "timeout");
10611    }
10612
10613    #[test]
10614    fn test_canvas_error_display() {
10615        let err = CanvasError::Invalid("empty chain".to_string());
10616        assert_eq!(err.to_string(), "Invalid workflow: empty chain");
10617
10618        let err = CanvasError::Broker("timeout".to_string());
10619        assert_eq!(err.to_string(), "Broker error: timeout");
10620
10621        let err = CanvasError::Serialization("malformed json".to_string());
10622        assert_eq!(err.to_string(), "Serialization error: malformed json");
10623
10624        let err = CanvasError::Cancelled("user requested".to_string());
10625        assert_eq!(err.to_string(), "Workflow cancelled: user requested");
10626
10627        let err = CanvasError::Timeout("exceeded 5s".to_string());
10628        assert_eq!(err.to_string(), "Workflow timeout: exceeded 5s");
10629    }
10630
10631    #[test]
10632    fn test_cancellation_token() {
10633        let workflow_id = Uuid::new_v4();
10634        let token = CancellationToken::new(workflow_id)
10635            .with_reason("user requested".to_string())
10636            .cancel_tree();
10637
10638        assert_eq!(token.workflow_id, workflow_id);
10639        assert_eq!(token.reason, Some("user requested".to_string()));
10640        assert!(token.cancel_tree);
10641        assert!(token.branch_id.is_none());
10642
10643        let display = format!("{}", token);
10644        assert!(display.contains("CancellationToken"));
10645        assert!(display.contains("reason=user requested"));
10646        assert!(display.contains("(tree)"));
10647    }
10648
10649    #[test]
10650    fn test_workflow_retry_policy() {
10651        let policy = WorkflowRetryPolicy::new(3)
10652            .failed_only()
10653            .with_backoff(2.0, 60)
10654            .with_initial_delay(1);
10655
10656        assert_eq!(policy.max_retries, 3);
10657        assert!(policy.retry_failed_only);
10658        assert_eq!(policy.backoff_factor, Some(2.0));
10659        assert_eq!(policy.max_backoff, Some(60));
10660        assert_eq!(policy.initial_delay, Some(1));
10661
10662        // Test delay calculation
10663        assert_eq!(policy.calculate_delay(0), 1); // 1 * 2^0 = 1
10664        assert_eq!(policy.calculate_delay(1), 2); // 1 * 2^1 = 2
10665        assert_eq!(policy.calculate_delay(2), 4); // 1 * 2^2 = 4
10666        assert_eq!(policy.calculate_delay(10), 60); // Capped at max_backoff
10667
10668        let display = format!("{}", policy);
10669        assert!(display.contains("WorkflowRetryPolicy"));
10670        assert!(display.contains("max_retries=3"));
10671        assert!(display.contains("(failed_only)"));
10672    }
10673
10674    #[test]
10675    fn test_workflow_timeout() {
10676        let timeout = WorkflowTimeout::new(300)
10677            .with_stage_timeout(60)
10678            .with_escalation(TimeoutEscalation::ContinuePartial);
10679
10680        assert_eq!(timeout.total_timeout, Some(300));
10681        assert_eq!(timeout.stage_timeout, Some(60));
10682        assert!(matches!(
10683            timeout.escalation,
10684            TimeoutEscalation::ContinuePartial
10685        ));
10686
10687        let display = format!("{}", timeout);
10688        assert!(display.contains("WorkflowTimeout"));
10689        assert!(display.contains("total=300s"));
10690        assert!(display.contains("stage=60s"));
10691    }
10692
10693    #[test]
10694    fn test_foreach_loop() {
10695        let task = Signature::new("process".to_string());
10696        let items = vec![
10697            serde_json::json!(1),
10698            serde_json::json!(2),
10699            serde_json::json!(3),
10700        ];
10701        let foreach = ForEach::new(task, items).with_concurrency(2);
10702
10703        assert_eq!(foreach.len(), 3);
10704        assert!(!foreach.is_empty());
10705        assert_eq!(foreach.concurrency, Some(2));
10706
10707        let display = format!("{}", foreach);
10708        assert!(display.contains("ForEach"));
10709        assert!(display.contains("process"));
10710        assert!(display.contains("3 items"));
10711        assert!(display.contains("concurrency=2"));
10712
10713        let empty = ForEach::new(Signature::new("task".to_string()), vec![]);
10714        assert!(empty.is_empty());
10715        assert_eq!(empty.len(), 0);
10716    }
10717
10718    #[test]
10719    fn test_while_loop() {
10720        let condition = Condition::field_equals("status", serde_json::json!("pending"));
10721        let body = Signature::new("check".to_string());
10722        let while_loop = WhileLoop::new(condition, body).with_max_iterations(100);
10723
10724        assert_eq!(while_loop.max_iterations, Some(100));
10725
10726        let display = format!("{}", while_loop);
10727        assert!(display.contains("While"));
10728        assert!(display.contains("check"));
10729        assert!(display.contains("max=100"));
10730
10731        let unlimited = WhileLoop::new(
10732            Condition::field_equals("x", serde_json::json!(0)),
10733            Signature::new("task".to_string()),
10734        )
10735        .unlimited();
10736        assert!(unlimited.max_iterations.is_none());
10737    }
10738
10739    #[test]
10740    fn test_workflow_state() {
10741        let workflow_id = Uuid::new_v4();
10742        let mut state = WorkflowState::new(workflow_id, 10);
10743
10744        assert_eq!(state.workflow_id, workflow_id);
10745        assert_eq!(state.status, WorkflowStatus::Pending);
10746        assert_eq!(state.total_tasks, 10);
10747        assert_eq!(state.completed_tasks, 0);
10748        assert_eq!(state.failed_tasks, 0);
10749        assert_eq!(state.progress(), 0.0);
10750        assert!(!state.is_complete());
10751
10752        // Mark some tasks as completed
10753        state.mark_completed();
10754        state.mark_completed();
10755        state.mark_completed();
10756        assert_eq!(state.completed_tasks, 3);
10757        assert_eq!(state.progress(), 30.0);
10758
10759        // Mark a task as failed
10760        state.mark_failed();
10761        assert_eq!(state.failed_tasks, 1);
10762
10763        // Set and get intermediate results
10764        state.set_result("step1".to_string(), serde_json::json!({"result": 42}));
10765        assert_eq!(
10766            state.get_result("step1"),
10767            Some(&serde_json::json!({"result": 42}))
10768        );
10769        assert_eq!(state.get_result("nonexistent"), None);
10770
10771        // Test completion states
10772        state.status = WorkflowStatus::Success;
10773        assert!(state.is_complete());
10774
10775        state.status = WorkflowStatus::Failed;
10776        assert!(state.is_complete());
10777
10778        state.status = WorkflowStatus::Cancelled;
10779        assert!(state.is_complete());
10780
10781        state.status = WorkflowStatus::Running;
10782        assert!(!state.is_complete());
10783
10784        let display = format!("{}", state);
10785        assert!(display.contains("WorkflowState"));
10786        assert!(display.contains("progress=30.0%"));
10787        assert!(display.contains("failed=1"));
10788    }
10789
10790    #[test]
10791    fn test_dag_export_chain() {
10792        let chain = Chain::new()
10793            .then("task1", vec![])
10794            .then("task2", vec![])
10795            .then("task3", vec![]);
10796
10797        // Test DOT export
10798        let dot = chain.to_dot();
10799        assert!(dot.contains("digraph Chain"));
10800        assert!(dot.contains("rankdir=LR"));
10801        assert!(dot.contains("task1"));
10802        assert!(dot.contains("task2"));
10803        assert!(dot.contains("task3"));
10804        assert!(dot.contains("n0 -> n1"));
10805        assert!(dot.contains("n1 -> n2"));
10806
10807        // Test Mermaid export
10808        let mmd = chain.to_mermaid();
10809        assert!(mmd.contains("graph LR"));
10810        assert!(mmd.contains("task1"));
10811        assert!(mmd.contains("task2"));
10812        assert!(mmd.contains("task3"));
10813        assert!(mmd.contains("n0 --> n1"));
10814        assert!(mmd.contains("n1 --> n2"));
10815
10816        // Test JSON export
10817        let json = chain.to_json().unwrap();
10818        assert!(json.contains("task1"));
10819        assert!(json.contains("task2"));
10820        assert!(json.contains("task3"));
10821    }
10822
10823    #[test]
10824    fn test_dag_export_group() {
10825        let group = Group::new()
10826            .add("task1", vec![])
10827            .add("task2", vec![])
10828            .add("task3", vec![]);
10829
10830        // Test DOT export
10831        let dot = group.to_dot();
10832        assert!(dot.contains("digraph Group"));
10833        assert!(dot.contains("rankdir=TB"));
10834        assert!(dot.contains("start"));
10835        assert!(dot.contains("task1"));
10836        assert!(dot.contains("task2"));
10837        assert!(dot.contains("task3"));
10838        assert!(dot.contains("start -> n0"));
10839        assert!(dot.contains("start -> n1"));
10840        assert!(dot.contains("start -> n2"));
10841
10842        // Test Mermaid export
10843        let mmd = group.to_mermaid();
10844        assert!(mmd.contains("graph TB"));
10845        assert!(mmd.contains("start"));
10846        assert!(mmd.contains("task1"));
10847        assert!(mmd.contains("start --> n0"));
10848
10849        // Test JSON export
10850        let json = group.to_json().unwrap();
10851        assert!(json.contains("task1"));
10852    }
10853
10854    #[test]
10855    fn test_dag_export_chord() {
10856        let header = Group::new().add("task1", vec![]).add("task2", vec![]);
10857        let body = Signature::new("callback".to_string());
10858        let chord = Chord::new(header, body);
10859
10860        // Test DOT export
10861        let dot = chord.to_dot();
10862        assert!(dot.contains("digraph Chord"));
10863        assert!(dot.contains("callback"));
10864        assert!(dot.contains("task1"));
10865        assert!(dot.contains("task2"));
10866        assert!(dot.contains("n0 -> callback"));
10867        assert!(dot.contains("n1 -> callback"));
10868
10869        // Test Mermaid export
10870        let mmd = chord.to_mermaid();
10871        assert!(mmd.contains("graph TB"));
10872        assert!(mmd.contains("callback"));
10873        assert!(mmd.contains("task1"));
10874        assert!(mmd.contains("n0 --> callback"));
10875
10876        // Test JSON export
10877        let json = chord.to_json().unwrap();
10878        assert!(json.contains("callback"));
10879        assert!(json.contains("task1"));
10880    }
10881
10882    #[test]
10883    fn test_canvas_error_new_variants() {
10884        let cancelled = CanvasError::Cancelled("user cancelled".to_string());
10885        assert!(cancelled.is_cancelled());
10886        assert!(!cancelled.is_timeout());
10887        assert!(!cancelled.is_retryable());
10888        assert_eq!(cancelled.category(), "cancelled");
10889
10890        let timeout = CanvasError::Timeout("exceeded limit".to_string());
10891        assert!(timeout.is_timeout());
10892        assert!(!timeout.is_cancelled());
10893        assert!(!timeout.is_retryable());
10894        assert_eq!(timeout.category(), "timeout");
10895    }
10896
10897    #[test]
10898    fn test_named_output() {
10899        let output = NamedOutput::new("result", serde_json::json!(42)).with_source("task1");
10900
10901        assert_eq!(output.name, "result");
10902        assert_eq!(output.value, serde_json::json!(42));
10903        assert_eq!(output.source, Some("task1".to_string()));
10904    }
10905
10906    #[test]
10907    fn test_result_transform() {
10908        let extract = ResultTransform::Extract {
10909            field: "data".to_string(),
10910        };
10911        assert!(format!("{}", extract).contains("Extract[data]"));
10912
10913        let map = ResultTransform::Map {
10914            task: Box::new(Signature::new("transform".to_string())),
10915        };
10916        assert!(format!("{}", map).contains("Map[transform]"));
10917    }
10918
10919    #[test]
10920    fn test_result_cache() {
10921        let cache = ResultCache::new("task:123")
10922            .with_policy(CachePolicy::OnSuccess)
10923            .with_ttl(3600);
10924
10925        assert_eq!(cache.key, "task:123");
10926        assert_eq!(cache.ttl, Some(3600));
10927
10928        let display = format!("{}", cache);
10929        assert!(display.contains("Cache[key=task:123]"));
10930        assert!(display.contains("ttl=3600s"));
10931    }
10932
10933    #[test]
10934    fn test_workflow_error_handler() {
10935        let handler = WorkflowErrorHandler::new(Signature::new("handle_error".to_string()))
10936            .for_errors(vec!["NetworkError".to_string(), "TimeoutError".to_string()])
10937            .suppress_error();
10938
10939        assert_eq!(handler.handler.task, "handle_error");
10940        assert_eq!(handler.error_types.len(), 2);
10941        assert!(handler.suppress);
10942
10943        let display = format!("{}", handler);
10944        assert!(display.contains("ErrorHandler[handle_error]"));
10945        assert!(display.contains("(suppress)"));
10946    }
10947
10948    #[test]
10949    fn test_compensation_workflow() {
10950        let mut workflow = CompensationWorkflow::new();
10951        assert!(workflow.is_empty());
10952        assert_eq!(workflow.len(), 0);
10953
10954        workflow = workflow
10955            .step(
10956                Signature::new("create".to_string()),
10957                Signature::new("delete".to_string()),
10958            )
10959            .step(
10960                Signature::new("update".to_string()),
10961                Signature::new("rollback".to_string()),
10962            );
10963
10964        assert!(!workflow.is_empty());
10965        assert_eq!(workflow.len(), 2);
10966        assert_eq!(workflow.forward.len(), 2);
10967        assert_eq!(workflow.compensations.len(), 2);
10968
10969        let display = format!("{}", workflow);
10970        assert!(display.contains("Compensation[2 steps, 2 compensations]"));
10971    }
10972
10973    #[test]
10974    fn test_saga() {
10975        let workflow = CompensationWorkflow::new()
10976            .step(
10977                Signature::new("reserve".to_string()),
10978                Signature::new("cancel_reservation".to_string()),
10979            )
10980            .step(
10981                Signature::new("charge".to_string()),
10982                Signature::new("refund".to_string()),
10983            );
10984
10985        let saga = Saga::new(workflow).with_isolation(SagaIsolation::Serializable);
10986
10987        assert_eq!(saga.workflow.len(), 2);
10988        assert!(matches!(saga.isolation, SagaIsolation::Serializable));
10989
10990        let display = format!("{}", saga);
10991        assert!(display.contains("Saga[2 steps"));
10992        assert!(display.contains("Serializable"));
10993    }
10994
10995    #[test]
10996    fn test_scatter_gather() {
10997        let scatter = Signature::new("distribute".to_string());
10998        let workers = vec![
10999            Signature::new("worker1".to_string()),
11000            Signature::new("worker2".to_string()),
11001            Signature::new("worker3".to_string()),
11002        ];
11003        let gather = Signature::new("collect".to_string());
11004
11005        let sg = ScatterGather::new(scatter, workers, gather).with_timeout(30);
11006
11007        assert_eq!(sg.workers.len(), 3);
11008        assert_eq!(sg.timeout, Some(30));
11009
11010        let display = format!("{}", sg);
11011        assert!(display.contains("ScatterGather"));
11012        assert!(display.contains("distribute"));
11013        assert!(display.contains("3 workers"));
11014        assert!(display.contains("collect"));
11015    }
11016
11017    #[test]
11018    fn test_pipeline() {
11019        let mut pipeline = Pipeline::new();
11020        assert!(pipeline.is_empty());
11021        assert_eq!(pipeline.len(), 0);
11022
11023        pipeline = pipeline
11024            .stage(Signature::new("fetch".to_string()))
11025            .stage(Signature::new("transform".to_string()))
11026            .stage(Signature::new("load".to_string()))
11027            .with_buffer_size(100);
11028
11029        assert!(!pipeline.is_empty());
11030        assert_eq!(pipeline.len(), 3);
11031        assert_eq!(pipeline.buffer_size, Some(100));
11032
11033        let display = format!("{}", pipeline);
11034        assert!(display.contains("Pipeline[3 stages]"));
11035        assert!(display.contains("buffer=100"));
11036    }
11037
11038    #[test]
11039    fn test_fanout() {
11040        let source = Signature::new("broadcast".to_string());
11041        let fanout = FanOut::new(source)
11042            .consumer(Signature::new("consumer1".to_string()))
11043            .consumer(Signature::new("consumer2".to_string()))
11044            .consumer(Signature::new("consumer3".to_string()));
11045
11046        assert!(!fanout.is_empty());
11047        assert_eq!(fanout.len(), 3);
11048
11049        let display = format!("{}", fanout);
11050        assert!(display.contains("FanOut"));
11051        assert!(display.contains("broadcast"));
11052        assert!(display.contains("3 consumers"));
11053    }
11054
11055    #[test]
11056    fn test_fanin() {
11057        let aggregator = Signature::new("aggregate".to_string());
11058        let fanin = FanIn::new(aggregator)
11059            .source(Signature::new("source1".to_string()))
11060            .source(Signature::new("source2".to_string()));
11061
11062        assert!(!fanin.is_empty());
11063        assert_eq!(fanin.len(), 2);
11064
11065        let display = format!("{}", fanin);
11066        assert!(display.contains("FanIn"));
11067        assert!(display.contains("2 sources"));
11068        assert!(display.contains("aggregate"));
11069    }
11070
11071    #[test]
11072    fn test_validation_result() {
11073        let mut result = ValidationResult::valid();
11074        assert!(result.valid);
11075        assert!(result.errors.is_empty());
11076        assert!(result.warnings.is_empty());
11077
11078        result.add_warning("This is a warning");
11079        assert!(result.valid);
11080        assert_eq!(result.warnings.len(), 1);
11081
11082        result.add_error("This is an error");
11083        assert!(!result.valid);
11084        assert_eq!(result.errors.len(), 1);
11085
11086        let display = format!("{}", result);
11087        assert!(display.contains("Invalid"));
11088        assert!(display.contains("1 errors"));
11089    }
11090
11091    #[test]
11092    fn test_workflow_validator_chain() {
11093        let empty_chain = Chain::new();
11094        let result = empty_chain.validate();
11095        assert!(!result.valid);
11096        assert!(result.errors.iter().any(|e| e.contains("cannot be empty")));
11097
11098        let valid_chain = Chain::new()
11099            .then("task1", vec![])
11100            .then("task2", vec![])
11101            .then("task3", vec![]);
11102        let result = valid_chain.validate();
11103        assert!(result.valid);
11104
11105        // Create a large chain to test warning
11106        let mut large_chain = Chain::new();
11107        for i in 0..150 {
11108            large_chain = large_chain.then(&format!("task{}", i), vec![]);
11109        }
11110        let result = large_chain.validate();
11111        assert!(result.valid);
11112        assert!(!result.warnings.is_empty());
11113    }
11114
11115    #[test]
11116    fn test_workflow_validator_group() {
11117        let empty_group = Group::new();
11118        let result = empty_group.validate();
11119        assert!(!result.valid);
11120        assert!(result.errors.iter().any(|e| e.contains("cannot be empty")));
11121
11122        let valid_group = Group::new().add("task1", vec![]).add("task2", vec![]);
11123        let result = valid_group.validate();
11124        assert!(result.valid);
11125    }
11126
11127    #[test]
11128    fn test_workflow_validator_chord() {
11129        let empty_header = Group::new();
11130        let body = Signature::new("callback".to_string());
11131        let chord = Chord::new(empty_header, body);
11132
11133        let result = chord.validate();
11134        assert!(!result.valid);
11135        assert!(result.errors.iter().any(|e| e.contains("cannot be empty")));
11136
11137        let valid_header = Group::new().add("task1", vec![]).add("task2", vec![]);
11138        let body = Signature::new("callback".to_string());
11139        let chord = Chord::new(valid_header, body);
11140
11141        let result = chord.validate();
11142        assert!(result.valid);
11143    }
11144
11145    #[test]
11146    fn test_loop_control() {
11147        let continue_ctrl = LoopControl::continue_loop();
11148        assert!(matches!(continue_ctrl, LoopControl::Continue));
11149        assert_eq!(format!("{}", continue_ctrl), "Continue");
11150
11151        let break_ctrl = LoopControl::break_loop();
11152        assert!(matches!(break_ctrl, LoopControl::Break));
11153        assert_eq!(format!("{}", break_ctrl), "Break");
11154
11155        let break_with = LoopControl::break_with(serde_json::json!({"result": 42}));
11156        assert!(matches!(break_with, LoopControl::BreakWith { .. }));
11157        assert_eq!(format!("{}", break_with), "BreakWith");
11158    }
11159
11160    #[test]
11161    fn test_error_propagation_mode() {
11162        let stop = ErrorPropagationMode::StopOnFirstError;
11163        assert!(!stop.allows_continue());
11164        assert_eq!(format!("{}", stop), "StopOnFirstError");
11165
11166        let continue_mode = ErrorPropagationMode::ContinueOnError;
11167        assert!(continue_mode.allows_continue());
11168        assert_eq!(format!("{}", continue_mode), "ContinueOnError");
11169
11170        let partial = ErrorPropagationMode::partial_failure(3);
11171        assert!(partial.allows_continue());
11172        assert!(format!("{}", partial).contains("PartialFailure"));
11173
11174        let partial_rate = ErrorPropagationMode::partial_failure_with_rate(5, 0.5);
11175        assert!(partial_rate.allows_continue());
11176        let display = format!("{}", partial_rate);
11177        assert!(display.contains("PartialFailure"));
11178        assert!(display.contains("50.0%"));
11179    }
11180
11181    #[test]
11182    fn test_partial_failure_tracker() {
11183        let mut tracker = PartialFailureTracker::new(10);
11184        assert_eq!(tracker.total_tasks, 10);
11185        assert_eq!(tracker.successful_tasks, 0);
11186        assert_eq!(tracker.failed_tasks, 0);
11187
11188        // Record successes
11189        tracker.record_success(Uuid::new_v4());
11190        tracker.record_success(Uuid::new_v4());
11191        assert_eq!(tracker.successful_tasks, 2);
11192        assert_eq!(tracker.success_rate(), 0.2);
11193
11194        // Record failures
11195        tracker.record_failure(Uuid::new_v4(), "error1".to_string());
11196        tracker.record_failure(Uuid::new_v4(), "error2".to_string());
11197        assert_eq!(tracker.failed_tasks, 2);
11198        assert_eq!(tracker.failure_rate(), 0.2);
11199
11200        // Test threshold checking
11201        let stop_mode = ErrorPropagationMode::StopOnFirstError;
11202        assert!(tracker.exceeds_threshold(&stop_mode));
11203        assert!(!tracker.should_continue(&stop_mode));
11204
11205        let continue_mode = ErrorPropagationMode::ContinueOnError;
11206        assert!(!tracker.exceeds_threshold(&continue_mode));
11207        assert!(tracker.should_continue(&continue_mode));
11208
11209        let partial_mode = ErrorPropagationMode::partial_failure(3);
11210        assert!(!tracker.exceeds_threshold(&partial_mode));
11211        assert!(tracker.should_continue(&partial_mode));
11212
11213        // Add more failures to exceed threshold
11214        tracker.record_failure(Uuid::new_v4(), "error3".to_string());
11215        assert!(tracker.exceeds_threshold(&partial_mode));
11216        assert!(!tracker.should_continue(&partial_mode));
11217
11218        let display = format!("{}", tracker);
11219        assert!(display.contains("PartialFailureTracker"));
11220        assert!(display.contains("2/10"));
11221    }
11222
11223    #[test]
11224    fn test_isolation_level() {
11225        let none = IsolationLevel::None;
11226        assert!(!none.has_resource_limits());
11227        assert!(!none.has_error_isolation());
11228        assert_eq!(format!("{}", none), "None");
11229
11230        let resource = IsolationLevel::resource(512);
11231        assert!(resource.has_resource_limits());
11232        assert!(!resource.has_error_isolation());
11233        let display = format!("{}", resource);
11234        assert!(display.contains("Resource"));
11235        assert!(display.contains("512MB"));
11236
11237        let error = IsolationLevel::Error;
11238        assert!(!error.has_resource_limits());
11239        assert!(error.has_error_isolation());
11240        assert_eq!(format!("{}", error), "Error");
11241
11242        let full = IsolationLevel::full(1024);
11243        assert!(full.has_resource_limits());
11244        assert!(full.has_error_isolation());
11245        let display = format!("{}", full);
11246        assert!(display.contains("Full"));
11247        assert!(display.contains("1024MB"));
11248    }
11249
11250    #[test]
11251    fn test_sub_workflow_isolation() {
11252        let workflow_id = Uuid::new_v4();
11253        let parent_id = Uuid::new_v4();
11254
11255        let isolation = SubWorkflowIsolation::new(workflow_id, IsolationLevel::Error)
11256            .with_parent(parent_id)
11257            .no_error_propagation()
11258            .no_cancellation_propagation();
11259
11260        assert_eq!(isolation.workflow_id, workflow_id);
11261        assert_eq!(isolation.parent_workflow_id, Some(parent_id));
11262        assert_eq!(isolation.isolation_level, IsolationLevel::Error);
11263        assert!(!isolation.propagate_errors);
11264        assert!(!isolation.propagate_cancellation);
11265
11266        let display = format!("{}", isolation);
11267        assert!(display.contains("SubWorkflowIsolation"));
11268        assert!(display.contains(&workflow_id.to_string()));
11269    }
11270
11271    #[test]
11272    fn test_workflow_checkpoint() {
11273        let workflow_id = Uuid::new_v4();
11274        let state = WorkflowState::new(workflow_id, 10);
11275        let mut checkpoint = WorkflowCheckpoint::new(workflow_id, state);
11276
11277        assert_eq!(checkpoint.workflow_id, workflow_id);
11278        assert_eq!(checkpoint.version, 1);
11279        assert_eq!(checkpoint.completed_tasks.len(), 0);
11280
11281        // Record tasks
11282        let task1 = Uuid::new_v4();
11283        let task2 = Uuid::new_v4();
11284        let task3 = Uuid::new_v4();
11285
11286        checkpoint.record_in_progress(task1);
11287        checkpoint.record_completed(task2);
11288        checkpoint.record_failed(task3, "test error".to_string());
11289
11290        assert!(checkpoint.is_completed(&task2));
11291        assert!(checkpoint.is_failed(&task3));
11292        assert!(!checkpoint.is_completed(&task1));
11293        assert_eq!(checkpoint.tasks_to_retry().len(), 1);
11294
11295        // Test serialization
11296        let json = checkpoint.to_json().unwrap();
11297        let deserialized = WorkflowCheckpoint::from_json(&json).unwrap();
11298        assert_eq!(deserialized.workflow_id, workflow_id);
11299        assert_eq!(deserialized.completed_tasks.len(), 1);
11300        assert_eq!(deserialized.failed_tasks.len(), 1);
11301
11302        let display = format!("{}", checkpoint);
11303        assert!(display.contains("WorkflowCheckpoint"));
11304        assert!(display.contains("completed=1"));
11305        assert!(display.contains("failed=1"));
11306    }
11307
11308    #[test]
11309    fn test_workflow_recovery_policy() {
11310        let auto = WorkflowRecoveryPolicy::auto_recover();
11311        assert!(auto.auto_recovery);
11312        assert!(auto.resume_from_checkpoint);
11313        assert!(auto.replay_failed);
11314        assert_eq!(auto.max_checkpoint_age, Some(3600));
11315
11316        let manual = WorkflowRecoveryPolicy::manual();
11317        assert!(!manual.auto_recovery);
11318        assert!(manual.resume_from_checkpoint);
11319        assert!(!manual.replay_failed);
11320        assert_eq!(manual.max_checkpoint_age, None);
11321
11322        let custom = WorkflowRecoveryPolicy::auto_recover()
11323            .with_max_checkpoint_age(7200)
11324            .with_retry_policy(WorkflowRetryPolicy::new(3));
11325        assert_eq!(custom.max_checkpoint_age, Some(7200));
11326        assert!(custom.retry_policy.is_some());
11327
11328        // Test checkpoint validation
11329        let workflow_id = Uuid::new_v4();
11330        let state = WorkflowState::new(workflow_id, 10);
11331        let checkpoint = WorkflowCheckpoint::new(workflow_id, state);
11332        assert!(auto.is_checkpoint_valid(&checkpoint));
11333
11334        let display = format!("{}", auto);
11335        assert!(display.contains("WorkflowRecoveryPolicy"));
11336        assert!(display.contains("auto"));
11337    }
11338
11339    #[test]
11340    fn test_optimization_pass() {
11341        let cse = OptimizationPass::CommonSubexpressionElimination;
11342        assert_eq!(format!("{}", cse), "CSE");
11343
11344        let dce = OptimizationPass::DeadCodeElimination;
11345        assert_eq!(format!("{}", dce), "DCE");
11346
11347        let fusion = OptimizationPass::TaskFusion;
11348        assert_eq!(format!("{}", fusion), "TaskFusion");
11349
11350        let scheduling = OptimizationPass::ParallelScheduling;
11351        assert_eq!(format!("{}", scheduling), "ParallelScheduling");
11352
11353        let resource = OptimizationPass::ResourceOptimization;
11354        assert_eq!(format!("{}", resource), "ResourceOptimization");
11355    }
11356
11357    #[test]
11358    fn test_workflow_compiler() {
11359        let compiler = WorkflowCompiler::new();
11360        assert!(!compiler.aggressive);
11361        assert_eq!(compiler.passes.len(), 2);
11362
11363        let aggressive = WorkflowCompiler::new().aggressive();
11364        assert!(aggressive.aggressive);
11365        assert!(aggressive.passes.len() > 2);
11366
11367        let custom = WorkflowCompiler::new()
11368            .add_pass(OptimizationPass::TaskFusion)
11369            .add_pass(OptimizationPass::ParallelScheduling);
11370        assert_eq!(custom.passes.len(), 4);
11371
11372        // Test basic optimization (no change expected)
11373        let chain = Chain::new().then("task1", vec![]).then("task2", vec![]);
11374        let optimized = compiler.optimize_chain(&chain);
11375        assert_eq!(optimized.tasks.len(), chain.tasks.len());
11376
11377        let group = Group::new().add("task1", vec![]).add("task2", vec![]);
11378        let optimized_group = compiler.optimize_group(&group);
11379        assert_eq!(optimized_group.tasks.len(), group.tasks.len());
11380
11381        let display = format!("{}", compiler);
11382        assert!(display.contains("WorkflowCompiler"));
11383        assert!(display.contains("DCE"));
11384        assert!(display.contains("CSE"));
11385    }
11386
11387    #[test]
11388    fn test_workflow_compiler_cse_chain() {
11389        use serde_json::json;
11390
11391        // Test Common Subexpression Elimination for chains
11392        let compiler = WorkflowCompiler::new().aggressive();
11393        let chain = Chain::new()
11394            .then_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11395            .then_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]))
11396            .then_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)])); // Duplicate
11397
11398        let optimized = compiler.optimize_chain(&chain);
11399        // CSE should remove the duplicate task in aggressive mode
11400        assert_eq!(optimized.tasks.len(), 2);
11401        assert_eq!(optimized.tasks[0].task, "task1");
11402        assert_eq!(optimized.tasks[1].task, "task2");
11403    }
11404
11405    #[test]
11406    fn test_workflow_compiler_cse_group() {
11407        use serde_json::json;
11408
11409        // Test Common Subexpression Elimination for groups
11410        let compiler = WorkflowCompiler::new().aggressive();
11411        let group = Group::new()
11412            .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11413            .add_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]))
11414            .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)])); // Duplicate
11415
11416        let optimized = compiler.optimize_group(&group);
11417        // CSE should remove the duplicate task in aggressive mode
11418        assert_eq!(optimized.tasks.len(), 2);
11419        assert_eq!(optimized.tasks[0].task, "task1");
11420        assert_eq!(optimized.tasks[1].task, "task2");
11421    }
11422
11423    #[test]
11424    fn test_workflow_compiler_dce_chain() {
11425        use serde_json::json;
11426
11427        // Test Dead Code Elimination for chains
11428        let compiler = WorkflowCompiler::new();
11429        let chain = Chain::new()
11430            .then_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11431            .then_signature(Signature::new("".to_string())) // Empty task name (dead code)
11432            .then_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]));
11433
11434        let optimized = compiler.optimize_chain(&chain);
11435        // DCE should remove the empty task
11436        assert_eq!(optimized.tasks.len(), 2);
11437        assert_eq!(optimized.tasks[0].task, "task1");
11438        assert_eq!(optimized.tasks[1].task, "task2");
11439    }
11440
11441    #[test]
11442    fn test_workflow_compiler_dce_group() {
11443        use serde_json::json;
11444
11445        // Test Dead Code Elimination for groups
11446        let compiler = WorkflowCompiler::new();
11447        let group = Group::new()
11448            .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11449            .add_signature(Signature::new("".to_string())) // Empty task name (dead code)
11450            .add_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]));
11451
11452        let optimized = compiler.optimize_group(&group);
11453        // DCE should remove the empty task
11454        assert_eq!(optimized.tasks.len(), 2);
11455        assert_eq!(optimized.tasks[0].task, "task1");
11456        assert_eq!(optimized.tasks[1].task, "task2");
11457    }
11458
11459    #[test]
11460    fn test_workflow_compiler_task_fusion() {
11461        use serde_json::json;
11462
11463        // Test Task Fusion for chains
11464        let compiler = WorkflowCompiler::new().aggressive();
11465        let chain = Chain::new()
11466            .then_signature(
11467                Signature::new("process".to_string())
11468                    .with_args(vec![json!(1)])
11469                    .immutable(),
11470            )
11471            .then_signature(
11472                Signature::new("process".to_string())
11473                    .with_args(vec![json!(2)])
11474                    .immutable(),
11475            )
11476            .then_signature(Signature::new("finalize".to_string()));
11477
11478        let optimized = compiler.optimize_chain(&chain);
11479        // Task fusion should combine the two "process" tasks
11480        assert_eq!(optimized.tasks.len(), 2);
11481        assert_eq!(optimized.tasks[0].task, "process");
11482        assert_eq!(optimized.tasks[0].args.len(), 2); // Fused args
11483        assert_eq!(optimized.tasks[1].task, "finalize");
11484    }
11485
11486    #[test]
11487    fn test_workflow_compiler_parallel_scheduling() {
11488        // Test Parallel Scheduling for groups
11489        let compiler = WorkflowCompiler::new().add_pass(OptimizationPass::ParallelScheduling);
11490
11491        let group = Group::new()
11492            .add_signature(Signature::new("task1".to_string()).with_priority(1))
11493            .add_signature(Signature::new("task2".to_string()).with_priority(5))
11494            .add_signature(Signature::new("task3".to_string()).with_priority(3));
11495
11496        let optimized = compiler.optimize_group(&group);
11497        // Parallel scheduling should sort by priority (highest first)
11498        assert_eq!(optimized.tasks.len(), 3);
11499        assert_eq!(optimized.tasks[0].options.priority, Some(5));
11500        assert_eq!(optimized.tasks[1].options.priority, Some(3));
11501        assert_eq!(optimized.tasks[2].options.priority, Some(1));
11502    }
11503
11504    #[test]
11505    fn test_workflow_compiler_resource_optimization() {
11506        // Test Resource Optimization for groups
11507        let compiler = WorkflowCompiler::new().add_pass(OptimizationPass::ResourceOptimization);
11508
11509        let group = Group::new()
11510            .add_signature(Signature::new("task1".to_string()).with_queue("queue_b".to_string()))
11511            .add_signature(Signature::new("task2".to_string()).with_queue("queue_a".to_string()))
11512            .add_signature(Signature::new("task3".to_string()).with_queue("queue_a".to_string()));
11513
11514        let optimized = compiler.optimize_group(&group);
11515        // Resource optimization should sort by queue
11516        assert_eq!(optimized.tasks.len(), 3);
11517        assert_eq!(
11518            optimized.tasks[0].options.queue.as_ref().unwrap(),
11519            "queue_a"
11520        );
11521        assert_eq!(
11522            optimized.tasks[1].options.queue.as_ref().unwrap(),
11523            "queue_a"
11524        );
11525        assert_eq!(
11526            optimized.tasks[2].options.queue.as_ref().unwrap(),
11527            "queue_b"
11528        );
11529    }
11530
11531    #[test]
11532    fn test_workflow_compiler_optimize_chord() {
11533        use serde_json::json;
11534
11535        // Test chord optimization
11536        let compiler = WorkflowCompiler::new().aggressive();
11537
11538        let group = Group::new()
11539            .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11540            .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)])) // Duplicate
11541            .add_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]));
11542
11543        let chord = Chord::new(group, Signature::new("callback".to_string()));
11544        let optimized = compiler.optimize_chord(&chord);
11545
11546        // CSE should remove duplicates from the chord's header group
11547        assert_eq!(optimized.header.tasks.len(), 2);
11548        assert_eq!(optimized.body.task, "callback");
11549    }
11550
11551    #[test]
11552    fn test_workflow_compiler_combined_passes() {
11553        use serde_json::json;
11554
11555        // Test multiple optimization passes together
11556        let compiler = WorkflowCompiler::new()
11557            .aggressive()
11558            .add_pass(OptimizationPass::ParallelScheduling);
11559
11560        let group = Group::new()
11561            .add_signature(
11562                Signature::new("task1".to_string())
11563                    .with_priority(1)
11564                    .with_args(vec![json!(1)]),
11565            )
11566            .add_signature(Signature::new("".to_string())) // Dead code
11567            .add_signature(
11568                Signature::new("task2".to_string())
11569                    .with_priority(5)
11570                    .with_args(vec![json!(2)]),
11571            )
11572            .add_signature(
11573                Signature::new("task1".to_string())
11574                    .with_priority(1)
11575                    .with_args(vec![json!(1)]),
11576            ); // Duplicate
11577
11578        let optimized = compiler.optimize_group(&group);
11579        // Should remove dead code, remove duplicate, and sort by priority
11580        assert_eq!(optimized.tasks.len(), 2);
11581        assert_eq!(optimized.tasks[0].options.priority, Some(5));
11582        assert_eq!(optimized.tasks[0].task, "task2");
11583        assert_eq!(optimized.tasks[1].options.priority, Some(1));
11584        assert_eq!(optimized.tasks[1].task, "task1");
11585    }
11586
11587    #[test]
11588    fn test_typed_result() {
11589        let result = TypedResult::new(42i32).with_metadata("source", serde_json::json!("test"));
11590
11591        assert_eq!(result.value, 42);
11592        assert_eq!(result.type_name(), "i32");
11593        assert!(result.metadata.contains_key("source"));
11594
11595        let display = format!("{}", result);
11596        assert!(display.contains("TypedResult"));
11597        assert!(display.contains("i32"));
11598    }
11599
11600    #[test]
11601    fn test_type_validator() {
11602        let validator = TypeValidator::new("i32");
11603        assert!(validator.validate("i32"));
11604        assert!(!validator.validate("String"));
11605
11606        let compatible = TypeValidator::new("serde_json::Value").allow_compatible();
11607        assert!(compatible.validate("i32"));
11608        assert!(compatible.validate("String"));
11609        assert!(compatible.allow_compatible);
11610
11611        let display = format!("{}", validator);
11612        assert!(display.contains("TypeValidator"));
11613        assert!(display.contains("i32"));
11614    }
11615
11616    #[test]
11617    fn test_task_dependency() {
11618        let task_id = Uuid::new_v4();
11619        let dep = TaskDependency::new(task_id)
11620            .with_output_key("result")
11621            .optional();
11622
11623        assert_eq!(dep.task_id, task_id);
11624        assert_eq!(dep.output_key, Some("result".to_string()));
11625        assert!(dep.optional);
11626
11627        let display = format!("{}", dep);
11628        assert!(display.contains("TaskDependency"));
11629        assert!(display.contains(&task_id.to_string()));
11630        assert!(display.contains("result"));
11631    }
11632
11633    #[test]
11634    fn test_dependency_graph() {
11635        let mut graph = DependencyGraph::new();
11636        let task1 = Uuid::new_v4();
11637        let task2 = Uuid::new_v4();
11638        let task3 = Uuid::new_v4();
11639
11640        graph.add_dependency(task2, TaskDependency::new(task1));
11641        graph.add_dependency(task3, TaskDependency::new(task2));
11642
11643        assert_eq!(graph.get_dependencies(&task2).len(), 1);
11644        assert_eq!(graph.get_dependents(&task1).len(), 1);
11645        assert!(!graph.has_circular_dependency());
11646
11647        let sorted = graph.topological_sort().unwrap();
11648        assert_eq!(sorted.len(), 3);
11649
11650        let display = format!("{}", graph);
11651        assert!(display.contains("DependencyGraph"));
11652        assert!(display.contains("2 tasks")); // Only task2 and task3 have dependencies
11653    }
11654
11655    #[test]
11656    fn test_circular_dependency() {
11657        let mut graph = DependencyGraph::new();
11658        let task1 = Uuid::new_v4();
11659        let task2 = Uuid::new_v4();
11660
11661        graph.add_dependency(task1, TaskDependency::new(task2));
11662        graph.add_dependency(task2, TaskDependency::new(task1));
11663
11664        assert!(graph.has_circular_dependency());
11665        assert!(graph.topological_sort().is_err());
11666    }
11667
11668    #[test]
11669    fn test_parallel_reduce() {
11670        let map_task = Signature::new("map".to_string());
11671        let reduce_task = Signature::new("reduce".to_string());
11672        let inputs = vec![
11673            serde_json::json!(1),
11674            serde_json::json!(2),
11675            serde_json::json!(3),
11676        ];
11677
11678        let pr = ParallelReduce::new(map_task, reduce_task, inputs)
11679            .with_parallelism(8)
11680            .with_initial_value(serde_json::json!(0));
11681
11682        assert_eq!(pr.parallelism, 8);
11683        assert_eq!(pr.input_count(), 3);
11684        assert!(!pr.is_empty());
11685        assert!(pr.initial_value.is_some());
11686
11687        let display = format!("{}", pr);
11688        assert!(display.contains("ParallelReduce"));
11689        assert!(display.contains("map"));
11690        assert!(display.contains("reduce"));
11691        assert!(display.contains("parallelism=8"));
11692    }
11693
11694    #[test]
11695    fn test_template_parameter() {
11696        let param = TemplateParameter::new("count", "usize")
11697            .with_default(serde_json::json!(10))
11698            .with_description("Number of items");
11699
11700        assert_eq!(param.name, "count");
11701        assert_eq!(param.param_type, "usize");
11702        assert!(!param.required);
11703        assert!(param.default.is_some());
11704        assert!(param.description.is_some());
11705
11706        let display = format!("{}", param);
11707        assert!(display.contains("count:usize"));
11708        assert!(display.contains("optional"));
11709    }
11710
11711    #[test]
11712    fn test_workflow_template() {
11713        let param =
11714            TemplateParameter::new("queue", "String").with_default(serde_json::json!("default"));
11715
11716        let template = WorkflowTemplate::new("etl_pipeline", "1.0")
11717            .add_parameter(param)
11718            .with_description("ETL workflow template")
11719            .with_chain(Chain::new().then("extract", vec![]));
11720
11721        assert_eq!(template.name, "etl_pipeline");
11722        assert_eq!(template.version, "1.0");
11723        assert_eq!(template.parameters.len(), 1);
11724        assert!(template.chain.is_some());
11725        assert!(template.description.is_some());
11726
11727        // Test instantiation with valid parameters
11728        let mut params = HashMap::new();
11729        params.insert("queue".to_string(), serde_json::json!("custom"));
11730        let instance = template.instantiate(params).unwrap();
11731        assert_eq!(instance.name, "etl_pipeline");
11732
11733        let display = format!("{}", template);
11734        assert!(display.contains("WorkflowTemplate"));
11735        assert!(display.contains("etl_pipeline@1.0"));
11736    }
11737
11738    #[test]
11739    fn test_workflow_template_validation() {
11740        let required_param = TemplateParameter::new("api_key", "String");
11741        let template = WorkflowTemplate::new("api_workflow", "1.0").add_parameter(required_param);
11742
11743        // Should fail without required parameter
11744        let result = template.instantiate(HashMap::new());
11745        assert!(result.is_err());
11746    }
11747
11748    #[test]
11749    fn test_workflow_event() {
11750        let task_id = Uuid::new_v4();
11751        let workflow_id = Uuid::new_v4();
11752
11753        let task_completed = WorkflowEvent::TaskCompleted { task_id };
11754        assert_eq!(
11755            format!("{}", task_completed),
11756            format!("TaskCompleted[{}]", task_id)
11757        );
11758
11759        let task_failed = WorkflowEvent::TaskFailed {
11760            task_id,
11761            error: "test error".to_string(),
11762        };
11763        assert!(format!("{}", task_failed).contains("TaskFailed"));
11764
11765        let workflow_started = WorkflowEvent::WorkflowStarted { workflow_id };
11766        assert!(format!("{}", workflow_started).contains("WorkflowStarted"));
11767
11768        let custom = WorkflowEvent::Custom {
11769            event_type: "data_updated".to_string(),
11770            data: "{}".to_string(),
11771        };
11772        assert!(format!("{}", custom).contains("Custom"));
11773        assert!(format!("{}", custom).contains("data_updated"));
11774    }
11775
11776    #[test]
11777    fn test_event_handler() {
11778        let handler_task = Signature::new("handle_completion".to_string());
11779        let handler = EventHandler::new("TaskCompleted", handler_task)
11780            .with_filter("task_type == 'important'");
11781
11782        assert_eq!(handler.event_type, "TaskCompleted");
11783        assert_eq!(handler.handler_task.task, "handle_completion");
11784        assert!(handler.filter.is_some());
11785
11786        let display = format!("{}", handler);
11787        assert!(display.contains("EventHandler"));
11788        assert!(display.contains("TaskCompleted"));
11789        assert!(display.contains("handle_completion"));
11790    }
11791
11792    #[test]
11793    fn test_event_driven_workflow() {
11794        let handler1 =
11795            EventHandler::new("TaskCompleted", Signature::new("on_complete".to_string()));
11796        let handler2 = EventHandler::new("TaskFailed", Signature::new("on_fail".to_string()));
11797
11798        let workflow = EventDrivenWorkflow::new()
11799            .on_event(handler1)
11800            .on_event(handler2)
11801            .on_task_completed(Signature::new("notify".to_string()));
11802
11803        assert!(workflow.active);
11804        assert_eq!(workflow.handlers.len(), 3);
11805        assert!(workflow.has_handlers());
11806
11807        let deactivated = workflow.clone().deactivate();
11808        assert!(!deactivated.active);
11809
11810        let display = format!("{}", workflow);
11811        assert!(display.contains("EventDrivenWorkflow"));
11812        assert!(display.contains("handlers=3"));
11813    }
11814
11815    #[test]
11816    fn test_state_version() {
11817        let v1 = StateVersion::new(1, 0, 0);
11818        let v2 = StateVersion::new(1, 1, 0);
11819        let v3 = StateVersion::new(2, 0, 0);
11820
11821        // Same major version is compatible
11822        assert!(v1.is_compatible(&v2));
11823        // Different major version is not compatible
11824        assert!(!v1.is_compatible(&v3));
11825
11826        // Display
11827        assert_eq!(format!("{}", v1), "1.0.0");
11828        assert_eq!(format!("{}", v2), "1.1.0");
11829
11830        // Current version
11831        let current = StateVersion::current();
11832        assert_eq!(current.major, 1);
11833        assert_eq!(current.minor, 0);
11834        assert_eq!(current.patch, 0);
11835    }
11836
11837    #[test]
11838    fn test_state_migration_error() {
11839        let v1 = StateVersion::new(1, 0, 0);
11840        let v2 = StateVersion::new(2, 0, 0);
11841
11842        let err = StateMigrationError::IncompatibleVersion { from: v1, to: v2 };
11843        let display = format!("{}", err);
11844        assert!(display.contains("Incompatible"));
11845        assert!(display.contains("1.0.0"));
11846        assert!(display.contains("2.0.0"));
11847
11848        let err2 = StateMigrationError::MigrationFailed("test error".to_string());
11849        assert!(format!("{}", err2).contains("migration failed"));
11850
11851        let err3 = StateMigrationError::UnsupportedVersion(v1);
11852        assert!(format!("{}", err3).contains("Unsupported"));
11853    }
11854
11855    #[test]
11856    fn test_versioned_workflow_state() {
11857        let workflow_id = Uuid::new_v4();
11858        let state = WorkflowState::new(workflow_id, 5);
11859        let mut versioned = VersionedWorkflowState::new(state);
11860
11861        // Check initial version
11862        assert_eq!(versioned.version, StateVersion::current());
11863        assert_eq!(versioned.migration_history.len(), 0);
11864
11865        // Migrate to compatible version
11866        let target = StateVersion::new(1, 1, 0);
11867        assert!(versioned.can_migrate_to(&target));
11868        assert!(versioned.migrate_to(target).is_ok());
11869        assert_eq!(versioned.version, target);
11870        assert_eq!(versioned.migration_history.len(), 1);
11871
11872        // Migrate to same version is OK
11873        assert!(versioned.migrate_to(target).is_ok());
11874        assert_eq!(versioned.migration_history.len(), 1);
11875
11876        // Migrate to incompatible version fails
11877        let incompatible = StateVersion::new(2, 0, 0);
11878        assert!(!versioned.can_migrate_to(&incompatible));
11879        assert!(versioned.migrate_to(incompatible).is_err());
11880    }
11881
11882    #[test]
11883    fn test_task_priority() {
11884        let low = TaskPriority::Low;
11885        let normal = TaskPriority::Normal;
11886        let high = TaskPriority::High;
11887        let critical = TaskPriority::Critical;
11888
11889        // Test ordering
11890        assert!(low < normal);
11891        assert!(normal < high);
11892        assert!(high < critical);
11893
11894        // Test display
11895        assert_eq!(format!("{}", low), "Low");
11896        assert_eq!(format!("{}", normal), "Normal");
11897        assert_eq!(format!("{}", high), "High");
11898        assert_eq!(format!("{}", critical), "Critical");
11899
11900        // Test default
11901        assert_eq!(TaskPriority::default(), TaskPriority::Normal);
11902    }
11903
11904    #[test]
11905    fn test_worker_capacity() {
11906        let mut worker = WorkerCapacity::new("worker1", 4, 8192);
11907
11908        assert_eq!(worker.worker_id, "worker1");
11909        assert_eq!(worker.cpu_cores, 4);
11910        assert_eq!(worker.memory_mb, 8192);
11911        assert_eq!(worker.current_load, 0.0);
11912        assert_eq!(worker.active_tasks, 0);
11913
11914        // Test capacity checks
11915        assert!(worker.has_capacity(0.5));
11916        assert!(worker.has_capacity(1.0));
11917        assert!(!worker.has_capacity(1.1));
11918
11919        // Test available capacity
11920        assert_eq!(worker.available_capacity(), 1.0);
11921        worker.current_load = 0.3;
11922        assert_eq!(worker.available_capacity(), 0.7);
11923    }
11924
11925    #[test]
11926    fn test_scheduling_decision() {
11927        let task_id = Uuid::new_v4();
11928        let decision = SchedulingDecision::new(task_id, "worker1", TaskPriority::High)
11929            .with_estimated_time(120);
11930
11931        assert_eq!(decision.task_id, task_id);
11932        assert_eq!(decision.worker_id, "worker1");
11933        assert_eq!(decision.priority, TaskPriority::High);
11934        assert_eq!(decision.estimated_time, Some(120));
11935    }
11936
11937    #[test]
11938    fn test_scheduling_strategy() {
11939        let rr = SchedulingStrategy::RoundRobin;
11940        let ll = SchedulingStrategy::LeastLoaded;
11941        let pb = SchedulingStrategy::PriorityBased;
11942        let ra = SchedulingStrategy::ResourceAware;
11943
11944        assert_eq!(format!("{}", rr), "RoundRobin");
11945        assert_eq!(format!("{}", ll), "LeastLoaded");
11946        assert_eq!(format!("{}", pb), "PriorityBased");
11947        assert_eq!(format!("{}", ra), "ResourceAware");
11948
11949        assert_eq!(
11950            SchedulingStrategy::default(),
11951            SchedulingStrategy::LeastLoaded
11952        );
11953    }
11954
11955    #[test]
11956    fn test_parallel_scheduler_round_robin() {
11957        let mut scheduler = ParallelScheduler::new(SchedulingStrategy::RoundRobin);
11958
11959        // Add workers
11960        scheduler.add_worker(WorkerCapacity::new("worker1", 4, 8192));
11961        scheduler.add_worker(WorkerCapacity::new("worker2", 4, 8192));
11962
11963        assert_eq!(scheduler.worker_count(), 2);
11964
11965        // Schedule tasks
11966        let task1 = Uuid::new_v4();
11967        let decision = scheduler.schedule_task(task1, TaskPriority::Normal);
11968        assert!(decision.is_some());
11969    }
11970
11971    #[test]
11972    fn test_parallel_scheduler_least_loaded() {
11973        let mut scheduler = ParallelScheduler::new(SchedulingStrategy::LeastLoaded);
11974
11975        let mut worker1 = WorkerCapacity::new("worker1", 4, 8192);
11976        worker1.current_load = 0.8;
11977        let mut worker2 = WorkerCapacity::new("worker2", 4, 8192);
11978        worker2.current_load = 0.3;
11979
11980        scheduler.add_worker(worker1);
11981        scheduler.add_worker(worker2);
11982
11983        // Should assign to worker2 (lower load)
11984        let task = Uuid::new_v4();
11985        let decision = scheduler.schedule_task(task, TaskPriority::Normal);
11986        assert!(decision.is_some());
11987        assert_eq!(decision.unwrap().worker_id, "worker2");
11988    }
11989
11990    #[test]
11991    fn test_parallel_scheduler_metrics() {
11992        let mut scheduler = ParallelScheduler::new(SchedulingStrategy::LeastLoaded);
11993
11994        let mut worker1 = WorkerCapacity::new("worker1", 4, 8192);
11995        worker1.current_load = 0.5;
11996        let mut worker2 = WorkerCapacity::new("worker2", 4, 8192);
11997        worker2.current_load = 0.3;
11998
11999        scheduler.add_worker(worker1);
12000        scheduler.add_worker(worker2);
12001
12002        // Test metrics
12003        assert_eq!(scheduler.worker_count(), 2);
12004        assert_eq!(scheduler.average_load(), 0.4);
12005        assert_eq!(scheduler.total_capacity(), 1.2);
12006
12007        let display = format!("{}", scheduler);
12008        assert!(display.contains("ParallelScheduler"));
12009        assert!(display.contains("workers=2"));
12010    }
12011
12012    #[test]
12013    fn test_parallel_scheduler_max_tasks() {
12014        let mut scheduler =
12015            ParallelScheduler::new(SchedulingStrategy::LeastLoaded).with_max_tasks_per_worker(2);
12016
12017        let mut worker = WorkerCapacity::new("worker1", 4, 8192);
12018        worker.active_tasks = 2;
12019        scheduler.add_worker(worker);
12020
12021        // Should not schedule (max reached)
12022        let task = Uuid::new_v4();
12023        let decision = scheduler.schedule_task(task, TaskPriority::Normal);
12024        assert!(decision.is_none());
12025    }
12026
12027    #[test]
12028    fn test_workflow_batch() {
12029        let mut batch = WorkflowBatch::new(5);
12030
12031        assert!(batch.is_empty());
12032        assert!(!batch.is_full());
12033        assert_eq!(batch.size(), 0);
12034        assert_eq!(batch.max_size, 5);
12035
12036        // Add workflows
12037        let wf1 = Uuid::new_v4();
12038        let wf2 = Uuid::new_v4();
12039        assert!(batch.add_workflow(wf1));
12040        assert!(batch.add_workflow(wf2));
12041        assert_eq!(batch.size(), 2);
12042        assert!(!batch.is_empty());
12043        assert!(!batch.is_full());
12044
12045        // Fill batch
12046        for _ in 0..3 {
12047            batch.add_workflow(Uuid::new_v4());
12048        }
12049        assert!(batch.is_full());
12050
12051        // Cannot add more
12052        assert!(!batch.add_workflow(Uuid::new_v4()));
12053
12054        // Test display
12055        let display = format!("{}", batch);
12056        assert!(display.contains("WorkflowBatch"));
12057        assert!(display.contains("5/5"));
12058    }
12059
12060    #[test]
12061    fn test_workflow_batch_timeout() {
12062        let mut batch = WorkflowBatch::new(10).with_timeout(0);
12063
12064        // With 0 timeout, should be immediately timed out
12065        std::thread::sleep(std::time::Duration::from_millis(10));
12066        assert!(batch.is_timed_out());
12067
12068        // Batch without timeout never times out
12069        batch.timeout = None;
12070        assert!(!batch.is_timed_out());
12071    }
12072
12073    #[test]
12074    fn test_batching_strategy() {
12075        let by_type = BatchingStrategy::ByType;
12076        let by_priority = BatchingStrategy::ByPriority;
12077        let by_size = BatchingStrategy::BySize;
12078        let by_time = BatchingStrategy::ByTimeWindow;
12079
12080        assert_eq!(format!("{}", by_type), "ByType");
12081        assert_eq!(format!("{}", by_priority), "ByPriority");
12082        assert_eq!(format!("{}", by_size), "BySize");
12083        assert_eq!(format!("{}", by_time), "ByTimeWindow");
12084
12085        assert_eq!(BatchingStrategy::default(), BatchingStrategy::ByType);
12086    }
12087
12088    #[test]
12089    fn test_workflow_batcher() {
12090        let mut batcher = WorkflowBatcher::new(BatchingStrategy::ByType)
12091            .with_batch_size(3)
12092            .with_timeout(60);
12093
12094        assert_eq!(batcher.batch_count(), 0);
12095        assert_eq!(batcher.total_workflow_count(), 0);
12096
12097        // Add workflows
12098        let wf1 = Uuid::new_v4();
12099        let wf2 = Uuid::new_v4();
12100        let wf3 = Uuid::new_v4();
12101
12102        batcher.add_workflow(wf1, TaskPriority::Normal);
12103        assert_eq!(batcher.batch_count(), 1);
12104        assert_eq!(batcher.total_workflow_count(), 1);
12105
12106        batcher.add_workflow(wf2, TaskPriority::Normal);
12107        batcher.add_workflow(wf3, TaskPriority::Normal);
12108        assert_eq!(batcher.batch_count(), 1);
12109        assert_eq!(batcher.total_workflow_count(), 3);
12110
12111        // Batch should be full
12112        let ready = batcher.get_ready_batches();
12113        assert_eq!(ready.len(), 1);
12114        assert!(ready[0].is_full());
12115    }
12116
12117    #[test]
12118    fn test_workflow_batcher_by_priority() {
12119        let mut batcher = WorkflowBatcher::new(BatchingStrategy::ByPriority).with_batch_size(5);
12120
12121        // Add workflows with different priorities
12122        let wf1 = Uuid::new_v4();
12123        let wf2 = Uuid::new_v4();
12124        let wf3 = Uuid::new_v4();
12125
12126        batcher.add_workflow(wf1, TaskPriority::High);
12127        batcher.add_workflow(wf2, TaskPriority::Low);
12128        batcher.add_workflow(wf3, TaskPriority::High);
12129
12130        // Should create 2 batches (one for High, one for Low)
12131        assert_eq!(batcher.batch_count(), 2);
12132        assert_eq!(batcher.total_workflow_count(), 3);
12133    }
12134
12135    #[test]
12136    fn test_workflow_batcher_remove_ready() {
12137        let mut batcher = WorkflowBatcher::new(BatchingStrategy::ByType).with_batch_size(2);
12138
12139        // Add workflows to fill one batch
12140        batcher.add_workflow(Uuid::new_v4(), TaskPriority::Normal);
12141        batcher.add_workflow(Uuid::new_v4(), TaskPriority::Normal);
12142
12143        // Add one more to create a second batch
12144        batcher.add_workflow(Uuid::new_v4(), TaskPriority::Normal);
12145
12146        assert_eq!(batcher.batch_count(), 2);
12147
12148        // Remove ready batches (the full one)
12149        let ready = batcher.remove_ready_batches();
12150        assert_eq!(ready.len(), 1);
12151        assert_eq!(batcher.batch_count(), 1);
12152
12153        let display = format!("{}", batcher);
12154        assert!(display.contains("WorkflowBatcher"));
12155        assert!(display.contains("batches=1"));
12156    }
12157
12158    #[test]
12159    fn test_streaming_map_reduce() {
12160        let map_task = Signature::new("map_task".to_string());
12161        let reduce_task = Signature::new("reduce_task".to_string());
12162
12163        let stream = StreamingMapReduce::new(map_task, reduce_task)
12164            .with_chunk_size(50)
12165            .with_buffer_size(500)
12166            .with_backpressure(true);
12167
12168        assert_eq!(stream.chunk_size, 50);
12169        assert_eq!(stream.buffer_size, 500);
12170        assert!(stream.backpressure);
12171
12172        let display = format!("{}", stream);
12173        assert!(display.contains("StreamingMapReduce"));
12174        assert!(display.contains("map_task"));
12175        assert!(display.contains("reduce_task"));
12176        assert!(display.contains("chunk_size=50"));
12177        assert!(display.contains("buffer_size=500"));
12178    }
12179
12180    #[test]
12181    fn test_resource_utilization() {
12182        let util = ResourceUtilization::new(0.8, 0.6, 0.4, 0.2);
12183
12184        assert_eq!(util.cpu, 0.8);
12185        assert_eq!(util.memory, 0.6);
12186        assert_eq!(util.disk_io, 0.4);
12187        assert_eq!(util.network_io, 0.2);
12188
12189        // Test overall
12190        assert!((util.overall() - 0.5).abs() < 0.01);
12191
12192        // Test overload
12193        assert!(util.is_overloaded(0.7));
12194        assert!(!util.is_overloaded(0.9));
12195
12196        // Test bottleneck
12197        assert_eq!(util.bottleneck(), "cpu");
12198
12199        // Test clamping
12200        let util2 = ResourceUtilization::new(1.5, -0.5, 0.5, 0.5);
12201        assert_eq!(util2.cpu, 1.0);
12202        assert_eq!(util2.memory, 0.0);
12203    }
12204
12205    #[test]
12206    fn test_resource_utilization_display() {
12207        let util = ResourceUtilization::new(0.5, 0.6, 0.7, 0.8);
12208        let display = format!("{}", util);
12209        assert!(display.contains("ResourceUtilization"));
12210        assert!(display.contains("cpu=0.50"));
12211        assert!(display.contains("mem=0.60"));
12212    }
12213
12214    #[test]
12215    fn test_workflow_resource_monitor() {
12216        let workflow_id = Uuid::new_v4();
12217        let mut monitor = WorkflowResourceMonitor::new(workflow_id)
12218            .with_max_history(100)
12219            .with_sampling_interval(10);
12220
12221        assert_eq!(monitor.workflow_id, workflow_id);
12222        assert_eq!(monitor.max_history, 100);
12223        assert_eq!(monitor.sampling_interval, 10);
12224        assert_eq!(monitor.history.len(), 0);
12225
12226        // Record some utilization
12227        monitor.record(ResourceUtilization::new(0.5, 0.6, 0.4, 0.3));
12228        monitor.record(ResourceUtilization::new(0.7, 0.8, 0.6, 0.5));
12229
12230        assert_eq!(monitor.history.len(), 2);
12231
12232        // Test peak
12233        let peak = monitor.peak_utilization().unwrap();
12234        assert!(peak.overall() > 0.6);
12235
12236        // Test average
12237        let avg = monitor.average_utilization(3600).unwrap();
12238        assert!(avg.cpu > 0.5 && avg.cpu < 0.7);
12239
12240        // Test clear
12241        monitor.clear();
12242        assert_eq!(monitor.history.len(), 0);
12243    }
12244
12245    #[test]
12246    fn test_workflow_resource_monitor_max_history() {
12247        let workflow_id = Uuid::new_v4();
12248        let mut monitor = WorkflowResourceMonitor::new(workflow_id).with_max_history(3);
12249
12250        // Add more than max_history
12251        for i in 0..5 {
12252            monitor.record(ResourceUtilization::new(i as f64 * 0.1, 0.5, 0.5, 0.5));
12253        }
12254
12255        // Should only keep last 3
12256        assert_eq!(monitor.history.len(), 3);
12257
12258        let display = format!("{}", monitor);
12259        assert!(display.contains("WorkflowResourceMonitor"));
12260        assert!(display.contains("samples=3"));
12261    }
12262
12263    #[test]
12264    fn test_batching_strategy_display() {
12265        let strategy = BatchingStrategy::ByPriority;
12266        assert_eq!(format!("{}", strategy), "ByPriority");
12267    }
12268
12269    #[test]
12270    fn test_observable() {
12271        let mut obs = Observable::new(42);
12272        assert_eq!(*obs.get(), 42);
12273        assert_eq!(obs.subscriber_count(), 0);
12274
12275        // Subscribe workflows
12276        let wf1 = Uuid::new_v4();
12277        let wf2 = Uuid::new_v4();
12278        obs.subscribe(wf1);
12279        obs.subscribe(wf2);
12280        assert_eq!(obs.subscriber_count(), 2);
12281
12282        // Update value
12283        obs.set(100);
12284        assert_eq!(*obs.get(), 100);
12285        assert_eq!(obs.history.len(), 1);
12286
12287        // Unsubscribe
12288        obs.unsubscribe(&wf1);
12289        assert_eq!(obs.subscriber_count(), 1);
12290    }
12291
12292    #[test]
12293    fn test_reactive_workflow() {
12294        let reaction = Signature::new("on_change".to_string());
12295        let workflow = ReactiveWorkflow::new(reaction)
12296            .watch("observable1")
12297            .watch("observable2")
12298            .with_debounce(100)
12299            .with_throttle(500)
12300            .with_filter("value > 10");
12301
12302        assert_eq!(workflow.watched_observables.len(), 2);
12303        assert_eq!(workflow.debounce_ms, Some(100));
12304        assert_eq!(workflow.throttle_ms, Some(500));
12305        assert!(workflow.filter.is_some());
12306
12307        let display = format!("{}", workflow);
12308        assert!(display.contains("ReactiveWorkflow"));
12309        assert!(display.contains("watching=2"));
12310        assert!(display.contains("on_change"));
12311    }
12312
12313    #[test]
12314    fn test_stream_operator() {
12315        let map_op = StreamOperator::Map;
12316        let filter_op = StreamOperator::Filter;
12317        let debounce_op = StreamOperator::Debounce;
12318
12319        assert_eq!(format!("{}", map_op), "Map");
12320        assert_eq!(format!("{}", filter_op), "Filter");
12321        assert_eq!(format!("{}", debounce_op), "Debounce");
12322    }
12323
12324    #[test]
12325    fn test_reactive_stream() {
12326        let mut stream = ReactiveStream::new("source1")
12327            .map(serde_json::json!({"transform": "uppercase"}))
12328            .filter(serde_json::json!({"condition": "length > 5"}))
12329            .take(10)
12330            .skip(2)
12331            .debounce(100)
12332            .throttle(500);
12333
12334        assert_eq!(stream.source_id, "source1");
12335        assert_eq!(stream.operators.len(), 6);
12336
12337        // Subscribe workflow
12338        let wf = Uuid::new_v4();
12339        stream.subscribe(wf);
12340        assert_eq!(stream.subscribers.len(), 1);
12341
12342        let display = format!("{}", stream);
12343        assert!(display.contains("ReactiveStream"));
12344        assert!(display.contains("source=source1"));
12345        assert!(display.contains("operators=6"));
12346    }
12347
12348    #[test]
12349    fn test_mock_task_result() {
12350        let success =
12351            MockTaskResult::success("task1", serde_json::json!({"result": "ok"})).with_delay(50);
12352        assert!(!success.should_fail);
12353        assert_eq!(success.delay_ms, 50);
12354
12355        let failure = MockTaskResult::failure("task2", "Task failed");
12356        assert!(failure.should_fail);
12357        assert_eq!(failure.failure_message, Some("Task failed".to_string()));
12358    }
12359
12360    #[test]
12361    fn test_mock_task_executor() {
12362        let mut executor = MockTaskExecutor::new();
12363
12364        // Register mock results
12365        executor.register(MockTaskResult::success("task1", serde_json::json!(42)));
12366        executor.register(MockTaskResult::failure("task2", "Error"));
12367
12368        // Execute successful task
12369        let result1 = executor.execute("task1");
12370        assert!(result1.is_ok());
12371        assert_eq!(result1.unwrap(), serde_json::json!(42));
12372
12373        // Execute failing task
12374        let result2 = executor.execute("task2");
12375        assert!(result2.is_err());
12376
12377        // Execute unregistered task
12378        let result3 = executor.execute("task3");
12379        assert!(result3.is_err());
12380
12381        // Check execution count
12382        assert_eq!(executor.execution_count("task1"), 1);
12383        assert_eq!(executor.execution_count("task2"), 1);
12384
12385        // Clear history
12386        executor.clear_history();
12387        assert_eq!(executor.execution_count("task1"), 0);
12388    }
12389
12390    #[test]
12391    fn test_test_data_injector() {
12392        let mut injector = TestDataInjector::new();
12393
12394        // Inject data
12395        injector.inject("key1", serde_json::json!({"value": 123}));
12396        injector.inject("key2", serde_json::json!("test"));
12397
12398        // Get data
12399        assert!(injector.get("key1").is_some());
12400        assert_eq!(injector.get("key2"), Some(&serde_json::json!("test")));
12401        assert!(injector.get("key3").is_none());
12402
12403        // Clear data
12404        injector.clear();
12405        assert!(injector.get("key1").is_none());
12406    }
12407
12408    #[test]
12409    fn test_workflow_snapshot() {
12410        let workflow_id = Uuid::new_v4();
12411        let state = WorkflowState::new(workflow_id, 5);
12412        let mut snapshot = WorkflowSnapshot::new(workflow_id, state);
12413
12414        assert_eq!(snapshot.workflow_id, workflow_id);
12415        assert_eq!(snapshot.completed_tasks.len(), 0);
12416
12417        // Record task
12418        let task_id = Uuid::new_v4();
12419        snapshot.record_task(task_id, serde_json::json!({"result": "ok"}));
12420
12421        assert_eq!(snapshot.completed_tasks.len(), 1);
12422        assert!(snapshot.task_results.contains_key(&task_id));
12423
12424        // Attach checkpoint
12425        let checkpoint = WorkflowCheckpoint::new(workflow_id, WorkflowState::new(workflow_id, 5));
12426        snapshot = snapshot.with_checkpoint(checkpoint);
12427        assert!(snapshot.checkpoint.is_some());
12428    }
12429
12430    #[test]
12431    fn test_time_travel_debugger() {
12432        let workflow_id = Uuid::new_v4();
12433        let mut debugger = TimeTravelDebugger::new(workflow_id);
12434
12435        assert_eq!(debugger.snapshot_count(), 0);
12436        assert!(!debugger.step_mode);
12437
12438        // Record snapshots
12439        let snapshot1 = WorkflowSnapshot::new(workflow_id, WorkflowState::new(workflow_id, 5));
12440        let snapshot2 = WorkflowSnapshot::new(workflow_id, WorkflowState::new(workflow_id, 5));
12441        debugger.record_snapshot(snapshot1);
12442        debugger.record_snapshot(snapshot2);
12443
12444        assert_eq!(debugger.snapshot_count(), 2);
12445        assert_eq!(debugger.current_index, 1);
12446
12447        // Step backward
12448        let snapshot = debugger.step_backward();
12449        assert!(snapshot.is_some());
12450        assert_eq!(debugger.current_index, 0);
12451
12452        // Step forward
12453        let snapshot = debugger.step_forward();
12454        assert!(snapshot.is_some());
12455        assert_eq!(debugger.current_index, 1);
12456
12457        // Replay from specific point
12458        let snapshot = debugger.replay_from(0);
12459        assert!(snapshot.is_some());
12460        assert_eq!(debugger.current_index, 0);
12461
12462        // Enable step mode
12463        debugger.enable_step_mode();
12464        assert!(debugger.step_mode);
12465
12466        // Test display
12467        let display = format!("{}", debugger);
12468        assert!(display.contains("TimeTravelDebugger"));
12469        assert!(display.contains("snapshots=2"));
12470
12471        // Clear snapshots
12472        debugger.clear();
12473        assert_eq!(debugger.snapshot_count(), 0);
12474    }
12475
12476    // ===== Integration Tests =====
12477
12478    /// Integration tests for broker, backend, chord barriers, and performance
12479    mod integration {
12480        use super::*;
12481        use std::sync::atomic::{AtomicUsize, Ordering};
12482        use std::sync::Arc;
12483        use std::time::{Duration, Instant};
12484
12485        /// Mock broker for testing
12486        #[derive(Clone)]
12487        struct MockBroker {
12488            tasks: Arc<std::sync::Mutex<Vec<String>>>,
12489        }
12490
12491        impl MockBroker {
12492            fn new() -> Self {
12493                Self {
12494                    tasks: Arc::new(std::sync::Mutex::new(Vec::new())),
12495                }
12496            }
12497
12498            fn enqueued_tasks(&self) -> Vec<String> {
12499                self.tasks.lock().unwrap().clone()
12500            }
12501
12502            fn task_count(&self) -> usize {
12503                self.tasks.lock().unwrap().len()
12504            }
12505        }
12506
12507        #[async_trait::async_trait]
12508        impl celers_core::Broker for MockBroker {
12509            async fn enqueue(
12510                &self,
12511                task: celers_core::SerializedTask,
12512            ) -> celers_core::Result<celers_core::TaskId> {
12513                let task_name = task.metadata.name.clone();
12514                let task_id = task.metadata.id;
12515                self.tasks.lock().unwrap().push(task_name);
12516                Ok(task_id)
12517            }
12518
12519            async fn dequeue(&self) -> celers_core::Result<Option<celers_core::BrokerMessage>> {
12520                Ok(None)
12521            }
12522
12523            async fn ack(
12524                &self,
12525                _task_id: &celers_core::TaskId,
12526                _receipt_handle: Option<&str>,
12527            ) -> celers_core::Result<()> {
12528                Ok(())
12529            }
12530
12531            async fn reject(
12532                &self,
12533                _task_id: &celers_core::TaskId,
12534                _receipt_handle: Option<&str>,
12535                _requeue: bool,
12536            ) -> celers_core::Result<()> {
12537                Ok(())
12538            }
12539
12540            async fn queue_size(&self) -> celers_core::Result<usize> {
12541                Ok(self.tasks.lock().unwrap().len())
12542            }
12543
12544            async fn cancel(&self, _task_id: &celers_core::TaskId) -> celers_core::Result<bool> {
12545                Ok(true)
12546            }
12547        }
12548
12549        // ===== Broker Integration Tests =====
12550
12551        #[tokio::test]
12552        async fn test_chain_broker_integration() {
12553            let broker = MockBroker::new();
12554
12555            let chain = Chain::new()
12556                .then("task1", vec![serde_json::json!(1)])
12557                .then("task2", vec![serde_json::json!(2)])
12558                .then("task3", vec![serde_json::json!(3)]);
12559
12560            // Apply the chain
12561            let result = chain.apply(&broker).await;
12562            assert!(result.is_ok(), "Chain apply should succeed");
12563
12564            // Verify only the first task was published
12565            // (subsequent tasks are linked via callback mechanism)
12566            let tasks = broker.enqueued_tasks();
12567            assert_eq!(tasks.len(), 1, "Chain should publish only first task");
12568            assert!(tasks.contains(&"task1".to_string()));
12569        }
12570
12571        #[tokio::test]
12572        async fn test_group_broker_integration() {
12573            let broker = MockBroker::new();
12574
12575            let group = Group::new()
12576                .add("task1", vec![serde_json::json!(1)])
12577                .add("task2", vec![serde_json::json!(2)])
12578                .add("task3", vec![serde_json::json!(3)]);
12579
12580            // Apply the group
12581            let result = group.apply(&broker).await;
12582            assert!(result.is_ok(), "Group apply should succeed");
12583
12584            // Verify all tasks were published in parallel
12585            let tasks = broker.enqueued_tasks();
12586            assert_eq!(tasks.len(), 3, "Should publish 3 tasks");
12587        }
12588
12589        #[tokio::test]
12590        async fn test_map_broker_integration() {
12591            let broker = MockBroker::new();
12592
12593            let map = Map::new(
12594                Signature::new("process".to_string()),
12595                vec![
12596                    vec![serde_json::json!(1)],
12597                    vec![serde_json::json!(2)],
12598                    vec![serde_json::json!(3)],
12599                ],
12600            );
12601
12602            let result = map.apply(&broker).await;
12603            assert!(result.is_ok(), "Map apply should succeed");
12604
12605            // Verify all task instances were published
12606            let tasks = broker.enqueued_tasks();
12607            assert_eq!(tasks.len(), 3, "Should publish 3 task instances");
12608            assert_eq!(tasks.iter().filter(|t| *t == "process").count(), 3);
12609        }
12610
12611        #[tokio::test]
12612        async fn test_nested_workflow_broker_integration() {
12613            let broker = MockBroker::new();
12614
12615            // Create nested workflows
12616            let inner_group1 = Group::new()
12617                .add("task1", vec![serde_json::json!(1)])
12618                .add("task2", vec![serde_json::json!(2)]);
12619
12620            let inner_group2 = Group::new()
12621                .add("task3", vec![serde_json::json!(3)])
12622                .add("task4", vec![serde_json::json!(4)]);
12623
12624            let _ = inner_group1.apply(&broker).await;
12625            let _ = inner_group2.apply(&broker).await;
12626
12627            assert_eq!(broker.task_count(), 4, "Should publish all nested tasks");
12628        }
12629
12630        // ===== Backend Integration Tests =====
12631
12632        #[cfg(feature = "backend-redis")]
12633        #[tokio::test]
12634        async fn test_chord_backend_integration() {
12635            let group = Group::new()
12636                .add("task1", vec![serde_json::json!(1)])
12637                .add("task2", vec![serde_json::json!(2)]);
12638            let callback = Signature::new("aggregate".to_string());
12639            let chord = Chord::new(group, callback);
12640
12641            assert_eq!(chord.header.tasks.len(), 2);
12642            assert_eq!(chord.body.task, "aggregate");
12643        }
12644
12645        #[cfg(feature = "backend-redis")]
12646        #[tokio::test]
12647        async fn test_chord_state_tracking() {
12648            let chord_id = Uuid::new_v4();
12649            let mut group = Group::new();
12650            group.group_id = Some(chord_id);
12651            let group = group
12652                .add("task1", vec![serde_json::json!(1)])
12653                .add("task2", vec![serde_json::json!(2)]);
12654            let callback = Signature::new("aggregate".to_string());
12655            let chord = Chord::new(group, callback);
12656
12657            assert_eq!(chord.header.group_id, Some(chord_id));
12658            assert_eq!(chord.header.tasks.len(), 2);
12659        }
12660
12661        // ===== Chord Barrier Race Condition Tests =====
12662
12663        #[tokio::test]
12664        async fn test_chord_concurrent_completion() {
12665            let counter = Arc::new(AtomicUsize::new(0));
12666            let barrier = Arc::new(tokio::sync::Barrier::new(10));
12667
12668            let mut handles = vec![];
12669
12670            // Simulate 10 tasks completing concurrently
12671            for _ in 0..10 {
12672                let counter = counter.clone();
12673                let barrier = barrier.clone();
12674
12675                let handle = tokio::spawn(async move {
12676                    // Wait for all tasks to be ready
12677                    barrier.wait().await;
12678
12679                    // Simulate task completion and counter increment (like Redis INCR)
12680                    let old = counter.fetch_add(1, Ordering::SeqCst);
12681                    old + 1
12682                });
12683
12684                handles.push(handle);
12685            }
12686
12687            // Wait for all tasks
12688            let mut results = vec![];
12689            for handle in handles {
12690                results.push(handle.await.unwrap());
12691            }
12692
12693            // Verify no duplicates (each increment should be unique)
12694            results.sort();
12695            assert_eq!(results, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
12696            assert_eq!(counter.load(Ordering::SeqCst), 10);
12697        }
12698
12699        #[tokio::test]
12700        async fn test_chord_barrier_idempotency() {
12701            // Test that callback is triggered exactly once even with race conditions
12702            let callback_count = Arc::new(AtomicUsize::new(0));
12703            let completed_count = Arc::new(AtomicUsize::new(0));
12704            let total_tasks = 5;
12705
12706            let mut handles = vec![];
12707
12708            for _ in 0..total_tasks {
12709                let callback_count = callback_count.clone();
12710                let completed_count = completed_count.clone();
12711
12712                let handle = tokio::spawn(async move {
12713                    // Simulate task completion
12714                    let count = completed_count.fetch_add(1, Ordering::SeqCst) + 1;
12715
12716                    // Only the last task should trigger callback
12717                    if count == total_tasks {
12718                        callback_count.fetch_add(1, Ordering::SeqCst);
12719                    }
12720                });
12721
12722                handles.push(handle);
12723            }
12724
12725            for handle in handles {
12726                handle.await.unwrap();
12727            }
12728
12729            // Verify callback was triggered exactly once
12730            assert_eq!(callback_count.load(Ordering::SeqCst), 1);
12731            assert_eq!(completed_count.load(Ordering::SeqCst), total_tasks);
12732        }
12733
12734        #[tokio::test]
12735        async fn test_chord_partial_failure_handling() {
12736            // Test chord behavior when some tasks fail
12737            let success_count = Arc::new(AtomicUsize::new(0));
12738            let failure_count = Arc::new(AtomicUsize::new(0));
12739
12740            let mut handles = vec![];
12741
12742            for i in 0..10 {
12743                let success_count = success_count.clone();
12744                let failure_count = failure_count.clone();
12745
12746                let handle = tokio::spawn(async move {
12747                    if i % 3 == 0 {
12748                        // Simulate failure
12749                        failure_count.fetch_add(1, Ordering::SeqCst);
12750                        Err::<(), &str>("Task failed")
12751                    } else {
12752                        // Simulate success
12753                        success_count.fetch_add(1, Ordering::SeqCst);
12754                        Ok(())
12755                    }
12756                });
12757
12758                handles.push(handle);
12759            }
12760
12761            for handle in handles {
12762                let _ = handle.await.unwrap();
12763            }
12764
12765            let success = success_count.load(Ordering::SeqCst);
12766            let failure = failure_count.load(Ordering::SeqCst);
12767
12768            assert_eq!(success + failure, 10);
12769            assert!(failure > 0, "Should have some failures");
12770        }
12771
12772        // ===== Performance Tests =====
12773
12774        #[test]
12775        fn test_chain_creation_performance() {
12776            let start = Instant::now();
12777
12778            for _ in 0..1000 {
12779                let _ = Chain::new()
12780                    .then("task1", vec![serde_json::json!(1)])
12781                    .then("task2", vec![serde_json::json!(2)])
12782                    .then("task3", vec![serde_json::json!(3)]);
12783            }
12784
12785            let duration = start.elapsed();
12786            assert!(
12787                duration < Duration::from_millis(100),
12788                "Creating 1000 chains should take less than 100ms, took {:?}",
12789                duration
12790            );
12791        }
12792
12793        #[test]
12794        fn test_group_creation_performance() {
12795            let start = Instant::now();
12796
12797            for _ in 0..1000 {
12798                let _ = Group::new()
12799                    .add("task1", vec![serde_json::json!(1)])
12800                    .add("task2", vec![serde_json::json!(2)])
12801                    .add("task3", vec![serde_json::json!(3)]);
12802            }
12803
12804            let duration = start.elapsed();
12805            assert!(
12806                duration < Duration::from_millis(100),
12807                "Creating 1000 groups should take less than 100ms, took {:?}",
12808                duration
12809            );
12810        }
12811
12812        #[test]
12813        fn test_large_workflow_creation() {
12814            let start = Instant::now();
12815
12816            let mut chain = Chain::new();
12817            for i in 0..1000 {
12818                let task_name = format!("task{}", i);
12819                chain = chain.then(&task_name, vec![serde_json::json!(i)]);
12820            }
12821
12822            let duration = start.elapsed();
12823            assert!(
12824                duration < Duration::from_millis(500),
12825                "Creating chain with 1000 tasks should take less than 500ms, took {:?}",
12826                duration
12827            );
12828            assert_eq!(chain.len(), 1000);
12829        }
12830
12831        #[test]
12832        fn test_map_with_large_dataset() {
12833            let start = Instant::now();
12834
12835            let args: Vec<Vec<serde_json::Value>> =
12836                (0..1000).map(|i| vec![serde_json::json!(i)]).collect();
12837
12838            let map = Map::new(Signature::new("process".to_string()), args);
12839
12840            let duration = start.elapsed();
12841            assert!(
12842                duration < Duration::from_millis(100),
12843                "Creating map with 1000 items should take less than 100ms, took {:?}",
12844                duration
12845            );
12846            assert_eq!(map.len(), 1000);
12847        }
12848
12849        #[test]
12850        fn test_workflow_serialization_performance() {
12851            let chain = Chain::new()
12852                .then("task1", vec![serde_json::json!(1)])
12853                .then("task2", vec![serde_json::json!(2)])
12854                .then("task3", vec![serde_json::json!(3)]);
12855
12856            let start = Instant::now();
12857
12858            for _ in 0..1000 {
12859                let _ = serde_json::to_string(&chain).unwrap();
12860            }
12861
12862            let duration = start.elapsed();
12863            assert!(
12864                duration < Duration::from_millis(100),
12865                "Serializing chain 1000 times should take less than 100ms, took {:?}",
12866                duration
12867            );
12868        }
12869
12870        #[tokio::test]
12871        async fn test_broker_publish_performance() {
12872            let broker = MockBroker::new();
12873            let start = Instant::now();
12874
12875            for i in 0..100 {
12876                let task_name = format!("task{}", i);
12877                let chain = Chain::new().then(&task_name, vec![serde_json::json!(i)]);
12878                let _ = chain.apply(&broker).await;
12879            }
12880
12881            let duration = start.elapsed();
12882            assert!(
12883                duration < Duration::from_secs(1),
12884                "Publishing 100 chains should take less than 1s, took {:?}",
12885                duration
12886            );
12887            assert_eq!(broker.task_count(), 100);
12888        }
12889
12890        #[tokio::test]
12891        async fn test_concurrent_workflow_enqueue() {
12892            let broker = Arc::new(MockBroker::new());
12893            let mut handles = vec![];
12894
12895            let start = Instant::now();
12896
12897            for i in 0..100 {
12898                let broker = broker.clone();
12899                let handle = tokio::spawn(async move {
12900                    let task_name = format!("task{}", i);
12901                    let chain = Chain::new().then(&task_name, vec![serde_json::json!(i)]);
12902                    chain.apply(&*broker).await
12903                });
12904                handles.push(handle);
12905            }
12906
12907            for handle in handles {
12908                handle.await.unwrap().unwrap();
12909            }
12910
12911            let duration = start.elapsed();
12912            assert!(
12913                duration < Duration::from_secs(2),
12914                "Concurrent publishing of 100 chains should take less than 2s, took {:?}",
12915                duration
12916            );
12917            assert_eq!(broker.task_count(), 100);
12918        }
12919
12920        #[test]
12921        fn test_memory_efficiency_large_group() {
12922            // Test that large groups don't cause excessive memory usage
12923            let mut group = Group::new();
12924
12925            for i in 0..10000 {
12926                let task_name = format!("task{}", i);
12927                group = group.add(&task_name, vec![serde_json::json!(i)]);
12928            }
12929
12930            assert_eq!(group.len(), 10000);
12931            assert!(!group.is_empty());
12932        }
12933
12934        #[test]
12935        fn test_workflow_clone_performance() {
12936            let chain = Chain::new()
12937                .then("task1", vec![serde_json::json!(1)])
12938                .then("task2", vec![serde_json::json!(2)])
12939                .then("task3", vec![serde_json::json!(3)]);
12940
12941            let start = Instant::now();
12942
12943            for _ in 0..1000 {
12944                let _ = chain.clone();
12945            }
12946
12947            let duration = start.elapsed();
12948            assert!(
12949                duration < Duration::from_millis(50),
12950                "Cloning chain 1000 times should take less than 50ms, took {:?}",
12951                duration
12952            );
12953        }
12954
12955        // ===== Stress Tests =====
12956
12957        #[test]
12958        fn test_deeply_nested_workflows() {
12959            // Test that deeply nested workflows don't cause stack overflow
12960            let mut current = Chain::new().then("task0", vec![serde_json::json!(0)]);
12961
12962            for i in 1..100 {
12963                let task_name = format!("task{}", i);
12964                current = current.then(&task_name, vec![serde_json::json!(i)]);
12965            }
12966
12967            assert_eq!(current.len(), 100);
12968        }
12969
12970        #[test]
12971        fn test_workflow_with_large_payloads() {
12972            // Test workflows with large argument payloads
12973            let large_data = vec![serde_json::json!({ "data": "x".repeat(10000) })];
12974
12975            let chain = Chain::new()
12976                .then("process_large", large_data.clone())
12977                .then("process_large2", large_data);
12978
12979            let serialized = serde_json::to_string(&chain).unwrap();
12980            assert!(
12981                serialized.len() > 20000,
12982                "Serialized chain should contain large data"
12983            );
12984        }
12985
12986        // ===== DAG Export Tests =====
12987
12988        #[test]
12989        fn test_dag_export_dot_format() {
12990            let chain = Chain::new()
12991                .then("task1", vec![serde_json::json!(1)])
12992                .then("task2", vec![serde_json::json!(2)])
12993                .then("task3", vec![serde_json::json!(3)]);
12994
12995            let dot = chain.to_dot();
12996            assert!(dot.contains("digraph Chain"));
12997            assert!(dot.contains("task1"));
12998            assert!(dot.contains("task2"));
12999            assert!(dot.contains("task3"));
13000            assert!(dot.contains("->"));
13001        }
13002
13003        #[test]
13004        fn test_dag_export_mermaid_format() {
13005            let group = Group::new()
13006                .add("task1", vec![serde_json::json!(1)])
13007                .add("task2", vec![serde_json::json!(2)]);
13008
13009            let mermaid = group.to_mermaid();
13010            assert!(mermaid.contains("graph"));
13011            assert!(mermaid.contains("task1"));
13012            assert!(mermaid.contains("task2"));
13013        }
13014
13015        #[test]
13016        fn test_dag_export_json_format() {
13017            let chain = Chain::new().then("task1", vec![serde_json::json!(1)]);
13018
13019            let json = chain.to_json().unwrap();
13020            assert!(json.contains("task1"));
13021            assert!(json.contains("tasks"));
13022        }
13023
13024        #[test]
13025        fn test_dag_export_render_commands() {
13026            let chain = Chain::new().then("task1", vec![serde_json::json!(1)]);
13027
13028            let svg_cmd = chain.svg_render_command();
13029            assert!(svg_cmd.contains("dot"));
13030            assert!(svg_cmd.contains("-Tsvg"));
13031
13032            let png_cmd = chain.png_render_command();
13033            assert!(png_cmd.contains("dot"));
13034            assert!(png_cmd.contains("-Tpng"));
13035        }
13036
13037        #[test]
13038        #[ignore] // Requires GraphViz to be installed
13039        fn test_dag_export_to_svg() {
13040            let chain = Chain::new()
13041                .then("task1", vec![serde_json::json!(1)])
13042                .then("task2", vec![serde_json::json!(2)]);
13043
13044            if is_graphviz_available() {
13045                let svg = chain.to_svg().unwrap();
13046                assert!(svg.contains("<svg"));
13047                assert!(svg.contains("</svg>"));
13048                assert!(svg.contains("task1"));
13049            } else {
13050                println!("Skipping: GraphViz not installed");
13051            }
13052        }
13053
13054        #[test]
13055        #[ignore] // Requires GraphViz to be installed
13056        fn test_dag_export_to_png() {
13057            let chain = Chain::new()
13058                .then("task1", vec![serde_json::json!(1)])
13059                .then("task2", vec![serde_json::json!(2)]);
13060
13061            if is_graphviz_available() {
13062                let png = chain.to_png().unwrap();
13063                assert!(!png.is_empty());
13064                // PNG magic bytes
13065                assert_eq!(&png[0..4], &[0x89, 0x50, 0x4E, 0x47]);
13066            } else {
13067                println!("Skipping: GraphViz not installed");
13068            }
13069        }
13070
13071        #[test]
13072        fn test_graphviz_availability_check() {
13073            // This test just verifies the function runs without panicking
13074            let _available = is_graphviz_available();
13075            // Result depends on system, so we just ensure it doesn't crash
13076        }
13077
13078        #[test]
13079        fn test_dag_format_enum() {
13080            let _dot = DagFormat::Dot;
13081            let _mermaid = DagFormat::Mermaid;
13082            let _json = DagFormat::Json;
13083            let _svg = DagFormat::Svg;
13084            let _png = DagFormat::Png;
13085        }
13086
13087        // ========================================================================
13088        // Visualization Features Tests
13089        // ========================================================================
13090
13091        #[test]
13092        fn test_visual_theme_light() {
13093            let theme = VisualTheme::light();
13094            assert_eq!(theme.name, "light");
13095            assert_eq!(theme.color_for_state("completed"), Some("#4CAF50"));
13096            assert_eq!(theme.color_for_state("running"), Some("#2196F3"));
13097            assert_eq!(theme.color_for_state("failed"), Some("#F44336"));
13098            assert_eq!(theme.shape_for_type("task"), Some("box"));
13099        }
13100
13101        #[test]
13102        fn test_visual_theme_dark() {
13103            let theme = VisualTheme::dark();
13104            assert_eq!(theme.name, "dark");
13105            assert_eq!(theme.color_for_state("completed"), Some("#388E3C"));
13106            assert_eq!(theme.color_for_state("running"), Some("#1976D2"));
13107            assert_eq!(theme.color_for_state("failed"), Some("#D32F2F"));
13108        }
13109
13110        #[test]
13111        fn test_visual_theme_default() {
13112            let theme = VisualTheme::default();
13113            assert_eq!(theme.name, "light");
13114        }
13115
13116        #[test]
13117        fn test_task_visual_metadata() {
13118            let task_id = Uuid::new_v4();
13119            let mut metadata =
13120                TaskVisualMetadata::new(task_id, "test_task".to_string(), "running".to_string());
13121
13122            assert_eq!(metadata.task_name, "test_task");
13123            assert_eq!(metadata.state, "running");
13124            assert_eq!(metadata.progress, 0.0);
13125            assert_eq!(metadata.color, "#2196F3");
13126
13127            metadata = metadata.with_progress(50.0);
13128            assert_eq!(metadata.progress, 50.0);
13129
13130            metadata = metadata.with_position(100.0, 200.0);
13131            assert_eq!(metadata.position, Some((100.0, 200.0)));
13132
13133            metadata.add_css_class("highlight".to_string());
13134            assert!(metadata.css_classes.contains(&"highlight".to_string()));
13135
13136            metadata.add_metadata("custom".to_string(), serde_json::json!("value"));
13137            assert_eq!(
13138                metadata.metadata.get("custom"),
13139                Some(&serde_json::json!("value"))
13140            );
13141        }
13142
13143        #[test]
13144        fn test_workflow_visualization_data() {
13145            let workflow_id = Uuid::new_v4();
13146            let state = WorkflowState {
13147                workflow_id,
13148                status: WorkflowStatus::Running,
13149                total_tasks: 3,
13150                completed_tasks: 1,
13151                failed_tasks: 0,
13152                start_time: Some(12345),
13153                end_time: None,
13154                current_stage: Some("stage1".to_string()),
13155                intermediate_results: HashMap::new(),
13156            };
13157
13158            let mut viz_data =
13159                WorkflowVisualizationData::new(workflow_id, "test_workflow".to_string(), state);
13160
13161            let task1_id = Uuid::new_v4();
13162            let task1 =
13163                TaskVisualMetadata::new(task1_id, "task1".to_string(), "completed".to_string());
13164            viz_data.add_task(task1);
13165
13166            let task2_id = Uuid::new_v4();
13167            let task2 =
13168                TaskVisualMetadata::new(task2_id, "task2".to_string(), "running".to_string());
13169            viz_data.add_task(task2);
13170
13171            viz_data.add_edge(task1_id, task2_id, "chain".to_string());
13172
13173            assert_eq!(viz_data.tasks.len(), 2);
13174            assert_eq!(viz_data.edges.len(), 1);
13175
13176            // Test JSON export
13177            let json = viz_data.to_json();
13178            assert!(json.is_ok());
13179
13180            // Test vis.js format
13181            let visjs = viz_data.to_visjs_format();
13182            assert!(visjs.is_object());
13183        }
13184
13185        #[test]
13186        fn test_workflow_visualization_data_with_theme() {
13187            let workflow_id = Uuid::new_v4();
13188            let state = WorkflowState {
13189                workflow_id,
13190                status: WorkflowStatus::Pending,
13191                total_tasks: 1,
13192                completed_tasks: 0,
13193                failed_tasks: 0,
13194                start_time: None,
13195                end_time: None,
13196                current_stage: None,
13197                intermediate_results: HashMap::new(),
13198            };
13199
13200            let viz_data = WorkflowVisualizationData::new(workflow_id, "test".to_string(), state)
13201                .with_theme(VisualTheme::dark())
13202                .with_layout("force".to_string());
13203
13204            assert_eq!(viz_data.theme.name, "dark");
13205            assert_eq!(viz_data.layout_hint, "force");
13206        }
13207
13208        #[test]
13209        fn test_timeline_entry() {
13210            let task_id = Uuid::new_v4();
13211            let mut entry = TimelineEntry::new(task_id, "test_task".to_string(), 1000);
13212
13213            assert_eq!(entry.task_name, "test_task");
13214            assert_eq!(entry.start_time, 1000);
13215            assert_eq!(entry.state, "running");
13216            assert_eq!(entry.end_time, None);
13217
13218            entry.complete(2000);
13219            assert_eq!(entry.end_time, Some(2000));
13220            assert_eq!(entry.duration, Some(1000));
13221            assert_eq!(entry.state, "completed");
13222            assert_eq!(entry.color, "#4CAF50");
13223        }
13224
13225        #[test]
13226        fn test_timeline_entry_fail() {
13227            let task_id = Uuid::new_v4();
13228            let mut entry = TimelineEntry::new(task_id, "test_task".to_string(), 1000);
13229
13230            entry.fail(2500);
13231            assert_eq!(entry.end_time, Some(2500));
13232            assert_eq!(entry.duration, Some(1500));
13233            assert_eq!(entry.state, "failed");
13234            assert_eq!(entry.color, "#F44336");
13235        }
13236
13237        #[test]
13238        fn test_timeline_entry_with_worker() {
13239            let task_id = Uuid::new_v4();
13240            let entry = TimelineEntry::new(task_id, "test".to_string(), 1000)
13241                .with_worker("worker-1".to_string())
13242                .with_parent(Uuid::new_v4());
13243
13244            assert_eq!(entry.worker_id, Some("worker-1".to_string()));
13245            assert!(entry.parent_id.is_some());
13246        }
13247
13248        #[test]
13249        fn test_execution_timeline() {
13250            let workflow_id = Uuid::new_v4();
13251            let mut timeline = ExecutionTimeline::new(workflow_id);
13252
13253            let task1_id = Uuid::new_v4();
13254            let index = timeline.start_task(task1_id, "task1".to_string());
13255            assert_eq!(timeline.entries.len(), 1);
13256
13257            timeline.complete_task(index);
13258            assert_eq!(timeline.entries[index].state, "completed");
13259
13260            timeline.complete_workflow();
13261            assert!(timeline.workflow_end.is_some());
13262        }
13263
13264        #[test]
13265        fn test_execution_timeline_fail_task() {
13266            let workflow_id = Uuid::new_v4();
13267            let mut timeline = ExecutionTimeline::new(workflow_id);
13268
13269            let task_id = Uuid::new_v4();
13270            let index = timeline.start_task(task_id, "failing_task".to_string());
13271
13272            timeline.fail_task(index);
13273            assert_eq!(timeline.entries[index].state, "failed");
13274        }
13275
13276        #[test]
13277        fn test_execution_timeline_json_export() {
13278            let workflow_id = Uuid::new_v4();
13279            let timeline = ExecutionTimeline::new(workflow_id);
13280
13281            let json = timeline.to_json();
13282            assert!(json.is_ok());
13283        }
13284
13285        #[test]
13286        fn test_execution_timeline_google_charts() {
13287            let workflow_id = Uuid::new_v4();
13288            let mut timeline = ExecutionTimeline::new(workflow_id);
13289
13290            timeline.add_entry(TimelineEntry::new(
13291                Uuid::new_v4(),
13292                "task1".to_string(),
13293                1000,
13294            ));
13295
13296            let chart_data = timeline.to_google_charts_format();
13297            assert!(chart_data.is_object());
13298            assert!(chart_data["cols"].is_array());
13299            assert!(chart_data["rows"].is_array());
13300        }
13301
13302        #[test]
13303        fn test_animation_frame() {
13304            let workflow_state = WorkflowState {
13305                workflow_id: Uuid::new_v4(),
13306                status: WorkflowStatus::Running,
13307                total_tasks: 5,
13308                completed_tasks: 2,
13309                failed_tasks: 0,
13310                start_time: Some(1000),
13311                end_time: None,
13312                current_stage: Some("processing".to_string()),
13313                intermediate_results: HashMap::new(),
13314            };
13315
13316            let mut frame = AnimationFrame::new(0, workflow_state);
13317
13318            let task1_id = Uuid::new_v4();
13319            let task2_id = Uuid::new_v4();
13320
13321            frame.set_task_state(task1_id, "completed".to_string());
13322            frame.add_active_task(task2_id);
13323            frame.add_completed_task(task1_id);
13324
13325            assert_eq!(
13326                frame.task_states.get(&task1_id),
13327                Some(&"completed".to_string())
13328            );
13329            assert!(frame.active_tasks.contains(&task2_id));
13330            assert!(frame.completed_tasks.contains(&task1_id));
13331            assert!(!frame.active_tasks.contains(&task1_id));
13332        }
13333
13334        #[test]
13335        fn test_animation_frame_with_events() {
13336            let workflow_state = WorkflowState {
13337                workflow_id: Uuid::new_v4(),
13338                status: WorkflowStatus::Running,
13339                total_tasks: 1,
13340                completed_tasks: 0,
13341                failed_tasks: 0,
13342                start_time: Some(1000),
13343                end_time: None,
13344                current_stage: None,
13345                intermediate_results: HashMap::new(),
13346            };
13347
13348            let mut frame = AnimationFrame::new(0, workflow_state);
13349
13350            let task_id = Uuid::new_v4();
13351            frame.add_event(WorkflowEvent::TaskCompleted { task_id });
13352
13353            assert_eq!(frame.events.len(), 1);
13354        }
13355
13356        #[test]
13357        fn test_workflow_animation() {
13358            let workflow_id = Uuid::new_v4();
13359            let mut animation = WorkflowAnimation::new(workflow_id, 100);
13360
13361            let state1 = WorkflowState {
13362                workflow_id,
13363                status: WorkflowStatus::Pending,
13364                total_tasks: 1,
13365                completed_tasks: 0,
13366                failed_tasks: 0,
13367                start_time: None,
13368                end_time: None,
13369                current_stage: None,
13370                intermediate_results: HashMap::new(),
13371            };
13372
13373            let frame1 = AnimationFrame::new(0, state1);
13374            animation.add_frame(frame1);
13375
13376            assert_eq!(animation.frame_count(), 1);
13377            assert_eq!(animation.total_duration, 100);
13378
13379            let retrieved_frame = animation.get_frame(0);
13380            assert!(retrieved_frame.is_some());
13381        }
13382
13383        #[test]
13384        fn test_workflow_animation_json_export() {
13385            let workflow_id = Uuid::new_v4();
13386            let animation = WorkflowAnimation::new(workflow_id, 100);
13387
13388            let json = animation.to_json();
13389            assert!(json.is_ok());
13390        }
13391
13392        #[test]
13393        fn test_dag_export_with_state_chain() {
13394            let mut chain = Chain::new();
13395            chain.tasks.push(Signature::new("task1".to_string()));
13396            chain.tasks.push(Signature::new("task2".to_string()));
13397
13398            let workflow_state = WorkflowState {
13399                workflow_id: Uuid::new_v4(),
13400                status: WorkflowStatus::Running,
13401                total_tasks: 2,
13402                completed_tasks: 1,
13403                failed_tasks: 0,
13404                start_time: Some(1000),
13405                end_time: None,
13406                current_stage: Some("task2".to_string()),
13407                intermediate_results: HashMap::new(),
13408            };
13409
13410            let mut task_states = HashMap::new();
13411            let task1_id = Uuid::new_v4();
13412            let task2_id = Uuid::new_v4();
13413            task_states.insert(task1_id, "completed".to_string());
13414            task_states.insert(task2_id, "running".to_string());
13415
13416            let dot = chain.to_dot_with_state(&workflow_state, &task_states);
13417            assert!(dot.contains("digraph Chain"));
13418            assert!(dot.contains("task1"));
13419            assert!(dot.contains("task2"));
13420
13421            let mermaid = chain.to_mermaid_with_state(&workflow_state, &task_states);
13422            assert!(mermaid.contains("graph LR"));
13423            assert!(mermaid.contains("completed"));
13424            assert!(mermaid.contains("running"));
13425
13426            let json = chain.to_json_with_state(&workflow_state, &task_states);
13427            assert!(json.is_ok());
13428        }
13429
13430        #[test]
13431        fn test_dag_export_with_state_group() {
13432            let mut group = Group::new();
13433            group.tasks.push(Signature::new("task1".to_string()));
13434            group.tasks.push(Signature::new("task2".to_string()));
13435
13436            let workflow_state = WorkflowState {
13437                workflow_id: Uuid::new_v4(),
13438                status: WorkflowStatus::Running,
13439                total_tasks: 2,
13440                completed_tasks: 0,
13441                failed_tasks: 0,
13442                start_time: Some(1000),
13443                end_time: None,
13444                current_stage: None,
13445                intermediate_results: HashMap::new(),
13446            };
13447
13448            let task_states = HashMap::new();
13449
13450            let dot = group.to_dot_with_state(&workflow_state, &task_states);
13451            assert!(dot.contains("digraph Group"));
13452
13453            let mermaid = group.to_mermaid_with_state(&workflow_state, &task_states);
13454            assert!(mermaid.contains("graph TB"));
13455
13456            let json = group.to_json_with_state(&workflow_state, &task_states);
13457            assert!(json.is_ok());
13458        }
13459
13460        #[test]
13461        fn test_dag_export_with_state_chord() {
13462            let mut header = Group::new();
13463            header.tasks.push(Signature::new("task1".to_string()));
13464            header.tasks.push(Signature::new("task2".to_string()));
13465            let body = Signature::new("callback".to_string());
13466            let chord = Chord::new(header, body);
13467
13468            let workflow_state = WorkflowState {
13469                workflow_id: Uuid::new_v4(),
13470                status: WorkflowStatus::Running,
13471                total_tasks: 3,
13472                completed_tasks: 2,
13473                failed_tasks: 0,
13474                start_time: Some(1000),
13475                end_time: None,
13476                current_stage: Some("callback".to_string()),
13477                intermediate_results: HashMap::new(),
13478            };
13479
13480            let task_states = HashMap::new();
13481
13482            let dot = chord.to_dot_with_state(&workflow_state, &task_states);
13483            assert!(dot.contains("digraph Chord"));
13484            assert!(dot.contains("callback"));
13485
13486            let mermaid = chord.to_mermaid_with_state(&workflow_state, &task_states);
13487            assert!(mermaid.contains("graph TB"));
13488            assert!(mermaid.contains("callback"));
13489
13490            let json = chord.to_json_with_state(&workflow_state, &task_states);
13491            assert!(json.is_ok());
13492        }
13493
13494        #[test]
13495        fn test_workflow_event_stream() {
13496            let workflow_id = Uuid::new_v4();
13497            let mut stream = WorkflowEventStream::new(workflow_id);
13498
13499            assert_eq!(stream.workflow_id, workflow_id);
13500            assert_eq!(stream.events.len(), 0);
13501
13502            let task_id = Uuid::new_v4();
13503            stream.push(WorkflowEvent::TaskCompleted { task_id });
13504            stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13505
13506            assert_eq!(stream.events.len(), 2);
13507
13508            let all_events = stream.all_events();
13509            assert_eq!(all_events.len(), 2);
13510        }
13511
13512        #[test]
13513        fn test_workflow_event_stream_buffer_limit() {
13514            let workflow_id = Uuid::new_v4();
13515            let mut stream = WorkflowEventStream::new(workflow_id).with_max_buffer_size(2);
13516
13517            let task1 = Uuid::new_v4();
13518            let task2 = Uuid::new_v4();
13519            let task3 = Uuid::new_v4();
13520
13521            stream.push(WorkflowEvent::TaskCompleted { task_id: task1 });
13522            stream.push(WorkflowEvent::TaskCompleted { task_id: task2 });
13523            stream.push(WorkflowEvent::TaskCompleted { task_id: task3 });
13524
13525            // Should only keep last 2 events
13526            assert_eq!(stream.events.len(), 2);
13527        }
13528
13529        #[test]
13530        fn test_workflow_event_stream_since() {
13531            let workflow_id = Uuid::new_v4();
13532            let mut stream = WorkflowEventStream::new(workflow_id);
13533
13534            stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13535            std::thread::sleep(std::time::Duration::from_millis(10));
13536
13537            let timestamp = std::time::SystemTime::now()
13538                .duration_since(std::time::UNIX_EPOCH)
13539                .unwrap()
13540                .as_millis() as u64;
13541
13542            std::thread::sleep(std::time::Duration::from_millis(10));
13543            let task_id = Uuid::new_v4();
13544            stream.push(WorkflowEvent::TaskCompleted { task_id });
13545
13546            let recent_events = stream.events_since(timestamp);
13547            assert_eq!(recent_events.len(), 1);
13548        }
13549
13550        #[test]
13551        fn test_workflow_event_stream_clear() {
13552            let workflow_id = Uuid::new_v4();
13553            let mut stream = WorkflowEventStream::new(workflow_id);
13554
13555            stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13556            assert_eq!(stream.events.len(), 1);
13557
13558            stream.clear();
13559            assert_eq!(stream.events.len(), 0);
13560        }
13561
13562        #[test]
13563        fn test_workflow_event_stream_sse_format() {
13564            let workflow_id = Uuid::new_v4();
13565            let mut stream = WorkflowEventStream::new(workflow_id);
13566
13567            stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13568
13569            let sse_messages = stream.to_sse_format();
13570            assert_eq!(sse_messages.len(), 1);
13571            assert!(sse_messages[0].starts_with("event: workflow"));
13572        }
13573
13574        // ========================================================================
13575        // Production-Ready Enhancements Tests
13576        // ========================================================================
13577
13578        #[test]
13579        fn test_workflow_metrics_collector() {
13580            let workflow_id = Uuid::new_v4();
13581            let mut collector = WorkflowMetricsCollector::new(workflow_id);
13582
13583            assert_eq!(collector.workflow_id, workflow_id);
13584            assert_eq!(collector.total_tasks, 0);
13585            assert_eq!(collector.completed_tasks, 0);
13586            assert_eq!(collector.failed_tasks, 0);
13587
13588            let task1 = Uuid::new_v4();
13589            let task2 = Uuid::new_v4();
13590
13591            collector.record_task_start(task1);
13592            collector.record_task_complete(task1, 100);
13593
13594            assert_eq!(collector.total_tasks, 1);
13595            assert_eq!(collector.completed_tasks, 1);
13596
13597            collector.record_task_start(task2);
13598            collector.record_task_failure(task2, 50);
13599
13600            assert_eq!(collector.total_tasks, 2);
13601            assert_eq!(collector.failed_tasks, 1);
13602
13603            collector.record_task_retry(task2);
13604            assert_eq!(*collector.task_retries.get(&task2).unwrap(), 1);
13605
13606            collector.finalize();
13607            assert!(collector.end_time.is_some());
13608            assert!(collector.total_duration.is_some());
13609            assert!(collector.avg_task_duration.is_some());
13610            assert!(collector.success_rate.is_some());
13611
13612            let summary = collector.summary();
13613            assert!(summary.contains("WorkflowMetrics"));
13614        }
13615
13616        #[test]
13617        fn test_workflow_metrics_collector_display() {
13618            let workflow_id = Uuid::new_v4();
13619            let collector = WorkflowMetricsCollector::new(workflow_id);
13620
13621            let display = format!("{}", collector);
13622            assert!(display.contains("WorkflowMetrics"));
13623        }
13624
13625        #[test]
13626        fn test_workflow_rate_limiter() {
13627            let mut limiter = WorkflowRateLimiter::new(2, 1000);
13628
13629            assert_eq!(limiter.max_workflows, 2);
13630            assert_eq!(limiter.window_ms, 1000);
13631
13632            assert!(limiter.allow_workflow());
13633            assert!(limiter.allow_workflow());
13634            assert!(!limiter.allow_workflow()); // Should be rejected
13635
13636            assert_eq!(limiter.total_workflows, 2);
13637            assert_eq!(limiter.rejected_workflows, 1);
13638
13639            let rate = limiter.current_rate();
13640            assert!(rate > 0.0);
13641
13642            let rejection_rate = limiter.rejection_rate();
13643            assert!(rejection_rate > 0.0);
13644
13645            limiter.reset();
13646            assert_eq!(limiter.workflow_timestamps.len(), 0);
13647        }
13648
13649        #[test]
13650        fn test_workflow_rate_limiter_display() {
13651            let limiter = WorkflowRateLimiter::new(10, 1000);
13652
13653            let display = format!("{}", limiter);
13654            assert!(display.contains("RateLimiter"));
13655        }
13656
13657        #[test]
13658        fn test_workflow_concurrency_control() {
13659            let mut control = WorkflowConcurrencyControl::new(2);
13660
13661            assert_eq!(control.max_concurrent, 2);
13662            assert_eq!(control.current_concurrency(), 0);
13663            assert_eq!(control.available_slots(), 2);
13664            assert!(!control.is_at_capacity());
13665
13666            let wf1 = Uuid::new_v4();
13667            let wf2 = Uuid::new_v4();
13668            let wf3 = Uuid::new_v4();
13669
13670            assert!(control.try_start(wf1));
13671            assert!(control.try_start(wf2));
13672            assert!(!control.try_start(wf3)); // Should be rejected
13673
13674            assert_eq!(control.current_concurrency(), 2);
13675            assert!(control.is_at_capacity());
13676            assert_eq!(control.peak_concurrency, 2);
13677
13678            assert!(control.complete(wf1));
13679            assert_eq!(control.current_concurrency(), 1);
13680            assert_eq!(control.total_completed, 1);
13681
13682            assert!(control.try_start(wf3));
13683            assert_eq!(control.current_concurrency(), 2);
13684        }
13685
13686        #[test]
13687        fn test_workflow_concurrency_control_display() {
13688            let control = WorkflowConcurrencyControl::new(5);
13689
13690            let display = format!("{}", control);
13691            assert!(display.contains("ConcurrencyControl"));
13692        }
13693
13694        #[test]
13695        fn test_workflow_builder() {
13696            let builder = WorkflowBuilder::new("test_workflow")
13697                .with_description("Test workflow description")
13698                .add_tag("test")
13699                .add_tag("production")
13700                .add_metadata("version", serde_json::json!("1.0"));
13701
13702            assert_eq!(builder.name, "test_workflow");
13703            assert_eq!(
13704                builder.description,
13705                Some("Test workflow description".to_string())
13706            );
13707            assert_eq!(builder.tags.len(), 2);
13708            assert_eq!(builder.metadata.len(), 1);
13709
13710            let chain = builder.clone().chain();
13711            assert!(chain.is_empty());
13712
13713            let group = builder.clone().group();
13714            assert!(group.is_empty());
13715
13716            let task = Signature::new("map_task".to_string());
13717            let argsets = vec![vec![serde_json::json!(1)], vec![serde_json::json!(2)]];
13718            let map = builder.map(task, argsets);
13719            assert_eq!(map.task.task, "map_task");
13720        }
13721
13722        #[test]
13723        fn test_workflow_registry() {
13724            let mut registry = WorkflowRegistry::new();
13725
13726            assert_eq!(registry.count(), 0);
13727
13728            let wf1 = Uuid::new_v4();
13729            let wf2 = Uuid::new_v4();
13730
13731            let mut metadata1 = HashMap::new();
13732            metadata1.insert("version".to_string(), serde_json::json!("1.0"));
13733
13734            registry.register(wf1, "workflow_1".to_string(), metadata1.clone());
13735            registry.register(wf2, "workflow_2".to_string(), HashMap::new());
13736
13737            assert_eq!(registry.count(), 2);
13738            assert_eq!(registry.get_name(&wf1), Some("workflow_1"));
13739            assert_eq!(registry.get_state(&wf1), Some(&WorkflowStatus::Pending));
13740
13741            registry.update_state(wf1, WorkflowStatus::Running);
13742            assert_eq!(registry.get_state(&wf1), Some(&WorkflowStatus::Running));
13743
13744            registry.add_tag(wf1, "production".to_string());
13745            registry.add_tag(wf2, "production".to_string());
13746
13747            let production_workflows = registry.get_by_tag("production");
13748            assert_eq!(production_workflows.len(), 2);
13749
13750            let running = registry.get_by_state(&WorkflowStatus::Running);
13751            assert_eq!(running.len(), 1);
13752
13753            assert!(registry.remove(&wf1));
13754            assert_eq!(registry.count(), 1);
13755
13756            registry.clear();
13757            assert_eq!(registry.count(), 0);
13758        }
13759
13760        #[test]
13761        fn test_workflow_registry_default() {
13762            let registry = WorkflowRegistry::default();
13763            assert_eq!(registry.count(), 0);
13764        }
13765
13766        #[test]
13767        fn test_workflow_registry_display() {
13768            let mut registry = WorkflowRegistry::new();
13769
13770            let wf1 = Uuid::new_v4();
13771            registry.register(wf1, "test".to_string(), HashMap::new());
13772
13773            let display = format!("{}", registry);
13774            assert!(display.contains("WorkflowRegistry"));
13775            assert!(display.contains("total=1"));
13776        }
13777
13778        // ===== NestedChain Tests =====
13779
13780        #[tokio::test]
13781        async fn test_nested_chain_apply() {
13782            let broker = MockBroker::new();
13783
13784            let nested_chain = NestedChain::new()
13785                .then("task1", vec![serde_json::json!(1)])
13786                .then_group(
13787                    Group::new()
13788                        .add("task2a", vec![serde_json::json!(2)])
13789                        .add("task2b", vec![serde_json::json!(3)]),
13790                )
13791                .then("task3", vec![serde_json::json!(4)]);
13792
13793            let result = nested_chain.apply(&broker).await;
13794            assert!(result.is_ok(), "NestedChain apply should succeed");
13795
13796            // Verify tasks were published
13797            assert!(
13798                broker.task_count() >= 4,
13799                "Should publish at least 4 tasks (task1, task2a, task2b, task3)"
13800            );
13801        }
13802
13803        #[tokio::test]
13804        async fn test_nested_chain_with_chains() {
13805            let broker = MockBroker::new();
13806
13807            let nested_chain = NestedChain::new()
13808                .then_chain(Chain::new().then("step1", vec![]).then("step2", vec![]))
13809                .then_chain(Chain::new().then("step3", vec![]).then("step4", vec![]));
13810
13811            let result = nested_chain.apply(&broker).await;
13812            assert!(result.is_ok(), "NestedChain with chains should succeed");
13813
13814            // Each chain only enqueues the first task (with links to subsequent tasks)
13815            // So we expect 2 tasks (one per chain), not 4
13816            assert_eq!(
13817                broker.task_count(),
13818                2,
13819                "Should publish 2 first tasks from two chains"
13820            );
13821        }
13822
13823        #[tokio::test]
13824        async fn test_nested_chain_empty_error() {
13825            let broker = MockBroker::new();
13826            let nested_chain = NestedChain::new();
13827
13828            let result = nested_chain.apply(&broker).await;
13829            assert!(result.is_err(), "Empty NestedChain should return error");
13830            match result {
13831                Err(CanvasError::Invalid(msg)) => {
13832                    assert!(msg.contains("empty"));
13833                }
13834                _ => panic!("Expected Invalid error for empty NestedChain"),
13835            }
13836        }
13837
13838        #[test]
13839        fn test_nested_chain_builder_methods() {
13840            let chain = NestedChain::new()
13841                .then("task1", vec![])
13842                .then_signature(Signature::new("task2".to_string()))
13843                .then_group(Group::new().add("task3", vec![]));
13844
13845            assert_eq!(chain.len(), 3);
13846            assert!(!chain.is_empty());
13847        }
13848
13849        #[test]
13850        fn test_nested_chain_display() {
13851            let chain = NestedChain::new()
13852                .then("task1", vec![])
13853                .then("task2", vec![]);
13854
13855            let display = format!("{}", chain);
13856            assert!(display.contains("NestedChain"));
13857            assert!(display.contains("->"));
13858        }
13859
13860        // ===== NestedGroup Tests =====
13861
13862        #[tokio::test]
13863        async fn test_nested_group_apply() {
13864            let broker = MockBroker::new();
13865
13866            let nested_group = NestedGroup::new()
13867                .add("task1", vec![serde_json::json!(1)])
13868                .add_chain(Chain::new().then("task2a", vec![]).then("task2b", vec![]))
13869                .add("task3", vec![serde_json::json!(3)]);
13870
13871            let result = nested_group.apply(&broker).await;
13872            assert!(result.is_ok(), "NestedGroup apply should succeed");
13873
13874            // Verify tasks were published
13875            // Chain only enqueues first task, so: task1 + task2a (from chain) + task3 = 3
13876            assert_eq!(
13877                broker.task_count(),
13878                3,
13879                "Should publish 3 tasks (task1, task2a from chain, task3)"
13880            );
13881        }
13882
13883        #[tokio::test]
13884        async fn test_nested_group_with_multiple_chains() {
13885            let broker = MockBroker::new();
13886
13887            let nested_group = NestedGroup::new()
13888                .add_chain(Chain::new().then("a1", vec![]).then("a2", vec![]))
13889                .add_chain(Chain::new().then("b1", vec![]).then("b2", vec![]))
13890                .add_chain(Chain::new().then("c1", vec![]).then("c2", vec![]));
13891
13892            let result = nested_group.apply(&broker).await;
13893            assert!(
13894                result.is_ok(),
13895                "NestedGroup with multiple chains should succeed"
13896            );
13897
13898            // Each chain only enqueues the first task (with links)
13899            // So we expect 3 tasks (one per chain), not 6
13900            assert_eq!(
13901                broker.task_count(),
13902                3,
13903                "Should publish 3 first tasks from three chains"
13904            );
13905        }
13906
13907        #[tokio::test]
13908        async fn test_nested_group_empty_error() {
13909            let broker = MockBroker::new();
13910            let nested_group = NestedGroup::new();
13911
13912            let result = nested_group.apply(&broker).await;
13913            assert!(result.is_err(), "Empty NestedGroup should return error");
13914            match result {
13915                Err(CanvasError::Invalid(msg)) => {
13916                    assert!(msg.contains("empty"));
13917                }
13918                _ => panic!("Expected Invalid error for empty NestedGroup"),
13919            }
13920        }
13921
13922        #[test]
13923        fn test_nested_group_builder_methods() {
13924            let group = NestedGroup::new()
13925                .add("task1", vec![])
13926                .add_signature(Signature::new("task2".to_string()))
13927                .add_chain(Chain::new().then("task3", vec![]));
13928
13929            assert_eq!(group.len(), 3);
13930            assert!(!group.is_empty());
13931        }
13932
13933        #[test]
13934        fn test_nested_group_display() {
13935            let group = NestedGroup::new().add("task1", vec![]).add("task2", vec![]);
13936
13937            let display = format!("{}", group);
13938            assert!(display.contains("NestedGroup"));
13939            assert!(display.contains("|"));
13940        }
13941
13942        #[tokio::test]
13943        async fn test_nested_workflows_complex_composition() {
13944            let broker = MockBroker::new();
13945
13946            // Create a complex nested workflow:
13947            // Chain of [ Group of tasks -> Chain of tasks -> Group of tasks ]
13948            let nested = NestedChain::new()
13949                .then_group(
13950                    Group::new()
13951                        .add("parallel1", vec![])
13952                        .add("parallel2", vec![])
13953                        .add("parallel3", vec![]),
13954                )
13955                .then_chain(Chain::new().then("seq1", vec![]).then("seq2", vec![]))
13956                .then_element(CanvasElement::Group(
13957                    Group::new().add("final1", vec![]).add("final2", vec![]),
13958                ));
13959
13960            let result = nested.apply(&broker).await;
13961            assert!(result.is_ok(), "Complex nested workflow should succeed");
13962
13963            // Total tasks: 3 (parallel group) + 1 (first from chain) + 2 (final group) = 6
13964            // Chains only enqueue their first task with links to subsequent tasks
13965            assert_eq!(broker.task_count(), 6, "Should publish 6 initial tasks");
13966        }
13967
13968        #[test]
13969        fn test_chain_iterator_methods() {
13970            let chain = Chain::new()
13971                .then("task1", vec![])
13972                .then("task2", vec![])
13973                .then("task3", vec![]);
13974
13975            // Test first/last
13976            assert_eq!(chain.first().unwrap().task, "task1");
13977            assert_eq!(chain.last().unwrap().task, "task3");
13978
13979            // Test get
13980            assert_eq!(chain.get(1).unwrap().task, "task2");
13981            assert!(chain.get(10).is_none());
13982
13983            // Test iter
13984            let names: Vec<String> = chain.iter().map(|s| s.task.clone()).collect();
13985            assert_eq!(names, vec!["task1", "task2", "task3"]);
13986
13987            assert_eq!(chain.len(), 3);
13988            assert!(!chain.is_empty());
13989        }
13990
13991        #[test]
13992        fn test_chain_into_iterator() {
13993            let chain = Chain::new().then("task1", vec![]).then("task2", vec![]);
13994
13995            let mut count = 0;
13996            for sig in &chain {
13997                assert!(sig.task.starts_with("task"));
13998                count += 1;
13999            }
14000            assert_eq!(count, 2);
14001
14002            // Test consuming iterator
14003            let tasks: Vec<Signature> = chain.into_iter().collect();
14004            assert_eq!(tasks.len(), 2);
14005        }
14006
14007        #[test]
14008        fn test_chain_from_vec() {
14009            let sigs = vec![
14010                Signature::new("task1".to_string()),
14011                Signature::new("task2".to_string()),
14012            ];
14013
14014            let chain = Chain::from(sigs);
14015            assert_eq!(chain.len(), 2);
14016            assert_eq!(chain.first().unwrap().task, "task1");
14017        }
14018
14019        #[test]
14020        fn test_chain_from_iter() {
14021            let chain: Chain = vec![
14022                Signature::new("task1".to_string()),
14023                Signature::new("task2".to_string()),
14024            ]
14025            .into_iter()
14026            .collect();
14027
14028            assert_eq!(chain.len(), 2);
14029        }
14030
14031        #[test]
14032        fn test_chain_with_capacity() {
14033            let chain = Chain::with_capacity(10);
14034            assert_eq!(chain.len(), 0);
14035            assert!(chain.is_empty());
14036        }
14037
14038        #[test]
14039        fn test_chain_extend() {
14040            let chain = Chain::new().then("task1", vec![]).extend(vec![
14041                Signature::new("task2".to_string()),
14042                Signature::new("task3".to_string()),
14043            ]);
14044
14045            assert_eq!(chain.len(), 3);
14046        }
14047
14048        #[test]
14049        fn test_chain_reverse() {
14050            let chain = Chain::new()
14051                .then("task1", vec![])
14052                .then("task2", vec![])
14053                .then("task3", vec![])
14054                .reverse();
14055
14056            assert_eq!(chain.first().unwrap().task, "task3");
14057            assert_eq!(chain.last().unwrap().task, "task1");
14058        }
14059
14060        #[test]
14061        fn test_chain_retain() {
14062            let chain = Chain::new()
14063                .then("keep1", vec![])
14064                .then("remove", vec![])
14065                .then("keep2", vec![])
14066                .retain(|sig| sig.task.starts_with("keep"));
14067
14068            assert_eq!(chain.len(), 2);
14069            assert_eq!(chain.first().unwrap().task, "keep1");
14070            assert_eq!(chain.last().unwrap().task, "keep2");
14071        }
14072
14073        #[test]
14074        fn test_group_iterator_methods() {
14075            let group = Group::new()
14076                .add("task1", vec![])
14077                .add("task2", vec![])
14078                .add("task3", vec![]);
14079
14080            // Test first/last
14081            assert_eq!(group.first().unwrap().task, "task1");
14082            assert_eq!(group.last().unwrap().task, "task3");
14083
14084            // Test get
14085            assert_eq!(group.get(1).unwrap().task, "task2");
14086            assert!(group.get(10).is_none());
14087
14088            // Test iter
14089            let names: Vec<String> = group.iter().map(|s| s.task.clone()).collect();
14090            assert_eq!(names, vec!["task1", "task2", "task3"]);
14091
14092            assert_eq!(group.len(), 3);
14093            assert!(!group.is_empty());
14094        }
14095
14096        #[test]
14097        fn test_group_find_filter() {
14098            let group = Group::new()
14099                .add("task_a", vec![])
14100                .add("task_b", vec![])
14101                .add("other", vec![]);
14102
14103            // Test find
14104            let found = group.find(|sig| sig.task == "task_b");
14105            assert!(found.is_some());
14106            assert_eq!(found.unwrap().task, "task_b");
14107
14108            // Test filter
14109            let filtered = group.clone().filter(|sig| sig.task.starts_with("task_"));
14110            assert_eq!(filtered.len(), 2);
14111        }
14112
14113        #[test]
14114        fn test_group_from_vec() {
14115            let sigs = vec![
14116                Signature::new("task1".to_string()),
14117                Signature::new("task2".to_string()),
14118            ];
14119
14120            let group = Group::from(sigs);
14121            assert_eq!(group.len(), 2);
14122            assert!(group.has_group_id());
14123        }
14124
14125        #[test]
14126        fn test_group_with_capacity() {
14127            let group = Group::with_capacity(10);
14128            assert_eq!(group.len(), 0);
14129            assert!(group.is_empty());
14130            assert!(group.has_group_id());
14131        }
14132
14133        #[test]
14134        fn test_signature_convenience_methods() {
14135            let sig = Signature::new("task".to_string())
14136                .add_kwarg("key1", serde_json::json!("value1"))
14137                .add_kwarg("key2", serde_json::json!(42))
14138                .add_arg(serde_json::json!(1))
14139                .add_arg(serde_json::json!(2));
14140
14141            assert!(sig.has_kwarg("key1"));
14142            assert!(sig.has_kwarg("key2"));
14143            assert!(!sig.has_kwarg("key3"));
14144
14145            assert_eq!(sig.get_kwarg("key1"), Some(&serde_json::json!("value1")));
14146            assert_eq!(sig.get_kwarg("key2"), Some(&serde_json::json!(42)));
14147
14148            assert_eq!(sig.args.len(), 2);
14149            assert_eq!(sig.args[0], serde_json::json!(1));
14150        }
14151
14152        #[test]
14153        fn test_workflow_registry_query_methods() {
14154            let mut registry = WorkflowRegistry::new();
14155
14156            let id1 = Uuid::new_v4();
14157            let id2 = Uuid::new_v4();
14158            let id3 = Uuid::new_v4();
14159
14160            registry.register(id1, "workflow_test_1".to_string(), HashMap::new());
14161            registry.register(id2, "workflow_test_2".to_string(), HashMap::new());
14162            registry.register(id3, "other_workflow".to_string(), HashMap::new());
14163
14164            // Test find_by_name
14165            let found = registry.find_by_name("test");
14166            assert_eq!(found.len(), 2);
14167
14168            // Test all_workflow_ids
14169            let all_ids = registry.all_workflow_ids();
14170            assert_eq!(all_ids.len(), 3);
14171
14172            // Test contains
14173            assert!(registry.contains(&id1));
14174            assert!(!registry.contains(&Uuid::new_v4()));
14175
14176            // Test tags
14177            registry.add_tag(id1, "tag1".to_string());
14178            registry.add_tag(id2, "tag1".to_string());
14179            registry.add_tag(id2, "tag2".to_string());
14180
14181            let all_tags = registry.all_tags();
14182            assert!(all_tags.contains(&"tag1".to_string()));
14183
14184            // Test get_by_tags_all
14185            let with_both = registry.get_by_tags_all(&["tag1", "tag2"]);
14186            assert_eq!(with_both.len(), 1);
14187            assert!(with_both.contains(&id2));
14188
14189            // Test get_by_tags_any
14190            let with_any = registry.get_by_tags_any(&["tag1", "tag2"]);
14191            assert!(with_any.len() >= 2);
14192
14193            // Test state counts
14194            registry.update_state(id1, WorkflowStatus::Running);
14195            registry.update_state(id2, WorkflowStatus::Success);
14196            registry.update_state(id3, WorkflowStatus::Pending);
14197
14198            assert_eq!(registry.running_count(), 1);
14199            assert_eq!(registry.pending_count(), 1);
14200            assert_eq!(registry.completed_count(), 1);
14201            assert_eq!(registry.count_by_state(&WorkflowStatus::Success), 1);
14202        }
14203
14204        #[test]
14205        fn test_workflow_registry_age_queries() {
14206            let mut registry = WorkflowRegistry::new();
14207            let id = Uuid::new_v4();
14208
14209            registry.register(id, "test".to_string(), HashMap::new());
14210
14211            // Age should be very small (just registered)
14212            let age = registry.get_age(&id);
14213            assert!(age.is_some());
14214            assert!(age.unwrap() < 1000); // Less than 1 second
14215
14216            // Test get_older_than
14217            let old = registry.get_older_than(1_000_000); // 1000 seconds
14218            assert_eq!(old.len(), 0); // Nothing is that old yet
14219
14220            // Sleep a tiny bit to ensure age > 0
14221            std::thread::sleep(std::time::Duration::from_millis(1));
14222
14223            let recent = registry.get_older_than(0); // 0 ms
14224            assert_eq!(recent.len(), 1); // Our workflow is older than 0ms
14225        }
14226    }
14227}