use std::hash::Hash;

use glam::Vec2;
use ordered_float::OrderedFloat;

use crate::{color::Color, handle::Handle};

/// Rect
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Rect {
    pub min: Vec2,
    pub max: Vec2,
}

impl Rect {
    pub fn new(center: Vec2, radius: f32) -> Self {
        Self {
            min: center - Vec2::splat(radius),
            max: center + Vec2::splat(radius),
        }
    }

    pub fn is_touching(&self, other: &Self) -> bool {
        !(self.min.x > other.max.x
            || self.max.x < other.min.x
            || self.min.y > other.max.y
            || self.max.y < other.min.y)
    }

    pub fn contains_pos(&self, pos: Vec2) -> bool {
        let Rect { min, max } = self;
        pos.x >= min.x && pos.y >= min.y && pos.x <= max.x && pos.y <= max.y
    }

    pub fn center(&self) -> Vec2 {
        (self.min + self.max) * 0.5
    }

    pub fn size(&self) -> Vec2 {
        self.max - self.min
    }
    pub fn overlap(&self, other: &Self) -> Vec2 {
        (self.max - other.min).min(other.max - self.min)
    }
}

// Line pattern types
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinePattern {
    Solid,
    Dashed { dash_length: f32, gap_length: f32 },
    Dotted { spacing: f32 },
}

#[derive(Debug, Clone)]
pub struct Line {
    pub start: Vec2,
    pub end: Vec2,
    pub thickness: f32,
    pub color: Color,
}

#[derive(Debug, Clone)]
pub(crate) enum DrawContent {
    Texture {
        handle: Handle,
        color: Color,
    },
    Lines(Vec<Line>),
    Circle {
        radius: f32,
        color: Color,
    },
    Polygon {
        vertices: Vec<Vec2>,
        color: Color,
    },
    PolygonTextured {
        vertices: Vec<Vec2>,
        uvs: Vec<Vec2>,
        color: Color,
        handle: Handle,
    },
}

impl DrawContent {
    pub fn texture_handle(&self) -> Option<&Handle> {
        if let Self::Texture { handle, .. } = self {
            Some(handle)
        } else if let Self::PolygonTextured { handle, .. } = self {
            Some(handle)
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct LineCacheKey {
    start: [OrderedFloat<f32>; 2],
    end: [OrderedFloat<f32>; 2],
    thickness: OrderedFloat<f32>,
}

/// DrawContentCacheKey
///
/// We only cache pipeline shape information here and ignore the color information
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum DrawContentCacheKey {
    Texture {
        handle: u64,
    },
    Lines(Vec<LineCacheKey>),
    Circle {
        radius: OrderedFloat<f32>,
    },
    Polygon(Vec<[OrderedFloat<f32>; 2]>),
    PolygonTextured {
        handle: u64,
        vertices: Vec<[OrderedFloat<f32>; 2]>,
        uvs: Vec<[OrderedFloat<f32>; 2]>,
    },
}

impl From<DrawContent> for DrawContentCacheKey {
    fn from(value: DrawContent) -> Self {
        match value {
            DrawContent::Texture { handle, color: _ } => Self::Texture {
                handle: handle.id(),
            },
            DrawContent::Lines(lines) => Self::Lines(
                lines
                    .into_iter()
                    .map(
                        |Line {
                             start,
                             end,
                             thickness,
                             color: _,
                         }| {
                            LineCacheKey {
                                start: [start.x.into(), start.y.into()],
                                end: [end.x.into(), end.y.into()],
                                thickness: thickness.into(),
                            }
                        },
                    )
                    .collect(),
            ),
            DrawContent::Circle { radius, color: _ } => Self::Circle {
                radius: radius.into(),
            },
            DrawContent::Polygon { vertices, color: _ } => Self::Polygon(
                vertices
                    .into_iter()
                    .map(|Vec2 { x, y }| [OrderedFloat(x), OrderedFloat(y)])
                    .collect(),
            ),
            DrawContent::PolygonTextured {
                vertices,
                uvs,
                handle,
                color: _,
            } => Self::PolygonTextured {
                handle: handle.id(),
                vertices: vertices
                    .into_iter()
                    .map(|Vec2 { x, y }| [OrderedFloat(x), OrderedFloat(y)])
                    .collect(),
                uvs: uvs
                    .into_iter()
                    .map(|Vec2 { x, y }| [OrderedFloat(x), OrderedFloat(y)])
                    .collect(),
            },
        }
    }
}
