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

// DOM-mode parallel to DescriptorHeapVulkan. WebGPU has no real bindless,
// so the "heap" is purely a CPU-side slot allocator with a side-table
// mapping slot → JS-side WebGPU handle. UIRenderer looks up the handle by
// slot at dispatch time to build (or fetch from cache) the bind group.

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

export namespace Crafter {
    struct DescriptorHeapWebGPU;

    struct DescriptorRange {
        std::uint16_t firstElement;
        std::uint16_t count;
    };

    class BufferSlot {
    public:
        DescriptorHeapWebGPU* heap = nullptr;
        std::uint16_t firstElement = 0xFFFF;
        BufferSlot() = default;
        BufferSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {}
        BufferSlot(const BufferSlot&) = delete;
        BufferSlot& operator=(const BufferSlot&) = delete;
        BufferSlot(BufferSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; }
        BufferSlot& operator=(BufferSlot&& o) noexcept;
        ~BufferSlot();
        explicit operator bool() const noexcept { return firstElement != 0xFFFF; }
        operator std::uint16_t() const noexcept { return firstElement; }
    };

    class ImageSlot {
    public:
        DescriptorHeapWebGPU* heap = nullptr;
        std::uint16_t firstElement = 0xFFFF;
        ImageSlot() = default;
        ImageSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {}
        ImageSlot(const ImageSlot&) = delete;
        ImageSlot& operator=(const ImageSlot&) = delete;
        ImageSlot(ImageSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; }
        ImageSlot& operator=(ImageSlot&& o) noexcept;
        ~ImageSlot();
        explicit operator bool() const noexcept { return firstElement != 0xFFFF; }
        operator std::uint16_t() const noexcept { return firstElement; }
    };

    class SamplerSlot {
    public:
        DescriptorHeapWebGPU* heap = nullptr;
        std::uint16_t firstElement = 0xFFFF;
        SamplerSlot() = default;
        SamplerSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {}
        SamplerSlot(const SamplerSlot&) = delete;
        SamplerSlot& operator=(const SamplerSlot&) = delete;
        SamplerSlot(SamplerSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; }
        SamplerSlot& operator=(SamplerSlot&& o) noexcept;
        ~SamplerSlot();
        explicit operator bool() const noexcept { return firstElement != 0xFFFF; }
        operator std::uint16_t() const noexcept { return firstElement; }
    };

    struct DescriptorHeapWebGPU {
        std::vector<WebGPUBufferRef>  bufferTable;
        std::vector<WebGPUTextureRef> imageTable;
        std::vector<WebGPUSamplerRef> samplerTable;

        std::vector<std::uint16_t> bufferFreelist;
        std::vector<std::uint16_t> imageFreelist;
        std::vector<std::uint16_t> samplerFreelist;

        std::uint16_t nextBuffer  = 0;
        std::uint16_t nextImage   = 0;
        std::uint16_t nextSampler = 0;

        void Initialize(std::uint16_t images, std::uint16_t buffers, std::uint16_t samplers) {
            imageTable.assign(images, 0);
            bufferTable.assign(buffers, 0);
            samplerTable.assign(samplers, 0);
            imageFreelist.reserve(images);
            bufferFreelist.reserve(buffers);
            samplerFreelist.reserve(samplers);
        }

