// IWYU pragma: no_include <bits/std_abs.h>
#include "core/rendering/surface.hpp"

#include <SDL3/SDL.h>

#include <algorithm>  // Para min, max, copy_n, fill
#include <cmath>      // Para abs
#include <cstdint>    // Para uint32_t
#include <cstring>    // Para memcpy, size_t
#include <fstream>    // Para basic_ifstream, basic_ostream, basic_ist...
#include <iostream>   // Para cerr
#include <memory>     // Para shared_ptr, __shared_ptr_access, default...
#include <sstream>    // Para basic_istringstream
#include <stdexcept>  // Para runtime_error
#include <vector>     // Para vector

#include "core/rendering/gif.hpp"              // Para Gif
#include "core/rendering/screen.hpp"           // Para Screen
#include "core/resources/resource_helper.hpp"  // Para ResourceHelper

// Carga una paleta desde un archivo .gif
auto loadPalette(const std::string& file_path) -> Palette {
    // Load file using ResourceHelper (supports both filesystem and pack)
    auto buffer = Resource::Helper::loadFile(file_path);
    if (buffer.empty()) {
        throw std::runtime_error("Error opening file: " + file_path);
    }

    // Cargar la paleta usando los datos del buffer
    std::vector<uint32_t> pal = GIF::Gif::loadPalette(buffer.data());
    if (pal.empty()) {
        throw std::runtime_error("No palette found in GIF file: " + file_path);
    }

    // Crear la paleta y copiar los datos desde 'pal'
    Palette palette = {};  // Inicializa la paleta con ceros
    std::copy_n(pal.begin(), std::min(pal.size(), palette.size()), palette.begin());

    // Mensaje de depuración
    printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");

    return palette;
}

// Carga una paleta desde un archivo .pal
auto readPalFile(const std::string& file_path) -> Palette {
    Palette palette{};
    palette.fill(0);  // Inicializar todo con 0 (transparente por defecto)

    // Load file using ResourceHelper (supports both filesystem and pack)
    auto file_data = Resource::Helper::loadFile(file_path);
    if (file_data.empty()) {
        throw std::runtime_error("No se pudo abrir el archivo .pal: " + file_path);
    }

    // Convert bytes to string for parsing
    std::string content(file_data.begin(), file_data.end());
    std::istringstream stream(content);

    std::string line;
    int line_number = 0;
    int color_index = 0;

    while (std::getline(stream, line)) {
        ++line_number;

        // Ignorar las tres primeras líneas del archivo
        if (line_number <= 3) {
            continue;
        }

        // Procesar las líneas restantes con valores RGB
        std::istringstream ss(line);
        int r;
        int g;
        int b;
        if (ss >> r >> g >> b) {
            // Construir el color ARGB (A = 255 por defecto)
            Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b;
            palette[color_index++] = color;

            // Limitar a un máximo de 256 colores (opcional)
            if (color_index >= 256) {
                break;
            }
        }
    }

    printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
    return palette;
}

// Constructor
Surface::Surface(int w, int h)
    : surface_data_(std::make_shared<SurfaceData>(w, h)),
      transparent_color_(255) { initializeSubPalette(sub_palette_); }

Surface::Surface(const std::string& file_path)
    : transparent_color_(255) {
    SurfaceData loaded_data = loadSurface(file_path);
    surface_data_ = std::make_shared<SurfaceData>(std::move(loaded_data));

    initializeSubPalette(sub_palette_);
}

