tod 0.15.0

An unofficial Todoist command-line client
use crate::config::{SortKey, SortRule};
use serde::{Deserialize, Serialize};
use std::sync::Once;

static SORT_VALUE_WARNING: Once = Once::new();

#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
pub struct LegacySortValue {
    pub priority_none: Option<u8>,
    pub priority_low: Option<u8>,
    pub priority_medium: Option<u8>,
    pub priority_high: Option<u8>,
    pub no_due_date: Option<u8>,
    pub not_recurring: Option<u8>,
    pub today: Option<u8>,
    pub overdue: Option<u8>,
    pub now: Option<u8>,
    pub deadline_value: Option<u8>,
    pub deadline_days: Option<u8>,
}

pub(crate) fn detect_and_migrate_sort_value(
    sort_value: Option<&LegacySortValue>,
) -> Option<Vec<SortRule>> {
    let sort_value = sort_value?;
    SORT_VALUE_WARNING.call_once(|| {
        eprintln!(
            "Legacy sort_value config detected; migrating to sort_order. \
             Please update your config because sort_value will be removed in a future version."
        );
    });

    let priority = [
        sort_value.priority_none,
        sort_value.priority_low,
        sort_value.priority_medium,
        sort_value.priority_high,
    ]
    .into_iter()
    .flatten()
    .max()
    .unwrap_or_default();

    let mut weighted_keys = vec![
        (SortKey::Priority, priority),
        (SortKey::Overdue, sort_value.overdue.unwrap_or_default()),
        (SortKey::Today, sort_value.today.unwrap_or_default()),
        (SortKey::Now, sort_value.now.unwrap_or_default()),
        (
            SortKey::NoDueDate,
            sort_value.no_due_date.unwrap_or_default(),
        ),
        (
            SortKey::NotRecurring,
            sort_value.not_recurring.unwrap_or_default(),
        ),
        (
            SortKey::Deadline,
            sort_value.deadline_value.unwrap_or_default(),
        ),
    ];

    weighted_keys.sort_by(|(left_key, left_weight), (right_key, right_weight)| {
        right_weight
            .cmp(left_weight)
            .then_with(|| sort_key_default_index(left_key).cmp(&sort_key_default_index(right_key)))
    });

    let mut keys: Vec<SortKey> = weighted_keys.into_iter().map(|(key, _)| key).collect();
    for default_key in SortKey::default_order() {
        if !keys.contains(&default_key) {
            keys.push(default_key);
        }
    }

    Some(
        keys.into_iter()
            .map(SortRule::with_default_direction)
            .collect(),
    )
}

fn sort_key_default_index(key: &SortKey) -> usize {
    SortKey::default_order()
        .iter()
        .position(|default_key| default_key == key)
        .unwrap_or(usize::MAX)
}