///|
/// Source of a declared rule's effective selection. No configuration values
/// are exposed in the public change report.
pub(all) enum ConfigEffectiveSource {
  Inactive
  Missing
  Explicit
  Default
} derive(Eq, Debug)

///|
pub extend ConfigEffectiveSource with Eq::{not_equal, equal}

///|
pub extend ConfigEffectiveSource with Debug::{to_repr}

///|
pub struct ConfigEffectiveChange {
  path : ConfigPath
  before : ConfigEffectiveSource
  after : ConfigEffectiveSource
  value_changed : Bool
}

///|
pub fn ConfigEffectiveChange::path(self : ConfigEffectiveChange) -> ConfigPath {
  self.path
}

///|
pub fn ConfigEffectiveChange::before(
  self : ConfigEffectiveChange,
) -> ConfigEffectiveSource {
  self.before
}

///|
pub fn ConfigEffectiveChange::after(
  self : ConfigEffectiveChange,
) -> ConfigEffectiveSource {
  self.after
}

///|
/// True for unequal selected values or transitions between inactive, missing
/// and present states. Equal explicit/default values are provenance-only.
pub fn ConfigEffectiveChange::value_changed(
  self : ConfigEffectiveChange,
) -> Bool {
  self.value_changed
}

///|
fn consumer_effective_selection(
  rule : ConfigConsumerRule,
  snapshot : ConfigValue,
  inputs : Map[String, ConfigConsumerRule],
) -> (ConfigEffectiveSource, ConfigValue?) {
  if !consumer_condition_matches(rule, snapshot, inputs) {
    return (Inactive, None)
  }
  match snapshot.get(rule.path) {
    Some(value) => (Explicit, Some(value))
    None =>
      match rule.default_value {
        Some(value) => (Default, Some(value))
        None => (Missing, None)
      }
  }
}

///|
/// Called only after BOTH snapshots pass the bound contract. No implicit
/// normalization, mutation, application execution or undeclared-path analysis.
fn consumer_effective_changes(
  contract : ConfigConsumerContract,
  historical : ConfigValue,
  candidate : ConfigValue,
) -> Array[ConfigEffectiveChange] {
  let inputs = consumer_unconditional_rules(contract.rules)
  let changes : Array[ConfigEffectiveChange] = []
  for rule in contract.rules {
    let (before, old_value) = consumer_effective_selection(
      rule, historical, inputs,
    )
    let (after, new_value) = consumer_effective_selection(
      rule, candidate, inputs,
    )
    let value_changed = if old_value is Some(_) && new_value is Some(_) {
      old_value != new_value
    } else {
      before != after
    }
    if before != after || value_changed {
      changes.push({ path: rule.path, before, after, value_changed, })
    }
  }
  changes.sort_by((a, b) => {
    a.path.to_string().lexical_compare(b.path.to_string())
  })
  changes
}