#include "StaticGeometrySystem.hpp"
#include "../components/StaticGeometry.hpp"
#include "../components/StaticGeometryMember.hpp"
#include "../components/Transform.hpp"
#include "../components/Lod.hpp"
#include "../components/LodSettings.hpp"
#include <OgreSceneManager.h>
#include <OgreEntity.h>
#include <OgreMeshManager.h>
#include <OgreLogManager.h>
#include <OgreVector3.h>
#include <OgreQuaternion.h>
#include <OGRE/MeshLodGenerator/OgreMeshLodGenerator.h>
#include <OgreDistanceLodStrategy.h>
#include <OgrePixelCountLodStrategy.h>

StaticGeometrySystem::StaticGeometrySystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
    : m_world(world)
    , m_sceneMgr(sceneMgr)
    , m_regionQuery(world.query<StaticGeometryComponent>())
    , m_memberQuery(world.query<StaticGeometryMemberComponent>())
{
}

StaticGeometrySystem::~StaticGeometrySystem() = default;

void StaticGeometrySystem::initialize()
{
    if (m_initialized) return;
    m_initialized = true;
}

void StaticGeometrySystem::update()
{
    if (!m_initialized) return;

    // First pass: resolve region references for members and detect transform changes
    m_memberQuery.each([&](flecs::entity entity, StaticGeometryMemberComponent& member) {
        if (!member.regionId.empty() && !member.regionEntity.is_alive()) {
            // Try to find the region entity by regionId
            m_regionQuery.each([&](flecs::entity e, StaticGeometryComponent& region) {
                if (region.regionId == member.regionId) {
                    member.regionEntity = e;
                }
            });
        }
        
        // Check if transform has changed (using version tracking)
        if (entity.has<TransformComponent>()) {
            const auto& transform = entity.get<TransformComponent>();
            if (transform.node) {
                // Store current transform version in member for comparison
                // For now, we rely on the member.dirty flag being set externally
                // when TransformComponent changes
            }
        }
        
        // Mark region dirty if member is dirty
        if (member.dirty && member.regionEntity.is_alive()) {
            if (member.regionEntity.has<StaticGeometryComponent>()) {
                member.regionEntity.get_mut<StaticGeometryComponent>().markDirty();
            }
        }
    });

    // Second pass: rebuild dirty regions
    m_regionQuery.each([&](flecs::entity entity, StaticGeometryComponent& region) {
        if (region.dirty || !region.built) {
            buildRegion(entity, region);
        }
    });
}

