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

/// macOS GPU Monitoring for Development Mode
/// Uses system_profiler and powermetrics for GPU info
/// Only available on macOS
const c = @cImport({
    @cInclude("sys/types.h");
    @cInclude("sys/sysctl.h");
});

/// GPU information structure for macOS
pub const MacOSGPUInfo = struct {
    index: u32,
    name: [256:0]u8,
    chipset_model: [256:0]u8,
    vram_mb: u32,
    is_integrated: bool,
    // Performance metrics (if available via powermetrics)
    utilization_percent: ?u32,
    temperature_celsius: ?u32,
    power_mw: ?u32,
};

/// Detect if running on Apple Silicon
pub fn isAppleSilicon() bool {
    if (builtin.os.tag != .macos) return false;

    var buf: [64]u8 = undefined;
    var len: usize = buf.len;
    const mib = [_]c_int{ c.CTL_HW, c.HW_MACHINE };

    const result = c.sysctl(&mib[0], 2, &buf[0], &len, null, 0);
    if (result != 0) return false;

    const machine = std.mem.sliceTo(&buf, 0);
    return std.mem.startsWith(u8, machine, "arm64") or
        std.mem.startsWith(u8, machine, "Apple");
}

/// Get GPU count on macOS
pub fn getGPUCount() u32 {
    if (builtin.os.tag != .macos) return 0;

    // Run system_profiler to check for GPUs
    const result = runSystemProfiler() catch return 0;
    defer std.heap.raw_c_allocator.free(result);

    // Parse output for GPU entries
    var lines = std.mem.splitScalar(u8, result, '\n');
    var count: u32 = 0;
    while (lines.next()) |line| {
        if (std.mem.indexOf(u8, line, "Chipset Model") != null) {
            count += 1;
        }
    }

    return count;
}

/// Run system_profiler SPDisplaysDataType
fn runSystemProfiler() ![]u8 {
    const argv = [_][]const u8{
        "system_profiler",
        "SPDisplaysDataType",
        "-json",
    };

    var child = std.process.Child.init(&argv, std.heap.page_allocator);
    child.stdout_behavior = .Pipe;
    child.stderr_behavior = .Ignore;

    try child.spawn();
    defer child.kill() catch {};

    const stdout = child.stdout.?.reader();
    const output = try stdout.readAllAlloc(std.heap.page_allocator, 1024 * 1024);

    const term = try child.wait();
    if (term != .Exited or term.Exited != 0) {
        return error.CommandFailed;
    }

    return output;
}

/// Parse GPU info from system_profiler JSON output
pub fn parseGPUInfo(allocator: std.mem.Allocator, json_output: []const u8) ![]MacOSGPUInfo {
    // Simple parser for system_profiler JSON
    // Format: {"SPDisplaysDataType": [{"sppci_model":"...", "sppci_vram":"...", ...}, ...]}

    var gpus = std.ArrayList(MacOSGPUInfo).init(allocator);
    defer gpus.deinit();

    // Parse JSON - look for _items array
    const items_key = "_items";
    if (std.mem.indexOf(u8, json_output, items_key)) |items_start| {
        const rest = json_output[items_start..];
        // Find array start
        if (std.mem.indexOf(u8, rest, "[")) |array_start| {
            const array = rest[array_start..];
            // Simple heuristic: find objects between { and }
            var i: usize = 0;
            while (i < array.len) {
                if (array[i] == '{') {
                    // Found object start
                    if (findObjectEnd(array[i..])) |obj_end| {
                        const obj = array[i .. i + obj_end];
                        if (try parseGPUObject(obj)) |gpu| {
                            try gpus.append(gpu);
                        }
                        i += obj_end;
                        continue;
                    }
                }
                i += 1;
            }
        }
    }

    return gpus.toOwnedSlice();
}

fn findObjectEnd(json: []const u8) ?usize {
    var depth: i32 = 0;
    var in_string = false;
    var i: usize = 0;
    while (i < json.len) : (i += 1) {
        const char = json[i];
        if (char == '"' and (i == 0 or json[i - 1] != '\\')) {
            in_string = !in_string;
        } else if (!in_string) {
            if (char == '{') {
                depth += 1;
            } else if (char == '}') {
                depth -= 1;
                if (depth == 0) {
                    return i + 1;
                }
            }
        }
    }
    return null;
}

