use crate::{saturating_add_signed::SaturatingAddSigned, utils::Utils};
use derive_more::{Add, Constructor, IsVariant, Sub};
use mkutils_macros::{
    SaturatingAdd as SaturatingAddImpl, SaturatingAddSigned as SaturatingAddSignedImpl,
    SaturatingSub as SaturatingSubImpl, Toggle,
};
use num::traits::{ConstZero, SaturatingSub};
use ratatui::layout::{Direction, Size};
use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, IsVariant, Toggle)]
pub enum Orientation {
    Horizontal,
    Vertical,
}

impl Orientation {
    #[must_use]
    pub const fn direction(&self) -> Direction {
        match self {
            Self::Horizontal => Direction::Horizontal,
            Self::Vertical => Direction::Vertical,
        }
    }
}

impl From<Orientation> for Direction {
    fn from(orientation: Orientation) -> Self {
        orientation.direction()
    }
}

// NOTE-ee355f
#[derive(
    Add,
    Clone,
    Constructor,
    Copy,
    Debug,
    Default,
    Deserialize,
    SaturatingAddImpl,
    SaturatingSubImpl,
    SaturatingAddSignedImpl,
    Serialize,
    Sub,
)]
#[saturating_add_signed(assoc(type Signed = Point<<T as SaturatingAddSigned>::Signed>))]
pub struct Point<T> {
    pub x: T,
    pub y: T,
}

impl<T> Point<T> {
    // TODO: would like to have an [impl<T, S: Into<T>> From<Point<S>> for Point<T>]
    // impl but conflicts with [From<T> for T] when [S = T] in the first
    pub fn into_point<S: From<T>>(self) -> Point<S> {
        self.x.pair(self.y).into()
    }

    pub fn into_pair(self) -> (T, T) {
        self.into()
    }

    pub const fn get(&self, orientation: Orientation) -> &T {
        match orientation {
            Orientation::Horizontal => &self.x,
            Orientation::Vertical => &self.y,
        }
    }

    pub const fn get_mut(&mut self, orientation: Orientation) -> &mut T {
        match orientation {
            Orientation::Horizontal => &mut self.x,
            Orientation::Vertical => &mut self.y,
        }
    }
}

impl<T: ConstZero> Point<T> {
    pub const ZERO: Self = Self { x: T::ZERO, y: T::ZERO };
}

impl<T: SaturatingSub> Point<T> {
    #[must_use]
    pub fn saturating_sub_scalar(&self, rhs: &T) -> Self {
        let x = self.x.saturating_sub(rhs);
        let y = self.y.saturating_sub(rhs);

        Self::new(x, y)
    }
}

impl<T, X: Into<T>, Y: Into<T>> From<(X, Y)> for Point<T> {
    fn from((x, y): (X, Y)) -> Self {
        Self::new(x.into(), y.into())
    }
}

impl<T: From<u16>> From<Size> for Point<T> {
    fn from(size: Size) -> Self {
        size.convert::<(u16, u16)>().into()
    }
}

impl<T, X: From<T>, Y: From<T>> From<Point<T>> for (X, Y) {
    fn from(point: Point<T>) -> Self {
        point.x.convert::<X>().pair(point.y.into())
    }
}

pub type PointU16 = Point<u16>;
pub type PointUsize = Point<usize>;
pub type PointIsize = Point<isize>;
