///|
type ExactRatRows = Array[Array[@symnum.BigRational]]

///|
type ExactIntRows = Array[Array[BigInt]]

///|
fn exact_rational_from_expr(expr : Expr) -> @symnum.BigRational? {
  match @symcore.exact_numeric_expr_num_den(expr) {
    Some((num, den)) => {
      let exact : Result[@symnum.BigRational, @symnum.RationalError] = try? @symnum.BigRational::new(
        num, den,
      )
      match exact {
        Ok(value) => Some(value)
        Err(_) => None
      }
    }
    None => None
  }
}

///|
fn exact_integer_from_expr(expr : Expr) -> BigInt? {
  match @symcore.exact_number_num_den(expr) {
    Some((num, den)) if den.equal_int(1) => Some(num)
    _ => None
  }
}

///|
fn exact_expr_from_rational(value : @symnum.BigRational) -> Expr {
  Expr::Number(value)
}

///|
fn exact_expr_from_integer(value : BigInt) -> Expr {
  Expr::Number(@symnum.BigRational::from_bigint(value))
}

///|
fn clone_exact_rows(rows : ExactRatRows) -> ExactRatRows {
  let out : ExactRatRows = []
  for row in rows {
    let copied : Array[@symnum.BigRational] = []
    for item in row {
      copied.push(item)
    }
    out.push(copied)
  }
  out
}

///|
fn clone_exact_int_rows(rows : ExactIntRows) -> ExactIntRows {
  let out : ExactIntRows = []
  for row in rows {
    let copied : Array[BigInt] = []
    for item in row {
      copied.push(item)
    }
    out.push(copied)
  }
  out
}

///|
fn swap_exact_rows(data : ExactRatRows, i : Int, j : Int) -> Unit {
  let tmp = data[i]
  data[i] = data[j]
  data[j] = tmp
}

///|
fn swap_exact_int_rows(data : ExactIntRows, i : Int, j : Int) -> Unit {
  let tmp = data[i]
  data[i] = data[j]
  data[j] = tmp
}

///|
fn matrix_exact_rational_data(rows : Array[Array[Expr]]) -> ExactRatRows? {
  let out : ExactRatRows = []
  for row in rows {
    let converted : Array[@symnum.BigRational] = []
    for item in row {
      match exact_rational_from_expr(item) {
        Some(value) => converted.push(value)
        None => return None
      }
    }
    out.push(converted)
  }
  Some(out)
}

///|
fn matrix_exact_integer_data(rows : Array[Array[Expr]]) -> ExactIntRows? {
  let out : ExactIntRows = []
  for row in rows {
    let converted : Array[BigInt] = []
    for item in row {
      match exact_integer_from_expr(item) {
        Some(value) => converted.push(value)
        None => return None
      }
    }
    out.push(converted)
  }
  Some(out)
}

///|
fn exact_div_unchecked(
  lhs : @symnum.BigRational,
  rhs : @symnum.BigRational,
) -> @symnum.BigRational {
  lhs.div_r(rhs) catch {
    _ => abort("exact_div_unchecked: zero pivot")
  }
}

///|
fn exact_int_div_unchecked(lhs : BigInt, rhs : BigInt) -> BigInt {
  if rhs.is_zero() {
    abort("exact_int_div_unchecked: zero pivot")
  }
  lhs.div(rhs)
}

///|
fn exact_pivot_row(data : ExactRatRows, start : Int, col : Int) -> Int {
  let mut pivot = start
  while pivot < data.length() && data[pivot][col].is_zero() {
    pivot += 1
  }
  pivot
}

