package {{ package }};

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
{% if needs_list %}
import java.util.List;
{% endif %}
{% if needs_map %}
import java.util.Map;
{% endif %}
import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Allocates Panama FFM upcall stubs for an I{{ trait_pascal }} implementation,
 * assembles the C vtable in native memory, and provides static
 * register{{ trait_pascal }}/unregister{{ trait_pascal }} helpers.
 */
@SuppressWarnings("PMD")
public final class {{ bridge_class }} implements AutoCloseable {

    private static final Linker LINKER = Linker.nativeLinker();
    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
    private static final ObjectMapper JSON = new ObjectMapper();

    /** Live registry — keeps Arenas and upcall stubs alive past the register call. */
    private static final ConcurrentHashMap<String, {{ bridge_class }}>
            {{ registry_field }} = new ConcurrentHashMap<>();

    // C vtable: {{ num_vtable_fields }} fields ({{ num_super_slots }} plugin methods + {{ num_methods }} trait methods + free_string + free_user_data)
    private static final MemoryLayout VTABLE_LAYOUT = MemoryLayout.structLayout(
{% for field in vtable_layout_fields_list %}
            {{ field }}{{ "," if not loop.last }}
{% endfor %}
    );
    private static final long VTABLE_SIZE = VTABLE_LAYOUT.byteSize();

    private final Arena arena;
    private final MemorySegment vtable;
    private final I{{ trait_pascal }} impl;

    {{ bridge_class }}(final I{{ trait_pascal }} impl) {
        this.impl = impl;
        this.arena = Arena.ofShared();
        this.vtable = arena.allocate(VTABLE_SIZE);
        try {
            initializeStubs();
        } catch (ReflectiveOperationException e) {
            arena.close();
            throw new RuntimeException("Failed to create trait bridge stubs", e);
        }
    }

    private void initializeStubs() throws ReflectiveOperationException {
        // Each stub is allocated by its own helper to keep this dispatcher and each
        // helper well under checkstyle's MethodLength cap, even for traits with
        // many methods (e.g. OcrBackend has ~15 stubs).
{% for stub in stubs %}
        initStub{{ stub.pascal_name }}({{ loop.index0 }}L * ValueLayout.ADDRESS.byteSize());
{% endfor %}
    }

{% for stub in stubs %}
    private void initStub{{ stub.pascal_name }}(long offset) throws ReflectiveOperationException {
        var {{ stub.var_name }} = LINKER.upcallStub(LOOKUP.bind(this, "{{ stub.handle_name }}",
            MethodType.methodType({{ stub.return_type }}, {{ stub.method_type_params }})),
{% if stub.returns_void %}            FunctionDescriptor.ofVoid({{ stub.descriptor_params }}),
{% else %}
            FunctionDescriptor.of({{ stub.descriptor_return }}, {{ stub.descriptor_params }}),
{% endif %}
            arena);
        vtable.set(ValueLayout.ADDRESS, offset, {{ stub.var_name }});
    }

{% endfor %}

    MemorySegment vtableSegment() { return vtable; }

{% if lifecycle_methods %}
{% for method in lifecycle_methods %}
    private {{ method.signature }} {
        try {
{% if method.void_call %}            {{ method.body }};
            return {{ method.success_return }};
{% else %}            return {{ method.body }};
{% endif %}        } catch (Throwable e) { return {{ method.error_return }}; }
    }

{% endfor %}{% endif %}
{% for method in methods %}
    private int {{ method.handle_name }}({{ method.sig_params }}) {
        try {
{% for unmarshal in method.unmarshal_params %}
            {{ unmarshal }}
{% endfor %}
{% if method.has_return %}
            {{ method.return_type }} callbackResult = impl.{{ method.name }}({{ method.call_args }});
{% if method.raw_result %}
            MemorySegment jsonCs = arena.allocateFrom(callbackResult);
{% else %}
            String json = JSON.writeValueAsString(callbackResult);
            MemorySegment jsonCs = arena.allocateFrom(json);
{% endif %}
            outResult.set(ValueLayout.ADDRESS, 0, jsonCs);
{% else %}
            impl.{{ method.name }}({{ method.call_args }});
{% endif %}
            return 0;
        } catch (Throwable e) {
            writeError(outError, e);
            return 1;
        }
    }

{% endfor %}
    private void writeError(MemorySegment outError, Throwable e) {
        try { outError.set(ValueLayout.ADDRESS, 0, arena.allocateFrom(e.getClass().getSimpleName() + ": " + e.getMessage())); }
        catch (Throwable ignored) { /* swallow */ }
    }

    private void freeString(MemorySegment ptr) {
        // Strings returned by Java callbacks are arena-owned and released when this bridge closes.
    }

    private void freeUserData(MemorySegment userData) {
        // User data is Java-side state (the impl object), not freed by Rust on drop.
    }

    /** Read a NUL-terminated native C string safely without unbounded reinterpret. */
    private static String readNativeString(MemorySegment ptr) {
        return ptr.reinterpret(4096).getString(0);
    }

    @Override
    public void close() { arena.close(); }

    /** Register a {{ trait_pascal }} implementation via Panama FFM upcall stubs. */
{% if register_takes_name %}
    public static void register{{ trait_pascal }}(final I{{ trait_pascal }} impl) throws Exception {
        var bridge = new {{ bridge_class }}(impl);
        try {
            try (var nameArena = Arena.ofShared()) {
                var nameCs = nameArena.allocateFrom({{ name_expr }});
{% else %}
    public static void register{{ trait_pascal }}(final I{{ trait_pascal }} impl, String name) throws Exception {
        var bridge = new {{ bridge_class }}(impl);
        try {
            try (var nameArena = Arena.ofShared()) {
                var nameCs = nameArena.allocateFrom({{ name_expr }});
{% endif %}
                MemorySegment outErr = nameArena.allocate(ValueLayout.ADDRESS);
                int rc = (int) (long) NativeLib.{{ prefix_upper }}_REGISTER_{{ trait_snake_upper }}.invoke(nameCs, bridge.vtableSegment(), MemorySegment.NULL, outErr);
                if (rc != 0) {
                    MemorySegment errPtr = outErr.get(ValueLayout.ADDRESS, 0);
                    String msg = errPtr.equals(MemorySegment.NULL) ? "registration failed (rc=" + rc + ")" : readNativeString(errPtr);
                    throw new RuntimeException("register{{ trait_pascal }}: " + msg);
                }
            }
        } catch (Throwable t) {
            bridge.close();
            if (t instanceof Exception e) {
                throw e;
            } else {
                throw new RuntimeException("Unexpected error during registration", t);
            }
        }
        {{ registry_field }}.put({{ name_expr }}, bridge);
    }

{{ unregister_method }}{{ clear_method }}}
