// enemy_config.cpp - Implementació del parser de EnemyConfig
// © 2026 JailDesigner

#include "game/entities/enemy_config.hpp"

#include <cstdint>
#include <exception>
#include <iostream>
#include <string>

namespace {

    auto parseColor(const fkyaml::node& node, SDL_Color& out) -> bool {
        if (!node.is_sequence() || node.size() != 3) {
            return false;
        }
        const auto R = node[0].get_value<uint32_t>();
        const auto G = node[1].get_value<uint32_t>();
        const auto B = node[2].get_value<uint32_t>();
        out = SDL_Color{
            .r = static_cast<uint8_t>(R),
            .g = static_cast<uint8_t>(G),
            .b = static_cast<uint8_t>(B),
            .a = 255};
        return true;
    }

    auto aiTypeFromString(const std::string& s) -> std::optional<EnemyType> {
        if (s == "pentagon") { return EnemyType::PENTAGON; }
        if (s == "square") { return EnemyType::SQUARE; }
        if (s == "pinwheel") { return EnemyType::PINWHEEL; }
        if (s == "star") { return EnemyType::STAR; }
        if (s == "orb") { return EnemyType::ORB; }
        return std::nullopt;
    }

    // Cada parseXxx valida + omple la sub-struct corresponent. Retornen false
    // amb log si falta un camp requerit. Separar-los baixa la complexitat
    // cognitiva del fromYaml() principal.

    auto parseAiType(const fkyaml::node& node, EnemyType expected, const std::string& name, EnemyType& out) -> bool {
        if (!node.contains("ai_type")) {
            std::cerr << "[EnemyConfig] Error: falta 'ai_type' a " << name << '\n';
            return false;
        }
        const auto AI_STR = node["ai_type"].get_value<std::string>();
        const auto PARSED = aiTypeFromString(AI_STR);
        if (!PARSED) {
            std::cerr << "[EnemyConfig] Error: ai_type desconegut '" << AI_STR << "' a " << name << '\n';
            return false;
        }
        if (*PARSED != expected) {
            std::cerr << "[EnemyConfig] Error: ai_type '" << AI_STR
                      << "' no coincideix amb el tipus esperat (per directori) a " << name << '\n';
            return false;
        }
        out = *PARSED;
        return true;
    }

    auto parseShape(const fkyaml::node& node, const std::string& name, EnemyConfig::ShapeCfg& out) -> bool {
        if (!node.contains("shape") || !node["shape"].contains("path")) {
            std::cerr << "[EnemyConfig] Error: falta 'shape.path' a " << name << '\n';
            return false;
        }
        const auto& shape = node["shape"];
        out.path = shape["path"].get_value<std::string>();
        out.scale = shape.contains("scale") ? shape["scale"].get_value<float>() : 1.0F;
        out.collision_factor = shape.contains("collision_factor")
            ? shape["collision_factor"].get_value<float>()
            : 1.0F;
        return true;
    }

    auto parsePhysics(const fkyaml::node& node, const std::string& name, EnemyConfig::PhysicsCfg& out) -> bool {
        if (!node.contains("physics")) {
            std::cerr << "[EnemyConfig] Error: falta 'physics' a " << name << '\n';
            return false;
        }
        const auto& p = node["physics"];
        out.mass = p["mass"].get_value<float>();
        out.speed = p["speed"].get_value<float>();
        out.rotation_delta_min = p["rotation_delta_min"].get_value<float>();
        out.rotation_delta_max = p["rotation_delta_max"].get_value<float>();
        out.restitution = p["restitution"].get_value<float>();
        out.linear_damping = p["linear_damping"].get_value<float>();
        out.angular_damping = p["angular_damping"].get_value<float>();
        return true;
    }

