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

#include "typenamecatcher.hpp"
#include "desctypes.hpp"

namespace ox {

class TypeStore {
	private:
		HashMap<String, UPtr<DescriptorType>> m_cache;

	public:
		constexpr TypeStore() noexcept = default;

		constexpr virtual ~TypeStore() noexcept = default;

		constexpr Result<const DescriptorType*> get(const auto &name, int typeVersion,
		                                            const TypeParamPack &typeParams) const noexcept {
			const auto typeId = buildTypeId(name, typeVersion, typeParams);
			OX_REQUIRE(out, m_cache.at(typeId));
			return out->get();
		}

		template<typename T>
		constexpr Result<const DescriptorType*> get() const noexcept {
			constexpr auto typeName = ModelTypeName_v<T>;
			constexpr auto typeVersion = ModelTypeVersion_v<T>;
			const auto typeId = buildTypeId(typeName, typeVersion, {});
			OX_REQUIRE(out, m_cache.at(typeId));
			return out->get();
		}

		constexpr DescriptorType *getInit(StringViewCR typeName, int typeVersion, PrimitiveType pt,
		                                  const TypeParamPack &typeParams) noexcept {
			const auto typeId = buildTypeId(typeName, typeVersion, typeParams);
			auto &out = m_cache[typeId];
			out = ox::make_unique<DescriptorType>(String(typeName), typeVersion, pt, typeParams);
			return out.get();
		}

		constexpr Result<const DescriptorType*> getLoad(const auto &typeId) noexcept {
			auto [val, err] = m_cache.at(typeId);
			if (err) {
				if (!std::is_constant_evaluated()) {
					OX_REQUIRE_M(dt, loadDescriptor(typeId));
					for (auto &f : dt->fieldList) {
						if (typeId == f.typeId) {
							f.type = dt.get();
						} else {
							OX_RETURN_ERROR(this->getLoad(f.typeId).moveTo(f.type));
						}
					}
					auto &out = m_cache[typeId];
					out = std::move(dt);
					return out.get();
				} else {
					return ox::Error(1, "Type not available");
				}
			}
			return val->get();
		}

		constexpr Result<const DescriptorType*> getLoad(const auto &typeName, auto typeVersion,
		                                                const TypeParamPack &typeParams = {}) noexcept {
			return getLoad(buildTypeId(typeName, typeVersion, typeParams));
		}

		template<typename T>
		constexpr Result<const DescriptorType*> getLoad() noexcept {
			constexpr auto typeName = requireModelTypeName<T>();
			constexpr auto typeVersion = requireModelTypeVersion<T>();
			return getLoad(typeName, typeVersion);
		}

		constexpr void set(const auto &typeId, UPtr<DescriptorType> dt) noexcept {
			m_cache[typeId] = std::move(dt);
		}

		constexpr void set(const auto &typeId, DescriptorType *dt) noexcept {
			m_cache[typeId] = UPtr<DescriptorType>(dt);
		}

		[[nodiscard]]
		constexpr auto typeList() const noexcept {
			const auto &keys = m_cache.keys();
			ox::Vector<DescriptorType*> descs;
			for (const auto &k : keys) {
				descs.emplace_back(m_cache.at(k).unwrap()->get());
			}
			return descs;
		}

	protected:
		virtual Result<UPtr<DescriptorType>> loadDescriptor(ox::StringView) noexcept {
			return ox::Error(1);
		}

		Result<UPtr<DescriptorType>> loadDescriptor(ox::StringViewCR name, int version,
		                                                 const ox::TypeParamPack &typeParams) noexcept {
			const auto typeId = buildTypeId(name, version, typeParams);
			return loadDescriptor(typeId);
		}

};

}
