const std = @import("std");
const build_options = @import("build_options");

fn isScript(data: []const u8) bool {
    return data.len >= 2 and data[0] == '#' and data[1] == '!';
}

fn readU32BE(data: []const u8, offset: usize) ?u32 {
    if (data.len < offset + 4) return null;
    return (@as(u32, data[offset]) << 24) |
        (@as(u32, data[offset + 1]) << 16) |
        (@as(u32, data[offset + 2]) << 8) |
        (@as(u32, data[offset + 3]));
}

fn readU32LE(data: []const u8, offset: usize) ?u32 {
    if (data.len < offset + 4) return null;
    return (@as(u32, data[offset])) |
        (@as(u32, data[offset + 1]) << 8) |
        (@as(u32, data[offset + 2]) << 16) |
        (@as(u32, data[offset + 3]) << 24);
}

fn readU16LE(data: []const u8, offset: usize) ?u16 {
    if (data.len < offset + 2) return null;
    return (@as(u16, data[offset])) | (@as(u16, data[offset + 1]) << 8);
}

fn readU16BE(data: []const u8, offset: usize) ?u16 {
    if (data.len < offset + 2) return null;
    return (@as(u16, data[offset]) << 8) | (@as(u16, data[offset + 1]));
}

fn machCpuTypeForArch(arch: std.Target.Cpu.Arch) ?u32 {
    return switch (arch) {
        .x86_64 => 0x01000007,
        .aarch64 => 0x0100000c,
        else => null,
    };
}

fn elfMachineForArch(arch: std.Target.Cpu.Arch) ?u16 {
    return switch (arch) {
        .x86_64 => 62,
        .aarch64 => 183,
        else => null,
    };
}

fn machOHasCpuType(data: []const u8, want_cputype: u32) bool {
    // Mach-O header: u32 magic; i32 cputype at offset 4.
    // Accept both endiannesses (cputype will appear swapped if magic swapped).
    const magic_le = readU32LE(data, 0) orelse return false;
    const magic_be = readU32BE(data, 0) orelse return false;

    const MH_MAGIC_64: u32 = 0xfeedfacf;
    const MH_CIGAM_64: u32 = 0xcffaedfe;

    if (magic_le == MH_MAGIC_64) {
        const cputype = readU32LE(data, 4) orelse return false;
        return cputype == want_cputype;
    }
    if (magic_be == MH_MAGIC_64 or magic_le == MH_CIGAM_64) {
        const cputype = readU32BE(data, 4) orelse return false;
        return cputype == want_cputype;
    }
    return false;
}

fn fatMachOHasCpuType(data: []const u8, want_cputype: u32) bool {
    // fat_header: magic (0xcafebabe big-endian), nfat_arch u32
    const FAT_MAGIC: u32 = 0xcafebabe;
    const magic = readU32BE(data, 0) orelse return false;
    if (magic != FAT_MAGIC) return false;

    const nfat = readU32BE(data, 4) orelse return false;
    // fat_arch entries start at offset 8, 20 bytes each, big-endian
    var off: usize = 8;
    var i: u32 = 0;
    while (i < nfat) : (i += 1) {
        const cputype = readU32BE(data, off) orelse return false;
        if (cputype == want_cputype) return true;
        off += 20;
        if (off > data.len) return false;
    }
    return false;
}

fn elfHasMachine(data: []const u8, want_machine: u16) bool {
    // Minimal ELF check: magic + e_machine at offset 18
    if (data.len < 20) return false;
    if (!(data[0] == 0x7f and data[1] == 'E' and data[2] == 'L' and data[3] == 'F')) return false;
    const ei_data = data[5]; // 1=little, 2=big
    return switch (ei_data) {
        1 => (readU16LE(data, 18) orelse return false) == want_machine,
        2 => (readU16BE(data, 18) orelse return false) == want_machine,
        else => false,
    };
}

fn isNativeForTarget(data: []const u8) bool {
    if (isScript(data)) return true;

    const target = @import("builtin").target;
    const arch = target.cpu.arch;

    if (machCpuTypeForArch(arch)) |want_cputype| {
        if (fatMachOHasCpuType(data, want_cputype)) return true;
        if (machOHasCpuType(data, want_cputype)) return true;
    }
    if (elfMachineForArch(arch)) |want_machine| {
        if (elfHasMachine(data, want_machine)) return true;
    }

    return false;
}

/// Embedded rsync binary data
/// For dev builds: uses placeholder wrapper that calls system rsync
/// For release builds: embed full static rsync binary from rsync_release.bin
///
/// To prepare for release:
/// 1. Download or build a static rsync binary for your target platform
/// 2. Place it at cli/src/assets/rsync_release.bin
/// 3. Build with: zig build prod (or release/cross targets)
const placeholder_data = @embedFile("../assets/rsync/rsync_placeholder.bin");

const release_data = if (build_options.has_rsync_release)
    @embedFile(build_options.rsync_release_path)
else
    placeholder_data;

// Prefer placeholder in Debug/ReleaseSafe. In Release*, only use rsync_release.bin if it
// appears compatible with the target architecture; otherwise fall back to placeholder.
const use_release = (@import("builtin").mode != .Debug and @import("builtin").mode != .ReleaseSafe) and
    build_options.has_rsync_release and
    !isScript(release_data) and
    isNativeForTarget(release_data);

const rsync_binary_data = if (use_release) release_data else placeholder_data;

pub const RSYNC_BINARY: []const u8 = rsync_binary_data;

pub const USING_RELEASE_BINARY: bool = use_release;

pub fn rsyncBinarySha256() [32]u8 {
    var digest: [32]u8 = undefined;
    std.crypto.hash.sha2.Sha256.hash(RSYNC_BINARY, &digest, .{});
    return digest;
}

/// Get embedded rsync binary data
pub fn getRsyncBinary() []const u8 {
    return RSYNC_BINARY;
}
