#include "shape_manager.hpp"

#include <algorithm>   // for std::min, std::max, std::transform
#include <cctype>      // for ::tolower
#include <cstdlib>     // for rand
#include <string>      // for std::string

#include "ball.hpp"                   // for Ball
#include "defines.hpp"                // for constantes
#include "scene/scene_manager.hpp"    // for SceneManager
#include "state/state_manager.hpp"    // for StateManager
#include "ui/ui_manager.hpp"          // for UIManager

// Includes de todas las shapes (necesario para creación polimórfica)
#include "shapes/atom_shape.hpp"
#include "shapes/cube_shape.hpp"
#include "shapes/cylinder_shape.hpp"
#include "shapes/helix_shape.hpp"
#include "shapes/icosahedron_shape.hpp"
#include "shapes/lissajous_shape.hpp"
#include "shapes/png_shape.hpp"
#include "shapes/sphere_shape.hpp"
#include "shapes/torus_shape.hpp"

ShapeManager::ShapeManager()
    : engine_(nullptr)
    , scene_mgr_(nullptr)
    , ui_mgr_(nullptr)
    , state_mgr_(nullptr)
    , current_mode_(SimulationMode::PHYSICS)
    , current_shape_type_(ShapeType::SPHERE)
    , last_shape_type_(ShapeType::SPHERE)
    , active_shape_(nullptr)
    , shape_scale_factor_(1.0f)
    , depth_zoom_enabled_(true)
    , screen_width_(0)
    , screen_height_(0)
    , shape_convergence_(0.0f) {
}

ShapeManager::~ShapeManager() {
}

void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
                              StateManager* state_mgr, int screen_width, int screen_height) {
    engine_ = engine;
    scene_mgr_ = scene_mgr;
    ui_mgr_ = ui_mgr;
    state_mgr_ = state_mgr;
    screen_width_ = screen_width;
    screen_height_ = screen_height;
}

void ShapeManager::updateScreenSize(int width, int height) {
    screen_width_ = width;
    screen_height_ = height;
}

// ============================================================================
// IMPLEMENTACIÓN COMPLETA - Migrado desde Engine
// ============================================================================

void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
    if (current_mode_ == SimulationMode::PHYSICS) {
        // Cambiar a modo figura (usar última figura seleccionada)
        activateShapeInternal(last_shape_type_);

        // Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO
        if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) {
            if (active_shape_) {
                PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
                if (png_shape) {
                    png_shape->setLogoMode(true);
                }
            }
        }

        // Si estamos en LOGO MODE, resetear convergencia al entrar
        if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) {
            shape_convergence_ = 0.0f;
        }
    } else {
        // Volver a modo física normal
        current_mode_ = SimulationMode::PHYSICS;

        // Desactivar atracción y resetear escala de profundidad
        scene_mgr_->enableShapeAttractionAll(false);
        scene_mgr_->resetDepthScalesAll();  // Reset escala a 100% (evita "pop" visual)

        // Activar gravedad al salir (solo si se especifica)
        if (force_gravity_on_exit) {
            scene_mgr_->forceBallsGravityOn();
        }

        // Mostrar notificación (solo si NO estamos en modo demo o logo)
        if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
            ui_mgr_->showNotification("Modo física");
        }
    }
}

void ShapeManager::activateShape(ShapeType type) {
    activateShapeInternal(type);
}

void ShapeManager::handleShapeScaleChange(bool increase) {
    if (current_mode_ == SimulationMode::SHAPE) {
        if (increase) {
            shape_scale_factor_ += SHAPE_SCALE_STEP;
        } else {
            shape_scale_factor_ -= SHAPE_SCALE_STEP;
        }
        clampShapeScale();

        // Mostrar notificación si está en modo SANDBOX
        if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
            std::string notification = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
            ui_mgr_->showNotification(notification);
        }
    }
}

void ShapeManager::resetShapeScale() {
    if (current_mode_ == SimulationMode::SHAPE) {
        shape_scale_factor_ = SHAPE_SCALE_DEFAULT;

        // Mostrar notificación si está en modo SANDBOX
        if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
            ui_mgr_->showNotification("Escala 100%");
        }
    }
}

void ShapeManager::toggleDepthZoom() {
    if (current_mode_ == SimulationMode::SHAPE) {
        depth_zoom_enabled_ = !depth_zoom_enabled_;

        // Mostrar notificación si está en modo SANDBOX
        if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
            ui_mgr_->showNotification(depth_zoom_enabled_ ? "Profundidad on" : "Profundidad off");
        }
    }
}

