///|
/// One eligible base document and its typed source origin. The document is
/// immutable and has an object root. Semantic controls are parsed only after
/// the ordered layers merge.
pub struct ControlLayer {
  priv root : @config.ConfigValue
  priv origin : @config.Origin
}

///|
/// Capture a base document. A non-object root raises the ordinary typed
/// configuration root error.
pub fn ControlLayer::new(value : @config.ConfigValue, origin : @config.Origin) -> ControlLayer raise @config.ConfigError {
  // Validate the source root without loading it. Ordinary config loading drops
  // null fields, but a null profile control must retain its typed shape error.
  ignore(@config.in_memory(value, name=origin.location.unwrap_or(origin.source)))
  { root: value, origin }
}

///|
/// An immutable merge of eligible base documents with original control
/// origins. Later layers win values and individual group definitions.
pub struct ControlLayers {
  priv layers : Array[ControlLayer]
  priv root : @config.ConfigValue
}

///|
/// Merge the layers without interpreting controls. An empty list is a valid
/// base with no controls. Caller arrays are copied.
pub fn ControlLayers::new(layers : Array[ControlLayer]) -> ControlLayers raise @config.ConfigError {
  let mut controls : @ion.Ion? = None
  for layer in layers {
    guard layer.root.to_ion().kind() is Struct(fields) else { continue }
    for field in fields {
      if field.name().text() == Some("profiles") {
        controls = Some(match controls {
          None => field.value()
          Some(previous) => merge_control_values(previous, field.value())
        })
      }
    }
  }
  let fields = controls.map(value => [@ion.field("profiles", value)]).unwrap_or([])
  let root = @config.ConfigValue::from_ion(@ion.Ion::from_fields(fields), origin="profile controls")
  { layers: layers.copy(), root }
}

///|
/// Reserved control values are literal metadata. Merge objects by field and
/// replace other values, including null, before their one semantic parse.
/// Ordinary configuration's null-as-unset rule must not hide a shape failure.
fn merge_control_values(base : @ion.Ion, overlay : @ion.Ion) -> @ion.Ion {
  match (base.kind(), overlay.kind()) {
    (Struct(left), Struct(right)) => {
      let fields = left.copy()
      for field in right {
        match fields.search_by(existing => existing.name().text() == field.name().text()) {
          Some(index) => fields[index] = @ion.Field::from_token(field.name(), merge_control_values(fields[index].value(), field.value()))
          None => fields.push(field)
        }
      }
      @ion.Ion::from_fields(fields).with_annotations(if overlay.has_annotations() { overlay.annotations() } else { base.annotations() })
    }
    _ => overlay
  }
}

///|
/// Containers, including empty objects, keep their declaring source as well
/// as leaves. Config's leaf-only provenance alone cannot recover those origins.
fn ControlLayers::origin(self : ControlLayers, names : Array[String]) -> @config.Origin {
  for layer in self.layers.rev() {
    if layer.contains(names) { return layer.origin }
  }
  // parse_controls asks only about paths present in the merged documents.
  // Merging cannot create a control that no input layer contains.
  abort("A present merged profile control must belong to an input layer")
}

///|
/// Inspect raw values so nulls and empty containers still count as present.
fn ControlLayer::contains(self : ControlLayer, names : Array[String]) -> Bool {
  let mut value = self.root.to_ion()
  for name in names {
    guard value.kind() is Struct(fields) else { return false }
    guard fields.iter().find_first(field => field.name().text() == Some(name)) is Some(field) else { return false }
    value = field.value()
  }
  true
}

///|
/// Resolve prepared activation against merged base controls. Every base stays
/// eligible even when a later base replaces all of its control values.
pub fn resolve_layers(policy : Policy, input : SelectionInput, layers : ControlLayers) -> @config.ProfileResolution raise @config.ConfigError {
  let controls = if input.disabled() { empty_controls() } else {
    parse_controls(layers.root, names => layers.origin(names))
  }
  resolve_prepared(policy, input, controls, authorities=layers.layers.map(layer => layer.origin))
}