///|
/// Deterministic train/validation/test partition.
pub struct DataPartition {
  train : Array[Double]
  validation : Array[Double]
  test_set : Array[Double]
  train_indices : Array[Int]
  validation_indices : Array[Int]
  test_indices : Array[Int]
}

///|
/// One fold in deterministic cross validation.
pub struct DataFold {
  fold : Int
  train : Array[Double]
  holdout : Array[Double]
  train_indices : Array[Int]
  holdout_indices : Array[Int]
}

///|
/// Sampling configuration.
pub struct SamplingRule {
  seed : Int
  train_fraction : Double
  validation_fraction : Double
  folds : Int
}

///|
pub fn sampling_default_rule() -> SamplingRule {
  { seed: 20260819, train_fraction: 0.7, validation_fraction: 0.15, folds: 5 }
}

///|
pub fn sampling_rule(
  seed : Int,
  train_fraction : Double,
  validation_fraction : Double,
  folds : Int,
) -> SamplingRule {
  let train = if train_fraction < 0.0 {
    0.0
  } else if train_fraction > 1.0 {
    1.0
  } else {
    train_fraction
  }
  let validation = if validation_fraction < 0.0 {
    0.0
  } else if validation_fraction > 1.0 {
    1.0
  } else {
    validation_fraction
  }
  {
    seed,
    train_fraction: train,
    validation_fraction: validation,
    folds: if folds < 2 {
      2
    } else {
      folds
    },
  }
}

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

///|
pub fn sampling_shuffle_indices(length : Int, seed : Int) -> Array[Int] {
  let result = sampling_indices(length)
  if result.length() <= 1 {
    return result
  }
  let rng = DeterministicRng::new(seed)
  for index = result.length() - 1; index > 0; index = index - 1 {
    let swap_index = rng.next_int(index + 1)
    let value = result[index]
    result[index] = result[swap_index]
    result[swap_index] = value
  }
  result
}

///|
pub fn sampling_shuffle(data : Array[Double], seed : Int) -> Array[Double] {
  let indices = sampling_shuffle_indices(data.length(), seed)
  let result = []
  for index in indices {
    result.push(data[index])
  }
  result
}

///|
pub fn sampling_take_indices(
  data : Array[Double],
  indices : Array[Int],
) -> Array[Double] {
  let result = []
  for index in indices {
    if index >= 0 && index < data.length() {
      result.push(data[index])
    }
  }
  result
}

///|
pub fn sampling_take(
  data : Array[Double],
  count : Int,
  seed : Int,
) -> Array[Double] {
  let indices = sampling_shuffle_indices(data.length(), seed)
  let limit = if count < 0 {
    0
  } else if count > indices.length() {
    indices.length()
  } else {
    count
  }
  let selected = []
  for index = 0; index < limit; index = index + 1 {
    selected.push(indices[index])
  }
  sampling_take_indices(data, selected)
}

///|
pub fn sampling_without_replacement(
  data : Array[Double],
  count : Int,
  seed : Int,
) -> Array[Double] {
  sampling_take(data, count, seed)
}

