intent_engine/
plan.rs

1//! Plan Interface - Declarative Task Management
2//!
3//! Provides a declarative API for creating and updating task structures,
4//! inspired by TodoWrite pattern. Simplifies complex operations into
5//! single atomic calls.
6
7use serde::{Deserialize, Serialize};
8use sqlx::Row;
9use std::collections::HashMap;
10
11/// Request for creating/updating task structure declaratively
12#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
13pub struct PlanRequest {
14    /// Task tree to create or update
15    pub tasks: Vec<TaskTree>,
16}
17
18/// Hierarchical task definition with nested children
19#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
20pub struct TaskTree {
21    /// Task name (used as identifier for lookups)
22    pub name: String,
23
24    /// Optional task specification/description
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub spec: Option<String>,
27
28    /// Optional priority level
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub priority: Option<PriorityValue>,
31
32    /// Nested child tasks (direct hierarchy expression)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub children: Option<Vec<TaskTree>>,
35
36    /// Task dependencies by name (name-based references)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub depends_on: Option<Vec<String>>,
39
40    /// Optional explicit task ID (for forced updates)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub task_id: Option<i64>,
43
44    /// Optional task status (for TodoWriter compatibility)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub status: Option<TaskStatus>,
47
48    /// Optional active form description (for TodoWriter compatibility)
49    /// Used for UI display when task is in_progress
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub active_form: Option<String>,
52
53    /// Explicit parent task ID
54    /// - None: use default behavior (auto-parent to focused task for new root tasks)
55    /// - Some(None): explicitly create as root task (no parent)
56    /// - Some(Some(id)): explicitly set parent to task with given ID
57    #[serde(
58        default,
59        skip_serializing_if = "Option::is_none",
60        deserialize_with = "deserialize_parent_id"
61    )]
62    pub parent_id: Option<Option<i64>>,
63}
64
65/// Custom deserializer for parent_id field
66/// Handles the three-state logic:
67/// - Field absent → None (handled by #[serde(default)])
68/// - Field is null → Some(None) (explicit root task)
69/// - Field is number → Some(Some(id)) (explicit parent)
70fn deserialize_parent_id<'de, D>(
71    deserializer: D,
72) -> std::result::Result<Option<Option<i64>>, D::Error>
73where
74    D: serde::Deserializer<'de>,
75{
76    // When this function is called, the field EXISTS in the JSON.
77    // (Field-absent case is handled by #[serde(default)] returning None)
78    //
79    // Now we deserialize the value:
80    // - null → inner Option is None → we return Some(None)
81    // - number → inner Option is Some(n) → we return Some(Some(n))
82    let inner: Option<i64> = Option::deserialize(deserializer)?;
83    Ok(Some(inner))
84}
85
86/// Task status for workflow management
87#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
88#[serde(rename_all = "snake_case")]
89pub enum TaskStatus {
90    Todo,
91    Doing,
92    Done,
93}
94
95impl TaskStatus {
96    /// Convert to database string representation
97    pub fn as_db_str(&self) -> &'static str {
98        match self {
99            TaskStatus::Todo => "todo",
100            TaskStatus::Doing => "doing",
101            TaskStatus::Done => "done",
102        }
103    }
104
105    /// Create from database string representation
106    pub fn from_db_str(s: &str) -> Option<Self> {
107        match s {
108            "todo" => Some(TaskStatus::Todo),
109            "doing" => Some(TaskStatus::Doing),
110            "done" => Some(TaskStatus::Done),
111            _ => None,
112        }
113    }
114
115    /// Convert to string representation for JSON API
116    pub fn as_str(&self) -> &'static str {
117        match self {
118            TaskStatus::Todo => "todo",
119            TaskStatus::Doing => "doing",
120            TaskStatus::Done => "done",
121        }
122    }
123}
124
125/// Priority value as string enum for JSON API
126#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
127#[serde(rename_all = "lowercase")]
128pub enum PriorityValue {
129    Critical,
130    High,
131    Medium,
132    Low,
133}
134
135impl PriorityValue {
136    /// Convert to integer representation for database storage
137    pub fn to_int(&self) -> i32 {
138        match self {
139            PriorityValue::Critical => 1,
140            PriorityValue::High => 2,
141            PriorityValue::Medium => 3,
142            PriorityValue::Low => 4,
143        }
144    }
145
146    /// Create from integer representation
147    pub fn from_int(value: i32) -> Option<Self> {
148        match value {
149            1 => Some(PriorityValue::Critical),
150            2 => Some(PriorityValue::High),
151            3 => Some(PriorityValue::Medium),
152            4 => Some(PriorityValue::Low),
153            _ => None,
154        }
155    }
156
157    /// Convert to string representation
158    pub fn as_str(&self) -> &'static str {
159        match self {
160            PriorityValue::Critical => "critical",
161            PriorityValue::High => "high",
162            PriorityValue::Medium => "medium",
163            PriorityValue::Low => "low",
164        }
165    }
166}
167
168/// Result of plan execution
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub struct PlanResult {
171    /// Whether the operation succeeded
172    pub success: bool,
173
174    /// Mapping of task names to their IDs (for reference)
175    pub task_id_map: HashMap<String, i64>,
176
177    /// Number of tasks created
178    pub created_count: usize,
179
180    /// Number of tasks updated
181    pub updated_count: usize,
182
183    /// Number of dependencies created
184    pub dependency_count: usize,
185
186    /// Currently focused task (if a task has status="doing")
187    /// Includes full task details and event history
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub focused_task: Option<crate::db::models::TaskWithEvents>,
190
191    /// Optional error message if success = false
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub error: Option<String>,
194}
195
196impl PlanResult {
197    /// Create a successful result with optional focused task
198    pub fn success(
199        task_id_map: HashMap<String, i64>,
200        created_count: usize,
201        updated_count: usize,
202        dependency_count: usize,
203        focused_task: Option<crate::db::models::TaskWithEvents>,
204    ) -> Self {
205        Self {
206            success: true,
207            task_id_map,
208            created_count,
209            updated_count,
210            dependency_count,
211            focused_task,
212            error: None,
213        }
214    }
215
216    /// Create an error result
217    pub fn error(message: impl Into<String>) -> Self {
218        Self {
219            success: false,
220            task_id_map: HashMap::new(),
221            created_count: 0,
222            updated_count: 0,
223            dependency_count: 0,
224            focused_task: None,
225            error: Some(message.into()),
226        }
227    }
228}
229
230// ============================================================================
231// Name Extraction and Classification Logic
232// ============================================================================
233
234/// Extract all task names from a task tree (recursive)
235pub fn extract_all_names(tasks: &[TaskTree]) -> Vec<String> {
236    let mut names = Vec::new();
237
238    for task in tasks {
239        names.push(task.name.clone());
240
241        if let Some(children) = &task.children {
242            names.extend(extract_all_names(children));
243        }
244    }
245
246    names
247}
248
249/// Flatten task tree into a linear list with parent information
250#[derive(Debug, Clone, PartialEq)]
251pub struct FlatTask {
252    pub name: String,
253    pub spec: Option<String>,
254    pub priority: Option<PriorityValue>,
255    /// Parent from children nesting (takes precedence over explicit_parent_id)
256    pub parent_name: Option<String>,
257    pub depends_on: Vec<String>,
258    pub task_id: Option<i64>,
259    pub status: Option<TaskStatus>,
260    pub active_form: Option<String>,
261    /// Explicit parent_id from JSON
262    /// - None: use default behavior (auto-parent to focused task for new root tasks)
263    /// - Some(None): explicitly create as root task (no parent)
264    /// - Some(Some(id)): explicitly set parent to task with given ID
265    pub explicit_parent_id: Option<Option<i64>>,
266}
267
268pub fn flatten_task_tree(tasks: &[TaskTree]) -> Vec<FlatTask> {
269    flatten_task_tree_recursive(tasks, None)
270}
271
272fn flatten_task_tree_recursive(tasks: &[TaskTree], parent_name: Option<String>) -> Vec<FlatTask> {
273    let mut flat = Vec::new();
274
275    for task in tasks {
276        let flat_task = FlatTask {
277            name: task.name.clone(),
278            spec: task.spec.clone(),
279            priority: task.priority.clone(),
280            parent_name: parent_name.clone(),
281            depends_on: task.depends_on.clone().unwrap_or_default(),
282            task_id: task.task_id,
283            status: task.status.clone(),
284            active_form: task.active_form.clone(),
285            explicit_parent_id: task.parent_id,
286        };
287
288        flat.push(flat_task);
289
290        // Recursively flatten children
291        if let Some(children) = &task.children {
292            flat.extend(flatten_task_tree_recursive(
293                children,
294                Some(task.name.clone()),
295            ));
296        }
297    }
298
299    flat
300}
301
302/// Operation classification result
303#[derive(Debug, Clone, PartialEq)]
304pub enum Operation {
305    Create(FlatTask),
306    Update { task_id: i64, task: FlatTask },
307}
308
309/// Classify tasks into create/update operations based on existing task IDs
310///
311/// # Arguments
312/// * `flat_tasks` - Flattened task list
313/// * `existing_names` - Map of existing task names to their IDs
314///
315/// # Returns
316/// Classified operations (create or update)
317pub fn classify_operations(
318    flat_tasks: &[FlatTask],
319    existing_names: &HashMap<String, i64>,
320) -> Vec<Operation> {
321    let mut operations = Vec::new();
322
323    for task in flat_tasks {
324        // Priority: explicit task_id > name lookup > create
325        let operation = if let Some(task_id) = task.task_id {
326            // Explicit task_id → forced update
327            Operation::Update {
328                task_id,
329                task: task.clone(),
330            }
331        } else if let Some(&task_id) = existing_names.get(&task.name) {
332            // Name found in DB → update
333            Operation::Update {
334                task_id,
335                task: task.clone(),
336            }
337        } else {
338            // Not found → create
339            Operation::Create(task.clone())
340        };
341
342        operations.push(operation);
343    }
344
345    operations
346}
347
348/// Find duplicate names in a task list
349pub fn find_duplicate_names(tasks: &[TaskTree]) -> Vec<String> {
350    let mut seen = HashMap::new();
351    let mut duplicates = Vec::new();
352
353    for name in extract_all_names(tasks) {
354        let count = seen.entry(name.clone()).or_insert(0);
355        *count += 1;
356        if *count == 2 {
357            // Only add once when we first detect the duplicate
358            duplicates.push(name);
359        }
360    }
361
362    duplicates
363}
364
365// ============================================================================
366// Database Operations (Plan Executor)
367// ============================================================================
368
369use crate::error::{IntentError, Result};
370use sqlx::SqlitePool;
371
372/// Plan executor for creating/updating task structures
373pub struct PlanExecutor<'a> {
374    pool: &'a SqlitePool,
375    project_path: Option<String>,
376    /// Default parent ID for root-level tasks (auto-parenting to focused task)
377    default_parent_id: Option<i64>,
378}
379
380impl<'a> PlanExecutor<'a> {
381    /// Create a new plan executor
382    pub fn new(pool: &'a SqlitePool) -> Self {
383        Self {
384            pool,
385            project_path: None,
386            default_parent_id: None,
387        }
388    }
389
390    /// Create a plan executor with project path for dashboard notifications
391    pub fn with_project_path(pool: &'a SqlitePool, project_path: String) -> Self {
392        Self {
393            pool,
394            project_path: Some(project_path),
395            default_parent_id: None,
396        }
397    }
398
399    /// Set default parent ID for root-level tasks (auto-parenting to focused task)
400    /// When set, new root-level tasks will automatically become children of this task
401    pub fn with_default_parent(mut self, parent_id: i64) -> Self {
402        self.default_parent_id = Some(parent_id);
403        self
404    }
405
406    /// Get TaskManager configured for this executor
407    fn get_task_manager(&self) -> crate::tasks::TaskManager<'a> {
408        match &self.project_path {
409            Some(path) => crate::tasks::TaskManager::with_project_path(self.pool, path.clone()),
410            None => crate::tasks::TaskManager::new(self.pool),
411        }
412    }
413
414    /// Execute a plan request (Phase 2: create + update mode)
415    pub async fn execute(&self, request: &PlanRequest) -> Result<PlanResult> {
416        // 1. Check for duplicate names in the request
417        let duplicates = find_duplicate_names(&request.tasks);
418        if !duplicates.is_empty() {
419            return Ok(PlanResult::error(format!(
420                "Duplicate task names in request: {:?}",
421                duplicates
422            )));
423        }
424
425        // 2. Extract all task names
426        let all_names = extract_all_names(&request.tasks);
427
428        // 3. Find existing tasks by name
429        let existing = self.find_tasks_by_names(&all_names).await?;
430
431        // 4. Flatten the task tree
432        let flat_tasks = flatten_task_tree(&request.tasks);
433
434        // 5. Validate dependencies exist in the plan
435        if let Err(e) = self.validate_dependencies(&flat_tasks) {
436            return Ok(PlanResult::error(e.to_string()));
437        }
438
439        // 6. Detect circular dependencies
440        if let Err(e) = self.detect_circular_dependencies(&flat_tasks) {
441            return Ok(PlanResult::error(e.to_string()));
442        }
443
444        // 7. Validate batch-level single doing constraint
445        if let Err(e) = self.validate_batch_single_doing(&flat_tasks) {
446            return Ok(PlanResult::error(e.to_string()));
447        }
448
449        // 8. Get TaskManager for transaction operations
450        let task_mgr = self.get_task_manager();
451
452        // 9. Execute in transaction
453        let mut tx = self.pool.begin().await?;
454
455        // 10. Create or update tasks based on existence
456        let mut task_id_map = HashMap::new();
457        let mut created_count = 0;
458        let mut updated_count = 0;
459        let mut newly_created_names: std::collections::HashSet<String> =
460            std::collections::HashSet::new();
461
462        for task in &flat_tasks {
463            if let Some(&existing_id) = existing.get(&task.name) {
464                // Task exists -> UPDATE via TaskManager
465                task_mgr
466                    .update_task_in_tx(
467                        &mut tx,
468                        existing_id,
469                        task.spec.as_deref(),
470                        task.priority.as_ref().map(|p| p.to_int()),
471                        task.status.as_ref().map(|s| s.as_db_str()),
472                        task.active_form.as_deref(),
473                    )
474                    .await?;
475                task_id_map.insert(task.name.clone(), existing_id);
476                updated_count += 1;
477            } else {
478                // Task doesn't exist -> CREATE via TaskManager
479                let id = task_mgr
480                    .create_task_in_tx(
481                        &mut tx,
482                        &task.name,
483                        task.spec.as_deref(),
484                        task.priority.as_ref().map(|p| p.to_int()),
485                        task.status.as_ref().map(|s| s.as_db_str()),
486                        task.active_form.as_deref(),
487                        "ai", // Plan-created tasks are AI-owned
488                    )
489                    .await?;
490                task_id_map.insert(task.name.clone(), id);
491                newly_created_names.insert(task.name.clone());
492                created_count += 1;
493            }
494        }
495
496        // 11. Build parent-child relationships via TaskManager
497        for task in &flat_tasks {
498            if let Some(parent_name) = &task.parent_name {
499                let task_id = task_id_map.get(&task.name).ok_or_else(|| {
500                    IntentError::InvalidInput(format!("Task not found: {}", task.name))
501                })?;
502                let parent_id = task_id_map.get(parent_name).ok_or_else(|| {
503                    IntentError::InvalidInput(format!("Parent task not found: {}", parent_name))
504                })?;
505                task_mgr
506                    .set_parent_in_tx(&mut tx, *task_id, *parent_id)
507                    .await?;
508            }
509        }
510
511        // 11b. Handle explicit parent_id (takes precedence over auto-parenting)
512        // Priority: children nesting > explicit parent_id > auto-parent
513        for task in &flat_tasks {
514            // Skip if parent was set via children nesting
515            if task.parent_name.is_some() {
516                continue;
517            }
518
519            // Handle explicit parent_id
520            if let Some(explicit_parent) = &task.explicit_parent_id {
521                let task_id = task_id_map.get(&task.name).ok_or_else(|| {
522                    IntentError::InvalidInput(format!("Task not found: {}", task.name))
523                })?;
524
525                match explicit_parent {
526                    None => {
527                        // parent_id: null → explicitly set as root task (clear parent)
528                        task_mgr.clear_parent_in_tx(&mut tx, *task_id).await?;
529                    },
530                    Some(parent_id) => {
531                        // parent_id: N → set parent to task N (validate exists)
532                        // Note: parent task may be in this batch or already in DB
533                        task_mgr
534                            .set_parent_in_tx(&mut tx, *task_id, *parent_id)
535                            .await?;
536                    },
537                }
538            }
539        }
540
541        // 11c. Auto-parent newly created root tasks to default_parent_id (focused task)
542        if let Some(default_parent) = self.default_parent_id {
543            for task in &flat_tasks {
544                // Only auto-parent if:
545                // 1. Task was newly created (not updated)
546                // 2. Task has no explicit parent in the plan (children nesting)
547                // 3. Task has no explicit parent_id in JSON
548                if newly_created_names.contains(&task.name)
549                    && task.parent_name.is_none()
550                    && task.explicit_parent_id.is_none()
551                {
552                    if let Some(&task_id) = task_id_map.get(&task.name) {
553                        task_mgr
554                            .set_parent_in_tx(&mut tx, task_id, default_parent)
555                            .await?;
556                    }
557                }
558            }
559        }
560
561        // 12. Build dependencies
562        let dep_count = self
563            .build_dependencies(&mut tx, &flat_tasks, &task_id_map)
564            .await?;
565
566        // 13. Commit transaction
567        tx.commit().await?;
568
569        // 14. Notify Dashboard about the batch change (via TaskManager)
570        task_mgr.notify_batch_changed().await;
571
572        // 15. Auto-focus the doing task if present and return full context
573        // Find the doing task in the batch
574        let doing_task = flat_tasks
575            .iter()
576            .find(|task| matches!(task.status, Some(TaskStatus::Doing)));
577
578        let focused_task_response = if let Some(doing_task) = doing_task {
579            // Get the task ID from the map
580            if let Some(&task_id) = task_id_map.get(&doing_task.name) {
581                // Call task_start with events to get full context
582                let response = task_mgr.start_task(task_id, true).await?;
583                Some(response)
584            } else {
585                None
586            }
587        } else {
588            None
589        };
590
591        // 16. Return success result with focused task
592        Ok(PlanResult::success(
593            task_id_map,
594            created_count,
595            updated_count,
596            dep_count,
597            focused_task_response,
598        ))
599    }
600
601    /// Find tasks by names
602    async fn find_tasks_by_names(&self, names: &[String]) -> Result<HashMap<String, i64>> {
603        if names.is_empty() {
604            return Ok(HashMap::new());
605        }
606
607        let mut map = HashMap::new();
608
609        // Query all names at once using IN clause
610        // Build placeholders: ?, ?, ?...
611        let placeholders = names.iter().map(|_| "?").collect::<Vec<_>>().join(",");
612        let query = format!(
613            "SELECT id, name FROM tasks WHERE name IN ({})",
614            placeholders
615        );
616
617        let mut query_builder = sqlx::query(&query);
618        for name in names {
619            query_builder = query_builder.bind(name);
620        }
621
622        let rows = query_builder.fetch_all(self.pool).await?;
623
624        for row in rows {
625            let id: i64 = row.get("id");
626            let name: String = row.get("name");
627            map.insert(name, id);
628        }
629
630        Ok(map)
631    }
632
633    /// Build dependency relationships
634    async fn build_dependencies(
635        &self,
636        tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
637        flat_tasks: &[FlatTask],
638        task_id_map: &HashMap<String, i64>,
639    ) -> Result<usize> {
640        let mut count = 0;
641
642        for task in flat_tasks {
643            if !task.depends_on.is_empty() {
644                let blocked_id = task_id_map.get(&task.name).ok_or_else(|| {
645                    IntentError::InvalidInput(format!("Task not found: {}", task.name))
646                })?;
647
648                for dep_name in &task.depends_on {
649                    let blocking_id = task_id_map.get(dep_name).ok_or_else(|| {
650                        IntentError::InvalidInput(format!(
651                            "Dependency '{}' not found for task '{}'",
652                            dep_name, task.name
653                        ))
654                    })?;
655
656                    sqlx::query(
657                        "INSERT INTO dependencies (blocking_task_id, blocked_task_id) VALUES (?, ?)",
658                    )
659                    .bind(blocking_id)
660                    .bind(blocked_id)
661                    .execute(&mut **tx)
662                    .await?;
663
664                    count += 1;
665                }
666            }
667        }
668
669        Ok(count)
670    }
671
672    /// Validate that all dependencies exist in the plan
673    fn validate_dependencies(&self, flat_tasks: &[FlatTask]) -> Result<()> {
674        let task_names: std::collections::HashSet<_> =
675            flat_tasks.iter().map(|t| t.name.as_str()).collect();
676
677        for task in flat_tasks {
678            for dep_name in &task.depends_on {
679                if !task_names.contains(dep_name.as_str()) {
680                    return Err(IntentError::InvalidInput(format!(
681                        "Task '{}' depends on '{}', but '{}' is not in the plan",
682                        task.name, dep_name, dep_name
683                    )));
684                }
685            }
686        }
687
688        Ok(())
689    }
690
691    /// Validate batch-level single doing constraint
692    /// Ensures only one task in the request batch can have status='doing'
693    /// (Database can have multiple 'doing' tasks to support hierarchical workflows)
694    fn validate_batch_single_doing(&self, flat_tasks: &[FlatTask]) -> Result<()> {
695        // Find all tasks in the request that want to be doing
696        let doing_tasks: Vec<&FlatTask> = flat_tasks
697            .iter()
698            .filter(|task| matches!(task.status, Some(TaskStatus::Doing)))
699            .collect();
700
701        // If more than one task in the request wants to be doing, that's an error
702        if doing_tasks.len() > 1 {
703            let names: Vec<&str> = doing_tasks.iter().map(|t| t.name.as_str()).collect();
704            return Err(IntentError::InvalidInput(format!(
705                "Batch single doing constraint violated: only one task per batch can have status='doing'. Found: {}",
706                names.join(", ")
707            )));
708        }
709
710        Ok(())
711    }
712
713    /// Detect circular dependencies using Tarjan's algorithm for strongly connected components
714    fn detect_circular_dependencies(&self, flat_tasks: &[FlatTask]) -> Result<()> {
715        if flat_tasks.is_empty() {
716            return Ok(());
717        }
718
719        // Build name-to-index mapping
720        let name_to_idx: HashMap<&str, usize> = flat_tasks
721            .iter()
722            .enumerate()
723            .map(|(i, t)| (t.name.as_str(), i))
724            .collect();
725
726        // Build dependency graph (adjacency list)
727        let mut graph: Vec<Vec<usize>> = vec![Vec::new(); flat_tasks.len()];
728        for (idx, task) in flat_tasks.iter().enumerate() {
729            for dep_name in &task.depends_on {
730                if let Some(&dep_idx) = name_to_idx.get(dep_name.as_str()) {
731                    graph[idx].push(dep_idx);
732                }
733            }
734        }
735
736        // Check for self-loops first
737        for task in flat_tasks {
738            if task.depends_on.contains(&task.name) {
739                return Err(IntentError::InvalidInput(format!(
740                    "Circular dependency detected: task '{}' depends on itself",
741                    task.name
742                )));
743            }
744        }
745
746        // Run Tarjan's SCC algorithm
747        let sccs = self.tarjan_scc(&graph);
748
749        // Check for cycles (any SCC with size > 1)
750        for scc in sccs {
751            if scc.len() > 1 {
752                // Found a cycle - build error message
753                let cycle_names: Vec<&str> = scc
754                    .iter()
755                    .map(|&idx| flat_tasks[idx].name.as_str())
756                    .collect();
757
758                return Err(IntentError::InvalidInput(format!(
759                    "Circular dependency detected: {}",
760                    cycle_names.join(" → ")
761                )));
762            }
763        }
764
765        Ok(())
766    }
767
768    /// Tarjan's algorithm for finding strongly connected components
769    /// Returns a list of SCCs, where each SCC is a list of node indices
770    fn tarjan_scc(&self, graph: &[Vec<usize>]) -> Vec<Vec<usize>> {
771        let n = graph.len();
772        let mut index = 0;
773        let mut stack = Vec::new();
774        let mut indices = vec![None; n];
775        let mut lowlinks = vec![0; n];
776        let mut on_stack = vec![false; n];
777        let mut sccs = Vec::new();
778
779        #[allow(clippy::too_many_arguments)]
780        fn strongconnect(
781            v: usize,
782            graph: &[Vec<usize>],
783            index: &mut usize,
784            stack: &mut Vec<usize>,
785            indices: &mut [Option<usize>],
786            lowlinks: &mut [usize],
787            on_stack: &mut [bool],
788            sccs: &mut Vec<Vec<usize>>,
789        ) {
790            // Set the depth index for v to the smallest unused index
791            indices[v] = Some(*index);
792            lowlinks[v] = *index;
793            *index += 1;
794            stack.push(v);
795            on_stack[v] = true;
796
797            // Consider successors of v
798            for &w in &graph[v] {
799                if indices[w].is_none() {
800                    // Successor w has not yet been visited; recurse on it
801                    strongconnect(w, graph, index, stack, indices, lowlinks, on_stack, sccs);
802                    lowlinks[v] = lowlinks[v].min(lowlinks[w]);
803                } else if on_stack[w] {
804                    // Successor w is in stack and hence in the current SCC
805                    lowlinks[v] = lowlinks[v].min(indices[w].unwrap());
806                }
807            }
808
809            // If v is a root node, pop the stack and generate an SCC
810            if lowlinks[v] == indices[v].unwrap() {
811                let mut scc = Vec::new();
812                loop {
813                    let w = stack.pop().unwrap();
814                    on_stack[w] = false;
815                    scc.push(w);
816                    if w == v {
817                        break;
818                    }
819                }
820                sccs.push(scc);
821            }
822        }
823
824        // Find SCCs for all nodes
825        for v in 0..n {
826            if indices[v].is_none() {
827                strongconnect(
828                    v,
829                    graph,
830                    &mut index,
831                    &mut stack,
832                    &mut indices,
833                    &mut lowlinks,
834                    &mut on_stack,
835                    &mut sccs,
836                );
837            }
838        }
839
840        sccs
841    }
842}
843
844#[cfg(test)]
845mod tests {
846    use super::*;
847
848    #[test]
849    fn test_priority_value_to_int() {
850        assert_eq!(PriorityValue::Critical.to_int(), 1);
851        assert_eq!(PriorityValue::High.to_int(), 2);
852        assert_eq!(PriorityValue::Medium.to_int(), 3);
853        assert_eq!(PriorityValue::Low.to_int(), 4);
854    }
855
856    #[test]
857    fn test_priority_value_from_int() {
858        assert_eq!(PriorityValue::from_int(1), Some(PriorityValue::Critical));
859        assert_eq!(PriorityValue::from_int(2), Some(PriorityValue::High));
860        assert_eq!(PriorityValue::from_int(3), Some(PriorityValue::Medium));
861        assert_eq!(PriorityValue::from_int(4), Some(PriorityValue::Low));
862        assert_eq!(PriorityValue::from_int(999), None);
863    }
864
865    #[test]
866    fn test_priority_value_as_str() {
867        assert_eq!(PriorityValue::Critical.as_str(), "critical");
868        assert_eq!(PriorityValue::High.as_str(), "high");
869        assert_eq!(PriorityValue::Medium.as_str(), "medium");
870        assert_eq!(PriorityValue::Low.as_str(), "low");
871    }
872
873    #[test]
874    fn test_plan_request_deserialization_minimal() {
875        let json = r#"{"tasks": [{"name": "Test Task"}]}"#;
876        let request: PlanRequest = serde_json::from_str(json).unwrap();
877
878        assert_eq!(request.tasks.len(), 1);
879        assert_eq!(request.tasks[0].name, "Test Task");
880        assert_eq!(request.tasks[0].spec, None);
881        assert_eq!(request.tasks[0].priority, None);
882        assert_eq!(request.tasks[0].children, None);
883        assert_eq!(request.tasks[0].depends_on, None);
884        assert_eq!(request.tasks[0].task_id, None);
885    }
886
887    #[test]
888    fn test_plan_request_deserialization_full() {
889        let json = r#"{
890            "tasks": [{
891                "name": "Parent Task",
892                "spec": "Parent spec",
893                "priority": "high",
894                "children": [{
895                    "name": "Child Task",
896                    "spec": "Child spec"
897                }],
898                "depends_on": ["Other Task"],
899                "task_id": 42
900            }]
901        }"#;
902
903        let request: PlanRequest = serde_json::from_str(json).unwrap();
904
905        assert_eq!(request.tasks.len(), 1);
906        let parent = &request.tasks[0];
907        assert_eq!(parent.name, "Parent Task");
908        assert_eq!(parent.spec, Some("Parent spec".to_string()));
909        assert_eq!(parent.priority, Some(PriorityValue::High));
910        assert_eq!(parent.task_id, Some(42));
911
912        let children = parent.children.as_ref().unwrap();
913        assert_eq!(children.len(), 1);
914        assert_eq!(children[0].name, "Child Task");
915
916        let depends = parent.depends_on.as_ref().unwrap();
917        assert_eq!(depends.len(), 1);
918        assert_eq!(depends[0], "Other Task");
919    }
920
921    #[test]
922    fn test_plan_request_serialization() {
923        let request = PlanRequest {
924            tasks: vec![TaskTree {
925                name: "Test Task".to_string(),
926                spec: Some("Test spec".to_string()),
927                priority: Some(PriorityValue::Medium),
928                children: None,
929                depends_on: None,
930                task_id: None,
931                status: None,
932                active_form: None,
933                parent_id: None,
934            }],
935        };
936
937        let json = serde_json::to_string(&request).unwrap();
938        assert!(json.contains("\"name\":\"Test Task\""));
939        assert!(json.contains("\"spec\":\"Test spec\""));
940        assert!(json.contains("\"priority\":\"medium\""));
941    }
942
943    #[test]
944    fn test_plan_result_success() {
945        let mut map = HashMap::new();
946        map.insert("Task 1".to_string(), 1);
947        map.insert("Task 2".to_string(), 2);
948
949        let result = PlanResult::success(map.clone(), 2, 0, 1, None);
950
951        assert!(result.success);
952        assert_eq!(result.task_id_map, map);
953        assert_eq!(result.created_count, 2);
954        assert_eq!(result.updated_count, 0);
955        assert_eq!(result.dependency_count, 1);
956        assert_eq!(result.focused_task, None);
957        assert_eq!(result.error, None);
958    }
959
960    #[test]
961    fn test_plan_result_error() {
962        let result = PlanResult::error("Test error");
963
964        assert!(!result.success);
965        assert_eq!(result.task_id_map.len(), 0);
966        assert_eq!(result.created_count, 0);
967        assert_eq!(result.updated_count, 0);
968        assert_eq!(result.dependency_count, 0);
969        assert_eq!(result.error, Some("Test error".to_string()));
970    }
971
972    #[test]
973    fn test_task_tree_nested() {
974        let tree = TaskTree {
975            name: "Parent".to_string(),
976            spec: None,
977            priority: None,
978            children: Some(vec![
979                TaskTree {
980                    name: "Child 1".to_string(),
981                    spec: None,
982                    priority: None,
983                    children: None,
984                    depends_on: None,
985                    task_id: None,
986                    status: None,
987                    active_form: None,
988                    parent_id: None,
989                },
990                TaskTree {
991                    name: "Child 2".to_string(),
992                    spec: None,
993                    priority: Some(PriorityValue::High),
994                    children: None,
995                    depends_on: None,
996                    task_id: None,
997                    status: None,
998                    active_form: None,
999                    parent_id: None,
1000                },
1001            ]),
1002            depends_on: None,
1003            task_id: None,
1004            status: None,
1005            active_form: None,
1006            parent_id: None,
1007        };
1008
1009        let json = serde_json::to_string_pretty(&tree).unwrap();
1010        let deserialized: TaskTree = serde_json::from_str(&json).unwrap();
1011
1012        assert_eq!(tree, deserialized);
1013        assert_eq!(deserialized.children.as_ref().unwrap().len(), 2);
1014    }
1015
1016    #[test]
1017    fn test_priority_value_case_insensitive_deserialization() {
1018        // Test lowercase
1019        let json = r#"{"name": "Test", "priority": "high"}"#;
1020        let task: TaskTree = serde_json::from_str(json).unwrap();
1021        assert_eq!(task.priority, Some(PriorityValue::High));
1022
1023        // Serde expects exact case match for rename_all = "lowercase"
1024        // So "High" would fail, which is correct behavior
1025    }
1026
1027    #[test]
1028    fn test_extract_all_names_simple() {
1029        let tasks = vec![
1030            TaskTree {
1031                name: "Task 1".to_string(),
1032                spec: None,
1033                priority: None,
1034                children: None,
1035                depends_on: None,
1036                task_id: None,
1037                status: None,
1038                active_form: None,
1039                parent_id: None,
1040            },
1041            TaskTree {
1042                name: "Task 2".to_string(),
1043                spec: None,
1044                priority: None,
1045                children: None,
1046                depends_on: None,
1047                task_id: None,
1048                status: None,
1049                active_form: None,
1050                parent_id: None,
1051            },
1052        ];
1053
1054        let names = extract_all_names(&tasks);
1055        assert_eq!(names, vec!["Task 1", "Task 2"]);
1056    }
1057
1058    #[test]
1059    fn test_extract_all_names_nested() {
1060        let tasks = vec![TaskTree {
1061            name: "Parent".to_string(),
1062            spec: None,
1063            priority: None,
1064            children: Some(vec![
1065                TaskTree {
1066                    name: "Child 1".to_string(),
1067                    spec: None,
1068                    priority: None,
1069                    children: None,
1070                    depends_on: None,
1071                    task_id: None,
1072                    status: None,
1073                    active_form: None,
1074                    parent_id: None,
1075                },
1076                TaskTree {
1077                    name: "Child 2".to_string(),
1078                    spec: None,
1079                    priority: None,
1080                    children: Some(vec![TaskTree {
1081                        name: "Grandchild".to_string(),
1082                        spec: None,
1083                        priority: None,
1084                        children: None,
1085                        depends_on: None,
1086                        task_id: None,
1087                        status: None,
1088                        active_form: None,
1089                        parent_id: None,
1090                    }]),
1091                    depends_on: None,
1092                    task_id: None,
1093                    status: None,
1094                    active_form: None,
1095                    parent_id: None,
1096                },
1097            ]),
1098            depends_on: None,
1099            task_id: None,
1100            status: None,
1101            active_form: None,
1102            parent_id: None,
1103        }];
1104
1105        let names = extract_all_names(&tasks);
1106        assert_eq!(names, vec!["Parent", "Child 1", "Child 2", "Grandchild"]);
1107    }
1108
1109    #[test]
1110    fn test_flatten_task_tree_simple() {
1111        let tasks = vec![TaskTree {
1112            name: "Task 1".to_string(),
1113            spec: Some("Spec 1".to_string()),
1114            priority: Some(PriorityValue::High),
1115            children: None,
1116            depends_on: Some(vec!["Task 0".to_string()]),
1117            task_id: None,
1118            status: None,
1119            active_form: None,
1120            parent_id: None,
1121        }];
1122
1123        let flat = flatten_task_tree(&tasks);
1124        assert_eq!(flat.len(), 1);
1125        assert_eq!(flat[0].name, "Task 1");
1126        assert_eq!(flat[0].spec, Some("Spec 1".to_string()));
1127        assert_eq!(flat[0].priority, Some(PriorityValue::High));
1128        assert_eq!(flat[0].parent_name, None);
1129        assert_eq!(flat[0].depends_on, vec!["Task 0"]);
1130    }
1131
1132    #[test]
1133    fn test_flatten_task_tree_nested() {
1134        let tasks = vec![TaskTree {
1135            name: "Parent".to_string(),
1136            spec: None,
1137            priority: None,
1138            children: Some(vec![
1139                TaskTree {
1140                    name: "Child 1".to_string(),
1141                    spec: None,
1142                    priority: None,
1143                    children: None,
1144                    depends_on: None,
1145                    task_id: None,
1146                    status: None,
1147                    active_form: None,
1148                    parent_id: None,
1149                },
1150                TaskTree {
1151                    name: "Child 2".to_string(),
1152                    spec: None,
1153                    priority: None,
1154                    children: None,
1155                    depends_on: None,
1156                    task_id: None,
1157                    status: None,
1158                    active_form: None,
1159                    parent_id: None,
1160                },
1161            ]),
1162            depends_on: None,
1163            task_id: None,
1164            status: None,
1165            active_form: None,
1166            parent_id: None,
1167        }];
1168
1169        let flat = flatten_task_tree(&tasks);
1170        assert_eq!(flat.len(), 3);
1171
1172        // Parent should have no parent_name
1173        assert_eq!(flat[0].name, "Parent");
1174        assert_eq!(flat[0].parent_name, None);
1175
1176        // Children should have Parent as parent_name
1177        assert_eq!(flat[1].name, "Child 1");
1178        assert_eq!(flat[1].parent_name, Some("Parent".to_string()));
1179
1180        assert_eq!(flat[2].name, "Child 2");
1181        assert_eq!(flat[2].parent_name, Some("Parent".to_string()));
1182    }
1183
1184    #[test]
1185    fn test_classify_operations_all_create() {
1186        let flat_tasks = vec![
1187            FlatTask {
1188                name: "Task 1".to_string(),
1189                spec: None,
1190                priority: None,
1191                parent_name: None,
1192                depends_on: vec![],
1193                task_id: None,
1194                status: None,
1195                active_form: None,
1196                explicit_parent_id: None,
1197            },
1198            FlatTask {
1199                name: "Task 2".to_string(),
1200                spec: None,
1201                priority: None,
1202                parent_name: None,
1203                depends_on: vec![],
1204                task_id: None,
1205                status: None,
1206                active_form: None,
1207                explicit_parent_id: None,
1208            },
1209        ];
1210
1211        let existing = HashMap::new();
1212        let operations = classify_operations(&flat_tasks, &existing);
1213
1214        assert_eq!(operations.len(), 2);
1215        assert!(matches!(operations[0], Operation::Create(_)));
1216        assert!(matches!(operations[1], Operation::Create(_)));
1217    }
1218
1219    #[test]
1220    fn test_classify_operations_all_update() {
1221        let flat_tasks = vec![
1222            FlatTask {
1223                name: "Task 1".to_string(),
1224                spec: None,
1225                priority: None,
1226                parent_name: None,
1227                depends_on: vec![],
1228                task_id: None,
1229                status: None,
1230                active_form: None,
1231                explicit_parent_id: None,
1232            },
1233            FlatTask {
1234                name: "Task 2".to_string(),
1235                spec: None,
1236                priority: None,
1237                parent_name: None,
1238                depends_on: vec![],
1239                task_id: None,
1240                status: None,
1241                active_form: None,
1242                explicit_parent_id: None,
1243            },
1244        ];
1245
1246        let mut existing = HashMap::new();
1247        existing.insert("Task 1".to_string(), 1);
1248        existing.insert("Task 2".to_string(), 2);
1249
1250        let operations = classify_operations(&flat_tasks, &existing);
1251
1252        assert_eq!(operations.len(), 2);
1253        assert!(matches!(
1254            operations[0],
1255            Operation::Update { task_id: 1, .. }
1256        ));
1257        assert!(matches!(
1258            operations[1],
1259            Operation::Update { task_id: 2, .. }
1260        ));
1261    }
1262
1263    #[test]
1264    fn test_classify_operations_mixed() {
1265        let flat_tasks = vec![
1266            FlatTask {
1267                name: "Existing Task".to_string(),
1268                spec: None,
1269                priority: None,
1270                parent_name: None,
1271                depends_on: vec![],
1272                task_id: None,
1273                status: None,
1274                active_form: None,
1275                explicit_parent_id: None,
1276            },
1277            FlatTask {
1278                name: "New Task".to_string(),
1279                spec: None,
1280                priority: None,
1281                parent_name: None,
1282                depends_on: vec![],
1283                task_id: None,
1284                status: None,
1285                active_form: None,
1286                explicit_parent_id: None,
1287            },
1288        ];
1289
1290        let mut existing = HashMap::new();
1291        existing.insert("Existing Task".to_string(), 42);
1292
1293        let operations = classify_operations(&flat_tasks, &existing);
1294
1295        assert_eq!(operations.len(), 2);
1296        assert!(matches!(
1297            operations[0],
1298            Operation::Update { task_id: 42, .. }
1299        ));
1300        assert!(matches!(operations[1], Operation::Create(_)));
1301    }
1302
1303    #[test]
1304    fn test_classify_operations_explicit_task_id() {
1305        let flat_tasks = vec![FlatTask {
1306            name: "Task".to_string(),
1307            spec: None,
1308            priority: None,
1309            parent_name: None,
1310            depends_on: vec![],
1311            task_id: Some(99), // Explicit task_id
1312            status: None,
1313            active_form: None,
1314            explicit_parent_id: None,
1315        }];
1316
1317        let existing = HashMap::new(); // Not in existing
1318
1319        let operations = classify_operations(&flat_tasks, &existing);
1320
1321        // Should still be update because of explicit task_id
1322        assert_eq!(operations.len(), 1);
1323        assert!(matches!(
1324            operations[0],
1325            Operation::Update { task_id: 99, .. }
1326        ));
1327    }
1328
1329    #[test]
1330    fn test_find_duplicate_names_no_duplicates() {
1331        let tasks = vec![
1332            TaskTree {
1333                name: "Task 1".to_string(),
1334                spec: None,
1335                priority: None,
1336                children: None,
1337                depends_on: None,
1338                task_id: None,
1339                status: None,
1340                active_form: None,
1341                parent_id: None,
1342            },
1343            TaskTree {
1344                name: "Task 2".to_string(),
1345                spec: None,
1346                priority: None,
1347                children: None,
1348                depends_on: None,
1349                task_id: None,
1350                status: None,
1351                active_form: None,
1352                parent_id: None,
1353            },
1354        ];
1355
1356        let duplicates = find_duplicate_names(&tasks);
1357        assert_eq!(duplicates.len(), 0);
1358    }
1359
1360    #[test]
1361    fn test_find_duplicate_names_with_duplicates() {
1362        let tasks = vec![
1363            TaskTree {
1364                name: "Duplicate".to_string(),
1365                spec: None,
1366                priority: None,
1367                children: None,
1368                depends_on: None,
1369                task_id: None,
1370                status: None,
1371                active_form: None,
1372                parent_id: None,
1373            },
1374            TaskTree {
1375                name: "Unique".to_string(),
1376                spec: None,
1377                priority: None,
1378                children: None,
1379                depends_on: None,
1380                task_id: None,
1381                status: None,
1382                active_form: None,
1383                parent_id: None,
1384            },
1385            TaskTree {
1386                name: "Duplicate".to_string(),
1387                spec: None,
1388                priority: None,
1389                children: None,
1390                depends_on: None,
1391                task_id: None,
1392                status: None,
1393                active_form: None,
1394                parent_id: None,
1395            },
1396        ];
1397
1398        let duplicates = find_duplicate_names(&tasks);
1399        assert_eq!(duplicates.len(), 1);
1400        assert_eq!(duplicates[0], "Duplicate");
1401    }
1402
1403    #[test]
1404    fn test_find_duplicate_names_nested() {
1405        let tasks = vec![TaskTree {
1406            name: "Parent".to_string(),
1407            spec: None,
1408            priority: None,
1409            children: Some(vec![TaskTree {
1410                name: "Parent".to_string(), // Duplicate name in child
1411                spec: None,
1412                priority: None,
1413                children: None,
1414                depends_on: None,
1415                task_id: None,
1416                status: None,
1417                active_form: None,
1418                parent_id: None,
1419            }]),
1420            depends_on: None,
1421            task_id: None,
1422            status: None,
1423            active_form: None,
1424            parent_id: None,
1425        }];
1426
1427        let duplicates = find_duplicate_names(&tasks);
1428        assert_eq!(duplicates.len(), 1);
1429        assert_eq!(duplicates[0], "Parent");
1430    }
1431
1432    #[test]
1433    fn test_flatten_task_tree_empty() {
1434        let tasks: Vec<TaskTree> = vec![];
1435        let flat = flatten_task_tree(&tasks);
1436        assert_eq!(flat.len(), 0);
1437    }
1438
1439    #[test]
1440    fn test_flatten_task_tree_deep_nesting() {
1441        // Create 4-level deep nesting: Root -> L1 -> L2 -> L3
1442        let tasks = vec![TaskTree {
1443            name: "Root".to_string(),
1444            spec: None,
1445            priority: None,
1446            children: Some(vec![TaskTree {
1447                name: "Level1".to_string(),
1448                spec: None,
1449                priority: None,
1450                children: Some(vec![TaskTree {
1451                    name: "Level2".to_string(),
1452                    spec: None,
1453                    priority: None,
1454                    children: Some(vec![TaskTree {
1455                        name: "Level3".to_string(),
1456                        spec: None,
1457                        priority: None,
1458                        children: None,
1459                        depends_on: None,
1460                        task_id: None,
1461                        status: None,
1462                        active_form: None,
1463                        parent_id: None,
1464                    }]),
1465                    depends_on: None,
1466                    task_id: None,
1467                    status: None,
1468                    active_form: None,
1469                    parent_id: None,
1470                }]),
1471                depends_on: None,
1472                task_id: None,
1473                status: None,
1474                active_form: None,
1475                parent_id: None,
1476            }]),
1477            depends_on: None,
1478            task_id: None,
1479            status: None,
1480            active_form: None,
1481            parent_id: None,
1482        }];
1483
1484        let flat = flatten_task_tree(&tasks);
1485        assert_eq!(flat.len(), 4);
1486
1487        // Check parent relationships
1488        assert_eq!(flat[0].name, "Root");
1489        assert_eq!(flat[0].parent_name, None);
1490
1491        assert_eq!(flat[1].name, "Level1");
1492        assert_eq!(flat[1].parent_name, Some("Root".to_string()));
1493
1494        assert_eq!(flat[2].name, "Level2");
1495        assert_eq!(flat[2].parent_name, Some("Level1".to_string()));
1496
1497        assert_eq!(flat[3].name, "Level3");
1498        assert_eq!(flat[3].parent_name, Some("Level2".to_string()));
1499    }
1500
1501    #[test]
1502    fn test_flatten_task_tree_many_siblings() {
1503        let children: Vec<TaskTree> = (0..10)
1504            .map(|i| TaskTree {
1505                name: format!("Child {}", i),
1506                spec: None,
1507                priority: None,
1508                children: None,
1509                depends_on: None,
1510                task_id: None,
1511                status: None,
1512                active_form: None,
1513                parent_id: None,
1514            })
1515            .collect();
1516
1517        let tasks = vec![TaskTree {
1518            name: "Parent".to_string(),
1519            spec: None,
1520            priority: None,
1521            children: Some(children),
1522            depends_on: None,
1523            task_id: None,
1524            status: None,
1525            active_form: None,
1526            parent_id: None,
1527        }];
1528
1529        let flat = flatten_task_tree(&tasks);
1530        assert_eq!(flat.len(), 11); // 1 parent + 10 children
1531
1532        // All children should have same parent
1533        for child in flat.iter().skip(1).take(10) {
1534            assert_eq!(child.parent_name, Some("Parent".to_string()));
1535        }
1536    }
1537
1538    #[test]
1539    fn test_flatten_task_tree_complex_mixed() {
1540        // Complex structure with multiple levels and siblings
1541        let tasks = vec![
1542            TaskTree {
1543                name: "Task 1".to_string(),
1544                spec: None,
1545                priority: None,
1546                children: Some(vec![
1547                    TaskTree {
1548                        name: "Task 1.1".to_string(),
1549                        spec: None,
1550                        priority: None,
1551                        children: None,
1552                        depends_on: None,
1553                        task_id: None,
1554                        status: None,
1555                        active_form: None,
1556                        parent_id: None,
1557                    },
1558                    TaskTree {
1559                        name: "Task 1.2".to_string(),
1560                        spec: None,
1561                        priority: None,
1562                        children: Some(vec![TaskTree {
1563                            name: "Task 1.2.1".to_string(),
1564                            spec: None,
1565                            priority: None,
1566                            children: None,
1567                            depends_on: None,
1568                            task_id: None,
1569                            status: None,
1570                            active_form: None,
1571                            parent_id: None,
1572                        }]),
1573                        depends_on: None,
1574                        task_id: None,
1575                        status: None,
1576                        active_form: None,
1577                        parent_id: None,
1578                    },
1579                ]),
1580                depends_on: None,
1581                task_id: None,
1582                status: None,
1583                active_form: None,
1584                parent_id: None,
1585            },
1586            TaskTree {
1587                name: "Task 2".to_string(),
1588                spec: None,
1589                priority: None,
1590                children: None,
1591                depends_on: Some(vec!["Task 1".to_string()]),
1592                task_id: None,
1593                status: None,
1594                active_form: None,
1595                parent_id: None,
1596            },
1597        ];
1598
1599        let flat = flatten_task_tree(&tasks);
1600        assert_eq!(flat.len(), 5);
1601
1602        // Verify structure
1603        assert_eq!(flat[0].name, "Task 1");
1604        assert_eq!(flat[0].parent_name, None);
1605
1606        assert_eq!(flat[1].name, "Task 1.1");
1607        assert_eq!(flat[1].parent_name, Some("Task 1".to_string()));
1608
1609        assert_eq!(flat[2].name, "Task 1.2");
1610        assert_eq!(flat[2].parent_name, Some("Task 1".to_string()));
1611
1612        assert_eq!(flat[3].name, "Task 1.2.1");
1613        assert_eq!(flat[3].parent_name, Some("Task 1.2".to_string()));
1614
1615        assert_eq!(flat[4].name, "Task 2");
1616        assert_eq!(flat[4].parent_name, None);
1617        assert_eq!(flat[4].depends_on, vec!["Task 1"]);
1618    }
1619
1620    #[tokio::test]
1621    async fn test_plan_executor_integration() {
1622        use crate::test_utils::test_helpers::TestContext;
1623
1624        let ctx = TestContext::new().await;
1625
1626        // Create a plan with hierarchy and dependencies
1627        let request = PlanRequest {
1628            tasks: vec![TaskTree {
1629                name: "Integration Test Plan".to_string(),
1630                spec: Some("Test plan execution end-to-end".to_string()),
1631                priority: Some(PriorityValue::High),
1632                children: Some(vec![
1633                    TaskTree {
1634                        name: "Subtask A".to_string(),
1635                        spec: Some("First subtask".to_string()),
1636                        priority: None,
1637                        children: None,
1638                        depends_on: None,
1639                        task_id: None,
1640                        status: None,
1641                        active_form: None,
1642                        parent_id: None,
1643                    },
1644                    TaskTree {
1645                        name: "Subtask B".to_string(),
1646                        spec: Some("Second subtask depends on A".to_string()),
1647                        priority: None,
1648                        children: None,
1649                        depends_on: Some(vec!["Subtask A".to_string()]),
1650                        task_id: None,
1651                        status: None,
1652                        active_form: None,
1653                        parent_id: None,
1654                    },
1655                ]),
1656                depends_on: None,
1657                task_id: None,
1658                status: None,
1659                active_form: None,
1660                parent_id: None,
1661            }],
1662        };
1663
1664        // Execute the plan
1665        let executor = PlanExecutor::new(&ctx.pool);
1666        let result = executor.execute(&request).await.unwrap();
1667
1668        // Verify success
1669        assert!(result.success, "Plan execution should succeed");
1670        assert_eq!(result.created_count, 3, "Should create 3 tasks");
1671        assert_eq!(result.updated_count, 0, "Should not update any tasks");
1672        assert_eq!(result.dependency_count, 1, "Should create 1 dependency");
1673        assert!(result.error.is_none(), "Should have no error");
1674
1675        // Verify task ID map
1676        assert_eq!(result.task_id_map.len(), 3);
1677        assert!(result.task_id_map.contains_key("Integration Test Plan"));
1678        assert!(result.task_id_map.contains_key("Subtask A"));
1679        assert!(result.task_id_map.contains_key("Subtask B"));
1680
1681        // Verify tasks were created in database
1682        let parent_id = *result.task_id_map.get("Integration Test Plan").unwrap();
1683        let subtask_a_id = *result.task_id_map.get("Subtask A").unwrap();
1684        let subtask_b_id = *result.task_id_map.get("Subtask B").unwrap();
1685
1686        // Check parent task
1687        let parent: (String, String, i64, Option<i64>) =
1688            sqlx::query_as("SELECT name, spec, priority, parent_id FROM tasks WHERE id = ?")
1689                .bind(parent_id)
1690                .fetch_one(&ctx.pool)
1691                .await
1692                .unwrap();
1693
1694        assert_eq!(parent.0, "Integration Test Plan");
1695        assert_eq!(parent.1, "Test plan execution end-to-end");
1696        assert_eq!(parent.2, 2); // High priority = 2
1697        assert_eq!(parent.3, None); // No parent
1698
1699        // Check subtask A
1700        let subtask_a: (String, Option<i64>) =
1701            sqlx::query_as(crate::sql_constants::SELECT_TASK_NAME_PARENT)
1702                .bind(subtask_a_id)
1703                .fetch_one(&ctx.pool)
1704                .await
1705                .unwrap();
1706
1707        assert_eq!(subtask_a.0, "Subtask A");
1708        assert_eq!(subtask_a.1, Some(parent_id)); // Parent should be set
1709
1710        // Check dependency
1711        let dep: (i64, i64) = sqlx::query_as(
1712            "SELECT blocking_task_id, blocked_task_id FROM dependencies WHERE blocked_task_id = ?",
1713        )
1714        .bind(subtask_b_id)
1715        .fetch_one(&ctx.pool)
1716        .await
1717        .unwrap();
1718
1719        assert_eq!(dep.0, subtask_a_id); // Blocking task
1720        assert_eq!(dep.1, subtask_b_id); // Blocked task
1721    }
1722
1723    #[tokio::test]
1724    async fn test_plan_executor_idempotency() {
1725        use crate::test_utils::test_helpers::TestContext;
1726
1727        let ctx = TestContext::new().await;
1728
1729        // Create a plan
1730        let request = PlanRequest {
1731            tasks: vec![TaskTree {
1732                name: "Idempotent Task".to_string(),
1733                spec: Some("Initial spec".to_string()),
1734                priority: Some(PriorityValue::High),
1735                children: Some(vec![
1736                    TaskTree {
1737                        name: "Child 1".to_string(),
1738                        spec: Some("Child spec 1".to_string()),
1739                        priority: None,
1740                        children: None,
1741                        depends_on: None,
1742                        task_id: None,
1743                        status: None,
1744                        active_form: None,
1745                        parent_id: None,
1746                    },
1747                    TaskTree {
1748                        name: "Child 2".to_string(),
1749                        spec: Some("Child spec 2".to_string()),
1750                        priority: Some(PriorityValue::Low),
1751                        children: None,
1752                        depends_on: None,
1753                        task_id: None,
1754                        status: None,
1755                        active_form: None,
1756                        parent_id: None,
1757                    },
1758                ]),
1759                depends_on: None,
1760                task_id: None,
1761                status: None,
1762                active_form: None,
1763                parent_id: None,
1764            }],
1765        };
1766
1767        let executor = PlanExecutor::new(&ctx.pool);
1768
1769        // First execution - should create all tasks
1770        let result1 = executor.execute(&request).await.unwrap();
1771        assert!(result1.success, "First execution should succeed");
1772        assert_eq!(result1.created_count, 3, "Should create 3 tasks");
1773        assert_eq!(result1.updated_count, 0, "Should not update any tasks");
1774        assert_eq!(result1.task_id_map.len(), 3, "Should have 3 task IDs");
1775
1776        // Get task IDs from first execution
1777        let parent_id_1 = *result1.task_id_map.get("Idempotent Task").unwrap();
1778        let child1_id_1 = *result1.task_id_map.get("Child 1").unwrap();
1779        let child2_id_1 = *result1.task_id_map.get("Child 2").unwrap();
1780
1781        // Second execution with same plan - should update all tasks (idempotent)
1782        let result2 = executor.execute(&request).await.unwrap();
1783        assert!(result2.success, "Second execution should succeed");
1784        assert_eq!(result2.created_count, 0, "Should not create any new tasks");
1785        assert_eq!(result2.updated_count, 3, "Should update all 3 tasks");
1786        assert_eq!(result2.task_id_map.len(), 3, "Should still have 3 task IDs");
1787
1788        // Task IDs should remain the same (idempotent)
1789        let parent_id_2 = *result2.task_id_map.get("Idempotent Task").unwrap();
1790        let child1_id_2 = *result2.task_id_map.get("Child 1").unwrap();
1791        let child2_id_2 = *result2.task_id_map.get("Child 2").unwrap();
1792
1793        assert_eq!(parent_id_1, parent_id_2, "Parent ID should not change");
1794        assert_eq!(child1_id_1, child1_id_2, "Child 1 ID should not change");
1795        assert_eq!(child2_id_1, child2_id_2, "Child 2 ID should not change");
1796
1797        // Verify data in database hasn't changed (spec, priority)
1798        let parent: (String, i64) = sqlx::query_as("SELECT spec, priority FROM tasks WHERE id = ?")
1799            .bind(parent_id_2)
1800            .fetch_one(&ctx.pool)
1801            .await
1802            .unwrap();
1803
1804        assert_eq!(parent.0, "Initial spec");
1805        assert_eq!(parent.1, 2); // High priority = 2
1806
1807        // Third execution with modified plan - should update with new values
1808        let modified_request = PlanRequest {
1809            tasks: vec![TaskTree {
1810                name: "Idempotent Task".to_string(),
1811                spec: Some("Updated spec".to_string()), // Changed
1812                priority: Some(PriorityValue::Critical), // Changed
1813                children: Some(vec![
1814                    TaskTree {
1815                        name: "Child 1".to_string(),
1816                        spec: Some("Updated child spec 1".to_string()), // Changed
1817                        priority: None,
1818                        children: None,
1819                        depends_on: None,
1820                        task_id: None,
1821                        status: None,
1822                        active_form: None,
1823                        parent_id: None,
1824                    },
1825                    TaskTree {
1826                        name: "Child 2".to_string(),
1827                        spec: Some("Child spec 2".to_string()), // Unchanged
1828                        priority: Some(PriorityValue::Low),
1829                        children: None,
1830                        depends_on: None,
1831                        task_id: None,
1832                        status: None,
1833                        active_form: None,
1834                        parent_id: None,
1835                    },
1836                ]),
1837                depends_on: None,
1838                task_id: None,
1839                status: None,
1840                active_form: None,
1841                parent_id: None,
1842            }],
1843        };
1844
1845        let result3 = executor.execute(&modified_request).await.unwrap();
1846        assert!(result3.success, "Third execution should succeed");
1847        assert_eq!(result3.created_count, 0, "Should not create any new tasks");
1848        assert_eq!(result3.updated_count, 3, "Should update all 3 tasks");
1849
1850        // Verify updates were applied
1851        let updated_parent: (String, i64) =
1852            sqlx::query_as("SELECT spec, priority FROM tasks WHERE id = ?")
1853                .bind(parent_id_2)
1854                .fetch_one(&ctx.pool)
1855                .await
1856                .unwrap();
1857
1858        assert_eq!(updated_parent.0, "Updated spec");
1859        assert_eq!(updated_parent.1, 1); // Critical priority = 1
1860
1861        let updated_child1: (String,) = sqlx::query_as("SELECT spec FROM tasks WHERE id = ?")
1862            .bind(child1_id_2)
1863            .fetch_one(&ctx.pool)
1864            .await
1865            .unwrap();
1866
1867        assert_eq!(updated_child1.0, "Updated child spec 1");
1868    }
1869
1870    #[tokio::test]
1871    async fn test_plan_executor_dependencies() {
1872        use crate::test_utils::test_helpers::TestContext;
1873
1874        let ctx = TestContext::new().await;
1875
1876        // Create a plan with multiple dependency relationships
1877        let request = PlanRequest {
1878            tasks: vec![
1879                TaskTree {
1880                    name: "Foundation".to_string(),
1881                    spec: Some("Base layer".to_string()),
1882                    priority: Some(PriorityValue::Critical),
1883                    children: None,
1884                    depends_on: None,
1885                    task_id: None,
1886                    status: None,
1887                    active_form: None,
1888                    parent_id: None,
1889                },
1890                TaskTree {
1891                    name: "Layer 1".to_string(),
1892                    spec: Some("Depends on Foundation".to_string()),
1893                    priority: Some(PriorityValue::High),
1894                    children: None,
1895                    depends_on: Some(vec!["Foundation".to_string()]),
1896                    task_id: None,
1897                    status: None,
1898                    active_form: None,
1899                    parent_id: None,
1900                },
1901                TaskTree {
1902                    name: "Layer 2".to_string(),
1903                    spec: Some("Depends on Layer 1".to_string()),
1904                    priority: None,
1905                    children: None,
1906                    depends_on: Some(vec!["Layer 1".to_string()]),
1907                    task_id: None,
1908                    status: None,
1909                    active_form: None,
1910                    parent_id: None,
1911                },
1912                TaskTree {
1913                    name: "Integration".to_string(),
1914                    spec: Some("Depends on both Foundation and Layer 2".to_string()),
1915                    priority: None,
1916                    children: None,
1917                    depends_on: Some(vec!["Foundation".to_string(), "Layer 2".to_string()]),
1918                    task_id: None,
1919                    status: None,
1920                    active_form: None,
1921                    parent_id: None,
1922                },
1923            ],
1924        };
1925
1926        let executor = PlanExecutor::new(&ctx.pool);
1927        let result = executor.execute(&request).await.unwrap();
1928
1929        assert!(result.success, "Plan execution should succeed");
1930        assert_eq!(result.created_count, 4, "Should create 4 tasks");
1931        assert_eq!(result.dependency_count, 4, "Should create 4 dependencies");
1932
1933        // Get task IDs
1934        let foundation_id = *result.task_id_map.get("Foundation").unwrap();
1935        let layer1_id = *result.task_id_map.get("Layer 1").unwrap();
1936        let layer2_id = *result.task_id_map.get("Layer 2").unwrap();
1937        let integration_id = *result.task_id_map.get("Integration").unwrap();
1938
1939        // Verify dependency: Layer 1 -> Foundation
1940        let deps1: Vec<(i64,)> =
1941            sqlx::query_as("SELECT blocking_task_id FROM dependencies WHERE blocked_task_id = ?")
1942                .bind(layer1_id)
1943                .fetch_all(&ctx.pool)
1944                .await
1945                .unwrap();
1946
1947        assert_eq!(deps1.len(), 1);
1948        assert_eq!(deps1[0].0, foundation_id);
1949
1950        // Verify dependency: Layer 2 -> Layer 1
1951        let deps2: Vec<(i64,)> =
1952            sqlx::query_as("SELECT blocking_task_id FROM dependencies WHERE blocked_task_id = ?")
1953                .bind(layer2_id)
1954                .fetch_all(&ctx.pool)
1955                .await
1956                .unwrap();
1957
1958        assert_eq!(deps2.len(), 1);
1959        assert_eq!(deps2[0].0, layer1_id);
1960
1961        // Verify dependencies: Integration -> Foundation, Layer 2
1962        let deps3: Vec<(i64,)> =
1963            sqlx::query_as("SELECT blocking_task_id FROM dependencies WHERE blocked_task_id = ? ORDER BY blocking_task_id")
1964                .bind(integration_id)
1965                .fetch_all(&ctx.pool)
1966                .await
1967                .unwrap();
1968
1969        assert_eq!(deps3.len(), 2);
1970        let mut blocking_ids: Vec<i64> = deps3.iter().map(|d| d.0).collect();
1971        blocking_ids.sort();
1972
1973        let mut expected_ids = vec![foundation_id, layer2_id];
1974        expected_ids.sort();
1975
1976        assert_eq!(blocking_ids, expected_ids);
1977    }
1978
1979    #[tokio::test]
1980    async fn test_plan_executor_invalid_dependency() {
1981        use crate::test_utils::test_helpers::TestContext;
1982
1983        let ctx = TestContext::new().await;
1984
1985        // Create a plan with an invalid dependency
1986        let request = PlanRequest {
1987            tasks: vec![TaskTree {
1988                name: "Task A".to_string(),
1989                spec: Some("Depends on non-existent task".to_string()),
1990                priority: None,
1991                children: None,
1992                depends_on: Some(vec!["NonExistent".to_string()]),
1993                task_id: None,
1994                status: None,
1995                active_form: None,
1996                parent_id: None,
1997            }],
1998        };
1999
2000        let executor = PlanExecutor::new(&ctx.pool);
2001        let result = executor.execute(&request).await.unwrap();
2002
2003        assert!(!result.success, "Plan execution should fail");
2004        assert!(result.error.is_some(), "Should have error message");
2005        let error = result.error.unwrap();
2006        assert!(
2007            error.contains("NonExistent"),
2008            "Error should mention the missing dependency: {}",
2009            error
2010        );
2011    }
2012
2013    #[tokio::test]
2014    async fn test_plan_executor_simple_cycle() {
2015        use crate::test_utils::test_helpers::TestContext;
2016
2017        let ctx = TestContext::new().await;
2018
2019        // Create a plan with a simple cycle: A → B → A
2020        let request = PlanRequest {
2021            tasks: vec![
2022                TaskTree {
2023                    name: "Task A".to_string(),
2024                    spec: Some("Depends on B".to_string()),
2025                    priority: None,
2026                    children: None,
2027                    depends_on: Some(vec!["Task B".to_string()]),
2028                    task_id: None,
2029                    status: None,
2030                    active_form: None,
2031                    parent_id: None,
2032                },
2033                TaskTree {
2034                    name: "Task B".to_string(),
2035                    spec: Some("Depends on A".to_string()),
2036                    priority: None,
2037                    children: None,
2038                    depends_on: Some(vec!["Task A".to_string()]),
2039                    task_id: None,
2040                    status: None,
2041                    active_form: None,
2042                    parent_id: None,
2043                },
2044            ],
2045        };
2046
2047        let executor = PlanExecutor::new(&ctx.pool);
2048        let result = executor.execute(&request).await.unwrap();
2049
2050        assert!(!result.success, "Plan execution should fail");
2051        assert!(result.error.is_some(), "Should have error message");
2052        let error = result.error.unwrap();
2053        assert!(
2054            error.contains("Circular dependency"),
2055            "Error should mention circular dependency: {}",
2056            error
2057        );
2058        assert!(
2059            error.contains("Task A") && error.contains("Task B"),
2060            "Error should mention both tasks in the cycle: {}",
2061            error
2062        );
2063    }
2064
2065    #[tokio::test]
2066    async fn test_plan_executor_complex_cycle() {
2067        use crate::test_utils::test_helpers::TestContext;
2068
2069        let ctx = TestContext::new().await;
2070
2071        // Create a plan with a complex cycle: A → B → C → A
2072        let request = PlanRequest {
2073            tasks: vec![
2074                TaskTree {
2075                    name: "Task A".to_string(),
2076                    spec: Some("Depends on B".to_string()),
2077                    priority: None,
2078                    children: None,
2079                    depends_on: Some(vec!["Task B".to_string()]),
2080                    task_id: None,
2081                    status: None,
2082                    active_form: None,
2083                    parent_id: None,
2084                },
2085                TaskTree {
2086                    name: "Task B".to_string(),
2087                    spec: Some("Depends on C".to_string()),
2088                    priority: None,
2089                    children: None,
2090                    depends_on: Some(vec!["Task C".to_string()]),
2091                    task_id: None,
2092                    status: None,
2093                    active_form: None,
2094                    parent_id: None,
2095                },
2096                TaskTree {
2097                    name: "Task C".to_string(),
2098                    spec: Some("Depends on A".to_string()),
2099                    priority: None,
2100                    children: None,
2101                    depends_on: Some(vec!["Task A".to_string()]),
2102                    task_id: None,
2103                    status: None,
2104                    active_form: None,
2105                    parent_id: None,
2106                },
2107            ],
2108        };
2109
2110        let executor = PlanExecutor::new(&ctx.pool);
2111        let result = executor.execute(&request).await.unwrap();
2112
2113        assert!(!result.success, "Plan execution should fail");
2114        assert!(result.error.is_some(), "Should have error message");
2115        let error = result.error.unwrap();
2116        assert!(
2117            error.contains("Circular dependency"),
2118            "Error should mention circular dependency: {}",
2119            error
2120        );
2121        assert!(
2122            error.contains("Task A") && error.contains("Task B") && error.contains("Task C"),
2123            "Error should mention all tasks in the cycle: {}",
2124            error
2125        );
2126    }
2127
2128    #[tokio::test]
2129    async fn test_plan_executor_valid_dag() {
2130        use crate::test_utils::test_helpers::TestContext;
2131
2132        let ctx = TestContext::new().await;
2133
2134        // Create a valid DAG: no cycles
2135        //   A
2136        //  / \
2137        // B   C
2138        //  \ /
2139        //   D
2140        let request = PlanRequest {
2141            tasks: vec![
2142                TaskTree {
2143                    name: "Task A".to_string(),
2144                    spec: Some("Root task".to_string()),
2145                    priority: None,
2146                    children: None,
2147                    depends_on: None,
2148                    task_id: None,
2149                    status: None,
2150                    active_form: None,
2151                    parent_id: None,
2152                },
2153                TaskTree {
2154                    name: "Task B".to_string(),
2155                    spec: Some("Depends on A".to_string()),
2156                    priority: None,
2157                    children: None,
2158                    depends_on: Some(vec!["Task A".to_string()]),
2159                    task_id: None,
2160                    status: None,
2161                    active_form: None,
2162                    parent_id: None,
2163                },
2164                TaskTree {
2165                    name: "Task C".to_string(),
2166                    spec: Some("Depends on A".to_string()),
2167                    priority: None,
2168                    children: None,
2169                    depends_on: Some(vec!["Task A".to_string()]),
2170                    task_id: None,
2171                    status: None,
2172                    active_form: None,
2173                    parent_id: None,
2174                },
2175                TaskTree {
2176                    name: "Task D".to_string(),
2177                    spec: Some("Depends on B and C".to_string()),
2178                    priority: None,
2179                    children: None,
2180                    depends_on: Some(vec!["Task B".to_string(), "Task C".to_string()]),
2181                    task_id: None,
2182                    status: None,
2183                    active_form: None,
2184                    parent_id: None,
2185                },
2186            ],
2187        };
2188
2189        let executor = PlanExecutor::new(&ctx.pool);
2190        let result = executor.execute(&request).await.unwrap();
2191
2192        assert!(
2193            result.success,
2194            "Plan execution should succeed for valid DAG"
2195        );
2196        assert_eq!(result.created_count, 4, "Should create 4 tasks");
2197        assert_eq!(result.dependency_count, 4, "Should create 4 dependencies");
2198    }
2199
2200    #[tokio::test]
2201    async fn test_plan_executor_self_dependency() {
2202        use crate::test_utils::test_helpers::TestContext;
2203
2204        let ctx = TestContext::new().await;
2205
2206        // Create a plan with self-dependency: A → A
2207        let request = PlanRequest {
2208            tasks: vec![TaskTree {
2209                name: "Task A".to_string(),
2210                spec: Some("Depends on itself".to_string()),
2211                priority: None,
2212                children: None,
2213                depends_on: Some(vec!["Task A".to_string()]),
2214                task_id: None,
2215                status: None,
2216                active_form: None,
2217                parent_id: None,
2218            }],
2219        };
2220
2221        let executor = PlanExecutor::new(&ctx.pool);
2222        let result = executor.execute(&request).await.unwrap();
2223
2224        assert!(
2225            !result.success,
2226            "Plan execution should fail for self-dependency"
2227        );
2228        assert!(result.error.is_some(), "Should have error message");
2229        let error = result.error.unwrap();
2230        assert!(
2231            error.contains("Circular dependency"),
2232            "Error should mention circular dependency: {}",
2233            error
2234        );
2235    }
2236
2237    // Database query tests
2238    #[tokio::test]
2239    async fn test_find_tasks_by_names_empty() {
2240        use crate::test_utils::test_helpers::TestContext;
2241
2242        let ctx = TestContext::new().await;
2243        let executor = PlanExecutor::new(&ctx.pool);
2244
2245        let result = executor.find_tasks_by_names(&[]).await.unwrap();
2246        assert!(result.is_empty(), "Empty input should return empty map");
2247    }
2248
2249    #[tokio::test]
2250    async fn test_find_tasks_by_names_partial() {
2251        use crate::test_utils::test_helpers::TestContext;
2252
2253        let ctx = TestContext::new().await;
2254        let executor = PlanExecutor::new(&ctx.pool);
2255
2256        // Create some tasks first
2257        let request = PlanRequest {
2258            tasks: vec![
2259                TaskTree {
2260                    name: "Task A".to_string(),
2261                    spec: None,
2262                    priority: None,
2263                    children: None,
2264                    depends_on: None,
2265                    task_id: None,
2266                    status: None,
2267                    active_form: None,
2268                    parent_id: None,
2269                },
2270                TaskTree {
2271                    name: "Task B".to_string(),
2272                    spec: None,
2273                    priority: None,
2274                    children: None,
2275                    depends_on: None,
2276                    task_id: None,
2277                    status: None,
2278                    active_form: None,
2279                    parent_id: None,
2280                },
2281            ],
2282        };
2283        executor.execute(&request).await.unwrap();
2284
2285        // Query for A, B, and C (C doesn't exist)
2286        let names = vec![
2287            "Task A".to_string(),
2288            "Task B".to_string(),
2289            "Task C".to_string(),
2290        ];
2291        let result = executor.find_tasks_by_names(&names).await.unwrap();
2292
2293        assert_eq!(result.len(), 2, "Should find 2 out of 3 tasks");
2294        assert!(result.contains_key("Task A"));
2295        assert!(result.contains_key("Task B"));
2296        assert!(!result.contains_key("Task C"));
2297    }
2298
2299    // Performance tests
2300    #[tokio::test]
2301    async fn test_plan_1000_tasks_performance() {
2302        use crate::test_utils::test_helpers::TestContext;
2303
2304        let ctx = TestContext::new().await;
2305        let executor = PlanExecutor::new(&ctx.pool);
2306
2307        // Generate 1000 flat tasks
2308        let mut tasks = Vec::new();
2309        for i in 0..1000 {
2310            tasks.push(TaskTree {
2311                name: format!("Task {}", i),
2312                spec: Some(format!("Spec for task {}", i)),
2313                priority: Some(PriorityValue::Medium),
2314                children: None,
2315                depends_on: None,
2316                task_id: None,
2317                status: None,
2318                active_form: None,
2319                parent_id: None,
2320            });
2321        }
2322
2323        let request = PlanRequest { tasks };
2324
2325        let start = std::time::Instant::now();
2326        let result = executor.execute(&request).await.unwrap();
2327        let duration = start.elapsed();
2328
2329        assert!(result.success);
2330        assert_eq!(result.created_count, 1000);
2331        assert!(
2332            duration.as_secs() < 10,
2333            "Should complete 1000 tasks in under 10 seconds, took {:?}",
2334            duration
2335        );
2336
2337        println!("✅ Created 1000 tasks in {:?}", duration);
2338    }
2339
2340    #[tokio::test]
2341    async fn test_plan_deep_nesting_20_levels() {
2342        use crate::test_utils::test_helpers::TestContext;
2343
2344        let ctx = TestContext::new().await;
2345        let executor = PlanExecutor::new(&ctx.pool);
2346
2347        // Generate deep nesting: 20 levels
2348        fn build_deep_tree(depth: usize, current: usize) -> TaskTree {
2349            TaskTree {
2350                name: format!("Level {}", current),
2351                spec: Some(format!("Task at depth {}", current)),
2352                priority: Some(PriorityValue::Low),
2353                children: if current < depth {
2354                    Some(vec![build_deep_tree(depth, current + 1)])
2355                } else {
2356                    None
2357                },
2358                depends_on: None,
2359                task_id: None,
2360                status: None,
2361                active_form: None,
2362                parent_id: None,
2363            }
2364        }
2365
2366        let request = PlanRequest {
2367            tasks: vec![build_deep_tree(20, 1)],
2368        };
2369
2370        let start = std::time::Instant::now();
2371        let result = executor.execute(&request).await.unwrap();
2372        let duration = start.elapsed();
2373
2374        assert!(result.success);
2375        assert_eq!(
2376            result.created_count, 20,
2377            "Should create 20 tasks (1 per level)"
2378        );
2379        assert!(
2380            duration.as_secs() < 5,
2381            "Should handle 20-level nesting in under 5 seconds, took {:?}",
2382            duration
2383        );
2384
2385        println!("✅ Created 20-level deep tree in {:?}", duration);
2386    }
2387
2388    #[test]
2389    fn test_flatten_preserves_all_fields() {
2390        let tasks = vec![TaskTree {
2391            name: "Full Task".to_string(),
2392            spec: Some("Detailed spec".to_string()),
2393            priority: Some(PriorityValue::Critical),
2394            children: None,
2395            depends_on: Some(vec!["Dep1".to_string(), "Dep2".to_string()]),
2396            task_id: Some(42),
2397            status: None,
2398            active_form: None,
2399            parent_id: None,
2400        }];
2401
2402        let flat = flatten_task_tree(&tasks);
2403        assert_eq!(flat.len(), 1);
2404
2405        let task = &flat[0];
2406        assert_eq!(task.name, "Full Task");
2407        assert_eq!(task.spec, Some("Detailed spec".to_string()));
2408        assert_eq!(task.priority, Some(PriorityValue::Critical));
2409        assert_eq!(task.depends_on, vec!["Dep1", "Dep2"]);
2410        assert_eq!(task.task_id, Some(42));
2411    }
2412}
2413
2414#[cfg(test)]
2415mod dataflow_tests {
2416    use super::*;
2417    use crate::tasks::TaskManager;
2418    use crate::test_utils::test_helpers::TestContext;
2419
2420    #[tokio::test]
2421    async fn test_complete_dataflow_status_and_active_form() {
2422        // 创建测试环境
2423        let ctx = TestContext::new().await;
2424
2425        // 第1步:使用Plan工具创建带status和active_form的任务
2426        let request = PlanRequest {
2427            tasks: vec![TaskTree {
2428                name: "Test Active Form Task".to_string(),
2429                spec: Some("Testing complete dataflow".to_string()),
2430                priority: Some(PriorityValue::High),
2431                children: None,
2432                depends_on: None,
2433                task_id: None,
2434                status: Some(TaskStatus::Doing),
2435                active_form: Some("Testing complete dataflow now".to_string()),
2436                parent_id: None,
2437            }],
2438        };
2439
2440        let executor = PlanExecutor::new(&ctx.pool);
2441        let result = executor.execute(&request).await.unwrap();
2442
2443        assert!(result.success);
2444        assert_eq!(result.created_count, 1);
2445
2446        // 第2步:使用TaskManager读取任务(模拟MCP task_list工具)
2447        let task_mgr = TaskManager::new(&ctx.pool);
2448        let result = task_mgr
2449            .find_tasks(None, None, None, None, None)
2450            .await
2451            .unwrap();
2452
2453        assert_eq!(result.tasks.len(), 1);
2454        let task = &result.tasks[0];
2455
2456        // 第3步:验证所有字段都正确传递
2457        assert_eq!(task.name, "Test Active Form Task");
2458        assert_eq!(task.status, "doing"); // InProgress maps to "doing"
2459        assert_eq!(
2460            task.active_form,
2461            Some("Testing complete dataflow now".to_string())
2462        );
2463
2464        // 第4步:验证序列化为JSON(模拟MCP输出)
2465        let json = serde_json::to_value(task).unwrap();
2466        assert_eq!(json["name"], "Test Active Form Task");
2467        assert_eq!(json["status"], "doing");
2468        assert_eq!(json["active_form"], "Testing complete dataflow now");
2469
2470        println!("✅ 完整数据流验证成功!");
2471        println!("   Plan工具写入 -> Task读取 -> JSON序列化 -> MCP输出");
2472        println!("   active_form: {:?}", task.active_form);
2473    }
2474}
2475
2476#[cfg(test)]
2477mod parent_id_tests {
2478    use super::*;
2479    use crate::test_utils::test_helpers::TestContext;
2480
2481    #[test]
2482    fn test_parent_id_json_deserialization_absent() {
2483        // Field absent → None (use default behavior)
2484        let json = r#"{"name": "Test Task"}"#;
2485        let task: TaskTree = serde_json::from_str(json).unwrap();
2486        assert_eq!(task.parent_id, None);
2487    }
2488
2489    #[test]
2490    fn test_parent_id_json_deserialization_null() {
2491        // Field is null → Some(None) (explicit root task)
2492        let json = r#"{"name": "Test Task", "parent_id": null}"#;
2493        let task: TaskTree = serde_json::from_str(json).unwrap();
2494        assert_eq!(task.parent_id, Some(None));
2495    }
2496
2497    #[test]
2498    fn test_parent_id_json_deserialization_number() {
2499        // Field is number → Some(Some(id)) (explicit parent)
2500        let json = r#"{"name": "Test Task", "parent_id": 42}"#;
2501        let task: TaskTree = serde_json::from_str(json).unwrap();
2502        assert_eq!(task.parent_id, Some(Some(42)));
2503    }
2504
2505    #[test]
2506    fn test_flatten_propagates_parent_id() {
2507        let tasks = vec![TaskTree {
2508            name: "Task with explicit parent".to_string(),
2509            spec: None,
2510            priority: None,
2511            children: None,
2512            depends_on: None,
2513            task_id: None,
2514            status: None,
2515            active_form: None,
2516            parent_id: Some(Some(99)),
2517        }];
2518
2519        let flat = flatten_task_tree(&tasks);
2520        assert_eq!(flat.len(), 1);
2521        assert_eq!(flat[0].explicit_parent_id, Some(Some(99)));
2522    }
2523
2524    #[test]
2525    fn test_flatten_propagates_null_parent_id() {
2526        let tasks = vec![TaskTree {
2527            name: "Explicit root task".to_string(),
2528            spec: None,
2529            priority: None,
2530            children: None,
2531            depends_on: None,
2532            task_id: None,
2533            status: None,
2534            active_form: None,
2535            parent_id: Some(None), // Explicit null
2536        }];
2537
2538        let flat = flatten_task_tree(&tasks);
2539        assert_eq!(flat.len(), 1);
2540        assert_eq!(flat[0].explicit_parent_id, Some(None));
2541    }
2542
2543    #[tokio::test]
2544    async fn test_explicit_parent_id_sets_parent() {
2545        let ctx = TestContext::new().await;
2546
2547        // First create a parent task
2548        let request1 = PlanRequest {
2549            tasks: vec![TaskTree {
2550                name: "Parent Task".to_string(),
2551                spec: Some("This is the parent".to_string()),
2552                priority: None,
2553                children: None,
2554                depends_on: None,
2555                task_id: None,
2556                status: Some(TaskStatus::Doing),
2557                active_form: None,
2558                parent_id: None,
2559            }],
2560        };
2561
2562        let executor = PlanExecutor::new(&ctx.pool);
2563        let result1 = executor.execute(&request1).await.unwrap();
2564        assert!(result1.success);
2565        let parent_id = *result1.task_id_map.get("Parent Task").unwrap();
2566
2567        // Now create a child task using explicit parent_id
2568        let request2 = PlanRequest {
2569            tasks: vec![TaskTree {
2570                name: "Child Task".to_string(),
2571                spec: Some("This uses explicit parent_id".to_string()),
2572                priority: None,
2573                children: None,
2574                depends_on: None,
2575                task_id: None,
2576                status: None,
2577                active_form: None,
2578                parent_id: Some(Some(parent_id)),
2579            }],
2580        };
2581
2582        let result2 = executor.execute(&request2).await.unwrap();
2583        assert!(result2.success);
2584        let child_id = *result2.task_id_map.get("Child Task").unwrap();
2585
2586        // Verify parent-child relationship
2587        let row: (Option<i64>,) = sqlx::query_as("SELECT parent_id FROM tasks WHERE id = ?")
2588            .bind(child_id)
2589            .fetch_one(&ctx.pool)
2590            .await
2591            .unwrap();
2592        assert_eq!(row.0, Some(parent_id));
2593    }
2594
2595    #[tokio::test]
2596    async fn test_explicit_null_parent_id_creates_root() {
2597        let ctx = TestContext::new().await;
2598
2599        // Create a task with explicit null parent_id (should be root)
2600        // Even when default_parent_id is set
2601        let request = PlanRequest {
2602            tasks: vec![TaskTree {
2603                name: "Explicit Root Task".to_string(),
2604                spec: Some("Should be root despite default parent".to_string()),
2605                priority: None,
2606                children: None,
2607                depends_on: None,
2608                task_id: None,
2609                status: Some(TaskStatus::Doing),
2610                active_form: None,
2611                parent_id: Some(None), // Explicit null = root
2612            }],
2613        };
2614
2615        // Create executor with a default parent
2616        // First create a "default parent" task
2617        let parent_request = PlanRequest {
2618            tasks: vec![TaskTree {
2619                name: "Default Parent".to_string(),
2620                spec: None,
2621                priority: None,
2622                children: None,
2623                depends_on: None,
2624                task_id: None,
2625                status: None,
2626                active_form: None,
2627                parent_id: None,
2628            }],
2629        };
2630        let executor = PlanExecutor::new(&ctx.pool);
2631        let parent_result = executor.execute(&parent_request).await.unwrap();
2632        let default_parent_id = *parent_result.task_id_map.get("Default Parent").unwrap();
2633
2634        // Now execute with default parent set, but our task has explicit null parent_id
2635        let executor_with_default =
2636            PlanExecutor::new(&ctx.pool).with_default_parent(default_parent_id);
2637        let result = executor_with_default.execute(&request).await.unwrap();
2638        assert!(result.success);
2639        let task_id = *result.task_id_map.get("Explicit Root Task").unwrap();
2640
2641        // Verify it's a root task (parent_id is NULL)
2642        let row: (Option<i64>,) = sqlx::query_as("SELECT parent_id FROM tasks WHERE id = ?")
2643            .bind(task_id)
2644            .fetch_one(&ctx.pool)
2645            .await
2646            .unwrap();
2647        assert_eq!(
2648            row.0, None,
2649            "Task with explicit null parent_id should be root"
2650        );
2651    }
2652
2653    #[tokio::test]
2654    async fn test_children_nesting_takes_precedence_over_parent_id() {
2655        let ctx = TestContext::new().await;
2656
2657        // Create a task hierarchy where children nesting should override parent_id
2658        let request = PlanRequest {
2659            tasks: vec![TaskTree {
2660                name: "Parent via Nesting".to_string(),
2661                spec: None,
2662                priority: None,
2663                children: Some(vec![TaskTree {
2664                    name: "Child via Nesting".to_string(),
2665                    spec: None,
2666                    priority: None,
2667                    children: None,
2668                    depends_on: None,
2669                    task_id: None,
2670                    status: None,
2671                    active_form: None,
2672                    parent_id: Some(Some(999)), // This should be ignored!
2673                }]),
2674                depends_on: None,
2675                task_id: None,
2676                status: Some(TaskStatus::Doing),
2677                active_form: None,
2678                parent_id: None,
2679            }],
2680        };
2681
2682        let executor = PlanExecutor::new(&ctx.pool);
2683        let result = executor.execute(&request).await.unwrap();
2684        assert!(result.success);
2685
2686        let parent_id = *result.task_id_map.get("Parent via Nesting").unwrap();
2687        let child_id = *result.task_id_map.get("Child via Nesting").unwrap();
2688
2689        // Verify child's parent is "Parent via Nesting", not 999
2690        let row: (Option<i64>,) = sqlx::query_as("SELECT parent_id FROM tasks WHERE id = ?")
2691            .bind(child_id)
2692            .fetch_one(&ctx.pool)
2693            .await
2694            .unwrap();
2695        assert_eq!(
2696            row.0,
2697            Some(parent_id),
2698            "Children nesting should take precedence"
2699        );
2700    }
2701
2702    #[tokio::test]
2703    async fn test_modify_existing_task_parent() {
2704        let ctx = TestContext::new().await;
2705        let executor = PlanExecutor::new(&ctx.pool);
2706
2707        // Create two independent tasks
2708        let request1 = PlanRequest {
2709            tasks: vec![
2710                TaskTree {
2711                    name: "Task A".to_string(),
2712                    spec: None,
2713                    priority: None,
2714                    children: None,
2715                    depends_on: None,
2716                    task_id: None,
2717                    status: Some(TaskStatus::Doing),
2718                    active_form: None,
2719                    parent_id: None,
2720                },
2721                TaskTree {
2722                    name: "Task B".to_string(),
2723                    spec: None,
2724                    priority: None,
2725                    children: None,
2726                    depends_on: None,
2727                    task_id: None,
2728                    status: None,
2729                    active_form: None,
2730                    parent_id: None,
2731                },
2732            ],
2733        };
2734
2735        let result1 = executor.execute(&request1).await.unwrap();
2736        assert!(result1.success);
2737        let task_a_id = *result1.task_id_map.get("Task A").unwrap();
2738        let task_b_id = *result1.task_id_map.get("Task B").unwrap();
2739
2740        // Verify both are root tasks initially
2741        let row: (Option<i64>,) = sqlx::query_as("SELECT parent_id FROM tasks WHERE id = ?")
2742            .bind(task_b_id)
2743            .fetch_one(&ctx.pool)
2744            .await
2745            .unwrap();
2746        assert_eq!(row.0, None, "Task B should initially be root");
2747
2748        // Now update Task B to be a child of Task A using parent_id
2749        let request2 = PlanRequest {
2750            tasks: vec![TaskTree {
2751                name: "Task B".to_string(), // Same name = update
2752                spec: None,
2753                priority: None,
2754                children: None,
2755                depends_on: None,
2756                task_id: None,
2757                status: None,
2758                active_form: None,
2759                parent_id: Some(Some(task_a_id)), // Set parent to Task A
2760            }],
2761        };
2762
2763        let result2 = executor.execute(&request2).await.unwrap();
2764        assert!(result2.success);
2765        assert_eq!(result2.updated_count, 1, "Should update existing task");
2766
2767        // Verify Task B is now a child of Task A
2768        let row: (Option<i64>,) = sqlx::query_as("SELECT parent_id FROM tasks WHERE id = ?")
2769            .bind(task_b_id)
2770            .fetch_one(&ctx.pool)
2771            .await
2772            .unwrap();
2773        assert_eq!(
2774            row.0,
2775            Some(task_a_id),
2776            "Task B should now be child of Task A"
2777        );
2778    }
2779}