///|
fn abs_double(value : Double) -> Double {
  if value < 0.0 {
    0.0 - value
  } else {
    value
  }
}

///|
fn clamp_int(value : Int, min_value : Int, max_value : Int) -> Int {
  if value < min_value {
    min_value
  } else if value > max_value {
    max_value
  } else {
    value
  }
}

///|
fn terrain_cell_at(
  grid : @terrain.TerrainGrid,
  row : Int,
  col : Int,
) -> @core.TerrainCell? {
  if row < 0 || col < 0 || row >= grid.rows || col >= grid.cols {
    None
  } else {
    grid.cells.get(row * grid.cols + col)
  }
}

///|
fn selected_cell(
  grid : @terrain.TerrainGrid,
  row : Int,
  col : Int,
) -> @core.TerrainCell {
  let bounded_row = clamp_int(row, 0, grid.rows - 1)
  let bounded_col = clamp_int(col, 0, grid.cols - 1)
  terrain_cell_at(grid, bounded_row, bounded_col).unwrap()
}

///|
fn cell_metric_pair(
  grid : @terrain.TerrainGrid,
  cell : @core.TerrainCell,
) -> (Double, Double) {
  let mut max_delta = 0.0
  let mut total_delta = 0.0
  let mut neighbor_count = 0
  match terrain_cell_at(grid, cell.row - 1, cell.col) {
    Some(neighbor) => {
      let delta = abs_double(cell.elevation_m - neighbor.elevation_m)
      total_delta += delta
      neighbor_count += 1
      if delta > max_delta {
        max_delta = delta
      }
    }
    None => ()
  }
  match terrain_cell_at(grid, cell.row + 1, cell.col) {
    Some(neighbor) => {
      let delta = abs_double(cell.elevation_m - neighbor.elevation_m)
      total_delta += delta
      neighbor_count += 1
      if delta > max_delta {
        max_delta = delta
      }
    }
    None => ()
  }
  match terrain_cell_at(grid, cell.row, cell.col - 1) {
    Some(neighbor) => {
      let delta = abs_double(cell.elevation_m - neighbor.elevation_m)
      total_delta += delta
      neighbor_count += 1
      if delta > max_delta {
        max_delta = delta
      }
    }
    None => ()
  }
  match terrain_cell_at(grid, cell.row, cell.col + 1) {
    Some(neighbor) => {
      let delta = abs_double(cell.elevation_m - neighbor.elevation_m)
      total_delta += delta
      neighbor_count += 1
      if delta > max_delta {
        max_delta = delta
      }
    }
    None => ()
  }
  let slope_grade = if grid.cell_size_m <= 0.0 {
    0.0
  } else {
    max_delta / grid.cell_size_m
  }
  let roughness_m = if neighbor_count == 0 {
    0.0
  } else {
    total_delta / neighbor_count.to_double()
  }
  (slope_grade, roughness_m)
}

///|
fn cell_hazard(slope_grade : Double, roughness_m : Double) -> String {
  if slope_grade >= 0.35 || roughness_m >= 2.5 {
    "blocked"
  } else if slope_grade >= 0.18 || roughness_m >= 1.2 {
    "caution"
  } else {
    "clear"
  }
}

///|
fn default_selected_cell(grid : @terrain.TerrainGrid) -> @core.TerrainCell {
  let mut selected = grid.cells.get(0).unwrap()
  let mut selected_risk = 0.0
  for cell in grid.cells {
    let metric = cell_metric_pair(grid, cell)
    let risk = metric.0 * 10.0 + metric.1
    if risk > selected_risk {
      selected = cell
      selected_risk = risk
    }
  }
  selected
}

///|
fn cell_id(grid : @terrain.TerrainGrid, row : Int, col : Int) -> String {
  "\{grid.tile_id}/r\{row}-c\{col}"
}