#[allow(clippy::wildcard_imports)]
use super::*;
pub fn query_matches(query: &str, haystack: &str) -> bool {
let mut bare_terms = Vec::new();
let hay = haystack.to_lowercase();
for token in query.split_whitespace() {
if token.eq_ignore_ascii_case("overdue") {
if !is_overdue_haystack(haystack) {
return false;
}
continue;
}
if let Some((key, value)) = parse_token(token) {
let value = value.to_lowercase();
let ok = match key.to_lowercase().as_str() {
"assignee" | "label" | "status" | "priority" | "cycle" | "milestone"
| "reviewer" => hay.contains(&value),
"start" | "due" => hay.contains(&value),
"parent" => value
.split('|')
.any(|candidate| field_value(haystack, "parent") == Some(candidate)),
"category" => false,
"scope" => false,
"archived" => false,
"project" => false,
"epic" => false,
"sort" => matches!(
value.as_str(),
"created"
| "priority"
| "status"
| "cycle"
| "milestone"
| "due"
| "start"
| "reviewer"
| "project"
| "epic"
),
"group" => matches!(value.as_str(), "cycle" | "milestone" | "project" | "epic"),
"overdue" if value == "true" => is_overdue_haystack(haystack),
_ => false,
};
if !ok {
return false;
}
} else {
bare_terms.push(token);
}
}
let bare = bare_terms.join(" ").to_lowercase();
bare.is_empty() || hay.contains(&bare)
}
pub(super) fn field_value<'a>(haystack: &'a str, key: &str) -> Option<&'a str> {
for line in haystack.lines() {
let prefix = format!("{key}:");
if let Some(rest) = line.strip_prefix(&prefix) {
return Some(rest.trim().trim_matches('"'));
}
}
None
}
pub(super) fn is_overdue_haystack(haystack: &str) -> bool {
let status = field_value(haystack, "status").unwrap_or_default();
let due = field_value(haystack, "due_date").unwrap_or_default();
!due.is_empty() && due < crate::model::today().as_str() && status != "done"
}
pub fn number_query(raw: &str) -> Option<u32> {
let trimmed = raw.trim();
if trimmed.is_empty() || !trimmed.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
trimmed.parse::<u32>().ok()
}