const std = @import("std");

// Clean build configuration for optimized CLI (Zig 0.15 std.Build API)
pub fn build(b: *std.Build) void {
    // Standard target options
    const target = b.standardTargetOptions(.{});

    const test_filter = b.option([]const u8, "test-filter", "Filter unit tests by name");
    _ = test_filter;

    // Standard optimize option - let user choose, default to ReleaseSmall for production
    const optimize = b.standardOptimizeOption(.{});

    const options = b.addOptions();

    const arch = target.result.cpu.arch;
    const os_tag = target.result.os.tag;

    const arch_str: []const u8 = switch (arch) {
        .x86_64 => "x86_64",
        .aarch64 => "arm64",
        else => "unknown",
    };
    const os_str: []const u8 = switch (os_tag) {
        .linux => "linux",
        .macos => "darwin",
        .windows => "windows",
        else => "unknown",
    };

    const candidate_specific = b.fmt("src/assets/rsync/rsync_release_{s}_{s}.bin", .{ os_str, arch_str });
    const candidate_default = "src/assets/rsync/rsync_release.bin";

    var selected_candidate: []const u8 = "";
    var has_rsync_release = false;

    // Prefer a platform-specific asset if available.
    if (std.fs.cwd().openFile(candidate_specific, .{}) catch null) |f| {
        f.close();
        selected_candidate = candidate_specific;
        has_rsync_release = true;
    } else if (std.fs.cwd().openFile(candidate_default, .{}) catch null) |f| {
        f.close();
        selected_candidate = candidate_default;
        has_rsync_release = true;
    }

    if ((optimize == .ReleaseSmall or optimize == .ReleaseFast) and !has_rsync_release) {
        std.debug.panic(
            "Release build requires an embedded rsync binary asset. Provide one of: '{s}' or '{s}'",
            .{ candidate_specific, candidate_default },
        );
    }

    // rsync_embedded_binary.zig calls @embedFile() from cli/src/utils, so the embed path
    // must be relative to that directory.
    const selected_embed_path = if (has_rsync_release)
        b.fmt("../assets/rsync/{s}", .{std.fs.path.basename(selected_candidate)})
    else
        "";

    options.addOption(bool, "has_rsync_release", has_rsync_release);
    options.addOption([]const u8, "rsync_release_path", selected_embed_path);

    // Check for SQLite assets (platform-specific only, no generic fallback)
    const sqlite_dir = b.fmt("src/assets/sqlite_{s}_{s}", .{ os_str, arch_str });

    var has_sqlite_release = false;
    var sqlite_release_path: []const u8 = "";

    // Only check platform-specific directory
    if (std.fs.cwd().access(sqlite_dir, .{})) |_| {
        has_sqlite_release = true;
        sqlite_release_path = sqlite_dir;
    } else |_| {}

    if (optimize == .ReleaseSmall and !has_sqlite_release) {
        std.debug.panic(
            "ReleaseSmall build requires SQLite amalgamation (detected optimize={s}). Run: make build-sqlite",
            .{@tagName(optimize)},
        );
    }

    options.addOption(bool, "has_sqlite_release", has_sqlite_release);
    options.addOption([]const u8, "sqlite_release_path", sqlite_release_path);

    // CLI executable - declared BEFORE SQLite setup so exe can be referenced
    const exe = b.addExecutable(.{
        .name = "ml",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    exe.root_module.strip = true;
    exe.root_module.addOptions("build_options", options);
    // LTO disabled: requires LLD linker which may not be available
    // exe.want_lto = true;

    // Check if we're cross-compiling (target differs from host)
    const host_target = b.graph.host;
    const is_cross_compiling = (target.result.os.tag != host_target.query.os_tag) or
        (target.result.cpu.arch != host_target.query.cpu_arch);

    // Link native dataset_hash library (only when not cross-compiling)
    exe.linkLibC();
    if (!is_cross_compiling) {
        exe.addLibraryPath(b.path("../native/build"));
        exe.linkSystemLibrary("dataset_hash");
        exe.addIncludePath(b.path("../native/dataset_hash"));
    } else {
        // Cross-compiling: native library not available, skip it
        std.log.warn("Cross-compiling detected - skipping native library linking", .{});
    }

    // SQLite setup: embedded for ReleaseSmall only, system lib for dev
    const use_embedded_sqlite = has_sqlite_release and (optimize == .ReleaseSmall);
    if (use_embedded_sqlite) {
        // Release: compile SQLite from downloaded amalgamation
        const sqlite_c_path = b.fmt("{s}/sqlite3.c", .{sqlite_release_path});
        exe.addCSourceFile(.{ .file = b.path(sqlite_c_path), .flags = &.{
            "-DSQLITE_ENABLE_FTS5",
            "-DSQLITE_ENABLE_JSON1",
            "-DSQLITE_THREADSAFE=1",
            "-DSQLITE_USE_URI",
        } });
        exe.addIncludePath(b.path(sqlite_release_path));

        // Compile SQLite constants wrapper (needed for SQLITE_TRANSIENT workaround)
        exe.addCSourceFile(.{ .file = b.path("src/assets/sqlite/sqlite_constants.c"), .flags = &.{ "-Wall", "-Wextra" } });
    } else {
        // Dev: link against system SQLite
        exe.linkSystemLibrary("sqlite3");

        // Add system include paths for sqlite3.h
        exe.addIncludePath(.{ .cwd_relative = "/usr/include" });
        exe.addIncludePath(.{ .cwd_relative = "/usr/local/include" });
        exe.addIncludePath(.{ .cwd_relative = "/opt/homebrew/include" });

        // Compile SQLite constants wrapper with system headers
        exe.addCSourceFile(.{ .file = b.path("src/assets/sqlite/sqlite_constants.c"), .flags = &.{ "-Wall", "-Wextra" } });
    }

    // Install the executable to zig-out/bin
    b.installArtifact(exe);

    // Default build: install optimized CLI (used by `zig build`)
    const prod_step = b.step("prod", "Build production CLI binary");
    prod_step.dependOn(&exe.step);

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

    // Standard Zig test discovery - find all test files automatically
    const test_step = b.step("test", "Run unit tests");

    // Safety check for release builds
    const safety_check_step = b.step("safety-check", "Verify ReleaseSafe mode is used for production");
    if (optimize != .ReleaseSafe and optimize != .Debug) {
        const warn_no_safe = b.addSystemCommand(&.{ "echo", "WARNING: Building without ReleaseSafe mode. Production builds should use -Doptimize=ReleaseSafe" });
        safety_check_step.dependOn(&warn_no_safe.step);
    }

    // Test main executable
    const main_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = .Debug,
        }),
    });
    main_tests.root_module.addOptions("build_options", options);
    const run_main_tests = b.addRunArtifact(main_tests);
    test_step.dependOn(&run_main_tests.step);

    // Find all test files in tests/ directory automatically
    var test_dir = std.fs.cwd().openDir("tests", .{ .iterate = true }) catch |err| {
        std.log.warn("Failed to open tests directory: {}", .{err});
        return;
    };
    defer test_dir.close();

    // Create src module that tests can import from
    const src_module = b.createModule(.{
        .root_source_file = b.path("src.zig"),
        .target = target,
        .optimize = .Debug,
    });

    var iter = test_dir.iterate();
    while (iter.next() catch |err| {
        std.log.warn("Error iterating test files: {}", .{err});
        return;
    }) |entry| {
        if (entry.kind == .file and std.mem.endsWith(u8, entry.name, "_test.zig")) {
            const test_path = b.pathJoin(&.{ "tests", entry.name });

            const test_module = b.createModule(.{
                .root_source_file = b.path(test_path),
                .target = target,
                .optimize = .Debug,
            });

            test_module.addOptions("build_options", options);

            // Make src module available to tests as "src"
            test_module.addImport("src", src_module);

            const test_exe = b.addTest(.{
                .root_module = test_module,
            });
            test_exe.linkLibC();

            const run_test = b.addRunArtifact(test_exe);
            test_step.dependOn(&run_test.step);
        }
    }
}
