use serde::{Deserialize, Serialize};

macro_rules! px_newtype_nonneg {
    ($name:ident) => {
        #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
        #[serde(transparent)]
        pub struct $name(f32);

        impl $name {
            #[must_use]
            pub const fn px(value: f32) -> Self {
                assert!(
                    value.is_finite() && value >= 0.0,
                    concat!(
                        stringify!($name),
                        " must be a non-negative finite value in device-independent pixels"
                    )
                );
                Self(value)
            }

            #[must_use]
            pub const fn value_px(self) -> f32 {
                self.0
            }
        }

        impl core::fmt::Display for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                write!(f, "{}px", self.0)
            }
        }
    };
}

macro_rules! px_newtype_signed {
    ($name:ident) => {
        #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
        #[serde(transparent)]
        pub struct $name(f32);

        impl $name {
            #[must_use]
            pub const fn px(value: f32) -> Self {
                assert!(
                    value.is_finite(),
                    concat!(
                        stringify!($name),
                        " must be a finite value in device-independent pixels"
                    )
                );
                Self(value)
            }

            #[must_use]
            pub const fn value_px(self) -> f32 {
                self.0
            }
        }

        impl core::fmt::Display for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                write!(f, "{}px", self.0)
            }
        }
    };
}

px_newtype_nonneg!(Spacing);
px_newtype_nonneg!(Radius);
px_newtype_nonneg!(StrokeWidth);
px_newtype_nonneg!(BlurRadius);
px_newtype_signed!(ShadowOffset);

impl StrokeWidth {
    pub const HAIRLINE: Self = Self::px(1.0);
}
