///|
/// Small dense-matrix helpers for calibration and feature pipelines.
/// The implementation favors predictable behavior for small HRV feature
/// tables over large numerical workloads.

///|
/// Matrix shape.
pub(all) struct MatrixShape {
  rows : Int
  columns : Int
  rectangular : Bool
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Return the shape of a nested array.
pub fn matrix_shape(matrix : Array[Array[Double]]) -> MatrixShape {
  if matrix.length() == 0 {
    return { rows: 0, columns: 0, rectangular: true }
  }
  let columns = matrix[0].length()
  let mut rectangular = true
  for row in matrix {
    if row.length() != columns {
      rectangular = false
    }
  }
  { rows: matrix.length(), columns, rectangular }
}

///|
/// Make a zero matrix.
pub fn matrix_zeros(rows : Int, columns : Int) -> Array[Array[Double]] {
  let result = []
  if rows <= 0 || columns <= 0 {
    return result
  }
  for _ in 0.. Array[Array[Double]] {
  let result = matrix_zeros(size, size)
  for i in 0.. Array[Array[Double]] {
  let result = []
  for row in matrix {
    let copied = []
    for value in row {
      copied.push(value)
    }
    result.push(copied)
  }
  result
}

///|
/// Transpose a rectangular matrix.
pub fn matrix_transpose(matrix : Array[Array[Double]]) -> Array[Array[Double]] {
  let shape = matrix_shape(matrix)
  if !shape.rectangular || shape.rows == 0 || shape.columns == 0 {
    return []
  }
  let result = matrix_zeros(shape.columns, shape.rows)
  for i in 0.. Array[Array[Double]] {
  let lshape = matrix_shape(left)
  let rshape = matrix_shape(right)
  if !lshape.rectangular ||
    !rshape.rectangular ||
    lshape.rows != rshape.rows ||
    lshape.columns != rshape.columns {
    return []
  }
  let result = matrix_zeros(lshape.rows, lshape.columns)
  for i in 0.. Array[Array[Double]] {
  let lshape = matrix_shape(left)
  let rshape = matrix_shape(right)
  if !lshape.rectangular ||
    !rshape.rectangular ||
    lshape.rows != rshape.rows ||
    lshape.columns != rshape.columns {
    return []
  }
  let result = matrix_zeros(lshape.rows, lshape.columns)
  for i in 0.. Array[Array[Double]] {
  let result = []
  for row in matrix {
    let output = []
    for value in row {
      output.push(value * scalar)
    }
    result.push(output)
  }
  result
}

///|
/// Multiply two rectangular matrices.
pub fn matrix_multiply(
  left : Array[Array[Double]],
  right : Array[Array[Double]],
) -> Array[Array[Double]] {
  let lshape = matrix_shape(left)
  let rshape = matrix_shape(right)
  if !lshape.rectangular || !rshape.rectangular || lshape.columns != rshape.rows {
    return []
  }
  let result = matrix_zeros(lshape.rows, rshape.columns)
  for i in 0.. Array[Double] {
  let shape = matrix_shape(matrix)
  if !shape.rectangular || shape.columns != vector.length() {
    return []
  }
  let result = []
  for row in matrix {
    let mut value = 0.0
    for j in 0.. Array[Double] {
  let shape = matrix_shape(matrix)
  if !shape.rectangular || shape.rows == 0 {
    return []
  }
  let result = Array::make(shape.columns, 0.0)
  for row in matrix {
    for j in 0.. Array[Double] {
  let shape = matrix_shape(matrix)
  if !shape.rectangular || shape.rows == 0 {
    return []
  }
  let means = matrix_column_means(matrix)
  let result = Array::make(shape.columns, 0.0)
  for row in matrix {
    for j in 0.. 1 {
    for j in 0.. Array[Array[Double]] {
  let shape = matrix_shape(matrix)
  let means = matrix_column_means(matrix)
  if !shape.rectangular || shape.columns != means.length() {
    return []
  }
  let result = matrix_copy(matrix)
  for i in 0.. Array[Array[Double]] {
  let shape = matrix_shape(matrix)
  let means = matrix_column_means(matrix)
  let scales = matrix_column_scales(matrix)
  if !shape.rectangular || shape.columns != means.length() {
    return []
  }
  let result = matrix_copy(matrix)
  for i in 0.. Array[Array[Double]] {
  let shape = matrix_shape(matrix)
  if !shape.rectangular || shape.rows <= 1 {
    return matrix_zeros(shape.columns, shape.columns)
  }
  let centered = matrix_center(matrix)
  let transpose = matrix_transpose(centered)
  matrix_scale(
    matrix_multiply(transpose, centered),
    1.0 / (shape.rows - 1).to_double(),
  )
}

///|
/// Calculate the squared norm of a vector.
pub fn vector_squared_norm(values : Array[Double]) -> Double {
  let mut result = 0.0
  for value in values {
    result += value * value
  }
  result
}

///|
/// Calculate Euclidean distance between aligned vectors.
pub fn vector_distance(left : Array[Double], right : Array[Double]) -> Double {
  let n = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  if n == 0 {
    return 0.0
  }
  let mut sum = 0.0
  for i in 0.. Array[Double] {
  let norm = vector_squared_norm(values).sqrt()
  let result = []
  for value in values {
    result.push(if norm == 0.0 { 0.0 } else { value / norm })
  }
  result
}

///|
/// Solve a two-variable linear system by Cramer's rule.
pub fn solve_two_by_two(
  matrix : Array[Array[Double]],
  target : Array[Double],
) -> Array[Double] {
  if matrix.length() != 2 ||
    matrix[0].length() != 2 ||
    matrix[1].length() != 2 ||
    target.length() != 2 {
    return []
  }
  let determinant = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]
  if determinant == 0.0 {
    return []
  }
  [
    (target[0] * matrix[1][1] - matrix[0][1] * target[1]) / determinant,
    (matrix[0][0] * target[1] - target[0] * matrix[1][0]) / determinant,
  ]
}

///|
/// Calculate a two-feature least-squares regression with intercept.
pub fn fit_two_feature_regression(
  features : Array[Array[Double]],
  target : Array[Double],
) -> Array[Double] {
  let shape = matrix_shape(features)
  if !shape.rectangular ||
    shape.columns != 2 ||
    shape.rows != target.length() ||
    shape.rows == 0 {
    return []
  }
  let mean_x = mean_value(features.map(fn(row) { row[0] }))
  let mean_y = mean_value(features.map(fn(row) { row[1] }))
  let mean_target = mean_value(target)
  let mut xx = 0.0
  let mut yy = 0.0
  let mut xy = 0.0
  let mut xt = 0.0
  let mut yt = 0.0
  for i in 0.. Array[Double] {
  let result = []
  for row in matrix {
    for value in row {
      result.push(value)
    }
  }
  result
}

///|
/// Rebuild a matrix from a row-major vector.
pub fn matrix_unflatten(
  values : Array[Double],
  rows : Int,
  columns : Int,
) -> Array[Array[Double]] {
  if rows <= 0 || columns <= 0 || values.length() < rows * columns {
    return []
  }
  let result = matrix_zeros(rows, columns)
  for i in 0.. Array[Double] {
  let shape = matrix_shape(matrix)
  let result = []
  let n = if shape.rows < shape.columns { shape.rows } else { shape.columns }
  for i in 0.. Bool {
  let shape = matrix_shape(matrix)
  if !shape.rectangular {
    return false
  }
  for row in matrix {
    for value in row {
      if value.is_nan() || value.is_inf() {
        return false
      }
    }
  }
  true
}