///|
pub(all) struct DocumentSnapshotChange {
  document : String
  source : String
  kind : String
  excerpt : String?
  change : String
  previous : String?
  current : String?
} derive(ToJson)

///|
pub extend DocumentSnapshotChange with ToJson::{to_json}

///|
fn document_snapshot_key(entry : DocumentSnapshotEntry) -> String {
  @json.to_json([
    entry.document,
    entry.source,
    entry.kind,
    @json.to_json(entry.excerpt).stringify(),
  ]).stringify()
}

///|
/// Compare complete verified snapshots. Line movement is ignored, repeated
/// references share one identity, and excerpt edits are removal + addition.
pub fn compare_document_snapshots(
  previous : DocumentSnapshot,
  current : DocumentSnapshot,
) -> Array[DocumentSnapshotChange] {
  let old : Map[String, DocumentSnapshotEntry] = Map([])
  let present : Map[String, Bool] = Map([])
  let changes : Array[DocumentSnapshotChange] = []
  for entry in previous.entries {
    let key = document_snapshot_key(entry)
    if old.get(key) is None {
      old.set(key, entry)
    }
  }
  for entry in current.entries {
    let key = document_snapshot_key(entry)
    if present.get(key) is Some(_) {
      continue
    }
    present.set(key, true)
    let before = old.get(key)
    if before is Some(value) && value.observed == entry.observed {
      continue
    }
    changes.push({
      document: entry.document,
      source: entry.source,
      kind: entry.kind,
      excerpt: entry.excerpt,
      change: if before is Some(_) {
        "changed"
      } else {
        "added"
      },
      previous: before.map(value => value.observed),
      current: Some(entry.observed),
    })
  }
  for entry in previous.entries {
    let key = document_snapshot_key(entry)
    if present.get(key) is Some(_) {
      continue
    }
    present.set(key, true)
    changes.push({
      document: entry.document,
      source: entry.source,
      kind: entry.kind,
      excerpt: entry.excerpt,
      change: "removed",
      previous: Some(entry.observed),
      current: None,
    })
  }
  changes
}