use crate::menu_utils::{FzfPreview, FzfSelectable};
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct AnnotatedValue<T> {
    pub value: T,
    pub annotation: Option<String>,
}

impl<T> AnnotatedValue<T> {
    pub fn new(value: T, annotation: Option<String>) -> Self {
        Self { value, annotation }
    }
}

impl<T: PartialEq> PartialEq for AnnotatedValue<T> {
    fn eq(&self, other: &Self) -> bool {
        self.annotation == other.annotation && self.value == other.value
    }
}

impl<T: Eq> Eq for AnnotatedValue<T> {}

impl<T: PartialOrd> PartialOrd for AnnotatedValue<T> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match (&self.annotation, &other.annotation) {
            (Some(a), Some(b)) => {
                let ann_cmp = a.cmp(b);
                if ann_cmp != std::cmp::Ordering::Equal {
                    return Some(ann_cmp);
                }
                self.value.partial_cmp(&other.value)
            }
            (Some(_), None) => Some(std::cmp::Ordering::Less),
            (None, Some(_)) => Some(std::cmp::Ordering::Greater),
            (None, None) => self.value.partial_cmp(&other.value),
        }
    }
}

impl<T: Ord> Ord for AnnotatedValue<T> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        match (&self.annotation, &other.annotation) {
            (Some(a), Some(b)) => {
                let ann_cmp = a.cmp(b);
                if ann_cmp != std::cmp::Ordering::Equal {
                    return ann_cmp;
                }
                self.value.cmp(&other.value)
            }
            (Some(_), None) => std::cmp::Ordering::Less,
            (None, Some(_)) => std::cmp::Ordering::Greater,
            (None, None) => self.value.cmp(&other.value),
        }
    }
}

impl<T: FzfSelectable> FzfSelectable for AnnotatedValue<T> {
    fn fzf_display_text(&self) -> String {
        match &self.annotation {
            Some(ann) => format!("{} - {}", ann, self.value.fzf_display_text()),
            None => self.value.fzf_display_text(),
        }
    }

    fn fzf_key(&self) -> String {
        self.value.fzf_key()
    }

    fn fzf_preview(&self) -> FzfPreview {
        self.value.fzf_preview()
    }
}

pub trait AnnotationProvider {
    fn annotate(&self, value: &str) -> Option<String>;
}

pub fn annotate_list<T: FzfSelectable + Clone + Ord>(
    provider: Option<&dyn AnnotationProvider>,
    items: Vec<T>,
) -> Vec<AnnotatedValue<T>> {
    let mut list: Vec<AnnotatedValue<T>> = items
        .into_iter()
        .map(|item| {
            let annotation = if let Some(p) = provider {
                let key = item.fzf_key();
                p.annotate(&key)
            } else {
                None
            };
            AnnotatedValue::new(item, annotation)
        })
        .collect();

    list.sort();
    list
}

pub struct LocaleAnnotationProvider;

impl AnnotationProvider for LocaleAnnotationProvider {
    fn annotate(&self, value: &str) -> Option<String> {
        let map: HashMap<&str, &str> = [
            ("en_US.UTF-8", "English (United States)"),
            ("en_GB.UTF-8", "English (United Kingdom)"),
            ("de_DE.UTF-8", "German (Germany)"),
            ("fr_FR.UTF-8", "French (France)"),
            ("es_ES.UTF-8", "Spanish (Spain)"),
            ("it_IT.UTF-8", "Italian (Italy)"),
            ("pt_BR.UTF-8", "Portuguese (Brazil)"),
            ("ru_RU.UTF-8", "Russian (Russia)"),
            ("ja_JP.UTF-8", "Japanese (Japan)"),
            ("zh_CN.UTF-8", "Chinese (China)"),
        ]
        .iter()
        .cloned()
        .collect();

        map.get(value).map(|s| s.to_string())
    }
}

pub struct KeymapAnnotationProvider;

impl AnnotationProvider for KeymapAnnotationProvider {
    fn annotate(&self, value: &str) -> Option<String> {
        let map: HashMap<&str, &str> = [
            ("us", "English (US)"),
            ("de-latin1", "German"),
            ("uk", "English (UK)"),
            ("fr", "French"),
            ("es", "Spanish"),
            ("it", "Italian"),
            ("pt-latin1", "Portuguese"),
            ("ru", "Russian"),
            ("jp106", "Japanese"),
        ]
        .iter()
        .cloned()
        .collect();

        map.get(value).map(|s| s.to_string())
    }
}

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

    #[test]
    fn test_annotated_value_display() {
        let val = AnnotatedValue::new("de_DE.UTF-8", Some("German (Germany)".to_string()));
        assert_eq!(val.fzf_display_text(), "German (Germany) - de_DE.UTF-8");

        let val_no_ann = AnnotatedValue::new("unknown", None);
        assert_eq!(val_no_ann.fzf_display_text(), "unknown");
    }

    #[test]
    fn test_annotate_list() {
        let provider = LocaleAnnotationProvider;
        let items = vec!["unknown", "de_DE.UTF-8"];
        let annotated = annotate_list(Some(&provider), items);

        assert_eq!(annotated.len(), 2);
        // Annotated should come first
        assert_eq!(
            annotated[0].fzf_display_text(),
            "German (Germany) - de_DE.UTF-8"
        );
        assert_eq!(annotated[1].fzf_display_text(), "unknown");
    }

    #[test]
    fn test_annotate_list_sorting() {
        let provider = LocaleAnnotationProvider;
        // "en_US.UTF-8" -> "English (United States)"
        // "de_DE.UTF-8" -> "German (Germany)"
        // "unknown1"
        // "unknown2"
        let items = vec!["unknown2", "en_US.UTF-8", "unknown1", "de_DE.UTF-8"];
        let annotated = annotate_list(Some(&provider), items);

        assert_eq!(annotated.len(), 4);

        // Expected order:
        // 1. English (United States) (Annotated, 'E' < 'G')
        // 2. German (Germany) (Annotated)
        // 3. unknown1 (Non-annotated, 'u1' < 'u2')
        // 4. unknown2 (Non-annotated)

        assert_eq!(annotated[0].value, "en_US.UTF-8");
        assert_eq!(annotated[1].value, "de_DE.UTF-8");
        assert_eq!(annotated[2].value, "unknown1");
        assert_eq!(annotated[3].value, "unknown2");
    }

    #[test]
    fn test_annotate_list_no_provider() {
        let items = vec!["de_DE.UTF-8", "unknown"];
        let annotated = annotate_list(None, items);

        assert_eq!(annotated.len(), 2);
        assert_eq!(annotated[0].fzf_display_text(), "de_DE.UTF-8");
        assert_eq!(annotated[1].fzf_display_text(), "unknown");
    }
}
