// gpu_device.cpp - Implementación del wrapper de SDL_GPUDevice

#include "core/rendering/gpu/gpu_device.hpp"

#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>

#include <cstdint>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#include "core/utils/path_utils.hpp"

namespace Rendering::GPU {

GpuDevice::~GpuDevice() { destroy(); }

auto GpuDevice::init(SDL_Window* window) -> bool {
    window_ = window;

    // Solicitar backends en orden de preferencia: Vulkan (Linux/Windows),
    // Metal (macOS). Sin DirectX según decisión de proyecto.
    // SDL_GPU_SHADERFORMAT_SPIRV: shaders compilados con glslc.
    // SDL_GPU_SHADERFORMAT_MSL: pendiente para macOS (Fase futura).
    constexpr SDL_GPUShaderFormat SHADER_FORMATS =
        SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL;

    device_ = SDL_CreateGPUDevice(SHADER_FORMATS, /*debug=*/true, /*name=*/nullptr);
    if (device_ == nullptr) {
        std::cerr << "[GpuDevice] SDL_CreateGPUDevice falló: " << SDL_GetError() << '\n';
        return false;
    }

    const char* driver = SDL_GetGPUDeviceDriver(device_);
    std::cout << "[GpuDevice] Backend GPU: " << (driver != nullptr ? driver : "?") << '\n';

    if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
        std::cerr << "[GpuDevice] SDL_ClaimWindowForGPUDevice falló: " << SDL_GetError() << '\n';
        SDL_DestroyGPUDevice(device_);
        device_ = nullptr;
        return false;
    }

    swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_);
    return true;
}

void GpuDevice::destroy() {
    if (device_ != nullptr) {
        if (window_ != nullptr) {
            SDL_ReleaseWindowFromGPUDevice(device_, window_);
        }
        SDL_DestroyGPUDevice(device_);
        device_ = nullptr;
    }
    window_ = nullptr;
    swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
}

auto GpuDevice::loadShader(const std::string& spv_filename,
                           SDL_GPUShaderStage stage,
                           uint32_t num_uniform_buffers,
                           uint32_t num_samplers) const -> SDL_GPUShader* {
    if (device_ == nullptr) {
        return nullptr;
    }

    // Los .spv viven en build/shaders/ junto al binario.
    const std::string PATH = Utils::getExecutableDirectory() + "/shaders/" + spv_filename;

    std::ifstream file(PATH, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        std::cerr << "[GpuDevice] No s'ha pogut obrir el shader: " << PATH << '\n';
        return nullptr;
    }
    const std::streamsize SIZE = file.tellg();
    file.seekg(0, std::ios::beg);
    std::vector<uint8_t> buffer(static_cast<size_t>(SIZE));
    if (!file.read(reinterpret_cast<char*>(buffer.data()), SIZE)) {
        std::cerr << "[GpuDevice] Error llegint shader: " << PATH << '\n';
        return nullptr;
    }

    SDL_GPUShaderCreateInfo info{};
    info.code = buffer.data();
    info.code_size = buffer.size();
    info.entrypoint = "main";
    info.format = SDL_GPU_SHADERFORMAT_SPIRV;
    info.stage = stage;
    info.num_uniform_buffers = num_uniform_buffers;
    info.num_samplers = num_samplers;
    info.num_storage_buffers = 0;
    info.num_storage_textures = 0;

    SDL_GPUShader* shader = SDL_CreateGPUShader(device_, &info);
    if (shader == nullptr) {
        std::cerr << "[GpuDevice] SDL_CreateGPUShader (" << spv_filename << "): " << SDL_GetError() << '\n';
    }
    return shader;
}

}  // namespace Rendering::GPU
