package common import ( "log/slog" "tasksquire/taskwarrior" "tasksquire/timewarrior" ) // FindTaskByUUID queries Taskwarrior for a task with the given UUID. // Returns nil if not found. func FindTaskByUUID(tw taskwarrior.TaskWarrior, uuid string) *taskwarrior.Task { if uuid == "" { return nil } // Use empty report to query by UUID filter report := &taskwarrior.Report{Name: ""} tasks := tw.GetTasks(report, "uuid:"+uuid) if len(tasks) > 0 { return tasks[0] } return nil } // SyncIntervalToTask synchronizes a Timewarrior interval's state to the corresponding Taskwarrior task. // Action should be "start" or "stop". // This function is idempotent and handles edge cases gracefully. func SyncIntervalToTask(interval *timewarrior.Interval, tw taskwarrior.TaskWarrior, action string) { if interval == nil { return } // Extract UUID from interval tags uuid := timewarrior.ExtractUUID(interval.Tags) if uuid == "" { slog.Debug("Interval has no UUID tag, skipping task sync", "intervalID", interval.ID) return } // Find corresponding task task := FindTaskByUUID(tw, uuid) if task == nil { slog.Warn("Task not found for UUID, skipping sync", "uuid", uuid) return } // Perform sync action switch action { case "start": // Start task if it's pending (idempotent - taskwarrior handles already-started tasks) if task.Status == "pending" { slog.Info("Starting Taskwarrior task from interval", "uuid", uuid, "description", task.Description, "alreadyStarted", task.Start != "") tw.StartTask(task) } else { slog.Debug("Task not pending, skipping start", "uuid", uuid, "status", task.Status) } case "stop": // Only stop if task is pending and currently started if task.Status == "pending" && task.Start != "" { slog.Info("Stopping Taskwarrior task from interval", "uuid", uuid, "description", task.Description) tw.StopTask(task) } else { slog.Debug("Task not started or not pending, skipping stop", "uuid", uuid, "status", task.Status, "hasStart", task.Start != "") } default: slog.Error("Unknown sync action", "action", action) } }