#pragma once
#include "ShaderCache.h"

struct InteriorSun : Feature
{
public:
	virtual inline std::string GetName() override { return "Interior Sun"; }
	virtual std::string GetDisplayName() override { return T("feature.interior_sun.name", "Interior Sun"); }
	virtual inline std::string GetShortName() override { return "InteriorSun"; }
	virtual std::string_view GetCategory() const override { return FeatureCategories::kLighting; }
	virtual std::pair<std::string, std::vector<std::string>> GetFeatureSummary() override
	{
		return { T("feature.interior_sun.description", "Allows for the sun and moon to cast light and shadows into interior spaces."),
			{ T("feature.interior_sun.key_feature_1", "Functions only for explicitly enabled interiors"),
				T("feature.interior_sun.key_feature_2", "Utilizes existing sun, moon, and weather systems"),
				T("feature.interior_sun.key_feature_3", "Includes an option to force double-sided rendering for unprepared interiors"),
				T("feature.interior_sun.key_feature_4", "Fixes geometry culling issues that cause light leakage") } };
	};

	/** @brief Draws the ImGui settings UI for Interior Sun configuration. */
	virtual void DrawSettings() override;
	virtual void LoadSettings(json& o_json) override;
	virtual void SaveSettings(json& o_json) override;
	virtual void RestoreDefaultSettings() override;
	/** @brief Installs rendering hooks and patches for interior sun/shadow support. */
	virtual void PostPostLoad() override;
	/** @brief Updates the interior sun state based on the current cell during the early prepass. */
	virtual void EarlyPrepass() override;

	/** @brief Registers the menu open/close event handler for tracking main menu state. */
	virtual void DataLoaded() override
	{
		MenuOpenCloseEventHandler::Register();
	}

	struct Settings
	{
		bool ForceDoubleSidedRendering = true;
		float InteriorShadowDistance = 5000;
	};

	Settings settings;

	std::atomic<bool> isInteriorWithSun = false;

	/** @brief Hook that intercepts world space queries to enable/disable interior sun based on cell flags. */
	struct GetWorldSpace
	{
		static RE::TESWorldSpace* thunk(RE::TES* tes);
		static inline REL::Relocation<decltype(thunk)> func;
	};

	/** @brief Hook that overrides directional shadow light culling to include rooms visible from the sun direction. */
	struct DirShadowLightCulling
	{
		static void thunk(RE::BSShadowDirectionalLight* dirLight, RE::BSTArray<RE::BSTArray<RE::NiPointer<RE::NiAVObject>>>& jobArrays, RE::BSTArray<RE::NiPointer<RE::NiAVObject>>& nodes);
		static inline REL::Relocation<decltype(thunk)> func;
	};

	/** @brief Hook that updates the raster cull mode before each render pass to support double-sided rendering. */
	struct BSBatchRenderer_RenderPassImmediately
	{
		static void thunk(RE::BSRenderPass* a_pass, uint32_t a_technique, bool a_alphaTest, uint32_t a_renderFlags);
		static inline REL::Relocation<decltype(thunk)> func;
	};

	/**
	 * @brief Conditionally disables backface culling for shadow rendering in interior sun cells.
	 * @param pass The current render pass being processed.
	 * @param technique The shader technique flags for the pass.
	 */
	void UpdateRasterStateCullMode(const RE::BSRenderPass* pass, const uint32_t technique) const
	{
		if (isInteriorWithSun && settings.ForceDoubleSidedRendering && technique & static_cast<uint32_t>(SIE::ShaderCache::UtilityShaderFlags::RenderShadowmap)) {
			const auto flags = pass->shaderProperty->flags;
			const auto renderTwoSided = flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kTwoSided) || flags.none(RE::BSShaderProperty::EShaderPropertyFlag::kAssumeShadowmask, RE::BSShaderProperty::EShaderPropertyFlag::kSkinned);
			if (renderTwoSided && *rasterStateCullMode != 0) {
				*rasterStateCullMode = 0;
				globals::game::stateUpdateFlags->set(RE::BSGraphics::DIRTY_RASTER_CULL_MODE);
			} else if (!renderTwoSided && *rasterStateCullMode != RE::BSGraphics::RasterStateCullMode::RASTER_STATE_CULL_MODE_BACK) {
				*rasterStateCullMode = RE::BSGraphics::RasterStateCullMode::RASTER_STATE_CULL_MODE_BACK;
				globals::game::stateUpdateFlags->set(RE::BSGraphics::DIRTY_RASTER_CULL_MODE);
			}
		}
	}

	/** @brief Event handler that resets interior sun state when the main menu opens. */
	class MenuOpenCloseEventHandler : public RE::BSTEventSink<RE::MenuOpenCloseEvent>
	{
	public:
		/** @brief Processes menu open/close events to reset interior sun state on main menu. */
		virtual RE::BSEventNotifyControl ProcessEvent(const RE::MenuOpenCloseEvent* a_event, RE::BSTEventSource<RE::MenuOpenCloseEvent>*);

		/** @brief Registers the singleton event handler with the UI event source. */
		static bool Register()
		{
			static MenuOpenCloseEventHandler singleton;
			auto ui = globals::game::ui;
			if (!ui) {
				logger::error("UI event source not found");
				return false;
			}
			ui->GetEventSource<RE::MenuOpenCloseEvent>()->AddEventSink(&singleton);

			logger::info("Registered {}", typeid(singleton).name());
			return true;
		}
	};

	/**
	 * @brief Checks whether a cell is an interior with sunlight shadow flags enabled.
	 * @param cell The cell to check.
	 * @return True if the cell is an interior configured for sun and sky lighting with sunlight shadows.
	 */
	static bool IsInteriorWithSun(const RE::TESObjectCELL* cell);
	/** @brief Returns whether this feature is loaded and the current cell has interior sun enabled. */
	bool IsActiveInteriorSun() const { return loaded && isInteriorWithSun.load(); }
	virtual bool IsCore() const override { return true; };

private:
	enum class CellFlagExt : uint16_t
	{
		kSunlightShadows = 1 << 15,
	};

	float* gShadowDistance = nullptr;
	float* gInteriorShadowDistance = nullptr;
	uint32_t* rasterStateCullMode = nullptr;

	RE::TESObjectCELL* currentCell = nullptr;

	bool arraysCleared = true;
	RE::BSTArray<RE::NiPointer<RE::NiAVObject>> currentCellRoomsAndPortals = {};
	RE::BSTArray<RE::BSTArray<RE::NiPointer<RE::NiAVObject>>> replacementJobArrays = {};
	eastl::hash_set<RE::NiAVObject*> addedSet = {};

	static RE::TESWorldSpace* enableInteriorSun;
	static RE::TESWorldSpace* disableInteriorSun;

	void ClearArrays();

	void InitialiseOnNewCell(const RE::NiPointer<RE::BSPortalGraph>& portalGraph);

	bool IsInSunDirectionAndWithinShadowDistance(const RE::NiPointer<RE::NiAVObject>& object, const RE::NiPoint3& lightDir, const RE::NiPoint3& playerPos) const;

	void PopulateReplacementJobArrays(RE::TESObjectCELL* cell, const RE::NiPointer<RE::BSPortalGraph>& portalGraph, const RE::BSShadowDirectionalLight* dirLight, RE::BSTArray<RE::BSTArray<RE::NiPointer<RE::NiAVObject>>>& jobArrays);

	static void SetShadowDistance(bool inInterior);
};
