///|
/// Merge current domains by interval name; conflicts remain visible as empty sets.
/// This is a matrix snapshot merge, so source evidence is labeled by input side.
pub fn merge_networks(
  left : Network,
  right : Network,
) -> Network raise AllenError {
  let names = left.names.copy()
  for name in right.names {
    if !names.contains(name) {
      names.push(name)
    }
  }
  let mut out = Network::new(names)
  for source in left.domain_constraints(label="merge-left-domain") {
    out = out.constrain(source)
  }
  for source in right.domain_constraints(label="merge-right-domain") {
    out = out.constrain(source)
  }
  out
}

///|
pub(all) struct DomainChange {
  left : String
  right : String
  before : RelSet
  after : RelSet
  removed : RelSet
  added : RelSet
}

///|
/// Compare same-named matrices even when their positional orders differ.
pub fn compare_domains(
  before : Network,
  after : Network,
) -> Array[DomainChange] raise AllenError {
  if before.size() != after.size() {
    raise InvalidFormat("comparison name sets differ")
  }
  let positions = before.names.map(name => after.index(name))
  let changes : Array[DomainChange] = []
  let n = before.size()
  for i = 0; i < n; i = i + 1 {
    for j = i; j < n; j = j + 1 {
      let a = before.cells[i * n + j]
      let b = after.cells[positions[i] * n + positions[j]]
      if a != b {
        changes.push({
          left: before.names[i],
          right: before.names[j],
          before: a,
          after: b,
          removed: a.difference(b),
          added: b.difference(a),
        })
      }
    }
  }
  changes
}

///|
pub(all) struct NetworkStats {
  intervals : Int32
  pairs : Int32
  constrained_pairs : Int32
  atomic_pairs : Int32
  ambiguous_pairs : Int32
  empty_domains : Int32
  alternatives : Int32
  source_constraints : Int32
}

///|
pub fn Network::stats(self : Network) -> NetworkStats {
  let n = self.size()
  let mut constrained = 0
  let mut atoms = 0
  let mut ambiguous = 0
  let mut empty = 0
  let mut alternatives = 0
  for i = 0; i < n; i = i + 1 {
    if self.cells[i * n + i].is_empty() {
      empty = empty + 1
    }
    for j = i + 1; j < n; j = j + 1 {
      let set = self.cells[i * n + j]
      if set != RelSet::all() {
        constrained = constrained + 1
      }
      let count = set.size()
      alternatives = alternatives + count
      if count == 0 {
        empty = empty + 1
      } else if count == 1 {
        atoms = atoms + 1
      } else {
        ambiguous = ambiguous + 1
      }
    }
  }
  {
    intervals: n,
    pairs: n * (n - 1) / 2,
    constrained_pairs: constrained,
    atomic_pairs: atoms,
    ambiguous_pairs: ambiguous,
    empty_domains: empty,
    alternatives,
    source_constraints: self.sources.length(),
  }
}