// Carga una superficie desde un archivo
auto Surface::loadSurface(const std::string& file_path) -> SurfaceData {
    // Load file using ResourceHelper (supports both filesystem and pack)
    std::vector<Uint8> buffer = Resource::Helper::loadFile(file_path);
    if (buffer.empty()) {
        std::cerr << "Error opening file: " << file_path << '\n';
        throw std::runtime_error("Error opening file");
    }

    // Crear un objeto Gif y llamar a la función loadGif
    Uint16 w = 0;
    Uint16 h = 0;
    std::vector<Uint8> raw_pixels = GIF::Gif::loadGif(buffer.data(), w, h);
    if (raw_pixels.empty()) {
        std::cerr << "Error loading GIF from file: " << file_path << '\n';
        throw std::runtime_error("Error loading GIF");
    }

    // Si el constructor de Surface espera un std::shared_ptr<Uint8[]>,
    // reservamos un bloque dinámico y copiamos los datos del vector.
    size_t pixel_count = raw_pixels.size();
    auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixel_count], std::default_delete<Uint8[]>());
    std::memcpy(pixels.get(), raw_pixels.data(), pixel_count);

    // Crear y devolver directamente el objeto SurfaceData
    printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
    return {static_cast<float>(w), static_cast<float>(h), pixels};
}

// Carga una paleta desde un archivo
void Surface::loadPalette(const std::string& file_path) {
    palette_ = ::loadPalette(file_path);
}

// Carga una paleta desde otra paleta
void Surface::loadPalette(const Palette& palette) {
    palette_ = palette;
}

// Establece un color en la paleta
void Surface::setColor(int index, Uint32 color) {
    palette_.at(index) = color;
}

// Rellena la superficie con un color
void Surface::clear(Uint8 color) {
    const size_t TOTAL_PIXELS = surface_data_->width * surface_data_->height;
    Uint8* data_ptr = surface_data_->data.get();
    std::fill(data_ptr, data_ptr + TOTAL_PIXELS, color);
}

// Pone un pixel en la SurfaceData
void Surface::putPixel(int x, int y, Uint8 color) {
    if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) {
        return;  // Coordenadas fuera de rango
    }

    const int INDEX = x + (y * surface_data_->width);
    surface_data_->data.get()[INDEX] = color;
}

// Obtiene el color de un pixel de la surface_data
auto Surface::getPixel(int x, int y) -> Uint8 { return surface_data_->data.get()[x + (y * static_cast<int>(surface_data_->width))]; }

// Dibuja un rectangulo relleno
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) {
    // Limitar los valores del rectángulo al tamaño de la superficie
    float x_start = std::max(0.0F, rect->x);
    float y_start = std::max(0.0F, rect->y);
    float x_end = std::min(rect->x + rect->w, surface_data_->width);
    float y_end = std::min(rect->y + rect->h, surface_data_->height);

    // Rellenar fila a fila con memset (memoria contigua por fila)
    Uint8* data_ptr = surface_data_->data.get();
    const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
    const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start));
    for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) {
        std::memset(data_ptr + (static_cast<size_t>(y) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
    }
}

// Dibuja el borde de un rectangulo
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
    // Limitar los valores del rectángulo al tamaño de la superficie
    float x_start = std::max(0.0F, rect->x);
    float y_start = std::max(0.0F, rect->y);
    float x_end = std::min(rect->x + rect->w, surface_data_->width);
    float y_end = std::min(rect->y + rect->h, surface_data_->height);

    // Dibujar bordes horizontales con memset (líneas contiguas en memoria)
    Uint8* data_ptr = surface_data_->data.get();
    const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
    const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start));
    std::memset(data_ptr + (static_cast<size_t>(y_start) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
    std::memset(data_ptr + ((static_cast<size_t>(y_end) - 1) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);

    // Dibujar bordes verticales
    for (int y = y_start; y < y_end; ++y) {
        // Borde izquierdo
        const int LEFT_INDEX = x_start + (y * surface_data_->width);
        surface_data_->data.get()[LEFT_INDEX] = color;

        // Borde derecho
        const int RIGHT_INDEX = (x_end - 1) + (y * surface_data_->width);
        surface_data_->data.get()[RIGHT_INDEX] = color;
    }
}

// Dibuja una linea
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) {
    // Calcula las diferencias
    float dx = std::abs(x2 - x1);
    float dy = std::abs(y2 - y1);

    // Determina la dirección del incremento
    float sx = (x1 < x2) ? 1 : -1;
    float sy = (y1 < y2) ? 1 : -1;

    float err = dx - dy;

    while (true) {
        // Asegúrate de no dibujar fuera de los límites de la superficie
        if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height) {
            surface_data_->data.get()[static_cast<size_t>(x1 + (y1 * surface_data_->width))] = color;
        }

        // Si alcanzamos el punto final, salimos
        if (x1 == x2 && y1 == y2) {
            break;
        }

        int e2 = 2 * err;
        if (e2 > -dy) {
            err -= dy;
            x1 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y1 += sy;
        }
    }
}

