/*
 * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <imgui.h>

#include <ox/std/math.hpp>
#include <ox/std/point.hpp>
#include <ox/std/size.hpp>
#include <ox/std/typetraits.hpp>

namespace studio {

struct Selection {
	ox::Point a, b;
	constexpr Selection() noexcept = default;
	constexpr Selection(ox::Point const &pA, ox::Point const &pB) noexcept: a(pA), b(pB) {}
	[[nodiscard]]
	constexpr ox::Size size() const noexcept {
		return {b.x - a.x, b.y - a.y};
	}
	[[nodiscard]]
	constexpr bool contains(ox::Point const &pt) const noexcept {
		return a.x <= pt.x && a.y <= pt.y
		    && b.x >= pt.x && b.y >= pt.y;
	}
};

constexpr auto iterateSelection(Selection const &sel, auto const &cb) {
	constexpr auto retErr = ox::is_same_v<decltype(cb(0, 0)), ox::Error>;
	for (auto x = sel.a.x; x <= sel.b.x; ++x) {
		for (auto y = sel.a.y; y <= sel.b.y; ++y) {
			if constexpr(retErr) {
				OX_RETURN_ERROR(cb(x, y));
			} else {
				cb(x, y);
			}
		}
	}
	if constexpr(retErr) {
		return ox::Error{};
	}
};

constexpr auto iterateSelectionRows(Selection const &sel, auto const &cb) {
	constexpr auto retErr = ox::is_same_v<decltype(cb(0, 0)), ox::Error>;
	for (auto y = sel.a.y; y <= sel.b.y; ++y) {
		for (auto x = sel.a.x; x <= sel.b.x; ++x) {
			if constexpr(retErr) {
				OX_RETURN_ERROR(cb(x, y));
			} else {
				cb(x, y);
			}
		}
	}
	if constexpr(retErr) {
		return ox::Error{};
	}
};

class SelectionTracker {
	private:
		bool m_selectionOngoing{};
		ox::Point m_pointA;
		ox::Point m_pointB;
	public:
		[[nodiscard]]
		constexpr bool selectionOngoing() const noexcept {
			return m_selectionOngoing;
		}

		/**
		 *
		 * @param cursor
		 * @param allowStart
		 * @return true if changed, false otherwise
		 */
		constexpr bool updateCursorPoint(ox::Point cursor, bool allowStart = true) noexcept {
			auto changed = false;
			if (!m_selectionOngoing && allowStart) {
				m_pointA = cursor;
				m_selectionOngoing = true;
			}
			if (m_selectionOngoing) {
				m_pointB = cursor;
				changed = true;
			}
			return changed;
		}

		constexpr void updateCursorPoint(ox::Vec2 cursor, bool allowStart = true) noexcept {
			updateCursorPoint(
					ox::Point{
							static_cast<int32_t>(cursor.x),
							static_cast<int32_t>(cursor.y),
					},
					allowStart);
		}

		constexpr void updateCursorPoint(ImVec2 cursor, bool allowStart = true) noexcept {
			updateCursorPoint(
					ox::Point{
						static_cast<int32_t>(cursor.x),
						static_cast<int32_t>(cursor.y),
					},
					allowStart);
		}

		constexpr void reset() noexcept {
			m_selectionOngoing = {};
		}

		constexpr void finishSelection() noexcept {
			m_selectionOngoing = {};
		}

		[[nodiscard]]
		constexpr Selection selection() const noexcept {
			return {
				{
					ox::max(0, ox::min(m_pointA.x, m_pointB.x)),
					ox::max(0, ox::min(m_pointA.y, m_pointB.y)),
				},
				{
					ox::max(0, ox::max(m_pointA.x, m_pointB.x)),
					ox::max(0, ox::max(m_pointA.y, m_pointB.y)),
				},
			};
		}
};

}