///|
fn scheme_input(value : @protocol.Value) -> @scheme.Value raise {
  match value {
    Integer(n) => @scheme.integer(n.to_string())
    Boolean(b) => @scheme.Boolean(b)
    Unit => @scheme.Unit
    Decimal(n, e) =>
      @scheme.Tagged("decimal", [@scheme.Text(@protocol.decimal_source(n, e))])
    Float64(bits) =>
      @scheme.Number(@scheme.Number::Real(bits.reinterpret_as_double()))
    Text(units) | Character(units) =>
      @scheme.Tagged(
        if value is Text(_) {
          "sdk-text"
        } else {
          "sdk-character"
        },
        [@scheme.Vector(units.map(n => @scheme.integer(n.to_string())))],
      )
    Sequence(items) => @scheme.list(items.map(scheme_input))
    Product(items) => @scheme.Vector(items.map(scheme_input))
    Record(fields) =>
      @scheme.Record(Map(fields.map(p => (p.0, scheme_input(p.1)))))
    Constructor(owner, tag, items) => {
      if owner == "morphir/SDK:int#int-32" {
        validate_sdk_boundary(value)
      }
      @scheme.Tagged(tag, items.map(scheme_input))
    }
    Optional(None) => @scheme.Tagged("morphir/SDK:maybe#nothing", [])
    Optional(Some(v)) =>
      @scheme.Tagged("morphir/SDK:maybe#just", [scheme_input(v)])
    Outcome(Ok(v)) => @scheme.Tagged("morphir/SDK:result#ok", [scheme_input(v)])
    Outcome(Err(v)) =>
      @scheme.Tagged("morphir/SDK:result#err", [scheme_input(v)])
    ModelError(_) => raise @protocol.Invalid("execution.model_error_argument")
  }
}

///|
fn scheme_output(
  value : @scheme.Value,
  expected : @protocol.ValueType,
  definitions : Array[@protocol.CustomType],
  depth : Int32,
) -> @protocol.Value raise {
  if depth > 64 {
    raise @protocol.Invalid("execution.value_limit")
  }
  fn child(
    v : @scheme.Value,
    t : @protocol.ValueType,
  ) -> @protocol.Value raise {
    scheme_output(v, t, definitions, depth + 1)
  }
  fn units(values : Array[@scheme.Value]) -> Array[Int32] raise {
    values.map(v => {
      guard v is Number(Integer(n)) else {
        raise @protocol.Invalid("execution.evaluator_utf16")
      }
      if n.compare_int(0) < 0 || n.compare_int(65535) > 0 {
        raise @protocol.Invalid("execution.evaluator_utf16")
      }
      n.to_int()
    })
  }
  match (value, expected) {
    (Number(Integer(n)), Int) => @protocol.Integer(n)
    (Boolean(b), Bool) => @protocol.Boolean(b)
    (Unit, Unit) => @protocol.Unit
    (Number(Real(n)), Float64) => @protocol.Float64(n.reinterpret_as_uint64())
    (Tagged("decimal", [Text(source)]), Decimal) => {
      guard @decimal.from_string(source) is Some(d) else {
        raise @protocol.Invalid("execution.evaluator_decimal")
      }
      @protocol.Decimal(d.coefficient, d.exponent)
    }
    (Text(source), Text) =>
      @protocol.Text(
        @sdk.Text::from_string(source).to_units().map(n => n.to_int()),
      )
    (Character(c), Character) =>
      @protocol.Character(
        @sdk.Character::from_char(c).to_units().map(n => n.to_int()),
      )
    (Tagged("sdk-text", [Vector(values)]), Text) =>
      @protocol.Text(units(values))
    (Tagged("sdk-character", [Vector(values)]), Character) =>
      @protocol.Character(units(values))
    (_, Sequence(t)) =>
      @protocol.Sequence(value.to_array().map(v => child(v, t)))
    (Vector(values), Product(types)) => {
      if values.length() != types.length() {
        raise @protocol.Invalid("execution.evaluator_tuple_arity")
      }
      let items = []
      for i, v in values {
        items.push(child(v, types[i]))
      }
      @protocol.Product(items)
    }
    (Record(fields), Record(types)) => {
      if fields.length() != types.length() {
        raise @protocol.Invalid("execution.evaluator_record_fields")
      }
      @protocol.Record(
        types.map(p => {
          guard fields.get(p.0) is Some(v) else {
            raise @protocol.Invalid("execution.evaluator_record_field: " + p.0)
          }
          (p.0, child(v, p.1))
        }),
      )
    }
    (Tagged(tag, values), Custom(owner)) => {
      guard definitions.filter(d => d.name == owner).get(0) is Some(definition) else {
        raise @protocol.Invalid("execution.unresolved_custom_type: " + owner)
      }
      guard definition.constructors.filter(c => c.name == tag).get(0)
        is Some(variant) else {
        raise @protocol.Invalid("execution.evaluator_constructor")
      }
      if values.length() != variant.inputs.length() {
        raise @protocol.Invalid("execution.evaluator_constructor_arity")
      }
      let items = []
      for i, v in values {
        items.push(child(v, variant.inputs[i]))
      }
      @protocol.Constructor(owner, tag, items)
    }
    (Tagged("morphir/SDK:maybe#nothing", []), Optional(_)) =>
      @protocol.Optional(None)
    (Tagged("morphir/SDK:maybe#just", [v]), Optional(t)) =>
      @protocol.Optional(Some(child(v, t)))
    (Tagged("morphir/SDK:result#ok", [v]), Outcome(ok, _)) =>
      @protocol.Outcome(Ok(child(v, ok)))
    (Tagged("morphir/SDK:result#err", [v]), Outcome(_, err)) =>
      @protocol.Outcome(Err(child(v, err)))
    _ => raise @protocol.Invalid("execution.evaluator_type: " + expected.name())
  }
}