#pragma once

#include "warfactory/IDataNode.h"
#include <vector>
#include <string>
#include <unordered_map>
#include <cmath>

namespace warfactory {

/**
 * @brief Universal region fusion system managing proximity-based region merging with templates
 *
 * Template-based fusion system that can handle any region type using duck typing.
 * Requires RegionType to have: getNameType(), getX(), getY(), getMass(), setPosition(), addMass()
 *
 * Handles fusion of overlapping or nearby regions based on configurable criteria.
 * Supports different radius calculation methods, fusion thresholds, and applies
 * to all region types (ResourceRegion, TectonicRegion, ClimateRegion, etc.) universally.
 *
 * Fusion process: Check proximity → Check same type → Merge masses → Update positions
 */
template<typename RegionType>
class UniversalRegionFusionFunction {
public:
    enum class RadiusCalculation {
        NATURAL_LOGARITHM,    // ln(mass / mass_reference) * base_radius
        SQUARE_ROOT,          // sqrt(mass / mass_reference) * base_radius
        LINEAR,               // (mass / mass_reference) * base_radius
        FIXED                 // base_radius (constant)
    };

    enum class MassCombination {
        ADDITIVE,             // total_mass = mass1 + mass2
        WEIGHTED_AVERAGE,     // average based on individual masses
        LARGEST_WINS,         // keep mass of largest region
        MULTIPLICATIVE        // total_mass = mass1 * mass2 * factor
    };

private:
    bool enabled;                              // Enable/disable fusion system
    RadiusCalculation radius_calculation;      // Method for calculating region radius
    float base_radius;                         // Base radius multiplier
    float mass_reference;                      // Reference mass for radius calculations
    float fusion_threshold_percentage;        // Overlap percentage required for fusion (0.0-1.0)
    bool use_largest_radius_for_threshold;    // Use largest radius for threshold calculation
    bool weighted_position_update;            // Use mass-weighted position calculation
    MassCombination mass_combination;          // How to combine masses during fusion
    bool applies_to_all_region_types;         // Apply fusion to all region types

public:
    UniversalRegionFusionFunction();

    // ========================================
    // CONFIGURATION
    // ========================================

    /**
     * @brief Configure fusion system from JSON parameters
     * @param config_node JSON configuration containing manage_all_region_fusion parameters
     * Expected structure:
     * {
     *   "enabled": true,
     *   "radius_calculation": "natural_logarithm",
     *   "base_radius": 1.5,
     *   "mass_reference": 5.0,
     *   "fusion_threshold_percentage": 0.3,
     *   "use_largest_radius_for_threshold": true,
     *   "weighted_position_update": true,
     *   "mass_combination": "additive",
     *   "applies_to_all_region_types": true
     * }
     */
    void configure(const IDataNode& config_node);

    /**
     * @brief Enable or disable the fusion system
     * @param enable True to enable fusion, false to disable
     */
    void setEnabled(bool enable) { enabled = enable; }

    /**
     * @brief Set radius calculation method
     * @param method RadiusCalculation method to use
     */
    void setRadiusCalculation(RadiusCalculation method) { radius_calculation = method; }

    /**
     * @brief Set base radius and mass reference parameters
     * @param base_rad Base radius multiplier
     * @param mass_ref Reference mass for calculations
     */
    void setRadiusParameters(float base_rad, float mass_ref);

    /**
     * @brief Set fusion threshold percentage
     * @param threshold Overlap percentage required for fusion (0.0 to 1.0)
     */
    void setFusionThreshold(float threshold) { fusion_threshold_percentage = threshold; }

    /**
     * @brief Set mass combination method for fusion
     * @param method How to combine masses when regions fuse
     */
    void setMassCombination(MassCombination method) { mass_combination = method; }

    // ========================================
    // FUSION PROCESS
    // ========================================

    /**
     * @brief Execute region fusion for one geological cycle
     * @param regions Vector of all regions to process (MODIFIED - regions may be merged/removed)
     * @param cycle_duration_years Duration of current cycle in years
     * @return Number of fusion operations performed
     *
     * Template constraints: RegionType must have:
     * - std::string getNameType() const
     * - float getX() const, float getY() const
     * - float getMass() const
     * - void setPosition(float x, float y)
     * - void addMass(float mass)
     */
    int executeFusion(std::vector<RegionType>& regions, double cycle_duration_years) {
        if (!enabled) return 0;

        int fusion_count = 0;
        bool found_fusion = true;

        while (found_fusion) {
            found_fusion = false;

            for (size_t i = 0; i < regions.size() && !found_fusion; ++i) {
                for (size_t j = i + 1; j < regions.size(); ++j) {
                    if (shouldFuseRegions(regions[i], regions[j])) {
                        fuseRegions(regions[i], regions[j]);
                        regions.erase(regions.begin() + j);
                        fusion_count++;
                        found_fusion = true;
                        break;
                    }
                }
            }
        }

        return fusion_count;
    }

