///|
fn clamp_percent(value : Int) -> Int {
  if value < 0 {
    0
  } else if value > 100 {
    100
  } else {
    value
  }
}

///|
fn positive_mod(value : Int, modulo : Int) -> Int {
  let result = value % modulo
  if result < 0 {
    result + modulo
  } else {
    result
  }
}

///|
fn cell_roll(seed : Int, x : Int, y : Int, salt : Int, modulo : Int) -> Int {
  let seed_part = positive_mod(seed, 10_000)
  let mixed = seed_part + x * 73 + y * 997 + salt * 131
  positive_mod(mixed * 37 + 17, modulo)
}

///|
/// Configuration for deterministic random grid generation.
pub(all) struct RandomGridConfig {
  width : Int
  height : Int
  seed : Int
  blocked_percent : Int
  weighted_percent : Int
  max_weight : Int
} derive(Eq, Debug)

///|
pub fn RandomGridConfig::new(
  width : Int,
  height : Int,
  seed : Int
) -> RandomGridConfig {
  {
    width,
    height,
    seed,
    blocked_percent: 20,
    weighted_percent: 15,
    max_weight: 6,
  }
}

///|
pub fn RandomGridConfig::with_blocked_percent(
  self : RandomGridConfig,
  blocked_percent : Int
) -> RandomGridConfig {
  {
    width: self.width,
    height: self.height,
    seed: self.seed,
    blocked_percent: clamp_percent(blocked_percent),
    weighted_percent: self.weighted_percent,
    max_weight: self.max_weight,
  }
}

///|
pub fn RandomGridConfig::with_weighted_percent(
  self : RandomGridConfig,
  weighted_percent : Int
) -> RandomGridConfig {
  {
    width: self.width,
    height: self.height,
    seed: self.seed,
    blocked_percent: self.blocked_percent,
    weighted_percent: clamp_percent(weighted_percent),
    max_weight: self.max_weight,
  }
}

///|
pub fn RandomGridConfig::with_max_weight(
  self : RandomGridConfig,
  max_weight : Int
) -> RandomGridConfig {
  {
    width: self.width,
    height: self.height,
    seed: self.seed,
    blocked_percent: self.blocked_percent,
    weighted_percent: self.weighted_percent,
    max_weight: if max_weight < 2 { 2 } else { max_weight },
  }
}

///|
pub(all) struct GridStats {
  cells : Int
  open_cells : Int
  blocked_cells : Int
  weighted_cells : Int
} derive(Eq, Debug)

///|
pub fn GridMap::set_open(self : GridMap, point : Point) -> GridMap {
  match self.index(point) {
    Some(index) => self.weights[index] = 1
    None => ()
  }
  self
}

///|
pub fn GridMap::blocked_count(self : GridMap) -> Int {
  let mut count = 0
  for i = 0; i < self.weights.length(); i = i + 1 {
    if self.weights[i] < 0 {
      count = count + 1
    }
  }
  count
}

///|
pub fn GridMap::weighted_count(self : GridMap) -> Int {
  let mut count = 0
  for i = 0; i < self.weights.length(); i = i + 1 {
    if self.weights[i] > 1 {
      count = count + 1
    }
  }
  count
}

///|
pub fn GridMap::stats(self : GridMap) -> GridStats {
  let blocked = self.blocked_count()
  let weighted = self.weighted_count()
  {
    cells: self.cell_count(),
    open_cells: self.cell_count() - blocked,
    blocked_cells: blocked,
    weighted_cells: weighted,
  }
}

///|
/// Builds a deterministic pseudo-random grid from a seed.
pub fn GridMap::random(config : RandomGridConfig) -> GridMap {
  let grid = GridMap::new(config.width, config.height)
  for y = 0; y < config.height; y = y + 1 {
    for x = 0; x < config.width; x = x + 1 {
      let point = Point::new(x, y)
      let terrain_roll = cell_roll(config.seed, x, y, 1, 100)
      if terrain_roll < config.blocked_percent {
        grid.set_blocked(point) |> ignore
      } else {
        let weight_roll = cell_roll(config.seed, x, y, 2, 100)
        if weight_roll < config.weighted_percent {
          let weight = 2 + cell_roll(config.seed, x, y, 3, config.max_weight - 1)
          grid.set_weight(point, weight) |> ignore
        }
      }
    }
  }
  grid
}

///|
/// Builds a deterministic grid and guarantees important cells stay open.
pub fn GridMap::random_with_clear_points(
  config : RandomGridConfig,
  clear_points : Array[Point]
) -> GridMap {
  let grid = GridMap::random(config)
  for i = 0; i < clear_points.length(); i = i + 1 {
    grid.set_open(clear_points[i]) |> ignore
  }
  grid
}