///|
/// A compact row-major dense matrix used by the filtering algorithms.
///
/// `Matrix` deliberately keeps its storage private.  This prevents callers
/// from accidentally changing the shape while still allowing the numerical
/// kernels to update values in place.  The type is small enough for embedded
/// and WebAssembly workloads and predictable enough for deterministic tests.
pub struct Matrix {
  rows : Int
  cols : Int
  data : Array[Double]
} derive(Debug)

///|
/// A result returned by a matrix operation that may be singular.
pub(all) enum MatrixSolveResult {
  Solved(Array[Double])
  Singular
  InvalidShape
} derive(Debug, Eq)

///|
/// Construct a zero-filled matrix.
pub fn Matrix::zeros(rows : Int, cols : Int) -> Matrix {
  let safe_rows = if rows < 0 { 0 } else { rows }
  let safe_cols = if cols < 0 { 0 } else { cols }
  {
    rows: safe_rows,
    cols: safe_cols,
    data: Array::make(safe_rows * safe_cols, 0.0),
  }
}

///|
/// Construct a matrix whose diagonal is `value` and whose other entries are
/// zero.
pub fn Matrix::diagonal(size : Int, value : Double) -> Matrix {
  let result = Matrix::zeros(size, size)
  for i in 0.. Matrix {
  Matrix::diagonal(size, 1.0)
}

///|
/// Construct a matrix from a flat row-major buffer.  Short buffers are padded
/// with zeroes and long buffers are safely truncated.
pub fn Matrix::from_flat(
  rows : Int,
  cols : Int,
  values : Array[Double],
) -> Matrix {
  let result = Matrix::zeros(rows, cols)
  let copied = if values.length() < result.data.length() {
    values.length()
  } else {
    result.data.length()
  }
  for i in 0.. Matrix {
  let rows = source.length()
  let mut cols = 0
  for row in source {
    if row.length() > cols {
      cols = row.length()
    }
  }
  let result = Matrix::zeros(rows, cols)
  for i, row in source {
    for j in 0.. Matrix? {
  if source.length() == 0 {
    return Some(Matrix::zeros(0, 0))
  }
  let cols = source[0].length()
  for row in source {
    if row.length() != cols {
      return None
    }
  }
  Some(Matrix::from_rows(source))
}

///|
pub fn Matrix::rows(self : Matrix) -> Int {
  self.rows
}

///|
pub fn Matrix::cols(self : Matrix) -> Int {
  self.cols
}

///|
pub fn Matrix::size(self : Matrix) -> Int {
  self.rows * self.cols
}

///|
pub fn Matrix::is_empty(self : Matrix) -> Bool {
  self.rows == 0 || self.cols == 0
}

///|
pub fn Matrix::is_square(self : Matrix) -> Bool {
  self.rows == self.cols
}

///|
/// Return whether a pair of coordinates is safe to access.
pub fn Matrix::valid_index(self : Matrix, row : Int, col : Int) -> Bool {
  row >= 0 && row < self.rows && col >= 0 && col < self.cols
}

///|
/// Read an entry.  This method is intended for validated matrix operations;
/// use `try_get` when coordinates originate outside the algorithm.
pub fn Matrix::get(self : Matrix, row : Int, col : Int) -> Double {
  self.data[row * self.cols + col]
}

///|
/// Read an entry without risking an out-of-bounds panic.
pub fn Matrix::try_get(self : Matrix, row : Int, col : Int) -> Double? {
  if self.valid_index(row, col) {
    Some(self.get(row, col))
  } else {
    None
  }
}

///|
/// Set an entry when coordinates are valid.  Returns whether the write was
/// performed.
pub fn Matrix::set(self : Matrix, row : Int, col : Int, value : Double) -> Bool {
  if self.valid_index(row, col) {
    self.data[row * self.cols + col] = value
    true
  } else {
    false
  }
}

///|
pub fn Matrix::copy(self : Matrix) -> Matrix {
  Matrix::from_flat(self.rows, self.cols, self.data.copy())
}

///|
/// Return the requested row as a new owned array.
pub fn Matrix::row(self : Matrix, index : Int) -> Array[Double] {
  if index < 0 || index >= self.rows {
    return []
  }
  Array::makei(self.cols, j => self.get(index, j))
}

///|
/// Return the requested column as a new owned array.
pub fn Matrix::column(self : Matrix, index : Int) -> Array[Double] {
  if index < 0 || index >= self.cols {
    return []
  }
  Array::makei(self.rows, i => self.get(i, index))
}

///|
/// Convert to independent row arrays for interop and inspection.
pub fn Matrix::to_rows(self : Matrix) -> Array[Array[Double]] {
  Array::makei(self.rows, i => self.row(i))
}

///|
/// Apply a scalar function to every entry.
pub fn Matrix::map(self : Matrix, f : (Double) -> Double) -> Matrix {
  let result = self.copy()
  for i in 0.. Double,
) -> Matrix {
  let result = Matrix::zeros(self.rows, self.cols)
  for i in 0.. Unit {
  self.data.fill(value)
}

///|
pub fn Matrix::is_finite(self : Matrix) -> Bool {
  for value in self.data {
    if value.is_nan() || value.is_inf() {
      return false
    }
  }
  true
}

///|
pub fn Matrix::max_abs(self : Matrix) -> Double {
  let mut result = 0.0
  for value in self.data {
    let current = value.abs()
    if current > result {
      result = current
    }
  }
  result
}

///|
pub fn Matrix::diagonal_min(self : Matrix) -> Double {
  if !self.is_square() || self.rows == 0 {
    return 0.0
  }
  let mut result = self.get(0, 0)
  for i in 1.. Double {
  if !self.is_square() || self.rows == 0 {
    return 0.0
  }
  let mut result = self.get(0, 0)
  for i in 1.. result {
      result = self.get(i, i)
    }
  }
  result
}

///|
pub fn Matrix::approx_equal(
  self : Matrix,
  other : Matrix,
  tolerance : Double,
) -> Bool {
  if self.rows != other.rows || self.cols != other.cols {
    return false
  }
  for i in 0.. tolerance {
      return false
    }
  }
  true
}