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