/*
 * 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/bit.hpp>
#include <ox/std/error.hpp>
#include <ox/std/fmt.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/typetraits.hpp>
#include <ox/std/vector.hpp>

#include "optype.hpp"
#include "types.hpp"
#include "typenamecatcher.hpp"

namespace ox {

using FieldName = String;

using TypeParamPack = Vector<String>;

template<typename T>
constexpr auto buildTypeId() noexcept {
	constexpr auto name = requireModelTypeName<T>();
	constexpr auto version = requireModelTypeVersion<T>();
	return ox::sfmt("{};{}", name, version);
}

static constexpr auto buildTypeId(
		StringViewCR name, int version,
		const TypeParamPack &typeParams = {}) noexcept {
	String tp;
	if (!typeParams.empty()) {
		tp = "#";
		for (const auto &p : typeParams) {
			tp += p + ",";
		}
		tp.resize(tp.size() - 1);
		tp += "#";
	}
	return ox::sfmt("{}{};{}", name, tp, version);
}

enum class PrimitiveType: uint8_t {
	UnsignedInteger = 0,
	SignedInteger = 1,
	Bool = 2,
	// Float = 3, reserved, but not implemented
	String = 4,
	Struct = 5,
	Union = 6,
};

struct Subscript {
	static constexpr auto TypeName = "net.drinkingtea.ox.Subscript";
	static constexpr auto TypeVersion = 1;
	enum class SubscriptType: uint32_t {
		None        = 0,
		Ptr         = 1,
		PtrArray    = 2,
		InlineArray = 3,
		Vector      = 4,
	};
	SubscriptType subscriptType = SubscriptType::None;
	uint64_t length = 0;
	uint64_t smallSzLen = 0;
};

template<typename T>
constexpr Error model(T *io, CommonPtrWith<Subscript> auto *type) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<Subscript>());
	if constexpr(T::opType() == OpType::Reflect) {
		uint32_t st = 0;
		OX_RETURN_ERROR(io->field("subscriptType", &st));
	} else if constexpr(T::opType() == OpType::Write) {
		auto pt = type ? static_cast<uint8_t>(type->subscriptType) : 0;
		OX_RETURN_ERROR(io->field("subscriptType", &pt));
	} else {
		auto pt = type ? static_cast<uint32_t>(type->subscriptType) : 0;
		OX_RETURN_ERROR(io->field("subscriptType", &pt));
		type->subscriptType = static_cast<Subscript::SubscriptType>(pt);
	}
	OX_RETURN_ERROR(io->field("length", &type->length));
	OX_RETURN_ERROR(io->field("smallSzLen", &type->smallSzLen));
	return {};
}

using SubscriptStack = Vector<Subscript, 3>;

struct DescriptorField {
	// order of fields matters

	static constexpr auto TypeName = "net.drinkingtea.ox.DescriptorField";
	static constexpr auto TypeVersion = 3;

	// do not serialize type
	const struct DescriptorType *type = nullptr;
	String fieldName;
	int subscriptLevels = 0;
	SubscriptStack subscriptStack;
	String typeId; // gives reference to type for lookup if type is null

	constexpr DescriptorField() noexcept = default;

	constexpr DescriptorField(const DescriptorType *pType, String pFieldName,
	                          int pSubscriptLevels,
	                          SubscriptStack pSubscriptType,
	                          String pTypeId) noexcept:
		type(pType),
		fieldName(std::move(pFieldName)),
		subscriptLevels(pSubscriptLevels),
		subscriptStack(std::move(pSubscriptType)),
		typeId(std::move(pTypeId)) {
		oxAssert(subscriptLevels <= static_cast<int>(subscriptStack.size()), "Subscript level mismatch");
	}

	constexpr DescriptorField(const DescriptorField &other) noexcept:
		type(other.type),
		fieldName(other.fieldName),
		subscriptLevels(other.subscriptLevels),
		subscriptStack(other.subscriptStack),
		typeId(other.typeId) {
	}

	constexpr DescriptorField(DescriptorField &&other) noexcept:
		type(other.type),
		fieldName(std::move(other.fieldName)),
		subscriptLevels(other.subscriptLevels),
		subscriptStack(std::move(other.subscriptStack)),
		typeId(std::move(other.typeId)) {
		other.type = {};
		other.subscriptLevels = {};
		other.subscriptStack = {};
	}

	constexpr ~DescriptorField() noexcept = default;

	constexpr DescriptorField &operator=(const DescriptorField &other) noexcept = delete;

	constexpr DescriptorField &operator=(DescriptorField &&other) noexcept = delete;

};

using FieldList = Vector<DescriptorField>;

struct DescriptorType {

	static constexpr auto TypeName = "net.drinkingtea.ox.TypeDescriptor";
	static constexpr auto TypeVersion = 1;

	String typeName;
	int typeVersion = 0;
	PrimitiveType primitiveType = PrimitiveType::UnsignedInteger;
	TypeParamPack typeParams;
	// fieldList only applies to structs and unions
	FieldList fieldList;
	// - number of bytes for integer and float types
	// - number of fields for structs and lists
	int64_t length = 0;
	bool preloadable = false;

	constexpr DescriptorType() noexcept = default;

	constexpr explicit DescriptorType(String tn, int typeVersion, PrimitiveType t, TypeParamPack pTypeParams) noexcept:
		typeName(std::move(tn)),
		typeVersion(typeVersion),
		primitiveType(t),
		typeParams(std::move(pTypeParams)) {
	}

};

[[nodiscard]]
constexpr auto buildTypeId(const DescriptorType &t) noexcept {
	return buildTypeId(t.typeName, t.typeVersion, t.typeParams);
}

template<typename T>
constexpr Error model(T *io, CommonPtrWith<DescriptorType> auto *type) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<DescriptorType>());
	OX_RETURN_ERROR(io->field("typeName", &type->typeName));
	OX_RETURN_ERROR(io->field("typeVersion", &type->typeVersion));
	if constexpr(T::opType() == OpType::Reflect) {
		uint8_t pt = 0;
		OX_RETURN_ERROR(io->field("primitiveType", &pt));
	} else if constexpr(T::opType() == OpType::Write) {
		auto pt = type ? static_cast<uint8_t>(type->primitiveType) : 0;
		OX_RETURN_ERROR(io->field("primitiveType", &pt));
	} else {
		auto pt = type ? static_cast<uint8_t>(type->primitiveType) : 0;
		OX_RETURN_ERROR(io->field("primitiveType", &pt));
		type->primitiveType = static_cast<PrimitiveType>(pt);
	}
	OX_RETURN_ERROR(io->field("typeParams", &type->typeParams));
	OX_RETURN_ERROR(io->field("fieldList", &type->fieldList));
	OX_RETURN_ERROR(io->field("length", &type->length));
	OX_RETURN_ERROR(io->field("preloadable", &type->preloadable));
	return {};
}

template<typename T>
constexpr Error model(T *io, CommonPtrWith<DescriptorField> auto *field) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<DescriptorField>());
	OX_RETURN_ERROR(io->field("typeId", &field->typeId));
	OX_RETURN_ERROR(io->field("fieldName", &field->fieldName));
	OX_RETURN_ERROR(io->field("subscriptLevels", &field->subscriptLevels));
	OX_RETURN_ERROR(io->field("subscriptStack", &field->subscriptStack));
	// defaultValue is unused now, but leave placeholder for backwards compatibility
	int defaultValue = 0;
	OX_RETURN_ERROR(io->field("defaultValue", &defaultValue));
	return {};
}

template<typename ReaderBase>
class TypeDescReader;

#if 0 // unused right now
template<typename T>
constexpr Error model(TypeDescReader<T> *io, CommonPtrWith<DescriptorField> auto *field) noexcept {
	io->template setTypeInfo<DescriptorField>();
	oxReturnError(io->field("typeName", &field->typeName));
	oxReturnError(io->field("typeVersion", &field->typeVersion));
	auto &typeStore = io->typeStore();
	auto &[type, err] = typeStore->at(field->typeName).value;
	oxReturnError(err);
	field->type = type.get();
	oxReturnError(io->field("fieldName", &field->fieldName));
	oxReturnError(io->field("subscriptLevels", &field->subscriptLevels));
	if constexpr(T::opType() != ox::OpType::Reflect) {
		auto subscriptStack = static_cast<uint32_t>(field->subscriptStack);
		oxReturnError(io->field("subscriptStack", &subscriptStack));
		field->subscriptStack = static_cast<DescriptorField::SubscriptType>(subscriptStack);
	} else {
		oxReturnError(io->field("subscriptStack", &field->subscriptStack));
	}
	// defaultValue is unused now, but placeholder for backwards compatibility
	int defaultValue = 0;
	oxReturnError(io->field("defaultValue", &defaultValue));
	return {};
}
#endif

}
