#include "core/rendering/sprite/animated_sprite.hpp"

#include <cstddef>    // Para size_t
#include <fstream>    // Para basic_ostream, basic_istream, operator<<, basic...
#include <iostream>   // Para cout, cerr
#include <sstream>    // Para basic_stringstream
#include <stdexcept>  // Para runtime_error
#include <utility>

#include "core/rendering/surface.hpp"          // Para Surface
#include "core/resources/resource_cache.hpp"   // Para Resource
#include "core/resources/resource_helper.hpp"  // Para ResourceHelper
#include "external/fkyaml_node.hpp"            // Para fkyaml::node
#include "utils/utils.hpp"                     // Para printWithDots

// Helper: Convierte un nodo YAML de frames (array) a vector de SDL_FRect
auto convertYAMLFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector<SDL_FRect> {
    std::vector<SDL_FRect> frames;
    SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height};

    for (const auto& frame_index_node : frames_node) {
        const int NUM_TILE = frame_index_node.get_value<int>();
        if (NUM_TILE <= max_tiles) {
            rect.x = (NUM_TILE % frames_per_row) * frame_width;
            rect.y = (NUM_TILE / frames_per_row) * frame_height;
            frames.emplace_back(rect);
        }
    }

    return frames;
}

// Carga las animaciones desde un fichero YAML
auto AnimatedSprite::loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr<Surface>& surface, float& frame_width, float& frame_height) -> std::vector<AnimationData> {
    std::vector<AnimationData> animations;

    // Extract filename for logging
    const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);

    try {
        // Load YAML file using ResourceHelper (supports both filesystem and pack)
        auto file_data = Resource::Helper::loadFile(file_path);

        if (file_data.empty()) {
            std::cerr << "Error: Unable to load animation file " << FILE_NAME << '\n';
            throw std::runtime_error("Animation file not found: " + file_path);
        }

        printWithDots("Animation : ", FILE_NAME, "[ LOADED ]");

        // Parse YAML from string
        std::string yaml_content(file_data.begin(), file_data.end());
        auto yaml = fkyaml::node::deserialize(yaml_content);

        // --- Parse global configuration ---
        if (yaml.contains("tileSetFile")) {
            auto tile_set_file = yaml["tileSetFile"].get_value<std::string>();
            surface = Resource::Cache::get()->getSurface(tile_set_file);
        }

        if (yaml.contains("frameWidth")) {
            frame_width = static_cast<float>(yaml["frameWidth"].get_value<int>());
        }

        if (yaml.contains("frameHeight")) {
            frame_height = static_cast<float>(yaml["frameHeight"].get_value<int>());
        }

        // Calculate sprite sheet parameters
        int frames_per_row = 1;
        int max_tiles = 1;
        if (surface) {
            frames_per_row = surface->getWidth() / static_cast<int>(frame_width);
            const int W = surface->getWidth() / static_cast<int>(frame_width);
            const int H = surface->getHeight() / static_cast<int>(frame_height);
            max_tiles = W * H;
        }

        // --- Parse animations array ---
        if (yaml.contains("animations") && yaml["animations"].is_sequence()) {
            const auto& animations_node = yaml["animations"];

            for (const auto& anim_node : animations_node) {
                AnimationData animation;

                // Parse animation name
                if (anim_node.contains("name")) {
                    animation.name = anim_node["name"].get_value<std::string>();
                }

                // Parse speed (seconds per frame)
                if (anim_node.contains("speed")) {
                    animation.speed = anim_node["speed"].get_value<float>();
                }

                // Parse loop frame index
                if (anim_node.contains("loop")) {
                    animation.loop = anim_node["loop"].get_value<int>();
                }

                // Parse frames array
                if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
                    animation.frames = convertYAMLFramesToRects(
                        anim_node["frames"],
                        frame_width,
                        frame_height,
                        frames_per_row,
                        max_tiles);
                }

                animations.push_back(animation);
            }
        }

    } catch (const fkyaml::exception& e) {
        std::cerr << "YAML parsing error in " << FILE_NAME << ": " << e.what() << '\n';
        throw;
    } catch (const std::exception& e) {
        std::cerr << "Error loading animation " << FILE_NAME << ": " << e.what() << '\n';
        throw;
    }

    return animations;
}

