///|
pub(all) struct GridDescriptor {
  grid : Grid1D
  cell_centers : Array[Double]
  cell_edges : Array[Double]
} derive(Debug, ToJson)

///|
pub fn describe_grid(grid : Grid1D) -> GridDescriptor {
  {
    grid,
    cell_centers: grid_coordinates(grid),
    cell_edges: Array::makei(grid.cells + 1, fn(i) { i.to_double() * grid.dx }),
  }
}

///|
pub fn grid_coordinates(grid : Grid1D) -> Array[Double] {
  Array::makei(grid.cells, fn(i) { grid.position(i) })
}

///|
pub fn grid_cell_edges(grid : Grid1D) -> Array[Double] {
  Array::makei(grid.cells + 1, fn(i) { i.to_double() * grid.dx })
}

///|
pub fn grid_cell_volume(grid : Grid1D) -> Double {
  grid.dx
}

///|
pub fn grid_index_at(grid : Grid1D, x : Double) -> Int {
  nearest_index(grid, x)
}

///|
pub fn grid_neighbor_indices(grid : Grid1D, index : Int) -> (Int, Int) {
  (modulo_index(index - 1, grid.cells), modulo_index(index + 1, grid.cells))
}

///|
pub fn grid_distance(grid : Grid1D, first : Double, second : Double) -> Double {
  let raw = (grid.wrap(first) - grid.wrap(second)).abs()
  raw.min(grid.length - raw)
}

///|
pub fn grid_interpolate_coordinates(
  grid : Grid1D,
  count : Int,
) -> Array[Double] {
  linspace(0.0, grid.length, count).map(fn(x) { grid.wrap(x) })
}

///|
pub fn grid_periodic_copy(
  grid : Grid1D,
  values : ArrayView[Double],
  shift : Int,
) -> Array[Double] {
  Array::makei(grid.cells, fn(i) {
    if values.length() == 0 {
      0.0
    } else {
      values[modulo_index(i - shift, values.length())]
    }
  })
}

///|
pub fn grid_roll(
  grid : Grid1D,
  values : ArrayView[Double],
  shift : Int,
) -> Array[Double] {
  grid_periodic_copy(grid, values, shift)
}

///|
pub fn grid_integral(grid : Grid1D, values : ArrayView[Double]) -> Double {
  sum_values(values) * grid.dx
}

///|
pub fn grid_weighted_integral(
  grid : Grid1D,
  values : ArrayView[Double],
  weight : ArrayView[Double],
) -> Double {
  weighted_sum(values, weight) * grid.dx
}

///|
pub fn grid_variance(grid : Grid1D, values : ArrayView[Double]) -> Double {
  let average = mean(values)
  safe_ratio(
    values.fold(init=0.0, fn(acc, value) {
      let difference = value - average
      acc + difference * difference
    }) *
    grid.dx,
    grid.length,
    0.0,
  )
}

///|
pub fn grid_center_of_mass(
  grid : Grid1D,
  density : ArrayView[Double],
) -> Double {
  let mut numerator = 0.0
  let mut denominator = 0.0
  for i in 0.. Double {
  mean(values)
}

///|
pub fn grid_smooth(
  grid : Grid1D,
  values : ArrayView[Double],
  passes : Int,
) -> Array[Double] {
  let mut output = values.to_owned()
  for _ in 0.. Array[Double] {
  Array::makei(grid.cells, fn(i) {
    if i < values.length() {
      clamp(values[i], low, high)
    } else {
      0.0
    }
  })
}

///|
pub fn grid_regrid(
  source : Grid1D,
  values : ArrayView[Double],
  target : Grid1D,
) -> Array[Double] {
  resample_periodic(source, values, target, Linear)
}

///|
pub fn grid_l2_difference(
  grid : Grid1D,
  first : ArrayView[Double],
  second : ArrayView[Double],
) -> Double {
  field_l2_error(first, second) * grid.dx.sqrt()
}