use crate::input::{AddTodoInput, SearchTodoInput, UpdateTodoInput};
use crate::sortby::SortBy;
use crate::storage::{load_todos_from_file, save_todos_to_file};
use crate::todo::Todo;
use chrono::Utc;
use colored::*;
use uuid::Uuid;
pub fn add_todo_cli(file_path: &str, todo_input: AddTodoInput) {
let mut todos = load_todos_from_file(file_path);
let todo = Todo::new(todo_input);
if todos.contains_key(&todo.id) {
println!(
"{}",
"❌ A todo with this ID already exists. Try again."
.red()
.bold()
);
return;
}
todos.insert(todo.id, todo);
save_todos_to_file(&todos, file_path);
println!("{}", "✅ Todo added successfully".green().bold());
}
pub fn list_todos_cli(file_path: &str, sort_by: &SortBy) {
let todos = load_todos_from_file(file_path);
if todos.is_empty() {
println!("{}", "❌ No todos found.".red().bold());
return;
}
let mut todo_list: Vec<&Todo> = todos.values().collect();
match sort_by {
SortBy::Priority => todo_list.sort_by_key(|t| t.priority),
SortBy::Status => todo_list.sort_by_key(|t| t.status),
SortBy::Created => todo_list.sort_by_key(|t| t.created_at),
SortBy::DueDate => todo_list.sort_by_key(|t| t.due_date),
SortBy::Overdue => {
let now = Utc::now();
todo_list.sort_by_key(|t| match t.due_date {
Some(due) if due < now => 0,
Some(_) => 1,
None => 2,
});
}
}
println!(
"{}",
format!("--- Todos (sorted by {sort_by}) ---")
.bold()
.blue()
.underline()
);
for todo in &todo_list {
let priority_str = todo.priority.to_string();
let priority_color = match priority_str.as_str() {
"High" => priority_str.red().bold(),
"Medium" => priority_str.yellow(),
"Low" => priority_str.green(),
_ => priority_str.normal(),
};
let status_str = todo.status.to_string();
let status_color = match status_str.as_str() {
"Pending" => status_str.red().bold(),
"In Progress" => status_str.yellow(),
"Done" => status_str.green(),
_ => status_str.normal(),
};
println!("{:<10} {}", "ID:".bold(), todo.id.to_string().cyan());
println!("{:<10} {}", "Title:".bold(), todo.title.bold());
println!("{:<10} {}", "Priority:".bold(), priority_color);
println!("{:<10} {}", "Status:".bold(), status_color);
println!(
"{:<10} {}",
"Description:".bold(),
todo.description.as_deref().unwrap_or("None")
);
println!("{:<10} {}", "Created:".bold(), todo.created_at);
if let Some(due) = todo.due_date {
let mut overdue_str = due.to_string();
overdue_str.push_str("⚠️ Overdue!");
if due < Utc::now() {
println!("{:<10} {}", "Due Date:".bold(), overdue_str.red());
} else {
println!("{:<10} {}", "Due Date:".bold(), due.to_string().yellow());
}
}
if let Some(tags) = &todo.tags {
println!("{:<10} {}", "Tags:".bold(), tags.join(", ").cyan());
}
if let Some(pid) = todo.parent_id {
println!("{:<10} {}", "Parent ID:".bold(), pid.to_string().blue());
}
if let Some(subs) = &todo.subtasks {
let subs_str: Vec<String> = subs.iter().map(|id| id.to_string()).collect();
println!(
"{:<10} {}",
"Subtasks:".bold(),
subs_str.join(", ").purple()
);
}
if let Some(rec) = &todo.recurrence {
println!("{:<10} {}", "Recurrence:".bold(), rec.to_string().yellow());
}
println!();
}
}
pub fn search_todo_cli(file_path: &str, todo_input: SearchTodoInput) {
let todos = load_todos_from_file(file_path);
let mut results: Vec<&Todo> = todos.values().collect();
if let Some(id_str) = todo_input.id {
if let Ok(uuid) = Uuid::parse_str(&id_str) {
results.retain(|t| t.id == uuid);
} else {
println!("{}", format!("⚠️ Invalid UUID format: {id_str}").red());
return;
}
}
if let Some(title_query) = todo_input.title {
let query_lower = title_query.to_lowercase();
results.retain(|t| t.title.to_lowercase().contains(&query_lower));
}
if let Some(p) = todo_input.priority {
results.retain(|t| t.priority == p);
}
if let Some(s) = todo_input.status {
results.retain(|t| t.status == s);
}
if let Some(due) = todo_input.due_date {
results.retain(|t| t.due_date == Some(due));
}
if let Some(rec) = todo_input.recurrence {
results.retain(|t| t.recurrence.as_ref() == Some(&rec));
}
if let Some(tag_lit) = todo_input.tags {
results.retain(|t| {
if let Some(todo_tags) = &t.tags {
tag_lit.iter().all(|tag| todo_tags.contains(tag))
} else {
false
}
});
}
if let Some(pid) = todo_input.parent_id {
results.retain(|t| t.parent_id == Some(pid));
}
if results.is_empty() {
println!("{}", "⚠️ No todos found with the given filters.".yellow());
} else {
println!(
"{}",
format!("Found {} todo(s):", results.len()).bold().blue()
);
for todo in results {
let priority_str = todo.priority.to_string();
let priority_color = match priority_str.as_str() {
"High" => priority_str.red().bold(),
"Medium" => priority_str.yellow(),
"Low" => priority_str.green(),
_ => priority_str.normal(),
};
let status_str = todo.status.to_string();
let status_color = match status_str.as_str() {
"Pending" => status_str.red().bold(),
"In Progress" => status_str.yellow(),
"Done" => status_str.green(),
_ => status_str.normal(),
};
println!("{:<10} {}", "ID:".bold(), todo.id.to_string().cyan());
println!("{:<10} {}", "Title:".bold(), todo.title.bold());
println!("{:<10} {}", "Priority:".bold(), priority_color);
println!("{:<10} {}", "Status:".bold(), status_color);
println!(
"{:<10} {}",
"Description:".bold(),
todo.description.as_deref().unwrap_or("None")
);
println!("{:<10} {}", "Created:".bold(), todo.created_at);
if let Some(due) = todo.due_date {
let mut overdue_str = due.to_string();
overdue_str.push_str(" ⚠️ Overdue!");
if due < Utc::now() {
println!("{:<10} {}", "Due Date:".bold(), overdue_str.red());
} else {
println!("{:<10} {}", "Due Date:".bold(), due.to_string().yellow());
}
}
if let Some(tags) = &todo.tags {
println!("{:<10} {}", "Tags:".bold(), tags.join(", ").cyan());
}
if let Some(pid) = todo.parent_id {
println!("{:<10} {}", "Parent ID:".bold(), pid.to_string().blue());
}
if let Some(subs) = &todo.subtasks {
let subs_str: Vec<String> = subs.iter().map(|id| id.to_string()).collect();
println!(
"{:<10} {}",
"Subtasks:".bold(),
subs_str.join(", ").purple()
);
}
if let Some(rec) = &todo.recurrence {
println!("{:<10} {}", "Recurrence:".bold(), rec.to_string().yellow());
}
println!();
}
}
}
pub fn update_todo_cli(file_path: &str, todo_input: UpdateTodoInput) -> bool {
let mut todos = load_todos_from_file(file_path);
if let Some(todo) = todos.get_mut(&todo_input.id) {
if let Some(title) = todo_input.new_title {
todo.title = title
}
if let Some(desc) = todo_input.new_description {
todo.description = Some(desc)
}
if let Some(p) = todo_input.new_priority {
todo.priority = p;
}
if let Some(s) = todo_input.new_status {
todo.status = s;
}
if let Some(d) = todo_input.new_due_date {
todo.due_date = Some(d);
}
if let Some(tags) = todo_input.new_tags {
todo.tags = Some(tags);
}
if let Some(rec) = todo_input.new_recurrence {
todo.recurrence = Some(rec);
}
if let Some(pid) = todo_input.new_parent_id {
todo.parent_id = Some(pid);
}
if let Some(subs) = todo_input.new_subtasks {
todo.subtasks = Some(subs);
}
save_todos_to_file(&todos, file_path);
true
} else {
false
}
}
pub fn delete_todo_cli(file_path: &str, id: Uuid) -> bool {
let mut todos = load_todos_from_file(file_path);
if todos.remove(&id).is_some() {
save_todos_to_file(&todos, file_path);
true
} else {
false
}
}