///|
pub fn sampling_with_replacement(
  data : Array[Double],
  count : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  if data.length() == 0 || count <= 0 {
    return result
  }
  let rng = DeterministicRng::new(seed)
  for _ in 0.. Array[Array[Double]] {
  let result = []
  if replicates <= 0 {
    return result
  }
  for index = 0; index < replicates; index = index + 1 {
    result.push(sampling_with_replacement(data, data.length(), seed + index))
  }
  result
}

///|
pub fn sampling_bootstrap_means(
  data : Array[Double],
  replicates : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for sample in sampling_bootstrap(data, replicates, seed) {
    result.push(mean(sample))
  }
  result
}

///|
pub fn sampling_bootstrap_medians(
  data : Array[Double],
  replicates : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for sample in sampling_bootstrap(data, replicates, seed) {
    result.push(median(sample))
  }
  result
}

///|
pub fn sampling_bootstrap_scales(
  data : Array[Double],
  replicates : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for sample in sampling_bootstrap(data, replicates, seed) {
    result.push(mad(sample))
  }
  result
}

///|
pub fn sampling_bootstrap_interval(
  data : Array[Double],
  replicates : Int,
  confidence : Double,
  seed : Int,
) -> BootstrapInterval {
  let estimates = sampling_bootstrap_means(data, replicates, seed)
  let confidence_value = if confidence <= 0.0 {
    0.5
  } else if confidence >= 1.0 {
    0.999
  } else {
    confidence
  }
  let tail = (1.0 - confidence_value) / 2.0
  {
    estimate: mean(data),
    lower: quantile(estimates, tail),
    upper: quantile(estimates, 1.0 - tail),
    confidence: confidence_value,
    replicates: estimates.length(),
  }
}

///|
pub fn sampling_jackknife(data : Array[Double]) -> Array[Array[Double]] {
  let result = []
  for index = 0; index < data.length(); index = index + 1 {
    let sample = []
    for cursor = 0; cursor < data.length(); cursor = cursor + 1 {
      if cursor != index {
        sample.push(data[cursor])
      }
    }
    result.push(sample)
  }
  result
}

///|
pub fn sampling_jackknife_means(data : Array[Double]) -> Array[Double] {
  let result = []
  for sample in sampling_jackknife(data) {
    result.push(mean(sample))
  }
  result
}

///|
pub fn sampling_jackknife_medians(data : Array[Double]) -> Array[Double] {
  let result = []
  for sample in sampling_jackknife(data) {
    result.push(median(sample))
  }
  result
}

///|
pub fn sampling_permutation(data : Array[Double], seed : Int) -> Array[Double] {
  sampling_shuffle(data, seed)
}

///|
pub fn sampling_permutation_difference(
  left : Array[Double],
  right : Array[Double],
  permutations : Int,
  seed : Int,
) -> Array[Double] {
  let observed = mean(left) - mean(right)
  let combined = []
  for value in left {
    combined.push(value)
  }
  for value in right {
    combined.push(value)
  }
  let result = []
  let left_count = left.length()
  for iteration = 0; iteration < permutations; iteration = iteration + 1 {
    let shuffled = sampling_shuffle(combined, seed + iteration)
    let first = []
    let second = []
    for index = 0; index < shuffled.length(); index = index + 1 {
      if index < left_count {
        first.push(shuffled[index])
      } else {
        second.push(shuffled[index])
      }
    }
    result.push(mean(first) - mean(second) - observed)
  }
  result
}

///|
pub fn sampling_permutation_p_value(
  left : Array[Double],
  right : Array[Double],
  permutations : Int,
  seed : Int,
) -> Double {
  let observed = abs_double(mean(left) - mean(right))
  let differences = sampling_permutation_difference(
    left, right, permutations, seed,
  )
  if differences.length() == 0 {
    return 0.0
  }
  let mut extreme = 0
  for difference in differences {
    if abs_double(difference) >= observed {
      extreme += 1
    }
  }
  (extreme + 1).to_double() / (differences.length() + 1).to_double()
}

///|
pub fn sampling_partition_indices(
  length : Int,
  rule : SamplingRule,
) -> DataPartition {
  let shuffled = sampling_shuffle_indices(length, rule.seed)
  let train_count = (length.to_double() * rule.train_fraction).to_int()
  let validation_count = (length.to_double() * rule.validation_fraction).to_int()
  let train_indices = []
  let validation_indices = []
  let test_indices = []
  for position = 0; position < shuffled.length(); position = position + 1 {
    if position < train_count {
      train_indices.push(shuffled[position])
    } else if position < train_count + validation_count {
      validation_indices.push(shuffled[position])
    } else {
      test_indices.push(shuffled[position])
    }
  }
  {
    train: [],
    validation: [],
    test_set: [],
    train_indices,
    validation_indices,
    test_indices,
  }
}

///|
pub fn sampling_partition(
  data : Array[Double],
  rule : SamplingRule,
) -> DataPartition {
  let indices = sampling_partition_indices(data.length(), rule)
  {
    train: sampling_take_indices(data, indices.train_indices),
    validation: sampling_take_indices(data, indices.validation_indices),
    test_set: sampling_take_indices(data, indices.test_indices),
    train_indices: indices.train_indices,
    validation_indices: indices.validation_indices,
    test_indices: indices.test_indices,
  }
}

///|
pub fn sampling_partition_sizes(
  data : Array[Double],
  rule : SamplingRule,
) -> Array[Int] {
  let partition = sampling_partition(data, rule)
  [
    partition.train.length(),
    partition.validation.length(),
    partition.test_set.length(),
  ]
}

///|
pub fn sampling_kfold_indices(
  length : Int,
  folds : Int,
  seed : Int,
) -> Array[Array[Int]] {
  let result = []
  let count = if folds < 2 { 2 } else { folds }
  let shuffled = sampling_shuffle_indices(length, seed)
  for fold = 0; fold < count; fold = fold + 1 {
    let indices = []
    for position = fold
        position < shuffled.length()
        position = position + count {
      indices.push(shuffled[position])
    }
    result.push(indices)
  }
  result
}

///|
pub fn sampling_kfold(
  data : Array[Double],
  folds : Int,
  seed : Int,
) -> Array[DataFold] {
  let result = []
  let groups = sampling_kfold_indices(data.length(), folds, seed)
  for fold = 0; fold < groups.length(); fold = fold + 1 {
    let holdout_indices = groups[fold]
    let train_indices = []
    let is_holdout = []
    for _ in 0..= 0 && index < is_holdout.length() {
        is_holdout[index] = true
      }
    }
    for index = 0; index < data.length(); index = index + 1 {
      if !is_holdout[index] {
        train_indices.push(index)
      }
    }
    result.push({
      fold,
      train: sampling_take_indices(data, train_indices),
      holdout: sampling_take_indices(data, holdout_indices),
      train_indices,
      holdout_indices,
    })
  }
  result
}

