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

// User-authored compute shader for DOM mode.
//
// Contract:
//   - WGSL source authored by the user.
//   - Group 0 binding 0 is reserved for the UIDispatchHeader uniform
//     (with dynamic offset). The library writes it from the first 48
//     bytes of the push data each dispatch.
//   - Group 1 is reserved for the ping-pong textures: binding 0 is the
//     storage `out` (texture_storage_2d<rgba8unorm, write>), binding 1
//     is the sampled `prev` (texture_2d<f32>). The library auto-binds
//     the right textures depending on the current ping-pong state.
//   - Groups 2+ are user-defined. The user declares each binding via a
//     UICustomBinding descriptor at Load time, naming:
//        - the @group(N) and @binding(N) numbers,
//        - the resource KIND (buffer / sampled texture / sampler),
//        - the BYTE OFFSET in the per-dispatch push data where a
//          uint32 heap slot index lives.
//     At Dispatch time the renderer reads each declared slot out of
//     push data, looks the GPU handle up in the heap (bufferTable /
//     imageTable / samplerTable), and assembles the bind group.

export module Crafter.Graphics:WebGPUComputeShader;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :WebGPU;

export namespace Crafter {
    // Texel format for runtime-allocated textures and write-only storage-
    // texture bindings. Values are the wire contract with dom-webgpu.js's
    // texelFormatStr(); keep them in sync. rgba8unorm (0) is the default so
    // an unset `format` byte preserves prior behaviour.
    enum class WebGPUTexelFormat : std::uint8_t {
        RGBA8Unorm  = 0,
        RGBA16Float = 1,   // HDR post-process targets — filterable per spec
        R32Float    = 2,
    };

    enum class UICustomBindingKind : std::uint8_t {
        Buffer              = 0,   // read-only-storage SSBO, handle is a slot into heap.bufferTable
        SampledTexture      = 1,   // sampled texture_2d<f32>, handle is a slot into heap.imageTable
        Sampler             = 2,   // filtering sampler, handle is a slot into heap.samplerTable
        SampledTextureArray = 3,   // sampled texture_2d_array<f32>, handle is a slot into heap.imageTable
        // read-write storage SSBO (var<storage, read_write> in WGSL). Use
        // for buffers shaders need to MUTATE — e.g. physics shaders that
        // integrate node momentum, write brace stress, or output TLAS
        // instance transforms.
        BufferReadWrite     = 4,
        // write-only storage texture (texture_storage_2d<FMT, write> in
        // WGSL). The `format` field carries the WebGPUTexelFormat. Lets a
        // compute pass write a user-owned texture (e.g. an rgba16float bloom
        // mip) at an arbitrary group/binding — the missing primitive for an
        // HDR mip pyramid. Bind the same texture as SampledTexture to read
        // it back in a later pass.
        StorageTexture      = 5,
    };

    struct UICustomBinding {
        std::uint8_t  group;        // @group(N), must be >= 2 (0 and 1 are reserved)
        std::uint8_t  binding;      // @binding(N)
        UICustomBindingKind kind;
        std::uint8_t  format;       // WebGPUTexelFormat for StorageTexture; 0 otherwise
        std::uint32_t pushOffset;   // offset in push data where the slot uint32 lives
    };
    static_assert(sizeof(UICustomBinding) == 8);

    class WebGPUComputeShader {
    public:
        std::uint32_t pipelineHandle = 0;
        bool          rayQueryCapable = false;
        std::vector<UICustomBinding> customBindings;

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

        // Compile + link a custom compute shader. `wgsl` is the source
        // string; the library does NOT add anything to it (except when
        // `rayQuery` is true — then a RT prelude exposing the rayQuery*
        // API is prepended). The user's shader must declare
        // @group(0)/@group(1) bindings matching the contract above
        // (rayQuery shaders MUST NOT redeclare group(1)).
        // `bindings` lists every additional resource (groups 2+) that the
        // renderer should bind at dispatch time.
        void Load(std::string_view wgsl,
                  std::span<const UICustomBinding> bindings = {},
                  bool rayQuery = false);

        // Path-based overload for symmetry with the Vulkan ComputeShader.
        // Reads the file from disk (browser VFS) and forwards to Load(wgsl).
        void Load(const std::filesystem::path& wgslPath,
                  std::span<const UICustomBinding> bindings = {},
                  bool rayQuery = false);
    };
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
