///|
/// Immutable-by-interface evaluation context. Constructors and accessors copy
/// JSON recursively; neither a caller's map nor a returned map aliases storage.
pub struct EvaluationContext {
  priv targeting_key : String?
  priv attributes : Map[String, Json]
}

///|
pub fn EvaluationContext::empty() -> EvaluationContext {
  { targeting_key: None, attributes: Map([]), }
}

///|
/// Keys, including the empty string, are allowed. Validate the whole envelope
/// so to_json() also satisfies the maximum JSON depth of 64.
pub fn EvaluationContext::from_attributes(
  attributes : Map[String, Json],
) -> Result[EvaluationContext, SdkError] {
  match validate_json(Json::object(attributes)) {
    Err(error) => Err(error)
    Ok(_) =>
      Ok({
        targeting_key: None,
        attributes: copy_context_attributes(attributes),
      })
  }
}

///|
/// A replacement, not an in-place mutation. Empty targeting keys are legal.
pub fn EvaluationContext::with_targeting_key(
  self : EvaluationContext,
  targeting_key : String,
) -> EvaluationContext {
  {
    targeting_key: Some(targeting_key),
    attributes: copy_context_attributes(self.attributes),
  }
}

///|
pub fn EvaluationContext::without_targeting_key(
  self : EvaluationContext,
) -> EvaluationContext {
  { targeting_key: None, attributes: copy_context_attributes(self.attributes), }
}

///|
/// Validate before copying or modifying anything; failure leaves self intact.
pub fn EvaluationContext::with_attribute(
  self : EvaluationContext,
  key : String,
  value : Json,
) -> Result[EvaluationContext, SdkError] {
  match validate_json(Json::object(Map([(key, value)]))) {
    Err(error) => Err(error)
    Ok(_) => {
      let attributes = copy_context_attributes(self.attributes)
      attributes.set(key, copy_json(value))
      Ok({ targeting_key: self.targeting_key, attributes, })
    }
  }
}

///|
pub fn EvaluationContext::without_attribute(
  self : EvaluationContext,
  key : String,
) -> EvaluationContext {
  let attributes = copy_context_attributes(self.attributes)
  attributes.remove(key)
  { targeting_key: self.targeting_key, attributes, }
}

///|
pub fn EvaluationContext::get(self : EvaluationContext, key : String) -> Json? {
  match self.attributes.get(key) {
    Some(value) => Some(copy_json(value))
    None => None
  }
}

///|
pub fn EvaluationContext::targeting_key(self : EvaluationContext) -> String? {
  self.targeting_key
}

///|
pub fn EvaluationContext::attributes(
  self : EvaluationContext,
) -> Map[String, Json] {
  copy_context_attributes(self.attributes)
}

///|
pub fn EvaluationContext::copy(self : EvaluationContext) -> EvaluationContext {
  {
    targeting_key: self.targeting_key,
    attributes: copy_context_attributes(self.attributes),
  }
}

///|
/// Later attributes replace earlier values atomically, without recursive JSON
/// object merging. A present later targeting key wins, including an empty key;
/// an absent later targeting key preserves the earlier key.
pub fn EvaluationContext::merge(
  self : EvaluationContext,
  later : EvaluationContext,
) -> EvaluationContext {
  let attributes = copy_context_attributes(self.attributes)
  for key, value in later.attributes {
    attributes.set(key, copy_json(value))
  }
  let targeting_key = match later.targeting_key {
    Some(_) => later.targeting_key
    None => self.targeting_key
  }
  { targeting_key, attributes, }
}

///|
fn copy_context_attributes(source : Map[String, Json]) -> Map[String, Json] {
  let result = Map([])
  for key, value in source {
    result.set(key, copy_json(value))
  }
  result
}