        DescriptorRange AllocateBufferSlots(std::uint16_t count) {
            if (count == 1 && !bufferFreelist.empty()) {
                auto f = bufferFreelist.back(); bufferFreelist.pop_back();
                return { f, 1 };
            }
            if (nextBuffer + count > bufferTable.size()) {
                std::println("DescriptorHeapWebGPU: buffer slots exhausted");
                std::abort();
            }
            DescriptorRange r{ nextBuffer, count };
            nextBuffer = static_cast<std::uint16_t>(nextBuffer + count);
            return r;
        }
        DescriptorRange AllocateImageSlots(std::uint16_t count) {
            if (count == 1 && !imageFreelist.empty()) {
                auto f = imageFreelist.back(); imageFreelist.pop_back();
                return { f, 1 };
            }
            if (nextImage + count > imageTable.size()) {
                std::println("DescriptorHeapWebGPU: image slots exhausted");
                std::abort();
            }
            DescriptorRange r{ nextImage, count };
            nextImage = static_cast<std::uint16_t>(nextImage + count);
            return r;
        }
        DescriptorRange AllocateSamplerSlots(std::uint16_t count) {
            if (count == 1 && !samplerFreelist.empty()) {
                auto f = samplerFreelist.back(); samplerFreelist.pop_back();
                return { f, 1 };
            }
            if (nextSampler + count > samplerTable.size()) {
                std::println("DescriptorHeapWebGPU: sampler slots exhausted");
                std::abort();
            }
            DescriptorRange r{ nextSampler, count };
            nextSampler = static_cast<std::uint16_t>(nextSampler + count);
            return r;
        }

        void FreeBufferSlots(std::uint16_t first, std::uint16_t count) noexcept {
            for (std::uint16_t i = 0; i < count; ++i) {
                bufferTable[first + i] = 0;
                bufferFreelist.push_back(first + i);
            }
        }
        void FreeImageSlots(std::uint16_t first, std::uint16_t count) noexcept {
            for (std::uint16_t i = 0; i < count; ++i) {
                imageTable[first + i] = 0;
                imageFreelist.push_back(first + i);
            }
        }
        void FreeSamplerSlots(std::uint16_t first, std::uint16_t count) noexcept {
            for (std::uint16_t i = 0; i < count; ++i) {
                samplerTable[first + i] = 0;
                samplerFreelist.push_back(first + i);
            }
        }
    };

    // ─── slot dtors (defined here since they reference DescriptorHeapWebGPU) ─

    inline BufferSlot::~BufferSlot() {
        if (firstElement != 0xFFFF && heap) heap->FreeBufferSlots(firstElement, 1);
    }
    inline BufferSlot& BufferSlot::operator=(BufferSlot&& o) noexcept {
        if (this != &o) {
            if (firstElement != 0xFFFF && heap) heap->FreeBufferSlots(firstElement, 1);
            heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF;
        }
        return *this;
    }
    inline ImageSlot::~ImageSlot() {
        if (firstElement != 0xFFFF && heap) heap->FreeImageSlots(firstElement, 1);
    }
    inline ImageSlot& ImageSlot::operator=(ImageSlot&& o) noexcept {
        if (this != &o) {
            if (firstElement != 0xFFFF && heap) heap->FreeImageSlots(firstElement, 1);
            heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF;
        }
        return *this;
    }
    inline SamplerSlot::~SamplerSlot() {
        if (firstElement != 0xFFFF && heap) heap->FreeSamplerSlots(firstElement, 1);
    }
    inline SamplerSlot& SamplerSlot::operator=(SamplerSlot&& o) noexcept {
        if (this != &o) {
            if (firstElement != 0xFFFF && heap) heap->FreeSamplerSlots(firstElement, 1);
            heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF;
        }
        return *this;
    }

    // Convenience: create the "standard" linear-filter clamp-to-edge sampler,
    // allocate a slot for it, and return the slot. The wgpu* bridge call is
    // intentionally kept inside the library — example code shouldn't need to
    // reach into Crafter::WebGPU directly.
    inline SamplerSlot AllocateLinearClampSampler(DescriptorHeapWebGPU& heap) {
        DescriptorRange r = heap.AllocateSamplerSlots(1);
        heap.samplerTable[r.firstElement] = WebGPU::wgpuCreateLinearClampSampler();
        return SamplerSlot(&heap, r.firstElement);
    }

    // Same as AllocateLinearClampSampler but the address modes are
    // `repeat` instead of `clamp-to-edge`. Mip filtering is also linear.
    inline SamplerSlot AllocateLinearRepeatSampler(DescriptorHeapWebGPU& heap) {
        DescriptorRange r = heap.AllocateSamplerSlots(1);
        heap.samplerTable[r.firstElement] = WebGPU::wgpuCreateLinearRepeatSampler();
        return SamplerSlot(&heap, r.firstElement);
    }
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
