#include "gpu_texture.hpp"

#include <SDL3/SDL_log.h>
#include <SDL3/SDL_pixels.h>
#include <cstring>  // memcpy
#include <string>

// stb_image is compiled in texture.cpp (STB_IMAGE_IMPLEMENTATION defined there)
#include "external/stb_image.h"
#include "resource_manager.hpp"

// ---------------------------------------------------------------------------
// Public interface
// ---------------------------------------------------------------------------

bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
    unsigned char* resource_data = nullptr;
    size_t resource_size = 0;

    if (!ResourceManager::loadResource(file_path, resource_data, resource_size)) {
        SDL_Log("GpuTexture: can't load resource '%s'", file_path.c_str());
        return false;
    }

    int w = 0, h = 0, orig = 0;
    unsigned char* pixels = stbi_load_from_memory(
        resource_data, static_cast<int>(resource_size),
        &w, &h, &orig, STBI_rgb_alpha);
    delete[] resource_data;

    if (!pixels) {
        SDL_Log("GpuTexture: stbi decode failed for '%s': %s",
                file_path.c_str(), stbi_failure_reason());
        return false;
    }

    destroy(device);
    bool ok = uploadPixels(device, pixels, w, h, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
    stbi_image_free(pixels);

    if (ok) {
        ok = createSampler(device, true /*nearest = pixel-perfect sprites*/);
    }
    return ok;
}

bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) {
    if (!surface) return false;

    // Ensure RGBA32 format
    SDL_Surface* rgba = surface;
    bool need_free = false;
    if (surface->format != SDL_PIXELFORMAT_RGBA32) {
        rgba = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
        if (!rgba) {
            SDL_Log("GpuTexture: SDL_ConvertSurface failed: %s", SDL_GetError());
            return false;
        }
        need_free = true;
    }

    destroy(device);
    bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h,
                           SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
    if (ok) ok = createSampler(device, nearest);

    if (need_free) SDL_DestroySurface(rgba);
    return ok;
}

bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
                                    SDL_GPUTextureFormat format) {
    destroy(device);

    SDL_GPUTextureCreateInfo info = {};
    info.type               = SDL_GPU_TEXTURETYPE_2D;
    info.format             = format;
    info.usage              = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET
                            | SDL_GPU_TEXTUREUSAGE_SAMPLER;
    info.width              = static_cast<Uint32>(w);
    info.height             = static_cast<Uint32>(h);
    info.layer_count_or_depth = 1;
    info.num_levels         = 1;
    info.sample_count       = SDL_GPU_SAMPLECOUNT_1;

    texture_ = SDL_CreateGPUTexture(device, &info);
    if (!texture_) {
        SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError());
        return false;
    }
    width_  = w;
    height_ = h;

    // Render targets are sampled with linear filter (postfx reads them)
    return createSampler(device, false);
}

bool GpuTexture::createWhite(SDL_GPUDevice* device) {
    destroy(device);
    // 1×1 white RGBA pixel
    const Uint8 white[4] = {255, 255, 255, 255};
    bool ok = uploadPixels(device, white, 1, 1,
                           SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
    if (ok) ok = createSampler(device, true);
    return ok;
}

void GpuTexture::destroy(SDL_GPUDevice* device) {
    if (!device) return;
    if (sampler_)  { SDL_ReleaseGPUSampler(device, sampler_);   sampler_  = nullptr; }
    if (texture_)  { SDL_ReleaseGPUTexture(device, texture_);   texture_  = nullptr; }
    width_ = height_ = 0;
}

// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------

bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
                               int w, int h, SDL_GPUTextureFormat format) {
    // Create GPU texture
    SDL_GPUTextureCreateInfo tex_info = {};
    tex_info.type               = SDL_GPU_TEXTURETYPE_2D;
    tex_info.format             = format;
    tex_info.usage              = SDL_GPU_TEXTUREUSAGE_SAMPLER;
    tex_info.width              = static_cast<Uint32>(w);
    tex_info.height             = static_cast<Uint32>(h);
    tex_info.layer_count_or_depth = 1;
    tex_info.num_levels         = 1;
    tex_info.sample_count       = SDL_GPU_SAMPLECOUNT_1;

    texture_ = SDL_CreateGPUTexture(device, &tex_info);
    if (!texture_) {
        SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError());
        return false;
    }

    // Create transfer buffer and upload pixels
    Uint32 data_size = static_cast<Uint32>(w * h * 4);  // RGBA = 4 bytes/pixel

    SDL_GPUTransferBufferCreateInfo tb_info = {};
    tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
    tb_info.size  = data_size;

    SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(device, &tb_info);
    if (!transfer) {
        SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError());
        SDL_ReleaseGPUTexture(device, texture_);
        texture_ = nullptr;
        return false;
    }

    void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
    if (!mapped) {
        SDL_Log("GpuTexture: map failed: %s", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer);
        SDL_ReleaseGPUTexture(device, texture_);
        texture_ = nullptr;
        return false;
    }
    memcpy(mapped, pixels, data_size);
    SDL_UnmapGPUTransferBuffer(device, transfer);

    // Upload via command buffer
    SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device);
    SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);

    SDL_GPUTextureTransferInfo src = {};
    src.transfer_buffer = transfer;
    src.offset          = 0;
    src.pixels_per_row  = static_cast<Uint32>(w);
    src.rows_per_layer  = static_cast<Uint32>(h);

    SDL_GPUTextureRegion dst = {};
    dst.texture   = texture_;
    dst.mip_level = 0;
    dst.layer     = 0;
    dst.x = dst.y = dst.z = 0;
    dst.w = static_cast<Uint32>(w);
    dst.h = static_cast<Uint32>(h);
    dst.d = 1;

    SDL_UploadToGPUTexture(copy, &src, &dst, false);
    SDL_EndGPUCopyPass(copy);
    SDL_SubmitGPUCommandBuffer(cmd);

    SDL_ReleaseGPUTransferBuffer(device, transfer);
    width_  = w;
    height_ = h;
    return true;
}

bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
    SDL_GPUSamplerCreateInfo info = {};
    info.min_filter    = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
    info.mag_filter    = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
    info.mipmap_mode   = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
    info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
    info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
    info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;

    sampler_ = SDL_CreateGPUSampler(device, &info);
    if (!sampler_) {
        SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError());
        return false;
    }
    return true;
}
