{%- match callback.closure_fn_type %}
{%- when Some with (fn_type) %}
export type {{ callback.interface_name }} = {{ fn_type }};
{%- when None %}
export interface {{ callback.interface_name }} {
{%- for method in callback.methods %}
  {{ method.ts_name }}({% for param in method.params %}{{ param.name }}: {{ param.ts_type }}{% if !loop.last %}, {% endif %}{% endfor %}){% match method.return_type %}{% when Some with (return_type) %}: {{ return_type }}{% when None %}{% endmatch %};
{%- endfor %}
{%- for method in callback.async_methods %}
  {{ method.ts_name }}({% for param in method.params %}{{ param.name }}: {{ param.ts_type }}{% if !loop.last %}, {% endif %}{% endfor %}): Promise<{% match method.return_type %}{% when Some with (t) %}{{ t }}{% when None %}void{% endmatch %}>;
{%- endfor %}
}
{%- endmatch %}

const _{{ callback.trait_name_snake }}_registry = new Map<number, {{ callback.interface_name }}>();
const _{{ callback.trait_name_snake }}_ref_counts = new Map<number, number>();
const _{{ callback.trait_name_snake }}_local_handles = new Set<number>();
const _{{ callback.trait_name_snake }}_native_handle = Symbol("{{ callback.interface_name }}NativeHandle");
let _{{ callback.trait_name_snake }}_next_id = _callback_handle_js_namespace_start;
const _{{ callback.trait_name_snake }}_finalizer: FinalizationRegistry<number> | null =
  typeof FinalizationRegistry === "undefined"
    ? null
    : new FinalizationRegistry<number>((handle) => {
        (_exports.{{ callback.local_free_fn }} as Function)(handle);
      });

const _{{ callback.trait_name_snake }}_lookup = (handle: number): {{ callback.interface_name }} => {
  const handle_key = _callback_handle_key(handle);
  const impl = _{{ callback.trait_name_snake }}_registry.get(handle_key);
  if (!impl) {
    throw new Error(`Callback handle ${handle_key} not found in {{ callback.interface_name }} registry`);
  }
  return impl;
};

const _{{ callback.trait_name_snake }}_retain = (handle: number): number => {
  const handle_key = _callback_handle_key(handle);
  const currentCount = _{{ callback.trait_name_snake }}_ref_counts.get(handle_key);
  if (currentCount === undefined) {
    throw new Error(`Cannot clone unknown callback handle ${handle_key} in {{ callback.interface_name }} registry`);
  }
  _{{ callback.trait_name_snake }}_ref_counts.set(handle_key, currentCount + 1);
  return handle_key;
};

const _{{ callback.trait_name_snake }}_release = (handle: number): void => {
  const handle_key = _callback_handle_key(handle);
  const currentCount = _{{ callback.trait_name_snake }}_ref_counts.get(handle_key);
  if (currentCount === undefined) {
    throw new Error(`Cannot free unknown callback handle ${handle_key} in {{ callback.interface_name }} registry`);
  }
  if (currentCount === 1) {
    const impl = _{{ callback.trait_name_snake }}_registry.get(handle_key);
    _{{ callback.trait_name_snake }}_ref_counts.delete(handle_key);
    _{{ callback.trait_name_snake }}_registry.delete(handle_key);
    if (_{{ callback.trait_name_snake }}_local_handles.delete(handle_key)) {
      if (impl && typeof impl === "object") {
        _{{ callback.trait_name_snake }}_finalizer?.unregister(impl as object);
      }
      (_exports.{{ callback.local_free_fn }} as Function)(handle_key);
    }
    return;
  }
  _{{ callback.trait_name_snake }}_ref_counts.set(handle_key, currentCount - 1);
};

export function register{{ callback.interface_name }}(impl: {{ callback.interface_name }}): number {
  const existingHandle = (impl as Record<PropertyKey, unknown>)[_{{ callback.trait_name_snake }}_native_handle];
  if (typeof existingHandle === "number") {
    return _{{ callback.trait_name_snake }}_retain(existingHandle);
  }
  const id = _{{ callback.trait_name_snake }}_next_id++;
  _{{ callback.trait_name_snake }}_registry.set(id, impl);
  _{{ callback.trait_name_snake }}_ref_counts.set(id, 1);
  return _callback_handle_key((_exports.{{ callback.create_handle_fn }} as Function)(id) as number);
}

