///|
// A scalar fallback seals an object against subsequent fallbacks. This state is
// part of the value, so it survives embedding, editing, resolution and copying.
fn rebuild_object(original : Value, fields : Map[String, Value]) -> Value {
  if original is SealedObject(_) {
    SealedObject(fields)
  } else {
    Object(fields)
  }
}

///|
fn completely_resolved(value : Value) -> Bool {
  match value {
    Text(_) | Bare(_) | Number(_) | Boolean(_) | Null => true
    Object(fields) | SealedObject(fields) =>
      fields.values().all(completely_resolved)
    List(items) => items.iter().all(completely_resolved)
    _ => false
  }
}

///|
fn ignores_fallbacks(value : Value) -> Bool {
  match value {
    SealedObject(_) => true
    Object(_) => false
    Bound(_, _, body) => ignores_fallbacks(body)
    DelayedMerge(low, _) => ignores_fallbacks(low)
    _ => completely_resolved(value)
  }
}

///|
// A delayed stack is evaluated from its newest binding. Keep its binary form
// left-associated when a whole previously parsed stack receives a fallback.
fn append_delayed(low : Value, high : Value) -> Value {
  match high {
    DelayedMerge(previous, head) =>
      DelayedMerge(append_delayed(low, previous), head)
    _ => DelayedMerge(low, high)
  }
}

///|
fn only_path(value : Value, parts : Array[String], at : Int) -> Value {
  if at == parts.length() {
    return value
  }
  match value {
    Object(fields) | SealedObject(fields) => {
      let selected = Map([])
      if fields.get(parts[at]) is Some(child) {
        selected[parts[at]] = only_path(child, parts, at + 1)
      }
      rebuild_object(value, selected)
    }
    _ => value
  }
}

///|
/// Merge arbitrary values, preserving scalar fallback barriers and raw history.
/// Both inputs and the result own independent mutable maps and arrays.
pub fn value_with_fallback(
  value : Value,
  fallback : Value,
) -> Value raise ParseError {
  let work = tree_work()
  let high = clone_tree(value, 0, work)
  let low = clone_tree(fallback, 0, work)
  merge(low, high)
}