{%- let interface_base_class = int.interface_base_class %}
{% include "InterfaceBaseClass.sys.mjs" %}

{{ int.js_docstring }}
export class {{ int.js_class_name }} extends {{ int.interface_base_class.name }} {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        super();
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an int using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!(opts[constructUniffiObject] instanceof UniFFIPointer)) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }

    {%- for cons in int.constructors %}
    {%- let callable = cons.callable %}
    {{ cons.js_docstring|indent(4) }}
    static {% if cons.callable.is_js_async %}async {% endif %}{{ cons.name }}({% filter indent(8) %}{% include "js/CallableArgs.sys.mjs" %}{% endfilter %}) {
       {% filter indent(8) %}{% include "js/CallableBody.sys.mjs" %}{% endfilter %}
    }
    {%- endfor %}

    {%- for meth in int.methods %}
    {%- let callable = meth.callable %}

    {{ meth.js_docstring|indent(4) }}
    {% if meth.callable.is_js_async %}async {% endif %}{{ meth.name }}({% filter indent(8) %}{% include "js/CallableArgs.sys.mjs" %}{% endfilter %}) {
       {% filter indent(8) %}{% include "js/CallableBody.sys.mjs" %}{% endfilter %}
    }
    {%- endfor %}

}

{% match int.vtable -%}
{% when None -%}
// Export the FFIConverter object to make external types work.
export class {{ int.self_type.ffi_converter }} extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new {{ int.js_class_name }}(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a '{{ int.js_class_name }}' instance");
        }
        return ptr;
    }

    static lowerReceiver(value) {
        // This works exactly the same as lower for non-trait interfaces
        return this.lower(value);
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointer({{ int.object_id }}));
    }

    static write(dataStream, value) {
        dataStream.writePointer({{ int.object_id }}, this.lower(value));
    }

    static computeSize(value) {
        return 8;
    }
}
{% when Some(vtable) -%}
// FfiConverter for a trait interface.  This is a hybrid of the FFIConverter regular interfaces and
// for callback interfaces.
//
// Export the FFIConverter object to make external types work.
export class {{ int.self_type.ffi_converter }} extends FfiConverter {
    static lift(handle) {
        if (handle instanceof UniFFIPointer) {
          // Rust handle.  Construct an object from it
          const opts = {};
          opts[constructUniffiObject] = handle;
          return new {{ int.js_class_name }}(opts);
        } else {
          // JS handle.  Get the JS object from the callback handler
          return {{ vtable.js_handler_var }}.takeCallbackObj(handle)
        }
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (ptr instanceof UniFFIPointer) {
          // Rust-implemented interface, return the ptr.  The C++ code will clone it.
          return ptr;
        } else {
          // JS-implemented interface, store the object in the handle map and return the handle
          if (!(value instanceof {{ int.interface_base_class.name }})) {
              throw new UniFFITypeError("expected '{{ int.interface_base_class.name }}' subclass");
          }
          return {{ vtable.js_handler_var }}.storeCallbackObj(value)
        }
    }

    // lowerReceiver is used when calling methods on an interface we got from Rust, 
    // it treats value like a regular interface.
    static lowerReceiver(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a '{{ int.js_class_name }}' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readHandleOrPointer({{ int.object_id }}))
    }

    static write(dataStream, value) {
        if (value[uniffiObjectPtr] instanceof UniFFIPointer) {
          // Rust-implemented interface, return the ptr.
          dataStream.writePointer({{ int.object_id }}, this.lower(value));
        } else {
          dataStream.writeInt64(this.lower(value))
        }
    }

    static computeSize(value) {
        return 8;
    }
}

{% include "CallbackInterfaceHandler.sys.mjs" -%}
{% endmatch %}
