const @"Thread-<%= @name %>" = beam.Thread(nif.<%= @alias || @name %>);
pub const @"ThreadResource-<%= @name %>" = beam.Resource(*@"Thread-<%= @name %>", @This(), .{
  .Callbacks = beam.ThreadedCallbacks(@"Thread-<%= @name %>")
});

// leak_check is disabled on Windows due to issues with debug allocator stack traces
const leak_check_<%= @name %> = if (builtin.os.tag == .windows) false else <%= @leak_check %>;

<% needs_make? = @params |> Map.values |> Enum.any?(&Type.needs_make?(&1.type)) %>
<% needs_size? = @params |> Enum.any?(fn {_index, param} -> Parameter.needs_size?(param) end) %>

fn @"<%= @name %>-launch"(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callconv(.c) e.ErlNifTerm {

    <%= if allocator = @allocator do %>
    const allocator = nif.<%= allocator %>();
    <% else %>
    const allocator = beam.allocator;
    <% end %>

    // set context due to reentry
    beam.context = .{
        .env = env,
        // TODO: make this configurable
        .mode = .synchronous,
        .allocator = allocator,
        .io = beam.io.get(allocator),
    };

    <%= if needs_make? do %>
    var error_info: ?beam.term = null;
    <% end %>

    <%= if needs_size? do %>
    var payload_sizes: struct {
        <%= for {index, param} <- @params, Parameter.needs_size?(param) do %>
        arg<%= index %>: ?usize = null,
        <% end %>
    } = .{};
    <% end %>

    const payload_opts = .{
        <%= for {index, param} <- @params do %>
        .{
            <%= Parameter.render_payload_options(param, index) %>
        },
        <% end %>
    };

    const cleanup_opts = .{
        <%= for {index, param} <- @params, do: Parameter.render_cleanup(param, index) %>
    };

    const result = @"Thread-<%= @name %>".launch(@"ThreadResource-<%= @name %>", argc, args, payload_opts, cleanup_opts, leak_check_<%= @name %>, <%= if needs_make? do %>&error_info<% else %>null<% end %>) catch |err| {
        // OutOfMemory during thread setup (not payload building)
        return e.enif_raise_exception(env, beam.make(.{err}, .{}).v);
    };

    return result.v;
}

fn @"<%= @name %>-join"(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callconv(.c) e.ErlNifTerm {
    // argc must be 1.
    _ = argc;

    const thread_rsrc = beam.get(@"ThreadResource-<%= @name %>", .{.v = args[0]}, .{}) catch |err| {
        return e.enif_raise_exception(env, beam.make(.{
            .__struct__ = .@"Elixir.ArgumentError",
            .message = beam.make("expected thread resource for join, got invalid term", .{}),
            .__exception__ = true,
            .__error__ = err,
        }, .{}).v);
    };
    const thread = @"ThreadResource-<%= @name %>".unpack(thread_rsrc);

    // set context due to reentry
    beam.context = .{
        .env = env,
        .mode = .threaded,
        .allocator = beam.allocator, //thread.allocator
        .io = beam.io.get(beam.allocator),
    };

    const result = thread.join() catch |err| {
        const message = switch (err) {
            error.memoryleak => "memory leak detected in threaded function `<%= @name %>`",
            error.threaderror => "thread error in threaded function `<%= @name %>`",
            error.threadtooktoolong => "thread took too long to respond in threaded function `<%= @name %>`",
            error.processnotjoined => "failed to join thread in threaded function `<%= @name %>`",
        };
        return beam.raise_exception(beam.make(.{
            .__struct__ = .@"Elixir.RuntimeError",
            .message = message,
            .__exception__ = true,
        }, .{}), .{}).v;
    };

    if (comptime @"Thread-<%= @name %>".makes_error_result()) {
        const result_term = switch(result) {
            .ok => |ok_result| beam.make(ok_result, .{}),
            .error_return_trace => |error_result| beam.raise_exception(beam.copy(env, error_result), .{}),
        };
        return result_term.v;
    } else {
        return beam.make(result, .{}).v;
    }
}
