1use crate::tools::file_ops::hematite_dir;
2use serde_json::{json, Value};
3use std::fs;
4use std::path::PathBuf;
5
6pub async fn manage_tasks(args: &Value) -> Result<String, String> {
9 let action = args
10 .get("action")
11 .and_then(|v| v.as_str())
12 .unwrap_or("list");
13 let task_path = hematite_dir().join("TASK.md");
14
15 match action {
16 "list" => list_tasks(&task_path),
17 "add" => {
18 let title = args
19 .get("title")
20 .and_then(|v| v.as_str())
21 .ok_or("manage_tasks: 'title' required for 'add'")?;
22 add_task(&task_path, title)
23 }
24 "update" => {
25 let id = args
26 .get("id")
27 .and_then(|v| v.as_u64())
28 .ok_or("manage_tasks: 'id' required for 'update'")? as usize;
29 let status = args
30 .get("status")
31 .and_then(|v| v.as_str())
32 .ok_or("manage_tasks: 'status' ([ ], [/], [x]) required for 'update'")?;
33 update_task(&task_path, id, status)
34 }
35 "remove" => {
36 let id = args
37 .get("id")
38 .and_then(|v| v.as_u64())
39 .ok_or("manage_tasks: 'id' required for 'remove'")? as usize;
40 remove_task(&task_path, id)
41 }
42 _ => Err(format!("manage_tasks: unknown action '{action}'")),
43 }
44}
45
46fn list_tasks(path: &PathBuf) -> Result<String, String> {
47 if !path.exists() {
48 return Ok("No task ledger found. Use 'add' to start tracking mission goals.".into());
49 }
50 let content = fs::read_to_string(path).map_err(|e| format!("Failed to read tasks: {e}"))?;
51 Ok(format!(
52 "--- TASK LEDGER (.hematite/TASK.md) ---\n\n{}",
53 content
54 ))
55}
56
57fn add_task(path: &PathBuf, title: &str) -> Result<String, String> {
58 let mut tasks = if path.exists() {
59 fs::read_to_string(path).unwrap_or_default()
60 } else {
61 String::new()
62 };
63
64 if !tasks.is_empty() && !tasks.ends_with('\n') {
65 tasks.push('\n');
66 }
67 tasks.push_str(&format!("- [ ] {}\n", title));
68
69 fs::create_dir_all(path.parent().expect("Invalid task path")).map_err(|e| e.to_string())?;
70 fs::write(path, &tasks).map_err(|e| format!("Failed to write task: {e}"))?;
71
72 Ok(format!("Added task: [ ] {}", title))
73}
74
75fn update_task(path: &PathBuf, id: usize, status: &str) -> Result<String, String> {
76 if !path.exists() {
77 return Err("Task ledger not found".into());
78 }
79 let content = fs::read_to_string(path).map_err(|e| e.to_string())?;
80 let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
81
82 if id < 1 || id > lines.len() {
83 return Err(format!(
84 "Invalid task ID {id}. Ledger has {} items.",
85 lines.len()
86 ));
87 }
88
89 let line = &mut lines[id - 1];
90 if line.starts_with("- [") && line.len() >= 5 {
91 let new_line = format!(
93 "- [{}] {}",
94 status.trim_matches(|c| c == '[' || c == ']' || c == ' '),
95 &line[6..]
96 );
97 *line = new_line;
98 } else {
99 return Err("Target line is not a valid task format".into());
100 }
101
102 fs::write(path, lines.join("\n") + "\n").map_err(|e| e.to_string())?;
103 Ok(format!("Updated task {id} to status [{}]", status))
104}
105
106fn remove_task(path: &PathBuf, id: usize) -> Result<String, String> {
107 if !path.exists() {
108 return Err("Task ledger not found".into());
109 }
110 let content = fs::read_to_string(path).map_err(|e| e.to_string())?;
111 let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
112
113 if id < 1 || id > lines.len() {
114 return Err(format!("Invalid task ID {id}."));
115 }
116
117 let removed = lines.remove(id - 1);
118 fs::write(path, lines.join("\n") + "\n").map_err(|e| e.to_string())?;
119 Ok(format!("Removed task: {}", removed))
120}
121
122pub fn get_tasks_params() -> Value {
123 json!({
124 "type": "object",
125 "properties": {
126 "action": {
127 "type": "string",
128 "description": "The action to perform: 'list', 'add', 'update', 'remove'.",
129 "enum": ["list", "add", "update", "remove"]
130 },
131 "id": {
132 "type": "integer",
133 "description": "The 1-based ID of the task to update or remove."
134 },
135 "title": {
136 "type": "string",
137 "description": "The description of the task (required for 'add')."
138 },
139 "status": {
140 "type": "string",
141 "description": "The status to set: '[ ]' (todo), '[/]' (in-progress), '[x]' (done).",
142 "enum": [" ", "/", "x", "[ ]", "[/]", "[x]"]
143 }
144 },
145 "required": ["action"]
146 })
147}