<% _ = opts %>
const std = @import("std");

const BuildMode = enum{ nif_lib, sema };

// Helper to strip surrounding quotes from paths (needed for Windows paths with spaces)
fn stripQuotes(path: []const u8) []const u8 {
    if (path.len >= 2 and path[0] == '"' and path[path.len - 1] == '"') {
        return path[1..path.len - 1];
    }
    return path;
}

pub fn build(b: *std.Build) void {
    const resolved_target = b.standardTargetOptions(<%=Zig.Target.for_builder()%>);
    // Host target for sema - must run on build machine during cross-compilation
    const host_target = b.graph.host;
    const optimize: std.builtin.OptimizeMode = <%= render_optimize_mode(assigns) %>;
    const error_tracing = <%= @error_tracing %>;

    // ERTS and zigler paths (auto-injected by zigler as -D flags)
    // stripQuotes handles Windows paths with spaces that are quoted on the command line
    const erts_include = stripQuotes(b.option([]const u8, "erts_include", "ERTS include path") orelse @panic("erts_include required"));
    const erl_nif_header = stripQuotes(b.option([]const u8, "erl_nif_header", "Path to erl_nif.h (or erl_nif_win.h on Windows)") orelse @panic("erl_nif_header required"));
    const erl_nif_win_path = stripQuotes(b.option([]const u8, "erl_nif_win_path", "Path to Windows erl_nif compatibility headers") orelse @panic("erl_nif_win_path required"));
    const zigler_priv = stripQuotes(b.option([]const u8, "zigler_priv", "Path to zigler priv directory") orelse @panic("zigler_priv required"));
    const module_root = stripQuotes(b.option([]const u8, "module_root", "Path to module source directory") orelse @panic("module_root required"));

    const mode = b.option(BuildMode, "zigler-mode", "Build either the nif library or the sema analysis module") orelse .nif_lib;

    <%= for {name, _} <- @dependencies do %>
    const <%= name %> = b.dependency("<%= name %>", .{
        .target = resolved_target,
        .optimize = optimize
    });
    <% end %>

    switch (mode) {
        .nif_lib => {
            // For NIF library, use the cross-compile target
            const target_for_modules = resolved_target;

            // Translate erl_nif.h to Zig module (replaces @cImport in Zig 0.16.0)
            // On Windows, uses custom erl_nif_win.h to avoid translate-c issues with macro-based function pointers
            const erl_translate_c = b.addTranslateC(.{
                .root_source_file = .{ .cwd_relative = erl_nif_header },
                .target = resolved_target,
                .optimize = optimize,
                .link_libc = true,
            });
            erl_translate_c.addSystemIncludePath(.{ .cwd_relative = erts_include });
            erl_translate_c.addSystemIncludePath(.{ .cwd_relative = erl_nif_win_path });
            const erl_module = erl_translate_c.createModule();

            <%= Builder.render_build(BuildModule.erl_nif()) %>
            erl_nif.addImport("erl", erl_module);
            <%= Builder.render_build(BuildModule.beam()) %>
            <%= Builder.render_build(BuildModule.attributes()) %>
            <%= for extra_module <- @extra_modules, do: Builder.render_build(extra_module)%>
            <%= Builder.render_build(BuildModule.from_beam_module(assigns)) %>
<%= if @c && @c.headers != [] do %>
            // Translate user-specified C headers
<%= for {header_path, module_name} <- @c.headers do %>
            {
<%= case header_path do %>
<% {:system, header_name} -> %>
                // System header - generate wrapper that includes it
                const wf_<%= module_name %> = b.addWriteFiles();
                const wrapper_<%= module_name %> = wf_<%= module_name %>.add("<%= module_name %>_wrapper.h", "#include <<%= header_name %>>\n");
                const translate_c_<%= module_name %> = b.addTranslateC(.{
                    .root_source_file = wrapper_<%= module_name %>,
                    .target = resolved_target,
                    .optimize = optimize,
                    .link_libc = true,
                });
<% _ -> %>
                const translate_c_<%= module_name %> = b.addTranslateC(.{
                    .root_source_file = .{ .cwd_relative = "<%= header_path %>" },
                    .target = resolved_target,
                    .optimize = optimize,
                    .link_libc = true,
                });
<% end %>
<%= for dir <- @c.include_dirs do %>
<%= case dir do %>
<% {:system, path} -> %>                translate_c_<%= module_name %>.addSystemIncludePath(.{ .cwd_relative = "<%= path %>" });
<% _ -> %>                translate_c_<%= module_name %>.addIncludePath(.{ .cwd_relative = "<%= dir %>" });
<% end %>
<% end %>
                nif.addImport("<%= module_name %>", translate_c_<%= module_name %>.createModule());
            }
<% end %>
<% end %>
<%= if @easy_c do %>
            // Translate easy_c header
            {
                const wf_easy_c = b.addWriteFiles();
                const wrapper_easy_c = wf_easy_c.add("easy_c_wrapper.h", "#include <<%= @easy_c %>>\n");
                const translate_c_easy_c = b.addTranslateC(.{
                    .root_source_file = wrapper_easy_c,
                    .target = resolved_target,
                    .optimize = optimize,
                    .link_libc = true,
                });
<%= for dir <- (if @c, do: @c.include_dirs, else: []) do %>
<%= case dir do %>
<% {:system, path} -> %>                translate_c_easy_c.addSystemIncludePath(.{ .cwd_relative = "<%= path %>" });
<% _ -> %>                translate_c_easy_c.addIncludePath(.{ .cwd_relative = "<%= dir %>" });
<% end %>
<% end %>
                nif.addImport("easy_c", translate_c_easy_c.createModule());
            }
<% end %>
            <%= Builder.render_build(BuildModule.nif_shim()) %>

            const lib = b.addLibrary(.{
                .name = "<%= @module %>",
                .linkage = .dynamic,
                .version = .{.major = <%= @version.major %>,
                             .minor = <%= @version.minor %>,
                             .patch = <%= @version.patch %>},
                .root_module = nif_shim
            });

            <%= render_c(@c, :lib) %>
            <%= for extra_module <- @extra_modules, do: render_c(extra_module, :lib) %>

            lib.linker_allow_shlib_undefined = true;

            // Only for 32-bit x86 targets
            if (resolved_target.result.cpu.arch == .x86) {
                lib.link_z_notext = true; // passes -z notext to the linker
            }

            <%= if @libc_txt do %>
            lib.setLibCFile(b.path("<%= @libc_txt %>"));
            <% end %>

            b.installArtifact(lib);
        },
        .sema => {
            // For sema, use host target - must run on build machine during cross-compilation
            const target_for_modules = host_target;

            <%= Builder.render_build(BuildModule.stub_erl_nif()) %>
            <%= Builder.render_build(BuildModule.beam()) %>
            <%= Builder.render_build(BuildModule.attributes()) %>
            <%= for extra_module <- @extra_modules, do: Builder.render_build(extra_module)%>
            <%= Builder.render_build(BuildModule.from_beam_module(assigns)) %>
<%= if @c && @c.headers != [] do %>
            // Translate user-specified C headers for sema (using host target for cross-compilation)
<%= for {header_path, module_name} <- @c.headers do %>
            {
<%= case header_path do %>
<% {:system, header_name} -> %>
                // System header - generate wrapper that includes it
                const wf_<%= module_name %> = b.addWriteFiles();
                const wrapper_<%= module_name %> = wf_<%= module_name %>.add("<%= module_name %>_wrapper.h", "#include <<%= header_name %>>\n");
                const translate_c_<%= module_name %> = b.addTranslateC(.{
                    .root_source_file = wrapper_<%= module_name %>,
                    .target = host_target,
                    .optimize = optimize,
                    .link_libc = true,
                });
<% _ -> %>
                const translate_c_<%= module_name %> = b.addTranslateC(.{
                    .root_source_file = .{ .cwd_relative = "<%= header_path %>" },
                    .target = host_target,
                    .optimize = optimize,
                    .link_libc = true,
                });
<% end %>
<%= for dir <- @c.include_dirs do %>
<%= case dir do %>
<% {:system, path} -> %>                translate_c_<%= module_name %>.addSystemIncludePath(.{ .cwd_relative = "<%= path %>" });
<% _ -> %>                translate_c_<%= module_name %>.addIncludePath(.{ .cwd_relative = "<%= dir %>" });
<% end %>
<% end %>
                nif.addImport("<%= module_name %>", translate_c_<%= module_name %>.createModule());
            }
<% end %>
<% end %>
<%= if @easy_c do %>
            // Translate easy_c header for sema (using host target for cross-compilation)
            {
                const wf_easy_c = b.addWriteFiles();
                const wrapper_easy_c = wf_easy_c.add("easy_c_wrapper.h", "#include <<%= @easy_c %>>\n");
                const translate_c_easy_c = b.addTranslateC(.{
                    .root_source_file = wrapper_easy_c,
                    .target = host_target,
                    .optimize = optimize,
                    .link_libc = true,
                });
<%= for dir <- (if @c, do: @c.include_dirs, else: []) do %>
<%= case dir do %>
<% {:system, path} -> %>                translate_c_easy_c.addSystemIncludePath(.{ .cwd_relative = "<%= path %>" });
<% _ -> %>                translate_c_easy_c.addIncludePath(.{ .cwd_relative = "<%= dir %>" });
<% end %>
<% end %>
                nif.addImport("easy_c", translate_c_easy_c.createModule());
            }
<% end %>
            <%= Builder.render_build(BuildModule.sema()) %>

            const sema_exe = b.addExecutable(.{
                .name = "sema",
                .root_module = sema,
            });

            <%= render_c(@c, :sema_exe) %>
            <%= for extra_module <- @extra_modules, do: render_c(extra_module, :sema_exe) %>

            b.installArtifact(sema_exe);

            const sema_run_cmd = b.addRunArtifact(sema_exe);
            if (b.args) |args| sema_run_cmd.addArgs(args);
            const sema_step = b.step("sema", "Run sema");
            sema_step.dependOn(&sema_run_cmd.step);
        }
    }
}

fn getenvAlloc(alloc: std.mem.Allocator, name: []const u8) ?[]u8 {
    return std.process.getEnvVarOwned(alloc, name) catch null;
}
