#include "png_shape.hpp"

#include <algorithm>
#include <cmath>
#include <iostream>
#include <map>

#include "defines.hpp"
#include "external/stb_image.h"
#include "resource_manager.hpp"

PNGShape::PNGShape(const char* png_path) {
    // Cargar PNG desde path
    if (!loadPNG(png_path)) {
        std::cerr << "[PNGShape] Usando fallback 10x10" << '\n';
        // Fallback: generar un cuadrado simple si falla la carga
        image_width_ = 10;
        image_height_ = 10;
        pixel_data_.resize(100, true);  // Cuadrado 10x10 blanco
    }

    // Inicializar next_idle_time_ con valores apropiados (no hardcoded 5.0)
    next_idle_time_ = PNG_IDLE_TIME_MIN + (rand() % 1000) / 1000.0f * (PNG_IDLE_TIME_MAX - PNG_IDLE_TIME_MIN);
}

auto PNGShape::loadPNG(const char* resource_key) -> bool {
    {
        std::string fn = std::string(resource_key);
        fn = fn.substr(fn.find_last_of("\\/") + 1);
        std::cout << "[PNGShape] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
    }
    unsigned char* file_data = nullptr;
    size_t file_size = 0;
    if (!ResourceManager::loadResource(resource_key, file_data, file_size)) {
        std::cerr << "[PNGShape] ERROR: recurso no encontrado: " << resource_key << '\n';
        return false;
    }
    int width;
    int height;
    int channels;
    unsigned char* pixels = stbi_load_from_memory(file_data, static_cast<int>(file_size), &width, &height, &channels, 1);
    delete[] file_data;
    if (pixels == nullptr) {
        std::cerr << "[PNGShape] ERROR al decodificar PNG: " << stbi_failure_reason() << '\n';
        return false;
    }
    image_width_ = width;
    image_height_ = height;
    pixel_data_.resize(width * height);
    for (int i = 0; i < width * height; i++) {
        pixel_data_[i] = (pixels[i] > 128);
    }
    stbi_image_free(pixels);
    return true;
}

void PNGShape::detectEdges() {
    edge_points_.clear();

    // Detectar píxeles del contorno (píxeles blancos con al menos un vecino negro)
    for (int y = 0; y < image_height_; y++) {
        for (int x = 0; x < image_width_; x++) {
            int idx = (y * image_width_) + x;

            if (!pixel_data_[idx]) {
                continue;  // Solo píxeles blancos
            }

            // Verificar vecinos (arriba, abajo, izq, der)
            bool is_edge = false;

            if (x == 0 || x == image_width_ - 1 || y == 0 || y == image_height_ - 1) {
                is_edge = true;  // Bordes de la imagen
            } else {
                // Verificar 4 vecinos
                if (!pixel_data_[idx - 1] ||             // Izquierda
                    !pixel_data_[idx + 1] ||             // Derecha
                    !pixel_data_[idx - image_width_] ||  // Arriba
                    !pixel_data_[idx + image_width_]) {  // Abajo
                    is_edge = true;
                }
            }

            if (is_edge) {
                edge_points_.push_back({static_cast<float>(x), static_cast<float>(y)});
            }
        }
    }
}

void PNGShape::floodFill() {
    // TODO: Implementar flood-fill para Enfoque B (voxelización)
    // Por ahora, rellenar con todos los píxeles blancos
    filled_points_.clear();

    for (int y = 0; y < image_height_; y++) {
        for (int x = 0; x < image_width_; x++) {
            int idx = (y * image_width_) + x;
            if (pixel_data_[idx]) {
                filled_points_.push_back({static_cast<float>(x), static_cast<float>(y)});
            }
        }
    }
}

void PNGShape::generateExtrudedPoints() {
    if (PNG_USE_EDGES_ONLY) {
        // Usar solo bordes (contorno) de las letras
        detectEdges();
    } else {
        // Usar relleno completo (todos los píxeles blancos)
        floodFill();
    }
}