void ShapeManager::update(float delta_time) {
    if (!active_shape_ || current_mode_ != SimulationMode::SHAPE) return;

    // Actualizar animación de la figura
    active_shape_->update(delta_time, static_cast<float>(screen_width_), static_cast<float>(screen_height_));

    // Obtener factor de escala para física (base de figura + escala manual)
    float scale_factor = active_shape_->getScaleFactor(static_cast<float>(screen_height_)) * shape_scale_factor_;

    // Centro de la pantalla
    float center_x = screen_width_ / 2.0f;
    float center_y = screen_height_ / 2.0f;

    // Obtener referencia mutable a las bolas desde SceneManager
    auto& balls = scene_mgr_->getBallsMutable();

    // Actualizar cada pelota con física de atracción
    for (size_t i = 0; i < balls.size(); i++) {
        // Obtener posición 3D rotada del punto i
        float x_3d, y_3d, z_3d;
        active_shape_->getPoint3D(static_cast<int>(i), x_3d, y_3d, z_3d);

        // Aplicar escala manual a las coordenadas 3D
        x_3d *= shape_scale_factor_;
        y_3d *= shape_scale_factor_;
        z_3d *= shape_scale_factor_;

        // Proyección 2D ortográfica (punto objetivo móvil)
        float target_x = center_x + x_3d;
        float target_y = center_y + y_3d;

        // Actualizar target de la pelota para cálculo de convergencia
        balls[i]->setShapeTarget2D(target_x, target_y);

        // Aplicar fuerza de atracción física hacia el punto rotado
        // Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
        float shape_size = scale_factor * 80.0f;  // 80px = radio base
        balls[i]->applyShapeForce(target_x, target_y, shape_size, delta_time,
                                 SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR,
                                 SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE);

        // Calcular brillo según profundidad Z para renderizado
        // Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)
        float z_normalized = (z_3d + shape_size) / (2.0f * shape_size);
        z_normalized = std::max(0.0f, std::min(1.0f, z_normalized));
        balls[i]->setDepthBrightness(z_normalized);

        // Calcular escala según profundidad Z (perspectiva) - solo si está activado
        // 0.0 (fondo) → 0.5x, 0.5 (medio) → 1.0x, 1.0 (frente) → 1.5x
        float depth_scale = depth_zoom_enabled_ ? (0.5f + z_normalized * 1.0f) : 1.0f;
        balls[i]->setDepthScale(depth_scale);
    }

    // Calcular convergencia en LOGO MODE (% de pelotas cerca de su objetivo)
    if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
        int balls_near = 0;
        float distance_threshold = LOGO_CONVERGENCE_DISTANCE;  // 20px fijo (más permisivo)

        for (const auto& ball : balls) {
            if (ball->getDistanceToTarget() < distance_threshold) {
                balls_near++;
            }
        }

        shape_convergence_ = static_cast<float>(balls_near) / scene_mgr_->getBallCount();

        // Notificar a la figura sobre el porcentaje de convergencia
        // Esto permite que PNGShape decida cuándo empezar a contar para flips
        active_shape_->setConvergence(shape_convergence_);
    }
}

void ShapeManager::generateShape() {
    if (!active_shape_) return;

    int num_points = static_cast<int>(scene_mgr_->getBallCount());
    active_shape_->generatePoints(num_points, static_cast<float>(screen_width_), static_cast<float>(screen_height_));
}

// ============================================================================
// MÉTODOS PRIVADOS
// ============================================================================

void ShapeManager::activateShapeInternal(ShapeType type) {
    // Guardar como última figura seleccionada
    last_shape_type_ = type;
    current_shape_type_ = type;

    // Cambiar a modo figura
    current_mode_ = SimulationMode::SHAPE;

    // Desactivar gravedad al entrar en modo figura
    scene_mgr_->forceBallsGravityOff();

    // Crear instancia polimórfica de la figura correspondiente
    switch (type) {
        case ShapeType::SPHERE:
            active_shape_ = std::make_unique<SphereShape>();
            break;
        case ShapeType::CUBE:
            active_shape_ = std::make_unique<CubeShape>();
            break;
        case ShapeType::HELIX:
            active_shape_ = std::make_unique<HelixShape>();
            break;
        case ShapeType::TORUS:
            active_shape_ = std::make_unique<TorusShape>();
            break;
        case ShapeType::LISSAJOUS:
            active_shape_ = std::make_unique<LissajousShape>();
            break;
        case ShapeType::CYLINDER:
            active_shape_ = std::make_unique<CylinderShape>();
            break;
        case ShapeType::ICOSAHEDRON:
            active_shape_ = std::make_unique<IcosahedronShape>();
            break;
        case ShapeType::ATOM:
            active_shape_ = std::make_unique<AtomShape>();
            break;
        case ShapeType::PNG_SHAPE:
            active_shape_ = std::make_unique<PNGShape>("shapes/jailgames.png");
            break;
        default:
            active_shape_ = std::make_unique<SphereShape>();  // Fallback
            break;
    }

    // Generar puntos de la figura
    generateShape();

    // Activar atracción física en todas las pelotas
    scene_mgr_->enableShapeAttractionAll(true);

    // Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo)
    if (active_shape_ && state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
        std::string shape_name = active_shape_->getName();
        std::transform(shape_name.begin(), shape_name.end(), shape_name.begin(), ::tolower);
        std::string notification = std::string("Modo ") + shape_name;
        ui_mgr_->showNotification(notification);
    }
}

void ShapeManager::setShapeScaleFactor(float scale) {
    shape_scale_factor_ = scale;
    clampShapeScale();
}

void ShapeManager::clampShapeScale() {
    // Calcular tamaño máximo permitido según resolución actual
    // La figura más grande (esfera/cubo) usa ~33% de altura por defecto
    // Permitir hasta que la figura ocupe 90% de la dimensión más pequeña
    float max_dimension = std::min(screen_width_, screen_height_);
    float base_size_factor = 0.333f;  // ROTOBALL_RADIUS_FACTOR o similar
    float max_scale_for_screen = (max_dimension * 0.9f) / (max_dimension * base_size_factor);

    // Limitar entre SHAPE_SCALE_MIN y el mínimo de (SHAPE_SCALE_MAX, max_scale_for_screen)
    float max_allowed = std::min(SHAPE_SCALE_MAX, max_scale_for_screen);
    shape_scale_factor_ = std::max(SHAPE_SCALE_MIN, std::min(max_allowed, shape_scale_factor_));
}
