1use std::cmp::Ordering;
5
6use todoapp_core::{
7 ComponentStore, Dir, DueFilter, Filter, Id, Query, SortField, SortKey, Status, TaskEntityStore,
8 Title,
9};
10
11use crate::service::{Services, TaskSnapshot};
12
13#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
15pub struct QueryHit {
16 pub task: TaskSnapshot,
17 pub path: Vec<String>,
18}
19
20impl<'a, St: ComponentStore + TaskEntityStore> Services<'a, St> {
21 pub async fn evaluate(&self, q: &Query) -> Vec<QueryHit> {
22 let today = self.clock.today();
23 let mut hits: Vec<(QueryHit, Vec<f64>)> = Vec::new();
28 for id in self.query.select(&q.filter, today).await {
29 let Ok(t) = self.snapshot(&id).await else {
30 continue;
31 };
32 let path = self.breadcrumb(&t.id).await;
33 let key = self.priority_key(&t.id).await;
34 hits.push((QueryHit { task: t, path }, key));
35 }
36
37 let keys = if q.sort.is_empty() {
38 vec![SortKey {
39 key: SortField::Priority,
40 dir: Dir::Asc,
41 }]
42 } else {
43 q.sort.clone()
44 };
45 hits.sort_by(|a, b| cmp_hits(a, b, &keys));
46 hits.into_iter().map(|(h, _)| h).collect()
47 }
48
49 pub async fn what_next(&self) -> Vec<QueryHit> {
53 self.what_next_for(None, None, None).await
54 }
55
56 pub async fn what_next_for(
58 &self,
59 assignee: Option<Id>,
60 within: Option<Id>,
61 tag: Option<String>,
62 ) -> Vec<QueryHit> {
63 self.evaluate(&Query {
64 filter: Filter {
65 status: vec![Status::Todo],
66 assignee,
67 within,
68 tags: tag.into_iter().collect(),
69 archived: Some(false),
70 ..Default::default()
71 },
72 sort: vec![SortKey {
73 key: SortField::Priority,
74 dir: Dir::Asc,
75 }],
76 })
77 .await
78 }
79
80 pub async fn due_today(&self) -> Vec<QueryHit> {
82 self.evaluate(&Query {
83 filter: Filter {
84 due: Some(DueFilter::Today),
85 archived: Some(false),
86 ..Default::default()
87 },
88 sort: vec![
89 SortKey {
90 key: SortField::Due,
91 dir: Dir::Asc,
92 },
93 SortKey {
94 key: SortField::Priority,
95 dir: Dir::Asc,
96 },
97 ],
98 })
99 .await
100 }
101
102 async fn breadcrumb(&self, id: &Id) -> Vec<String> {
106 let mut chain = Vec::new();
107 let mut cur = self.parent_of(id).await;
108 while let Some(pid) = cur {
109 if let Some(t) = self.store.get::<Title>(&pid).await {
110 chain.push(t.0);
111 cur = self.parent_of(&pid).await;
112 } else {
113 break;
114 }
115 }
116 chain.reverse();
117 chain
118 }
119
120 async fn priority_key(&self, id: &Id) -> Vec<f64> {
123 let mut key = Vec::new();
124 let mut cur = id.clone();
125 while let Some(parent) = self.parent_of(&cur).await {
126 let pos = self
127 .children_of(&parent)
128 .await
129 .into_iter()
130 .find(|l| l.to == cur)
131 .map(|l| l.position.0)
132 .unwrap_or(0.0);
133 key.push(pos);
134 cur = parent;
135 }
136 key.reverse();
137 key
138 }
139}
140
141fn cmp_hits(a: &(QueryHit, Vec<f64>), b: &(QueryHit, Vec<f64>), keys: &[SortKey]) -> Ordering {
143 for k in keys {
144 let ord = match k.key {
145 SortField::Priority => cmp_f64_seq(&a.1, &b.1),
146 SortField::Due => a.0.task.due_date.cmp(&b.0.task.due_date),
147 SortField::Created => a.0.task.created_at.cmp(&b.0.task.created_at),
148 SortField::Updated => a.0.task.updated_at.cmp(&b.0.task.updated_at),
149 };
150 let ord = match k.dir {
151 Dir::Asc => ord,
152 Dir::Desc => ord.reverse(),
153 };
154 if ord != Ordering::Equal {
155 return ord;
156 }
157 }
158 a.0.task.id.cmp(&b.0.task.id)
160}
161
162fn cmp_f64_seq(a: &[f64], b: &[f64]) -> Ordering {
164 for (x, y) in a.iter().zip(b.iter()) {
165 let o = x.total_cmp(y);
166 if o != Ordering::Equal {
167 return o;
168 }
169 }
170 a.len().cmp(&b.len())
171}