use std::{fmt, str::FromStr};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LintKind {
    DuplicatedKey,
    EndingBlankLine,
    ExtraBlankLine,
    IncorrectDelimiter,
    KeyWithoutValue,
    LeadingCharacter,
    LowercaseKey,
    QuoteCharacter,
    SpaceCharacter,
    SubstitutionKey,
    TrailingWhitespace,
    UnorderedKey,
    ValueWithoutQuotes,
    SchemaViolation,
}

impl FromStr for LintKind {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "DuplicatedKey" => Ok(LintKind::DuplicatedKey),
            "EndingBlankLine" => Ok(LintKind::EndingBlankLine),
            "ExtraBlankLine" => Ok(LintKind::ExtraBlankLine),
            "IncorrectDelimiter" => Ok(LintKind::IncorrectDelimiter),
            "KeyWithoutValue" => Ok(LintKind::KeyWithoutValue),
            "LeadingCharacter" => Ok(LintKind::LeadingCharacter),
            "LowercaseKey" => Ok(LintKind::LowercaseKey),
            "QuoteCharacter" => Ok(LintKind::QuoteCharacter),
            "SpaceCharacter" => Ok(LintKind::SpaceCharacter),
            "SubstitutionKey" => Ok(LintKind::SubstitutionKey),
            "TrailingWhitespace" => Ok(LintKind::TrailingWhitespace),
            "UnorderedKey" => Ok(LintKind::UnorderedKey),
            "ValueWithoutQuotes" => Ok(LintKind::ValueWithoutQuotes),
            _ => Err(()),
        }
    }
}

impl fmt::Display for LintKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", format!("{self:?}").replace("LintKind", ""))
    }
}

#[cfg(feature = "clap")]
impl clap::ValueEnum for LintKind {
    fn value_variants<'a>() -> &'a [Self] {
        &[
            LintKind::DuplicatedKey,
            LintKind::EndingBlankLine,
            LintKind::ExtraBlankLine,
            LintKind::IncorrectDelimiter,
            LintKind::KeyWithoutValue,
            LintKind::LeadingCharacter,
            LintKind::LowercaseKey,
            LintKind::QuoteCharacter,
            LintKind::SpaceCharacter,
            LintKind::SubstitutionKey,
            LintKind::TrailingWhitespace,
            LintKind::UnorderedKey,
            LintKind::ValueWithoutQuotes,
            LintKind::SchemaViolation,
        ]
    }

    fn to_possible_value<'a>(&self) -> Option<clap::builder::PossibleValue> {
        Some(match self {
            LintKind::DuplicatedKey => clap::builder::PossibleValue::new("DuplicatedKey"),
            LintKind::EndingBlankLine => clap::builder::PossibleValue::new("EndingBlankLine"),
            LintKind::ExtraBlankLine => clap::builder::PossibleValue::new("ExtraBlankLine"),
            LintKind::IncorrectDelimiter => clap::builder::PossibleValue::new("IncorrectDelimiter"),
            LintKind::KeyWithoutValue => clap::builder::PossibleValue::new("KeyWithoutValue"),
            LintKind::LeadingCharacter => clap::builder::PossibleValue::new("LeadingCharacter"),
            LintKind::LowercaseKey => clap::builder::PossibleValue::new("LowercaseKey"),
            LintKind::QuoteCharacter => clap::builder::PossibleValue::new("QuoteCharacter"),
            LintKind::SpaceCharacter => clap::builder::PossibleValue::new("SpaceCharacter"),
            LintKind::SubstitutionKey => clap::builder::PossibleValue::new("SubstitutionKey"),
            LintKind::TrailingWhitespace => clap::builder::PossibleValue::new("TrailingWhitespace"),
            LintKind::UnorderedKey => clap::builder::PossibleValue::new("UnorderedKey"),
            LintKind::ValueWithoutQuotes => clap::builder::PossibleValue::new("ValueWithoutQuotes"),
            LintKind::SchemaViolation => clap::builder::PossibleValue::new("SchemaViolation"),
        })
    }
}

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

    #[test]
    fn test_str_to_lint_variant_conversion() {
        let expected = <LintKind as FromStr>::from_str("DuplicatedKey").expect("parse lint");
        assert_eq!(expected, LintKind::DuplicatedKey);
    }

    #[test]
    fn test_invalid_lint_str_variant() {
        assert_eq!(Err(()), <LintKind as FromStr>::from_str("FooBarLint"));
    }

    #[test]
    fn test_lint_variant_display_output() {
        let one = LintKind::EndingBlankLine;
        let two = LintKind::DuplicatedKey;
        let three = LintKind::SubstitutionKey;

        assert_eq!(
            "EndingBlankLine DuplicatedKey SubstitutionKey",
            format!("{one} {two} {three}")
        );
    }
}
