/*
	OmniaFramework - A collection of useful functionality
	Copyright (C) 2026  OmniaX-Dev

	This file is part of OmniaFramework.

	OmniaFramework is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	OmniaFramework is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with OmniaFramework.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "Widget.hpp"
#include "gui/Events.hpp"
#include "io/Memory.hpp"
#include "../..//render/BasicRenderer.hpp"
#include "../Window.hpp"

namespace ogfx
{
	namespace gui
	{
		ostd::BaseObject* Widget::s_dragAndDropData { nullptr };
		bool Widget::s_hasDragAndDropData { false };

		Widget::Widget(const Rectangle& bounds, Window& window) : Rectangle(bounds), m_widgets(window, *this)
		{
			m_window = &window;
		}

		Widget::~Widget(void)
		{
			if (m_window && m_tabIndex >= 0)
				m_window->getFocusManager().unregisterTabIndex(*this);
			auto& fm = m_window->getFocusManager();
			if (fm.getFocused() == this)
				fm.clearFocus();  // you'd add this method
		}

		bool Widget::addWidget(Widget& child, const Vec2& position, bool __skip_callback)
		{
			if (!m_allowChildren)
				return false;
			if (position.x != 0 || position.y != 0)
				child.setPosition(position);
			child.reloadTheme();
			bool result = m_widgets.addWidget(child);
			if (!result) return false;
			if (!__skip_callback)
				onWidgetAdded(child);
			if (m_layout && !__skip_callback)
				relayout();
			return result;
		}

		bool Widget::removeWidget(Widget& child)
		{
			bool result = m_widgets.removeWidget(child);
			if (result && m_layout)
				relayout();
			return result;
		}

		Vec2 Widget::getGlobalPosition(void) const
		{
			Vec2 glob = getPosition();
			if (!m_rootChild && m_parent != nullptr)
			{
				glob += m_parent->getGlobalPosition();
				if (!isIgnoreScrollEnabled())
				{
					glob += m_parent->getPadding().getPosition();
					glob += m_parent->getContentOffset();
					glob += m_parent->getScrollOffset();
				}
			}
			glob += m_margin.getPosition();
			return glob;
		}

		Vec2 Widget::getGlobalContentPosition(void) const
		{
			return getGlobalPosition() + getContentBounds().getPosition();
		}

		Rectangle Widget::getGlobalBounds(void) const
		{
			return { getGlobalPosition(), getSize() + (m_margin.getSize() * 2) };
		}

		Rectangle Widget::getContentBounds(void) const
		{
			auto pad = getPadding();
			return { Vec2 { m_contentOffset.x, m_contentOffset.y } + pad.getPosition(), (getSize() - m_contentOffset - (pad.getSize() * 2)) };
		}

		Rectangle Widget::getPureContentBounds(void) const
		{
			return { { m_contentOffset.x, m_contentOffset.y }, (getSize() - m_contentOffset) };
		}

		Rectangle Widget::getGlobalContentBounds(void) const
		{
			return { getGlobalContentPosition(), getContentBounds().getSize() };
		}

		Rectangle Widget::getGlobalPureContentBounds(void) const
		{
			return { getGlobalPosition() + getContentOffset(), getPureContentBounds().getSize() };
		}

		Rectangle Widget::getContentExtents(void) const
		{
			// If a layout owns this widget, prefer its measure() — it knows
			// the real desired extent (e.g. a vertical box with 50 items
			// reports 50*itemHeight even if only 5 are visible).
			if (m_layout)
			{
				Vec2 desired = m_layout->measure(*this);
				return { 0, 0, desired.x, desired.y };
			}
			f32 maxX = 0, maxY = 0;
			for (auto* child : m_widgets.getWidgets())
			{
				if (!child || child->isInvalid()) continue;
				if (child->isIgnoreScrollEnabled()) continue;
				if (!child->isVisible()) continue;
				Vec2 localPos = getPadding().getPosition() + child->getPosition() + child->getMargin().getPosition() + child->getContentBounds().getPosition();
				maxX = std::max(maxX, localPos.x + child->getw() + child->getMargin().w);
				maxY = std::max(maxY, localPos.y + child->geth() + child->getMargin().h);
			}
			return { 0, 0, maxX, maxY };
		}

		bool Widget::contains(Vec2 p, bool includeBounds) const
		{
			return Rectangle(getGlobalPosition(), getSize()).contains(p, includeBounds);
		}

		void Widget::enable(bool enable)
		{
			m_enabled = enable;
			setThemeQualifier("disabled", !enable);
		}

		void Widget::addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value)
		{
			m_themeOverrides.push_back({ fullKey, value });
		}

		void Widget::reloadTheme(bool propagate)
		{
			if (getWindow().theme() == nullptr)
				return;
			if (!isThemingEnabled())
				return;

			auto& theme = *getWindow().theme();
			applyTheme(theme);
			ostd::Stylesheet& const_cast_theme = const_cast<ostd::Stylesheet&>(theme);

			struct tBackup {
				const ostd::Stylesheet::TypeVariant* ptr;
				ostd::Stylesheet::TypeVariant val;
				String key;
			};

			stdvec<tBackup> backup;
			for (auto& rule : m_themeOverrides)
			{
				auto currentValuePtr = theme.getFull(rule.fullKey);
				ostd::Stylesheet::TypeVariant currentValue;
				if (currentValuePtr)
					currentValue = *currentValuePtr;
				backup.push_back({ currentValuePtr, currentValue, rule.fullKey });
				const_cast_theme.setFull(rule.fullKey, rule.value);
			}
			__applyTheme(const_cast_theme, propagate);
			for (auto&[ptr, val, key] : backup)
			{
				if (ptr == nullptr)
					const_cast_theme.removeRule(key);
				else
					const_cast_theme.setFull(key, val);
			}
		}

		void Widget::setThemeQualifier(const String& qualifier, bool value)
		{
			for (auto& [name, state] : m_qualifierList)
			{
				if (name == qualifier)
				{
					if (state == value) return;
					state = value;
					reloadTheme();
					return;
				}
			}
		}

		bool Widget::getThemeQualifier(const String& qualifier) const
		{
			for (auto& [name, state] : m_qualifierList)
			{
				if (name == qualifier)
					return state;
			}
			return false;
		}

		bool Widget::addThemeID(const String& id)
		{
			if (STDVEC_CONTAINS(m_themeIDList, id))
				return false;
			m_themeIDList.push_back(id);
			return true;
		}

		bool Widget::removeThemeID(const String& id)
		{
			if (!STDVEC_CONTAINS(m_themeIDList, id))
				return false;
			STDVEC_REMOVE(m_themeIDList, id);
			return true;
		}

		void Widget::setVisible(bool v)
		{
			m_visible = v;
		}

		bool Widget::setTabIndex(i32 index)
		{
			if (getWindow().getFocusManager().registerTabIndex(*this, index))
			{
				m_tabIndex = index;
				return true;
			}
			return false;
		}

		void Widget::setLayout(std::unique_ptr<Layout> layout)
		{
			m_layout = std::move(layout);
			if (m_layout)
				relayout();
		}

		void Widget::relayout(void)
		{
			if (!m_layout) return;
			m_layout->arrange(*this);

			// After our children's bounds changed, give *their* layouts a chance
			// to re-run (a horizontal layout containing a vertical layout, etc.).
			for (auto* c : m_widgets.getWidgets())
			{
				if (!c || c->isInvalid()) continue;
				if (c->hasLayout())
					c->relayout();
			}
		}

		void Widget::setCallback(eCallback type, EventCallback callback)
		{
			switch (type)
			{
				case eCallback::MousePressed:    callback_onMousePressed    = std::move(callback); break;
				case eCallback::MouseReleased:   callback_onMouseReleased   = std::move(callback); break;
				case eCallback::MouseMoved:      callback_onMouseMoved      = std::move(callback); break;
				case eCallback::MouseScrolled:   callback_onMouseScrolled   = std::move(callback); break;
				case eCallback::DragAndDrop:     callback_onDragAndDrop     = std::move(callback); break;
				case eCallback::MouseEntered:    callback_onMouseEntered    = std::move(callback); break;
				case eCallback::MouseExited:     callback_onMouseExited     = std::move(callback); break;
				case eCallback::MouseDragged:    callback_onMouseDragged    = std::move(callback); break;
				case eCallback::KeyPressed:      callback_onKeyPressed      = std::move(callback); break;
				case eCallback::KeyReleased:     callback_onKeyReleased     = std::move(callback); break;
				case eCallback::TextEntered:     callback_onTextEntered     = std::move(callback); break;
				case eCallback::WindowClosed:    callback_onWindowClosed    = std::move(callback); break;
				case eCallback::WindowResized:   callback_onWindowResized   = std::move(callback); break;
				case eCallback::WindowFocused:   callback_onWindowFocused   = std::move(callback); break;
				case eCallback::WindowFocusLost: callback_onWindowFocusLost = std::move(callback); break;
				case eCallback::ActionPerformed: callback_onActionPerformed = std::move(callback); break;
				default: break;
			}
		}

		void Widget::__draw(ogfx::BasicRenderer2D& gfx)
		{
			if (!isVisible())
				return;
			beforeDraw(gfx);
			if (m_useBackgroundGradient)
				gfx.fillGradientRect({ getGlobalPosition(), getSize() }, m_backgroundGradient);
			else if (m_showBackground)
				gfx.fillRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderRadius);
			onDraw(gfx);

			// gfx.fillRect(getGlobalPureContentBounds(), { 0, 255, 0, 120 });
			const bool needsContentClip = !m_rootChild;
			if (needsContentClip)
				gfx.pushClippingRect(getGlobalPureContentBounds(), true);
			if (hasChildren())
				m_widgets.draw(gfx);
			if (needsContentClip)
				gfx.popClippingRect();
			if (m_showBorder)
				gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth);
			afterDraw(gfx);
			if (isFocused() && isFocusBorderEnabled())
				gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_focusBorderColor, m_borderRadius, m_focusBorderWidth);
		}

		void Widget::__update(void)
		{
			onUpdate();
			if (hasChildren())
				m_widgets.update();
		}

		void Widget::__onMousePressed(const Event& event)
		{
			if (event.isHandled() || !isMousePressedEventEnabled()) return;
			setThemeQualifier("pressed");
			if (hasChildren())
				m_widgets.onMousePressed(event);
			if (!event.isHandled())
			{
				if (callback_onMousePressed)
					callback_onMousePressed(event);
				onMousePressed(event);
				m_pressedButton = event.mouse->button;
			}
		}

		void Widget::__onMouseReleased(const Event& event)
		{
			if (event.isHandled() || !isMouseReleasedEventEnabled()) return;
			setThemeQualifier("pressed", false);
			m_pressedButton = ogfx::MouseEventData::eButton::None;
			if (hasChildren())
				m_widgets.onMouseReleased(event);
			if (!event.isHandled())
			{
				if (callback_onMouseReleased)
					callback_onMouseReleased(event);
				onMouseReleased(event);
			}
		}

		void Widget::__onDragAndDrop(const Event& event)
		{
			if (hasChildren())
				m_widgets.onMouseReleased(event);
			if (!event.isHandled())
			{
				if (callback_onDragAndDrop)
					callback_onDragAndDrop(event);
				onDragAndDrop(event);
			}
		}

		void Widget::__onMouseMoved(const Event& event)
		{
			if (event.isHandled() || !isMouseMovedEventEnabled()) return;
			if (hasChildren())
				m_widgets.onMouseMoved(event);
			if (!event.isHandled())
			{
				if (callback_onMouseMoved)
					callback_onMouseMoved(event);
				onMouseMoved(event);
			}
			if (isTooltipEnabled() && isMouseInside())
				getWindow().restartTooltipTimer();
		}

		void Widget::__onMouseScrolled(const Event& event)
		{
			if (event.isHandled() || !isMouseScrolledEventEnabled()) return;
			if (hasChildren())
				m_widgets.onMouseScrolled(event);
			if (!event.isHandled())
			{
				if (callback_onMouseScrolled)
					callback_onMouseScrolled(event);
				onMouseScrolled(event);
			}
		}

		void Widget::__onMouseEntered(const Event& event)
		{
			if (event.isHandled() || !isMouseEnteredEventEnabled()) return;
			setThemeQualifier("hover");
			if (callback_onMouseEntered)
				callback_onMouseEntered(event);
			onMouseEntered(event);
			if (isTooltipEnabled())
				getWindow().startTooltipTimer(getTooltipText());
		}

		void Widget::__onMouseExited(const Event& event)
		{
			if (event.isHandled() || !isMouseExitedEventEnabled()) return;
			setThemeQualifier("hover", false);
			if (callback_onMouseExited)
				callback_onMouseExited(event);
			onMouseExited(event);
			if (isTooltipEnabled())
				getWindow().stopTooltipTimer();
		}

		void Widget::__onMouseDragged(const Event& event)
		{
			if (event.isHandled() || !isMouseDraggedEventEnabled()) return;
			if (hasChildren())
				m_widgets.onMouseDragged(event);
			if (!event.isHandled())
			{
				if (callback_onMouseDragged)
					callback_onMouseDragged(event);
				onMouseDragged(event);
			}
			if (isTooltipEnabled() && isMouseInside())
				getWindow().restartTooltipTimer();
		}

		void Widget::__onKeyPressed(const Event& event)
		{
			if (event.isHandled() || !isKeyPressedEventEnabled()) return;
			if (callback_onKeyPressed)
				callback_onKeyPressed(event);
			onKeyPressed(event);
		}

		void Widget::__onKeyReleased(const Event& event)
		{
			if (event.isHandled() || !isKeyReleasedEventEnabled()) return;
			m_pressedButton = ogfx::MouseEventData::eButton::None;
			if (callback_onKeyReleased)
				callback_onKeyReleased(event);
			onKeyReleased(event);
		}

		void Widget::__onTextEntered(const Event& event)
		{
			if (event.isHandled() || !isTextEnteredEventEnabled()) return;
			if (callback_onTextEntered)
				callback_onTextEntered(event);
			onTextEntered(event);
		}

		void Widget::__onWindowClosed(const Event& event)
		{
			if (hasChildren())
				m_widgets.onWindowClosed(event);
			if (!event.isHandled())
			{
				if (callback_onWindowClosed)
					callback_onWindowClosed(event);
				onWindowClosed(event);
			}
		}

		void Widget::__onWindowResized(const Event& event)
		{
			if (hasChildren())
				m_widgets.onWindowResized(event);
			if (!event.isHandled())
			{
				if (callback_onWindowResized)
					callback_onWindowResized(event);
				onWindowResized(event);
			}
			if (m_layout)
				relayout();
		}

		void Widget::__onWindowFocused(const Event& event)
		{
			if (hasChildren())
				m_widgets.onWindowFocused(event);
			if (!event.isHandled())
			{
				if (callback_onWindowFocused)
					callback_onWindowFocused(event);
				onWindowFocused(event);
			}
		}

		void Widget::__onWindowFocusLost(const Event& event)
		{
			if (hasChildren())
				m_widgets.onWindowFocusLost(event);
			if (!event.isHandled())
			{
				if (callback_onWindowFocusLost)
					callback_onWindowFocusLost(event);
				onWindowFocusLost(event);
			}
		}

		void Widget::__applyTheme(const ostd::Stylesheet& theme, bool propagate)
		{
			if (propagate && hasChildren())
				m_widgets.onThemeApplied(theme);
			apply_common_theme_values(theme);
			applyTheme(theme);
		}

		void Widget::apply_common_theme_values(const ostd::Stylesheet& theme)
		{
			setTextColor(getThemeValue<Color>(theme, "textColor", m_textColor));
			setBackgroundColor(getThemeValue<Color>(theme, "backgroundColor", m_backgroundColor));
			setFontSize(getThemeValue<i32>(theme, "fontSize", m_fontSize));
			setBorderRadius(getThemeValue<i32>(theme, "borderRadius", m_borderRadius));
			setBorderWidth(getThemeValue<i32>(theme, "borderWidth", m_borderWidth));
			enableBorder(getThemeValue<bool>(theme, "showBorder", m_showBorder));
			setBorderColor(getThemeValue<Color>(theme, "borderColor", m_borderColor));
			enableFocusBorder(getThemeValue<bool>(theme, "showFocusBorder", m_showFocusBorder));
			setFocusBorderColor(getThemeValue<Color>(theme, "focusBorderColor", m_focusBorderColor));
			setFocusBorderWidth(getThemeValue<i32>(theme, "focusBorderWidth", m_focusBorderWidth));
			enableBackground(getThemeValue<bool>(theme, "showBackground", m_showBackground));
			setPadding(getThemeValue<Rectangle>(theme, "padding", m_padding));
			setMargin(getThemeValue<Rectangle>(theme, "margin", m_margin));
			enableBackgroundGradient(getThemeValue<bool>(theme, "useBackgroundGradient", m_useBackgroundGradient));
			setBackgroundGradient(getThemeValue<ColorGradient>(theme, "backgroundGradient", m_backgroundGradient));
			if (isBackgroundGradientEnabled() && isBackgroundEnabled())
				enableBackground(false);
		}
	}
}
