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