use core::num::NonZeroU16;

use serde::{Deserialize, Serialize};

use crate::theme::Spacing;

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct FlexWeight(NonZeroU16);

impl FlexWeight {
    pub const ONE: Self = Self(NonZeroU16::MIN);

    #[must_use]
    pub const fn new(weight: NonZeroU16) -> Self {
        Self(weight)
    }

    #[must_use]
    pub fn as_f32(self) -> f32 {
        f32::from(self.0.get())
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct GridLine(NonZeroU16);

impl GridLine {
    #[must_use]
    pub const fn new(line: NonZeroU16) -> Self {
        Self(line)
    }

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

    #[must_use]
    pub const fn taffy_index(self) -> i16 {
        let n = self.0.get();
        if n > i16::MAX as u16 {
            i16::MAX
        } else {
            n.cast_signed()
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TrackSize {
    Fixed(Spacing),
    Auto,
    Flex(FlexWeight),
}

impl TrackSize {
    pub const FLEX_1: Self = Self::Flex(FlexWeight::ONE);
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TrackName(&'static str);

impl TrackName {
    #[must_use]
    pub const fn new(name: &'static str) -> Self {
        Self(name)
    }

    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        self.0
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct GridTrack {
    pub name: Option<TrackName>,
    pub size: TrackSize,
}

impl GridTrack {
    #[must_use]
    pub const fn unnamed(size: TrackSize) -> Self {
        Self { name: None, size }
    }

    #[must_use]
    pub const fn named(name: TrackName, size: TrackSize) -> Self {
        Self {
            name: Some(name),
            size,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GridLineRef {
    Line(GridLine),
    Name(TrackName),
}

impl From<GridLine> for GridLineRef {
    fn from(line: GridLine) -> Self {
        Self::Line(line)
    }
}

impl From<TrackName> for GridLineRef {
    fn from(name: TrackName) -> Self {
        Self::Name(name)
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct GridSpan {
    pub column_start: GridLineRef,
    pub column_end: GridLineRef,
    pub row_start: GridLineRef,
    pub row_end: GridLineRef,
}

impl GridSpan {
    #[must_use]
    pub fn rect(
        column_start: GridLine,
        column_end: GridLine,
        row_start: GridLine,
        row_end: GridLine,
    ) -> Option<Self> {
        if column_end.get() <= column_start.get() || row_end.get() <= row_start.get() {
            return None;
        }
        Some(Self {
            column_start: GridLineRef::Line(column_start),
            column_end: GridLineRef::Line(column_end),
            row_start: GridLineRef::Line(row_start),
            row_end: GridLineRef::Line(row_end),
        })
    }
}