void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {  // NOLINT(readability-make-member-function-const)
    auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();

    // Aplicar render offset
    dx += Screen::get()->getRenderOffsetX();
    dy += Screen::get()->getRenderOffsetY();

    // Limitar la región para evitar accesos fuera de rango en origen
    w = std::min(w, surface_data_->width - sx);
    h = std::min(h, surface_data_->height - sy);

    // Limitar la región para evitar accesos fuera de rango en destino
    w = std::min(w, surface_data->width - dx);
    h = std::min(h, surface_data->height - dy);

    const Uint8* src_ptr = surface_data_->data.get();
    Uint8* dst_ptr = surface_data->data.get();
    for (int iy = 0; iy < h; ++iy) {
        for (int ix = 0; ix < w; ++ix) {
            // Verificar que las coordenadas de destino están dentro de los límites
            if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
                if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
                    int src_x = sx + ix;
                    int src_y = sy + iy;

                    Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
                    if (color != static_cast<Uint8>(transparent_color_)) {
                        dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
                    }
                }
            }
        }
    }
}

void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) {  // NOLINT(readability-make-member-function-const)
    auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();

    // Aplicar render offset
    x += Screen::get()->getRenderOffsetX();
    y += Screen::get()->getRenderOffsetY();

    // Determina la región de origen (clip) a renderizar
    float sx = (src_rect != nullptr) ? src_rect->x : 0;
    float sy = (src_rect != nullptr) ? src_rect->y : 0;
    float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
    float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;

    // Guardar dimensiones originales antes del clipping (necesarias para flip)
    float orig_w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
    float orig_h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;

    // Limitar la región para evitar accesos fuera de rango en origen
    w = std::min(w, surface_data_->width - sx);
    h = std::min(h, surface_data_->height - sy);

    // Limitar la región para evitar accesos fuera de rango en destino
    w = std::min(w, surface_data_dest->width - static_cast<float>(x));
    h = std::min(h, surface_data_dest->height - static_cast<float>(y));

    // Renderiza píxel por píxel aplicando el flip si es necesario
    const Uint8* src_ptr = surface_data_->data.get();
    Uint8* dst_ptr = surface_data_dest->data.get();
    for (int iy = 0; iy < h; ++iy) {
        for (int ix = 0; ix < w; ++ix) {
            // Coordenadas de origen (flip usa dimensiones originales, no clipped)
            int src_x = (flip == SDL_FLIP_HORIZONTAL) ? static_cast<int>(sx + orig_w - 1 - ix) : static_cast<int>(sx + ix);
            int src_y = (flip == SDL_FLIP_VERTICAL) ? static_cast<int>(sy + orig_h - 1 - iy) : static_cast<int>(sy + iy);

            // Coordenadas de destino
            int dest_x = x + ix;
            int dest_y = y + iy;

            // Verificar que las coordenadas están dentro de los límites
            if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height &&
                src_x >= 0 && src_x < surface_data_->width && src_y >= 0 && src_y < surface_data_->height) {
                Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
                if (color != static_cast<Uint8>(transparent_color_)) {
                    dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color];
                }
            }
        }
    }
}

// Helper para calcular coordenadas con flip
void Surface::calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y) {
    src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
    src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
}

// Helper para copiar un pixel si no es transparente
void Surface::copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const {
    if (dest_x < 0 || dest_y < 0) {
        return;
    }

    Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
    if (color != static_cast<Uint8>(transparent_color_)) {
        dest_data[dest_x + (dest_y * dest_width)] = sub_palette_[color];
    }
}