void PNGShape::generatePoints(int num_points, float screen_width, float screen_height) {
    num_points_ = num_points;
    extrusion_depth_ = screen_height * PNG_EXTRUSION_DEPTH_FACTOR;
    num_layers_ = PNG_NUM_EXTRUSION_LAYERS;

    // Generar AMBOS conjuntos de puntos (relleno Y bordes)
    floodFill();    // Generar filled_points_
    detectEdges();  // Generar edge_points_

    // Guardar copias originales (las funciones de filtrado modifican los vectores)
    std::vector<Point2D> filled_points_original = filled_points_;
    std::vector<Point2D> edge_points_original = edge_points_;

    // Conjunto de puntos ACTIVO (será modificado por filtros)
    std::vector<Point2D> active_points_data;
    std::string mode_name;

    // === SISTEMA DE DISTRIBUCIÓN ADAPTATIVA ===
    // Estrategia: Optimizar según número de pelotas disponibles
    // Objetivo: SIEMPRE intentar usar relleno primero, solo bordes si es necesario

    size_t num_2d_points = 0;
    size_t total_3d_points = 0;

    // NIVEL 1: Decidir punto de partida (relleno o bordes por configuración)
    if (PNG_USE_EDGES_ONLY) {
        active_points_data = edge_points_original;
        mode_name = "BORDES (config)";
    } else {
        active_points_data = filled_points_original;
        mode_name = "RELLENO";
    }

    num_2d_points = active_points_data.size();
    total_3d_points = num_2d_points * num_layers_;

    // NIVEL 2: Reducir capas AGRESIVAMENTE hasta 1 (priorizar calidad 2D sobre profundidad 3D)
    // Objetivo: Llenar bien el texto en 2D antes de reducir píxeles
    while (num_layers_ > 1 && num_points < static_cast<int>(total_3d_points)) {
        num_layers_ = std::max(1, num_layers_ / 2);
        total_3d_points = num_2d_points * num_layers_;
    }

    // NIVEL 3: Filas alternas en RELLENO (solo si 1 capa no alcanza)
    // Esto permite usar relleno incluso con pocas pelotas
    int row_skip = 1;
    if (!PNG_USE_EDGES_ONLY) {  // Solo si empezamos con relleno
        while (row_skip < 5 && num_points < static_cast<int>(total_3d_points)) {
            row_skip++;
            // ✅ CLAVE: Recalcular desde el ORIGINAL cada vez (no desde el filtrado previo)
            active_points_data = extractAlternateRows(filled_points_original, row_skip);
            num_2d_points = active_points_data.size();
            total_3d_points = num_2d_points * num_layers_;
            mode_name = "RELLENO + FILAS/" + std::to_string(row_skip);
        }
    }

    // NIVEL 4: Cambiar a BORDES (solo si relleno con filas alternas no alcanza)
    if (!PNG_USE_EDGES_ONLY && num_points < static_cast<int>(total_3d_points)) {
        active_points_data = edge_points_original;
        mode_name = "BORDES (auto)";
        num_2d_points = active_points_data.size();
        total_3d_points = num_2d_points * num_layers_;
        row_skip = 1;  // Reset row_skip para bordes
    }

    // NIVEL 5: Filas alternas en BORDES (si aún no alcanza)
    while (row_skip < 8 && num_points < static_cast<int>(total_3d_points)) {
        row_skip++;
        // ✅ CLAVE: Recalcular desde edge_points_original cada vez
        active_points_data = extractAlternateRows(edge_points_original, row_skip);
        num_2d_points = active_points_data.size();
        total_3d_points = num_2d_points * num_layers_;
        if (mode_name.find("FILAS") == std::string::npos) {
            mode_name += " + FILAS/" + std::to_string(row_skip);
        }
    }

    // NIVEL 6: Vértices/esquinas (último recurso, muy pocas pelotas)
    if (num_points < static_cast<int>(total_3d_points) && num_points < 150) {
        // Determinar desde qué conjunto extraer vértices (el que esté activo actualmente)
        const std::vector<Point2D>& source_for_vertices = (mode_name.find("BORDES") != std::string::npos)
            ? edge_points_original
            : filled_points_original;

        std::vector<Point2D> vertices = extractCornerVertices(source_for_vertices);
        if (!vertices.empty() && vertices.size() < active_points_data.size()) {
            active_points_data = vertices;
            mode_name = "VÉRTICES";
        }
    }

    // ✅ CLAVE: Guardar el conjunto de puntos optimizado final en optimized_points_ (usado por getPoint3D)
    optimized_points_ = active_points_data;

    // Calcular escala para centrar la imagen en pantalla
    float max_dimension = std::max(static_cast<float>(image_width_), static_cast<float>(image_height_));
    scale_factor_ = (screen_height * PNG_SIZE_FACTOR) / max_dimension;

    // Calcular offset para centrar
    center_offset_x_ = image_width_ * 0.5f;
    center_offset_y_ = image_height_ * 0.5f;
}

