//!
// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac
b: *std.Build,
nw_builder: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.Mode,
nw_mod: *std.Build.Module,
spirvReflect: SpirvReflect.SpirvGenerator2,
gltf2ozz: ozz.GltfToOzz,
options: *std.Build.Step.Options,
cookShaders: bool,

// list of all shaders discovered under
// content/_shaders/def
reflectShaderPathList: [][]u8 = undefined,

const engineDepList = [_][]const u8{
    "assets",
    "audio",
    "core",
    "papyrus",
    "platform",
    "physics",
};

const BuildSystem = @This();
const std = @import("std");
const Build = std.Build;
const LazyPath = Build.LazyPath;

const SpirvReflect = @import("SpirvReflect");
const ozz = @import("ozz");

pub const InitOptions = struct {
    import_name: []const u8 = "Backlog",
    target: Build.ResolvedTarget,
    optimize: std.builtin.OptimizeMode,
};

pub fn init(b: *std.Build, opts: InitOptions) BuildSystem {
    const nwdep = b.dependency(opts.import_name, .{
        .target = opts.target,
        .optimize = opts.optimize,
    });

    const self = BuildSystem{
        .b = b,
        .nw_builder = nwdep.builder,
        .target = opts.target,
        .optimize = opts.optimize,
        .nw_mod = nwdep.module("Backlog"),
        .spirvReflect = SpirvReflect.SpirvGenerator2.init(nwdep.builder, .{}),
        .options = createGameOptions(b),
        .gltf2ozz = ozz.GltfToOzz.init(nwdep.builder, .{}),
        .cookShaders = b.option(bool, "cookShaders", "generates shaders and updates .json files before running the build. (needs to be done whenever shaders are updated, this just runs tools/scripts/cook-shaders.py)") orelse false,
    };

    const exeList = [2]*std.Build.Step.Compile{ self.gltf2ozz.exe, self.spirvReflect.reflect };
    const install_tools = b.step("tools", "installs tools needed to generate outputs for the engine");
    for (exeList) |exe| {
        const toolsInstall = b.addInstallArtifact(exe, .{
            .dest_dir = .{ .override = .{ .custom = "tools" } },
        });

        install_tools.dependOn(&toolsInstall.step);
    }

    {
        const runArtifact = b.addRunArtifact(self.gltf2ozz.exe);
        if (b.args) |args| {
            runArtifact.addArgs(args);
        }

        const run_exe = b.step("gltf2ozz", "runs the gltf animation converter.");
        run_exe.dependOn(&runArtifact.step);
    }

    {
        const runArtifact = b.addRunArtifact(self.spirvReflect.reflect);
        if (b.args) |args| {
            runArtifact.addArgs(args);
        }

        const run_exe = b.step("spv-reflect", "runs the gltf animation converter.");
        run_exe.dependOn(&runArtifact.step);
    }

    return self;
}

pub const AddProgramOptions = struct {
    name: []const u8,
    desc: []const u8,
    root_source_file: LazyPath,
    imports: []const Build.Module.Import = &.{},
};

pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Step.Compile {
    const b = self.b;

    const exe = self.nw_builder.addExecutable(.{
        .name = opts.name,
        .target = self.target,
        .optimize = self.optimize,
        .root_source_file = self.nw_builder.path("engine/main.zig"),
    });

    b.installArtifact(exe);
    const runArtifact = b.addRunArtifact(exe);
    if (b.args) |args| {
        runArtifact.addArgs(args);
    }
    const run_exe = b.step(self.b.fmt("run-{s}", .{opts.name}), opts.desc);
    run_exe.dependOn(&runArtifact.step);

    // main path = name/main.zig
    const mod = b.addModule(opts.name, .{
        .target = self.target,
        .optimize = self.optimize,
        .root_source_file = opts.root_source_file,
        .imports = opts.imports,
    });

    exe.root_module.addImport("main", mod);
    exe.root_module.addImport("Backlog", self.nw_mod);
    mod.addImport("Backlog", self.nw_mod);
    exe.root_module.addOptions("BacklogOptions", self.options);

    if (self.cookShaders) {}

    // I want to generate definitions from the spirv-reflect-tool during pre-build.
    //
    // shaders will now be part of content, not code. However. .zig code definitions will be generated
    // from content via the build.zig script.
    //
    // heres some thoughts:
    //
    // -Dupdate_shaders=true
    //
    // -Dupdate_shaders basically adds a run step to the build process 'python', 'tools/scripts/cook-shaders.py'
    //
    // 1. ensure spirv-reflect is compiled.
    // 2. shadercross cooker generates all shader outputs - specifically spv files. they are output to content/_shaders/
    // - search paths for shaders:
    // - content/shaders/**
    // - engine/<module names>/shaders/**
    // - shaders must have a unique name across the entire project, naming scheme should be <module>.shadername.<stage>.hlsl
    // 3. spirv-cross --reflect is called on each shader, updating the content/_shaders/defs/shader-name.json folder with up to date .json files
    //
    // final tree
    //
    // engine/
    //  - ui/
    //      - shaders/
    //          - papyrus.rect.vert.hlsl
    // content/
    //  - _shaders/
    //      - def/
    //          papyrus.rect.vert.json
    //      - msl/
    //          papyrus.rect.vert.msl
    //      - spv/
    //          papyrus.rect.vert.spv
    //      - dxil/
    //          papyrus.rect.vert.dxil
    //
    //
    // During build:
    // 1. spirv-reflect is called on each json in each folder and .zig shader interfaces added to the executable as a global import.
    // 2. if -Dupdate_shaders is not
    //
    // At any time:
    // asset-cooker will cook shaders and place them under content/_shaders/<msl|dxil|dxil>/<shader-name>.<msl|spv|dxil>

    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/triangle_mesh.vert"), "triangle_mesh_vert");
    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/default_lit.frag"), "default_lit");

    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/debug.vert"), "debug_vert");
    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/debug.frag"), "debug_frag");

    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/skybox/skybox.vert"), "skybox_vert");
    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/skybox/skybox.frag"), "skybox_frag");

    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/PapyrusRect.vert"), "papyrus_vk_vert");
    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/PapyrusRect.frag"), "papyrus_vk_frag");

    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/FontSDF.vert"), "FontSDF_vert");
    // self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/FontSDF.frag"), "FontSDF_frag");

    b.getInstallStep().dependOn(self.nw_builder.getInstallStep());

    return exe;
}

pub fn createGameOptions(b: *std.Build) *std.Build.Step.Options {
    const opts = b.addOptions();

    // build options for core.zig
    opts.addOption(
        bool,
        "mutex_job_queue",
        b.option(bool, "mutex_job_queue", "temporary test, reverts to old mutex based queue behaviour in jobs.zig:JobManager") orelse false,
    );
    opts.addOption(
        bool,
        "zero_logging",
        b.option(bool, "zero_logging", "disables all logging, only intended for use on job dispatch testing") orelse false,
    );
    opts.addOption(
        bool,
        "slow_logging",
        b.option(bool, "slow_logging", "Disables buffered logging, takes a hit to performance but gain timing information on logging") orelse false,
    );
    opts.addOption(
        bool,
        "force_mailbox",
        b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false,
    );
    opts.addOption(
        bool,
        "use_renderthread",
        true,
    );

    return opts;
}

// ========= standalone build instance =======
// maybe it should be an engine launcher or something..
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const spirvDep = b.dependency("SpirvReflect", .{
        .target = target,
        .optimize = optimize,
    });
    _ = spirvDep;

    const mod = b.addModule("Backlog", .{
        .target = target,
        .optimize = optimize,
        .root_source_file = b.path("engine/backlog.zig"),
    });

    for (engineDepList) |depName| {
        const dep = b.dependency(
            depName,
            .{
                .target = target,
                .optimize = optimize,
            },
        );
        mod.addImport(depName, dep.module(depName));
    }
}
