#pragma once

// Workaround for https://github.com/chriskohlhoff/asio/issues/1441
#if defined(_WIN32)
#	if defined(WIN32_LEAN_AND_MEAN)
#		include <boost/stacktrace.hpp>
#	else
#		define WIN32_LEAN_AND_MEAN
#		include <boost/stacktrace.hpp>
#		undef WIN32_LEAN_AND_MEAN
#	endif
#else
#	include <boost/stacktrace.hpp>
#endif

#include "concepts.hpp"

#include <fmt/ranges.h>
#include <fmt/std.h>

#include <cstdlib>
#include <filesystem>
#include <source_location>
#include <string_view>

namespace dv {

#if defined(NDEBUG) || (defined(_WIN32) && !defined(_DEBUG))
static constexpr bool DEBUG_ENABLED{false};
#else
static constexpr bool DEBUG_ENABLED{true};
#endif

template<typename EXPR, typename MSG>
requires dv::concepts::InvocableReturnArgumentsWeak<EXPR, bool>
	  && dv::concepts::InvocableReturnArgumentsWeak<MSG, std::string_view>
void runtime_assert(
	EXPR &&expression, MSG &&message, const std::source_location &location = std::source_location::current()) {
	if constexpr (dv::DEBUG_ENABLED) {
		if (!static_cast<bool>(expression())) [[unlikely]] {
			// Clean up file path. File here comes from std::source_location::file_name(), so it's just a
			// 'bag-of-bytes'. We simply treat it as a UTF-8 string to print, which will work anywhere minus Windows,
			// where the wide-characters aren't accessible anyway by design. No way to get that in a portable way.
			std::filesystem::path filePath{location.file_name()};
			filePath = std::filesystem::absolute(filePath).lexically_normal().make_preferred();

			fmt::print(stderr, "{}({}): {}()\nruntime assertion failed: {}\nStacktrace:\n{}\n", filePath,
				location.line(), location.function_name(), static_cast<std::string_view>(message()),
				boost::stacktrace::to_string(boost::stacktrace::stacktrace()));
			std::abort();
		}
	}
}

} // namespace dv
