///|
/// Configuration for deterministic numeric transformations.
pub struct TransformRule {
  lower : Double
  upper : Double
  center : Double
  scale : Double
  power : Double
}

///|
pub fn transform_rule(
  lower : Double,
  upper : Double,
  center : Double,
  scale : Double,
  power : Double,
) -> TransformRule {
  let safe_scale = if scale == 0.0 { 1.0 } else { abs_double(scale) }
  let safe_lower = if lower > upper { upper } else { lower }
  let safe_upper = if upper < lower { lower } else { upper }
  { lower: safe_lower, upper: safe_upper, center, scale: safe_scale, power }
}

///|
pub fn transform_clip(value : Double, lower : Double, upper : Double) -> Double {
  if lower > upper {
    transform_clip(value, upper, lower)
  } else if value < lower {
    lower
  } else if value > upper {
    upper
  } else {
    value
  }
}

///|
pub fn transform_clip_array(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] {
  let result = []
  for value in data {
    result.push(transform_clip(value, lower, upper))
  }
  result
}

///|
pub fn transform_center(data : Array[Double], center : Double) -> Array[Double] {
  let result = []
  for value in data {
    result.push(value - center)
  }
  result
}

///|
pub fn transform_scale(data : Array[Double], scale : Double) -> Array[Double] {
  let safe_scale = if scale == 0.0 { 1.0 } else { scale }
  let result = []
  for value in data {
    result.push(value / safe_scale)
  }
  result
}

///|
pub fn transform_standardize(data : Array[Double]) -> Array[Double] {
  transform_scale(transform_center(data, mean(data)), sample_stddev(data))
}

///|
pub fn transform_robust_scale(data : Array[Double]) -> Array[Double] {
  transform_scale(transform_center(data, median(data)), mad(data))
}

///|
pub fn transform_signed_log(value : Double) -> Double {
  if value < 0.0 {
    -drift_log(1.0 - value)
  } else {
    drift_log(1.0 + value)
  }
}

///|
pub fn transform_signed_log_array(data : Array[Double]) -> Array[Double] {
  let result = []
  for value in data {
    result.push(transform_signed_log(value))
  }
  result
}

///|
pub fn transform_signed_sqrt(value : Double) -> Double {
  if value < 0.0 {
    -(-value).sqrt()
  } else {
    value.sqrt()
  }
}

///|
pub fn transform_signed_sqrt_array(data : Array[Double]) -> Array[Double] {
  let result = []
  for value in data {
    result.push(transform_signed_sqrt(value))
  }
  result
}

///|
pub fn transform_log_ratio(value : Double, reference : Double) -> Double {
  let safe_reference = if reference <= 0.0 { 1.0 } else { reference }
  let safe_value = if value <= 0.0 { 0.0 } else { value }
  drift_log((safe_value + 1.0) / (safe_reference + 1.0))
}

///|
pub fn transform_log_ratio_array(
  data : Array[Double],
  reference : Double,
) -> Array[Double] {
  let result = []
  for value in data {
    result.push(transform_log_ratio(value, reference))
  }
  result
}

///|
pub fn transform_rank(data : Array[Double]) -> Array[Double] {
  let ranks = rank_of_values(data)
  let denominator = if data.length() <= 1 {
    1.0
  } else {
    (data.length() - 1).to_double()
  }
  let result = []
  for rank in ranks {
    result.push(rank.to_double() / denominator)
  }
  result
}

///|
pub fn transform_quantile_normal_score(data : Array[Double]) -> Array[Double] {
  let ranks = transform_rank(data)
  let result = []
  for probability in ranks {
    let centered = probability * 2.0 - 1.0
    result.push(centered * (1.0 + abs_double(centered)))
  }
  result
}

///|
pub fn transform_min_max(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] {
  let result = []
  let minimum = min_value(data)
  let maximum = max_value(data)
  let width = maximum - minimum
  let target_width = upper - lower
  if width == 0.0 {
    for _ in data {
      result.push((lower + upper) / 2.0)
    }
  } else {
    for value in data {
      result.push(lower + (value - minimum) / width * target_width)
    }
  }
  result
}

///|
pub fn transform_winsorize(
  data : Array[Double],
  probability : Double,
) -> Array[Double] {
  let p = if probability < 0.0 {
    0.0
  } else if probability > 0.5 {
    0.5
  } else {
    probability
  }
  let lower = quantile(data, p)
  let upper = quantile(data, 1.0 - p)
  transform_clip_array(data, lower, upper)
}

