/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
*/

// Standalone compute pipeline. Dispatches at any point in the frame
// (inside or outside the UI render pass) via the JS bridge's
// wgpuDispatchCompute, which mirrors the wgpuBuildTLAS pattern of
// attaching to the active encoder when one exists or creating an
// ephemeral encoder+submit when not.
//
// This is the WebGPU counterpart to the Vulkan `:ComputeShader` partition.
// They expose the same conceptual API — Load + Dispatch — but with
// backend-specific binding plumbing. See `:GraphicsTypes` for the
// `GraphicsComputeShader` alias picking the right one per target.
//
// WGSL contract:
//   @group(0) @binding(0) uniform PushData    // optional; only if pushUniformSize>0
//   @group(1+) @binding(N)                    // user bindings via UICustomBinding
// When rayQuery is on, @group(1) is reserved for the RT heap; user
// bindings start at @group(2).

module;
export module Crafter.Graphics:PlainComputeShader;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :WebGPU;
import :WebGPUComputeShader;  // for UICustomBinding + UICustomBindingKind

export namespace Crafter {
    class PlainComputeShader {
    public:
        std::uint32_t pipelineHandle  = 0;
        std::uint32_t pushUniformSize = 0;
        bool          rayQueryCapable = false;
        std::vector<UICustomBinding> customBindings;

        PlainComputeShader() = default;
        PlainComputeShader(const PlainComputeShader&) = delete;
        PlainComputeShader& operator=(const PlainComputeShader&) = delete;
        PlainComputeShader(PlainComputeShader&& o) noexcept
            : pipelineHandle(o.pipelineHandle),
              pushUniformSize(o.pushUniformSize),
              rayQueryCapable(o.rayQueryCapable),
              customBindings(std::move(o.customBindings)) {
            o.pipelineHandle = 0;
        }

        // Compile + link a standalone compute shader.
        //   wgsl             — source.
        //   pushUniformSize  — byte size of the @group(0)@binding(0) uniform
        //                      struct, or 0 if the shader doesn't declare one.
        //   bindings         — every user-declared resource the dispatch
        //                      should bind (groups 1+ if no rayQuery, 2+ if
        //                      rayQuery). Order MUST match `handles` at
        //                      Dispatch time.
        //   rayQuery         — prepend the RT prelude + rayQuery library
        //                      so the shader can call `rayQuery*` helpers.
        void Load(std::string_view wgsl,
                  std::uint32_t pushUniformSize_,
                  std::span<const UICustomBinding> bindings = {},
                  bool rayQuery = false) {
            pushUniformSize = pushUniformSize_;
            rayQueryCapable = rayQuery;
            customBindings.assign(bindings.begin(), bindings.end());
            pipelineHandle = WebGPU::wgpuLoadComputePipeline(
                wgsl.data(), static_cast<std::int32_t>(wgsl.size()),
                static_cast<std::int32_t>(pushUniformSize),
                customBindings.empty() ? nullptr : customBindings.data(),
                static_cast<std::int32_t>(customBindings.size()),
                rayQuery ? 1 : 0);
        }

        void Load(const std::filesystem::path& wgslPath,
                  std::uint32_t pushUniformSize_,
                  std::span<const UICustomBinding> bindings = {},
                  bool rayQuery = false) {
            std::ifstream f(wgslPath, std::ios::binary);
            if (!f) {
                std::println(std::cerr,
                    "PlainComputeShader::Load: cannot open {}", wgslPath.string());
                std::abort();
            }
            std::string wgsl((std::istreambuf_iterator<char>(f)),
                              std::istreambuf_iterator<char>());
            Load(std::string_view{wgsl}, pushUniformSize_, bindings, rayQuery);
        }

        // Bind, push, dispatch. `handles` is parallel to the
        // UICustomBinding[] passed at Load — order matches.
        void Dispatch(const void* push, std::uint32_t pushBytes,
                      std::span<const std::uint32_t> handles,
                      std::uint32_t gx,
                      std::uint32_t gy = 1,
                      std::uint32_t gz = 1) const {
            if (pipelineHandle == 0) return;
            WebGPU::wgpuDispatchCompute(
                pipelineHandle,
                push, static_cast<std::int32_t>(pushBytes),
                handles.empty() ? nullptr : handles.data(),
                static_cast<std::int32_t>(handles.size()),
                static_cast<std::int32_t>(gx),
                static_cast<std::int32_t>(gy),
                static_cast<std::int32_t>(gz));
        }
    };
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
