#pragma once
#include "D3D12CommonHeader.h"

namespace XEngine::graphics::d3d12 {

/**
 * @brief 前向声明，用于友元类关系
 */
class descriptor_heap;

/**
 * @struct descriptor_handle
 * @brief 封装 CPU 和 GPU 描述符句柄的包装结构体
 *
 * @details
 * 该结构体为 D3D12 中的描述符句柄管理提供统一接口。
 * 描述符句柄表示描述符堆中特定描述符的指针。
 * 
 * - **CPU 句柄**：CPU 用于创建和修改描述符视图（SRV、UAV、CBV、RTV、DSV）
 * - **GPU 句柄**：GPU 在着色器执行期间访问描述符（仅着色器可见）
 *
 * @note
 * - CPU 句柄在分配后始终有效
 * - GPU 句柄仅在从着色器可见的描述符堆分配时有效
 * - DEBUG 模式下存储额外的跟踪信息用于验证和调试
 *
 * @see descriptor_heap
 * @see D3D12_CPU_DESCRIPTOR_HANDLE
 * @see D3D12_GPU_DESCRIPTOR_HANDLE
 *
 * @example
 * @code
 * descriptor_handle handle = heap.allocate();
 * if (handle.is_valid()) {
 *     device->CreateShaderResourceView(texture, &srvDesc, handle.cpu);
 *     if (handle.is_shader_visible()) {
 *         cmdList->SetGraphicsRootDescriptorTable(0, handle.gpu);
 *     }
 * }
 * @endcode
 */
struct descriptor_handle
{
    /**
     * @brief CPU 端描述符句柄
     * 
     * @details
     * 用于 CPU 端操作，如创建视图（CreateShaderResourceView、
     * CreateUnorderedAccessView、CreateRenderTargetView 等）。
     * 从任何类型的描述符堆成功分配后，此句柄始终有效。
     */
    D3D12_CPU_DESCRIPTOR_HANDLE cpu{};

    /**
     * @brief GPU 端描述符句柄
     * 
     * @details
     * 用于通过根描述符表将描述符绑定到图形管线。
     * 仅当从着色器可见的描述符堆分配时有效
     * （CBV_SRV_UAV 或 SAMPLER 类型，带有 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE）。
     * 
     * @note 值为 0 表示此句柄不是着色器可见的
     */
    D3D12_GPU_DESCRIPTOR_HANDLE gpu{};

    /**
     * @brief 检查此句柄是否指向有效的描述符
     * 
     * @return 如果 CPU 句柄非空则返回 true，否则返回 false
     * 
     * @note 有效句柄不保证底层描述符已正确初始化
     */
    constexpr bool is_valid() const { return cpu.ptr != 0; }

    /**
     * @brief 检查此句柄是否可从 GPU 着色器访问
     * 
     * @return 如果 GPU 句柄非空（着色器可见）则返回 true，否则返回 false
     * 
     * @details
     * 着色器可见句柄可通过 SetGraphicsRootDescriptorTable 或
     * SetComputeRootDescriptorTable 绑定到管线。非着色器可见句柄（RTV、DSV）
     * 只能通过 OMSetRenderTargets 或类似方法绑定。
     */
    constexpr bool is_shader_visible() const { return gpu.ptr != 0; }

#ifdef _DEBUG
private:
    /**
     * @brief 指向所属描述符堆的指针（仅 DEBUG 模式）
     * 
     * @details
     * 用于验证以确保句柄被释放到正确的堆。
     * 此指针在分配时设置，在释放操作时验证。
     */
    friend class descriptor_heap;
    descriptor_heap* container{nullptr};