// Copia una región de la superficie de origen a la de destino
void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip) {
    auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();

    // Si srcRect es nullptr, tomar toda la superficie fuente
    float sx = (src_rect != nullptr) ? src_rect->x : 0;
    float sy = (src_rect != nullptr) ? src_rect->y : 0;
    float sw = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
    float sh = (src_rect != nullptr) ? src_rect->h : surface_data_->height;

    // Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
    float dx = (dst_rect != nullptr) ? dst_rect->x : 0;
    float dy = (dst_rect != nullptr) ? dst_rect->y : 0;

    // Aplicar render offset
    dx += Screen::get()->getRenderOffsetX();
    dy += Screen::get()->getRenderOffsetY();
    float dw = (dst_rect != nullptr) ? dst_rect->w : sw;
    float dh = (dst_rect != nullptr) ? dst_rect->h : sh;

    // Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
    if (sw != dw || sh != dh) {
        dw = sw;  // Respetar las dimensiones de srcRect
        dh = sh;
    }

    // Limitar la región para evitar accesos fuera de rango en src y dst
    sw = std::min(sw, surface_data_->width - sx);
    sh = std::min(sh, surface_data_->height - sy);
    dw = std::min(dw, surface_data->width - dx);
    dh = std::min(dh, surface_data->height - dy);

    int final_width = std::min(sw, dw);
    int final_height = std::min(sh, dh);

    // Renderiza píxel por píxel aplicando el flip si es necesario
    for (int iy = 0; iy < final_height; ++iy) {
        for (int ix = 0; ix < final_width; ++ix) {
            int src_x = 0;
            int src_y = 0;
            calculateFlippedCoords(ix, iy, sx, sy, final_width, final_height, flip, src_x, src_y);

            int dest_x = dx + ix;
            int dest_y = dy + iy;

            // Verificar límites de destino antes de copiar
            if (dest_x >= 0 && dest_x < surface_data->width && dest_y >= 0 && dest_y < surface_data->height) {
                copyPixelIfNotTransparent(surface_data->data.get(), dest_x, dest_y, surface_data->width, src_x, src_y);
            }
        }
    }
}

// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) const {
    auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();

    // Aplicar render offset
    x += Screen::get()->getRenderOffsetX();
    y += Screen::get()->getRenderOffsetY();

    // Determina la región de origen (clip) a renderizar
    float sx = (src_rect != nullptr) ? src_rect->x : 0;
    float sy = (src_rect != nullptr) ? src_rect->y : 0;
    float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
    float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;

    // Limitar la región para evitar accesos fuera de rango
    w = std::min(w, surface_data_->width - sx);
    h = std::min(h, surface_data_->height - sy);

    // Renderiza píxel por píxel aplicando el flip si es necesario
    for (int iy = 0; iy < h; ++iy) {
        for (int ix = 0; ix < w; ++ix) {
            // Coordenadas de origen
            int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
            int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);

            // Coordenadas de destino
            int dest_x = x + ix;
            int dest_y = y + iy;

            // Verifica que las coordenadas de destino estén dentro de los límites
            if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height) {
                continue;  // Saltar píxeles fuera del rango del destino
            }

            // Copia el píxel si no es transparente
            Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
            if (color != static_cast<Uint8>(transparent_color_)) {
                surface_data->data[dest_x + (dest_y * surface_data->width)] =
                    (color == source_color) ? target_color : color;
            }
        }
    }
}

// Hash 2D estable per a dithering sense flickering
static auto pixelThreshold(int col, int row) -> float {
    auto h = (static_cast<uint32_t>(col) * 2246822519U) ^ (static_cast<uint32_t>(row) * 2654435761U);
    h ^= (h >> 13);
    h *= 1274126177U;
    h ^= (h >> 16);
    return static_cast<float>(h & 0xFFFFU) / 65536.0F;
}

// Calcula la densidad de fade para un pixel en posición screen_y
static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> float {
    if (screen_y < fade_h) {
        return static_cast<float>(fade_h - screen_y) / static_cast<float>(fade_h);
    }
    if (screen_y >= canvas_height - fade_h) {
        return static_cast<float>(screen_y - (canvas_height - fade_h)) / static_cast<float>(fade_h);
    }
    return 0.0F;
}

// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect) const {
    // Aplicar render offset
    x += Screen::get()->getRenderOffsetX();
    y += Screen::get()->getRenderOffsetY();

    const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
    const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
    const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : static_cast<int>(surface_data_->width);
    const int SH = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : static_cast<int>(surface_data_->height);

    auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();

    for (int row = 0; row < SH; row++) {
        const int SCREEN_Y = y + row;
        if (SCREEN_Y < 0 || SCREEN_Y >= static_cast<int>(surface_data_dest->height)) {
            continue;
        }

        const float DENSITY = computeFadeDensity(SCREEN_Y, fade_h, canvas_height);

        for (int col = 0; col < SW; col++) {
            const int SCREEN_X = x + col;
            if (SCREEN_X < 0 || SCREEN_X >= static_cast<int>(surface_data_dest->width)) {
                continue;
            }

            const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast<int>(surface_data_->width)) + (SX + col)];
            if (COLOR == static_cast<Uint8>(transparent_color_)) {
                continue;
            }

            if (pixelThreshold(col, row) < DENSITY) {
                continue;  // Pixel tapat per la zona de fade
            }

            surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast<int>(surface_data_dest->width))] = sub_palette_[COLOR];
        }
    }
}

// Idem però reemplaçant un color índex
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect) const {
    // Aplicar render offset
    x += Screen::get()->getRenderOffsetX();
    y += Screen::get()->getRenderOffsetY();

    const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
    const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
    const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : static_cast<int>(surface_data_->width);
    const int SH = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : static_cast<int>(surface_data_->height);

    auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();

    for (int row = 0; row < SH; row++) {
        const int SCREEN_Y = y + row;
        if (SCREEN_Y < 0 || SCREEN_Y >= static_cast<int>(surface_data_dest->height)) {
            continue;
        }

        const float DENSITY = computeFadeDensity(SCREEN_Y, fade_h, canvas_height);

        for (int col = 0; col < SW; col++) {
            const int SCREEN_X = x + col;
            if (SCREEN_X < 0 || SCREEN_X >= static_cast<int>(surface_data_dest->width)) {
                continue;
            }

            const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast<int>(surface_data_->width)) + (SX + col)];
            if (COLOR == static_cast<Uint8>(transparent_color_)) {
                continue;
            }

            if (pixelThreshold(col, row) < DENSITY) {
                continue;  // Pixel tapat per la zona de fade
            }

            const Uint8 OUT_COLOR = (COLOR == source_color) ? target_color : sub_palette_[COLOR];
            surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast<int>(surface_data_dest->width))] = OUT_COLOR;
        }
    }
}

// Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture ni SDL_Renderer)
void Surface::toARGBBuffer(Uint32* buffer) const {
    if (!surface_data_ || !surface_data_->data || (buffer == nullptr)) { return; }

    const int WIDTH = static_cast<int>(surface_data_->width);
    const int HEIGHT = static_cast<int>(surface_data_->height);
    const Uint8* src = surface_data_->data.get();

    // Obtenemos el tamaño de la paleta para evitar accesos fuera de rango
    const size_t PAL_SIZE = palette_.size();

    for (int i = 0; i < WIDTH * HEIGHT; ++i) {
        Uint8 color_index = src[i];

        // Verificación de seguridad: ¿El índice existe en la paleta?
        if (color_index < PAL_SIZE) {
            buffer[i] = palette_[color_index];
        } else {
            buffer[i] = 0xFF000000;  // Negro opaco si el índice es erróneo
        }
    }
}

// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
    if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
        throw std::runtime_error("Renderer or texture is null.");
    }

    if (surface_data_->width <= 0 || surface_data_->height <= 0 || (surface_data_->data == nullptr)) {
        throw std::runtime_error("Invalid surface dimensions or data.");
    }

    Uint32* pixels = nullptr;
    int pitch = 0;

    // Bloquea la textura para modificar los píxeles directamente
    if (!SDL_LockTexture(texture, nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
        throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
    }

    // Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
    int row_stride = pitch / sizeof(Uint32);

    // Cachear punteros fuera del bucle para permitir autovectorización SIMD
    const Uint8* src = surface_data_->data.get();
    const Uint32* pal = palette_.data();
    const int WIDTH = surface_data_->width;
    const int HEIGHT = surface_data_->height;
    for (int y = 0; y < HEIGHT; ++y) {
        const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
        Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
        for (int x = 0; x < WIDTH; ++x) {
            dst_row[x] = pal[src_row[x]];
        }
    }

    SDL_UnlockTexture(texture);  // Desbloquea la textura

    // Renderiza la textura en la pantalla completa
    if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr)) {
        throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
    }
}

// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) {
    if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
        throw std::runtime_error("Renderer or texture is null.");
    }

    if (surface_data_->width <= 0 || surface_data_->height <= 0 || (surface_data_->data == nullptr)) {
        throw std::runtime_error("Invalid surface dimensions or data.");
    }

    Uint32* pixels = nullptr;
    int pitch = 0;

    SDL_Rect lock_rect;
    if (dest_rect != nullptr) {
        lock_rect.x = static_cast<int>(dest_rect->x);
        lock_rect.y = static_cast<int>(dest_rect->y);
        lock_rect.w = static_cast<int>(dest_rect->w);
        lock_rect.h = static_cast<int>(dest_rect->h);
    }

    // Usa lockRect solo si destRect no es nulo
    if (!SDL_LockTexture(texture, (dest_rect != nullptr) ? &lock_rect : nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
        throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
    }

    int row_stride = pitch / sizeof(Uint32);

    // Cachear punteros fuera del bucle para permitir autovectorización SIMD
    const Uint8* src = surface_data_->data.get();
    const Uint32* pal = palette_.data();
    const int WIDTH = surface_data_->width;
    const int HEIGHT = surface_data_->height;
    for (int y = 0; y < HEIGHT; ++y) {
        const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
        Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
        for (int x = 0; x < WIDTH; ++x) {
            dst_row[x] = pal[src_row[x]];
        }
    }

    SDL_UnlockTexture(texture);

    // Renderiza la textura con los rectángulos especificados
    if (!SDL_RenderTexture(renderer, texture, src_rect, dest_rect)) {
        throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
    }
}

// Realiza un efecto de fundido en la paleta principal
auto Surface::fadePalette() -> bool {
    // Verificar que el tamaño mínimo de palette_ sea adecuado
    static constexpr int PALETTE_SIZE = 19;
    if (sizeof(palette_) / sizeof(palette_[0]) < PALETTE_SIZE) {
        throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
    }

    // Desplazar colores (pares e impares)
    for (int i = 18; i > 1; --i) {
        palette_[i] = palette_[i - 2];
    }

    // Ajustar el primer color
    palette_[1] = palette_[0];

    // Devolver si el índice 15 coincide con el índice 0
    return palette_[15] == palette_[0];
}

// Realiza un efecto de fundido en la paleta secundaria
auto Surface::fadeSubPalette(Uint32 delay) -> bool {
    // Variable estática para almacenar el último tick
    static Uint32 last_tick_ = 0;

    // Obtener el tiempo actual
    Uint32 current_tick = SDL_GetTicks();

    // Verificar si ha pasado el tiempo de retardo
    if (current_tick - last_tick_ < delay) {
        return false;  // No se realiza el fade
    }

    // Actualizar el último tick
    last_tick_ = current_tick;

    // Verificar que el tamaño mínimo de sub_palette_ sea adecuado
    static constexpr int SUB_PALETTE_SIZE = 19;
    if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < SUB_PALETTE_SIZE) {
        throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
    }

    // Desplazar colores (pares e impares)
    for (int i = 18; i > 1; --i) {
        sub_palette_[i] = sub_palette_[i - 2];
    }

    // Ajustar el primer color
    sub_palette_[1] = sub_palette_[0];

    // Devolver si el índice 15 coincide con el índice 0
    return sub_palette_[15] == sub_palette_[0];
}

// Restaura la sub paleta a su estado original
void Surface::resetSubPalette() { initializeSubPalette(sub_palette_); }
