#pragma once

#include <SDL3/SDL.h>

#include <array>    // Para array
#include <memory>   // Para default_delete, shared_ptr, __shared_pt...
#include <numeric>  // Para iota
#include <string>   // Para string
#include <utility>  // Para move

#include "utils/utils.hpp"  // Para PaletteColor

// Alias
using Palette = std::array<Uint32, 256>;
using SubPalette = std::array<Uint8, 256>;

// Carga una paleta desde un archivo .gif
auto loadPalette(const std::string& file_path) -> Palette;

// Carga una paleta desde un archivo .pal
auto readPalFile(const std::string& file_path) -> Palette;

struct SurfaceData {
    std::shared_ptr<Uint8[]> data;  // Usa std::shared_ptr para gestión automática
    float width;                    // Ancho de la imagen
    float height;                   // Alto de la imagen

    // Constructor por defecto
    SurfaceData()
        : data(nullptr),
          width(0),
          height(0) {}

    // Constructor que inicializa dimensiones y asigna memoria
    SurfaceData(float w, float h)
        : data(std::shared_ptr<Uint8[]>(new Uint8[static_cast<size_t>(w * h)](), std::default_delete<Uint8[]>())),
          width(w),
          height(h) {}

    // Constructor para inicializar directamente con datos
    SurfaceData(float w, float h, std::shared_ptr<Uint8[]> pixels)
        : data(std::move(pixels)),
          width(w),
          height(h) {}

    // Constructor de movimiento
    SurfaceData(SurfaceData&& other) noexcept = default;

    // Operador de movimiento
    auto operator=(SurfaceData&& other) noexcept -> SurfaceData& = default;

    // Evita copias accidentales
    SurfaceData(const SurfaceData&) = delete;
    auto operator=(const SurfaceData&) -> SurfaceData& = delete;
};

class Surface {
   private:
    std::shared_ptr<SurfaceData> surface_data_;  // Datos a dibujar
    Palette palette_;                            // Paleta para volcar la SurfaceData a una Textura
    SubPalette sub_palette_;                     // Paleta para reindexar colores
    int transparent_color_;                      // Indice de la paleta que se omite en la copia de datos

   public:
    // Constructor
    Surface(int w, int h);
    explicit Surface(const std::string& file_path);

    // Destructor
    ~Surface() = default;

    // Carga una SurfaceData desde un archivo
    static auto loadSurface(const std::string& file_path) -> SurfaceData;

    // Carga una paleta desde un archivo
    void loadPalette(const std::string& file_path);
    void loadPalette(const Palette& palette);

    // Copia una región de la SurfaceData de origen a la SurfaceData de destino
    void render(float dx, float dy, float sx, float sy, float w, float h);
    void render(int x, int y, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
    void render(SDL_FRect* src_rect = nullptr, SDL_FRect* dst_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);

    // Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
    void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;

    // Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
    void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect = nullptr) const;

    // Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color)
    void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect = nullptr) const;

    // Establece un color en la paleta
    void setColor(int index, Uint32 color);

    // Rellena la SurfaceData con un color
    void clear(Uint8 color);

    // Vuelca la SurfaceData a una textura
    void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture);
    void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect);

    // Realiza un efecto de fundido en las paletas
    auto fadePalette() -> bool;
    auto fadeSubPalette(Uint32 delay = 0) -> bool;

    // Restaura la sub paleta a su estado original
    void resetSubPalette();

    // Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture)
    void toARGBBuffer(Uint32* buffer) const;

    // Pone un pixel en la SurfaceData
    void putPixel(int x, int y, Uint8 color);

    // Obtiene el color de un pixel de la surface_data
    auto getPixel(int x, int y) -> Uint8;

    // Dibuja un rectangulo relleno
    void fillRect(const SDL_FRect* rect, Uint8 color);

    // Dibuja el borde de un rectangulo
    void drawRectBorder(const SDL_FRect* rect, Uint8 color);

    // Dibuja una linea
    void drawLine(float x1, float y1, float x2, float y2, Uint8 color);

    // Metodos para gestionar surface_data_
    [[nodiscard]] auto getSurfaceData() const -> std::shared_ptr<SurfaceData> { return surface_data_; }
    void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = std::move(new_data); }

    // Obtien ancho y alto
    [[nodiscard]] auto getWidth() const -> float { return surface_data_->width; }
    [[nodiscard]] auto getHeight() const -> float { return surface_data_->height; }

    // Color transparente
    [[nodiscard]] auto getTransparentColor() const -> Uint8 { return transparent_color_; }
    void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; }

    // Paleta
    void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
    [[nodiscard]] auto getPalette() const -> const Palette& { return palette_; }
    [[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; }

    // Inicializa la sub paleta
    static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }

   private:
    // Helper para calcular coordenadas con flip
    static void calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y);

    // Helper para copiar un pixel si no es transparente
    void copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const;
};