    auto parseAnimation(const fkyaml::node& node, const std::string& name, EnemyConfig::AnimationCfg& out) -> bool {
        if (!node.contains("animation") ||
            !node["animation"].contains("pulse") ||
            !node["animation"].contains("rotation_accel")) {
            std::cerr << "[EnemyConfig] Error: falta 'animation.pulse' o 'animation.rotation_accel' a " << name << '\n';
            return false;
        }
        const auto& p = node["animation"]["pulse"];
        out.pulse.trigger_prob_per_second = p["trigger_prob_per_second"].get_value<float>();
        out.pulse.duration_min = p["duration_min"].get_value<float>();
        out.pulse.duration_max = p["duration_max"].get_value<float>();
        out.pulse.amplitude_min = p["amplitude_min"].get_value<float>();
        out.pulse.amplitude_max = p["amplitude_max"].get_value<float>();
        out.pulse.frequency_min = p["frequency_min"].get_value<float>();
        out.pulse.frequency_max = p["frequency_max"].get_value<float>();

        const auto& r = node["animation"]["rotation_accel"];
        out.rotation_accel.trigger_prob_per_second = r["trigger_prob_per_second"].get_value<float>();
        out.rotation_accel.duration_min = r["duration_min"].get_value<float>();
        out.rotation_accel.duration_max = r["duration_max"].get_value<float>();
        out.rotation_accel.multiplier_min = r["multiplier_min"].get_value<float>();
        out.rotation_accel.multiplier_max = r["multiplier_max"].get_value<float>();
        return true;
    }

    auto parseWounded(const fkyaml::node& node, const std::string& name, EnemyConfig::WoundedCfg& out) -> bool {
        if (!node.contains("wounded")) {
            std::cerr << "[EnemyConfig] Error: falta 'wounded' a " << name << '\n';
            return false;
        }
        const auto& w = node["wounded"];
        out.duration = w["duration"].get_value<float>();
        out.blink_hz = w["blink_hz"].get_value<float>();
        return true;
    }

    auto parseSpawn(const fkyaml::node& node, const std::string& name, EnemyConfig::SpawnCfg& out) -> bool {
        if (!node.contains("spawn")) {
            std::cerr << "[EnemyConfig] Error: falta 'spawn' a " << name << '\n';
            return false;
        }
        const auto& s = node["spawn"];
        out.invulnerability_duration = s["invulnerability_duration"].get_value<float>();
        out.invulnerability_brightness_start = s["invulnerability_brightness_start"].get_value<float>();
        out.invulnerability_brightness_end = s["invulnerability_brightness_end"].get_value<float>();
        out.invulnerability_scale_start = s["invulnerability_scale_start"].get_value<float>();
        out.invulnerability_scale_end = s["invulnerability_scale_end"].get_value<float>();
        out.safety_distance = s["safety_distance"].get_value<float>();
        return true;
    }

    // Tots els camps de behavior són opcionals; només l'AI corresponent els consumeix.
    void parseBehavior(const fkyaml::node& node, EnemyConfig::BehaviorCfg& out) {
        if (!node.contains("behavior")) {
            return;
        }
        const auto& b = node["behavior"];
        const auto READ_OPT = [&b](const char* key, float& dst) {
            if (b.contains(key)) {
                dst = b[key].get_value<float>();
            }
        };
        READ_OPT("zigzag_prob_per_second", out.zigzag_prob_per_second);
        READ_OPT("angle_change_max", out.angle_change_max);
        READ_OPT("tracking_strength", out.tracking_strength);
        READ_OPT("tracking_interval", out.tracking_interval);
        READ_OPT("rotation_proximity_multiplier", out.rotation_proximity_multiplier);
        READ_OPT("proximity_distance", out.proximity_distance);
    }

    auto parseColors(const fkyaml::node& node, const std::string& name, EnemyConfig::ColorsCfg& out) -> bool {
        if (!node.contains("colors") ||
            !parseColor(node["colors"]["normal"], out.normal) ||
            !parseColor(node["colors"]["wounded"], out.wounded)) {
            std::cerr << "[EnemyConfig] Error: 'colors.normal' / 'colors.wounded' no són [r,g,b] a "
                      << name << '\n';
            return false;
        }
        return true;
    }

    auto parseScore(const fkyaml::node& node, const std::string& name, int& out) -> bool {
        if (!node.contains("score")) {
            std::cerr << "[EnemyConfig] Error: falta 'score' a " << name << '\n';
            return false;
        }
        out = node["score"].get_value<int>();
        return true;
    }

    // health és opcional: si el YAML no l'inclou, el default {1} de l'struct
    // ja cobreix el comportament de tots els enemics actuals (1 hit → mort).
    void parseHealth(const fkyaml::node& node, int& out) {
        if (node.contains("health")) {
            out = node["health"].get_value<int>();
        }
    }