export function unregister{{ callback.interface_name }}(handle: number): void {
  _{{ callback.trait_name_snake }}_release(handle);
}

_callbackImports["__boltffi_callback_{{ callback.trait_name_snake }}_free"] = (handle: number): void => {
  _{{ callback.trait_name_snake }}_release(handle);
};

_callbackImports["__boltffi_callback_{{ callback.trait_name_snake }}_clone"] = (handle: number): number => {
  return _{{ callback.trait_name_snake }}_retain(handle);
};

{%- if callback.closure_fn_type.is_none() %}
class {{ callback.proxy_class_name }} implements {{ callback.interface_name }} {
  private readonly _handle: number;

  constructor(handle: number) {
    const handle_key = _callback_handle_key(handle);
    this._handle = handle_key;
    (this as Record<PropertyKey, unknown>)[_{{ callback.trait_name_snake }}_native_handle] = handle_key;
  }
{%- for method in callback.methods %}

  {{ method.ts_name }}({% for param in method.proxy_params %}{{ param.name }}: {{ param.ts_type }}{% if !loop.last %}, {% endif %}{% endfor %}){% match method.return_type %}{% when Some with (return_type) %}: {{ return_type }}{% when None %}{% endmatch %} {
{%- if !method.proxy_wrapper_code().is_empty() %}
    {{ method.proxy_wrapper_code() }}
{%- endif %}
{%- if method.proxy_cleanup_code().is_empty() %}
{%- if method.proxy_return_route.is_void() %}
    (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %});
{%- elif method.proxy_return_route.is_direct() %}
    return (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %}){{ method.proxy_return_route.ts_cast() }};
{%- elif method.proxy_return_route.is_packed() %}
    const packed = (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %}) as bigint;
    const reader = _module.takePackedBuffer(packed);
    return {{ method.proxy_return_route.decode_expr() }};
{%- elif method.proxy_return_route.is_struct_return_slot() %}
    const __outPtr = _module.exports.boltffi_wasm_alloc({{ method.proxy_return_route.return_slot_size().unwrap() }});
    try {
      (_exports.{{ method.proxy_export_name }} as Function)(__outPtr, this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %});
      return {{ method.proxy_return_route.decode_expr() }};
    } finally {
      _module.exports.boltffi_wasm_free(__outPtr, {{ method.proxy_return_route.return_slot_size().unwrap() }});
    }
{%- else %}
    const packed = (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %}) as bigint;
    return {{ method.proxy_return_route.decode_expr() }};
{%- endif %}
{%- else %}
    try {
{%- if method.proxy_return_route.is_void() %}
      (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %});
{%- elif method.proxy_return_route.is_direct() %}
      return (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %}){{ method.proxy_return_route.ts_cast() }};
{%- elif method.proxy_return_route.is_packed() %}
      const packed = (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %}) as bigint;
      const reader = _module.takePackedBuffer(packed);
      return {{ method.proxy_return_route.decode_expr() }};
{%- elif method.proxy_return_route.is_struct_return_slot() %}
      const __outPtr = _module.exports.boltffi_wasm_alloc({{ method.proxy_return_route.return_slot_size().unwrap() }});
      try {
        (_exports.{{ method.proxy_export_name }} as Function)(__outPtr, this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %});
        return {{ method.proxy_return_route.decode_expr() }};
      } finally {
        _module.exports.boltffi_wasm_free(__outPtr, {{ method.proxy_return_route.return_slot_size().unwrap() }});
      }
{%- else %}
      const packed = (_exports.{{ method.proxy_export_name }} as Function)(this._handle{% if !method.proxy_call_args().is_empty() %}, {{ method.proxy_call_args() }}{% endif %}) as bigint;
      return {{ method.proxy_return_route.decode_expr() }};
{%- endif %}
    } finally {
      {{ method.proxy_cleanup_code() }}
    }
{%- endif %}
  }
{%- endfor %}
}

