use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeSpecifier {
    BinaryNumber,
    CharacterAsciiValue,
    DecimalNumber,
    IntegerNumber,
    ScientificNotation,
    UnsignedDecimalNumber,
    FloatingPointNumber,
    FloatingPointNumberWithSignificantDigits,
    OctalNumber,
    String,
    TrueOrFalse,
    TypeOfArgument,
    PrimitiveValue,
    HexadecimalNumberLowercase,
    HexadecimalNumberUppercase,
    Json,
}
impl TypeSpecifier {
    const fn is_numeric(self) -> bool {
        matches!(
            self,
            Self::BinaryNumber
                | Self::DecimalNumber
                | Self::IntegerNumber
                | Self::ScientificNotation
                | Self::UnsignedDecimalNumber
                | Self::FloatingPointNumber
                | Self::FloatingPointNumberWithSignificantDigits
                | Self::OctalNumber
                | Self::HexadecimalNumberLowercase
                | Self::HexadecimalNumberUppercase
        )
    }
}
impl std::fmt::Display for TypeSpecifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let specifier = match self {
            Self::BinaryNumber => 'b',
            Self::CharacterAsciiValue => 'c',
            Self::DecimalNumber => 'd',
            Self::IntegerNumber => 'i',
            Self::ScientificNotation => 'e',
            Self::UnsignedDecimalNumber => 'u',
            Self::FloatingPointNumber => 'f',
            Self::FloatingPointNumberWithSignificantDigits => 'g',
            Self::OctalNumber => 'o',
            Self::String => 's',
            Self::TrueOrFalse => 't',
            Self::TypeOfArgument => 'T',
            Self::PrimitiveValue => 'v',
            Self::HexadecimalNumberLowercase => 'x',
            Self::HexadecimalNumberUppercase => 'X',
            Self::Json => 'j',
        };
        write!(f, "{specifier}")
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgumentReference {
    Indexed(usize),
    Named(String),
}
impl std::fmt::Display for ArgumentReference {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ArgumentReference::Indexed(index) => write!(f, "{index}$"),
            ArgumentReference::Named(name) => write!(f, "({name})"),
        }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaddingSpecifier {
    Zero,
    Char(char),
}
impl std::fmt::Display for PaddingSpecifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PaddingSpecifier::Zero => write!(f, "0"),
            PaddingSpecifier::Char(c) => write!(f, "'{c}"),
        }
    }
}
impl PaddingSpecifier {
    pub fn char(self) -> char {
        match self {
            PaddingSpecifier::Zero => '0',
            PaddingSpecifier::Char(c) => c,
        }
    }
    pub const fn is_zero(self) -> bool {
        match self {
            PaddingSpecifier::Zero => true,
            PaddingSpecifier::Char(_) => false,
        }
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Placeholder {
    pub type_specifier: TypeSpecifier,
    pub requested_argument: Option<ArgumentReference>,
    pub plus_sign: bool,
    pub padding_specifier: Option<PaddingSpecifier>,
    pub left_align: bool,
    pub width: Option<usize>,
    pub precision: Option<usize>,
}
impl Placeholder {
    pub fn padding_specifier_is_zero(&self) -> bool {
        self.padding_specifier
            .is_some_and(PaddingSpecifier::is_zero)
    }
    pub fn numeric_width(&self) -> Option<usize> {
        self.width
            .filter(|_| self.padding_specifier_is_zero() && self.type_specifier.is_numeric())
    }
}
impl std::fmt::Display for Placeholder {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "%")?;
        if let Some(argument) = &self.requested_argument {
            write!(f, "{argument}")?;
        }
        if self.plus_sign {
            write!(f, "+")?;
        }
        if let Some(padding_specifier) = &self.padding_specifier {
            write!(f, "{padding_specifier}")?;
        }
        if self.left_align {
            write!(f, "-")?;
        }
        if let Some(width) = self.width {
            write!(f, "{width}")?;
        }
        if let Some(precision) = self.precision {
            write!(f, ".{precision}")?;
        }
        write!(f, "{}", self.type_specifier)
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
    parts: Vec<Part>,
}
impl std::fmt::Display for Message {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for part in &self.parts {
            write!(f, "{part}")?;
        }
        Ok(())
    }
}
impl FromIterator<Part> for Message {
    fn from_iter<T: IntoIterator<Item = Part>>(iter: T) -> Self {
        Self {
            parts: iter.into_iter().collect(),
        }
    }
}
impl Message {
    pub(crate) fn parts(&self) -> std::slice::Iter<'_, Part> {
        self.parts.iter()
    }
    #[must_use]
    pub fn from_literal(literal: String) -> Message {
        Message {
            parts: vec![Part::Text(literal)],
        }
    }
}
impl Serialize for Message {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let string = self.to_string();
        serializer.serialize_str(&string)
    }
}
impl<'de> Deserialize<'de> for Message {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let string = String::deserialize(deserializer)?;
        string.parse().map_err(serde::de::Error::custom)
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Part {
    Percent,
    Text(String),
    Placeholder(Placeholder),
}
impl From<Placeholder> for Part {
    fn from(placeholder: Placeholder) -> Self {
        Self::Placeholder(placeholder)
    }
}
impl From<String> for Part {
    fn from(text: String) -> Self {
        Self::Text(text)
    }
}
impl std::fmt::Display for Part {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Part::Percent => write!(f, "%%"),
            Part::Text(text) => write!(f, "{text}"),
            Part::Placeholder(placeholder) => write!(f, "{placeholder}"),
        }
    }
}