1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4
5use crate::cache::CardCache;
6use crate::categories::Category;
7use crate::{deserialize_duration, serialize_duration, Id};
8
9#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10pub struct FilterUtil {
11 pub allowed_cards: Vec<Id>, pub disallowed_cards: Vec<Id>,
13 pub allowed_categories: Vec<Category>,
14 pub excluded_categories: Vec<Category>,
15 pub contains: Option<String>,
16 pub tags: Vec<String>,
17 pub suspended: Option<bool>,
18 pub resolved: Option<bool>,
19 pub pending: Option<bool>,
20 pub finished: Option<bool>,
21 #[serde(
22 serialize_with = "serialize_duration",
23 deserialize_with = "deserialize_duration"
24 )]
25 pub max_strength: Option<Duration>,
26 #[serde(
27 serialize_with = "serialize_duration",
28 deserialize_with = "deserialize_duration"
29 )]
30 pub min_strength: Option<Duration>,
31 #[serde(
32 serialize_with = "serialize_duration",
33 deserialize_with = "deserialize_duration"
34 )]
35 pub max_stability: Option<Duration>,
36 #[serde(
37 serialize_with = "serialize_duration",
38 deserialize_with = "deserialize_duration"
39 )]
40 pub min_stability: Option<Duration>,
41 pub max_recall_rate: Option<f32>,
42 pub min_recall_rate: Option<f32>,
43 pub max_lapses: Option<u32>,
44 pub all_dependencies: Option<Box<Self>>,
45 pub any_dependencies: Option<Box<Self>>,
46 pub all_dependents: Option<Box<Self>>,
47 pub any_dependents: Option<Box<Self>>,
48}
49
50impl FilterUtil {
51 pub fn new_permissive() -> Self {
52 Self::default()
53 }
54
55 pub fn new_review() -> Self {
56 FilterUtil {
57 max_recall_rate: Some(0.9),
58 resolved: Some(true),
59 suspended: Some(false),
60 finished: Some(true),
61 ..Default::default()
62 }
63 }
64
65 pub fn evaluate_cards(&self, cards: Vec<Id>, cache: &mut CardCache) -> Vec<Id> {
66 cards
67 .into_iter()
68 .filter(|card| self.keep_card(*card, cache))
69 .collect()
70 }
71
72 pub fn keep_card(&self, card: Id, cache: &mut CardCache) -> bool {
73 let card = cache.get_ref(card);
74
75 if self.allowed_cards.contains(&card.id()) {
76 return true;
77 };
78
79 if self.disallowed_cards.contains(&card.id()) {
80 return false;
81 };
82
83 if !self.allowed_categories.is_empty()
84 && !self
85 .allowed_categories
86 .iter()
87 .any(|cat| card.category() == cat)
88 {
89 return false;
90 }
91
92 if self
93 .excluded_categories
94 .iter()
95 .any(|cat| card.category() == cat)
96 {
97 return false;
98 }
99
100 if let Some(suspended) = self.suspended {
101 if card.is_suspended() != suspended {
102 return false;
103 }
104 }
105
106 if let Some(resolved) = self.resolved {
107 if card.is_resolved(cache) != resolved {
108 return false;
109 }
110 }
111
112 if let Some(finished) = self.finished {
113 if card.is_finished() != finished {
114 return false;
115 }
116 }
117 if let Some(pending) = self.pending {
118 if card.is_pending() != pending {
119 return false;
120 }
121 }
122
123 if let Some(max_strength) = self.max_strength {
124 if !card
125 .strength()
126 .map(|card_strength| card_strength < max_strength)
127 .unwrap_or(false)
128 {
129 return false;
130 }
131 }
132
133 if let Some(min_strength) = self.min_strength {
134 if !card
135 .strength()
136 .map(|card_strength| card_strength > min_strength)
137 .unwrap_or(false)
138 {
139 return false;
140 }
141 }
142
143 if let Some(max_stability) = self.max_stability {
144 if !card
145 .stability()
146 .map(|card_stability| card_stability < max_stability)
147 .unwrap_or(false)
148 {
149 return false;
150 }
151 }
152
153 if let Some(min_stability) = self.min_stability {
154 if !card
155 .stability()
156 .map(|card_stability| card_stability > min_stability)
157 .unwrap_or(false)
158 {
159 return false;
160 }
161 }
162
163 if let Some(max_recall_rate) = self.max_recall_rate {
164 if card.recall_rate().unwrap_or_default() > max_recall_rate {
165 return false;
166 }
167 }
168
169 if let Some(min_recall_rate) = self.min_recall_rate {
170 if !card
171 .recall_rate()
172 .map(|card_recall_rate| card_recall_rate > min_recall_rate)
173 .unwrap_or(false)
174 {
175 return false;
176 }
177 }
178
179 if let Some(search) = self.contains.as_deref() {
180 if !(card
181 .front_text()
182 .to_lowercase()
183 .contains(&search.to_lowercase())
184 || card
185 .back_text()
186 .to_lowercase()
187 .contains(&search.to_lowercase()))
188 {
189 return false;
190 }
191 }
192
193 if !self.tags.is_empty() && !self.tags.iter().any(|tag| card.contains_tag(tag)) {
194 return false;
195 }
196
197 if let Some(all_dependencies) = &self.all_dependencies {
199 let dependencies = cache.dependencies(card.id());
200 if !dependencies.is_empty()
201 && !dependencies
202 .iter()
203 .all(|card| all_dependencies.keep_card(*card, cache))
204 {
205 return false;
206 }
207 }
208
209 if let Some(all_dependents) = &self.any_dependents {
210 let dependents = cache.dependents(card.id());
211 if !dependents.is_empty()
212 && !dependents
213 .iter()
214 .all(|card| all_dependents.keep_card(*card, cache))
215 {
216 return false;
217 }
218 }
219
220 if let Some(any_dependents) = &self.any_dependents {
221 let dependents = cache.dependents(card.id());
222 if !dependents
223 .iter()
224 .any(|card| any_dependents.keep_card(*card, cache))
225 {
226 return false;
227 }
228 }
229
230 if let Some(any_depencies) = &self.any_dependencies {
231 let dependencies = cache.dependencies(card.id());
232 if dependencies
233 .iter()
234 .any(|card| any_depencies.keep_card(*card, cache))
235 {
236 return false;
237 }
238 }
239
240 true
241 }
242}