use std::{fmt::Display, ops::Range, path::PathBuf};

use bevy::prelude::Reflect;
use serde::{Deserialize, Serialize};

pub type Kilometer = f32;

#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)]
pub struct Coords {
    pub x: i32,
    pub y: i32,
}

impl Coords {
    pub fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }
}

#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)]
pub struct CoordRect {
    pub origin: Coords,
    pub size: Coords,
}

impl CoordRect {
    pub fn from_size(center: Coords, size: Coords) -> Self {
        Self {
            origin: Coords {
                x: center.x - size.x / 2,
                y: center.y - size.y / 2,
            },
            size,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.size.x <= 0 || self.size.y <= 0
    }

    pub fn width_range(&self) -> Range<i32> {
        self.origin.x..(self.origin.x + self.size.x)
    }

    pub fn height_range(&self) -> Range<i32> {
        self.origin.y..(self.origin.y + self.size.y)
    }

    /// Return iterator of all possible coordinates in rect, also known as the cartesian product of
    /// the x and y ranges
    pub fn iter_coords(&self) -> impl Iterator<Item = Coords> {
        self.width_range()
            .flat_map(|x| self.height_range().map(move |y| Coords::new(x, y)))
    }

    pub fn pick_random_coords(&self, count: usize) -> Vec<Coords> {
        if self.is_empty() {
            return vec![];
        }
        fastrand::choose_multiple(self.iter_coords(), count)
    }
}

impl Display for Coords {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

pub fn roll_d20() -> i8 {
    fastrand::i8(1..=20)
}

pub fn definitions_path() -> PathBuf {
    PathBuf::from(".").join("defs")
}

pub const SAVE_FILE: &str = "world.bin";
