///|
/// A changed endpoint retains its contract comparison, including literal-only
/// changes that a variable/operator comparison alone would not detect.
pub struct CatalogTemplateChange {
  name : String
  contract : TemplateDiff
}

///|
pub fn CatalogTemplateChange::name(self : CatalogTemplateChange) -> String {
  self.name
}

///|
pub fn CatalogTemplateChange::contract(
  self : CatalogTemplateChange,
) -> TemplateDiff {
  self.contract
}

///|
pub struct CatalogDiff {
  added : Array[String]
  removed : Array[String]
  changed : Array[CatalogTemplateChange]
}

///|
pub fn CatalogDiff::added(self : CatalogDiff) -> Array[String] {
  self.added.copy()
}

///|
pub fn CatalogDiff::removed(self : CatalogDiff) -> Array[String] {
  self.removed.copy()
}

///|
pub fn CatalogDiff::changed(self : CatalogDiff) -> Array[CatalogTemplateChange] {
  self.changed.copy()
}

///|
pub fn CatalogDiff::is_empty(self : CatalogDiff) -> Bool {
  self.added.is_empty() && self.removed.is_empty() && self.changed.is_empty()
}

///|
/// Compare two endpoint catalogs before deploying a new API/link definition.
/// Additions and changes follow the next catalog order; removals follow the old.
/// A report is structural evidence, not a proof of backward compatibility.
pub fn TemplateCatalog::diff_catalog(
  self : TemplateCatalog,
  next : TemplateCatalog,
) -> CatalogDiff {
  let added : Array[String] = []
  let removed : Array[String] = []
  let changed : Array[CatalogTemplateChange] = []
  for name in next.names {
    let after = next.templates[name]
    match self.templates.get(name) {
      None => added.push(name)
      Some(before) =>
        if before.source != after.source {
          changed.push({ name, contract: before.diff_contract(after), })
        }
    }
  }
  for name in self.names {
    if !next.templates.contains(name) {
      removed.push(name)
    }
  }
  { added, removed, changed, }
}