lkml 0.1.2

Program to download and assort emails from mailing lists supported by `lei`
use std::{collections::HashSet, path::Path};

use maildir::Maildir;
use thiserror::Error;

use crate::{
    assort::mail::Type,
    config::{self, Keyword},
};

pub struct Folder {
    pub maildir: Maildir,
    pub priority: usize,
    pub keywords: HashSet<Keyword>,
    pub flagging_keywords: Option<HashSet<Keyword>>,
    pub name: String,
    pub mark_read: bool,
}

impl Folder {
    pub fn new(f: &config::Folder, parent: &Path) -> Self {
        let maildir = if f.name == "INBOX" {
            Maildir::from(parent.to_owned())
        } else {
            Maildir::from(parent.join(format!(".{}", f.name)))
        };
        Self {
            maildir,
            priority: f.priority,
            keywords: f.keywords.clone(),
            flagging_keywords: f.flagging_keywords.clone(),
            name: f.name.clone(),
            mark_read: f.mark_read,
        }
    }

    pub fn rest(maildir: Maildir) -> Self {
        Self {
            maildir,
            priority: usize::MAX,
            keywords: HashSet::new(),
            name: "INBOX".to_owned(),
            flagging_keywords: None,
            mark_read: false,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Action {
    mark_read: bool,
    mark_flagged: bool,
    dest: Dest,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dest {
    Drop(DropReason),
    Folder(usize),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DropReason {
    DuplicateQuirk,
    VerbatimCopy,
    Ignored,
    PrefixCopy,
}

impl Dest {
    pub fn max_prio(a: Self, b: Self) -> Option<Self> {
        match (a, b) {
            (Dest::Drop(_), _) | (_, Dest::Drop(_)) => None,
            (Dest::Folder(a), Dest::Folder(b)) => Some(Dest::Folder(a.min(b))),
        }
    }
}

#[derive(Debug, Error)]
#[error("cannot convert `Typ::New` into `Dest`")]
pub struct DestConvertError;

impl TryFrom<Type> for Dest {
    type Error = DestConvertError;
    fn try_from(value: Type) -> std::result::Result<Self, Self::Error> {
        Ok(match value {
            Type::New => return Err(DestConvertError),
            Type::Folder(id) => Dest::Folder(id),
        })
    }
}

impl From<Dest> for Option<Type> {
    fn from(value: Dest) -> Self {
        match value {
            Dest::Folder(id) => Some(Type::Folder(id)),
            Dest::Drop(_) => None,
        }
    }
}

impl Action {
    pub fn delete(reason: DropReason) -> Self {
        Self {
            dest: Dest::Drop(reason),
            mark_read: false,
            mark_flagged: false,
        }
    }

    pub fn folder(id: usize) -> Self {
        Self {
            dest: Dest::Folder(id),
            mark_read: false,
            mark_flagged: false,
        }
    }

    pub fn with_cleared_flags(&self) -> Self {
        let Self {
            mark_read: _,
            mark_flagged: _,
            dest,
        } = self;
        Self {
            dest: *dest,
            mark_read: false,
            mark_flagged: false,
        }
    }

    pub fn flags(&self) -> &'static str {
        match (self.mark_read, self.mark_flagged) {
            (true, true) => "FS",
            (true, false) => "S",
            (false, true) => "F",
            (false, false) => "",
        }
    }

    pub fn folder_idx(&self) -> Option<usize> {
        match self.dest {
            Dest::Drop(_) => None,
            Dest::Folder(id) => Some(id),
        }
    }

    pub fn flag(&mut self) {
        self.mark_flagged = true;
        self.mark_read = false;
    }

    pub fn read(&mut self) {
        self.mark_flagged = false;
        self.mark_read = true;
    }

    pub fn is_flagged(&self) -> bool {
        self.mark_flagged
    }

    pub fn dest(&self) -> Dest {
        self.dest
    }

    pub fn set_dest(&mut self, dest: Dest) {
        self.dest = dest;
    }
}