use crate::core::model::{Category, ColorTag, State, SubTask, Todo};
use crate::core::store::Store;
use anyhow::{bail, Result};
use chrono::Local;
pub fn add_todo(store: &dyn Store, category: Category, title: String, notes: Option<String>) -> Result<u64> {
let mut state = store.load()?;
let id = state.next_id;
state.next_id += 1;
let now = Local::now().timestamp();
let todo = Todo {
id,
title,
notes,
created_at: now,
updated_at: now,
color: ColorTag::None,
subtasks: Vec::new(),
};
state.get_category_mut(category).push(todo);
store.save(&state)?;
Ok(id)
}
pub fn edit_todo(store: &dyn Store, id: u64, title: Option<String>, notes: Option<String>) -> Result<()> {
let mut state = store.load()?;
let now = Local::now().timestamp();
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
if let Some(todo) = vec.iter_mut().find(|t| t.id == id) {
if let Some(t) = title { todo.title = t; }
if let Some(n) = notes { todo.notes = Some(n); }
todo.updated_at = now;
store.save(&state)?;
return Ok(());
}
}
bail!("Todo not found: {}", id)
}
pub fn move_todo(store: &dyn Store, id: u64, target_cat: Category) -> Result<()> {
let mut state = store.load()?;
let now = Local::now().timestamp();
let mut found_todo: Option<Todo> = None;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
if let Some(idx) = vec.iter().position(|t| t.id == id) {
found_todo = Some(vec.remove(idx));
break;
}
}
if let Some(mut todo) = found_todo {
todo.updated_at = now;
state.get_category_mut(target_cat).push(todo);
store.save(&state)?;
Ok(())
} else {
bail!("Todo not found: {}", id)
}
}
pub fn remove_todo(store: &dyn Store, id: u64) -> Result<()> {
let mut state = store.load()?;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
if let Some(idx) = vec.iter().position(|t| t.id == id) {
vec.remove(idx);
store.save(&state)?;
return Ok(());
}
}
bail!("Todo not found: {}", id)
}
pub fn toggle_done(store: &dyn Store, id: u64) -> Result<()> {
move_todo(store, id, Category::Completed)
}
pub fn reorder_item(store: &dyn Store, id: u64, move_up: bool) -> Result<()> {
let mut state = store.load()?;
let now = Local::now().timestamp();
let mut modified = false;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
if let Some(idx) = vec.iter().position(|t| t.id == id) {
if move_up {
if idx > 0 {
vec.swap(idx, idx - 1);
vec[idx].updated_at = now;
vec[idx-1].updated_at = now;
modified = true;
}
} else {
if idx < vec.len() - 1 {
vec.swap(idx, idx + 1);
vec[idx].updated_at = now;
vec[idx+1].updated_at = now;
modified = true;
}
}
if modified { break; }
}
for todo in vec.iter_mut() {
if reorder_in_subs(&mut todo.subtasks, id, move_up, now) {
todo.updated_at = now;
modified = true;
break;
}
}
if modified { break; }
}
if modified {
store.save(&state)?;
Ok(())
} else {
Ok(())
}
}
fn reorder_in_subs(subs: &mut Vec<SubTask>, id: u64, move_up: bool, now: i64) -> bool {
if let Some(idx) = subs.iter().position(|s| s.id == id) {
if move_up {
if idx > 0 {
subs.swap(idx, idx - 1);
subs[idx].updated_at = now;
subs[idx-1].updated_at = now;
return true;
}
} else {
if idx < subs.len() - 1 {
subs.swap(idx, idx + 1);
subs[idx].updated_at = now;
subs[idx+1].updated_at = now;
return true;
}
}
return false; }
for sub in subs.iter_mut() {
if reorder_in_subs(&mut sub.subtasks, id, move_up, now) {
sub.updated_at = now;
return true;
}
}
false
}
pub fn add_subtask(store: &dyn Store, parent_id: u64, title: String, notes: Option<String>) -> Result<u64> {
let mut state = store.load()?;
let id = state.next_id;
state.next_id += 1;
let now = Local::now().timestamp();
let sub = SubTask {
id,
title,
done: false,
created_at: now,
updated_at: now,
color: ColorTag::None,
notes,
subtasks: Vec::new(),
};
let mut added = false;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
for todo in vec.iter_mut() {
if todo.id == parent_id {
todo.subtasks.push(sub.clone());
todo.updated_at = now;
added = true;
break;
}
if add_to_sub(&mut todo.subtasks, parent_id, &sub, now) {
todo.updated_at = now;
added = true;
break;
}
}
if added { break; }
}
if added {
store.save(&state)?;
Ok(id)
} else {
bail!("Parent not found: {}", parent_id)
}
}
fn add_to_sub(subs: &mut Vec<SubTask>, parent_id: u64, new_sub: &SubTask, now: i64) -> bool {
for sub in subs.iter_mut() {
if sub.id == parent_id {
sub.subtasks.push(new_sub.clone());
sub.updated_at = now;
return true;
}
if add_to_sub(&mut sub.subtasks, parent_id, new_sub, now) {
sub.updated_at = now;
return true;
}
}
false
}
pub fn edit_subtask(store: &dyn Store, parent_id: u64, sub_id: u64, title: Option<String>, done: Option<bool>, notes: Option<String>) -> Result<()> {
let mut state = store.load()?;
let now = Local::now().timestamp();
let mut found = false;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
for todo in vec.iter_mut() {
if edit_in_subs(&mut todo.subtasks, sub_id, &title, done, ¬es, now) {
todo.updated_at = now;
found = true;
break;
}
}
if found { break; }
}
if found {
store.save(&state)?;
Ok(())
} else {
bail!("Subtask not found: {}", sub_id)
}
}
fn edit_in_subs(subs: &mut Vec<SubTask>, sub_id: u64, title: &Option<String>, done: Option<bool>, notes: &Option<String>, now: i64) -> bool {
for sub in subs.iter_mut() {
if sub.id == sub_id {
if let Some(t) = title { sub.title = t.clone(); }
if let Some(d) = done { sub.done = d; }
if let Some(n) = notes { sub.notes = Some(n.clone()); }
sub.updated_at = now;
return true;
}
if edit_in_subs(&mut sub.subtasks, sub_id, title, done, notes, now) {
sub.updated_at = now;
return true;
}
}
false
}
pub fn remove_subtask(store: &dyn Store, _parent_id: u64, sub_id: u64) -> Result<()> {
let mut state = store.load()?;
let now = Local::now().timestamp();
let mut found = false;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
for todo in vec.iter_mut() {
if remove_from_subs(&mut todo.subtasks, sub_id, now) {
todo.updated_at = now;
found = true;
break;
}
}
if found { break; }
}
if found {
store.save(&state)?;
Ok(())
} else {
bail!("Subtask not found: {}", sub_id)
}
}
fn remove_from_subs(subs: &mut Vec<SubTask>, sub_id: u64, now: i64) -> bool {
if let Some(idx) = subs.iter().position(|s| s.id == sub_id) {
subs.remove(idx);
return true;
}
for sub in subs.iter_mut() {
if remove_from_subs(&mut sub.subtasks, sub_id, now) {
sub.updated_at = now;
return true;
}
}
false
}
pub fn set_color(store: &dyn Store, id: u64, color: ColorTag) -> Result<()> {
let mut state = store.load()?;
let now = Local::now().timestamp();
let mut found = false;
for cat in State::all_categories() {
let vec = state.get_category_mut(cat);
if let Some(todo) = vec.iter_mut().find(|t| t.id == id) {
todo.color = color;
todo.updated_at = now;
found = true;
} else {
for todo in vec.iter_mut() {
if set_color_in_subs(&mut todo.subtasks, id, color, now) {
todo.updated_at = now;
found = true;
break;
}
}
}
if found { break; }
}
if found {
store.save(&state)?;
Ok(())
} else {
bail!("Item not found: {}", id)
}
}
fn set_color_in_subs(subs: &mut Vec<SubTask>, id: u64, color: ColorTag, now: i64) -> bool {
for sub in subs.iter_mut() {
if sub.id == id {
sub.color = color;
sub.updated_at = now;
return true;
}
if set_color_in_subs(&mut sub.subtasks, id, color, now) {
sub.updated_at = now;
return true;
}
}
false
}