use std::fmt;

use assert_matches::assert_matches;

use crate::{json, test, warning};

use super::{Id, Path, Set};

#[derive(Debug)]
enum Warning {
    Root,
    One,
    OneAgain,
    Three,
}

impl super::Warning for Warning {
    fn id(&self) -> Id {
        match self {
            Self::Root => Id::from_static("root"),
            Self::One => Id::from_static("one"),
            Self::OneAgain => Id::from_static("one_again"),
            Self::Three => Id::from_static("three"),
        }
    }
}

impl fmt::Display for Warning {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Root => write!(f, "NopeRoot"),
            Self::One => write!(f, "NopeOne"),
            Self::OneAgain => write!(f, "NopeOneAgain"),
            Self::Three => write!(f, "NopeThree"),
        }
    }
}

#[test]
fn should_group_by_elem() {
    test::setup();

    let mut warning_set = Set::<Warning>::new();

    // Push warnings into the set out of order.
    // They should be sorted by `ElemId`.
    warning_set.insert_warning(Warning::Root, json::ElemId::from(0), || warning::Element {
        id: json::ElemId::from(0),
        path: Path("$".to_owned()),
        span: json::parser::Span::default(),
    });
    warning_set.insert_warning(Warning::One, json::ElemId::from(1), || warning::Element {
        id: json::ElemId::from(1),
        path: Path("$.field_one".to_owned()),
        span: json::parser::Span::default(),
    });
    warning_set.insert_warning(Warning::Three, json::ElemId::from(3), || warning::Element {
        id: json::ElemId::from(3),
        path: Path("$.field_three".to_owned()),
        span: json::parser::Span::default(),
    });
    warning_set.insert_warning(Warning::OneAgain, json::ElemId::from(1), || {
        warning::Element {
            id: json::ElemId::from(1),
            path: Path("$.field_one".to_owned()),
            span: json::parser::Span::default(),
        }
    });

    let mut iter = warning_set.iter();

    {
        let (element, warnings) = iter.next().unwrap().to_parts();

        assert_eq!(
            element.path.as_str(),
            "$",
            "The root object should be emitted first"
        );
        assert_matches!(warnings.as_slice(), [Warning::Root]);
    }

    {
        let (element, warnings) = iter.next().unwrap().to_parts();

        assert_eq!(element.path.as_str(), "$.field_one");
        assert_matches!(
            warnings.as_slice(),
            [Warning::One, Warning::OneAgain],
            "[`json::Element`] 1 should have two warnings"
        );
    }

    {
        // [`json::Element`] 2 has no [`Warning`]s so we expect [`json::Element`] 3 next
        let (element, warnings) = iter.next().unwrap().to_parts();

        assert_eq!(element.path.as_str(), "$.field_three");
        assert_matches!(warnings.as_slice(), [Warning::Three]);
    }
}

#[test]
fn should_into_group_by_elem() {
    test::setup();

    let mut warning_set = Set::<Warning>::new();

    // Push warnings into the set out of order.
    // They should be sorted by `ElemId`.
    warning_set.insert_warning(Warning::Root, json::ElemId::from(0), || warning::Element {
        id: json::ElemId::from(0),
        path: Path("$".to_owned()),
        span: json::parser::Span::default(),
    });
    warning_set.insert_warning(Warning::One, json::ElemId::from(1), || warning::Element {
        id: json::ElemId::from(1),
        path: Path("$.field_one".to_owned()),
        span: json::parser::Span::default(),
    });
    warning_set.insert_warning(Warning::Three, json::ElemId::from(3), || warning::Element {
        id: json::ElemId::from(3),
        path: Path("$.field_three".to_owned()),
        span: json::parser::Span::default(),
    });
    warning_set.insert_warning(Warning::OneAgain, json::ElemId::from(1), || {
        warning::Element {
            id: json::ElemId::from(1),
            path: Path("$.field_one".to_owned()),
            span: json::parser::Span::default(),
        }
    });

    let mut iter = warning_set.iter();

    {
        let (element, warnings) = iter.next().unwrap().to_parts();

        assert_eq!(element.path.as_str(), "$");
        assert_matches!(warnings.as_slice(), [Warning::Root]);
    }

    {
        let (element, warnings) = iter.next().unwrap().to_parts();

        assert_eq!(element.path.as_str(), "$.field_one");
        assert_matches!(
            warnings.as_slice(),
            [Warning::One, Warning::OneAgain],
            "[`json::Element`] 1 should have two warnings"
        );
    }

    {
        // [`json::Element`] 2 has no [`Warning`]s so we expect [`json::Element`] 3 next
        let (element, warnings) = iter.next().unwrap().to_parts();

        assert_eq!(element.path.as_str(), "$.field_three");
        assert_matches!(warnings.as_slice(), [Warning::Three]);
    }
}
