///|
pub(all) struct ResourcePatch {
  path : String
  value : Json?
  remove : Bool
} derive(Debug, Eq, ToJson)

///|
pub(all) struct OperationResult {
  success : Bool
  resource : Json
  issues : Array[Issue]
} derive(Debug, Eq, ToJson)

///|
pub fn ResourcePatch::set(path : String, value : Json) -> ResourcePatch {
  { path, value: Some(value), remove: false }
}

///|
pub fn ResourcePatch::remove(path : String) -> ResourcePatch {
  { path, value: None, remove: true }
}

///|
pub fn resource_set_field(
  value : Json,
  field : String,
  replacement : Json,
) -> Json {
  match value {
    Object(fields) => {
      fields[field] = replacement
      Json::object(fields)
    }
    _ => value
  }
}

///|
pub fn resource_remove_field(value : Json, field : String) -> Json {
  match value {
    Object(fields) => {
      fields.remove(field)
      Json::object(fields)
    }
    _ => value
  }
}

///|
pub fn apply_resource_patch(value : Json, patch : ResourcePatch) -> Json {
  if patch.remove {
    resource_remove_field(value, patch.path)
  } else {
    match patch.value {
      Some(replacement) => resource_set_field(value, patch.path, replacement)
      None => value
    }
  }
}

///|
pub fn apply_resource_patches(
  value : Json,
  patches : Array[ResourcePatch],
) -> Json {
  patches.fold(init=value, fn(current, patch) {
    apply_resource_patch(current, patch)
  })
}

///|
pub fn update_resource_status(value : Json, status : String) -> Json {
  resource_set_field(value, "status", Json::string(status))
}

///|
pub fn update_resource_id(value : Json, id : String) -> Json {
  resource_set_field(value, "id", Json::string(id))
}

///|
pub fn operation_validate(value : Json, profile? : Profile) -> OperationResult {
  let report = match profile {
    None => validate_resource(value)
    Some(profile) => validate_with_profile(value, profile)
  }
  {
    success: report_error_count(report) == 0,
    resource: value,
    issues: report.issues,
  }
}

///|
pub fn operation_normalize(value : Json) -> OperationResult {
  { success: true, resource: canonical_resource(value), issues: [] }
}

///|
pub fn operation_redact(
  value : Json,
  policy : PrivacyPolicy,
) -> OperationResult {
  { success: true, resource: redact_by_policy(value, policy), issues: [] }
}

///|
pub fn upsert_resource(values : Array[Json], value : Json) -> Array[Json] {
  let output : Array[Json] = []
  let target = bundle_reference_key(value)
  let mut replaced = false
  for existing in values {
    if target is Some(key) && bundle_reference_key(existing) == Some(key) {
      output.push(value)
      replaced = true
    } else {
      output.push(existing)
    }
  }
  if !replaced {
    output.push(value)
  }
  output
}

///|
pub fn delete_resource(
  values : Array[Json],
  kind : String,
  id : String,
) -> Array[Json] {
  values.filter(fn(value) {
    !(resource_type(value) == Some(kind) &&
    field_string(value, "id") == Some(id))
  })
}

///|
pub fn find_or_create_resource(
  values : Array[Json],
  kind : String,
  id : String,
) -> (Array[Json], Json) {
  match find_first_resource(values, kind, id) {
    Some(value) => (values, value)
    None => {
      let value = Json::object(
        Map([("resourceType", Json::string(kind)), ("id", Json::string(id))]),
      )
      (upsert_resource(values, value), value)
    }
  }
}