#include "DialogueSystem.hpp"
#include "../EditorApp.hpp"
#include <OgreFontManager.h>
#include <OgreImGuiOverlay.h>
#include <OgreLogManager.h>
#include <OgreOverlayManager.h>
#include <nlohmann/json.hpp>
#include <fstream>

// ---------------------------------------------------------------------------
// Settings JSON
// ---------------------------------------------------------------------------

bool DialogueSystem::Settings::loadFromJson(const std::string &path)
{
	std::ifstream f(path);
	if (!f.is_open())
		return false;

	nlohmann::json j;
	try {
		f >> j;
	} catch (...) {
		return false;
	}

	fontName = j.value("fontName", fontName);
	fontSize = j.value("fontSize", fontSize);
	speakerFontSize = j.value("speakerFontSize", speakerFontSize);
	backgroundOpacity = j.value("backgroundOpacity", backgroundOpacity);
	boxHeightFraction = j.value("boxHeightFraction", boxHeightFraction);
	boxPositionFraction = j.value("boxPositionFraction", boxPositionFraction);
	return true;
}

bool DialogueSystem::Settings::saveToJson(const std::string &path) const
{
	nlohmann::json j;
	j["fontName"] = fontName;
	j["fontSize"] = fontSize;
	j["speakerFontSize"] = speakerFontSize;
	j["backgroundOpacity"] = backgroundOpacity;
	j["boxHeightFraction"] = boxHeightFraction;
	j["boxPositionFraction"] = boxPositionFraction;

	std::ofstream f(path);
	if (!f.is_open())
		return false;

	try {
		f << j.dump(4);
	} catch (...) {
		return false;
	}
	return true;
}

// ---------------------------------------------------------------------------
// Singleton
// ---------------------------------------------------------------------------

DialogueSystem::DialogueSystem()
{
	// Subscribe to dialogue events
	m_showListenerId = EventBus::getInstance().subscribe(
		"dialogue_show",
		[this](const Ogre::String &,
		       const editScene::EventParams &params) {
			Ogre::String text = params.getString("text");
			if (text.empty())
				return;

			std::vector<Ogre::String> choices;
			const editScene::EventValue *choicesVal =
				params.get("choices");
			if (choicesVal &&
			    choicesVal->getType() ==
				    editScene::EventValue::STRING_ARRAY) {
				const auto &arr = choicesVal->getStringArray();
				choices.reserve(arr.size());
				for (const auto &s : arr)
					choices.push_back(s);
			}

			Ogre::String speaker = params.getString("speaker");
			this->show(text, choices, speaker);
		});

	m_hideListenerId = EventBus::getInstance().subscribe(
		"dialogue_hide",
		[this](const Ogre::String &, const editScene::EventParams &) {
			this->hide();
		});
}

DialogueSystem::~DialogueSystem()
{
	EventBus::getInstance().unsubscribe(m_showListenerId);
	EventBus::getInstance().unsubscribe(m_hideListenerId);
}

DialogueSystem &DialogueSystem::getInstance()
{
	static DialogueSystem instance;
	return instance;
}

void DialogueSystem::init(EditorApp *editorApp)
{
	m_editorApp = editorApp;
}

// ---------------------------------------------------------------------------
// Settings helpers
// ---------------------------------------------------------------------------

bool DialogueSystem::loadSettings(const std::string &path)
{
	if (!m_settings.loadFromJson(path)) {
		Ogre::LogManager::getSingleton().logMessage(
			"DialogueSystem: Could not load " + path +
			", using defaults.");
		return false;
	}
	m_fontLoaded = false;
	return true;
}

bool DialogueSystem::saveSettings(const std::string &path) const
{
	return m_settings.saveToJson(path);
}

// ---------------------------------------------------------------------------
// Runtime API
// ---------------------------------------------------------------------------

void DialogueSystem::show(const Ogre::String &text,
			  const std::vector<Ogre::String> &choices,
			  const Ogre::String &speaker)
{
	m_text = text;
	m_choices = choices;
	m_speaker = speaker;
	m_state = choices.empty() ? State::Showing : State::AwaitingChoice;
	if (onShow)
		onShow();
}

void DialogueSystem::hide()
{
	m_state = State::Idle;
	m_text.clear();
	m_choices.clear();
	m_speaker.clear();
}

void DialogueSystem::progress()
{
	if (m_state == State::Showing && m_choices.empty()) {
		m_state = State::Idle;
		if (onDismissed)
			onDismissed();
	}
}

void DialogueSystem::selectChoice(int index)
{
	if (m_state == State::AwaitingChoice && index >= 1 &&
	    index <= (int)m_choices.size()) {
		m_state = State::Idle;
		if (onChoiceSelected)
			onChoiceSelected(index);
	}
}

bool DialogueSystem::isActive() const
{
	return m_state != State::Idle;
}

// ---------------------------------------------------------------------------
// Editor preview
// ---------------------------------------------------------------------------

void DialogueSystem::setEditorPreviewEnabled(bool enabled)
{
	m_editorPreviewEnabled = enabled;
}

bool DialogueSystem::isEditorPreviewEnabled() const
{
	return m_editorPreviewEnabled;
}

// ---------------------------------------------------------------------------
// Font
// ---------------------------------------------------------------------------

