///|
fn baseline_result(
  values : Array[Double],
  baseline : Array[Double],
  weights : Array[Double],
  iterations : Int32,
  converged : Bool,
  relative_change : Double,
) -> BaselineResult raise SpectralError {
  let corrected = Array::makei(values.length(), fn(i) {
    values[i] - baseline[i]
  })
  for v in corrected {
    ignore(checked_result(v, "baseline correction"))
  }
  { baseline, corrected, weights, iterations, converged, relative_change, }
}

///|
/// Asymmetric least-squares. Weights in the result are those used for the
/// returned baseline, rather than the next unsolved weight proposal.
pub fn asls(
  input : Spectrum,
  asymmetry~ : Double = 0.01,
  options~ : IterationOptions = iteration_options(),
) -> BaselineResult raise SpectralError {
  validate_iteration_options(options)
  finite(asymmetry, "asymmetry")
  if asymmetry < 1.0e-5 || asymmetry > 1.0 - 1.0e-5 {
    raise Invalid("asymmetry must lie in [1e-5,1-1e-5]")
  }
  let y = input.values
  let mut weights = Array::make(y.length(), 1.0)
  for iteration in 1..<=options.max_iterations {
    let baseline = whittaker(y, weights, options.lambda)
    let next = Array::makei(y.length(), fn(i) {
      if y[i] > baseline[i] {
        asymmetry
      } else {
        1.0 - asymmetry
      }
    })
    let change = relative_difference(next, weights)
    let converged = change <= options.tolerance
    if converged || iteration == options.max_iterations {
      return baseline_result(y, baseline, weights, iteration, converged, change)
    }
    weights = next
  }
  raise Numerical("unreachable AsLS iteration state")
}

///|
/// Remove a fitted curve while retaining the original owned axis.
pub fn Spectrum::subtract_baseline(
  self : Spectrum,
  baseline : Array[Double],
) -> Spectrum raise SpectralError {
  validate_values(baseline, 3)
  if baseline.length() != self.length() {
    raise Invalid("baseline length differs")
  }
  let values = Array::makei(self.length(), fn(i) {
    self.values[i] - baseline[i]
  })
  spectrum(self.axis, values)
}