#include "LodSystem.hpp"
#include "../components/Lod.hpp"
#include "../components/LodSettings.hpp"
#include "../components/Renderable.hpp"
#include <OgreLogManager.h>
#include <OGRE/MeshLodGenerator/OgreMeshLodGenerator.h>
#include <OgreDistanceLodStrategy.h>
#include <OgrePixelCountLodStrategy.h>

EditorLodSystem::EditorLodSystem(flecs::world &world,
				 Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_lodQuery(world.query<LodComponent, RenderableComponent>())
	, m_settingsQuery(world.query<LodSettingsComponent>())
{
}

EditorLodSystem::~EditorLodSystem() = default;

void EditorLodSystem::initialize()
{
	if (m_initialized)
		return;

	// Create the LOD generator singleton
	m_lodGenerator = std::make_unique<Ogre::MeshLodGenerator>();

	m_initialized = true;
}

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

	// Process entities with LOD components
	m_lodQuery.each([&](flecs::entity entity, LodComponent &lod,
			    RenderableComponent &renderable) {
		// Try to resolve settings entity if we have an ID but no valid entity
		if (!lod.settingsId.empty() && !lod.hasValidSettings()) {
			m_world.query<LodSettingsComponent>().each(
				[&](flecs::entity e,
				    LodSettingsComponent &settings) {
					if (settings.settingsId ==
					    lod.settingsId) {
						lod.settingsEntity = e;
					}
				});
		}

		// Fallback: if we have a valid settingsEntity but no settingsId, get the ID from the settings
		if (lod.settingsId.empty() && lod.settingsEntity.is_alive() &&
		    lod.settingsEntity.has<LodSettingsComponent>()) {
			const auto &settings =
				lod.settingsEntity.get<LodSettingsComponent>();
			if (!settings.settingsId.empty()) {
				lod.settingsId = settings.settingsId;
			}
		}

		// Final fallback: if settingsId is still empty, try to use any available LOD settings
		if (lod.settingsId.empty() && !lod.hasValidSettings()) {
			m_world.query<LodSettingsComponent>().each(
				[&](flecs::entity e,
				    LodSettingsComponent &settings) {
					if (lod.settingsEntity ==
					    flecs::entity::null()) {
						lod.settingsEntity = e;
						lod.settingsId =
							settings.settingsId;
					}
				});
		}

		// Check if we need to apply/update LOD
		bool needsUpdate = lod.dirty;

		// Check if settings have been modified
		if (lod.hasValidSettings()) {
			const auto &settings =
				lod.settingsEntity.get<LodSettingsComponent>();
			if (settings.version != lod.appliedVersion) {
				needsUpdate = true;
			}
		}

		// Check if mesh changed
		if (!lod.lodApplied && !renderable.meshName.empty()) {
			needsUpdate = true;
		}

		if (needsUpdate) {
			applyLod(entity, lod, renderable);
		}
	});
}

void EditorLodSystem::applyLod(flecs::entity entity, LodComponent &lod,
			       RenderableComponent &renderable)
{
	// Skip if no settings entity
	if (!lod.settingsEntity.is_alive() ||
	    !lod.settingsEntity.has<LodSettingsComponent>()) {
		lod.lodApplied = false;
		lod.dirty = false;
		return;
	}

	// Get the settings
	const auto &settings = lod.settingsEntity.get<LodSettingsComponent>();

	// Skip if no LOD levels configured
	if (settings.lodLevels.empty()) {
		lod.lodApplied = false;
		lod.dirty = false;
		return;
	}

	// Need a loaded mesh
	if (renderable.meshName.empty() || !renderable.entity) {
		lod.dirty = false; // Will retry when mesh is loaded
		return;
	}

	try {
		Ogre::MeshPtr mesh = renderable.entity->getMesh();
		if (!mesh || !mesh->isLoaded()) {
			lod.dirty = false;
			return;
		}

		// Build Ogre LOD config
		Ogre::LodConfig lodConfig;
		buildOgreLodConfig(mesh, settings, lodConfig);

		// Apply distance multiplier
		if (lod.distanceMultiplier != 1.0f) {
			for (auto &level : lodConfig.levels) {
				level.distance *= lod.distanceMultiplier;
			}
		}

		// Generate LOD levels
		Ogre::LogManager::getSingleton().logMessage(
			"Generating LOD levels for: " + renderable.meshName);
		m_lodGenerator->generateLodLevels(lodConfig);

		lod.lodApplied = true;
		lod.appliedVersion = settings.version;
		lod.dirty = false;

	} catch (const Ogre::Exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"ERROR: Failed to generate LOD for " +
			renderable.meshName + ": " + e.getDescription());
		lod.dirty = false; // Don't retry immediately
	}
}

void EditorLodSystem::buildOgreLodConfig(Ogre::MeshPtr mesh,
					 const LodSettingsComponent &settings,
					 Ogre::LodConfig &outConfig)
{
	outConfig.mesh = mesh;

	// Set strategy
	switch (settings.strategy) {
	case LodSettingsComponent::Strategy::Distance:
		outConfig.strategy =
			Ogre::DistanceLodBoxStrategy::getSingletonPtr();
		break;
	case LodSettingsComponent::Strategy::PixelCount:
		outConfig.strategy =
			Ogre::PixelCountLodStrategy::getSingletonPtr();
		break;
	case LodSettingsComponent::Strategy::EdgePixelCount:
		// Fallback to pixel count
		outConfig.strategy =
			Ogre::PixelCountLodStrategy::getSingletonPtr();
		break;
	}

	// Build LOD levels
	outConfig.levels.clear();
	for (const auto &levelConfig : settings.lodLevels) {
		Ogre::LodLevel level;
		level.distance = levelConfig.distance;

		if (levelConfig.useManualMesh &&
		    !levelConfig.manualMeshName.empty()) {
			level.manualMeshName = levelConfig.manualMeshName;
		} else {
			// Set reduction method
			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;
			default:
				level.reductionMethod =
					Ogre::LodLevel::VRM_PROPORTIONAL;
				break;
			}
			level.reductionValue = levelConfig.reductionValue;
		}

		outConfig.levels.push_back(level);
	}

	// Advanced settings
	outConfig.advanced.useCompression = settings.useCompression;
	outConfig.advanced.useVertexNormals = settings.useVertexNormals;
	outConfig.advanced.preventPunchingHoles = settings.preventPunchingHoles;
	outConfig.advanced.preventBreakingLines = settings.preventBreakingLines;
	outConfig.advanced.useBackgroundQueue = settings.useBackgroundQueue;
	outConfig.advanced.outsideWeight = settings.outsideWeight;
	outConfig.advanced.outsideWalkAngle = settings.outsideWalkAngle;
}

void EditorLodSystem::regenerateLodForSettings(flecs::entity settingsEntity)
{
	if (!settingsEntity.is_alive() ||
	    !settingsEntity.has<LodSettingsComponent>()) {
		return;
	}

	// Mark all LOD components using these settings as dirty
	m_lodQuery.each([&](flecs::entity entity, LodComponent &lod,
			    RenderableComponent &renderable) {
		if (lod.settingsEntity == settingsEntity) {
			lod.markDirty();
		}
	});
}

bool EditorLodSystem::hasLodGenerated(Ogre::MeshPtr mesh) const
{
	if (!mesh)
		return false;
	return mesh->getNumLodLevels() > 1;
}