void StaticGeometrySystem::buildRegion(flecs::entity regionEntity, StaticGeometryComponent& region)
{
    if (!regionEntity.is_alive()) return;

    // Destroy existing static geometry
    destroyOgreStaticGeometry(region);

    // Generate unique region name if not set
    if (region.regionName.empty()) {
        region.regionName = "StaticRegion_" + std::to_string(regionEntity.id());
    }

    // Create new StaticGeometry
    region.staticGeometry = m_sceneMgr->createStaticGeometry(region.regionName);
    region.staticGeometry->setCastShadows(region.castShadows);
    
    if (region.renderingDistance > 0.0f) {
        region.staticGeometry->setRenderingDistance(region.renderingDistance);
    }
    
    region.staticGeometry->setRegionDimensions(Ogre::Vector3(region.regionDimensions));
    region.staticGeometry->setVisibilityFlags(region.visibilityFlags);

    // Collect all members for this region
    std::vector<std::pair<flecs::entity, StaticGeometryMemberComponent*>> members;
    m_memberQuery.each([&](flecs::entity memberEntity, StaticGeometryMemberComponent& member) {
        if (member.regionEntity == regionEntity && member.visible) {
            members.emplace_back(memberEntity, &member);
        }
    });

    // Track member IDs
    region.memberEntityIds.clear();
    region.memberEntityIds.reserve(members.size());

    // Check LOD consistency across members
    std::string regionLodSettingsId;
    flecs::entity regionLodSettingsEntity;
    bool lodConsistent = true;
    
    for (auto& [memberEntity, member] : members) {
        if (!member->lodSettingsId.empty()) {
            if (regionLodSettingsId.empty()) {
                regionLodSettingsId = member->lodSettingsId;
                regionLodSettingsEntity = member->lodSettingsEntity;
            } else if (regionLodSettingsId != member->lodSettingsId) {
                lodConsistent = false;
            }
        }
    }
    
    if (!lodConsistent) {
        Ogre::LogManager::getSingleton().logMessage(
            "StaticGeometry WARNING: Region '" + region.regionName + 
            "' has members with different LOD Settings! Using first found.", 
            Ogre::LML_WARNING);
    }
    
    // Get LOD generator
    Ogre::MeshLodGenerator* lodGenerator = nullptr;
    try {
        lodGenerator = Ogre::MeshLodGenerator::getSingletonPtr();
    } catch (...) {
        // Generator not created yet
    }

    // Add each member's geometry
    for (auto& [memberEntity, member] : members) {
        if (member->meshName.empty()) continue;

        try {
            // Check if mesh exists
            if (!Ogre::MeshManager::getSingleton().resourceExists(member->meshName, 
                                                                   Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)) {
                continue;
            }

            // Get global position and orientation from TransformComponent if available
            Ogre::Vector3 position = Ogre::Vector3::ZERO;
            Ogre::Quaternion orientation = Ogre::Quaternion::IDENTITY;
            Ogre::Vector3 scale = Ogre::Vector3::UNIT_SCALE;
            
            if (memberEntity.has<TransformComponent>()) {
                const auto& transform = memberEntity.get<TransformComponent>();
                if (transform.node) {
                    // Use derived (global) position and orientation
                    position = transform.node->_getDerivedPosition();
                    orientation = transform.node->_getDerivedOrientation();
                    scale = transform.node->getScale();
                }
            }

            // Get the mesh and apply LOD if configured
            Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
                member->meshName,
                Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
            
            // Apply LOD if this member uses LOD and we have valid settings
            if (lodGenerator && !member->lodSettingsId.empty() && 
                member->lodSettingsEntity.is_alive() &&
                member->lodSettingsEntity.has<LodSettingsComponent>()) {
                
                const auto& lodSettings = member->lodSettingsEntity.get<LodSettingsComponent>();
                if (!lodSettings.lodLevels.empty()) {
                    // Build LOD config
                    Ogre::LodConfig lodConfig;
                    lodConfig.mesh = mesh;
                    
                    // Set strategy
                    switch (lodSettings.strategy) {
                        case LodSettingsComponent::Strategy::Distance:
                            lodConfig.strategy = Ogre::DistanceLodBoxStrategy::getSingletonPtr();
                            break;
                        case LodSettingsComponent::Strategy::PixelCount:
                        case LodSettingsComponent::Strategy::EdgePixelCount:
                            lodConfig.strategy = Ogre::PixelCountLodStrategy::getSingletonPtr();
                            break;
                    }
                    
                    // Build LOD levels
                    for (const auto& levelConfig : lodSettings.lodLevels) {
                        Ogre::LodLevel level;
                        level.distance = levelConfig.distance;
                        
                        if (levelConfig.useManualMesh && !levelConfig.manualMeshName.empty()) {
                            level.manualMeshName = levelConfig.manualMeshName;
                        } else {
                            switch (levelConfig.reductionMethod) {
                                case LodLevelConfig::ReductionMethod::Proportional:
                                    level.reductionMethod = Ogre::LodLevel::VRM_PROPORTIONAL;
                                    break;
                                case LodLevelConfig::ReductionMethod::Constant:
                                    level.reductionMethod = Ogre::LodLevel::VRM_CONSTANT;
                                    break;
                                case LodLevelConfig::ReductionMethod::CollapseCost:
                                    level.reductionMethod = Ogre::LodLevel::VRM_COLLAPSE_COST;
                                    break;
                            }
                            level.reductionValue = levelConfig.reductionValue;
                        }
                        lodConfig.levels.push_back(level);
                    }
                    
                    // Apply advanced settings
                    lodConfig.advanced.useCompression = lodSettings.useCompression;
                    lodConfig.advanced.useVertexNormals = lodSettings.useVertexNormals;
                    lodConfig.advanced.preventPunchingHoles = lodSettings.preventPunchingHoles;
                    lodConfig.advanced.preventBreakingLines = lodSettings.preventBreakingLines;
                    
                    // Generate LOD levels
                    lodGenerator->generateLodLevels(lodConfig);
                }
            }

            // Create a unique entity name for this instance
            std::string entityName = region.regionName + "_" + std::to_string(memberEntity.id()) + "_" + member->meshName;
            
            // Create temporary entity
            Ogre::Entity* tempEntity = m_sceneMgr->createEntity(entityName, mesh);
            
            // Apply material override if specified
            if (!member->materialName.empty()) {
                tempEntity->setMaterialName(member->materialName);
            }
            
            // Set shadow casting
            tempEntity->setCastShadows(member->castShadows);

            // Add to static geometry using global transform
            region.staticGeometry->addEntity(tempEntity, 
                                              position,
                                              orientation,
                                              scale);

            // Destroy the temporary entity - the geometry is now copied
            m_sceneMgr->destroyEntity(tempEntity);

            // Track this member
            region.memberEntityIds.push_back(memberEntity.id());
            member->dirty = false;

        } catch (const Ogre::Exception& e) {
            Ogre::LogManager::getSingleton().logMessage(
                "StaticGeometry: Failed to add mesh " + member->meshName + 
                ": " + e.getDescription());
        }
    }

    // Build the static geometry
    try {
        region.staticGeometry->build();
        region.built = true;
        region.dirty = false;
        region.buildVersion++;

        Ogre::LogManager::getSingleton().logMessage(
            "StaticGeometry: Built region '" + region.regionName + 
            "' with " + std::to_string(members.size()) + " entities");

    } catch (const Ogre::Exception& e) {
        Ogre::LogManager::getSingleton().logMessage(
            "StaticGeometry: Failed to build region '" + region.regionName + 
            "': " + e.getDescription());
        destroyOgreStaticGeometry(region);
        region.built = false;
        region.dirty = true;
    }
}