    /**
     * @brief 描述符堆内的索引（仅 DEBUG 模式）
     * 
     * @details
     * 存储此描述符在其所属堆中的逻辑索引。
     * 用于调试和验证目的，跟踪描述符分配情况。
     */
    u32 index{u32_invalid_id};
#endif
};

/**
 * @class descriptor_heap
 * @brief 管理 D3D12 描述符堆的分配、释放和生命周期
 *
 * @details
 * 该类为 D3D12 描述符堆提供高级抽象，实现：
 * 
 * - **池化分配**：基于空闲列表管理，实现 O(1) 分配/释放
 * - **线程安全**：互斥锁保护操作，支持多线程资源创建
 * - **延迟释放**：帧感知的资源生命周期管理，防止 GPU 危险
 * - **类型安全**：通过构造函数参数实现编译时类型绑定
 *
 * ## 描述符堆类型
 * 
 * | 类型 | 着色器可见 | 典型用途 |
 * |------|------------|----------|
 * | CBV_SRV_UAV | 可选 | 常量缓冲区、纹理、UAV |
 * | SAMPLER | 可选 | 纹理采样器 |
 * | RTV | 否 | 渲染目标视图 |
 * | DSV | 否 | 深度模板视图 |
 *
 * ## 内存模型
 * 
 * - **着色器可见堆**：分配在 GPU 可见内存（显存或映射到 GPU 的系统内存）
 * - **非着色器可见堆**：仅分配在系统内存（CPU 可访问）
 *
 * ## 线程安全
 * 
 * 所有公有方法都是线程安全的。多个线程可以同时从同一个堆分配和释放描述符。
 * 内部互斥锁确保操作的原子性。
 *
 * ## 延迟释放机制
 * 
 * 当描述符被释放时，不会立即返回到空闲池。而是放入帧索引的延迟释放队列。
 * 当对应帧在 GPU 上完成后，调用 process_deferred_release() 时，描述符才会
 * 返回到空闲池，确保不会发生 GPU-GPU 或 CPU-GPU 危险。
 *
 * @see descriptor_handle
 * @see D3D12_DESCRIPTOR_HEAP_TYPE
 * @see ID3D12DescriptorHeap
 *
 * @example
 * @code
 * // 创建一个包含 1024 个描述符的着色器可见 CBV/SRV/UAV 堆
 * descriptor_heap cbv_srv_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
 * cbv_srv_heap.initialize(1024, true);
 * 
 * // 分配描述符
 * descriptor_handle tex_handle = cbv_srv_heap.allocate();
 * device->CreateShaderResourceView(texture, &srvDesc, tex_handle.cpu);
 * 
 * // 绑定到管线
 * ID3D12DescriptorHeap* heaps[] = {cbv_srv_heap.heap()};
 * cmdList->SetDescriptorHeaps(1, heaps);
 * cmdList->SetGraphicsRootDescriptorTable(0, tex_handle.gpu);
 * 
 * // 不再需要时释放（延迟到帧完成）
 * cbv_srv_heap.free(tex_handle);
 * @endcode
 */
class descriptor_heap
{
public:
    /**
     * @brief 构造指定类型的描述符堆
     * 
     * @param type D3D12 描述符堆类型（CBV_SRV_UAV、SAMPLER、RTV 或 DSV）
     * 
     * @note 此构造函数不分配任何资源。调用 initialize() 创建堆
     */
    explicit descriptor_heap(const D3D12_DESCRIPTOR_HEAP_TYPE type) : _type(type) {}

    /**
     * @brief 禁用拷贝构造以防止资源重复
     */
    DISABLE_COPY_AND_MOVE(descriptor_heap);

    /**
     * @brief 析构函数，断言所有资源已释放
     * 
     * @note 堆必须在析构前通过 release() 显式释放
     */
    ~descriptor_heap() { assert(!_heap); }

    /**
     * @brief 初始化具有指定容量的描述符堆
     * 
     * @param capacity 此堆可容纳的最大描述符数量
     * @param is_shader_visible 堆是否应着色器可见
     * 
     * @return 初始化成功返回 true，否则返回 false
     * 
     * @details
     * - 对于 RTV/DSV 堆，is_shader_visible 强制为 false（设计上 GPU 不可访问）
     * - 容量不得超过 D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2
     * - 对于采样器堆，容量不得超过 D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE
     * 
     * @pre 堆必须未初始化（如需重新初始化，先调用 release()）
     * @post 堆已准备好进行 allocate() 调用
     */
    bool initialize(u32 capacity, bool is_shader_visible);

    /**
     * @brief 处理特定帧的延迟释放
     * 
     * @param frame_index 应处理其延迟释放的帧索引
     * 
     * @details
     * 此方法应在新帧开始时调用，确认 GPU 已完成对应帧的处理。
     * 它将该帧期间释放的所有描述符返回到空闲池。
     * 
     * @pre GPU 必须已完成指定帧（通过 Fence 同步确认）
     */
    void process_deferred_release(u32 frame_index);

    /**
     * @brief 释放此堆持有的所有资源
     * 
     * @details
     * - 通过 flush() 等待所有帧完成
     * - 释放底层 ID3D12DescriptorHeap
     * - 清除所有内部跟踪结构
     * 
     * @post 堆处于未初始化状态，可重新初始化
     */
    void release();

    /**
     * @brief 从堆中分配描述符
     * 
     * @return 指向已分配描述符的 descriptor_handle
     * 
     * @throws 如果堆已满或未初始化则触发断言失败
     * 
     * @details
     * - 线程安全：使用互斥锁保护内部状态
     * - O(1) 复杂度：从空闲列表弹出
     * - 返回的句柄包含 CPU 和 GPU（如果着色器可见）地址
     * 
     * @pre 堆必须已初始化且有可用容量
     * @note 标记 [[nodiscard]] 以防止意外丢弃句柄
     */
    [[nodiscard]] descriptor_handle allocate();

    /**
     * @brief 释放描述符，将其放入延迟释放队列
     * 
     * @param handle 要释放的描述符句柄
     * 
     * @details
     * - 线程安全：使用互斥锁保护内部状态
     * - 描述符不会立即可重用
     * - 在 GPU 完成当前帧后调用 process_deferred_release() 时，
     *   描述符才会返回到空闲池
     * - 无效句柄会被安全忽略
     * 
     * @note DEBUG 模式下验证句柄属于此堆
     */
    void free(descriptor_handle handle);

