class RustCallStatus < FFI::Struct
  layout :code,    :int8,
         :error_buf, RustBuffer

  def code
    self[:code]
  end

  def error_buf
    self[:error_buf]
  end

  def to_s
    "RustCallStatus(code=#{self[:code]})"
  end
end

# These match the values from the uniffi::rustcalls module
CALL_SUCCESS = 0
CALL_ERROR = 1
CALL_PANIC = 2
{%- for e in ci.enum_definitions() %}
{% if ci.is_name_used_as_error(e.name()) %}
{% if e.is_flat() %}
class {{ e.name()|class_name_rb }}
    {%- for variant in e.variants() %}
    {{ variant.name()|class_name_rb }} = Class.new StandardError
    {%- endfor %}
{% else %}
module {{ e.name()|class_name_rb }}
  {%- for variant in e.variants() %}
  class {{ variant.name()|class_name_rb }} < StandardError
    def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %})
        {%- for field in variant.fields() %}
        @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
        {%- endfor %}
        super()
      end
    {%- if variant.has_fields() %}

    attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %}
    {% endif %}

    def to_s
     "#{self.class.name}({% for field in variant.fields() %}{{ field.name()|var_name_rb }}=#{@{{ field.name()|var_name_rb }}.inspect}{% if !loop.last %}, {% endif %}{% endfor %})"
    end
  end
  {%- endfor %}
{% endif %}
end
{% endif %}
{%- endfor %}

# Map error modules to the RustBuffer method name that reads them
ERROR_MODULE_TO_READER_METHOD = {
{%- for e in ci.enum_definitions() %}
{% if ci.is_name_used_as_error(e.name()) %}
{%- let typ=ci.get_type(e.name()).unwrap() %}
{%- let canonical_type_name = self::canonical_name(typ.borrow()).borrow()|class_name_rb %}
  {{ e.name()|class_name_rb }} => :read{{ canonical_type_name }},
{% endif %}
{%- endfor %}
}

private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC,
                 :RustCallStatus

def self.consume_buffer_into_error(error_module, rust_buffer)
  rust_buffer.consumeWithStream do |stream|
    reader_method = ERROR_MODULE_TO_READER_METHOD[error_module]
    return stream.send(reader_method)
  end
end

class InternalError < StandardError
end

def self.rust_call(fn_name, *args)
  # Call a rust function
  rust_call_with_error(nil, fn_name, *args)
end

def self.rust_call_with_error(error_module, fn_name, *args)
  # Call a rust function and handle errors
  #
  # Use this when the rust function returns a Result<>.  error_module must be the error_module that corresponds to that Result.


  # Note: RustCallStatus.new zeroes out the struct, which is exactly what we
  # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0,
  # data=NULL))
  status = RustCallStatus.new
  args << status

  result = UniFFILib.public_send(fn_name, *args)

  case status.code
  when CALL_SUCCESS
    result
  when CALL_ERROR
    if error_module.nil?
      status.error_buf.free
      raise InternalError, "CALL_ERROR with no error_module set"
    else
      raise consume_buffer_into_error(error_module, status.error_buf)
    end
  when CALL_PANIC
    # When the rust code sees a panic, it tries to construct a RustBuffer
    # with the message.  But if that code panics, then it just sends back
    # an empty buffer.
    if status.error_buf.len > 0
      raise InternalError, status.error_buf.consumeIntoString()
    else
      raise InternalError, "Rust panic"
    end
  else
    raise InternalError, "Unknown call status: #{status.code}"
  end
end

private_class_method :consume_buffer_into_error