// Extraer filas alternas de puntos (FUNCIÓN PURA: no modifica parámetros)
// Recibe vector original y devuelve nuevo vector filtrado
auto PNGShape::extractAlternateRows(const std::vector<Point2D>& source, int row_skip) -> std::vector<PNGShape::Point2D> {
    std::vector<Point2D> result;

    if (row_skip <= 1 || source.empty()) {
        return source;  // Sin filtrado, devolver copia del original
    }

    // Organizar puntos por fila (Y)
    std::map<int, std::vector<Point2D>> rows;
    for (const auto& p : source) {
        int row = static_cast<int>(p.y);
        rows[row].push_back(p);
    }

    // Tomar solo cada N filas
    int row_counter = 0;
    for (const auto& [row_y, row_points] : rows) {
        if (row_counter % row_skip == 0) {
            result.insert(result.end(), row_points.begin(), row_points.end());
        }
        row_counter++;
    }

    return result;
}

// Extraer vértices y esquinas (FUNCIÓN PURA: devuelve nuevo vector)
auto PNGShape::extractCornerVertices(const std::vector<Point2D>& source) -> std::vector<PNGShape::Point2D> {
    std::vector<Point2D> result;

    if (source.empty()) {
        return result;
    }

    // Estrategia simple: tomar bordes extremos de cada fila
    // Esto da el "esqueleto" mínimo de las letras

    std::map<int, std::pair<float, float>> row_extremes;  // Y -> (min_x, max_x)

    for (const auto& p : source) {
        int row = static_cast<int>(p.y);
        if (row_extremes.find(row) == row_extremes.end()) {
            row_extremes[row] = {p.x, p.x};
        } else {
            row_extremes[row].first = std::min(row_extremes[row].first, p.x);
            row_extremes[row].second = std::max(row_extremes[row].second, p.x);
        }
    }

    // Generar puntos en extremos de cada fila
    for (const auto& [row_y, extremes] : row_extremes) {
        result.push_back({extremes.first, static_cast<float>(row_y)});       // Extremo izquierdo
        if (extremes.second != extremes.first) {                             // Solo añadir derecho si es diferente
            result.push_back({extremes.second, static_cast<float>(row_y)});  // Extremo derecho
        }
    }

    return result;
}

void PNGShape::update(float delta_time, float screen_width, float screen_height) {
    if (!is_flipping_) {
        // Estado IDLE: texto de frente con pivoteo sutil

        // Solo contar tiempo para flips si:
        // - NO está en modo LOGO, O
        // - Está en modo LOGO Y ha alcanzado umbral de convergencia (80%)
        bool can_start_flip = !is_logo_mode_ || convergence_threshold_reached_;

        if (can_start_flip) {
            idle_timer_ += delta_time;
        }

        // Pivoteo sutil constante (movimiento orgánico)
        tilt_x_ += 0.4f * delta_time;  // Velocidad sutil en X
        tilt_y_ += 0.6f * delta_time;  // Velocidad sutil en Y

        if (idle_timer_ >= next_idle_time_) {
            // Iniciar voltereta
            is_flipping_ = true;
            flip_timer_ = 0.0f;
            idle_timer_ = 0.0f;

            // Elegir eje aleatorio (0=X, 1=Y, 2=ambos)
            flip_axis_ = rand() % 3;

            // Próximo tiempo idle aleatorio (según modo LOGO o MANUAL)
            float idle_min = is_logo_mode_ ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN;
            float idle_max = is_logo_mode_ ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX;
            next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min);
        }
    } else {
        // Estado FLIP: voltereta en curso
        flip_timer_ += delta_time;

        // Rotar según eje elegido
        if (flip_axis_ == 0 || flip_axis_ == 2) {
            angle_x_ += PNG_FLIP_SPEED * delta_time;
        }
        if (flip_axis_ == 1 || flip_axis_ == 2) {
            angle_y_ += PNG_FLIP_SPEED * delta_time;
        }

        // Terminar voltereta
        if (flip_timer_ >= PNG_FLIP_DURATION) {
            is_flipping_ = false;
            // Resetear ángulos a 0 (volver de frente)
            angle_x_ = 0.0f;
            angle_y_ = 0.0f;
        }
    }

    // Detectar transición de flip (de true a false) para incrementar contador
    if (was_flipping_last_frame_ && !is_flipping_) {
        flip_count_++;  // Flip completado
    }
    was_flipping_last_frame_ = is_flipping_;
}