void DialogueSystem::ensureFontLoaded(const Ogre::String &fontName,
				      float fontSize, float speakerFontSize)
{
	if (m_fontLoaded && m_currentFontName == fontName &&
	    m_currentFontSize == fontSize &&
	    m_currentSpeakerFontSize == speakerFontSize)
		return;

	if (!m_editorApp)
		return;

	Ogre::ImGuiOverlay *overlay = m_editorApp->getImGuiOverlay();
	if (!overlay)
		return;

	// Main dialogue font
	try {
		if (Ogre::FontManager::getSingleton().resourceExists(
			    "DialogueFont", "General")) {
			Ogre::FontManager::getSingleton().remove("DialogueFont",
								 "General");
		}
		Ogre::FontPtr font = Ogre::FontManager::getSingleton().create(
			"DialogueFont", "General");
		font->setType(Ogre::FontType::FT_TRUETYPE);
		font->setSource(fontName);
		font->setTrueTypeSize(fontSize);
		font->setTrueTypeResolution(75);
		font->addCodePointRange(
			Ogre::Font::CodePointRange(32, 255));
		font->load();
		m_dialogueFont = overlay->addFont("DialogueFont", "General");
	} catch (...) {
		Ogre::LogManager::getSingleton().logMessage(
			"DialogueSystem: Failed to load font " + fontName);
		m_dialogueFont = nullptr;
		m_speakerFont = nullptr;
		m_fontLoaded = false;
		return;
	}

	// Speaker font
	try {
		if (Ogre::FontManager::getSingleton().resourceExists(
			    "DialogueSpeakerFont", "General")) {
			Ogre::FontManager::getSingleton().remove(
				"DialogueSpeakerFont", "General");
		}
		Ogre::FontPtr font = Ogre::FontManager::getSingleton().create(
			"DialogueSpeakerFont", "General");
		font->setType(Ogre::FontType::FT_TRUETYPE);
		font->setSource(fontName);
		font->setTrueTypeSize(speakerFontSize);
		font->setTrueTypeResolution(75);
		font->addCodePointRange(
			Ogre::Font::CodePointRange(32, 255));
		font->load();
		m_speakerFont = overlay->addFont("DialogueSpeakerFont",
						 "General");
	} catch (...) {
		Ogre::LogManager::getSingleton().logMessage(
			"DialogueSystem: Failed to load speaker font " +
			fontName);
		m_speakerFont = nullptr;
	}

	m_currentFontName = fontName;
	m_currentFontSize = fontSize;
	m_currentSpeakerFontSize = speakerFontSize;
	m_fontLoaded = true;
}

void DialogueSystem::prepareFont()
{
	ensureFontLoaded(m_settings.fontName, m_settings.fontSize,
			 m_settings.speakerFontSize);
}

// ---------------------------------------------------------------------------
// Rendering
// ---------------------------------------------------------------------------

void DialogueSystem::update(float deltaTime)
{
	(void)deltaTime;

	bool shouldRender = false;
	if (m_editorApp) {
		bool inGame =
			m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
			m_editorApp->getGamePlayState() ==
				EditorApp::GamePlayState::Playing;
		shouldRender = inGame || m_editorPreviewEnabled;
	} else {
		shouldRender = m_editorPreviewEnabled;
	}

	if (!shouldRender || !isActive())
		return;

	renderDialogueBox();
}

void DialogueSystem::renderDialogueBox()
{
	ImVec2 size = ImGui::GetMainViewport()->Size;

	float boxHeight = size.y * m_settings.boxHeightFraction;
	float boxY = size.y * m_settings.boxPositionFraction;

	ImGui::SetNextWindowPos(ImVec2(0, boxY), ImGuiCond_Always);
	ImGui::SetNextWindowSize(ImVec2(size.x, boxHeight),
				ImGuiCond_Always);

	ImVec4 bgColor = ImVec4(0.0f, 0.0f, 0.0f,
				m_settings.backgroundOpacity);
	ImGui::PushStyleColor(ImGuiCol_WindowBg, bgColor);

	ImGui::Begin("DialogueBox", nullptr,
		     ImGuiWindowFlags_NoTitleBar |
			     ImGuiWindowFlags_NoDecoration |
			     ImGuiWindowFlags_NoResize |
			     ImGuiWindowFlags_NoMove |
			     ImGuiWindowFlags_NoCollapse |
			     ImGuiWindowFlags_NoFocusOnAppearing);

	ImVec2 p = ImGui::GetCursorScreenPos();

	// Speaker name
	if (!m_speaker.empty()) {
		if (m_speakerFont)
			ImGui::PushFont(m_speakerFont);
		ImGui::TextColored(ImVec4(0.8f, 0.8f, 1.0f, 1.0f), "%s",
				   m_speaker.c_str());
		if (m_speakerFont)
			ImGui::PopFont();
		ImGui::Spacing();
	}

	// Narration text
	if (m_dialogueFont)
		ImGui::PushFont(m_dialogueFont);

	ImGui::TextWrapped("%s", m_text.c_str());

	if (m_dialogueFont)
		ImGui::PopFont();

	ImGui::Spacing();

	// Choices or click-to-progress
	if (m_choices.empty()) {
		ImGui::SetCursorScreenPos(p);
		if (ImGui::InvisibleButton("DialogueProgress",
					   ImGui::GetWindowSize())) {
			progress();
		}
	} else {
		for (int i = 0; i < (int)m_choices.size(); i++) {
			if (ImGui::Button(m_choices[i].c_str())) {
				selectChoice(i + 1);
			}
		}
	}

	ImGui::End();
	ImGui::PopStyleColor();
}
