// zig fmt: off
const std = @import("std");
const Artificer = @import("artificer");
const Api = @import("artifacts-api");
const rl = @import("raylib");
const raylib_h = @cImport({
    @cInclude("stdio.h");
    @cInclude("raylib.h");
});
const App = @import("./app.zig");

pub const std_options = .{
    .log_scope_levels = &[_]std.log.ScopeLevel{
        .{ .scope = .api, .level = .info },
        .{ .scope = .raylib, .level = .warn },
    }
};

fn toRaylibTraceLogLevel(log_level: std.log.Level) rl.TraceLogLevel {
    return switch (log_level) {
        .err => rl.TraceLogLevel.log_error,
        .warn => rl.TraceLogLevel.log_warning,
        .info => rl.TraceLogLevel.log_info,
        .debug => rl.TraceLogLevel.log_trace,
    };
}

fn toZigLogLevel(log_type: c_int) ?std.log.Level {
    return switch (log_type) {
        @intFromEnum(rl.TraceLogLevel.log_trace) => std.log.Level.debug,
        @intFromEnum(rl.TraceLogLevel.log_debug) => std.log.Level.debug,
        @intFromEnum(rl.TraceLogLevel.log_info) => std.log.Level.info,
        @intFromEnum(rl.TraceLogLevel.log_warning) => std.log.Level.warn,
        @intFromEnum(rl.TraceLogLevel.log_error) => std.log.Level.err,
        @intFromEnum(rl.TraceLogLevel.log_fatal) => std.log.Level.err,
        else => null,
    };
}

fn raylibTraceLogCallback(logType: c_int, text: [*c]const u8, args: [*c]raylib_h.struct___va_list_tag_1) callconv(.C) void {
    const log_level = toZigLogLevel(logType) orelse return;

    const scope = .raylib;
    const raylib_log = std.log.scoped(scope);

    const max_tracelog_msg_length = 256; // from utils.c in raylib
    var buffer: [max_tracelog_msg_length:0]u8 = undefined;

    inline for (std.meta.fields(std.log.Level)) |field| {
        const message_level: std.log.Level = @enumFromInt(field.value);
        if (std.log.logEnabled(message_level, scope) and log_level == message_level) {
            @memset(&buffer, 0);
            const text_length = raylib_h.vsnprintf(&buffer, buffer.len, text, args);
            const formatted_text = buffer[0..@intCast(text_length)];

            const log_function = @field(raylib_log, field.name);
            @call(.auto, log_function, .{ "{s}", .{formatted_text} });
        }
    }
}

fn getAPITokenFromArgs(allocator: std.mem.Allocator) !?[]u8 {
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 2) {
        return null;
    }

    const filename = args[1];
    const cwd = std.fs.cwd();
    var token_buffer: [256]u8 = undefined;
    const token = try cwd.readFile(filename, &token_buffer);

    return try allocator.dupe(u8, std.mem.trim(u8, token, "\n\t "));
}

pub fn main() anyerror!void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
    rl.setTraceLogLevel(toRaylibTraceLogLevel(std.log.default_level));

    const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
    defer allocator.free(token);

    var store = try Api.Store.init(allocator);
    defer store.deinit(allocator);

    var server = try Api.Server.init(allocator, &store);
    defer server.deinit();

    try server.setToken(token);

    var artificer = try Artificer.init(allocator, &server);
    defer artificer.deinit();

    std.log.info("Prefetching server data", .{});
    {
        const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
        defer allocator.free(cwd_path);

        const cache_path = try std.fs.path.resolve(allocator, &.{ cwd_path, "./api-store.bin" });
        defer allocator.free(cache_path);

        try server.prefetchCached(allocator, cache_path);
    }

    rl.initWindow(800, 450, "Artificer");
    defer rl.closeWindow();

    rl.setWindowMinSize(200, 200);
    rl.setWindowState(.{
        .vsync_hint = true,
        .window_resizable = true
    });

    var app = try App.init(allocator, &artificer);
    defer app.deinit();

    while (!rl.windowShouldClose()) {
        try app.tick();
    }
}
