///|
/// Incremental JSON object transport. Fields are compared structurally; only
/// changed top-level fields are encoded. Owns a deep copy of retained values,
/// so callers may reuse mutable input maps/arrays. Receivers apply `full`, `set`
/// and `remove` atomically; null is a value, not a deletion.
pub struct JsonObjectPublisher {
  previous : Map[String, Json]
  mut initialized : Bool
}

///|
pub fn JsonObjectPublisher::new() -> JsonObjectPublisher {
  { previous: Map([]), initialized: false, }
}

///|
pub fn JsonObjectPublisher::reset(self : JsonObjectPublisher) -> Unit {
  self.previous.clear()
  self.initialized = false
}

///|
fn copy_json_value(value : Json) -> Json {
  match value {
    Array(items) => Json::array(items.map(copy_json_value))
    Object(fields) => {
      let copy : Map[String, Json] = Map([])
      for key, item in fields {
        copy[key] = copy_json_value(item)
      }
      Json::object(copy)
    }
    _ => value
  }
}

///|
/// First call (including an empty object) sends a full replacement; unchanged
/// subsequent objects return None. `reset` starts a new receiver session.
pub fn JsonObjectPublisher::encode(
  self : JsonObjectPublisher,
  fields : Map[String, Json],
) -> String? {
  let changed : Map[String, Json] = Map([])
  let removed : Array[Json] = []
  for key, value in fields {
    if self.previous.get(key) != Some(value) {
      let copy = copy_json_value(value)
      self.previous[key] = copy
      changed[key] = copy
    }
  }
  for key in self.previous.keys().to_array() {
    if !fields.contains(key) {
      self.previous.remove(key)
      removed.push(Json::string(key))
    }
  }
  if self.initialized && changed.is_empty() && removed.is_empty() {
    return None
  }
  let patch : Json = {
    "full": (!self.initialized).to_json(),
    "set": Json::object(changed),
    "remove": Json::array(removed),
  }
  self.initialized = true
  Some(patch.stringify())
}