/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>

#include "fieldcounter.hpp"
#include "optype.hpp"

namespace ox {

struct TypeInfoCatcher {

	CString name = "";
	int version = 0;

	template<typename T = std::nullptr_t>
	constexpr Error setTypeInfo(
			CString const n = T::TypeName,
			int const v = T::TypeVersion,
			Vector<String> const& = {},
			size_t = 0) noexcept {
		this->name = n;
		this->version = v;
		return {};
	}

	template<typename T>
	constexpr Error field(CString, T*, size_t) noexcept {
		return {};
	}

	template<typename T>
	constexpr Error field(CString, T const&) noexcept {
		return {};
	}

	template<typename T>
	constexpr Error fieldCString(CString, T const&) noexcept {
		return {};
	}

	static constexpr auto opType() noexcept {
		return OpType::Reflect;
	}

};

template<typename T>
[[nodiscard]]
constexpr int getModelTypeVersion(T *val) noexcept {
	TypeInfoCatcher nc;
	std::ignore = model(&nc, val);
	return nc.version;
}

template<typename T>
[[nodiscard]]
constexpr int getModelTypeVersion() noexcept {
	std::allocator<T> a;
	const auto t = a.allocate(1);
	const auto out = getModelTypeVersion(t);
	a.deallocate(t, 1);
	return out;
}

template<typename T>
[[nodiscard]]
consteval int requireModelTypeVersion() noexcept {
	constexpr auto version = getModelTypeVersion<T>();
	static_assert(version != 0, "TypeVersion is required");
	return version;
}

template<typename T, typename Str = const char*>
[[nodiscard]]
constexpr Str getModelTypeName(T *val) noexcept {
	TypeInfoCatcher nc;
	std::ignore = model(&nc, val);
	return nc.name;
}

template<typename T, typename Str = const char*>
[[nodiscard]]
consteval Str getModelTypeName() noexcept {
	std::allocator<T> a;
	auto t = a.allocate(1);
	auto out = getModelTypeName(t);
	a.deallocate(t, 1);
	return out;
}

template<typename T, typename Str = StringLiteral>
[[nodiscard]]
consteval auto requireModelTypeName() noexcept {
	constexpr auto name = getModelTypeName<T, Str>();
	static_assert(StringView{name}.size(), "Type lacks required TypeName");
	return name;
}

template<typename T, typename Str = StringLiteral>
constexpr auto ModelTypeName_v = requireModelTypeName<T, Str>();

template<typename T, typename Str = const char*>
constexpr auto ModelTypeVersion_v = requireModelTypeVersion<T>();

template<typename T>
constexpr auto ModelTypeId_v = [] {
	constexpr auto name = ModelTypeName_v<T, StringView>;
	constexpr auto version = ModelTypeVersion_v<T>;
	constexpr auto versionStr = ox::sfmt<IString<19>>("{}", version);
	return ox::sfmt<IString<name.size() + versionStr.size() + 1>>("{};{}", name, versionStr);
}();

}