    /**
     * @brief Process fusion for specific region types only
     * @param regions Vector of regions to process
     * @param allowed_types Vector of region type names to include in fusion
     * @return Number of fusion operations performed
     */
    int executeFusionForTypes(std::vector<RegionType>& regions, const std::vector<std::string>& allowed_types) {
        if (!enabled) return 0;

        int fusion_count = 0;
        bool found_fusion = true;

        while (found_fusion) {
            found_fusion = false;

            for (size_t i = 0; i < regions.size() && !found_fusion; ++i) {
                // Check if region type is allowed
                bool type_allowed = false;
                for (const auto& allowed_type : allowed_types) {
                    if (regions[i].getNameType() == allowed_type) {
                        type_allowed = true;
                        break;
                    }
                }
                if (!type_allowed) continue;

                for (size_t j = i + 1; j < regions.size(); ++j) {
                    if (shouldFuseRegions(regions[i], regions[j])) {
                        fuseRegions(regions[i], regions[j]);
                        regions.erase(regions.begin() + j);
                        fusion_count++;
                        found_fusion = true;
                        break;
                    }
                }
            }
        }

        return fusion_count;
    }

    /**
     * @brief Check if two regions should be fused based on proximity and same type
     * @param region1 First region to check
     * @param region2 Second region to check
     * @return True if regions meet fusion criteria (same type + proximity)
     */
    bool shouldFuseRegions(const RegionType& region1, const RegionType& region2) const {
        // Must have same type to fuse (duck typing constraint)
        if (region1.getNameType() != region2.getNameType()) {
            return false;
        }

        // Calculate distance and overlap
        float distance = calculateRegionDistance(region1, region2);
        float overlap = calculateOverlapPercentage(region1, region2, distance);

        return overlap >= fusion_threshold_percentage;
    }

    /**
     * @brief Perform fusion of two regions into one
     * @param region1 First region (will be modified to become fused region)
     * @param region2 Second region (data will be merged into region1)
     * @return True if fusion was successful
     */
    bool fuseRegions(RegionType& region1, const RegionType& region2) const {
        // Calculate new position
        auto new_position = calculateFusedPosition(region1, region2);
        region1.setPosition(new_position.first, new_position.second);

        // Combine masses
        float new_mass = calculateFusedMass(region1.getMass(), region2.getMass());
        region1.addMass(new_mass - region1.getMass()); // Add difference

        return true;
    }

    // ========================================
    // ANALYSIS AND UTILITIES
    // ========================================

    /**
     * @brief Calculate region radius based on mass and current settings
     * @param region_mass Mass of the region
     * @return Calculated radius for this region
     */
    float calculateRegionRadius(float region_mass) const {
        float ratio = region_mass / mass_reference;

        switch (radius_calculation) {
            case RadiusCalculation::NATURAL_LOGARITHM:
                return std::log(std::max(ratio, 0.1f)) * base_radius;
            case RadiusCalculation::SQUARE_ROOT:
                return std::sqrt(ratio) * base_radius;
            case RadiusCalculation::LINEAR:
                return ratio * base_radius;
            case RadiusCalculation::FIXED:
                return base_radius;
            default:
                return base_radius;
        }
    }

    /**
     * @brief Calculate distance between two regions
     * @param region1 First region
     * @param region2 Second region
     * @return Distance between region centers
     */
    float calculateRegionDistance(const RegionType& region1, const RegionType& region2) const {
        float dx = region1.getX() - region2.getX();
        float dy = region1.getY() - region2.getY();
        return std::sqrt(dx * dx + dy * dy);
    }

    /**
     * @brief Calculate overlap percentage between two regions
     * @param region1 First region
     * @param region2 Second region
     * @param distance Distance between regions (optional optimization)
     * @return Overlap percentage (0.0 = no overlap, 1.0 = complete overlap)
     */
    float calculateOverlapPercentage(const RegionType& region1, const RegionType& region2, float distance = -1.0f) const {
        if (distance < 0) {
            distance = calculateRegionDistance(region1, region2);
        }

        float radius1 = calculateRegionRadius(region1.getMass());
        float radius2 = calculateRegionRadius(region2.getMass());

        float threshold_radius = use_largest_radius_for_threshold ?
                                std::max(radius1, radius2) :
                                std::min(radius1, radius2);

        if (distance >= (radius1 + radius2)) {
            return 0.0f; // No overlap
        }

        // Simple overlap calculation
        float overlap_distance = (radius1 + radius2) - distance;
        float max_overlap = std::min(radius1, radius2) * 2.0f;

        return std::min(1.0f, overlap_distance / max_overlap);
    }

