///|
pub(all) suberror SpectralError {
  Invalid(String)
  Numerical(String)
  Budget(String)
}

///|
/// Owned arrays; construction rejects nonfinite samples and unordered axes.
pub struct Spectrum {
  axis : Array[Double]
  values : Array[Double]
}

///|
pub(all) struct BaselineResult {
  baseline : Array[Double]
  corrected : Array[Double]
  weights : Array[Double]
  iterations : Int32
  converged : Bool
  relative_change : Double
}

///|
pub(all) struct IterationOptions {
  lambda : Double
  max_iterations : Int32
  tolerance : Double
}

///|
pub fn iteration_options(
  lambda~ : Double = 10000.0,
  max_iterations~ : Int32 = 50,
  tolerance~ : Double = 0.001,
) -> IterationOptions {
  { lambda, max_iterations, tolerance, }
}

///|
fn finite(x : Double, label : String) -> Unit raise SpectralError {
  if x.is_nan() || x.is_inf() {
    raise Invalid(label + " must be finite")
  }
}

///|
fn validate_values(
  y : Array[Double],
  minimum : Int32,
) -> Unit raise SpectralError {
  if y.length() < minimum {
    raise Invalid("insufficient samples")
  }
  if y.length() > 65536 {
    raise Budget("at most 65536 samples")
  }
  for v in y {
    finite(v, "sample")
    if v.abs() > 1.0e100 {
      raise Invalid("sample magnitude exceeds 1e100")
    }
  }
}

///|
pub fn spectrum(
  axis : Array[Double],
  values : Array[Double],
) -> Spectrum raise SpectralError {
  validate_values(values, 3)
  if axis.length() != values.length() {
    raise Invalid("axis and values lengths differ")
  }
  for i in 0.. 1.0e100 {
      raise Invalid("axis magnitude exceeds 1e100")
    }
    if i > 0 && axis[i] <= axis[i - 1] {
      raise Invalid("axis must be strictly increasing")
    }
  }
  { axis: axis.copy(), values: values.copy(), }
}

///|
pub fn Spectrum::axis(self : Spectrum) -> Array[Double] {
  self.axis.copy()
}

///|
pub fn Spectrum::values(self : Spectrum) -> Array[Double] {
  self.values.copy()
}

///|
pub fn Spectrum::length(self : Spectrum) -> Int32 {
  self.values.length()
}

///|
pub fn Spectrum::with_values(
  self : Spectrum,
  values : Array[Double],
) -> Spectrum raise SpectralError {
  spectrum(self.axis, values)
}

///|
/// Validate bounded options before allocating iterative workspaces.
pub fn validate_iteration_options(
  o : IterationOptions,
) -> Unit raise SpectralError {
  finite(o.lambda, "lambda")
  finite(o.tolerance, "tolerance")
  if o.lambda <= 0.0 || o.lambda > 1.0e12 {
    raise Invalid("lambda must lie in (0,1e12]")
  }
  if o.max_iterations < 1 || o.max_iterations > 200 {
    raise Budget("iteration count must lie in [1,200]")
  }
  if o.tolerance <= 0.0 || o.tolerance > 1.0 {
    raise Invalid("tolerance must lie in (0,1]")
  }
}

///|
pub impl Show for SpectralError with fn output(self, logger) {
  logger.write_string(error_message(self))
}

///|
pub extend SpectralError with Show::{to_string, output}