{{ crate::render::typescript::templates::ts_doc_block(cls.doc, "") -}}
const _{{ cls.class_name }}Finalizer: FinalizationRegistry<number> | null =
  typeof FinalizationRegistry === "undefined"
    ? null
    : new FinalizationRegistry<number>((handle) => {
        (_exports.{{ cls.ffi_free }} as Function)(handle);
      });

export class {{ cls.class_name }} {
  private _handle: number;
  private _disposed: boolean;

  private constructor(handle: number) {
    this._handle = handle;
    this._disposed = false;
    _{{ cls.class_name }}Finalizer?.register(this, handle, this);
  }

  static _fromHandle(handle: number): {{ cls.class_name }} {
    if (handle === 0) {
      throw new Error("{{ cls.class_name }} received a null handle");
    }
    return new {{ cls.class_name }}(handle);
  }

  [Symbol.dispose](): void {
    this.dispose();
  }

  dispose(): void {
    if (this._disposed) {
      return;
    }
    this._disposed = true;
    _{{ cls.class_name }}Finalizer?.unregister(this);
    (_exports.{{ cls.ffi_free }} as Function)(this._handle);
    this._handle = 0;
  }

  private _assertNotDisposed(): void {
    if (this._disposed) {
      throw new Error("{{ cls.class_name }} has been disposed");
    }
  }
{%- for constructor in cls.constructors %}

{{ crate::render::typescript::templates::ts_doc_block(constructor.doc, "  ") }}  static {{ constructor.ts_name }}({% for param in constructor.params %}{{ param.name }}: {{ param.ts_type }}{% if !loop.last %}, {% endif %}{% endfor %}): {{ cls.class_name }}{% if constructor.returns_nullable_handle %} | null{% endif %} {
{%- if !constructor.wrapper_code().is_empty() %}
    {{ constructor.wrapper_code() }}
{%- endif %}
{%- if constructor.cleanup_code().is_empty() %}
    const handle = (_exports.{{ constructor.ffi_name }} as Function)({{ constructor.ffi_call_args() }});
{%- if constructor.returns_nullable_handle %}
    if (handle === 0) {
      return null;
    }
{%- endif %}
    return {{ cls.class_name }}._fromHandle(handle);
{%- else %}
    try {
      const handle = (_exports.{{ constructor.ffi_name }} as Function)({{ constructor.ffi_call_args() }});
{%- if constructor.returns_nullable_handle %}
      if (handle === 0) {
        return null;
      }
{%- endif %}
      return {{ cls.class_name }}._fromHandle(handle);
    } finally {
      {{ constructor.cleanup_code() }}
    }
{%- endif %}
  }
{%- endfor %}
{%- for method in cls.methods %}

{{ crate::render::typescript::templates::ts_doc_block(method.doc, "  ") }}  {% if method.is_static %}static {% endif %}{% if method.is_async() %}async {% endif %}{{ method.ts_name }}({% for param in method.params %}{{ param.name }}: {{ param.ts_type }}{% if !loop.last %}, {% endif %}{% endfor %}): {% if method.is_async() %}Promise<{% match method.return_type %}{% when Some with (t) %}{{ t }}{% when None %}void{% endmatch %}>{% else %}{% match method.return_type %}{% when Some with (t) %}{{ t }}{% when None %}void{% endmatch %}{% endif %} {
{%- if !method.is_static %}
    this._assertNotDisposed();
{%- endif %}
{%- if !method.wrapper_code().is_empty() %}
    {{ method.wrapper_code() }}
{%- endif %}
{%- if method.cleanup_code().is_empty() %}
{%- match method.mode %}
{%- when TsClassMethodMode::Sync with (sync_method) %}
{%- if sync_method.return_route.is_void() %}
    (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
{%- elif sync_method.return_route.is_direct() %}
{%- if let Some(handle_return) = method.return_handle %}
    const result = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
{%- if handle_return.nullable %}
    if (result === 0) {
      return null;
    }
{%- endif %}
    return {{ handle_return.class_name }}._fromHandle(result);
{%- elif let Some(callback_return) = method.return_callback %}
    const result = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
{%- if callback_return.nullable %}
    if (result === 0) {
      return null;
    }
{%- endif %}
    return {{ callback_return.wrap_fn }}(result);
{%- else %}
    return (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}){{ sync_method.return_route.ts_cast() }};
{%- endif %}
{%- elif sync_method.return_route.is_struct_return_slot() %}
    const __outPtr = _module.exports.boltffi_wasm_alloc({{ sync_method.return_route.return_slot_size().unwrap() }});
    try {
      (_exports.{{ method.ffi_name }} as Function)({% if method.ffi_call_args().is_empty() %}__outPtr{% else %}__outPtr, {{ method.ffi_call_args() }}{% endif %});
      return {{ sync_method.return_route.decode_expr() }};
    } finally {
      _module.exports.boltffi_wasm_free(__outPtr, {{ sync_method.return_route.return_slot_size().unwrap() }});
    }
{%- elif sync_method.return_route.is_nan_boxed_optional() %}
    const packed = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}) as number;
    return {{ sync_method.return_route.decode_expr() }};
{%- elif sync_method.return_route.is_packed() %}
    const packed = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}) as bigint;
    const reader = _module.takePackedBuffer(packed);
    return {{ sync_method.return_route.decode_expr() }};
{%- else %}
    const packed = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}) as bigint;
    return {{ sync_method.return_route.decode_expr() }};
{%- endif %}
{%- when TsClassMethodMode::Async with (async_method) %}
    const handle = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
    const awaitedHandle = await _module.asyncManager.pollAsync(
      handle,
      (futureHandle: number) => (_exports.{{ async_method.poll_sync_ffi_name }} as Function)(futureHandle),
      (futureHandle: number) => (_exports.{{ async_method.panic_message_ffi_name }} as Function)(futureHandle),
      (futureHandle: number) => (_exports.{{ async_method.free_ffi_name }} as Function)(futureHandle)
    );
{%- if async_method.return_route.is_async_scalar() %}
    try {
      const result = _module.completeAsync((statusPtr: number) => (_exports.{{ async_method.complete_ffi_name }} as Function)(awaitedHandle, statusPtr)){{ async_method.return_route.ts_cast() }};
      return result;
    } finally {
      (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
    }
{%- elif async_method.return_route.is_packed() %}
    const outPtr = _module.allocBufDescriptor();
    let completeCompleted = false;
    try {
      _module.completeAsync((statusPtr: number) => (_exports.{{ async_method.complete_ffi_name }} as Function)(outPtr, awaitedHandle, statusPtr));
      completeCompleted = true;
      const reader = _module.readerFromBuf(outPtr);
      const result = {{ async_method.return_route.decode_expr() }};
{%- if let Some(handle_return) = method.return_handle %}
{%- if handle_return.nullable %}
      if (result === 0) {
        return null;
      }
{%- endif %}
      return {{ handle_return.class_name }}._fromHandle(result);
{%- else %}
      return result;
{%- endif %}
    } finally {
      if (completeCompleted) {
        _module.freeBuf(outPtr);
      } else {
        _module.freeBufDescriptor(outPtr);
      }
      (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
    }
{%- elif async_method.return_route.is_void() %}
    try {
      _module.completeAsync((statusPtr: number) => (_exports.{{ async_method.complete_ffi_name }} as Function)(awaitedHandle, statusPtr));
    } finally {
      (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
    }
{%- elif async_method.return_route.is_direct() %}
    (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
    throw new Error("Unsupported async return route: Direct{{ async_method.return_route.ts_cast() }}");
{%- else %}
    (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
    throw new Error("Unsupported async return route: RawPacked {{ async_method.return_route.decode_expr() }}");
{%- endif %}
{%- endmatch %}
{%- else %}
{%- match method.mode %}
{%- when TsClassMethodMode::Sync with (sync_method) %}
    try {
{%- if sync_method.return_route.is_void() %}
      (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
{%- elif sync_method.return_route.is_direct() %}
{%- if let Some(handle_return) = method.return_handle %}
      const result = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
{%- if handle_return.nullable %}
      if (result === 0) {
        return null;
      }
{%- endif %}
      return {{ handle_return.class_name }}._fromHandle(result);
{%- elif let Some(callback_return) = method.return_callback %}
      const result = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
{%- if callback_return.nullable %}
      if (result === 0) {
        return null;
      }
{%- endif %}
      return {{ callback_return.wrap_fn }}(result);
{%- else %}
      return (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}){{ sync_method.return_route.ts_cast() }};
{%- endif %}
{%- elif sync_method.return_route.is_struct_return_slot() %}
      const __outPtr = _module.exports.boltffi_wasm_alloc({{ sync_method.return_route.return_slot_size().unwrap() }});
      try {
        (_exports.{{ method.ffi_name }} as Function)({% if method.ffi_call_args().is_empty() %}__outPtr{% else %}__outPtr, {{ method.ffi_call_args() }}{% endif %});
        return {{ sync_method.return_route.decode_expr() }};
      } finally {
        _module.exports.boltffi_wasm_free(__outPtr, {{ sync_method.return_route.return_slot_size().unwrap() }});
      }
{%- elif sync_method.return_route.is_nan_boxed_optional() %}
      const packed = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}) as number;
      return {{ sync_method.return_route.decode_expr() }};
{%- elif sync_method.return_route.is_packed() %}
      const packed = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}) as bigint;
      const reader = _module.takePackedBuffer(packed);
      return {{ sync_method.return_route.decode_expr() }};
{%- else %}
      const packed = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }}) as bigint;
      return {{ sync_method.return_route.decode_expr() }};
{%- endif %}
    } finally {
      {{ method.cleanup_code() }}
    }
{%- when TsClassMethodMode::Async with (async_method) %}
    try {
      const handle = (_exports.{{ method.ffi_name }} as Function)({{ method.ffi_call_args() }});
      const awaitedHandle = await _module.asyncManager.pollAsync(
        handle,
        (futureHandle: number) => (_exports.{{ async_method.poll_sync_ffi_name }} as Function)(futureHandle),
        (futureHandle: number) => (_exports.{{ async_method.panic_message_ffi_name }} as Function)(futureHandle),
        (futureHandle: number) => (_exports.{{ async_method.free_ffi_name }} as Function)(futureHandle)
      );
{%- if async_method.return_route.is_async_scalar() %}
      try {
        const result = _module.completeAsync((statusPtr: number) => (_exports.{{ async_method.complete_ffi_name }} as Function)(awaitedHandle, statusPtr)){{ async_method.return_route.ts_cast() }};
        return result;
      } finally {
        (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
      }
{%- elif async_method.return_route.is_packed() %}
      const outPtr = _module.allocBufDescriptor();
      let completeCompleted = false;
      try {
        _module.completeAsync((statusPtr: number) => (_exports.{{ async_method.complete_ffi_name }} as Function)(outPtr, awaitedHandle, statusPtr));
        completeCompleted = true;
        const reader = _module.readerFromBuf(outPtr);
        const result = {{ async_method.return_route.decode_expr() }};
{%- if let Some(handle_return) = method.return_handle %}
{%- if handle_return.nullable %}
        if (result === 0) {
          return null;
        }
{%- endif %}
        return {{ handle_return.class_name }}._fromHandle(result);
{%- else %}
        return result;
{%- endif %}
      } finally {
        if (completeCompleted) {
          _module.freeBuf(outPtr);
        } else {
          _module.freeBufDescriptor(outPtr);
        }
        (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
      }
{%- elif async_method.return_route.is_void() %}
      try {
        _module.completeAsync((statusPtr: number) => (_exports.{{ async_method.complete_ffi_name }} as Function)(awaitedHandle, statusPtr));
      } finally {
        (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
      }
{%- elif async_method.return_route.is_direct() %}
      (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
      throw new Error("Unsupported async return route: Direct{{ async_method.return_route.ts_cast() }}");
{%- else %}
      (_exports.{{ async_method.free_ffi_name }} as Function)(awaitedHandle);
      throw new Error("Unsupported async return route: RawPacked {{ async_method.return_route.decode_expr() }}");
{%- endif %}
    } finally {
      {{ method.cleanup_code() }}
    }
{%- endmatch %}
{%- endif %}
  }
{%- endfor %}
}
