///|
pub(all) struct BatchSummary {
  mean_spectrum : Spectrum
  sample_deviations : Array[Double]
  count : Int32
}

///|
/// Stable per-channel Welford statistics on an exactly common axis.
pub fn summarize_batch(
  batch : Array[Spectrum],
) -> BatchSummary raise SpectralError {
  if batch.length() < 2 || batch.length() > 256 {
    raise Budget("summary needs 2..256 spectra")
  }
  let n = batch[0].length()
  if n * batch.length() > 1048576 {
    raise Budget("summary exceeds sample budget")
  }
  for input in batch {
    require_same_axis(batch[0], input, 0.0)
  }
  let average = Array::make(n, 0.0)
  let m2 = Array::make(n, 0.0)
  for k in 0.. String {
  match error {
    Invalid(message) => "invalid: " + message
    Numerical(message) => "numerical: " + message
    Budget(message) => "budget: " + message
  }
}

///|
/// Transform independently against one frozen model; keep every input index.
/// A bad sample is reported, never silently removed or used to refit the model.
pub fn scatter_batch(
  model : ScatterModel,
  inputs : Array[Spectrum],
) -> Array[ScatterOutcome] raise SpectralError {
  if inputs.length() < 1 || inputs.length() > 256 {
    raise Budget("batch needs 1..256 spectra")
  }
  let mut samples = 0
  for input in inputs {
    samples = samples + input.length()
  }
  if samples > 1048576 {
    raise Budget("batch exceeds sample budget")
  }
  let outcomes : Array[ScatterOutcome] = []
  for i in 0.. Rejected(i, error_message(error))
    }
    outcomes.push(outcome)
  }
  outcomes
}

///|
/// Resample multiple measured spectra onto one owned common axis.
/// Any out-of-domain input fails the operation; no partial batch escapes.
pub fn align_batch(
  inputs : Array[Spectrum],
  axis : Array[Double],
) -> Array[Spectrum] raise SpectralError {
  if inputs.length() < 1 || inputs.length() > 256 {
    raise Budget("alignment needs 1..256 spectra")
  }
  if axis.length() > 65536 || axis.length() * inputs.length() > 1048576 {
    raise Budget("aligned batch exceeds sample budget")
  }
  let output : Array[Spectrum] = []
  for input in inputs {
    output.push(resample(input, axis))
  }
  output
}