// Deterministic, non-mutating document comparison for history and syncing.

///|
pub(all) struct DocumentDiff {
  added : Array[NodeID]
  removed : Array[NodeID]
  modified : Array[NodeID]
  metadata_changed : Bool
} derive(Eq, Debug)

///|
pub fn DocumentDiff::empty() -> DocumentDiff {
  { added: [], removed: [], modified: [], metadata_changed: false }
}

///|
pub fn DocumentDiff::has_changes(self : DocumentDiff) -> Bool {
  self.metadata_changed ||
  !self.added.is_empty() ||
  !self.removed.is_empty() ||
  !self.modified.is_empty()
}

///|
pub fn DocumentDiff::changed_count(self : DocumentDiff) -> Int {
  self.added.length() + self.removed.length() + self.modified.length()
}

///|
pub fn DocumentDiff::contains(self : DocumentDiff, id : NodeID) -> Bool {
  self.added.contains(id) ||
  self.removed.contains(id) ||
  self.modified.contains(id)
}

///|
fn append_if_missing(ids : Array[NodeID], id : NodeID) -> Unit {
  if !ids.contains(id) {
    ids.push(id)
  }
}

///|
pub fn ArtboardDocument::diff(
  self : ArtboardDocument,
  target : ArtboardDocument,
) -> DocumentDiff {
  let added : Array[NodeID] = []
  let removed : Array[NodeID] = []
  let modified : Array[NodeID] = []
  let target_order = target.ordered_node_ids()
  let source_order = self.ordered_node_ids()
  for i = 0; i < target_order.length(); i = i + 1 {
    let id = target_order[i]
    match self.nodes.get(id.value) {
      None => added.push(id)
      Some(source_node) =>
        match target.nodes.get(id.value) {
          Some(target_node) if source_node != target_node => modified.push(id)
          _ => ()
        }
    }
  }
  for i = 0; i < source_order.length(); i = i + 1 {
    let id = source_order[i]
    if target.nodes.get(id.value) is None {
      removed.push(id)
    }
  }
  for key, node in target.nodes {
    match self.nodes.get(key) {
      None => append_if_missing(added, node.id)
      Some(source_node) if source_node != node =>
        append_if_missing(modified, node.id)
      _ => ()
    }
  }
  for key, node in self.nodes {
    if target.nodes.get(key) is None {
      append_if_missing(removed, node.id)
    }
  }
  {
    added,
    removed,
    modified,
    metadata_changed: self.title != target.title ||
    self.width != target.width ||
    self.height != target.height,
  }
}

///|
pub fn ArtboardDocument::same_content(
  self : ArtboardDocument,
  other : ArtboardDocument,
) -> Bool {
  !self.diff(other).has_changes()
}

///|
pub fn ArtboardDocument::added_since(
  self : ArtboardDocument,
  baseline : ArtboardDocument,
) -> Array[NodeID] {
  baseline.diff(self).added
}

///|
pub fn ArtboardDocument::removed_since(
  self : ArtboardDocument,
  baseline : ArtboardDocument,
) -> Array[NodeID] {
  baseline.diff(self).removed
}

///|
pub fn ArtboardDocument::modified_since(
  self : ArtboardDocument,
  baseline : ArtboardDocument,
) -> Array[NodeID] {
  baseline.diff(self).modified
}