use super::funejson::Value; use super::todo_item::Todo; use super::todo_item_validate_draft::is_iso_date; use super::todo_item_validate_todo::{todos_from_value, todos_to_value}; use super::todo_list_add_todo::require_utc_now; use super::todo_list_update_todo::index_of_todo; use super::todo_next_occurrence::next_occurrence; /// Refuse a today that is not a real YYYY-MM-DD date, whether or not a repeat needs it. pub fn require_today(today: &str) { if !is_iso_date(today) { panic!("today must be a date like 2026-09-28"); } } /// Complete one open todo. A one-off is done at `now`; a repeating one stays /// open and rolls forward to its next occurrence (todo.next-occurrence). pub fn complete_todo(todo: &Todo, now: &str, today: &str) -> Todo { let mut next = todo.clone(); match (&todo.recurrence, todo.due.as_deref()) { (None, _) => { next.done = true; next.completed_at = Some(now.to_string()); } (Some(_), None) => panic!("repeating todo \"{}\" has no due date", todo.id), (Some(rule), Some(due)) => { next.done = false; next.completed_at = None; next.due = Some(next_occurrence(rule, due, today)); } } next } /// Tick or untick one todo. Reopening clears the completion time. /// /// # Panics /// On an unknown id, a `now` that is not a UTC timestamp or a `today` that is /// not a date. pub fn toggle_todo(todos: &[Todo], id: &str, now: &str, today: &str) -> Vec { let at = index_of_todo(todos, id); require_utc_now(now); require_today(today); let mut out = todos.to_vec(); if out[at].done { out[at].done = false; out[at].completed_at = None; } else { out[at] = complete_todo(&todos[at], now, today); } out } pub fn fune_vector(args: &[Value]) -> Value { let todos = todos_from_value(&args[0]); todos_to_value(&toggle_todo(&todos, args[1].as_str(), args[2].as_str(), args[3].as_str())) }