use emath::{Rect, Vec2, vec2};

/// A value for all four sides of a rectangle,
/// often used to express padding or spacing.
///
/// Can be added and subtracted to/from [`Rect`]s.
///
/// Negative margins are possible, but may produce weird behavior.
/// Use with care.
///
/// All values are stored as [`i8`] to keep the size of [`Margin`] small.
/// If you want floats, use [`crate::MarginF32`] instead.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Margin {
    pub left: i8,
    pub right: i8,
    pub top: i8,
    pub bottom: i8,
}

impl Margin {
    pub const ZERO: Self = Self {
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
    };

    /// The same margin on every side.
    #[doc(alias = "symmetric")]
    #[inline]
    pub const fn same(margin: i8) -> Self {
        Self {
            left: margin,
            right: margin,
            top: margin,
            bottom: margin,
        }
    }

    /// Margins with the same size on opposing sides
    #[inline]
    pub const fn symmetric(x: i8, y: i8) -> Self {
        Self {
            left: x,
            right: x,
            top: y,
            bottom: y,
        }
    }

    /// Left margin, as `f32`
    #[inline]
    pub const fn leftf(self) -> f32 {
        self.left as _
    }

    /// Right margin, as `f32`
    #[inline]
    pub const fn rightf(self) -> f32 {
        self.right as _
    }

    /// Top margin, as `f32`
    #[inline]
    pub const fn topf(self) -> f32 {
        self.top as _
    }

    /// Bottom margin, as `f32`
    #[inline]
    pub const fn bottomf(self) -> f32 {
        self.bottom as _
    }

    /// Total margins on both sides
    #[inline]
    pub fn sum(self) -> Vec2 {
        vec2(self.leftf() + self.rightf(), self.topf() + self.bottomf())
    }

    #[inline]
    pub const fn left_top(self) -> Vec2 {
        vec2(self.leftf(), self.topf())
    }

    #[inline]
    pub const fn right_bottom(self) -> Vec2 {
        vec2(self.rightf(), self.bottomf())
    }

    /// Are the margin on every side the same?
    #[doc(alias = "symmetric")]
    #[inline]
    pub const fn is_same(self) -> bool {
        self.left == self.right && self.left == self.top && self.left == self.bottom
    }
}

impl From<i8> for Margin {
    #[inline]
    fn from(v: i8) -> Self {
        Self::same(v)
    }
}

impl From<f32> for Margin {
    #[inline]
    fn from(v: f32) -> Self {
        Self::same(v.round() as _)
    }
}

impl From<Vec2> for Margin {
    #[inline]
    fn from(v: Vec2) -> Self {
        Self::symmetric(v.x.round() as _, v.y.round() as _)
    }
}

/// `Margin + Margin`
impl core::ops::Add for Margin {
    type Output = Self;

    #[inline]
    fn add(self, other: Self) -> Self {
        Self {
            left: self.left.saturating_add(other.left),
            right: self.right.saturating_add(other.right),
            top: self.top.saturating_add(other.top),
            bottom: self.bottom.saturating_add(other.bottom),
        }
    }
}

/// `Margin + i8`
impl core::ops::Add<i8> for Margin {
    type Output = Self;

    #[inline]
    fn add(self, v: i8) -> Self {
        Self {
            left: self.left.saturating_add(v),
            right: self.right.saturating_add(v),
            top: self.top.saturating_add(v),
            bottom: self.bottom.saturating_add(v),
        }
    }
}

/// `Margin += i8`
impl core::ops::AddAssign<i8> for Margin {
    #[inline]
    fn add_assign(&mut self, v: i8) {
        *self = *self + v;
    }
}

/// `Margin * f32`
impl core::ops::Mul<f32> for Margin {
    type Output = Self;

    #[inline]
    fn mul(self, v: f32) -> Self {
        Self {
            left: (self.leftf() * v).round() as _,
            right: (self.rightf() * v).round() as _,
            top: (self.topf() * v).round() as _,
            bottom: (self.bottomf() * v).round() as _,
        }
    }
}

/// `Margin *= f32`
impl core::ops::MulAssign<f32> for Margin {
    #[inline]
    fn mul_assign(&mut self, v: f32) {
        *self = *self * v;
    }
}

/// `Margin / f32`
impl core::ops::Div<f32> for Margin {
    type Output = Self;

    #[inline]
    fn div(self, v: f32) -> Self {
        #![expect(clippy::suspicious_arithmetic_impl)]
        self * v.recip()
    }
}

/// `Margin /= f32`
impl core::ops::DivAssign<f32> for Margin {
    #[inline]
    fn div_assign(&mut self, v: f32) {
        *self = *self / v;
    }
}

/// `Margin - Margin`
impl core::ops::Sub for Margin {
    type Output = Self;

    #[inline]
    fn sub(self, other: Self) -> Self {
        Self {
            left: self.left.saturating_sub(other.left),
            right: self.right.saturating_sub(other.right),
            top: self.top.saturating_sub(other.top),
            bottom: self.bottom.saturating_sub(other.bottom),
        }
    }
}

/// `Margin - i8`
impl core::ops::Sub<i8> for Margin {
    type Output = Self;

    #[inline]
    fn sub(self, v: i8) -> Self {
        Self {
            left: self.left.saturating_sub(v),
            right: self.right.saturating_sub(v),
            top: self.top.saturating_sub(v),
            bottom: self.bottom.saturating_sub(v),
        }
    }
}

/// `Margin -= i8`
impl core::ops::SubAssign<i8> for Margin {
    #[inline]
    fn sub_assign(&mut self, v: i8) {
        *self = *self - v;
    }
}

/// `Rect + Margin`
impl core::ops::Add<Margin> for Rect {
    type Output = Self;

    #[inline]
    fn add(self, margin: Margin) -> Self {
        Self::from_min_max(
            self.min - margin.left_top(),
            self.max + margin.right_bottom(),
        )
    }
}

/// `Rect += Margin`
impl core::ops::AddAssign<Margin> for Rect {
    #[inline]
    fn add_assign(&mut self, margin: Margin) {
        *self = *self + margin;
    }
}

/// `Rect - Margin`
impl core::ops::Sub<Margin> for Rect {
    type Output = Self;

    #[inline]
    fn sub(self, margin: Margin) -> Self {
        Self::from_min_max(
            self.min + margin.left_top(),
            self.max - margin.right_bottom(),
        )
    }
}

/// `Rect -= Margin`
impl core::ops::SubAssign<Margin> for Rect {
    #[inline]
    fn sub_assign(&mut self, margin: Margin) {
        *self = *self - margin;
    }
}
