const std = @import("std");
const log = std.log;

const root = @import("root");

pub fn logFn(
    comptime level: log.Level,
    comptime scope: @TypeOf(.enum_literal),
    comptime fmt: []const u8,
    args: anytype,
) void {
    if (!root.enableLogScope(scope)) return;

    const stack_buf_size = 1024;
    var stack_buf: [stack_buf_size]u8 = undefined;
    var heap_buf: ?[]u8 = null;

    // Tricky code ahead: when the heap allocation fails, we still want to fall back to
    // logging _something_.

    const len: usize = @truncate(std.fmt.count(fmt, args));
    if (len > stack_buf_size) {
        heap_buf = root.allocator.alloc(u8, len) catch null;
    }
    defer if (heap_buf) |buf| {
        root.allocator.free(buf);
    };

    const buf: []u8 = if (heap_buf) |b| b else stack_buf[0..len];
    const msg = std.fmt.bufPrint(buf, fmt, args) catch buf[0..len]; // no space? yeah whatever print as much as you can

    const scope_name = @tagName(scope);
    switch (level) {
        .err => __haku2_log_err(scope_name.ptr, scope_name.len, msg.ptr, msg.len),
        .warn => __haku2_log_warn(scope_name.ptr, scope_name.len, msg.ptr, msg.len),
        .info => __haku2_log_info(scope_name.ptr, scope_name.len, msg.ptr, msg.len),
        .debug => __haku2_log_debug(scope_name.ptr, scope_name.len, msg.ptr, msg.len),
    }
}

extern fn __haku2_log_err(scope: [*]const u8, scope_len: usize, msg: [*]const u8, len: usize) void;
extern fn __haku2_log_warn(scope: [*]const u8, scope_len: usize, msg: [*]const u8, len: usize) void;
extern fn __haku2_log_info(scope: [*]const u8, scope_len: usize, msg: [*]const u8, len: usize) void;
extern fn __haku2_log_debug(scope: [*]const u8, scope_len: usize, msg: [*]const u8, len: usize) void;
