const std = @import("std");
const builtin = @import("builtin");
const math = @import("math");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const static_llvm = b.option(bool, "static-llvm", "Statically link LLVM (self-contained binary, no LLVM needed at runtime)") orelse false;
    const llvm_prefix = b.option([]const u8, "llvm-prefix", "Path to LLVM installation") orelse "/opt/homebrew/opt/llvm@19";

    const include_dir = b.fmt("{s}/include", .{llvm_prefix});
    const lib_dir = b.fmt("{s}/lib", .{llvm_prefix});
    const llvm_config = b.fmt("{s}/bin/llvm-config", .{llvm_prefix});

    const mod = b.addModule("sx", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    mod.addSystemIncludePath(.{ .cwd_relative = include_dir });
    mod.addSystemIncludePath(.{ .cwd_relative = "." }); // for clang_shim.h
    mod.addLibraryPath(.{ .cwd_relative = lib_dir });
    mod.link_libc = true;
    mod.addCSourceFile(.{
        .file = b.path("llvm_shim.c"),
        .flags = &.{b.fmt("-I{s}", .{include_dir})},
    });
    // FFI step 2.16c runtime — `_Thread_local`-backed JNIEnv* slot.
    // Linked into sx-the-compiler so the JIT process-symbol generator
    // can resolve `sx_jni_env_tl_get` / `sx_jni_env_tl_set` without the
    // user importing the runtime module. AOT outputs pick up the same
    // .c file via lower-side auto-injected c_import.
    mod.addCSourceFile(.{
        .file = b.path("library/vendors/sx_jni_runtime/sx_jni_env_tl.c"),
        .flags = &.{},
    });
    // ERR E3.1 runtime — `_Thread_local` error return-trace ring buffer.
    // Same linkage rationale as the JNIEnv* slot above: linked into the
    // compiler so the JIT resolves `sx_trace_*` via dlsym; AOT outputs pick it
    // up via a lower-side auto-injected c_import (gated on needs_trace_runtime).
    mod.addCSourceFile(.{
        .file = b.path("library/vendors/sx_trace_runtime/sx_trace.c"),
        .flags = &.{},
    });
    mod.addCSourceFile(.{
        .file = b.path("clang_shim.cpp"),
        .flags = &.{
            b.fmt("-I{s}", .{include_dir}),
            "-std=c++17",
            "-fno-rtti",
            "-fno-exceptions",
            "-D__STDC_CONSTANT_MACROS",
            "-D__STDC_FORMAT_MACROS",
            "-D__STDC_LIMIT_MACROS",
            b.fmt("-DSX_LLVM_PREFIX=\"{s}\"", .{llvm_prefix}),
        },
    });

    const target_os = target.result.os.tag;

    if (static_llvm) {
        if (target_os == .windows) {
            // Windows target: enumerate LLVM .lib files in prefix.
            // Use the host-appropriate command to list files.
            const libs_raw = if (builtin.os.tag == .windows) blk: {
                const dir_cmd = b.fmt("dir /b {s}\\lib\\LLVM*.lib", .{llvm_prefix});
                break :blk std.mem.trim(u8, b.run(&.{ "cmd.exe", "/c", dir_cmd }), " \t\n\r");
            } else blk: {
                break :blk std.mem.trim(u8, b.run(&.{ "ls", lib_dir }), " \t\n\r");
            };
            var libs_it = std.mem.tokenizeAny(u8, libs_raw, "\r\n");
            while (libs_it.next()) |filename| {
                const trimmed = std.mem.trim(u8, filename, " \t");
                if (std.mem.endsWith(u8, trimmed, ".lib") and std.mem.startsWith(u8, trimmed, "LLVM")) {
                    mod.linkSystemLibrary(
                        trimmed[0 .. trimmed.len - 4],
                        .{ .preferred_link_mode = .static },
                    );
                }
            }
            // Windows system libraries LLVM depends on
            const win_syslibs = [_][]const u8{
                "advapi32", "shell32", "ole32",  "uuid",
                "psapi",    "version", "ntdll",  "ws2_32",
                "dbghelp",  "msvcprt",
            };
            for (&win_syslibs) |syslib| {
                mod.linkSystemLibrary(syslib, .{});
            }
        } else {
            // Unix target: query llvm-config for the static libraries needed
            const libs_raw = std.mem.trim(u8, b.run(&.{ llvm_config, "--libs", "--link-static" }), " \t\n\r");
            var libs_it = std.mem.tokenizeAny(u8, libs_raw, " \t\n\r");
            while (libs_it.next()) |flag| {
                if (flag.len > 2 and std.mem.startsWith(u8, flag, "-l")) {
                    mod.linkSystemLibrary(flag[2..], .{ .preferred_link_mode = .static });
                }
            }

            // Clang static libraries (for clang_shim: header parsing + C compilation)
            const clang_libs_raw = std.mem.trim(u8, b.run(&.{ "sh", "-c", b.fmt("ls {s}/lib/libclang*.a | xargs -n1 basename | sed 's/^lib//;s/\\.a$//'", .{llvm_prefix}) }), " \t\n\r");
            var clang_libs_it = std.mem.tokenizeAny(u8, clang_libs_raw, "\n");
            while (clang_libs_it.next()) |lib_name| {
                const trimmed = std.mem.trim(u8, lib_name, " \t\r");
                if (trimmed.len > 0) {
                    mod.linkSystemLibrary(trimmed, .{ .preferred_link_mode = .static });
                }
            }

            // System libraries LLVM depends on — link statically where possible.
            // Add homebrew lib paths for static archives.
            if (builtin.os.tag == .macos) {
                const homebrew_static_paths = [_][]const u8{
                    "/opt/homebrew/opt/zlib/lib",
                    "/opt/homebrew/opt/zstd/lib",
                    "/opt/homebrew/opt/ncurses/lib",
                };
                for (&homebrew_static_paths) |p| {
                    mod.addLibraryPath(.{ .cwd_relative = p });
                }
            }

            const syslibs_raw = std.mem.trim(u8, b.run(&.{ llvm_config, "--system-libs", "--link-static" }), " \t\n\r");
            var syslibs_it = std.mem.tokenizeAny(u8, syslibs_raw, " \t\n\r");
            while (syslibs_it.next()) |flag| {
                if (flag.len > 2 and std.mem.startsWith(u8, flag, "-l")) {
                    const name = flag[2..];
                    // Skip xml2 — only used by LLVM's Windows manifest parser (not needed)
                    if (std.mem.eql(u8, name, "xml2")) continue;
                    // Skip m — part of libSystem on macOS, libc on Linux
                    if (std.mem.eql(u8, name, "m")) continue;
                    mod.linkSystemLibrary(name, .{ .preferred_link_mode = .static });
                }
            }

            // On Linux, add the multiarch system library directory so LLVM's
            // system-lib dependencies (zstd, tinfo, xml2) are found by the linker.
            if (builtin.os.tag == .linux) {
                const multiarch = @tagName(builtin.cpu.arch) ++ "-linux-gnu";
                mod.addLibraryPath(.{ .cwd_relative = "/usr/lib/" ++ multiarch });
            }
        }

        // LLVM is C++ — link the C++ standard library.
        // Windows/MSVC: msvcprt already linked above
        // Linux (apt LLVM): compiled with GCC, needs libstdc++
        // macOS (Homebrew LLVM): compiled with Clang, needs libc++
        if (target_os == .linux) {
            mod.linkSystemLibrary("stdc++", .{});
        } else if (target_os != .windows) {
            mod.link_libcpp = true;
        }
    } else {
        mod.linkSystemLibrary("LLVM-19", .{});
        mod.linkSystemLibrary("clang-cpp", .{});
        // clang-cpp is C++ — need libc++ on macOS
        if (target_os != .windows and target_os != .linux) {
            mod.link_libcpp = true;
        }
    }

    const exe = b.addExecutable(.{
        .name = "sx",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "sx", .module = mod },
            },
        }),
    });

    b.installArtifact(exe);

    // Install the stdlib alongside the binary so `<prefix>/bin/sx` finds
    // `<prefix>/library/modules/...` via the install-layout fallback in
    // `src/imports.zig::discoverStdlibPaths`.
    const install_library = b.addInstallDirectory(.{
        .source_dir = b.path("library"),
        .install_dir = .prefix,
        .install_subdir = "library",
    });
    b.getInstallStep().dependOn(&install_library.step);

    const run_step = b.step("run", "Run the app");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const mod_tests = b.addTest(.{
        .root_module = mod,
    });
    const run_mod_tests = b.addRunArtifact(mod_tests);

    const exe_tests = b.addTest(.{
        .root_module = exe.root_module,
    });
    const run_exe_tests = b.addRunArtifact(exe_tests);

    const test_step = b.step("test", "Run tests");
    test_step.dependOn(&run_mod_tests.step);
    test_step.dependOn(&run_exe_tests.step);

}
