package taskwarrior import ( "log/slog" ) // TaskNode represents a task in the tree structure type TaskNode struct { Task *Task Children []*TaskNode Parent *TaskNode Depth int } // TaskTree manages the hierarchical task structure type TaskTree struct { Nodes map[string]*TaskNode // UUID -> TaskNode Roots []*TaskNode // Top-level tasks (no parent) FlatList []*TaskNode // Flattened tree in display order } // BuildTaskTree constructs a tree from a flat list of tasks // Three-pass algorithm: // 1. Create all nodes // 2. Establish parent-child relationships // 3. Calculate depths and flatten tree func BuildTaskTree(tasks Tasks) *TaskTree { tree := &TaskTree{ Nodes: make(map[string]*TaskNode), Roots: make([]*TaskNode, 0), FlatList: make([]*TaskNode, 0), } // Pass 1: Create all nodes for _, task := range tasks { node := &TaskNode{ Task: task, Children: make([]*TaskNode, 0), Parent: nil, Depth: 0, } tree.Nodes[task.Uuid] = node } // Pass 2: Establish parent-child relationships // Iterate over original tasks slice to preserve order for _, task := range tasks { node := tree.Nodes[task.Uuid] parentUUID := getParentUUID(node.Task) if parentUUID == "" { // No parent, this is a root task tree.Roots = append(tree.Roots, node) } else { // Find parent node parentNode, exists := tree.Nodes[parentUUID] if !exists { // Orphaned task - missing parent slog.Warn("Task has missing parent", "task_uuid", node.Task.Uuid, "parent_uuid", parentUUID, "task_desc", node.Task.Description) // Treat as root (graceful degradation) tree.Roots = append(tree.Roots, node) } else { // Establish relationship node.Parent = parentNode parentNode.Children = append(parentNode.Children, node) } } } // Pass 3: Calculate depths and flatten tree for _, root := range tree.Roots { flattenNode(root, 0, &tree.FlatList) } return tree } // getParentUUID extracts the parent UUID from a task's UDAs func getParentUUID(task *Task) string { if task.Udas == nil { return "" } parentVal, exists := task.Udas["parenttask"] if !exists { return "" } // Parent UDA is stored as a string if parentStr, ok := parentVal.(string); ok { return parentStr } return "" } // flattenNode recursively flattens the tree in depth-first order func flattenNode(node *TaskNode, depth int, flatList *[]*TaskNode) { node.Depth = depth *flatList = append(*flatList, node) // Recursively flatten children for _, child := range node.Children { flattenNode(child, depth+1, flatList) } } // GetChildrenStatus returns completed/total counts for a parent task func (tn *TaskNode) GetChildrenStatus() (completed int, total int) { total = len(tn.Children) completed = 0 for _, child := range tn.Children { if child.Task.Status == "completed" { completed++ } } return completed, total } // HasChildren returns true if the node has any children func (tn *TaskNode) HasChildren() bool { return len(tn.Children) > 0 }