///|
pub fn sampling_fold_scores(
  data : Array[Double],
  folds : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for fold in sampling_kfold(data, folds, seed) {
    result.push(abs_double(mean(fold.train) - mean(fold.holdout)))
  }
  result
}

///|
pub fn sampling_fold_robust_scores(
  data : Array[Double],
  folds : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for fold in sampling_kfold(data, folds, seed) {
    result.push(abs_double(median(fold.train) - median(fold.holdout)))
  }
  result
}

///|
pub fn sampling_fold_stability(
  data : Array[Double],
  folds : Int,
  seed : Int,
) -> Double {
  let scores = sampling_fold_robust_scores(data, folds, seed)
  if scores.length() == 0 {
    0.0
  } else {
    1.0 / (1.0 + mean(scores))
  }
}

///|
pub fn sampling_stratified_indices(
  labels : Array[Int],
  fraction : Double,
  seed : Int,
) -> Array[Int] {
  let result = []
  let groups = []
  for label in labels {
    if !groups.contains(label) {
      groups.push(label)
    }
  }
  for group_index = 0
      group_index < groups.length()
      group_index = group_index + 1 {
    let group_indices = []
    for index = 0; index < labels.length(); index = index + 1 {
      if labels[index] == groups[group_index] {
        group_indices.push(index)
      }
    }
    let shuffled = sampling_shuffle_indices(
      group_indices.length(),
      seed + group_index,
    )
    let count = (group_indices.length().to_double() * fraction).to_int()
    for position = 0
        position < count && position < shuffled.length()
        position = position + 1 {
      result.push(group_indices[shuffled[position]])
    }
  }
  result.sort()
  result
}

