///|
/// Applies host overrides atomically and returns a fresh validated invocation.
/// Unknown names, incompatible kinds and invalid positional cardinality raise
/// CliError. Unchanged values preserve their sources; changed values use Override.
/// Clearing an optional positional makes it absent, with no effective source.
///
/// ```mbt check
/// test {
///   let app = @cmder.cli(name="app", options=[@cmder.int("count", default=1)])
///   guard app.parse(argv=[]) is Parsed(call) else { fail("parse") }
///   let edited = call.with_overrides({ "count": @cmder.ArgumentValue::Integer(3) })
///   assert_true(edited.context.get_int("count") == Some(3))
///   assert_true(call.context.get_int("count") == Some(1))
///   assert_true(edited.source_of("count") == Some(@cmder.ValueSource::Override))
/// }
/// ```
pub fn Invocation::with_overrides(
  self : Invocation,
  overrides : Map[String, ArgumentValue],
) -> Invocation raise CliError {
  let flags = self.context.flags.copy()
  let values = self.context.values.copy()
  let sources = self.context.sources.copy()
  for name, value in overrides {
    guard self.context.specs.get(name) is Some(spec) else {
      raise CliError::UnknownArgument(name)
    }
    match (spec, value) {
      (Option({ kind: BoolOpt, .. }), Flag(value)) => flags[name] = value
      (Option({ kind: StringOpt(_), .. }), Text(value)) =>
        values[name] = [value]
      (Option({ kind: IntOpt(_), .. }), Integer(value)) =>
        values[name] = [value.to_string()]
      (Option({ kind: IntOpt(_), .. }), Text(value)) => {
        ignore(parse_integer_value(name, value))
        values[name] = [value]
      }
      (Positional(pos, multiple~), Positionals(items)) => {
        if (pos.required && items.is_empty()) ||
          (!multiple && items.length() > 1) {
          raise CliError::InvalidOverride(
            name, "invalid number of positional values",
          )
        }
        if items.is_empty() {
          values.remove(name)
          sources.remove(name)
          continue
        }
        values[name] = items[:].to_owned()
      }
      _ =>
        raise CliError::InvalidOverride(
          name, "value does not match the argument kind",
        )
    }
    sources[name] = Override
  }
  { ..self, context: { ..self.context, flags, values, sources, }, }
}