    // Forward-decl: aimModeFromString viu més avall (junt amb la resta de
    // helpers d'AI) però parseActionList el necessita per al payload de
    // FIRE_BULLET. Evita reordenar tot el bloc.
    auto aimModeFromString(const std::string& s) -> std::optional<AimMode>;

    auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> {
        if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
        if (s == "destroy") { return EnemyActionType::DESTROY; }
        if (s == "add_score") { return EnemyActionType::ADD_SCORE; }
        if (s == "create_debris") { return EnemyActionType::CREATE_DEBRIS; }
        if (s == "create_debris_partial") { return EnemyActionType::CREATE_DEBRIS_PARTIAL; }
        if (s == "create_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
        if (s == "create_fireworks_small") { return EnemyActionType::CREATE_FIREWORKS_SMALL; }
        if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
        if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; }
        if (s == "flash") { return EnemyActionType::FLASH; }
        if (s == "fire_bullet") { return EnemyActionType::FIRE_BULLET; }
        return std::nullopt;
    }

    auto parseActionList(const fkyaml::node& list_node, const std::string& enemy_name, const char* event_name, std::vector<EnemyAction>& out) -> bool {
        if (!list_node.is_sequence()) {
            std::cerr << "[EnemyConfig] Error: '" << event_name << "' ha de ser una llista a "
                      << enemy_name << '\n';
            return false;
        }
        for (const auto& item : list_node) {
            if (!item.contains("action")) {
                std::cerr << "[EnemyConfig] Error: entrada sense 'action' a " << event_name
                          << " (" << enemy_name << ")\n";
                return false;
            }
            const auto STR = item["action"].get_value<std::string>();
            const auto PARSED = actionTypeFromString(STR);
            if (!PARSED) {
                std::cerr << "[EnemyConfig] Error: acció desconeguda '" << STR << "' a "
                          << event_name << " (" << enemy_name << ")\n";
                return false;
            }
            EnemyAction action;
            action.type = *PARSED;
            // Payload de FIRE_BULLET. Camps opcionals; els defaults són els del struct.
            if (action.type == EnemyActionType::FIRE_BULLET) {
                if (item.contains("bullet")) {
                    action.bullet_config_name = item["bullet"].get_value<std::string>();
                }
                if (item.contains("bullet_speed")) {
                    action.bullet_speed = item["bullet_speed"].get_value<float>();
                }
                if (item.contains("aim_mode")) {
                    const auto AIM_STR = item["aim_mode"].get_value<std::string>();
                    const auto AIM = aimModeFromString(AIM_STR);
                    if (!AIM) {
                        std::cerr << "[EnemyConfig] Error: aim_mode desconegut '" << AIM_STR
                                  << "' a " << event_name << " (" << enemy_name << ")\n";
                        return false;
                    }
                    action.aim_mode = *AIM;
                }
                if (item.contains("jitter_rad")) {
                    action.jitter_rad = item["jitter_rad"].get_value<float>();
                }
            }
            out.push_back(action);
        }
        return true;
    }

    // Defaults: replica el flux hardcoded actual (set_hurt → destroy → score+debris+fireworks).
    // Construïm via mutació per esquivar warnings de designated-init parcial sobre
    // l'EnemyAction (que té payload de FIRE_BULLET no rellevant ací).
    void fillLegacyDefaults(EnemyEventConfig& events) {
        const auto MAKE = [](EnemyActionType type) {
            EnemyAction a;
            a.type = type;
            return a;
        };
        events.on_hit = {MAKE(EnemyActionType::SET_HURT)};
        events.on_hurt_end = {MAKE(EnemyActionType::DESTROY)};
        events.on_destroy = {
            MAKE(EnemyActionType::ADD_SCORE),
            MAKE(EnemyActionType::CREATE_DEBRIS),
            MAKE(EnemyActionType::CREATE_FIREWORKS),
        };
    }

    auto movementTypeFromString(const std::string& s) -> std::optional<MovementType> {
        if (s == "zigzag") { return MovementType::ZIGZAG; }
        if (s == "tracking") { return MovementType::TRACKING; }
        if (s == "rectilinear_proximity") { return MovementType::RECTILINEAR_PROXIMITY; }
        if (s == "wander") { return MovementType::WANDER; }
        if (s == "chase") { return MovementType::CHASE; }
        if (s == "flee") { return MovementType::FLEE; }
        return std::nullopt;
    }

