const std = @import("std");

// We have to use c_char here, otherwise we redeclare it and cast u8 to char which results in
// additional warnings (-Wbuiltin-declaration-mismatch and -Wpointer-sign).
extern "c" fn printf(format: [*c]const c_char, ...) c_int;

pub const LogLevel = enum(u8) {
    Debug = 0,
    Info = 1,
    Warn = 2,
    Error = 3,
};

var max_level: LogLevel = LogLevel.Debug;

fn logWithPrefix(comptime level: LogLevel, comptime fmt: []const u8, args: anytype) void {
    if (@intFromEnum(level) < @intFromEnum(max_level)) {
        return;
    }

    // Format the message using Zig's formatting
    var buffer: [512]u8 = undefined;
    const prefix_fmt = comptime switch (level) {
        .Debug => "[DEBUG] " ++ fmt,
        .Info => "[INFO] " ++ fmt,
        .Warn => "[WARN] " ++ fmt,
        .Error => "[ERROR] " ++ fmt,
    };

    const msg = std.fmt.bufPrint(&buffer, prefix_fmt, args) catch {
        _ = printf(@as([*c]const c_char, @ptrCast("[ERROR] logger formatting failed\n")));
        return;
    };

    // Print the formatted message (printf with only format string, no args)
    _ = printf(@as([*c]const c_char, @ptrCast(msg.ptr)));
}

pub fn debug(comptime fmt: []const u8, args: anytype) void {
    logWithPrefix(.Debug, fmt, args);
}

pub fn info(comptime fmt: []const u8, args: anytype) void {
    logWithPrefix(.Info, fmt, args);
}

pub fn warn(comptime fmt: []const u8, args: anytype) void {
    logWithPrefix(.Warn, fmt, args);
}

pub fn err(comptime fmt: []const u8, args: anytype) void {
    logWithPrefix(.Error, fmt, args);
}
