use serde::{Deserialize, Serialize};

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TermColor {
    Black,
    DarkGrey,
    Red,
    DarkRed,
    Green,
    DarkGreen,
    Yellow,
    DarkYellow,
    Blue,
    DarkBlue,
    Magenta,
    DarkMagenta,
    Cyan,
    DarkCyan,
    #[default]
    White,
    Grey,
    Rgb {
        r: u8,
        g: u8,
        b: u8,
    },
}

#[cfg(feature = "terminal")]
impl TermColor {
    pub fn to_crossterm_color(self) -> crossterm::style::Color {
        match self {
            Self::Black => crossterm::style::Color::Black,
            Self::DarkGrey => crossterm::style::Color::DarkGrey,
            Self::Red => crossterm::style::Color::Red,
            Self::DarkRed => crossterm::style::Color::DarkRed,
            Self::Green => crossterm::style::Color::Green,
            Self::DarkGreen => crossterm::style::Color::DarkGreen,
            Self::Yellow => crossterm::style::Color::Yellow,
            Self::DarkYellow => crossterm::style::Color::DarkYellow,
            Self::Blue => crossterm::style::Color::Blue,
            Self::DarkBlue => crossterm::style::Color::DarkBlue,
            Self::Magenta => crossterm::style::Color::Magenta,
            Self::DarkMagenta => crossterm::style::Color::DarkMagenta,
            Self::Cyan => crossterm::style::Color::Cyan,
            Self::DarkCyan => crossterm::style::Color::DarkCyan,
            Self::White => crossterm::style::Color::White,
            Self::Grey => crossterm::style::Color::Grey,
            Self::Rgb { r, g, b } => crossterm::style::Color::Rgb { r, g, b },
        }
    }
}

#[cfg(feature = "tui")]
impl TermColor {
    pub fn to_vec4(self) -> [f32; 4] {
        match self {
            Self::Black => [0.0, 0.0, 0.0, 1.0],
            Self::DarkGrey => [0.33, 0.33, 0.33, 1.0],
            Self::Red => [1.0, 0.0, 0.0, 1.0],
            Self::DarkRed => [0.55, 0.0, 0.0, 1.0],
            Self::Green => [0.0, 1.0, 0.0, 1.0],
            Self::DarkGreen => [0.0, 0.55, 0.0, 1.0],
            Self::Yellow => [1.0, 1.0, 0.0, 1.0],
            Self::DarkYellow => [0.55, 0.55, 0.0, 1.0],
            Self::Blue => [0.0, 0.0, 1.0, 1.0],
            Self::DarkBlue => [0.0, 0.0, 0.55, 1.0],
            Self::Magenta => [1.0, 0.0, 1.0, 1.0],
            Self::DarkMagenta => [0.55, 0.0, 0.55, 1.0],
            Self::Cyan => [0.0, 1.0, 1.0, 1.0],
            Self::DarkCyan => [0.0, 0.55, 0.55, 1.0],
            Self::White => [1.0, 1.0, 1.0, 1.0],
            Self::Grey => [0.75, 0.75, 0.75, 1.0],
            Self::Rgb { r, g, b } => [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0],
        }
    }
}

#[derive(Default, Debug, Clone, Copy)]
pub struct Position {
    pub column: f64,
    pub row: f64,
}

#[derive(Default, Debug, Clone, Copy)]
pub struct Velocity {
    pub column: f64,
    pub row: f64,
}

#[derive(Debug, Clone, Copy)]
pub struct Sprite {
    pub character: char,
    pub foreground: TermColor,
    pub background: TermColor,
}

impl Default for Sprite {
    fn default() -> Self {
        Self {
            character: ' ',
            foreground: TermColor::White,
            background: TermColor::Black,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Collider {
    pub width: u16,
    pub height: u16,
    pub offset_column: f64,
    pub offset_row: f64,
    pub layer: u32,
    pub mask: u32,
}

impl Default for Collider {
    fn default() -> Self {
        Self {
            width: 0,
            height: 0,
            offset_column: 0.0,
            offset_row: 0.0,
            layer: 1,
            mask: u32::MAX,
        }
    }
}

#[derive(Default, Debug, Clone)]
pub struct Name(pub String);

#[derive(Debug, Clone, Copy)]
pub struct Visibility {
    pub visible: bool,
}

impl Default for Visibility {
    fn default() -> Self {
        Self { visible: true }
    }
}

#[derive(Default, Debug, Clone, Copy)]
pub struct ZIndex(pub i32);

#[derive(Default, Debug, Clone, Copy)]
pub struct Parent(pub freecs::Entity);

#[derive(Debug, Clone)]
pub struct Label {
    pub text: String,
    pub foreground: TermColor,
    pub background: TermColor,
}

impl Default for Label {
    fn default() -> Self {
        Self {
            text: String::new(),
            foreground: TermColor::White,
            background: TermColor::Black,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct TilemapCell {
    pub character: char,
    pub foreground: TermColor,
    pub background: TermColor,
}

impl Default for TilemapCell {
    fn default() -> Self {
        Self {
            character: '\0',
            foreground: TermColor::White,
            background: TermColor::Black,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Tilemap {
    pub width: usize,
    pub height: usize,
    pub cells: Vec<TilemapCell>,
}

impl Tilemap {
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            width,
            height,
            cells: vec![TilemapCell::default(); width * height],
        }
    }

    pub fn get(&self, column: usize, row: usize) -> Option<&TilemapCell> {
        if column < self.width && row < self.height {
            Some(&self.cells[row * self.width + column])
        } else {
            None
        }
    }

    pub fn get_mut(&mut self, column: usize, row: usize) -> Option<&mut TilemapCell> {
        if column < self.width && row < self.height {
            Some(&mut self.cells[row * self.width + column])
        } else {
            None
        }
    }

    pub fn set(&mut self, column: usize, row: usize, cell: TilemapCell) {
        if column < self.width && row < self.height {
            self.cells[row * self.width + column] = cell;
        }
    }
}

impl Default for Tilemap {
    fn default() -> Self {
        Self::new(0, 0)
    }
}

#[derive(Debug, Clone)]
pub struct SpriteAnimation {
    pub frames: Vec<char>,
    pub frame_duration: f64,
    pub elapsed: f64,
    pub current_frame: usize,
    pub looping: bool,
    pub finished: bool,
}

impl Default for SpriteAnimation {
    fn default() -> Self {
        Self {
            frames: Vec::new(),
            frame_duration: 0.1,
            elapsed: 0.0,
            current_frame: 0,
            looping: true,
            finished: false,
        }
    }
}

#[derive(Default, Debug, Clone, Copy)]
pub struct LocalOffset {
    pub column: f64,
    pub row: f64,
}
