///|
/// Compatibility direction requested by the caller.
pub(all) enum CompatibilityMode {
  Backward
  Forward
  Full
} derive(Eq, Debug)

///|
/// Importance assigned to an evolution finding.
pub(all) enum Severity {
  Breaking
  Warning
  Info
} derive(Eq, Debug)

///|
/// A concrete payload showing why a compatibility rule fails.
pub(all) struct Witness {
  payload : String
  accepted_by : String
  rejected_by : String
  reason : String
} derive(Eq, Debug)

///|
/// One stable, path-addressable contract evolution finding.
pub(all) struct Change {
  code : String
  severity : Severity
  direction : String
  path : String
  message : String
  hint : String
  witness : Witness?
} derive(Eq, Debug)

///|
/// Complete analysis result for two contract versions.
pub(all) struct AnalysisReport {
  contract_name : String
  old_version : String
  new_version : String
  mode : CompatibilityMode
  changes : Array[Change]
} derive(Eq, Debug)

///|
pub fn CompatibilityMode::render(self : CompatibilityMode) -> String {
  match self {
    Backward => "backward"
    Forward => "forward"
    Full => "full"
  }
}

///|
pub fn Severity::render(self : Severity) -> String {
  match self {
    Breaking => "breaking"
    Warning => "warning"
    Info => "info"
  }
}

///|
pub fn AnalysisReport::breaking_count(self : AnalysisReport) -> Int {
  self.changes.count_if(change => change.severity == Breaking)
}

///|
pub fn AnalysisReport::warning_count(self : AnalysisReport) -> Int {
  self.changes.count_if(change => change.severity == Warning)
}

///|
pub fn AnalysisReport::info_count(self : AnalysisReport) -> Int {
  self.changes.count_if(change => change.severity == Info)
}

///|
pub fn AnalysisReport::is_compatible(self : AnalysisReport) -> Bool {
  self.breaking_count() == 0
}

///|
pub fn AnalysisReport::verdict(self : AnalysisReport) -> String {
  if self.is_compatible() {
    "compatible"
  } else {
    "incompatible"
  }
}

///|
fn breaking_change(
  code : String,
  direction : String,
  path : String,
  message : String,
  hint : String,
  witness : Witness,
) -> Change {
  {
    code,
    severity: Breaking,
    direction,
    path,
    message,
    hint,
    witness: Some(witness),
  }
}

///|
fn info_change(
  code : String,
  direction : String,
  path : String,
  message : String,
  hint : String,
) -> Change {
  { code, severity: Info, direction, path, message, hint, witness: None }
}