    /**
     * @brief Get all regions that could potentially fuse with target region
     * @param target_region Region to find fusion candidates for
     * @param all_regions Vector of all regions to check against
     * @return Vector of region indices that could fuse with target
     */
    std::vector<int> getFusionCandidates(const RegionType& target_region, const std::vector<RegionType>& all_regions) const {
        std::vector<int> candidates;

        for (size_t i = 0; i < all_regions.size(); ++i) {
            if (&all_regions[i] != &target_region && shouldFuseRegions(target_region, all_regions[i])) {
                candidates.push_back(static_cast<int>(i));
            }
        }

        return candidates;
    }

    /**
     * @brief Calculate fused region position using mass weighting
     * @param region1 First region
     * @param region2 Second region
     * @return New position coordinates (x, y) for fused region
     */
    std::pair<float, float> calculateFusedPosition(const RegionType& region1, const RegionType& region2) const {
        if (!weighted_position_update) {
            // Simple average
            return {
                (region1.getX() + region2.getX()) * 0.5f,
                (region1.getY() + region2.getY()) * 0.5f
            };
        }

        // Mass-weighted position
        float mass1 = region1.getMass();
        float mass2 = region2.getMass();
        float total_mass = mass1 + mass2;

        float new_x = (region1.getX() * mass1 + region2.getX() * mass2) / total_mass;
        float new_y = (region1.getY() * mass1 + region2.getY() * mass2) / total_mass;

        return {new_x, new_y};
    }

    /**
     * @brief Calculate fused region mass based on combination method
     * @param mass1 Mass of first region
     * @param mass2 Mass of second region
     * @return Combined mass for fused region
     */
    float calculateFusedMass(float mass1, float mass2) const {
        switch (mass_combination) {
            case MassCombination::ADDITIVE:
                return mass1 + mass2;
            case MassCombination::WEIGHTED_AVERAGE:
                return (mass1 + mass2) * 0.5f;
            case MassCombination::LARGEST_WINS:
                return std::max(mass1, mass2);
            case MassCombination::MULTIPLICATIVE:
                return mass1 * mass2 * 0.001f; // Scale down to prevent explosion
            default:
                return mass1 + mass2; // Default to additive
        }
    }

    /**
     * @brief Check if region type is allowed for fusion
     * @param region_type Type name of region
     * @return True if this region type can participate in fusion
     */
    bool isRegionTypeAllowed(const std::string& region_type) const;

    /**
     * @brief Get fusion statistics for analysis
     * @param regions Vector of regions to analyze
     * @return Map containing fusion statistics (candidate_pairs, total_regions, etc.)
     */
    std::unordered_map<std::string, int> getFusionStatistics(const std::vector<RegionType>& regions) const {
        std::unordered_map<std::string, int> stats;
        stats["total_regions"] = static_cast<int>(regions.size());

        int candidate_pairs = 0;
        for (size_t i = 0; i < regions.size(); ++i) {
            for (size_t j = i + 1; j < regions.size(); ++j) {
                if (shouldFuseRegions(regions[i], regions[j])) {
                    candidate_pairs++;
                }
            }
        }
        stats["candidate_pairs"] = candidate_pairs;

        return stats;
    }

    // ========================================
    // UTILITY FUNCTIONS
    // ========================================

    /**
     * @brief Convert string to RadiusCalculation enum
     * @param method_str String representation of radius calculation method
     * @return RadiusCalculation enum value
     */
    static RadiusCalculation stringToRadiusCalculation(const std::string& method_str);

    /**
     * @brief Convert string to MassCombination enum
     * @param combination_str String representation of mass combination method
     * @return MassCombination enum value
     */
    static MassCombination stringToMassCombination(const std::string& combination_str);

    /**
     * @brief Convert RadiusCalculation enum to string
     * @param method RadiusCalculation enum value
     * @return String representation
     */
    static std::string radiusCalculationToString(RadiusCalculation method);

    /**
     * @brief Convert MassCombination enum to string
     * @param combination MassCombination enum value
     * @return String representation
     */
    static std::string massCombinationToString(MassCombination combination);

private:
    /**
     * @brief Find all region pairs that meet fusion criteria
     * @param regions Vector of regions to analyze
     * @return Vector of region index pairs that should be fused
     */
    std::vector<std::pair<int, int>> findFusionPairs(const std::vector<Region>& regions) const;

    /**
     * @brief Remove region from vector by index (used after fusion)
     * @param regions Vector to modify
     * @param index Index of region to remove
     */
    void removeRegionAt(std::vector<Region>& regions, int index) const;

    /**
     * @brief Validate configuration parameters
     * @return True if all parameters are valid
     */
    bool validateConfiguration() const;
};

} // namespace warfactory