import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;

extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
    std::vector<std::string> depArgs(args.begin(), args.end());

    Configuration* event = GitProject({
        .source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git" },
        .args = depArgs,
    });
    Configuration* math  = GitProject({
        .source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" },
        .args = depArgs,
    });
    Configuration* asset = GitProject({
        .source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git" },
        .args = depArgs,
    });

    Configuration cfg;
    cfg.path = "./";
    cfg.name = "Crafter.Graphics";
    cfg.outputName = "Crafter.Graphics";
    cfg.type = ConfigurationType::LibraryStatic;
    auto opts = ApplyStandardArgs(cfg, args);
    cfg.dependencies = { event, math, asset };

    // Window backend follows the target triple. V1 had separate lib-wayland /
    // lib-win32 configurations; V2 picks the right one automatically based on
    // where the build is going. Cross-compile (`--target=...`) flips the
    // backend along with everything else. The DOM backend is reached by any
    // wasm32-* target and produces a Vulkan-free build whose Window is wired
    // to a custom JS env (see additional/dom-env.js).
    bool dom = cfg.target.find("wasm") != std::string::npos;
    bool windows = !dom
        && (cfg.target.find("windows") != std::string::npos
            || cfg.target.find("mingw") != std::string::npos);
    if (dom) {
        cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_DOM", ""});
        // No native window libs, no Vulkan loader, no Wayland/X11. The JS
        // bridge satisfies every dynamic symbol via wasm imports. Crafter.Build
        // strips -march/-mtune from the clang command line for any wasm32-*
        // triple, so cfg.march/mtune can stay at their defaults — keeping them
        // matches the VariantId of dependency PCMs.
        //
        // WasmAlloc / WasmFree live in Crafter.Graphics-Dom.cpp and back
        // dom-env.js's __writeUtf8 path (every keyboard / text-input event
        // routes through them). The TU defines no symbols main.cpp would
        // reference, so wasm-ld dead-strips it from libCrafter.Graphics.a
        // for examples that don't touch the `Dom::HtmlElement*` API (like
        // Sponza). `--export=` both forces the export AND pulls the
        // defining .o in — solving both halves of the dead-strip problem.
        cfg.linkFlags.push_back("-Wl,--export=WasmAlloc");
        cfg.linkFlags.push_back("-Wl,--export=WasmFree");
    } else if (windows) {
        cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_WIN32", ""});
        cfg.linkFlags.push_back("-lkernel32");
        cfg.linkFlags.push_back("-luser32");
        cfg.linkFlags.push_back("-lgdi32");
        // Windows.Gaming.Input (WGI) needs the WinRT activation runtime
        // and combase for HSTRING / RoGetActivationFactory.
        cfg.linkFlags.push_back("-lruntimeobject");
        cfg.linkFlags.push_back("-lcombase");
    } else {
        cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_WAYLAND", ""});
        cfg.linkFlags.push_back("-lwayland-client");
        cfg.linkFlags.push_back("-lxkbcommon");
        // Gamepad: libudev for hot-plug + device enumeration; libevdev
        // for event parsing + axis calibration. libevdev ships its headers
        // under a versioned dir (libevdev-1.0/) so the -I is mandatory.
        cfg.linkFlags.push_back("-ludev");
        cfg.linkFlags.push_back("-levdev");
        cfg.compileFlags.push_back("-I/usr/include/libevdev-1.0");
        cfg.cFiles.push_back("lib/xdg-shell-protocol");
        cfg.cFiles.push_back("lib/wayland-xdg-decoration-unstable-v1-client-protocol");
        cfg.cFiles.push_back("lib/fractional-scale-v1");
        cfg.cFiles.push_back("lib/viewporter");
    }

    // Vulkan is the only renderer on native targets. Software fallback is
    // provided externally via the Vulkan loader (e.g. llvmpipe / lavapipe) —
    // no separate code path. The DOM backend doesn't render in V1 (the
    // UIRenderer and every Vulkan-typed module are excluded below); the
    // WebGPU follow-up will gain its own headers/loader rather than reuse
    // the Vulkan ones.
    if (!dom) {
        ExternalDependency& vkHeaders = cfg.externalDependencies.emplace_back();
        vkHeaders.name = "Vulkan-Headers";
        vkHeaders.source.url = "https://github.com/KhronosGroup/Vulkan-Headers.git";
        vkHeaders.builder = ExternalBuilder::None;
        vkHeaders.includeDirs = { "include" };
        ExternalDependency& vkUtility = cfg.externalDependencies.emplace_back();
        vkUtility.name = "Vulkan-Utility-Libraries";
        vkUtility.source.url = "https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git";
        vkUtility.builder = ExternalBuilder::None;
        vkUtility.includeDirs = { "include" };
        cfg.linkFlags.push_back(windows ? "-lvulkan-1" : "-lvulkan");
    }

    if (opts.Has("--timing")) cfg.defines.push_back({"CRAFTER_TIMING", ""});

    // One master interface list. Every partition exists on every target
    // — Crafter.Build's dependency scanner doesn't respect `#ifdef` on
    // `import :X` statements, so the partition file must be present even
    // when its body is gated out. Vulkan-typed partitions stub to empty
    // modules under CRAFTER_GRAPHICS_WINDOW_DOM; the Dom/DomEvents/Router
    // partitions stub to empty modules in the opposite direction.
    std::array<fs::path, 42> ifaces = {
        "interfaces/Crafter.Graphics",
        "interfaces/Crafter.Graphics-Animation",
        "interfaces/Crafter.Graphics-Clipboard",
        "interfaces/Crafter.Graphics-ComputeShader",
        "interfaces/Crafter.Graphics-Decompress",
        "interfaces/Crafter.Graphics-DescriptorHeapVulkan",
        "interfaces/Crafter.Graphics-DescriptorHeapWebGPU",
        "interfaces/Crafter.Graphics-Device",
        "interfaces/Crafter.Graphics-Dom",
        "interfaces/Crafter.Graphics-DomEvents",
        "interfaces/Crafter.Graphics-Font",
        "interfaces/Crafter.Graphics-FontAtlas",
        "interfaces/Crafter.Graphics-ForwardDeclarations",
        "interfaces/Crafter.Graphics-Gamepad",
        "interfaces/Crafter.Graphics-GraphicsTypes",
        "interfaces/Crafter.Graphics-Image2D",
        "interfaces/Crafter.Graphics-ImageVulkan",
        "interfaces/Crafter.Graphics-Input",
        "interfaces/Crafter.Graphics-InputField",
        "interfaces/Crafter.Graphics-Keys",
        "interfaces/Crafter.Graphics-Mesh",
        "interfaces/Crafter.Graphics-PipelineRTVulkan",
        "interfaces/Crafter.Graphics-PipelineRTWebGPU",
        "interfaces/Crafter.Graphics-PlainComputeShader",
        "interfaces/Crafter.Graphics-RenderingElement3D",
        "interfaces/Crafter.Graphics-RenderPass",
        "interfaces/Crafter.Graphics-Router",
        "interfaces/Crafter.Graphics-RT",
        "interfaces/Crafter.Graphics-RTPass",
        "interfaces/Crafter.Graphics-SamplerVulkan",
        "interfaces/Crafter.Graphics-ShaderBindingTableVulkan",
        "interfaces/Crafter.Graphics-ShaderBindingTableWebGPU",
        "interfaces/Crafter.Graphics-ShaderVulkan",
        "interfaces/Crafter.Graphics-Types",
        "interfaces/Crafter.Graphics-UI",
        "interfaces/Crafter.Graphics-UIComponents",
        "interfaces/Crafter.Graphics-VulkanBuffer",
        "interfaces/Crafter.Graphics-VulkanTransition",
        "interfaces/Crafter.Graphics-WebGPU",
        "interfaces/Crafter.Graphics-WebGPUBuffer",
        "interfaces/Crafter.Graphics-WebGPUComputeShader",
        "interfaces/Crafter.Graphics-Window",
    };

    if (dom) {
        // DOM impl set. UI-Shared.cpp is backend-agnostic; UI-WebGPU.cpp
        // is the DOM-only implementation of UIRenderer's GPU-touching
        // methods. Font / FontAtlas / UIComponents / InputField are now
        // portable.
        std::array<fs::path, 17> domImpls = {
            "implementations/Crafter.Graphics-Clipboard",
            "implementations/Crafter.Graphics-Dom",
            "implementations/Crafter.Graphics-Font",
            "implementations/Crafter.Graphics-FontAtlas",
            "implementations/Crafter.Graphics-Gamepad",
            "implementations/Crafter.Graphics-Input",
            "implementations/Crafter.Graphics-InputField",
            "implementations/Crafter.Graphics-Mesh-WebGPU",
            "implementations/Crafter.Graphics-PipelineRTWebGPU",
            "implementations/Crafter.Graphics-RenderingElement3D-WebGPU",
            "implementations/Crafter.Graphics-Router",
            "implementations/Crafter.Graphics-ShaderBindingTableWebGPU",
            "implementations/Crafter.Graphics-UI-Shared",
            "implementations/Crafter.Graphics-UI-WebGPU",
            "implementations/Crafter.Graphics-UIComponents",
            "implementations/Crafter.Graphics-WebGPUComputeShader",
            "implementations/Crafter.Graphics-Window",
        };
        cfg.GetInterfacesAndImplementations(ifaces, domImpls);
        // JS glue shipped alongside the .wasm so the loader has the
        // env-import surface the Window/Dom bindings expect.
        cfg.files.emplace_back(fs::path("additional/dom-env.js"));
        cfg.files.emplace_back(fs::path("additional/dom-webgpu.js"));
    } else {
        std::array<fs::path, 14> impls = {
            "implementations/Crafter.Graphics-Clipboard",
            "implementations/Crafter.Graphics-ComputeShader",
            "implementations/Crafter.Graphics-Device",
            "implementations/Crafter.Graphics-Font",
            "implementations/Crafter.Graphics-FontAtlas",
            "implementations/Crafter.Graphics-Gamepad",
            "implementations/Crafter.Graphics-Input",
            "implementations/Crafter.Graphics-InputField",
            "implementations/Crafter.Graphics-Mesh",
            "implementations/Crafter.Graphics-RenderingElement3D",
            "implementations/Crafter.Graphics-UI",
            "implementations/Crafter.Graphics-UI-Shared",
            "implementations/Crafter.Graphics-UIComponents",
            "implementations/Crafter.Graphics-Window",
        };
        cfg.GetInterfacesAndImplementations(ifaces, impls);

        cfg.shaders.emplace_back(fs::path("shaders/ui-quads.comp.glsl"),   std::string("main"), ShaderType::Compute);
        cfg.shaders.emplace_back(fs::path("shaders/ui-circles.comp.glsl"), std::string("main"), ShaderType::Compute);
        cfg.shaders.emplace_back(fs::path("shaders/ui-images.comp.glsl"),  std::string("main"), ShaderType::Compute);
        cfg.shaders.emplace_back(fs::path("shaders/ui-text.comp.glsl"),    std::string("main"), ShaderType::Compute);
        cfg.shaders.emplace_back(fs::path("shaders/ui-fused.comp.glsl"),   std::string("main"), ShaderType::Compute);
        cfg.buildFiles.emplace_back(fs::path("shaders/ui-shared.glsl"));

        // Regression test for issue #18: drive the NVIDIA descriptor-heap
        // AS-read workaround's SPIR-V rewrite over real compiled shaders and
        // check the result with spirv-val (one push-constant block, correct
        // TLAS offset). The test executable recompiles the whole module plus
        // tests/PushConstantRewrite/main.cpp; Configuration isn't copyable
        // (it owns the parsed module list), so the shared build settings are
        // mirrored field by field. glslang and spirv-val are invoked at
        // runtime, so the test declares them as required tools. Remove with
        // the rest of the workaround.
        Test pcTest;
        Configuration& tc = pcTest.config;
        tc.path                 = cfg.path;
        tc.name                 = "PushConstantRewrite";
        tc.outputName           = "PushConstantRewrite";
        tc.type                 = ConfigurationType::Executable;
        tc.target               = cfg.target;
        tc.march                = cfg.march;
        tc.mtune                = cfg.mtune;
        tc.debug                = cfg.debug;
        tc.sysroot              = cfg.sysroot;
        tc.dependencies         = cfg.dependencies;
        tc.externalDependencies = cfg.externalDependencies;
        tc.compileFlags         = cfg.compileFlags;
        tc.linkFlags            = cfg.linkFlags;
        tc.defines              = cfg.defines;
        tc.cFiles               = cfg.cFiles;
        std::vector<fs::path> testImpls(impls.begin(), impls.end());
        testImpls.emplace_back("tests/PushConstantRewrite/main");
        tc.GetInterfacesAndImplementations(ifaces, testImpls);
        pcTest.requires_ = { "tool:glslang", "tool:spirv-val" };
        cfg.tests.push_back(std::move(pcTest));

        // Regression test for issue #32: the Wayland scroll-wheel chain
        // (wl_pointer.axis → Window::onMouseScroll). Drives the listener
        // callback directly, so it needs no compositor — but it does need
        // the Wayland backend compiled in, hence Linux-only.
        if (!windows) {
            Test scrollTest;
            Configuration& sc = scrollTest.config;
            sc.path                 = cfg.path;
            sc.name                 = "MouseScroll";
            sc.outputName           = "MouseScroll";
            sc.type                 = ConfigurationType::Executable;
            sc.target               = cfg.target;
            sc.march                = cfg.march;
            sc.mtune                = cfg.mtune;
            sc.debug                = cfg.debug;
            sc.sysroot              = cfg.sysroot;
            sc.dependencies         = cfg.dependencies;
            sc.externalDependencies = cfg.externalDependencies;
            sc.compileFlags         = cfg.compileFlags;
            sc.linkFlags            = cfg.linkFlags;
            sc.defines              = cfg.defines;
            sc.cFiles               = cfg.cFiles;
            std::vector<fs::path> scrollImpls(impls.begin(), impls.end());
            scrollImpls.emplace_back("tests/MouseScroll/main");
            sc.GetInterfacesAndImplementations(ifaces, scrollImpls);
            cfg.tests.push_back(std::move(scrollTest));

            // Issue #40: multi-frame-in-flight frame pacing (per-image fences
            // + per-frame semaphores, no steady-state wait-idle). Drives the
            // real frame loop against a live Wayland compositor for many more
            // frames than there are in-flight slots and asserts the validation
            // layer stays silent — the old singleton-semaphore design only
            // avoided being an active race because the wait-idle masked it, so
            // a clean multi-frame run is the regression guard. Needs the
            // Wayland backend + a real compositor, hence inside the !windows
            // block alongside MouseScroll.
            Test frameLoopTest;
            Configuration& fl = frameLoopTest.config;
            fl.path                 = cfg.path;
            fl.name                 = "FrameLoopSync";
            fl.outputName           = "FrameLoopSync";
            fl.type                 = ConfigurationType::Executable;
            fl.target               = cfg.target;
            fl.march                = cfg.march;
            fl.mtune                = cfg.mtune;
            fl.debug                = cfg.debug;
            fl.sysroot              = cfg.sysroot;
            fl.dependencies         = cfg.dependencies;
            fl.externalDependencies = cfg.externalDependencies;
            fl.compileFlags         = cfg.compileFlags;
            fl.linkFlags            = cfg.linkFlags;
            fl.defines              = cfg.defines;
            fl.cFiles               = cfg.cFiles;
            std::vector<fs::path> frameLoopImpls(impls.begin(), impls.end());
            frameLoopImpls.emplace_back("tests/FrameLoopSync/main");
            fl.GetInterfacesAndImplementations(ifaces, frameLoopImpls);
            cfg.tests.push_back(std::move(frameLoopTest));
        }

        // Issue #36: BLAS build options. Drives the real hardware AS-build
        // path — records Mesh::Build / Refit / BuildProcedural /
        // RefitProcedural with fast-build/fast-trace + allow-update flags
        // into one-time command buffers and submits them, asserting the
        // requested flags land, that an allowUpdate refit keeps the AS
        // handle (in-place UPDATE), and that the validation layer reports no
        // errors. Needs a Vulkan RT device at runtime (same as the RT
        // examples), so it shares the native build settings.
        Test blasTest;
        Configuration& bc = blasTest.config;
        bc.path                 = cfg.path;
        bc.name                 = "BLASBuildOptions";
        bc.outputName           = "BLASBuildOptions";
        bc.type                 = ConfigurationType::Executable;
        bc.target               = cfg.target;
        bc.march                = cfg.march;
        bc.mtune                = cfg.mtune;
        bc.debug                = cfg.debug;
        bc.sysroot              = cfg.sysroot;
        bc.dependencies         = cfg.dependencies;
        bc.externalDependencies = cfg.externalDependencies;
        bc.compileFlags         = cfg.compileFlags;
        bc.linkFlags            = cfg.linkFlags;
        bc.defines              = cfg.defines;
        bc.cFiles               = cfg.cFiles;
        std::vector<fs::path> blasImpls(impls.begin(), impls.end());
        blasImpls.emplace_back("tests/BLASBuildOptions/main");
        bc.GetInterfacesAndImplementations(ifaces, blasImpls);
        cfg.tests.push_back(std::move(blasTest));

        // Issue #51: FontAtlas only re-uploads the dirty sub-rect now,
        // tracked via FontAtlas::DirtyRect. The accumulation/clamp math is
        // pure CPU, so this test drives it directly — no GPU device needed
        // at runtime (the real copy params are covered by HelloUI rendering
        // text on both backends).
        Test atlasTest;
        Configuration& ac = atlasTest.config;
        ac.path                 = cfg.path;
        ac.name                 = "FontAtlasDirtyRect";
        ac.outputName           = "FontAtlasDirtyRect";
        ac.type                 = ConfigurationType::Executable;
        ac.target               = cfg.target;
        ac.march                = cfg.march;
        ac.mtune                = cfg.mtune;
        ac.debug                = cfg.debug;
        ac.sysroot              = cfg.sysroot;
        ac.dependencies         = cfg.dependencies;
        ac.externalDependencies = cfg.externalDependencies;
        ac.compileFlags         = cfg.compileFlags;
        ac.linkFlags            = cfg.linkFlags;
        ac.defines              = cfg.defines;
        ac.cFiles               = cfg.cFiles;
        std::vector<fs::path> atlasImpls(impls.begin(), impls.end());
        atlasImpls.emplace_back("tests/FontAtlasDirtyRect/main");
        ac.GetInterfacesAndImplementations(ifaces, atlasImpls);
        cfg.tests.push_back(std::move(atlasTest));

        // Issue #52: shaped-run cache for UIRenderer::ShapeText. Shapes a
        // string against a real CPU-side FontAtlas and asserts that a cache
        // hit is byte-equivalent to the uncached path, that hits don't touch
        // the atlas, and that translate / alignment / truncation /
        // InvalidateFont all behave. Needs a headless Vulkan device (the
        // atlas image is a real GPU image) but no swapchain — same native
        // build settings as the others. The font file is copied next to the
        // binary; the test also probes the project-root path.
        Test textTest;
        Configuration& xc = textTest.config;
        xc.path                 = cfg.path;
        xc.name                 = "ShapeTextCache";
        xc.outputName           = "ShapeTextCache";
        xc.type                 = ConfigurationType::Executable;
        xc.target               = cfg.target;
        xc.march                = cfg.march;
        xc.mtune                = cfg.mtune;
        xc.debug                = cfg.debug;
        xc.sysroot              = cfg.sysroot;
        xc.dependencies         = cfg.dependencies;
        xc.externalDependencies = cfg.externalDependencies;
        xc.compileFlags         = cfg.compileFlags;
        xc.linkFlags            = cfg.linkFlags;
        xc.defines              = cfg.defines;
        xc.cFiles               = cfg.cFiles;
        xc.files                = { fs::path("tests/ShapeTextCache/font.ttf") };
        std::vector<fs::path> textImpls(impls.begin(), impls.end());
        textImpls.emplace_back("tests/ShapeTextCache/main");
        xc.GetInterfacesAndImplementations(ifaces, textImpls);
        cfg.tests.push_back(std::move(textTest));

        // Issue #59: Device::GetMemoryType gained a `preferred` mask and a
        // required-only fallback so callers combining a mandatory flag with a
        // perf-only one (HOST_VISIBLE | DEVICE_LOCAL descriptor heaps) no
        // longer throw on devices without a host-visible device-local heap.
        // The selection is pure CPU logic over Device::memoryProperties, so
        // this test installs synthetic memory layouts and drives it directly —
        // no GPU device needed at runtime.
        Test memTest;
        Configuration& mc = memTest.config;
        mc.path                 = cfg.path;
        mc.name                 = "MemoryTypeFallback";
        mc.outputName           = "MemoryTypeFallback";
        mc.type                 = ConfigurationType::Executable;
        mc.target               = cfg.target;
        mc.march                = cfg.march;
        mc.mtune                = cfg.mtune;
        mc.debug                = cfg.debug;
        mc.sysroot              = cfg.sysroot;
        mc.dependencies         = cfg.dependencies;
        mc.externalDependencies = cfg.externalDependencies;
        mc.compileFlags         = cfg.compileFlags;
        mc.linkFlags            = cfg.linkFlags;
        mc.defines              = cfg.defines;
        mc.cFiles               = cfg.cFiles;
        std::vector<fs::path> memImpls(impls.begin(), impls.end());
        memImpls.emplace_back("tests/MemoryTypeFallback/main");
        mc.GetInterfacesAndImplementations(ifaces, memImpls);
        cfg.tests.push_back(std::move(memTest));

        // Issue #47: the fused UI uber-kernel (shaders/ui-fused.comp.glsl) and
        // its C++ push-constant mirror UIFusedHeader. Compiles the real shader
        // with glslang, validates with spirv-val, and pins the push-constant
        // member offsets to UIFusedHeader's layout so a GLSL/C++ drift can't
        // slip through (the C++ static_assert only guards the C++ side). No GPU
        // device at runtime, but glslang + spirv-val are required tools.
        Test fusedTest;
        Configuration& ftc = fusedTest.config;
        ftc.path                 = cfg.path;
        ftc.name                 = "UIFusedShader";
        ftc.outputName           = "UIFusedShader";
        ftc.type                 = ConfigurationType::Executable;
        ftc.target               = cfg.target;
        ftc.march                = cfg.march;
        ftc.mtune                = cfg.mtune;
        ftc.debug                = cfg.debug;
        ftc.sysroot              = cfg.sysroot;
        ftc.dependencies         = cfg.dependencies;
        ftc.externalDependencies = cfg.externalDependencies;
        ftc.compileFlags         = cfg.compileFlags;
        ftc.linkFlags            = cfg.linkFlags;
        ftc.defines              = cfg.defines;
        ftc.cFiles               = cfg.cFiles;
        std::vector<fs::path> fusedImpls(impls.begin(), impls.end());
        fusedImpls.emplace_back("tests/UIFusedShader/main");
        ftc.GetInterfacesAndImplementations(ifaces, fusedImpls);
        fusedTest.requires_ = { "tool:glslang", "tool:spirv-val" };
        cfg.tests.push_back(std::move(fusedTest));

        // Issue #60: VulkanBuffer::FlushDevice / FlushHost now record the chosen
        // memory type's propertyFlags at Create time and skip the
        // flush/invalidate when the memory is HOST_COHERENT. The gate is pure
        // logic over the recorded flags, so this test stamps them directly and
        // verifies the coherent path issues no Vulkan call — no GPU device
        // needed at runtime.
        Test flushTest;
        Configuration& fc = flushTest.config;
        fc.path                 = cfg.path;
        fc.name                 = "VulkanBufferFlushGate";
        fc.outputName           = "VulkanBufferFlushGate";
        fc.type                 = ConfigurationType::Executable;
        fc.target               = cfg.target;
        fc.march                = cfg.march;
        fc.mtune                = cfg.mtune;
        fc.debug                = cfg.debug;
        fc.sysroot              = cfg.sysroot;
        fc.dependencies         = cfg.dependencies;
        fc.externalDependencies = cfg.externalDependencies;
        fc.compileFlags         = cfg.compileFlags;
        fc.linkFlags            = cfg.linkFlags;
        fc.defines              = cfg.defines;
        fc.cFiles               = cfg.cFiles;
        std::vector<fs::path> flushImpls(impls.begin(), impls.end());
        flushImpls.emplace_back("tests/VulkanBufferFlushGate/main");
        fc.GetInterfacesAndImplementations(ifaces, flushImpls);
        cfg.tests.push_back(std::move(flushTest));

        // Issue #63: VulkanBuffer::Resize now reuses the existing allocation in
        // place when a new request still fits the created capacity and the
        // immutable-at-create properties match (usage flags fixed at create;
        // chosen memory type still satisfies the required flags), instead of
        // always destroying + reallocating. The reuse guard is pure logic over
        // recorded fields, so this test stamps a fake handle + capacity/flags and
        // verifies the in-place path issues no Vulkan call — no GPU device needed.
        Test resizeTest;
        Configuration& rc = resizeTest.config;
        rc.path                 = cfg.path;
        rc.name                 = "VulkanBufferResizeReuse";
        rc.outputName           = "VulkanBufferResizeReuse";
        rc.type                 = ConfigurationType::Executable;
        rc.target               = cfg.target;
        rc.march                = cfg.march;
        rc.mtune                = cfg.mtune;
        rc.debug                = cfg.debug;
        rc.sysroot              = cfg.sysroot;
        rc.dependencies         = cfg.dependencies;
        rc.externalDependencies = cfg.externalDependencies;
        rc.compileFlags         = cfg.compileFlags;
        rc.linkFlags            = cfg.linkFlags;
        rc.defines              = cfg.defines;
        rc.cFiles               = cfg.cFiles;
        std::vector<fs::path> resizeImpls(impls.begin(), impls.end());
        resizeImpls.emplace_back("tests/VulkanBufferResizeReuse/main");
        rc.GetInterfacesAndImplementations(ifaces, resizeImpls);
        cfg.tests.push_back(std::move(resizeTest));

        // Issue #89: Device::PreferDirectDeviceWrite chooses the upload strategy
        // for a CPU-written, GPU-read buffer — direct HOST_VISIBLE|DEVICE_LOCAL
        // map+write on ReBAR/UMA vs. staged-into-pure-DEVICE_LOCAL on a small
        // BAR window (#58). The decision is pure CPU logic over
        // Device::memoryProperties (types + heaps), so this test installs
        // synthetic ReBAR / UMA / small-window / no-BAR layouts and drives it
        // directly — no GPU device needed at runtime.
        Test uploadTest;
        Configuration& uc = uploadTest.config;
        uc.path                 = cfg.path;
        uc.name                 = "UploadStrategy";
        uc.outputName           = "UploadStrategy";
        uc.type                 = ConfigurationType::Executable;
        uc.target               = cfg.target;
        uc.march                = cfg.march;
        uc.mtune                = cfg.mtune;
        uc.debug                = cfg.debug;
        uc.sysroot              = cfg.sysroot;
        uc.dependencies         = cfg.dependencies;
        uc.externalDependencies = cfg.externalDependencies;
        uc.compileFlags         = cfg.compileFlags;
        uc.linkFlags            = cfg.linkFlags;
        uc.defines              = cfg.defines;
        uc.cFiles               = cfg.cFiles;
        std::vector<fs::path> uploadImpls(impls.begin(), impls.end());
        uploadImpls.emplace_back("tests/UploadStrategy/main");
        uc.GetInterfacesAndImplementations(ifaces, uploadImpls);
        cfg.tests.push_back(std::move(uploadTest));

        // Issue #57: Font::GetLineWidth memoises per-codepoint advances in
        // font units (Font::AdvanceUnits) and rescales per call, instead of
        // calling stbtt_GetCodepointHMetrics for every glyph on every caret
        // query. Pure CPU — Font only touches stb_truetype — so this drives
        // the public API directly with no Vulkan device. The font file is
        // copied next to the binary; the test also probes the project root.
        Test advTest;
        Configuration& vc = advTest.config;
        vc.path                 = cfg.path;
        vc.name                 = "FontAdvanceCache";
        vc.outputName           = "FontAdvanceCache";
        vc.type                 = ConfigurationType::Executable;
        vc.target               = cfg.target;
        vc.march                = cfg.march;
        vc.mtune                = cfg.mtune;
        vc.debug                = cfg.debug;
        vc.sysroot              = cfg.sysroot;
        vc.dependencies         = cfg.dependencies;
        vc.externalDependencies = cfg.externalDependencies;
        vc.compileFlags         = cfg.compileFlags;
        vc.linkFlags            = cfg.linkFlags;
        vc.defines              = cfg.defines;
        vc.cFiles               = cfg.cFiles;
        vc.files                = { fs::path("tests/FontAdvanceCache/font.ttf") };
        std::vector<fs::path> advImpls(impls.begin(), impls.end());
        advImpls.emplace_back("tests/FontAdvanceCache/main");
        vc.GetInterfacesAndImplementations(ifaces, advImpls);
        cfg.tests.push_back(std::move(advTest));

        // Issue #50: uiResolveScreenPixel now gates its per-pixel clip-rect
        // compares on the reserved kUIFlagClip bit, which FillHeader sets only
        // when the clip rect is narrower than the surface. The decision lives
        // in UIRenderer::ClipFlags — pure CPU logic over the clip rect and the
        // surface size — so this test drives it directly with synthetic
        // dimensions, no Window or GPU device needed at runtime.
        Test clipTest;
        Configuration& clc = clipTest.config;
        clc.path                 = cfg.path;
        clc.name                 = "UIClipFlag";
        clc.outputName           = "UIClipFlag";
        clc.type                 = ConfigurationType::Executable;
        clc.target               = cfg.target;
        clc.march                = cfg.march;
        clc.mtune                = cfg.mtune;
        clc.debug                = cfg.debug;
        clc.sysroot              = cfg.sysroot;
        clc.dependencies         = cfg.dependencies;
        clc.externalDependencies = cfg.externalDependencies;
        clc.compileFlags         = cfg.compileFlags;
        clc.linkFlags            = cfg.linkFlags;
        clc.defines              = cfg.defines;
        clc.cFiles               = cfg.cFiles;
        std::vector<fs::path> clipImpls(impls.begin(), impls.end());
        clipImpls.emplace_back("tests/UIClipFlag/main");
        clc.GetInterfacesAndImplementations(ifaces, clipImpls);
        cfg.tests.push_back(std::move(clipTest));

        // Issue #56: InputField_HitTestCursor mapped a click x to a cursor byte
        // offset by re-walking the prefix for every boundary (O(n^2) glyph
        // metric lookups) over raw byte boundaries. It now delegates to
        // Font::NearestCursorByte — one cumulative-advance pass over codepoint
        // boundaries. The mapping is pure CPU over a TrueType file, so this test
        // drives it directly: no Vulkan device or window. The font is copied
        // next to the binary; the test also probes the project-root path.
        Test hitTest;
        Configuration& hc = hitTest.config;
        hc.path                 = cfg.path;
        hc.name                 = "InputFieldHitTest";
        hc.outputName           = "InputFieldHitTest";
        hc.type                 = ConfigurationType::Executable;
        hc.target               = cfg.target;
        hc.march                = cfg.march;
        hc.mtune                = cfg.mtune;
        hc.debug                = cfg.debug;
        hc.sysroot              = cfg.sysroot;
        hc.dependencies         = cfg.dependencies;
        hc.externalDependencies = cfg.externalDependencies;
        hc.compileFlags         = cfg.compileFlags;
        hc.linkFlags            = cfg.linkFlags;
        hc.defines              = cfg.defines;
        hc.cFiles               = cfg.cFiles;
        hc.files                = { fs::path("tests/InputFieldHitTest/font.ttf") };
        std::vector<fs::path> hitImpls(impls.begin(), impls.end());
        hitImpls.emplace_back("tests/InputFieldHitTest/main");
        hc.GetInterfacesAndImplementations(ifaces, hitImpls);
        cfg.tests.push_back(std::move(hitTest));
    }

    return cfg;
}