    /**
     * @brief 返回 D3D12 描述符堆类型
     * @return 此堆的类型（CBV_SRV_UAV、SAMPLER、RTV 或 DSV）
     */
    constexpr D3D12_DESCRIPTOR_HEAP_TYPE type() const { return _type; }

    /**
     * @brief 返回堆中第一个描述符的 CPU 句柄
     * @return 起始 CPU 描述符句柄
     */
    constexpr D3D12_CPU_DESCRIPTOR_HANDLE cpu_start() const { return _cpu_start; }

    /**
     * @brief 返回堆中第一个描述符的 GPU 句柄
     * @return 起始 GPU 描述符句柄，如果非着色器可见则为 {0}
     */
    constexpr D3D12_GPU_DESCRIPTOR_HANDLE gpu_start() const { return _gpu_start; }

    /**
     * @brief 返回底层 D3D12 描述符堆接口
     * @return 指向 ID3D12DescriptorHeap 的指针，如果未初始化则为 nullptr
     */
    constexpr ID3D12DescriptorHeap* const heap() const { return _heap; }

    /**
     * @brief 返回此堆可容纳的最大描述符数量
     * @return 堆的总容量
     */
    constexpr u32 capacity() const { return _capacity; }

    /**
     * @brief 返回当前已分配的描述符数量
     * @return 已分配描述符计数（不包括延迟释放的）
     */
    constexpr u32 size() const { return _size; }

    /**
     * @brief 返回单个描述符的字节大小
     * @return 描述符大小，因堆类型和硬件而异
     */
    constexpr u32 descriptor_size() const { return _descriptor_size; }

    /**
     * @brief 检查此堆是否着色器可见
     * @return 如果 GPU 句柄有效则返回 true，否则返回 false
     */
    constexpr bool is_shader_visible() const { return _gpu_start.ptr != 0; }

private:
    /**
     * @brief 底层 D3D12 描述符堆接口
     * 
     * @details
     * 内存位置取决于着色器可见性：
     * - 着色器可见：分配在 GPU 可访问内存（显存或映射到 GPU 的系统内存）
     * - 非着色器可见：仅分配在系统内存
     */
    ID3D12DescriptorHeap* _heap{nullptr};

    /**
     * @brief 堆中第一个描述符的 CPU 句柄
     * 
     * @details
     * 用作计算单个描述符 CPU 句柄的基地址。
     * initialize() 成功调用后立即有效。
     */
    D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{};

    /**
     * @brief 堆中第一个描述符的 GPU 句柄
     * 
     * @details
     * 仅对着色器可见堆有效。用作计算可通过根描述符表绑定到管线的
     * GPU 句柄的基地址。值为 {0} 表示非着色器可见堆。
     */
    D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{};

    /**
     * @brief 存储可用描述符索引的空闲列表
     * 
     * @details
     * 实现为预分配数组，实现 O(1) 分配/释放。
     * 初始填充所有索引 [0, capacity-1]。
     * 分配描述符时，从前面消耗索引。
     * 释放描述符后（延迟处理完成），索引返回。
     */
    std::unique_ptr<u32[]> _free_handles{};

    /**
     * @brief 每帧的延迟释放队列
     * 
     * @details
     * 当调用 free() 时，描述符索引放入当前帧的队列。
     * 只有在该帧 GPU 完成后调用 process_deferred_release() 时，
     * 索引才会返回到 _free_handles。
     * 
     * 这防止了 GPU 危险：当描述符可能仍被 GPU 使用时被新资源重用。
     * 
     * 数组大小为 frame_buffer_count（通常为 3，三重缓冲）。
     */
    utl::vector<u32> _deferred_free_indices[frame_buffer_count]{};

    /**
     * @brief 用于线程安全访问内部状态的互斥锁
     * 
     * @details
     * 保护所有可变状态：_size、_free_handles 和 _deferred_free_indices。
     * 必需，因为资源创建/销毁可能发生在多个线程上。
     */
    std::mutex _mutex{};

    /**
     * @brief 此堆可容纳的最大描述符数量
     * 
     * @details
     * 在 initialize() 时设置，此后不可变。
     * 用于边界检查和空闲列表初始化。
     */
    u32 _capacity{0};

    /**
     * @brief 当前已分配的描述符数量
     * 
     * @details
     * 表示从 _free_handles 消耗的索引数量。
     * 不包括延迟释放队列中的描述符。
     */
    u32 _size{0};

    /**
     * @brief 单个描述符的字节大小
     * 
     * @details
     * 从 ID3D12Device::GetDescriptorHandleIncrementSize() 获取。
     * 因堆类型和硬件而异。用于句柄偏移计算。
     */
    u32 _descriptor_size{0};

    /**
     * @brief D3D12 描述符堆类型
     * 
     * @details
     * 构造时设置，不可变。决定：
     * - 可创建的视图类型（SRV、UAV、RTV 等）
     * - 是否支持着色器可见性
     * - 允许的最大容量
     */
    const D3D12_DESCRIPTOR_HEAP_TYPE _type;
};

} // namespace XEngine::graphics::d3d12