    auto aiActionTypeFromString(const std::string& s) -> std::optional<AiActionType> {
        if (s == "shoot") { return AiActionType::SHOOT; }
        return std::nullopt;
    }

    auto aimModeFromString(const std::string& s) -> std::optional<AimMode> {
        if (s == "random") { return AimMode::RANDOM; }
        if (s == "aimed") { return AimMode::AIMED; }
        return std::nullopt;
    }

    auto parseMovement(const fkyaml::node& mv_node, const std::string& enemy_name, MovementConfig& out) -> bool {
        if (!mv_node.contains("type")) {
            std::cerr << "[EnemyConfig] Error: falta 'ai.movement.type' a " << enemy_name << '\n';
            return false;
        }
        const auto TYPE_STR = mv_node["type"].get_value<std::string>();
        const auto PARSED = movementTypeFromString(TYPE_STR);
        if (!PARSED) {
            std::cerr << "[EnemyConfig] Error: movement type desconegut '" << TYPE_STR
                      << "' a " << enemy_name << '\n';
            return false;
        }
        out.type = *PARSED;
        const auto READ_OPT = [&mv_node](const char* key, float& dst) {
            if (mv_node.contains(key)) {
                dst = mv_node[key].get_value<float>();
            }
        };
        READ_OPT("angle_change_max", out.angle_change_max);
        READ_OPT("zigzag_prob_per_second", out.zigzag_prob_per_second);
        READ_OPT("tracking_strength", out.tracking_strength);
        READ_OPT("tracking_interval", out.tracking_interval);
        READ_OPT("rotation_proximity_multiplier", out.rotation_proximity_multiplier);
        READ_OPT("proximity_distance", out.proximity_distance);
        READ_OPT("chase_strength", out.chase_strength);
        READ_OPT("flee_strength", out.flee_strength);
        return true;
    }

    auto parseTickList(const fkyaml::node& list_node, const std::string& enemy_name, std::vector<AiTickAction>& out) -> bool {
        if (!list_node.is_sequence()) {
            std::cerr << "[EnemyConfig] Error: 'ai.tick' ha de ser una llista a " << enemy_name << '\n';
            return false;
        }
        for (const auto& item : list_node) {
            if (!item.contains("action")) {
                std::cerr << "[EnemyConfig] Error: entrada sense 'action' a ai.tick ("
                          << enemy_name << ")\n";
                return false;
            }
            const auto STR = item["action"].get_value<std::string>();
            const auto PARSED = aiActionTypeFromString(STR);
            if (!PARSED) {
                std::cerr << "[EnemyConfig] Error: acció d'ai desconeguda '" << STR
                          << "' a " << enemy_name << '\n';
                return false;
            }
            AiTickAction action;
            action.type = *PARSED;
            if (item.contains("interval")) {
                action.interval = item["interval"].get_value<float>();
            }
            if (item.contains("aim_mode")) {
                const auto AIM_STR = item["aim_mode"].get_value<std::string>();
                const auto AIM = aimModeFromString(AIM_STR);
                if (!AIM) {
                    std::cerr << "[EnemyConfig] Error: aim_mode desconegut '" << AIM_STR
                              << "' a ai.tick (" << enemy_name << ")\n";
                    return false;
                }
                action.aim_mode = *AIM;
            }
            if (item.contains("jitter_rad")) {
                action.jitter_rad = item["jitter_rad"].get_value<float>();
            }
            if (item.contains("bullet_speed")) {
                action.bullet_speed = item["bullet_speed"].get_value<float>();
            }
            if (item.contains("bullet")) {
                action.bullet_config_name = item["bullet"].get_value<std::string>();
            }
            out.push_back(action);
        }
        return true;
    }

