use std::cmp::Ordering;
use chrono::NaiveDate;
use crate::{AgendaEntry, Item, OccurrenceType, PriorityRange, WorkspaceIndex};
use super::export::agenda_title;
use crate::date::parse_org_date;
pub(super) fn compare_by_strategy(
(left_urgency, left): &(i64, AgendaEntry),
(right_urgency, right): &(i64, AgendaEntry),
index: &WorkspaceIndex,
todo_order: &[String],
) -> Ordering {
let settings = index.settings();
let todo_rank = |entry: &AgendaEntry| {
entry.item.todo.as_deref().map_or(-1, |todo| {
todo_order
.iter()
.position(|keyword| keyword == todo)
.map_or(-1, |index| index as i64)
})
};
for key in &settings.sorting_strategy {
let (name, direction) = key.rsplit_once('-').unwrap_or((key, "up"));
let ordering = match name {
"time" => compare_time_of_day(&left.occurrence.time, &right.occurrence.time),
"urgency" => left_urgency.cmp(right_urgency),
"priority" => {
priority_value(&left.item, index).cmp(&priority_value(&right.item, index))
}
"category" if direction != "keep" => left.item.category.cmp(&right.item.category),
"alpha" => agenda_title(&left.item).cmp(&agenda_title(&right.item)),
"todo-state" => todo_rank(left).cmp(&todo_rank(right)),
_ => Ordering::Equal,
};
let ordering = if direction == "down" {
ordering.reverse()
} else {
ordering
};
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
pub(super) fn urgency(entry: &AgendaEntry, today: NaiveDate, index: &WorkspaceIndex) -> i64 {
let priority = priority_value(&entry.item, index);
let date = parse_org_date(&entry.occurrence.date).ok();
match entry.occurrence.kind {
OccurrenceType::Deadline | OccurrenceType::UpcomingDeadline => {
let base = entry
.item
.planning
.deadline
.as_ref()
.and_then(|deadline| parse_org_date(&deadline.date).ok());
match (date, base) {
(Some(date), Some(base)) if date == today => priority + (today - base).num_days(),
_ => priority,
}
}
OccurrenceType::Scheduled | OccurrenceType::PastScheduled => {
let base = entry
.item
.planning
.scheduled
.as_ref()
.and_then(|scheduled| parse_org_date(&scheduled.date).ok());
match (date, base) {
(Some(date), Some(base)) => priority + 99 + (date - base).num_days(),
_ => priority + 99,
}
}
_ => priority,
}
}
pub(super) fn priority_value(item: &Item, index: &WorkspaceIndex) -> i64 {
index
.document(std::path::Path::new(&item.file))
.map_or_else(
|| PriorityRange::from_settings(index.settings()),
|document| document.priorities(),
)
.value(item.priority.as_deref())
}
fn compare_time_of_day(left: &Option<String>, right: &Option<String>) -> Ordering {
match (left, right) {
(Some(left), Some(right)) => time_minutes(left).cmp(&time_minutes(right)),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => Ordering::Equal,
}
}
fn time_minutes(time: &str) -> u32 {
let (hours, minutes) = time.split_once(':').unwrap_or((time, "0"));
hours.parse::<u32>().unwrap_or(0) * 60 + minutes.parse::<u32>().unwrap_or(0)
}
pub(super) fn compare_items(left: &Item, right: &Item, index: &WorkspaceIndex) -> Ordering {
priority_value(right, index)
.cmp(&priority_value(left, index))
.then_with(|| left.file.cmp(&right.file))
.then_with(|| left.line.cmp(&right.line))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AgendaEngine;
use crate::agenda::test_support::fixture;
use crate::config::test_support::settings;
#[test]
fn todo_lists_sort_by_each_files_own_priority_range() {
let (temp, _) = fixture("#+PRIORITIES: A E E\n* TODO Default lowest\n* TODO [#D] Fourth\n");
std::fs::write(temp.path().join("later.org"), "* TODO Default middle\n").unwrap();
let index = WorkspaceIndex::load(settings(temp.path())).unwrap();
let today = NaiveDate::from_ymd_opt(2026, 10, 13).unwrap();
let titles: Vec<String> = AgendaEngine::new(&index)
.todo(None, today)
.unwrap()
.into_iter()
.map(|item| item.title)
.collect();
assert_eq!(titles, ["Fourth", "Default middle", "Default lowest"]);
}
}