use std::cmp::Ordering;
use todoapp_core::{
ComponentStore, Dir, DueFilter, Filter, Id, Query, SortField, SortKey, Status, TaskEntityStore,
Title,
};
use crate::service::{Services, TaskSnapshot};
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QueryHit {
pub task: TaskSnapshot,
pub path: Vec<String>,
}
impl<'a, St: ComponentStore + TaskEntityStore> Services<'a, St> {
pub async fn evaluate(&self, q: &Query) -> Vec<QueryHit> {
let today = self.clock.today();
let mut hits: Vec<(QueryHit, Vec<f64>)> = Vec::new();
for id in self.query.select(&q.filter, today).await {
let Ok(t) = self.snapshot(&id).await else {
continue;
};
let path = self.breadcrumb(&t.id).await;
let key = self.priority_key(&t.id).await;
hits.push((QueryHit { task: t, path }, key));
}
let keys = if q.sort.is_empty() {
vec![SortKey {
key: SortField::Priority,
dir: Dir::Asc,
}]
} else {
q.sort.clone()
};
hits.sort_by(|a, b| cmp_hits(a, b, &keys));
hits.into_iter().map(|(h, _)| h).collect()
}
pub async fn what_next(&self) -> Vec<QueryHit> {
self.what_next_for(None, None, None).await
}
pub async fn what_next_for(
&self,
assignee: Option<Id>,
within: Option<Id>,
tag: Option<String>,
) -> Vec<QueryHit> {
self.evaluate(&Query {
filter: Filter {
status: vec![Status::Todo],
assignee,
within,
tags: tag.into_iter().collect(),
archived: Some(false),
..Default::default()
},
sort: vec![SortKey {
key: SortField::Priority,
dir: Dir::Asc,
}],
})
.await
}
pub async fn due_today(&self) -> Vec<QueryHit> {
self.evaluate(&Query {
filter: Filter {
due: Some(DueFilter::Today),
archived: Some(false),
..Default::default()
},
sort: vec![
SortKey {
key: SortField::Due,
dir: Dir::Asc,
},
SortKey {
key: SortField::Priority,
dir: Dir::Asc,
},
],
})
.await
}
async fn breadcrumb(&self, id: &Id) -> Vec<String> {
let mut chain = Vec::new();
let mut cur = self.parent_of(id).await;
while let Some(pid) = cur {
if let Some(t) = self.store.get::<Title>(&pid).await {
chain.push(t.0);
cur = self.parent_of(&pid).await;
} else {
break;
}
}
chain.reverse();
chain
}
async fn priority_key(&self, id: &Id) -> Vec<f64> {
let mut key = Vec::new();
let mut cur = id.clone();
while let Some(parent) = self.parent_of(&cur).await {
let pos = self
.children_of(&parent)
.await
.into_iter()
.find(|l| l.to == cur)
.map(|l| l.position.0)
.unwrap_or(0.0);
key.push(pos);
cur = parent;
}
key.reverse();
key
}
}
fn cmp_hits(a: &(QueryHit, Vec<f64>), b: &(QueryHit, Vec<f64>), keys: &[SortKey]) -> Ordering {
for k in keys {
let ord = match k.key {
SortField::Priority => cmp_f64_seq(&a.1, &b.1),
SortField::Due => a.0.task.due_date.cmp(&b.0.task.due_date),
SortField::Created => a.0.task.created_at.cmp(&b.0.task.created_at),
SortField::Updated => a.0.task.updated_at.cmp(&b.0.task.updated_at),
};
let ord = match k.dir {
Dir::Asc => ord,
Dir::Desc => ord.reverse(),
};
if ord != Ordering::Equal {
return ord;
}
}
a.0.task.id.cmp(&b.0.task.id)
}
fn cmp_f64_seq(a: &[f64], b: &[f64]) -> Ordering {
for (x, y) in a.iter().zip(b.iter()) {
let o = x.total_cmp(y);
if o != Ordering::Equal {
return o;
}
}
a.len().cmp(&b.len())
}