///|
/// A named view over one numeric dataset column.
pub struct CausalColumnView {
  name : String
  index : Int
  values : Array[Double]
  finite_count : Int
  missing_count : Int
  mean : Double
  standard_deviation : Double
  minimum : Double
  maximum : Double
}

///|
/// A row-level view used by scoring and review workflows.
pub struct CausalRowView {
  index : Int
  covariates : Array[Double]
  treatment : Bool
  outcome : Double
  finite : Bool
  score : Double
}

///|
/// Deterministic train/validation split indices.
pub struct CausalDataSplit {
  train : Array[Int]
  validation : Array[Int]
  train_fraction : Double
  seed : UInt64
  passes : Bool
}

///|
/// Cross-fitting fold assignment and size diagnostics.
pub struct CausalFoldAssignment {
  folds : Array[Int]
  sizes : Array[Int]
  requested_folds : Int
  passes : Bool
}

///|
/// Treatment-stratified group summary.
pub struct CausalStratumSummary {
  stratum : Int
  count : Int
  treated : Int
  control : Int
  treatment_rate : Double
  outcome_mean : Double
}

///|
/// Dataset view diagnostics used by audit and model preparation.
pub struct CausalDataViewSummary {
  rows : Int
  columns : Int
  finite_rows : Int
  treated : Int
  control : Int
  missing_cells : Int
  constant_columns : Int
  outcome_mean : Double
  outcome_standard_deviation : Double
  passes : Bool
}

///|
/// Returns a sorted copy without mutating caller-owned values.
pub fn causal_view_sorted(values : Array[Double]) -> Array[Double] {
  let result = values.copy()
  for i in 1.. 0 && result[cursor - 1] > value {
      result[cursor] = result[cursor - 1]
      cursor -= 1
    }
    result[cursor] = value
  }
  result
}

///|
/// Creates a column view from a matrix with a stable fallback for short rows.
pub fn causal_column_view(
  matrix : Array[Array[Double]],
  index : Int,
  name? : String = "",
  fill? : Double = 0.0,
) -> CausalColumnView {
  let values : Array[Double] = Array::new(capacity=matrix.length())
  for row in matrix {
    values.push(
      if index >= 0 && index < row.length() {
        row[index]
      } else {
        fill
      },
    )
  }
  let finite = causal_finite_values(values)
  {
    name: if name == "" {
      "x\{index}"
    } else {
      name
    },
    index,
    values,
    finite_count: finite.length(),
    missing_count: values.length() - finite.length(),
    mean: mean_or(finite, 0.0),
    standard_deviation: if finite.length() > 1 {
      std_dev(finite)
    } else {
      0.0
    },
    minimum: causal_minimum(finite),
    maximum: causal_maximum(finite),
  }
}