///|
fn exact_rational_det(data0 : ExactRatRows) -> @symnum.BigRational {
  if data0.is_empty() {
    return @symnum.BigRational::one()
  }
  let data = clone_exact_rows(data0)
  let mut sign = @symnum.BigRational::one()
  let mut det = @symnum.BigRational::one()
  for col in 0.. ExactIntRows {
  let out : ExactIntRows = []
  for _ in 0.. Array[BigInt] {
  let out : Array[BigInt] = []
  for i in start.. Unit {
  if left.is_empty() || right.is_empty() {
    return
  }
  let right_cols = right[0].length()
  let zero = 0N
  let right_t = make_exact_int_dense(right_cols, right.length(), zero)
  for i in 0.. (ExactIntRows, BigInt, Array[Int]) {
  let data = clone_exact_int_rows(data0)
  let m = data.length()
  if m == 0 {
    return (data, 1N, [])
  }
  let n = data[0].length()
  let mut d : BigInt? = None
  let pivots : Array[Int] = []
  let no_pivots : Array[Int] = []
  let zero = 0N
  let mut i = 0
  for j in 0..= m {
      break
    }
    let mut aij = data[i][j]
    if aij.is_zero() {
      let mut ip = i + 1
      while ip < m && data[ip][j].is_zero() {
        ip += 1
      }
      if ip == m {
        no_pivots.push(j)
        continue
      }
      swap_exact_int_rows(data, i, ip)
      aij = data[i][j]
    }

    if !pivots.is_empty() {
      let mut pivot_val = aij.mul(data[0][pivots[0]])
      match d {
        Some(prev) => pivot_val = exact_int_div_unchecked(pivot_val, prev)
        None => ()
      }
      for ip in 0.. value = exact_int_div_unchecked(value, prev)
          None => ()
        }
        data[ip][jnp] = value
      }
    }

    for jp in 0.. value = exact_int_div_unchecked(value, prev)
          None => ()
        }
        data[jp][kp] = value
      }
      data[jp][j] = zero
    }

    pivots.push(j)
    i += 1
    if i >= m {
      break
    }
    d = if aij.equal_int(1) { None } else { Some(aij) }
  }

  let denom = if pivots.is_empty() { 1N } else { data[0][pivots[0]] }
  (data, denom, pivots)
}

///|
fn exact_integer_fraction_rows_to_expr_rows(
  rows : ExactIntRows,
  den0 : BigInt,
) -> Array[Array[Expr]] {
  let den_negative = den0.op_lt(0N)
  let den = if den_negative { den0.neg() } else { den0 }
  let out : Array[Array[Expr]] = []
  for row in rows {
    let rendered : Array[Expr] = []
    for item0 in row {
      let item = if den_negative { item0.neg() } else { item0 }
      if item.is_zero() {
        rendered.push(@symcore.int(0))
      } else if den.equal_int(1) {
        rendered.push(exact_expr_from_integer(item))
      } else {
        let value = @symnum.BigRational::new(item, den) catch {
          _ =>
            abort(
              "exact_integer_fraction_rows_to_expr_rows: invalid denominator",
            )
        }
        rendered.push(Expr::Number(value))
      }
    }
    out.push(rendered)
  }
  out
}

///|
fn exact_integer_inverse(data0 : ExactIntRows) -> (ExactIntRows, BigInt)? {
  if data0.is_empty() {
    return Some(([], 1N))
  }
  let n = data0.length()
  let one = 1N
  let zero = 0N
  let aug : ExactIntRows = []
  for i in 0.. (ExactIntRows, BigInt)? {
  if lhs0.is_empty() {
    return Some(([], 1N))
  }
  let rows = lhs0.length()
  let cols = lhs0[0].length()
  let rhs_cols = rhs0[0].length()
  let aug : ExactIntRows = []
  for i in 0..= cols {
    return None
  }
  let num : ExactIntRows = []
  for i in 0.. Array[BigInt] {
  let m = rows.length()
  if m == 0 {
    return [1N]
  }
  let n = rows[0].length()
  if n == 1 {
    return [1N, rows[0][0].neg()]
  }

  let a = rows[0][0]
  let r : ExactIntRows = [tail_bigint_slice(rows[0], 1)]
  let c : ExactIntRows = []
  let minor : ExactIntRows = []
  for i in 1.. 2 {
      let prev = an_c
      an_c = make_exact_int_dense(prev.length(), 1, zero)
      exact_integer_dense_imatmul(an_c, minor, prev)
    }
    let ran_c = make_exact_int_dense(1, 1, zero)
    exact_integer_dense_imatmul(ran_c, r, an_c)
    for j in 0..<(n + 1 - i) {
      t[i + j][j] = ran_c[0][0].neg()
    }
  }

  let q_col : ExactIntRows = []
  for coeff in q {
    q_col.push([coeff])
  }
  let out = make_exact_int_dense(n + 1, 1, zero)
  exact_integer_dense_imatmul(out, t, q_col)
  let coeffs : Array[BigInt] = []
  for row in out {
    coeffs.push(row[0])
  }
  coeffs
}

///|
fn exact_integer_charpoly_expr(rows : ExactIntRows, x : Expr) -> Expr {
  let coeffs = exact_integer_charpoly_berk(rows)
  let degree = coeffs.length() - 1
  let terms : Array[Expr] = []
  for i in 0.. coeff_expr
      1 => if coeff.equal_int(1) { x } else { @symcore.mul([coeff_expr, x]) }
      _ => {
        let x_pow = @symcore.pow(x, @symcore.int(exp))
        if coeff.equal_int(1) {
          x_pow
        } else {
          @symcore.mul([coeff_expr, x_pow])
        }
      }
    }
    terms.push(term)
  }
  if terms.is_empty() {
    @symcore.int(0)
  } else {
    @symcore.add(terms)
  }
}