1use serde::{Deserialize, Serialize};
2use std::fmt::Write as _;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct HematiteTask {
7 pub id: usize,
8 pub text: String,
9 pub done: bool,
10}
11
12fn store_path() -> PathBuf {
13 crate::tools::file_ops::hematite_dir().join("tasks.json")
14}
15
16pub fn load() -> Vec<HematiteTask> {
17 let path = store_path();
18 let Ok(raw) = std::fs::read_to_string(&path) else {
19 return Vec::new();
20 };
21 serde_json::from_str(&raw).unwrap_or_default()
22}
23
24fn save(tasks: &[HematiteTask]) {
25 let path = store_path();
26 if let Some(parent) = path.parent() {
27 let _ = std::fs::create_dir_all(parent);
28 }
29 if let Ok(json) = serde_json::to_string_pretty(tasks) {
30 let _ = std::fs::write(&path, json);
31 }
32}
33
34pub fn add(text: &str) -> Vec<HematiteTask> {
35 let mut tasks = load();
36 let next_id = tasks.iter().map(|t| t.id).max().unwrap_or(0) + 1;
37 tasks.push(HematiteTask {
38 id: next_id,
39 text: text.trim().to_string(),
40 done: false,
41 });
42 save(&tasks);
43 tasks
44}
45
46pub fn mark_done(n: usize) -> Result<Vec<HematiteTask>, String> {
47 let mut tasks = load();
48 let task = tasks
49 .iter_mut()
50 .find(|t| t.id == n)
51 .ok_or_else(|| format!("No task with id {}.", n))?;
52 task.done = true;
53 save(&tasks);
54 Ok(tasks)
55}
56
57pub fn remove(n: usize) -> Result<Vec<HematiteTask>, String> {
58 let mut tasks = load();
59 let before = tasks.len();
60 tasks.retain(|t| t.id != n);
61 if tasks.len() == before {
62 return Err(format!("No task with id {}.", n));
63 }
64 save(&tasks);
65 Ok(tasks)
66}
67
68pub fn clear() {
69 save(&[]);
70}
71
72pub fn render_prompt_block(tasks: &[HematiteTask]) -> Option<String> {
74 if tasks.is_empty() {
75 return None;
76 }
77 let mut out = String::from("## Active Task List\n");
78 for t in tasks {
79 let mark = if t.done { "[x]" } else { "[ ]" };
80 let _ = writeln!(out, "{} {}. {}", mark, t.id, t.text);
81 }
82 out.push_str(
83 "\nWhen you complete a task, let the user know and suggest running `/task done <N>`.",
84 );
85 Some(out)
86}
87
88pub fn render_list(tasks: &[HematiteTask]) -> String {
90 if tasks.is_empty() {
91 return "No tasks. Use `/task add <text>` to add one.".to_string();
92 }
93 let mut out = String::from("## Task List\n\n");
94 for t in tasks {
95 let mark = if t.done { "[x]" } else { "[ ]" };
96 let _ = writeln!(out, "{} **{}**. {}", mark, t.id, t.text);
97 }
98 out
99}