///|
/// Creates all column views in matrix order.
pub fn causal_column_views(
  matrix : Array[Array[Double]],
  names? : Array[String] = [],
) -> Array[CausalColumnView] {
  let width = if matrix.length() == 0 {
    names.length()
  } else {
    let mut maximum = 0
    for row in matrix {
      if row.length() > maximum {
        maximum = row.length()
      }
    }
    maximum.max(names.length())
  }
  let result : Array[CausalColumnView] = Array::new(capacity=width)
  for index in 0.. CausalRowView {
  let valid = index >= 0 && index < dataset.n()
  let covariates = if valid { dataset.covariates[index].copy() } else { [] }
  let treatment = if valid { dataset.treatment[index] } else { false }
  let outcome = if valid { dataset.outcome[index] } else { 0.0 }
  let finite = valid && is_finite(outcome) && finite_array(covariates)
  {
    index,
    covariates,
    treatment,
    outcome,
    finite,
    score: if is_finite(score) {
      score
    } else {
      0.0
    },
  }
}

///|
/// Returns all row views in dataset order.
pub fn causal_row_views(dataset : CausalDataset) -> Array[CausalRowView] {
  let result : Array[CausalRowView] = Array::new(capacity=dataset.n())
  for index in 0.. Array[Int] {
  let order : Array[Int] = Array::new(capacity=values.length())
  for index in 0.. 0 {
      let previous = order[cursor - 1]
      let previous_value = if is_finite(values[previous]) {
        values[previous]
      } else {
        1.0e300
      }
      if previous_value <= value {
        break
      }
      order[cursor] = previous
      cursor -= 1
    }
    order[cursor] = index
  }
  order
}

///|
/// Splits indices into train and validation partitions reproducibly.
pub fn causal_train_validation_split(
  rows : Int,
  train_fraction? : Double = 0.8,
  seed? : UInt64 = 20260819,
) -> CausalDataSplit {
  let n = rows.max(0)
  let fraction = clamp(train_fraction, 0.0, 1.0)
  let target = (fraction * n.to_double()).to_int().min(n)
  let rng = RandomState::new(seed)
  let order : Array[Int] = Array::new(capacity=n)
  for index in 0.. 1 {
      let offset = (rng.uniform() * remaining.to_double())
        .to_int()
        .min(remaining - 1)
      let swap = i + offset
      let temporary = order[i]
      order[i] = order[swap]
      order[swap] = temporary
    }
  }
  let train : Array[Int] = Array::new(capacity=target)
  let validation : Array[Int] = Array::new(capacity=n - target)
  for i in 0.. CausalDataSplit {
  let fraction = clamp(train_fraction, 0.0, 1.0)
  let treated : Array[Int] = Array::new()
  let control : Array[Int] = Array::new()
  for index in 0.. CausalFoldAssignment {
  let n = rows.max(0)
  let folds_count = requested_folds.max(1).min(n.max(1))
  let split = causal_train_validation_split(n, train_fraction=1.0, seed~)
  let labels = Array::make(n, 0)
  let sizes = Array::make(folds_count, 0)
  for i in 0.. CausalDataSplit {
  let fold = held_out_fold.max(0).min(assignment.requested_folds - 1)
  let train : Array[Int] = Array::new()
  let validation : Array[Int] = Array::new()
  for index in 0.. Array[CausalDataSplit] {
  let assignment = causal_fold_assignment(rows, requested_folds, seed~)
  let result : Array[CausalDataSplit] = Array::new(
    capacity=assignment.requested_folds,
  )
  for fold in 0.. Array[Array[Int]] {
  let n = rows.max(0)
  let count = replicates.max(0)
  let rng = RandomState::new(seed)
  let result : Array[Array[Int]] = Array::new(capacity=count)
  for _ in 0.. 0 {
        sample.push(index)
      }
    }
    result.push(sample)
  }
  result
}

///|
/// Resamples a causal dataset using one bootstrap index vector.
pub fn causal_bootstrap_dataset(
  dataset : CausalDataset,
  indices : Array[Int],
) -> CausalDataset {
  dataset.select(indices)
}

///|
/// Returns the finite row mask for a rectangular dataset.
pub fn causal_finite_row_mask(dataset : CausalDataset) -> Array[Bool] {
  let result : Array[Bool] = Array::new(capacity=dataset.n())
  for index in 0.. Array[Int] {
  let result : Array[Int] = Array::new()
  for index in 0.. CausalDataset {
  dataset.select(causal_mask_indices(causal_finite_row_mask(dataset)))
}

///|
/// Assigns rows to integer strata using a finite score and fixed width.
pub fn causal_score_strata(
  scores : Array[Double],
  strata : Int,
  minimum? : Double = 0.0,
  maximum? : Double = 1.0,
) -> Array[Int] {
  let count = strata.max(1)
  let lower = minimum.min(maximum)
  let upper = maximum.max(minimum)
  let width = (upper - lower).max(1.0e-12)
  scores.map(fn(score) {
    if !is_finite(score) {
      0
    } else {
      ((score - lower) / width * count.to_double())
      .to_int()
      .max(0)
      .min(count - 1)
    }
  })
}

///|
/// Summarizes treatment and outcome within integer strata.
pub fn causal_stratum_summaries(
  strata : Array[Int],
  treatment : Array[Bool],
  outcome : Array[Double],
  stratum_count : Int,
) -> Array[CausalStratumSummary] {
  let count = stratum_count.max(1)
  let sizes = Array::make(count, 0)
  let treated = Array::make(count, 0)
  let outcome_sum = Array::make(count, 0.0)
  let n = strata.length().min(treatment.length()).min(outcome.length())
  for i in 0.. CausalDataset {
  let n = dataset.n().min(scores.length())
  let order = causal_order_indices(scores[:n].to_owned())
  dataset.select(order)
}

///|
/// Selects rows by a deterministic score quantile interval.
pub fn causal_quantile_slice(
  dataset : CausalDataset,
  scores : Array[Double],
  lower_probability : Double,
  upper_probability : Double,
) -> CausalDataset {
  let finite = causal_finite_values(scores)
  if finite.length() == 0 {
    return CausalDataset::new([], [], [])
  }
  let lower = quantile(finite, clamp(lower_probability, 0.0, 1.0))
  let upper = quantile(finite, clamp(upper_probability, 0.0, 1.0))
  causal_dataset_filter_score(dataset, scores, lower, upper)
}

///|
/// Builds a complete dataset view summary.
pub fn causal_data_view_summary(
  dataset : CausalDataset,
) -> CausalDataViewSummary {
  let mask = causal_finite_row_mask(dataset)
  let finite_rows = mask.fold(init=0, fn(total, value) {
    if value {
      total + 1
    } else {
      total
    }
  })
  let missing_cells = causal_matrix_missing(dataset.covariates)
  let constant_columns = causal_constant_columns(dataset.covariates)
  {
    rows: dataset.n(),
    columns: dataset.p(),
    finite_rows,
    treated: dataset.treated_count(),
    control: dataset.control_count(),
    missing_cells,
    constant_columns,
    outcome_mean: mean_or(causal_finite_values(dataset.outcome), 0.0),
    outcome_standard_deviation: std_dev(causal_finite_values(dataset.outcome)),
    passes: dataset.is_valid() &&
    finite_rows == dataset.n() &&
    dataset.treated_count() > 0 &&
    dataset.control_count() > 0,
  }
}

///|
/// Returns a compact data-view summary vector.
pub fn causal_data_view_summary_vector(
  summary : CausalDataViewSummary,
) -> Array[Double] {
  [
    summary.rows.to_double(),
    summary.columns.to_double(),
    summary.finite_rows.to_double(),
    summary.treated.to_double(),
    summary.control.to_double(),
    summary.missing_cells.to_double(),
    summary.constant_columns.to_double(),
    summary.outcome_mean,
    summary.outcome_standard_deviation,
    if summary.passes {
      1.0
    } else {
      0.0
    },
  ]
}

///|
/// Computes a stable fingerprint for split indices.
pub fn causal_split_fingerprint(split : CausalDataSplit) -> UInt64 {
  let rows : Array[Array[Double]] = Array::new()
  rows.push([split.train_fraction, split.seed.to_double()])
  for index in split.train {
    rows.push([1.0, index.to_double()])
  }
  for index in split.validation {
    rows.push([0.0, index.to_double()])
  }
  matrix_checksum(rows)
}

///|
/// Computes a stable fingerprint for fold assignments.
pub fn causal_fold_fingerprint(assignment : CausalFoldAssignment) -> UInt64 {
  let rows : Array[Array[Double]] = Array::new()
  for index in 0..