export function {{ callback.wrap_handle_fn }}(handle: number): {{ callback.interface_name }} {
  const handle_key = _callback_handle_key(handle);
  const existingImpl = _{{ callback.trait_name_snake }}_registry.get(handle_key);
  if (existingImpl) {
    return existingImpl;
  }
  const wrapped = new {{ callback.proxy_class_name }}(handle_key);
  _{{ callback.trait_name_snake }}_registry.set(handle_key, wrapped);
  _{{ callback.trait_name_snake }}_ref_counts.set(handle_key, 1);
  _{{ callback.trait_name_snake }}_local_handles.add(handle_key);
  _{{ callback.trait_name_snake }}_finalizer?.register(wrapped, handle_key, wrapped);
  return wrapped;
}
{%- endif %}

{%- for method in callback.methods %}
{% match method.encoded_return %}
{% when Some with (encoded_return) %}
_callbackImports["{{ method.import_name }}"] = (handle: number, out_ptr: number{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { import_ts_type, .. } %}, {{ param.name }}: {{ import_ts_type }}{% when TsCallbackParamKind::WireEncoded with { .. } %}, {{ param.name }}_ptr: number, {{ param.name }}_len: number{% endmatch %}{% endfor %}): void => {
  const impl = _{{ callback.trait_name_snake }}_lookup(handle);
  {%- for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::WireEncoded with { decode_expr } %}
  const {{ param.name }} = (() => { const reader = new WireReader(_module.readFromMemory({{ param.name }}_ptr, {{ param.name }}_len).buffer as ArrayBuffer); return {{ decode_expr }}; })();{% when TsCallbackParamKind::Primitive with { .. } %}{% endmatch %}{% endfor %}
  const result: {% match method.return_type %}{% when Some with (return_type) %}{{ return_type }}{% when None %}never{% endmatch %} = {% if callback.closure_fn_type.is_some() %}impl({% else %}impl.{{ method.ts_name }}({% endif %}{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { call_expr, .. } %}{{ call_expr }}{% when TsCallbackParamKind::WireEncoded with { .. } %}{{ param.name }}{% endmatch %}{% if !loop.last %}, {% endif %}{% endfor %});
  const writer = _module.allocWriter({{ encoded_return.size_expr }});
  {{ encoded_return.encode_expr }};
  _module.writeBufDescriptor(out_ptr, writer.ptr, writer.len, writer.capacity);
};
{% when None %}
{% match method.direct_import_return_type %}
{% when Some with (direct_import_return_type) %}
_callbackImports["{{ method.import_name }}"] = (handle: number{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { import_ts_type, .. } %}, {{ param.name }}: {{ import_ts_type }}{% when TsCallbackParamKind::WireEncoded with { .. } %}, {{ param.name }}_ptr: number, {{ param.name }}_len: number{% endmatch %}{% endfor %}): {{ direct_import_return_type }} => {
  const impl = _{{ callback.trait_name_snake }}_lookup(handle);
  {%- for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::WireEncoded with { decode_expr } %}
  const {{ param.name }} = (() => { const reader = new WireReader(_module.readFromMemory({{ param.name }}_ptr, {{ param.name }}_len).buffer as ArrayBuffer); return {{ decode_expr }}; })();{% when TsCallbackParamKind::Primitive with { .. } %}{% endmatch %}{% endfor %}
  return {% if callback.closure_fn_type.is_some() %}impl({% else %}impl.{{ method.ts_name }}({% endif %}{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { call_expr, .. } %}{{ call_expr }}{% when TsCallbackParamKind::WireEncoded with { .. } %}{{ param.name }}{% endmatch %}{% if !loop.last %}, {% endif %}{% endfor %});
};
{% when None %}
_callbackImports["{{ method.import_name }}"] = (handle: number{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { import_ts_type, .. } %}, {{ param.name }}: {{ import_ts_type }}{% when TsCallbackParamKind::WireEncoded with { .. } %}, {{ param.name }}_ptr: number, {{ param.name }}_len: number{% endmatch %}{% endfor %}): void => {
  const impl = _{{ callback.trait_name_snake }}_lookup(handle);
  {%- for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::WireEncoded with { decode_expr } %}
  const {{ param.name }} = (() => { const reader = new WireReader(_module.readFromMemory({{ param.name }}_ptr, {{ param.name }}_len).buffer as ArrayBuffer); return {{ decode_expr }}; })();{% when TsCallbackParamKind::Primitive with { .. } %}{% endmatch %}{% endfor %}
  {% if callback.closure_fn_type.is_some() %}impl({% else %}impl.{{ method.ts_name }}({% endif %}{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { call_expr, .. } %}{{ call_expr }}{% when TsCallbackParamKind::WireEncoded with { .. } %}{{ param.name }}{% endmatch %}{% if !loop.last %}, {% endif %}{% endfor %});
};
{% endmatch %}
{% endmatch %}
{%- endfor %}

{%- for method in callback.async_methods %}
_callbackImports["{{ method.start_import_name }}"] = (handle: number, requestId: number{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { import_ts_type, .. } %}, {{ param.name }}: {{ import_ts_type }}{% when TsCallbackParamKind::WireEncoded with { .. } %}, {{ param.name }}_ptr: number, {{ param.name }}_len: number{% endmatch %}{% endfor %}): void => {
  const complete = _exports.{{ method.complete_export_name }} as Function;
  const completeError = (err: unknown): void => {
    const errMsg = err instanceof Error ? err.message : String(err);
    const errWriter = _module.allocWriter(4 + errMsg.length * 3);
    try {
      errWriter.writeString(errMsg);
      complete(requestId, -2, errWriter.ptr, errWriter.len, errWriter.capacity);
    } catch (completeErr) {
      _module.freeWriter(errWriter);
      throw completeErr;
    }
  };
  let impl: {{ callback.interface_name }};
  try {
    impl = _{{ callback.trait_name_snake }}_lookup(handle);
  } catch (err) {
    completeError(err);
    return;
  }
  {%- for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::WireEncoded with { decode_expr } %}
  const {{ param.name }} = (() => { const reader = new WireReader(_module.readFromMemory({{ param.name }}_ptr, {{ param.name }}_len).buffer as ArrayBuffer); return {{ decode_expr }}; })();{% when TsCallbackParamKind::Primitive with { .. } %}{% endmatch %}{% endfor %}
  Promise.resolve()
    .then(() => {
      return {% if callback.closure_fn_type.is_some() %}impl({% else %}impl.{{ method.ts_name }}({% endif %}{% for param in method.params %}{% match param.kind %}{% when TsCallbackParamKind::Primitive with { call_expr, .. } %}{{ call_expr }}{% when TsCallbackParamKind::WireEncoded with { .. } %}{{ param.name }}{% endmatch %}{% if !loop.last %}, {% endif %}{% endfor %});
    })
    .then((result) => {
      {%- match method.return_type %}
      {%- when Some with (t) %}
      {%- match method.encode_expr %}
      {%- when Some with (encode_expr) %}
      const writer = _module.allocWriter({{ method.size_expr.as_ref().unwrap() }});
      try {
        {{ encode_expr }};
        complete(requestId, 0, writer.ptr, writer.len, writer.capacity);
      } catch (err) {
        _module.freeWriter(writer);
        throw err;
      }
      {%- when None %}
      const writer = _module.allocWriter({{ method.direct_size.unwrap() }});
      try {
        writer.{{ method.direct_write_method.as_ref().unwrap() }}({{ method.direct_write_value_expr.as_ref().unwrap() }});
        complete(requestId, 0, writer.ptr, writer.len, writer.capacity);
      } catch (err) {
        _module.freeWriter(writer);
        throw err;
      }
      {%- endmatch %}
      {%- when None %}
      complete(requestId, 0, 0, 0, 0);
      {%- endmatch %}
    })
    .catch((err) => completeError(err));
};
{%- endfor %}
