#pragma once

#include <psemek/ui/shape.hpp>
#include <psemek/ui/event.hpp>
#include <psemek/ui/painter.hpp>
#include <psemek/ui/style.hpp>

#include <psemek/async/event_loop.hpp>

#include <psemek/util/span.hpp>
#include <psemek/util/function.hpp>

#include <memory>
#include <optional>
#include <string>
#include <any>

namespace psemek::ui
{

	struct element
		: std::enable_shared_from_this<element>
	{
		virtual element * parent() const { return parent_; }
		virtual void set_parent(element * parent);

		using children_range = util::span<element * const>;

		virtual children_range children() const { return {}; }

		virtual element * root();
		virtual element const * root() const;
		virtual async::event_loop * loop() const;
		virtual void set_loop(async::event_loop * loop);

		virtual math::box<float, 2> events_bbox() const;

		virtual bool on_event(mouse_move  const &) { return false; }
		virtual bool on_event(mouse_click const &) { return false; }
		virtual bool on_event(mouse_wheel const &) { return false; }
		virtual bool on_event(key_press   const &) { return false; }
		virtual bool on_event(text_input  const &) { return false; }

		virtual bool focused() const { return false; }

		virtual struct shape const & shape() const = 0;
		virtual void reshape(math::box<float, 2> const & bbox) = 0;
		virtual void reshape();

		// Constraints on the element's width & height, assuming unlimited available space
		virtual math::box<float, 2> size_constraints() const;

		// Constraints on the element's width, assuming fixed height
		virtual math::interval<float> width_constraints(float height) const;

		// Constraints on the element's height, assuming fixed width
		virtual math::interval<float> height_constraints(float width) const;

		virtual bool enabled() const { return enabled_; }
		virtual void set_enabled(bool value) { enabled_ = value; }
		virtual void enable() { set_enabled(true); }
		virtual void disable() { set_enabled(false); }

		virtual bool hidden() const { return hidden_; }
		virtual void set_hidden(bool value) { hidden_ = value; }
		virtual void hide() { set_hidden(true); }
		virtual void show() { set_hidden(false); }

		virtual bool transparent() const { return false; }

		virtual std::shared_ptr<struct style> style() const { return style_; }
		virtual std::shared_ptr<struct style> own_style() const { return own_style_; }
		virtual std::shared_ptr<struct style> set_style(std::shared_ptr<struct style> st);
		virtual std::shared_ptr<struct style> set_own_style(std::shared_ptr<struct style> st);

		virtual void style_updated() const;
		virtual void own_style_updated() const;

		virtual std::optional<std::string> const & id() const { return id_; }
		virtual std::optional<std::string> set_id(std::optional<std::string> id);

		virtual std::optional<std::string> const & hint() const { return hint_; }
		virtual std::optional<std::string> set_hint(std::optional<std::string> hint);

		virtual std::any const & data() const { return data_; }
		virtual std::any & data() { return data_; }

		virtual void update(float /* dt */) {}

		virtual void draw(painter & p) const = 0;
		virtual void post_draw(painter & /* p */) const {}

		virtual ~element();

		virtual void post(util::function<void()> f) const;
		virtual void post_reshape();

	protected:
		virtual void release_children();

		virtual std::shared_ptr<struct style> merged_style() const;
		virtual std::shared_ptr<struct style> merged_own_style() const;

		static bool in_text_input();
		static void start_text_input();
		static void stop_text_input();

	private:
		element * parent_ = nullptr;
		async::event_loop * loop_ = nullptr;
		std::shared_ptr<struct style> style_;
		std::shared_ptr<struct style> own_style_;
		mutable std::shared_ptr<struct style> merged_style_;
		mutable std::shared_ptr<struct style> merged_own_style_;

		bool reshape_posted_ = false;
		bool enabled_ = true;
		bool hidden_ = false;

		mutable std::vector<util::function<void()>> delayed_callbacks_;

		std::optional<std::string> id_;
		std::optional<std::string> hint_;

		std::any data_;

		void post_delayed_callbacks() const;
	};

	template <typename Type>
	Type * find_first_parent_of_type(element * e)
	{
		while (true)
		{
			if (!e)
				return nullptr;

			if (auto p = dynamic_cast<Type *>(e))
				return p;

			e = e->parent();
		}
	}

	template <typename Type>
	Type * find_last_parent_of_type(element * e)
	{
		Type * r = nullptr;
		while (true)
		{
			if (auto p = find_first_parent_of_type<Type>(e))
			{
				r = p;
				e = p->parent();
			}
			else
				return r;
		}
	}

	element * find_element_by_id(element * root, std::string_view id);

	void send_fake_mouse_move_event(element * e, bool mouseover);

}