// Constructor con bytes YAML del cache (parsing lazy)
AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
    // Parsear YAML desde los bytes cargados en cache
    std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end());

    try {
        auto yaml = fkyaml::node::deserialize(yaml_content);

        // Variables para almacenar configuración global
        float frame_width = 0.0F;
        float frame_height = 0.0F;

        // --- Parse global configuration ---
        if (yaml.contains("tileSetFile")) {
            auto tile_set_file = yaml["tileSetFile"].get_value<std::string>();
            // Ahora SÍ podemos acceder al cache (ya está completamente cargado)
            surface_ = Resource::Cache::get()->getSurface(tile_set_file);
        }

        if (yaml.contains("frameWidth")) {
            frame_width = static_cast<float>(yaml["frameWidth"].get_value<int>());
        }

        if (yaml.contains("frameHeight")) {
            frame_height = static_cast<float>(yaml["frameHeight"].get_value<int>());
        }

        // Calculate sprite sheet parameters
        int frames_per_row = 1;
        int max_tiles = 1;
        if (surface_) {
            frames_per_row = surface_->getWidth() / static_cast<int>(frame_width);
            const int W = surface_->getWidth() / static_cast<int>(frame_width);
            const int H = surface_->getHeight() / static_cast<int>(frame_height);
            max_tiles = W * H;
        }

        // --- Parse animations array ---
        if (yaml.contains("animations") && yaml["animations"].is_sequence()) {
            const auto& animations_node = yaml["animations"];

            for (const auto& anim_node : animations_node) {
                AnimationData animation;

                // Parse animation name
                if (anim_node.contains("name")) {
                    animation.name = anim_node["name"].get_value<std::string>();
                }

                // Parse speed (seconds per frame)
                if (anim_node.contains("speed")) {
                    animation.speed = anim_node["speed"].get_value<float>();
                }

                // Parse loop frame index
                if (anim_node.contains("loop")) {
                    animation.loop = anim_node["loop"].get_value<int>();
                }

                // Parse frames array
                if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
                    animation.frames = convertYAMLFramesToRects(
                        anim_node["frames"],
                        frame_width,
                        frame_height,
                        frames_per_row,
                        max_tiles);
                }

                animations_.push_back(animation);
            }
        }

        // Set dimensions
        setWidth(frame_width);
        setHeight(frame_height);

        // Inicializar con la primera animación si existe
        if (!animations_.empty() && !animations_[0].frames.empty()) {
            setClip(animations_[0].frames[0]);
        }

    } catch (const fkyaml::exception& e) {
        std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n';
        throw;
    } catch (const std::exception& e) {
        std::cerr << "Error loading animation " << cached_data.name << ": " << e.what() << '\n';
        throw;
    }
}

// Constructor per a subclasses amb surface directa (sense YAML)
AnimatedSprite::AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
    : MovingSprite(std::move(surface), pos) {
    // animations_ queda buit (protegit per el guard de animate())
    if (surface_) {
        clip_ = {.x = 0, .y = 0, .w = static_cast<float>(surface_->getWidth()), .h = static_cast<float>(surface_->getHeight())};
    }
}

// Obtiene el indice de la animación a partir del nombre
auto AnimatedSprite::getIndex(const std::string& name) -> int {
    auto index = -1;

    for (const auto& a : animations_) {
        index++;
        if (a.name == name) {
            return index;
        }
    }
    std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << '\n';
    return -1;
}

// Calcula el frame correspondiente a la animación (time-based)
void AnimatedSprite::animate(float delta_time) {
    if (animations_.empty()) { return; }
    if (animations_[current_animation_].speed <= 0.0F) {
        return;
    }

    // Acumula el tiempo transcurrido
    animations_[current_animation_].accumulated_time += delta_time;

    // Calcula el frame actual a partir del tiempo acumulado
    const int TARGET_FRAME = static_cast<int>(
        animations_[current_animation_].accumulated_time /
        animations_[current_animation_].speed);

    // Si alcanza el final de la animación, maneja el loop
    if (TARGET_FRAME >= static_cast<int>(animations_[current_animation_].frames.size())) {
        if (animations_[current_animation_].loop == -1) {
            // Si no hay loop, congela en el último frame
            animations_[current_animation_].current_frame =
                static_cast<int>(animations_[current_animation_].frames.size()) - 1;
            animations_[current_animation_].completed = true;

            // Establece el clip del último frame
            if (animations_[current_animation_].current_frame >= 0) {
                setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
            }
        } else {
            // Si hay loop, vuelve al frame indicado
            animations_[current_animation_].accumulated_time =
                static_cast<float>(animations_[current_animation_].loop) *
                animations_[current_animation_].speed;
            animations_[current_animation_].current_frame = animations_[current_animation_].loop;

            // Establece el clip del frame de loop
            setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
        }
    } else {
        // Actualiza el frame actual
        animations_[current_animation_].current_frame = TARGET_FRAME;

        // Establece el clip del frame actual
        if (animations_[current_animation_].current_frame >= 0 &&
            animations_[current_animation_].current_frame <
                static_cast<int>(animations_[current_animation_].frames.size())) {
            setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
        }
    }
}

// Comprueba si ha terminado la animación
auto AnimatedSprite::animationIsCompleted() -> bool {
    return animations_[current_animation_].completed;
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(const std::string& name) {
    const auto NEW_ANIMATION = getIndex(name);
    if (current_animation_ != NEW_ANIMATION) {
        current_animation_ = NEW_ANIMATION;
        animations_[current_animation_].current_frame = 0;
        animations_[current_animation_].accumulated_time = 0.0F;
        animations_[current_animation_].completed = false;
        setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
    }
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(int index) {
    const auto NEW_ANIMATION = index;
    if (current_animation_ != NEW_ANIMATION) {
        current_animation_ = NEW_ANIMATION;
        animations_[current_animation_].current_frame = 0;
        animations_[current_animation_].accumulated_time = 0.0F;
        animations_[current_animation_].completed = false;
        setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
    }
}

// Actualiza las variables del objeto (time-based)
void AnimatedSprite::update(float delta_time) {
    animate(delta_time);
    MovingSprite::update(delta_time);
}

// Reinicia la animación
void AnimatedSprite::resetAnimation() {
    animations_[current_animation_].current_frame = 0;
    animations_[current_animation_].accumulated_time = 0.0F;
    animations_[current_animation_].completed = false;
}

// Establece el frame actual de la animación
void AnimatedSprite::setCurrentAnimationFrame(int num) {
    // Descarta valores fuera de rango
    if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
        num = 0;
    }

    // Cambia el valor de la variable
    animations_[current_animation_].current_frame = num;

    // Escoge el frame correspondiente de la animación
    setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}