Documentation
use std::fmt;

use owo_colors::OwoColorize;

#[derive(Debug, Clone, PartialEq)]
pub struct Note {
    pub title: String,
    pub description: Option<String>,
    pub tags: Option<Vec<String>>,
}

#[derive(Debug)]
pub enum NoteError {
    EmptyTitle,
}

impl Note {
    pub fn new(
        title: &str,
        description: Option<&str>,
        tags: Option<&Vec<String>>,
    ) -> Result<Self, NoteError> {
        if title.is_empty() {
            return Err(NoteError::EmptyTitle);
        }
        Ok(Self {
            title: title.to_owned(),
            description: description.map(|desc| desc.to_owned()),
            tags: tags.map(|tag_list| tag_list.iter().map(|tag| tag.to_owned()).collect()),
        })
    }
}

impl fmt::Display for Note {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let description = self.description.as_deref().unwrap_or("");
        let tags = match self.tags.as_deref() {
            Some(tags) => format!("[{}]", tags.join("][")),
            None => "".to_owned(),
        };

        writeln!(
            f,
            "{}{:\u{2500}<78}{}",
            "\u{250C}".purple().dimmed(),
            "\u{2500}".purple().dimmed(),
            "\u{2510}".purple().dimmed()
        )?;
        for title_line in textwrap::wrap(&self.title, 78) {
            writeln!(
                f,
                "{}{:^78}{}",
                "\u{2502}".purple().dimmed(),
                title_line.bright_purple().bold(),
                "\u{2502}".purple().dimmed()
            )?;
        }
        writeln!(
            f,
            "{:<79}{}",
            "\u{2502}".purple().dimmed(),
            "\u{2502}".purple().dimmed()
        )?;
        for desc_line in textwrap::wrap(description, 77) {
            writeln!(
                f,
                "{} {:<77}{}",
                "\u{2502}".purple().dimmed(),
                desc_line.yellow(),
                "\u{2502}".purple().dimmed()
            )?;
        }
        writeln!(
            f,
            "{:<79}{}",
            "\u{2502}".purple().dimmed(),
            "\u{2502}".purple().dimmed()
        )?;
        writeln!(
            f,
            "{}{:>78}{}",
            "\u{2502}".purple().dimmed(),
            tags.bright_cyan().bold(),
            "\u{2502}".purple().dimmed()
        )?;
        writeln!(
            f,
            "{}{:\u{2500}<78}{}",
            "\u{2514}".purple().dimmed(),
            "\u{2500}".purple().dimmed(),
            "\u{2518}".purple().dimmed()
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_should_return_a_note_with_title() {
        let title = "My Title";
        let description = None;
        let tags = None;

        let res = Note::new(title, description, tags).unwrap();

        assert_eq!(res.title, title);
        assert_eq!(res.description, None);
        assert_eq!(res.tags, None);
    }

    #[test]
    fn it_should_return_a_full_note() {
        let title = "My Title";
        let description = Some("Description");
        let tags = vec!["tag1".to_owned(), "tag2".to_owned()];

        let res = Note::new(title, description, Some(&tags)).unwrap();

        let expected_tags = vec!["tag1".to_owned(), "tag2".to_owned()];

        assert_eq!(res.title, title);
        assert_eq!(res.description.as_deref(), description);

        let matching = res
            .tags
            .clone()
            .unwrap()
            .iter()
            .zip(expected_tags.iter())
            .filter(|&(expected_tag, res_tag)| expected_tag == res_tag)
            .count();
        assert!(matching == expected_tags.len() && matching == res.tags.unwrap().len())
    }

    #[test]
    fn it_should_raise_an_empty_title_error_when_title_is_empty() {
        let title = "";

        let res = Note::new(title, None, None);

        match res {
            Err(NoteError::EmptyTitle) => {}
            _ => unreachable!(),
        }
    }
}