#pragma once

#include <psemek/ui/element.hpp>
#include <psemek/ui/box_shape.hpp>
#include <psemek/ui/tagged_text.hpp>
#include <psemek/ui/image_provider.hpp>

#include <string>
#include <string_view>
#include <variant>

namespace psemek::ui
{

	struct label
		: element
	{
		enum class halignment
		{
			left,
			center,
			right,
			stretch,
		};

		enum class valignment
		{
			top,
			center,
			bottom,
		};

		enum class overflow_mode
		{
			ignore,
			drop,
			ellipsis,
		};

		label() = default;
		explicit label(std::string text);

		virtual void set_text(std::string text);
		virtual void set_tagged_text(std::string text);
		virtual std::string_view text() const { return text_; }

		virtual void set_text_style(ui::text_style style);
		virtual ui::text_style text_style() const { return text_style_; }

		virtual void set_halign(halignment value);
		virtual halignment halign() const { return halign_; }

		virtual void set_valign(valignment value);
		virtual valignment valign() const { return valign_; }

		virtual void set_wrap(bool value);
		virtual bool wrap() const { return wrap_; }

		virtual void set_overflow(overflow_mode value);
		virtual overflow_mode overflow() const { return overflow_; }

		virtual void set_skip_spaces(bool value);
		virtual bool skip_spaces() const { return skip_spaces_; }

		virtual struct image_provider * set_image_provider(struct image_provider * provider);
		virtual struct image_provider * image_provider() const { return image_provider_; }

		virtual void set_tag_mapper(tagged_text::mapper mapper);

		using link_mouseover_callback = std::function<void(std::optional<std::string> const &)>;
		using link_callback = std::function<void(std::string const &)>;

		virtual void on_link_mouseover(link_mouseover_callback callback);
		virtual void on_link_click(link_callback callback);

		struct shape const & shape() const override { return shape_; }
		void reshape(math::box<float, 2> const & bbox) override;

		bool on_event(ui::mouse_move const & e) override;
		bool on_event(ui::mouse_click const & e) override;

		math::box<float, 2> size_constraints() const override;

		math::interval<float> width_constraints(float height) const override;
		math::interval<float> height_constraints(float width) const override;

		void style_updated() const override;
		void own_style_updated() const override;

		bool transparent() const override { return !hint(); }

		void draw(painter & p) const override;

	protected:
		virtual void on_state_changed();

	private:
		halignment halign_ = halignment::left;
		valignment valign_ = valignment::top;
		bool wrap_ = true;
		overflow_mode overflow_ = overflow_mode::ignore;
		bool skip_spaces_ = false;
		ui::text_style text_style_ = {};

		struct image_provider * image_provider_ = nullptr;
		tagged_text::mapper tag_mapper_ = nullptr;

		link_mouseover_callback link_mouseover_callback_;
		link_callback link_callback_;

		box_shape shape_;

		struct text_chunk
		{
			std::string text;
			ui::text_style style; // to be OR'd with base style
			std::optional<gfx::color_rgba> color;
			std::optional<std::string> link;
		};

		struct image_chunk
		{
			std::string id;
			std::optional<gfx::color_rgba> color;
			std::optional<std::string> link;
		};

		using chunk = std::variant<text_chunk, image_chunk>;

		std::string text_;
		bool tagged_ = false;
		std::vector<chunk> chunks_;

		struct cached_state
		{
			struct image
			{
				math::box<float, 2> texcoords;
				math::box<float, 2> position;
			};

			struct batch
			{
				gfx::texture_2d const * texture;
				gfx::color_rgba color;
				bool text;
				std::vector<image> images;
			};

			std::vector<batch> batches;
			math::vector<float, 2> size{0.f, 0.f};

			fonts::font_type font_type = fonts::font_type::bitmap;
			float sdf_scale = 0.f;

			std::vector<std::pair<math::box<float, 2>, std::string>> link_bboxes;
		};

		mutable std::optional<cached_state> cached_state_;
		mutable std::optional<cached_state> cached_state_inf_;
		std::optional<std::string> selected_link_;
		bool mouse_down_ = false;

		void update_cached_state() const;
		cached_state cached_state_for(math::box<float, 2> const & bbox) const;

		mutable std::shared_ptr<gfx::texture_2d> single_white_pixel_texture_;
		std::shared_ptr<gfx::texture_2d> single_white_pixel_texture() const;
	};

}
