use std::{cell::RefCell, error::Error, ops::Deref, rc::Rc};
use aimcal_core::{Aim, Priority, Todo, TodoDraft, TodoPatch, TodoStatus};
use crate::tui::dispatcher::{Action, Dispatcher};
use crate::util::{format_datetime, parse_datetime};
pub trait TodoStoreLike {
type Output<'a>: Deref<Target = TodoStore>
where
Self: 'a;
fn todo(&self) -> Self::Output<'_>;
}
#[derive(Debug)]
pub struct TodoStore {
pub data: TodoData,
pub dirty: TodoMarker,
pub verbose_priority: bool,
pub submit: bool,
}
impl TodoStore {
pub fn from_draft(draft: TodoDraft) -> Self {
Self::new(TodoData {
description: draft.description.unwrap_or_default(),
due: draft.due.map(format_datetime).unwrap_or_default(),
percent_complete: draft.percent_complete,
priority: draft.priority.unwrap_or_default(),
status: draft.status,
summary: draft.summary,
})
}
pub fn from_patch(todo: &impl Todo, patch: TodoPatch) -> Self {
Self::new(TodoData {
description: match patch.description {
Some(v) => v.unwrap_or_default(),
None => todo.description().unwrap_or_default().into_owned(),
},
due: match patch.due {
Some(v) => v.map(format_datetime).unwrap_or_default(),
None => todo.due().map(format_datetime).unwrap_or_default(),
},
percent_complete: patch
.percent_complete
.unwrap_or_else(|| todo.percent_complete()),
priority: patch.priority.unwrap_or_else(|| todo.priority()),
status: patch.status.unwrap_or_else(|| todo.status()),
summary: patch.summary.unwrap_or_else(|| todo.summary().into_owned()),
})
}
fn new(data: TodoData) -> Self {
use Priority::{P1, P3, P4, P6, P7, P9};
let verbose_priority = matches!(data.priority, P1 | P3 | P4 | P6 | P7 | P9);
Self {
data,
dirty: TodoMarker::default(),
verbose_priority,
submit: false,
}
}
pub fn submit_draft(self, aim: &Aim) -> Result<TodoDraft, Box<dyn Error>> {
Ok(TodoDraft {
calendar_id: None,
description: self.dirty.description.then_some(self.data.description),
due: parse_datetime(&aim.now(), &self.data.due)?,
percent_complete: self
.dirty
.percent_complete
.then_some(self.data.percent_complete)
.flatten(),
priority: Some(self.data.priority), status: self.data.status,
summary: if self.data.summary.is_empty() {
"New todo".to_string()
} else {
self.data.summary
},
})
}
pub fn submit_patch(self, aim: &Aim) -> Result<TodoPatch, Box<dyn Error>> {
Ok(TodoPatch {
description: if self.dirty.description {
if self.data.description.is_empty() {
Some(None)
} else {
Some(Some(self.data.description.clone()))
}
} else {
None
},
due: if self.dirty.due {
Some(parse_datetime(&aim.now(), &self.data.due)?)
} else {
None
},
percent_complete: self
.dirty
.percent_complete
.then_some(self.data.percent_complete),
priority: self.dirty.priority.then_some(self.data.priority),
status: self.dirty.status.then_some(self.data.status),
summary: self.dirty.summary.then(|| self.data.summary.clone()),
})
}
pub fn register_to(that: Rc<RefCell<Self>>, dispatcher: &mut Dispatcher) {
let callback = Rc::new(RefCell::new(move |action: &Action| match action {
Action::UpdateTodoDescription(v) => {
let mut that = that.borrow_mut();
that.data.description.clone_from(v);
that.dirty.description = true;
}
Action::UpdateTodoDue(v) => {
let mut that = that.borrow_mut();
that.data.due.clone_from(v);
that.dirty.due = true;
}
Action::UpdateTodoPercentComplete(v) => {
let mut that = that.borrow_mut();
that.data.percent_complete = *v;
that.dirty.percent_complete = true;
}
Action::UpdateTodoPriority(v) => {
let mut that = that.borrow_mut();
that.data.priority = *v;
that.dirty.priority = true;
}
Action::UpdateTodoStatus(v) => {
let mut that = that.borrow_mut();
that.data.status = *v;
that.dirty.status = true;
}
Action::UpdateTodoSummary(v) => {
let mut that = that.borrow_mut();
that.data.summary.clone_from(v);
that.dirty.summary = true;
}
Action::SubmitChanges => {
let mut that = that.borrow_mut();
that.submit = true;
}
_ => {}
}));
dispatcher.register(callback);
}
}
impl TodoStoreLike for TodoStore {
type Output<'a> = &'a Self;
fn todo(&self) -> &Self {
self
}
}
#[derive(Debug, Default)]
pub struct TodoData {
pub description: String,
pub due: String,
pub percent_complete: Option<u8>,
pub priority: Priority,
pub status: TodoStatus,
pub summary: String,
}
#[derive(Debug, Default)]
#[expect(clippy::struct_excessive_bools)]
pub struct TodoMarker {
description: bool,
due: bool,
percent_complete: bool,
priority: bool,
status: bool,
summary: bool,
}