/*
 * 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/byteswap.hpp>
#include <ox/std/istring.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>

#include "desctypes.hpp"
#include "fieldcounter.hpp"
#include "metadata.hpp"
#include "modelhandleradaptor.hpp"
#include "optype.hpp"
#include "typenamecatcher.hpp"
#include "types.hpp"
#include "typestore.hpp"

namespace ox {

namespace detail {

template<typename T>
constexpr int indirectionLevels_v = 0;

template<typename T>
constexpr int indirectionLevels_v<T*> = 1 + indirectionLevels_v<T>;

template<typename T, std::size_t sz>
constexpr int indirectionLevels_v<T[sz]> = 1 + indirectionLevels_v<T>;

template<typename T, std::size_t SmallVecSz>
constexpr int indirectionLevels_v<::ox::Vector<T, SmallVecSz>> = 1 + indirectionLevels_v<T>;

template<typename T>
constexpr auto buildSubscriptStack(const T*, SubscriptStack*) noexcept {
}

template<typename T>
constexpr auto buildSubscriptStack(const T**, SubscriptStack *s) noexcept {
	s->push_back({.subscriptType = Subscript::SubscriptType::Ptr});
}

template<typename T>
constexpr auto buildSubscriptStack(const UPtr<T>*, SubscriptStack *s) noexcept {
	s->push_back({.subscriptType = Subscript::SubscriptType::Ptr});
}

template<typename T, std::size_t sz>
constexpr auto buildSubscriptStack(const Array<T, sz>*, SubscriptStack *s) noexcept {
	s->push_back({.subscriptType = Subscript::SubscriptType::InlineArray, .length = sz});
	buildSubscriptStack(static_cast<T*>(nullptr), s);
}

template<typename T, std::size_t SmallVecSz>
constexpr auto buildSubscriptStack(const Vector<T, SmallVecSz>*, SubscriptStack *s) noexcept {
	s->push_back({
		.subscriptType = Subscript::SubscriptType::Vector,
		.smallSzLen = SmallVecSz,
	});
	buildSubscriptStack(static_cast<T*>(nullptr), s);
}

template<typename T>
constexpr auto buildSubscriptStack(const T*) {
	SubscriptStack s;
	buildSubscriptStack(static_cast<const T*>(nullptr), &s);
	return s;
}

}

class TypeDescWriter {

	private:
		TypeStore *m_typeStore = nullptr;
		DescriptorType *m_type = nullptr;

	public:
		explicit constexpr TypeDescWriter(TypeStore *typeStore = nullptr) noexcept;

		constexpr ~TypeDescWriter() noexcept = default;

		template<typename T = std::nullptr_t>
		constexpr ox::Error setTypeInfo(StringViewCR name = T::TypeName,
		                                int version = T::TypeVersion,
		                                const TypeParamPack &typeParams = {},
		                                std::size_t fields = ModelFieldCount_v<T>) noexcept;

		template<typename T>
		constexpr Error field(
				StringViewCR name,
				T const*val,
				std::size_t valLen,
				SubscriptStack const&subscriptStack) noexcept;

		template<typename T>
		constexpr Error field(StringViewCR name, T const*val, std::size_t valLen) noexcept;

		template<typename T, bool force>
		constexpr Error field(StringViewCR name, UnionView<T, force> val) noexcept;

		template<typename T>
		constexpr Error field(StringViewCR name, const T *val) noexcept;

		template<typename ...Args>
		constexpr Error fieldCString(StringViewCR name, Args&&...) noexcept;

		[[nodiscard]]
		constexpr DescriptorType *definition() noexcept {
			return m_type;
		}

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

	private:
		[[nodiscard]]
		constexpr const DescriptorType *type(const Integer_c auto *val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(const bool *val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(const char *val) const noexcept;

		template<std::size_t SmallStrSz>
		[[nodiscard]]
		constexpr const DescriptorType *type(const BasicString<SmallStrSz>*) const noexcept {
			constexpr auto PT = PrimitiveType::String;
			return getType(types::BasicString, 1, PT, 0, {sfmt("{}", SmallStrSz)});
		}

		template<std::size_t sz>
		[[nodiscard]]
		constexpr const DescriptorType *type(const IString<sz> *val) const noexcept;

		template<typename T>
		[[nodiscard]]
		constexpr const DescriptorType *type(const T *val) const noexcept;

		template<typename T>
		[[nodiscard]]
		constexpr const DescriptorType *type(const HashMap<String, T> *val) const noexcept;

		template<typename U>
		[[nodiscard]]
		constexpr const DescriptorType *type(UnionView<U> val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *getType(StringViewCR tn, int typeVersion, PrimitiveType t, int b,
		                                        const TypeParamPack &typeParams = {}) const noexcept;

};

constexpr TypeDescWriter::TypeDescWriter(TypeStore *typeStore) noexcept: m_typeStore(typeStore) {}

template<typename T>
constexpr ox::Error TypeDescWriter::setTypeInfo(
		StringViewCR typeName, int typeVersion,
		const TypeParamPack &typeParams, std::size_t) noexcept {
	PrimitiveType pt;
	if constexpr(is_union_v<T>) {
		pt = PrimitiveType::Union;
	} else if constexpr(isBasicString_v<T> || isIString_v<T>) {
		pt = PrimitiveType::String;
	} else {
		pt = PrimitiveType::Struct;
	}
	m_type = m_typeStore->getInit(typeName, typeVersion, pt, typeParams);
	m_type->preloadable = preloadable<T>::value;
	return {};
}

// array handler
template<typename T>
constexpr Error TypeDescWriter::field(StringViewCR name, T const*, std::size_t, SubscriptStack const&subscriptStack) noexcept {
	if (m_type) {
		constexpr typename remove_pointer<T>::type *p = nullptr;
		const auto t = type(p);
		oxAssert(t != nullptr, "field(const char *name, T *val, std::size_t): Type not found or generated");
		m_type->fieldList.emplace_back(t, String(name), detail::indirectionLevels_v<T> + 1, subscriptStack, buildTypeId(*t));
		return {};
	}
	return ox::Error(1);
}

// array handler
template<typename T>
constexpr Error TypeDescWriter::field(StringViewCR name, T const*, std::size_t) noexcept {
	if (m_type) {
		constexpr typename remove_pointer<T>::type *p = nullptr;
		const auto t = type(p);
		oxAssert(t != nullptr, "field(const char *name, T *val, std::size_t): Type not found or generated");
		auto const lvls = detail::indirectionLevels_v<T> + 1;
		SubscriptStack subscriptStack{lvls};
		m_type->fieldList.emplace_back(t, String(name), lvls, subscriptStack, buildTypeId(*t));
		return {};
	}
	return ox::Error(1);
}

template<typename T, bool force>
constexpr Error TypeDescWriter::field(StringViewCR name, UnionView<T, force> val) noexcept {
	if (m_type) {
		const auto t = type(val);
		oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
		m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, ox::String(t->typeName));
		return {};
	}
	return ox::Error(1);
}

template<typename T>
constexpr Error TypeDescWriter::field(StringViewCR name, const T *val) noexcept {
	if (m_type) {
		if constexpr(isVector_v<T> || isArray_v<T>) {
			typename T::value_type *data = nullptr;
			return field(name, data, 0, detail::buildSubscriptStack(val));
		} else if constexpr(isSmartPtr_v<T>) {
			typename T::value_type *data = nullptr;
			return field(name, data, 0, detail::buildSubscriptStack(val));
		} else if constexpr(is_pointer_v<T>) {
			return field(name, val, 0, detail::buildSubscriptStack(val));
		} else {
			const auto t = type(val);
			oxAssert(t != nullptr, "field(const char *name, T *val): Type not found or generated");
			m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, buildTypeId(*t));
			return {};
		}
	}
	return ox::Error(1);
}

template<typename ...Args>
constexpr Error TypeDescWriter::fieldCString(StringViewCR name, Args&&...) noexcept {
	constexpr auto s = "";
	const auto t = type(s);
	m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, ox::String(t->typeName));
	return {};
}

template<typename T>
constexpr const DescriptorType *TypeDescWriter::type(const T *val) const noexcept {
	if constexpr(isVector_v<T>) {
		return type(static_cast<typename T::value_type*>(nullptr));
	} else {
		auto [t, err] = m_typeStore->template get<T>();
		if (!err) {
			return t;
		} else {
			TypeDescWriter dw(m_typeStore);
			const auto reflectErr = model(&dw, val);
			oxLogError(reflectErr);
			oxAssert(reflectErr, "field(const char *name, T val): Type info could not be generated");
			return dw.m_type;
		}
	}
}

template<typename T>
constexpr const DescriptorType *TypeDescWriter::type(const HashMap<String, T>*) const noexcept {
	return type(static_cast<T*>(nullptr));
}

template<typename U>
constexpr const DescriptorType *TypeDescWriter::type(UnionView<U> val) const noexcept {
	const auto t = type(val.get());
	oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
	return t;
}

constexpr const DescriptorType *TypeDescWriter::type(Integer_c auto const *val) const noexcept {
	constexpr auto isSigned = ox::is_signed_v<decltype(*val)>;
	constexpr auto PT = isSigned ?
		PrimitiveType::SignedInteger :
		PrimitiveType::UnsignedInteger;
	constexpr auto sz = sizeof(*val);
	constexpr auto bytes = sizeof(*val);
	return getType([] {
			switch (sz) {
				case 1:
					return isSigned ? types::Int8 : types::Uint8;
				case 2:
					return isSigned ? types::Int16 : types::Uint16;
				case 4:
					return isSigned ? types::Int32 : types::Uint32;
				case 8:
					return isSigned ? types::Int64 : types::Uint64;
			}
			return types::Int32;
		}(), 0, PT, bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const bool*) const noexcept {
	constexpr auto PT = PrimitiveType::Bool;
	constexpr auto Bytes = 0;
	return getType(types::Bool, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const char*) const noexcept {
	constexpr auto PT = PrimitiveType::String;
	return getType(types::String, 0, PT, 0);
}

template<std::size_t sz>
constexpr const DescriptorType *TypeDescWriter::type(const IString<sz>*) const noexcept {
	constexpr auto PT = PrimitiveType::String;
	return getType(types::IString, 0, PT, 0);
}

constexpr const DescriptorType *TypeDescWriter::getType(StringViewCR tn, int typeVersion, PrimitiveType pt, int b,
                                                        const TypeParamPack &typeParams) const noexcept {
	auto t = m_typeStore->get(tn, typeVersion, typeParams);
	if (!t.error) {
		auto type = t.value;
		oxAssert(type != nullptr, "TypeDescWriter::getType returning null DescriptorType");
		return type;
	} else {
		auto dt = ox::make_unique<DescriptorType>(String(tn), typeVersion, pt, typeParams);
		dt->length = b;
		const auto out = dt.get();
		const auto typeId = buildTypeId(tn, typeVersion, typeParams);
		m_typeStore->set(typeId, std::move(dt));
		return out;
	}
}

template<typename T>
constexpr Result<DescriptorType*> buildTypeDef(TypeStore &typeStore) noexcept {
	TypeDescWriter writer(&typeStore);
	ModelHandlerInterface<TypeDescWriter, ox::OpType::Reflect> handler(writer);
	if (std::is_constant_evaluated()) {
		std::allocator<T> a;
		T *t = a.allocate(1);
		OX_RETURN_ERROR(model(&handler, t));
		a.deallocate(t, 1);
	} else {
		auto t = ox_malloca(sizeof(T), T);
		OX_RETURN_ERROR(model(&handler, t.get()));
	}
	return writer.definition();
}

template<typename T>
constexpr Result<DescriptorType*> buildTypeDef(TypeStore &typeStore, T &val) noexcept {
	TypeDescWriter writer(&typeStore);
	ModelHandlerInterface<TypeDescWriter, ox::OpType::Reflect> handler(writer);
	OX_RETURN_ERROR(model(&handler, &val));
	return writer.definition();
}

}
