use serde::{Deserialize, Serialize};
use slug;

#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum SlugifyStrategy {
    /// Classic slugification, the default
    #[default]
    On,
    /// No slugification, only remove unsafe characters for filepaths/urls
    Safe,
    /// Nothing is changed, hope for the best!
    Off,
}

fn strip_chars(s: &str, chars: &str) -> String {
    let mut sanitized_string = s.to_string();
    sanitized_string.retain(|c| !chars.contains(c));
    sanitized_string
}

fn strip_invalid_paths_chars(s: &str) -> String {
    // NTFS forbidden characters : https://gist.github.com/doctaphred/d01d05291546186941e1b7ddc02034d3
    // Also we need to trim whitespaces and `.` from the end of filename
    let trimmed = s.trim_end_matches([' ', '.']);
    strip_chars(trimmed, r#"<>:"/\|?*"#)
}

pub fn slugify_paths(s: &str, strategy: SlugifyStrategy) -> String {
    match strategy {
        SlugifyStrategy::On => slug::slugify(s),
        SlugifyStrategy::Safe => strip_invalid_paths_chars(s),
        SlugifyStrategy::Off => s.to_string(),
    }
}

pub fn slugify_anchors(s: &str, strategy: SlugifyStrategy) -> String {
    match strategy {
        SlugifyStrategy::On => slug::slugify(s),
        SlugifyStrategy::Safe | SlugifyStrategy::Off => {
            s.replace(|c: char| c.is_ascii_whitespace(), "_")
        }
    }
}

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

    #[test]
    fn can_slugify_paths() {
        let tests = vec![
            // input, (on, safe, off)
            ("input", ("input", "input", "input")),
            ("test ", ("test", "test", "test ")),
            ("tes t", ("tes-t", "tes t", "tes t")),
            // Invalid NTFS
            ("dot. ", ("dot", "dot", "dot. ")),
            ("日本", ("ri-ben", "日本", "日本")),
            ("héhé", ("hehe", "héhé", "héhé")),
            ("test (hey)", ("test-hey", "test (hey)", "test (hey)")),
        ];

        for (input, (on, safe, off)) in tests {
            assert_eq!(on, slugify_paths(input, SlugifyStrategy::On));
            assert_eq!(safe, slugify_paths(input, SlugifyStrategy::Safe));
            assert_eq!(off, slugify_paths(input, SlugifyStrategy::Off));
        }
    }

    #[test]
    fn can_slugify_anchors() {
        let tests = vec![
            // input, (on, safe, off)
            ("input", ("input", "input", "input")),
            ("test ", ("test", "test_", "test_")),
            ("tes t", ("tes-t", "tes_t", "tes_t")),
            // Invalid NTFS
            ("dot. ", ("dot", "dot._", "dot._")),
            ("日本", ("ri-ben", "日本", "日本")),
            ("héhé", ("hehe", "héhé", "héhé")),
            ("test (hey)", ("test-hey", "test_(hey)", "test_(hey)")),
        ];

        for (input, (on, safe, off)) in tests {
            assert_eq!(on, slugify_anchors(input, SlugifyStrategy::On));
            assert_eq!(safe, slugify_anchors(input, SlugifyStrategy::Safe));
            assert_eq!(off, slugify_anchors(input, SlugifyStrategy::Off));
        }
    }
}