void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const {
    // Usar SIEMPRE el vector optimizado (resultado final de generatePoints)
    const std::vector<Point2D>& points = optimized_points_;

    if (points.empty()) {
        x = y = z = 0.0f;
        return;
    }

    // ENFOQUE A: Extrusión 2D
    // Cada punto 2D se replica en múltiples capas Z

    int num_2d_points = static_cast<int>(points.size());
    int point_2d_index = index % num_2d_points;
    int layer_index = (index / num_2d_points) % num_layers_;

    // Obtener coordenadas 2D del píxel
    Point2D p = points[point_2d_index];

    // Centrar y escalar
    float x_base = (p.x - center_offset_x_) * scale_factor_;
    float y_base = (p.y - center_offset_y_) * scale_factor_;

    // Calcular Z según capa (distribuir uniformemente en profundidad)
    float z_base = 0.0f;
    if (num_layers_ > 1) {
        float layer_step = extrusion_depth_ / static_cast<float>(num_layers_ - 1);
        z_base = -extrusion_depth_ * 0.5f + layer_index * layer_step;
    }

    // Añadir pivoteo sutil en estado IDLE
    // Calcular tamaño del logo en pantalla para normalizar correctamente
    float logo_width = image_width_ * scale_factor_;
    float logo_height = image_height_ * scale_factor_;
    float logo_size = std::max(logo_width, logo_height);

    // Normalizar coordenadas a rango [-1, 1]
    float u = x_base / (logo_size * 0.5f);
    float v = y_base / (logo_size * 0.5f);

    // Calcular pivoteo (amplitudes más grandes)
    float tilt_amount_x = sinf(tilt_x_) * 0.15f;  // 15%
    float tilt_amount_y = sinf(tilt_y_) * 0.1f;   // 10%

    // Aplicar pivoteo proporcional al tamaño del logo
    float z_tilt = (u * tilt_amount_y + v * tilt_amount_x) * logo_size;
    z_base += z_tilt;  // Añadir pivoteo sutil a la profundidad

    // Aplicar rotación en eje Y (horizontal)
    float cos_y = cosf(angle_y_);
    float sin_y = sinf(angle_y_);
    float x_rot_y = (x_base * cos_y) - (z_base * sin_y);
    float z_rot_y = (x_base * sin_y) + (z_base * cos_y);

    // Aplicar rotación en eje X (vertical)
    float cos_x = cosf(angle_x_);
    float sin_x = sinf(angle_x_);
    float y_rot = (y_base * cos_x) - (z_rot_y * sin_x);
    float z_rot = (y_base * sin_x) + (z_rot_y * cos_x);

    // Retornar coordenadas finales
    x = x_rot_y;
    y = y_rot;

    // Cuando está de frente (sin rotación), usar Z con pivoteo sutil
    if (angle_x_ == 0.0f && angle_y_ == 0.0f) {
        // De frente: usar z_base que incluye el pivoteo sutil
        z = z_base;
    } else {
        z = z_rot;
    }
}

auto PNGShape::getScaleFactor(float screen_height) const -> float {
    // Escala dinámica según resolución
    return PNG_SIZE_FACTOR;
}

// Sistema de convergencia: notificar a la figura sobre el % de pelotas en posición
void PNGShape::setConvergence(float convergence) {
    current_convergence_ = convergence;

    // Umbral de convergencia
    constexpr float CONVERGENCE_THRESHOLD = 0.4f;

    // Activar threshold cuando convergencia supera el umbral
    if (!convergence_threshold_reached_ && convergence >= CONVERGENCE_THRESHOLD) {
        convergence_threshold_reached_ = true;
    }

    // Desactivar threshold cuando convergencia cae por debajo del umbral
    if (convergence < CONVERGENCE_THRESHOLD) {
        convergence_threshold_reached_ = false;
    }
}

// Obtener progreso del flip actual (0.0 = inicio del flip, 1.0 = fin del flip)
auto PNGShape::getFlipProgress() const -> float {
    if (!is_flipping_) {
        return 0.0f;  // No está flipping, progreso = 0
    }

    // Calcular progreso normalizado (0.0 - 1.0)
    return flip_timer_ / PNG_FLIP_DURATION;
}