fn parseGPUObject(json: []const u8) !?MacOSGPUInfo {
    var gpu = MacOSGPUInfo{
        .index = 0,
        .name = std.mem.zeroes([256:0]u8),
        .chipset_model = std.mem.zeroes([256:0]u8),
        .vram_mb = 0,
        .is_integrated = false,
        .utilization_percent = null,
        .temperature_celsius = null,
        .power_mw = null,
    };

    // Extract sppci_model
    if (extractJsonString(json, "sppci_model")) |model| {
        const len = @min(model.len, 255);
        @memcpy(gpu.chipset_model[0..len], model[0..len]);
        @memcpy(gpu.name[0..len], model[0..len]);
    }

    // Extract sppci_vram
    if (extractJsonString(json, "sppci_vram_shared")) |_| {
        gpu.is_integrated = true;
        gpu.vram_mb = 0; // Shared memory
    } else if (extractJsonString(json, "sppci_vram")) |vram| {
        // Parse "16384 MB" -> 16384
        var it = std.mem.splitScalar(u8, vram, ' ');
        if (it.next()) |num_str| {
            gpu.vram_mb = std.fmt.parseInt(u32, num_str, 10) catch 0;
        }
    }

    // Check if it's a valid GPU entry
    if (gpu.chipset_model[0] == 0) {
        return null;
    }

    return gpu;
}

fn extractJsonString(json: []const u8, key: []const u8) ?[]const u8 {
    const key_quoted = std.fmt.allocPrint(std.heap.page_allocator, "\"{s}\"", .{key}) catch return null;
    defer std.heap.page_allocator.free(key_quoted);

    if (std.mem.indexOf(u8, json, key_quoted)) |key_pos| {
        const after_key = json[key_pos + key_quoted.len ..];
        // Find value start (skip : and whitespace)
        var i: usize = 0;
        while (i < after_key.len and (after_key[i] == ':' or after_key[i] == ' ' or after_key[i] == '\t' or after_key[i] == '\n')) : (i += 1) {}

        if (i < after_key.len and after_key[i] == '"') {
            // String value
            const str_start = i + 1;
            var str_end = str_start;
            while (str_end < after_key.len and after_key[str_end] != '"') : (str_end += 1) {}
            return after_key[str_start..str_end];
        }
    }
    return null;
}

/// Format GPU info for display
pub fn formatMacOSGPUInfo(allocator: std.mem.Allocator, gpus: []const MacOSGPUInfo) ![]u8 {
    var buf = std.ArrayList(u8).init(allocator);
    defer buf.deinit();

    const writer = buf.writer();

    if (gpus.len == 0) {
        try writer.writeAll("GPU Status (macOS)\n");
        try writer.writeAll("═" ** 50);
        try writer.writeAll("\n\nNo GPUs detected\n");
        return buf.toOwnedSlice();
    }

    try writer.writeAll("GPU Status (macOS");
    if (isAppleSilicon()) {
        try writer.writeAll(" - Apple Silicon");
    }
    try writer.writeAll(")\n");
    try writer.writeAll("═" ** 50);
    try writer.writeAll("\n\n");

    for (gpus) |gpu| {
        const name = std.mem.sliceTo(&gpu.name, 0);
        const model = std.mem.sliceTo(&gpu.chipset_model, 0);

        try writer.print("GPU {d}: {s}\n", .{ gpu.index, name });
        if (!std.mem.eql(u8, model, name)) {
            try writer.print("\tModel: {s}\n", .{model});
        }
        if (gpu.is_integrated) {
            try writer.writeAll("\tType: Integrated (Unified Memory)\n");
        } else {
            try writer.print("\tVRAM: {d} MB\n", .{gpu.vram_mb});
        }
        if (gpu.utilization_percent) |util| {
            try writer.print("\tUtilization: {d}%\n", .{util});
        }
        if (gpu.temperature_celsius) |temp| {
            try writer.print("\tTemperature: {d}°C\n", .{temp});
        }
        if (gpu.power_mw) |power| {
            try writer.print("\tPower: {d:.1f} W\n", .{@as(f64, @floatFromInt(power)) / 1000.0});
        }
        try writer.writeAll("\n");
    }

    try writer.writeAll("Note: Detailed GPU metrics require powermetrics (sudo)\n");

    return buf.toOwnedSlice();
}

/// Quick check for GPU availability on macOS
pub fn isMacOSGPUAvailable() bool {
    if (builtin.os.tag != .macos) return false;
    return getGPUCount() > 0;
}