///|
pub fn sampling_stratified_values(
  data : Array[Double],
  labels : Array[Int],
  fraction : Double,
  seed : Int,
) -> Array[Double] {
  if data.length() != labels.length() {
    return []
  }
  sampling_take_indices(
    data,
    sampling_stratified_indices(labels, fraction, seed),
  )
}

///|
pub fn sampling_repeated_subsamples(
  data : Array[Double],
  count : Int,
  size : Int,
  seed : Int,
) -> Array[Array[Double]] {
  let result = []
  for index = 0; index < count; index = index + 1 {
    result.push(sampling_without_replacement(data, size, seed + index))
  }
  result
}

///|
pub fn sampling_subsample_means(
  data : Array[Double],
  count : Int,
  size : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for sample in sampling_repeated_subsamples(data, count, size, seed) {
    result.push(mean(sample))
  }
  result
}

///|
pub fn sampling_subsample_medians(
  data : Array[Double],
  count : Int,
  size : Int,
  seed : Int,
) -> Array[Double] {
  let result = []
  for sample in sampling_repeated_subsamples(data, count, size, seed) {
    result.push(median(sample))
  }
  result
}

///|
pub fn sampling_stability_score(
  data : Array[Double],
  count : Int,
  size : Int,
  seed : Int,
) -> Double {
  let estimates = sampling_subsample_medians(data, count, size, seed)
  if estimates.length() == 0 {
    0.0
  } else {
    1.0 / (1.0 + mad(estimates))
  }
}

///|
pub fn sampling_seed_sequence(seed : Int, length : Int) -> Array[Int] {
  let result = []
  let mut state = seed
  for _ in 0.. Bool {
  sampling_with_replacement(data, count, seed) ==
  sampling_with_replacement(data, count, seed)
}

///|
pub fn sampling_effective_size(weights : Array[Double]) -> Double {
  let total = sum_absolute(weights)
  if total == 0.0 {
    0.0
  } else {
    total * total / sum_squared(weights)
  }
}

///|
pub fn sampling_weighted_indices(
  weights : Array[Double],
  count : Int,
  seed : Int,
) -> Array[Int] {
  let result = []
  if weights.length() == 0 || count <= 0 {
    return result
  }
  let total = sum_absolute(weights)
  if total == 0.0 {
    return result
  }
  let rng = DeterministicRng::new(seed)
  for _ in 0.. Array[Double] {
  sampling_take_indices(data, sampling_weighted_indices(weights, count, seed))
}

///|
pub fn sampling_mean_interval(
  data : Array[Double],
  count : Int,
  seed : Int,
) -> Array[Double] {
  let samples = sampling_subsample_means(data, count, data.length(), seed)
  [quantile(samples, 0.025), quantile(samples, 0.975)]
}

///|
pub fn sampling_median_interval(
  data : Array[Double],
  count : Int,
  seed : Int,
) -> Array[Double] {
  let samples = sampling_subsample_medians(data, count, data.length(), seed)
  [quantile(samples, 0.025), quantile(samples, 0.975)]
}

///|
pub fn sampling_outlier_resistant_split(
  data : Array[Double],
  rule : SamplingRule,
) -> DataPartition {
  let shuffled = sampling_partition(data, rule)
  let train = quality_sanitize(shuffled.train, quality_default_rule())
  let validation = quality_sanitize(shuffled.validation, quality_default_rule())
  let test_set = quality_sanitize(shuffled.test_set, quality_default_rule())
  {
    train,
    validation,
    test_set,
    train_indices: shuffled.train_indices,
    validation_indices: shuffled.validation_indices,
    test_indices: shuffled.test_indices,
  }
}

///|
pub fn sampling_partition_quality(partition : DataPartition) -> Array[Double] {
  [
    robust_signal_quality(partition.train),
    robust_signal_quality(partition.validation),
    robust_signal_quality(partition.test_set),
    duplicate_fraction(partition.train),
    duplicate_fraction(partition.validation),
    duplicate_fraction(partition.test_set),
  ]
}