/*
 * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <ox/std/defines.hpp>

#include <ox/claw/claw.hpp>
#include <ox/fs/fs.hpp>
#include <ox/model/metadata.hpp>


#include "asset.hpp"
#include "context.hpp"
#include "typeconv.hpp"

namespace keel {

// Pointer to preloaded data that can be stored in FS in place of the actual
// data.
struct PreloadPtr {
	static constexpr auto TypeName = "net.drinkingtea.keel.PreloadPtr";
	static constexpr auto TypeVersion = 2;
	uint64_t preloadAddr = 0;
};

OX_MODEL_BEGIN(PreloadPtr)
	OX_MODEL_FIELD(preloadAddr)
OX_MODEL_END()

ox::Result<std::size_t> getPreloadAddr(Context &ctx, ox::FileAddress const &addr) noexcept;
ox::Result<std::size_t> getPreloadAddr(Context &ctx, ox::StringViewCR path) noexcept;


void createUuidMapping(Context &ctx, ox::StringViewCR filePath, ox::UUID const &uuid) noexcept;

// map of UUIDs to paths
using DuplicateSet = ox::HashMap<ox::UUID, ox::Vector<ox::String>>;

ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::StringViewCR path) noexcept;

ox::Result<ox::UUID> getUuid(Context &ctx, ox::FileAddress const &fileAddr) noexcept;

ox::Result<ox::UUID> getUuid(Context &ctx, ox::StringViewCR path) noexcept;

ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const &fileAddr) noexcept;

ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringViewCR fileId) noexcept;

ox::Error updatePath(Context &ctx, ox::StringViewCR oldPath, ox::StringViewCR newPath) noexcept;

constexpr ox::Result<ox::UUID> uuidUrlToUuid(ox::StringViewCR uuidUrl) noexcept {
	return ox::UUID::fromString(substr(uuidUrl, 7));
}

ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringViewCR uuid) noexcept;

ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::StringViewCR uuid) noexcept;

ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const &uuid) noexcept;

[[nodiscard]]
constexpr bool isUuidUrl(ox::StringViewCR path) noexcept {
	return ox::beginsWith(path, "uuid://");
}

#ifndef OX_BARE_METAL

namespace detail {
template<typename T>
constexpr auto makeLoader(Context &ctx) {
	return [&ctx](ox::StringView assetId) -> ox::Result<T> {
		if (!beginsWith(assetId, "/")) {
			auto const p = ctx.uuidToPath.at(assetId);
			if (p.error) {
				oxErrf("Could not find asset: {}\n", assetId);
				return ox::Error{1, "Asset ID not found"};
			}
			assetId = *p.value;
		}
		OX_REQUIRE(buff, ctx.rom->read(assetId));
		auto [obj, err] = readAsset<T>(buff);
		if (err) {
			if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) {
				return err;
			}
			OX_RETURN_ERROR(convert<T>(ctx, buff, obj));
		}
		return std::move(obj);
	};
};
}

template<typename T>
ox::Result<AssetRef<T>> readObjFile(
		Context &ctx,
		ox::StringViewCR assetId,
		bool const forceLoad) noexcept {
	static constexpr auto load = [](
		keel::Context &ctx,
		ox::StringViewCR assetId,
		bool forceLoad) -> ox::Result<keel::AssetRef<T>> {
		if (forceLoad) {
			ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
			return ctx.assetManager.loadAsset<T>(assetId);
		} else {
			auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
			if (err) {
				ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
				OX_RETURN_ERROR(ctx.assetManager.loadAsset<T>(assetId).moveTo(cached));
			}
			return cached;
		}
	};
	if (beginsWith(assetId, "uuid://")) {
		return load(ctx, substr(assetId, 7), forceLoad);
	} else {
		auto const [uuid, uuidErr] = getUuid(ctx, assetId);
		if (!uuidErr) {
			return load(ctx, uuid.toString(), forceLoad);
		} else {
			return load(ctx, assetId, forceLoad);
		}
	}
}

#else

template<typename T>
ox::Result<keel::AssetRef<T>> readObjNoCache(
		keel::Context &ctx,
		ox::StringViewCR assetId) noexcept {
	if constexpr(ox::preloadable<T>::value) {
		OX_REQUIRE(addr, getPreloadAddr(ctx, assetId));
		return keel::AssetRef<T>(std::bit_cast<T const*>(uintptr_t{addr}));
	} else {
		return ox::Error(1);
	}
}

#endif

ox::Error reloadAsset(Context &ctx, ox::StringViewCR assetId) noexcept;

template<typename T>
ox::Result<AssetRef<T>> readObj(
		Context &ctx,
		ox::StringViewCR assetId,
		[[maybe_unused]] bool const forceLoad = false) noexcept {
#ifndef OX_BARE_METAL
	return readObjFile<T>(ctx, assetId, forceLoad);
#else
	return readObjNoCache<T>(ctx, assetId);
#endif
}

template<typename T>
ox::Result<AssetRef<T>> readObj(
		Context &ctx,
		ox::FileAddress const &file,
		[[maybe_unused]] bool const forceLoad = false) noexcept {
#ifndef OX_BARE_METAL
	OX_REQUIRE(assetId, file.getPath());
	return readObj<T>(ctx, ox::StringView(assetId), forceLoad);
#else
	if constexpr(ox::preloadable<T>::value) {
		OX_REQUIRE(addr, getPreloadAddr(ctx, file));
		return keel::AssetRef<T>(std::bit_cast<T const*>(uintptr_t{addr}));
	} else {
		return ox::Error(1);
	}
#endif
}

template<typename T>
ox::Error writeObj(
		Context &ctx,
		ox::FileAddress const &file,
		T const &obj,
		ox::ClawFormat const fmt = ox::ClawFormat::Metal) noexcept {
	OX_REQUIRE(objBuff, ox::writeClaw(obj, fmt));
	return ctx.rom->write(file, objBuff.data(), objBuff.size());
}

ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs, DuplicateSet &duplicateSet) noexcept;

ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept;

ox::Result<ox::UPtr<ox::FileSystem>> loadRomFs(ox::StringViewCR path) noexcept;

}
