///|
pub(all) struct DependencyLocality {
  dependency : FD
  containing_relations : Array[Int32]
  jointly_preserved : Bool
} derive(Eq, Debug)

///|
pub fn Schema::dependency_locality(
  self : Schema,
  relations : Array[Array[Int32]],
  limits~ : Limits = default_limits(),
) -> Array[DependencyLocality] raise DesignError {
  let parts = self.fragments(relations, true)
  let budget = meter(limits)
  let preservation = self.preservation_of(parts, budget)
  let out = []
  for fd in self.dependencies() {
    let containing_relations = []
    let scope = union(fd.left, fd.right)
    for i in 0.. Array[Int32] {
  let counts = Array::make(n, 0)
  for part in parts {
    for a in part {
      counts[a] += 1
    }
  }
  let out = []
  for a in 0.. 1 {
      out.push(a)
    }
  }
  out
}

///|
pub fn Schema::compare_layouts(
  self : Schema,
  before : Array[Array[Int32]],
  after : Array[Array[Int32]],
) -> LayoutDelta raise DesignError {
  let old = self.fragments(before, true)
  let next = self.fragments(after, true)
  {
    unchanged: old.filter(r => next.contains(r)),
    removed: old.filter(r => !next.contains(r)),
    added: next.filter(r => !old.contains(r)),
    duplicated_attributes_before: duplicated_attributes(
      old,
      self.attribute_count(),
    ),
    duplicated_attributes_after: duplicated_attributes(
      next,
      self.attribute_count(),
    ),
  }
}

///|
pub(all) struct MigrationReview {
  before : PlanAudit
  after : PlanAudit
  layout : LayoutDelta
  lost_dependencies : Array[FD]
  gained_dependencies : Array[FD]
  lossless_regression : Bool
  normal_form_regression : Bool
  locality : Array[DependencyLocality]
} derive(Eq, Debug)

///|
pub fn Schema::review_migration(
  self : Schema,
  before : Plan,
  after : Plan,
  limits~ : Limits = default_limits(),
) -> MigrationReview raise DesignError {
  if before.target != after.target {
    raise Invalid("migration comparison requires same target normal form")
  }
  let old = self.audit_plan(before, limits~)
  let next = self.audit_plan(after, limits~)
  let lost_dependencies = next.missing_dependencies.filter(fd => {
    !old.missing_dependencies.contains(fd)
  })
  let gained_dependencies = old.missing_dependencies.filter(fd => {
    !next.missing_dependencies.contains(fd)
  })
  {
    before: old,
    after: next,
    layout: self.compare_layouts(before.relations, after.relations),
    lost_dependencies,
    gained_dependencies,
    lossless_regression: old.lossless && !next.lossless,
    normal_form_regression: old.target_satisfied && !next.target_satisfied,
    locality: self.dependency_locality(after.relations, limits~),
  }
}

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

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

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

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

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

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