    // Migració progressiva: si el YAML no porta secció `ai:`, derivem el
    // movement a partir de l'ai_type i copiem els paràmetres de la BehaviorCfg
    // ja parsejada. Comportament idèntic al hardcoded actual.
    void fillLegacyAiDefaults(EnemyType ai_type, const EnemyConfig::BehaviorCfg& legacy, EnemyAiConfig& out) {
        switch (ai_type) {
            case EnemyType::PENTAGON:
            case EnemyType::STAR:
                out.movement.type = MovementType::ZIGZAG;
                out.movement.angle_change_max = legacy.angle_change_max;
                out.movement.zigzag_prob_per_second = legacy.zigzag_prob_per_second;
                break;
            case EnemyType::SQUARE:
                out.movement.type = MovementType::TRACKING;
                out.movement.tracking_strength = legacy.tracking_strength;
                out.movement.tracking_interval = legacy.tracking_interval;
                break;
            case EnemyType::PINWHEEL:
                out.movement.type = MovementType::RECTILINEAR_PROXIMITY;
                out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
                out.movement.proximity_distance = legacy.proximity_distance;
                break;
            case EnemyType::ORB:
                // Sense legacy fallback: el YAML de l'orb ha de definir
                // ai.movement explícitament. Default chase lent perquè el switch
                // siga exhaustiu i no falli si algú omet el bloc ai.
                out.movement.type = MovementType::CHASE;
                out.movement.chase_strength = 0.3F;
                break;
        }
    }

    auto parseAi(const fkyaml::node& node, const std::string& name, EnemyType ai_type, const EnemyConfig::BehaviorCfg& legacy, EnemyAiConfig& out) -> bool {
        if (!node.contains("ai")) {
            fillLegacyAiDefaults(ai_type, legacy, out);
            return true;
        }
        const auto& ai = node["ai"];
        if (!ai.contains("movement")) {
            std::cerr << "[EnemyConfig] Error: falta 'ai.movement' a " << name << '\n';
            return false;
        }
        if (!parseMovement(ai["movement"], name, out.movement)) {
            return false;
        }
        if (ai.contains("tick") && !parseTickList(ai["tick"], name, out.tick)) {
            return false;
        }
        return true;
    }

    auto parseEvents(const fkyaml::node& node, const std::string& name, EnemyEventConfig& out) -> bool {
        if (!node.contains("events")) {
            fillLegacyDefaults(out);
            return true;
        }
        const auto& e = node["events"];
        if (e.contains("on_hit") && !parseActionList(e["on_hit"], name, "on_hit", out.on_hit)) {
            return false;
        }
        if (e.contains("on_no_health") &&
            !parseActionList(e["on_no_health"], name, "on_no_health", out.on_no_health)) {
            return false;
        }
        if (e.contains("on_hurt_end") &&
            !parseActionList(e["on_hurt_end"], name, "on_hurt_end", out.on_hurt_end)) {
            return false;
        }
        if (e.contains("on_destroy") &&
            !parseActionList(e["on_destroy"], name, "on_destroy", out.on_destroy)) {
            return false;
        }
        // Validació: destroy no pot aparèixer dins on_destroy (recursió infinita).
        for (const auto& a : out.on_destroy) {
            if (a.type == EnemyActionType::DESTROY) {
                std::cerr << "[EnemyConfig] Error: 'destroy' no pot aparèixer dins 'on_destroy' a "
                          << name << " (recursió infinita)\n";
                return false;
            }
        }
        return true;
    }

}  // namespace

auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
    -> std::optional<EnemyConfig> {
    try {
        EnemyConfig cfg;
        cfg.name = node.contains("name") ? node["name"].get_value<std::string>() : "enemy";

        if (!parseAiType(node, expected_ai_type, cfg.name, cfg.ai_type)) { return std::nullopt; }
        if (!parseShape(node, cfg.name, cfg.shape)) { return std::nullopt; }
        if (!parsePhysics(node, cfg.name, cfg.physics)) { return std::nullopt; }
        parseBehavior(node, cfg.behavior);
        if (!parseAnimation(node, cfg.name, cfg.animation)) { return std::nullopt; }
        if (!parseWounded(node, cfg.name, cfg.wounded)) { return std::nullopt; }
        if (!parseSpawn(node, cfg.name, cfg.spawn)) { return std::nullopt; }
        if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
        if (!parseScore(node, cfg.name, cfg.score)) { return std::nullopt; }
        parseHealth(node, cfg.health);
        if (!parseEvents(node, cfg.name, cfg.events)) { return std::nullopt; }
        if (!parseAi(node, cfg.name, cfg.ai_type, cfg.behavior, cfg.ai)) { return std::nullopt; }

        return cfg;
    } catch (const std::exception& e) {
        std::cerr << "[EnemyConfig] Excepció parsejant: " << e.what() << '\n';
        return std::nullopt;
    }
}
