///|
fn landscape_grid(start : Double, end : Double, samples : Int) -> Array[Double] {
  Array::makei(samples, fn(i) {
    if i == samples - 1 {
      end
    } else {
      start + (end - start) * i.to_double() / (samples - 1).to_double()
    }
  })
}

///|
/// Raw persistence landscape samples, layer-major, without normalization.
/// lambda_k(t) is the kth largest max(0, min(t-birth, death-t)).
/// Censored intervals are excluded; use identical ranges when comparing vectors.
pub fn persistence_landscape(
  analysis : Analysis,
  dimension~ : Int,
  start~ : Double,
  end~ : Double,
  samples? : Int = 101,
  layers? : Int = 3,
) -> Array[Array[Double]] raise TopologyError {
  if dimension < 0 ||
    dimension > 1 ||
    !finite(start) ||
    !finite(end) ||
    start.abs() > 1.0e100 ||
    end.abs() > 1.0e100 ||
    start >= end ||
    samples < 2 ||
    samples > 1001 ||
    layers < 1 ||
    layers > 16 {
    raise TopologyError(
      "landscape requires dimension 0/1, finite start < end, 2..1001 samples and 1..16 layers",
    )
  }
  let points = diagram(analysis, dimension)
  if points.length() * samples * layers > 2000000 {
    raise TopologyError(
      "landscape evaluation exceeds two million insertion steps; reduce samples or layers",
    )
  }
  let grid = landscape_grid(start, end, samples)
  let result = Array::makei(layers, fn(_) { Array::make(samples, 0.0) })
  for i = 0; i < samples; i = i + 1 {
    for point in points {
      let left = grid[i] - point.0
      let right = point.1 - grid[i]
      let mut value = maximum(0.0, if left < right { left } else { right })
      for layer = 0; layer < layers; layer = layer + 1 {
        if value > result[layer][i] {
          let previous = result[layer][i]
          result[layer][i] = value
          value = previous
        }
      }
    }
  }
  result
}

///|
/// Feature vector with explicit grid, order and censoring metadata.
pub fn landscape_json(
  analysis : Analysis,
  dimension~ : Int,
  start~ : Double,
  end~ : Double,
  samples? : Int = 101,
  layers? : Int = 3,
) -> Json raise TopologyError {
  let values = persistence_landscape(
    analysis,
    dimension~,
    start~,
    end~,
    samples~,
    layers~,
  )
  let vector : Array[Double] = []
  for layer in values {
    for value in layer {
      vector.push(value)
    }
  }
  let censored = analysis.intervals
    .filter(fn(interval) {
      interval.dimension == dimension && interval.death is None
    })
    .length()
  {
    "schema_version": 1,
    "feature": "persistence_landscape",
    "dimension": dimension,
    "start": start,
    "end": end,
    "samples": samples,
    "layers": layers,
    "cutoff": analysis.filtration.cutoff,
    "censored_intervals_excluded": censored,
    "grid": landscape_grid(start, end, samples),
    "values": values,
    "vector": vector,
    "vector_order": "layer-major, each layer in increasing scale order",
    "normalization": "none; raw tent heights",
    "note": "finite intervals only; identical dimensions, ranges, samples, layers and units are required for comparable vectors",
  }
}

///|
pub fn landscape_csv(
  analysis : Analysis,
  dimension~ : Int,
  start~ : Double,
  end~ : Double,
  samples? : Int = 101,
  layers? : Int = 3,
) -> String raise TopologyError {
  let values = persistence_landscape(
    analysis,
    dimension~,
    start~,
    end~,
    samples~,
    layers~,
  )
  let grid = landscape_grid(start, end, samples)
  let mut text = "scale"
  for layer = 0; layer < layers; layer = layer + 1 {
    text += ",lambda_\{layer + 1}"
  }
  text += "\n"
  for i = 0; i < samples; i = i + 1 {
    text += grid[i].to_string()
    for layer in values {
      text += ",\{layer[i]}"
    }
    text += "\n"
  }
  text
}