///|
/// Apply RFC 7396-style JSON Merge Patch semantics. Object members are merged
/// recursively, null removes a member, and every other value replaces target.
pub fn apply_merge_patch(target : JsonValue, patch : JsonValue) -> JsonValue {
  match patch {
    Object(patch_entries) => {
      let target_entries = match target {
        Object(entries) => entries
        _ => []
      }
      let output : Array[(String, JsonValue)] = []
      for entry in target_entries {
        output.push(entry)
      }
      for entry in patch_entries {
        if entry.1 is Null {
          remove_object_key(output, entry.0)
        } else {
          let existing = find_object_key(output, entry.0)
          let merged = match existing {
            Some(value) => apply_merge_patch(value, entry.1)
            None => apply_merge_patch(Null, entry.1)
          }
          replace_object_key(output, entry.0, merged)
        }
      }
      Object(output)
    }
    _ => patch
  }
}

///|
pub fn merge_patch_documents(
  target : String,
  patch : String,
) -> Result[String, String] {
  let target_value = match parse(target) {
    Ok(value) => value
    Err(error) => return Err("target: " + error.message())
  }
  let patch_value = match parse(patch) {
    Ok(value) => value
    Err(error) => return Err("patch: " + error.message())
  }
  match canonicalize(apply_merge_patch(target_value, patch_value)) {
    Ok(output) => Ok(output)
    Err(error) => Err(error.message())
  }
}

///|
fn find_object_key(
  entries : Array[(String, JsonValue)],
  key : String,
) -> JsonValue? {
  for entry in entries {
    if entry.0 == key {
      return Some(entry.1)
    }
  }
  None
}

///|
fn remove_object_key(
  entries : Array[(String, JsonValue)],
  key : String,
) -> Unit {
  let filtered : Array[(String, JsonValue)] = []
  for entry in entries {
    if entry.0 != key {
      filtered.push(entry)
    }
  }
  entries.clear()
  for entry in filtered {
    entries.push(entry)
  }
}

///|
fn replace_object_key(
  entries : Array[(String, JsonValue)],
  key : String,
  value : JsonValue,
) -> Unit {
  let mut found = false
  let mut index = 0
  while index < entries.length() {
    if entries[index].0 == key {
      entries[index] = (key, value)
      found = true
    }
    index = index + 1
  }
  if !found {
    entries.push((key, value))
  }
}