/*
 * Copyright 2025 Rive
 */

#pragma once

#include "rive/renderer/async_pipeline_manager.hpp"
#include "draw_pipeline_vulkan.hpp"
#include "render_pass_vulkan.hpp"

namespace rive::gpu
{
class PipelineManagerVulkan : public AsyncPipelineManager<DrawPipelineVulkan>
{
    using Super = AsyncPipelineManager<DrawPipelineVulkan>;

public:
    PipelineManagerVulkan(rcp<VulkanContext>,
                          ShaderCompilationMode,
                          VkImageView nullTextureView);
    ~PipelineManagerVulkan();

    RenderPassVulkan& getRenderPassSynchronous(InterlockMode,
                                               RenderPassOptionsVulkan,
                                               VkFormat,
                                               LoadAction);

    DrawPipelineLayoutVulkan& getDrawPipelineLayoutSynchronous(
        InterlockMode,
        RenderPassOptionsVulkan);

    uint32_t vendorID() const
    {
        return m_vk->physicalDeviceProperties().vendorID;
    }

    VkFormat atlasFormat() const { return m_atlasFormat; }

    VulkanContext* vulkanContext() const { return m_vk.get(); }
    VkDescriptorSetLayout perFlushDescriptorSetLayout() const
    {
        return m_perFlushDescriptorSetLayout;
    }
    VkDescriptorSetLayout perDrawDescriptorSetLayout() const
    {
        return m_perDrawDescriptorSetLayout;
    }
    VkDescriptorSetLayout immutableSamplerDescriptorSetLayout() const
    {
        return m_immutableSamplerDescriptorSetLayout;
    }
    VkDescriptorSetLayout emptyDescriptorSetLayout() const
    {
        return m_emptyDescriptorSetLayout;
    }

    VkSampler linearSampler() const { return m_linearSampler; }
    VkSampler imageSampler(uint32_t i) const { return m_imageSamplers[i]; }

    VkDescriptorSet nullImageDescriptorSet() const
    {
        return m_nullImageDescriptorSet;
    }
    VkDescriptorSet immutableSamplerDescriptorSet() const
    {
        return m_immutableSamplerDescriptorSet;
    }

    enum class PLSBackingType : bool
    {
        inputAttachment,
        storageTexture,
    };

    PLSBackingType plsBackingType(gpu::InterlockMode interlockMode)
    {
        if (interlockMode == gpu::InterlockMode::clockwise)
        {
            assert(m_vk->features.fragmentShaderPixelInterlock);
            return PLSBackingType::storageTexture;
        }
        return PLSBackingType::inputAttachment;
    }

private:
    virtual std::unique_ptr<DrawShaderVulkan> createVertexShader(
        DrawType drawType,
        ShaderFeatures shaderFeatures,
        InterlockMode interlockMode) override;

    virtual std::unique_ptr<DrawShaderVulkan> createFragmentShader(
        DrawType drawType,
        ShaderFeatures shaderFeatures,
        InterlockMode interlockMode,
        ShaderMiscFlags miscFlags) override;

    virtual std::unique_ptr<DrawPipelineVulkan> createPipeline(
        PipelineCreateType createType,
        uint64_t key,
        const PipelineProps& props) override;

    virtual PipelineStatus getPipelineStatus(
        const DrawPipelineVulkan&) const override;

    virtual void clearCacheInternal() override;

    std::unordered_map<uint32_t, std::unique_ptr<DrawPipelineLayoutVulkan>>
        m_drawPipelineLayouts;

    std::unordered_map<uint32_t, std::unique_ptr<RenderPassVulkan>>
        m_renderPasses;

    rcp<VulkanContext> m_vk;
    VkFormat m_atlasFormat;

    // Samplers.
    VkSampler m_linearSampler;
    VkSampler m_imageSamplers[ImageSampler::MAX_SAMPLER_PERMUTATIONS];

    // With the exception of PLS texture bindings, which differ by interlock
    // mode, all other shaders use the same shared descriptor set layouts.
    VkDescriptorSetLayout m_perFlushDescriptorSetLayout;
    VkDescriptorSetLayout m_perDrawDescriptorSetLayout;
    VkDescriptorSetLayout m_immutableSamplerDescriptorSetLayout;
    VkDescriptorSetLayout m_emptyDescriptorSetLayout; // For when a set isn't
                                                      // used by a shader.
    VkDescriptorPool m_staticDescriptorPool; // For descriptorSets that never
                                             // change between frames.
    VkDescriptorSet m_nullImageDescriptorSet;
    VkDescriptorSet m_immutableSamplerDescriptorSet; // Empty since samplers are
                                                     // immutable, but also
                                                     // required by Vulkan.
};

} // namespace rive::gpu
