///|
/// Result of Levinson-Durbin LPC solver.
pub(all) struct LpcResult {
  coeffs : Array[Double] // Linear prediction filter coefficients [a_1, ..., a_order]
  reflection : Array[Double] // Reflection / PARCOR coefficients [k_1, ..., k_order]
  prediction_error : Double // Residual error energy
} derive(Debug)

///|
/// Compute biased autocorrelation sequence up to max_lag.
pub fn autocorrelation(samples : Array[Double], max_lag : Int) -> Array[Double] {
  let len = samples.length()
  if max_lag < 0 {
    return []
  }
  let lag_count = if max_lag < len { max_lag + 1 } else { len }
  let r : Array[Double] = Array::make(lag_count, 0.0)
  if len == 0 {
    return r
  }

  for k in 0.. Result[LpcResult, @types.MelError] {
  if order <= 0 {
    return Err(DimensionMismatch("LPC order must be positive: \{order}"))
  }
  if r.length() < order + 1 {
    return Err(
      DimensionMismatch("Autocorrelation length must be at least order + 1"),
    )
  }

  let e0 = r[0]
  if e0 <= 1.0e-12 {
    let zeros : Array[Double] = Array::make(order, 0.0)
    return Ok({
      coeffs: zeros.copy(),
      reflection: zeros,
      prediction_error: 0.0,
    })
  }

  let a : Array[Double] = Array::make(order + 1, 0.0)
  let prev_a : Array[Double] = Array::make(order + 1, 0.0)
  let k_coeffs : Array[Double] = Array::make(order, 0.0)
  let mut err = e0

  for i in 1..<=order {
    let mut lambda = r[i]
    for j in 1.. 0.999999 {
      0.999999
    } else if k < -0.999999 {
      -0.999999
    } else {
      k
    }

    k_coeffs[i - 1] = clamped_k
    a[i] = clamped_k

    for j in 1.. Result[LpcResult, @types.MelError] {
  if samples.is_empty() {
    return Err(EmptyAudio)
  }
  if order <= 0 || order >= samples.length() {
    return Err(DimensionMismatch("LPC order must be in [1, sample length)"))
  }
  let r = autocorrelation(samples, order)
  levinson_durbin(r, order)
}

///|
/// Convert LPC filter coefficients to Linear Prediction Cepstral Coefficients (LPCC).
pub fn lpc_to_lpcc(
  lpc_coeffs : Array[Double],
  prediction_error : Double,
  n_lpcc : Int,
) -> Array[Double] {
  let p = lpc_coeffs.length()
  if n_lpcc <= 0 {
    return []
  }
  let c : Array[Double] = Array::make(n_lpcc, 0.0)
  if n_lpcc == 0 {
    return c
  }

  // c[0] = ln(prediction_error)
  let safe_err = if prediction_error > 1.0e-12 {
    prediction_error
  } else {
    1.0e-12
  }
  c[0] = @math.ln(safe_err)
  if n_lpcc == 1 {
    return c
  }

  // c[1] = a[0]
  if p > 0 {
    c[1] = lpc_coeffs[0]
  }

  // Recursive computation for 2 <= m <= p
  let min_len = if n_lpcc - 1 < p { n_lpcc - 1 } else { p }
  for m in 2..<=min_len {
    let mut sum = 0.0
    for k in 1.. p
  if n_lpcc - 1 > p {
    for m in (p + 1).. Array[Double] {
  let len = samples.length()
  let p = lpc_coeffs.length()
  let res : Array[Double] = Array::make(len, 0.0)

  for n in 0.. Result[@types.FeatureMatrix, @types.MelError] {
  let len = samples.length()
  if len == 0 {
    return Err(EmptyAudio)
  }
  if frame_length <= 0 || hop_length <= 0 {
    return Err(InvalidHopLength(hop_length))
  }
  if order <= 0 {
    return Err(DimensionMismatch("LPC order must be positive"))
  }

  let n_frames = if len >= frame_length {
    (len - frame_length) / hop_length + 1
  } else {
    1
  }

  let mat = @types.FeatureMatrix::zeros(order, n_frames)
  let frame_buf : Array[Double] = Array::make(frame_length, 0.0)

  for f in 0..
        for r in 0.. return Err(e)
    }
  }

  Ok(mat)
}

///|
/// Extract frame-wise Linear Prediction Cepstral Coefficients (LPCC) matrix [n_lpcc x n_frames].
pub fn lpcc_features(
  samples : Array[Double],
  order : Int,
  n_lpcc : Int,
  frame_length : Int,
  hop_length : Int,
) -> Result[@types.FeatureMatrix, @types.MelError] {
  let len = samples.length()
  if len == 0 {
    return Err(EmptyAudio)
  }
  if frame_length <= 0 || hop_length <= 0 {
    return Err(InvalidHopLength(hop_length))
  }
  if order <= 0 || n_lpcc <= 0 {
    return Err(DimensionMismatch("Order and n_lpcc must be positive"))
  }

  let n_frames = if len >= frame_length {
    (len - frame_length) / hop_length + 1
  } else {
    1
  }

  let mat = @types.FeatureMatrix::zeros(n_lpcc, n_frames)
  let frame_buf : Array[Double] = Array::make(frame_length, 0.0)

  for f in 0.. {
        let lpcc = lpc_to_lpcc(lpc_res.coeffs, lpc_res.prediction_error, n_lpcc)
        for r in 0.. return Err(e)
    }
  }

  Ok(mat)
}

///|
pub extend LpcResult with @moonbitlang/core/debug.Debug::{to_repr}