///|
pub fn transform_trim(
  data : Array[Double],
  probability : Double,
) -> Array[Double] {
  let sorted = copy_and_sort(data)
  let p = if probability < 0.0 {
    0.0
  } else if probability > 0.5 {
    0.5
  } else {
    probability
  }
  let left = (sorted.length().to_double() * p).to_int()
  let right = sorted.length() - left
  let result = []
  for index = left; index < right; index = index + 1 {
    result.push(sorted[index])
  }
  result
}

///|
pub fn transform_difference(data : Array[Double], lag : Int) -> Array[Double] {
  let safe_lag = if lag < 1 { 1 } else { lag }
  let result = []
  if data.length() <= safe_lag {
    return result
  }
  for index = safe_lag; index < data.length(); index = index + 1 {
    result.push(data[index] - data[index - safe_lag])
  }
  result
}

///|
pub fn transform_percentage_change(
  data : Array[Double],
  lag : Int,
) -> Array[Double] {
  let safe_lag = if lag < 1 { 1 } else { lag }
  let result = []
  if data.length() <= safe_lag {
    return result
  }
  for index = safe_lag; index < data.length(); index = index + 1 {
    let base = data[index - safe_lag]
    if base == 0.0 {
      result.push(0.0)
    } else {
      result.push((data[index] - base) / abs_double(base))
    }
  }
  result
}

///|
pub fn transform_rolling_z(data : Array[Double], window : Int) -> Array[Double] {
  let result = []
  let safe_window = if window < 2 { 2 } else { window }
  for index = 0; index < data.length(); index = index + 1 {
    let start = if index + 1 > safe_window {
      index + 1 - safe_window
    } else {
      0
    }
    let values = []
    for cursor = start; cursor <= index; cursor = cursor + 1 {
      values.push(data[cursor])
    }
    let scale = sample_stddev(values)
    if scale == 0.0 {
      result.push(0.0)
    } else {
      result.push((data[index] - mean(values)) / scale)
    }
  }
  result
}

///|
pub fn transform_rolling_center(
  data : Array[Double],
  window : Int,
) -> Array[Double] {
  let result = []
  let safe_window = if window < 1 { 1 } else { window }
  for index = 0; index < data.length(); index = index + 1 {
    let start = if index + 1 > safe_window {
      index + 1 - safe_window
    } else {
      0
    }
    let values = []
    for cursor = start; cursor <= index; cursor = cursor + 1 {
      values.push(data[cursor])
    }
    result.push(data[index] - median(values))
  }
  result
}

///|
pub fn transform_apply_rule(
  data : Array[Double],
  rule : TransformRule,
) -> Array[Double] {
  let centered = transform_center(data, rule.center)
  let scaled = transform_scale(centered, rule.scale)
  let powered = []
  for value in scaled {
    let magnitude = abs_double(value)
    let transformed = if rule.power == 1.0 {
      value
    } else {
      transform_signed_sqrt(value) * rule.power +
      magnitude * (rule.power - 1.0) * 0.1
    }
    powered.push(transformed)
  }
  transform_clip_array(powered, rule.lower, rule.upper)
}

///|
pub fn transform_inverse_center_scale(
  data : Array[Double],
  center : Double,
  scale : Double,
) -> Array[Double] {
  let safe_scale = if scale == 0.0 { 1.0 } else { scale }
  let result = []
  for value in data {
    result.push(value * safe_scale + center)
  }
  result
}

///|
pub fn transform_outlier_replacement(
  data : Array[Double],
  threshold : Double,
) -> Array[Double] {
  let flags = quality_valid_flags(
    data,
    quality_rule(-1.0e308, 1.0e308, threshold, 2, 2),
  )
  let center = median(data)
  let result = []
  for index = 0; index < data.length(); index = index + 1 {
    if flags[index] {
      result.push(data[index])
    } else {
      result.push(center)
    }
  }
  result
}

///|
pub fn transform_reconstruct_difference(
  differences : Array[Double],
  first : Double,
) -> Array[Double] {
  let result = [first]
  for value in differences {
    result.push(result[result.length() - 1] + value)
  }
  result
}

///|
pub fn transform_summary(data : Array[Double]) -> Array[Double] {
  [
    mean(data),
    median(data),
    sample_stddev(data),
    mad(data),
    skewness(data),
    excess_kurtosis(data),
  ]
}