///|
pub fn is_empty(data : Array[Double]) -> Bool {
  data.length() == 0
}

///|
pub fn is_constant(data : Array[Double], tolerance? : Double = 0.0) -> Bool {
  if data.length() <= 1 {
    true
  } else {
    let first = data[0]
    for value in data {
      if abs_double(value - first) > tolerance {
        return false
      }
    }
    true
  }
}

///|
pub fn is_sorted_non_decreasing(data : Array[Double]) -> Bool {
  for index = 1; index < data.length(); index = index + 1 {
    if data[index] < data[index - 1] {
      return false
    }
  }
  true
}

///|
pub fn is_sorted_non_increasing(data : Array[Double]) -> Bool {
  for index = 1; index < data.length(); index = index + 1 {
    if data[index] > data[index - 1] {
      return false
    }
  }
  true
}

///|
pub fn unique_value_count(data : Array[Double]) -> Int {
  let sorted = copy_and_sort(data)
  if sorted.length() == 0 {
    return 0
  }
  let mut count = 1
  for index = 1; index < sorted.length(); index = index + 1 {
    if sorted[index] != sorted[index - 1] {
      count += 1
    }
  }
  count
}

///|
pub fn duplicate_fraction(data : Array[Double]) -> Double {
  if data.length() == 0 {
    0.0
  } else {
    1.0 - unique_value_count(data).to_double() / data.length().to_double()
  }
}

///|
pub fn value_frequency(data : Array[Double], value : Double) -> Int {
  let mut count = 0
  for item in data {
    if item == value {
      count += 1
    }
  }
  count
}

///|
pub fn value_frequency_fraction(data : Array[Double], value : Double) -> Double {
  if data.length() == 0 {
    0.0
  } else {
    value_frequency(data, value).to_double() / data.length().to_double()
  }
}

///|
pub fn duplicate_indices(data : Array[Double]) -> Array[Int] {
  let result = []
  for index = 0; index < data.length(); index = index + 1 {
    if value_frequency(data, data[index]) > 1 {
      result.push(index)
    }
  }
  result
}

///|
pub fn monotonicity_score(data : Array[Double]) -> Double {
  if data.length() <= 1 {
    return 1.0
  }
  let mut increasing = 0
  let mut decreasing = 0
  for index = 1; index < data.length(); index = index + 1 {
    if data[index] > data[index - 1] {
      increasing += 1
    } else if data[index] < data[index - 1] {
      decreasing += 1
    }
  }
  let direction = if increasing >= decreasing { increasing } else { decreasing }
  direction.to_double() / (data.length() - 1).to_double()
}

///|
pub fn sign_change_count(data : Array[Double]) -> Int {
  let mut count = 0
  for index = 1; index < data.length(); index = index + 1 {
    if sign_double(data[index]) != sign_double(data[index - 1]) {
      count += 1
    }
  }
  count
}

///|
pub fn zero_fraction(data : Array[Double]) -> Double {
  if data.length() == 0 {
    return 0.0
  }
  let mut count = 0
  for value in data {
    if value == 0.0 {
      count += 1
    }
  }
  count.to_double() / data.length().to_double()
}

///|
pub fn range_ratio(data : Array[Double]) -> Double {
  let minimum = abs_double(min_value(data))
  let maximum = abs_double(max_value(data))
  if minimum == 0.0 {
    0.0
  } else {
    maximum / minimum
  }
}

///|
pub fn boundary_distance(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] {
  if lower > upper {
    abort("lower must not exceed upper")
  }
  let result = []
  for value in data {
    result.push(
      if value < lower {
        lower - value
      } else if value > upper {
        value - upper
      } else {
        0.0
      },
    )
  }
  result
}

///|
pub fn boundary_violation_count(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Int {
  let mut count = 0
  for distance in boundary_distance(data, lower, upper) {
    if distance > 0.0 {
      count += 1
    }
  }
  count
}

///|
pub fn boundary_violation_fraction(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Double {
  if data.length() == 0 {
    0.0
  } else {
    boundary_violation_count(data, lower, upper).to_double() /
    data.length().to_double()
  }
}

///|
pub fn robust_range_quality(data : Array[Double]) -> Double {
  if data.length() == 0 {
    0.0
  } else {
    1.0 /
    (
      1.0 +
      duplicate_fraction(data) +
      outlier_fraction(outlier_indices_z(data), data.length())
    )
  }
}

///|
pub fn compare_quality(first : Array[Double], second : Array[Double]) -> Double {
  robust_range_quality(first) - robust_range_quality(second)
}

///|
pub fn split_by_threshold(
  data : Array[Double],
  threshold : Double,
) -> Array[Array[Double]] {
  let lower = []
  let upper = []
  for value in data {
    if value <= threshold {
      lower.push(value)
    } else {
      upper.push(value)
    }
  }
  [lower, upper]
}

///|
pub fn split_by_median(data : Array[Double]) -> Array[Array[Double]] {
  split_by_threshold(data, median(data))
}

///|
pub fn central_fraction(
  data : Array[Double],
  probability : Double,
) -> Array[Double] {
  central_interval(data, probability)
}

///|
pub fn boundary_trimmed_data(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] {
  if lower > upper {
    abort("lower must not exceed upper")
  }
  let result = []
  for value in data {
    if value >= lower && value <= upper {
      result.push(value)
    }
  }
  result
}