void StaticGeometrySystem::destroyOgreStaticGeometry(StaticGeometryComponent& region)
{
    if (region.staticGeometry) {
        m_sceneMgr->destroyStaticGeometry(region.staticGeometry);
        region.staticGeometry = nullptr;
    }
    region.built = false;
}

void StaticGeometrySystem::rebuildRegion(flecs::entity regionEntity)
{
    if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
        return;
    }
    
    auto& region = regionEntity.get_mut<StaticGeometryComponent>();
    region.markDirty();
    
    // Also mark all members as dirty to force refresh
    m_memberQuery.each([&](flecs::entity e, StaticGeometryMemberComponent& member) {
        if (member.regionEntity == regionEntity) {
            member.markDirty();
        }
    });
}

void StaticGeometrySystem::destroyRegion(flecs::entity regionEntity)
{
    if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
        return;
    }
    
    auto& region = regionEntity.get_mut<StaticGeometryComponent>();
    destroyOgreStaticGeometry(region);
}

Ogre::StaticGeometry* StaticGeometrySystem::getStaticGeometry(flecs::entity regionEntity)
{
    if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
        return nullptr;
    }
    
    return regionEntity.get<StaticGeometryComponent>().staticGeometry;
}

bool StaticGeometrySystem::isRegionDirty(flecs::entity regionEntity)
{
    if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
        return false;
    }
    
    const auto& region = regionEntity.get<StaticGeometryComponent>();
    if (region.dirty) return true;
    
    // Check if any members are dirty
    return areMembersDirty(regionEntity);
}

bool StaticGeometrySystem::areMembersDirty(flecs::entity regionEntity)
{
    bool dirty = false;
    m_memberQuery.each([&](flecs::entity e, StaticGeometryMemberComponent& member) {
        if (member.regionEntity == regionEntity && member.dirty) {
            dirty = true;
        }
    });
    return dirty;
}

std::size_t StaticGeometrySystem::getMemberCount(flecs::entity regionEntity)
{
    if (!regionEntity.is_alive() || !regionEntity.has<StaticGeometryComponent>()) {
        return 0;
    }
    
    return regionEntity.get<StaticGeometryComponent>().memberEntityIds.size();
}
