///|
pub fn FeatureImportance::feature_index(self : FeatureImportance) -> Int {
  self.source_feature_index
}

///|
pub fn FeatureImportance::baseline_accuracy(self : FeatureImportance) -> Double {
  self.unpermuted_accuracy
}

///|
pub fn FeatureImportance::permuted_accuracy(self : FeatureImportance) -> Double {
  self.permuted_accuracy_value
}

///|
pub fn FeatureImportance::accuracy_drop(self : FeatureImportance) -> Double {
  self.measured_accuracy_drop
}

///|
pub fn PermutationImportance::baseline_accuracy(
  self : PermutationImportance,
) -> Double {
  self.original_accuracy
}

///|
pub fn PermutationImportance::feature_count(
  self : PermutationImportance,
) -> Int {
  self.ordered_importances.length()
}

///|
pub fn PermutationImportance::ranked_features(
  self : PermutationImportance,
) -> Array[FeatureImportance] {
  self.ordered_importances.copy()
}

///|
fn reversed_feature_rows(data : Dataset, feature : Int) -> Array[Array[Double]] {
  let rows = data.features()
  let count = rows.length()
  for row = 0; row < count; row = row + 1 {
    rows[row][feature] = data.feature_rows[count - row - 1][feature]
  }
  rows
}

///|
fn sort_feature_importances(values : Array[FeatureImportance]) -> Unit {
  for index = 1; index < values.length(); index = index + 1 {
    let selected = values[index]
    let mut position = index
    while position > 0 &&
          (
            values[position - 1].accuracy_drop() < selected.accuracy_drop() ||
            (
              values[position - 1].accuracy_drop() == selected.accuracy_drop() &&
              values[position - 1].feature_index() > selected.feature_index()
            )
          ) {
      values[position] = values[position - 1]
      position = position - 1
    }
    values[position] = selected
  }
}

///|
fn importance_result(
  baseline : Double,
  permuted_accuracies : Array[Double],
) -> PermutationImportance {
  let values : Array[FeatureImportance] = []
  for feature, accuracy in permuted_accuracies {
    values.push({
      source_feature_index: feature,
      unpermuted_accuracy: baseline,
      permuted_accuracy_value: accuracy,
      measured_accuracy_drop: baseline - accuracy,
    })
  }
  sort_feature_importances(values)
  { original_accuracy: baseline, ordered_importances: values }
}

///|
fn checked_importance_dimensions(
  expected : Int,
  data : Dataset,
) -> Result[Unit, SvmError] {
  if data.feature_count() != expected {
    Err(PredictionDimensionMismatch(expected, data.feature_count()))
  } else {
    Ok(())
  }
}

///|
/// Measures binary accuracy changes using a deterministic reverse permutation.
pub fn binary_permutation_importance(
  model : BinaryModel,
  data : Dataset,
) -> Result[PermutationImportance, SvmError] {
  match checked_importance_dimensions(model.feature_count(), data) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let labels = data.labels()
  let baseline_predictions = match model.predict_batch(data.features()) {
    Err(error) => return Err(error)
    Ok(value) => value
  }
  let baseline = match classification_metrics(labels, baseline_predictions) {
    Err(error) => return Err(error)
    Ok(value) => value.accuracy()
  }
  let permuted_accuracies : Array[Double] = []
  for feature = 0; feature < data.feature_count(); feature = feature + 1 {
    let predictions = match
      model.predict_batch(reversed_feature_rows(data, feature)) {
      Err(error) => return Err(error)
      Ok(value) => value
    }
    let accuracy = match classification_metrics(labels, predictions) {
      Err(error) => return Err(error)
      Ok(value) => value.accuracy()
    }
    permuted_accuracies.push(accuracy)
  }
  Ok(importance_result(baseline, permuted_accuracies))
}

///|
/// Measures OvR accuracy changes using the same deterministic permutation.
pub fn multiclass_permutation_importance(
  model : MulticlassModel,
  data : Dataset,
) -> Result[PermutationImportance, SvmError] {
  match checked_importance_dimensions(model.feature_count(), data) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let labels = data.labels()
  let baseline_predictions = match model.predict_batch(data.features()) {
    Err(error) => return Err(error)
    Ok(value) => value
  }
  let baseline = match classification_metrics(labels, baseline_predictions) {
    Err(error) => return Err(error)
    Ok(value) => value.accuracy()
  }
  let permuted_accuracies : Array[Double] = []
  for feature = 0; feature < data.feature_count(); feature = feature + 1 {
    let predictions = match
      model.predict_batch(reversed_feature_rows(data, feature)) {
      Err(error) => return Err(error)
      Ok(value) => value
    }
    let accuracy = match classification_metrics(labels, predictions) {
      Err(error) => return Err(error)
      Ok(value) => value.accuracy()
    }
    permuted_accuracies.push(accuracy)
  }
  Ok(importance_result(baseline, permuted_accuracies))
}