/*********************************************************************
 * \file   AssetContainer.hpp
 *
 * \author Romain BOULLARD
 * \date   February 2026
 *********************************************************************/
#ifndef BIGFOOT_ENGINE_ASSETCONTAINER_HPP
#define BIGFOOT_ENGINE_ASSETCONTAINER_HPP
#include <Engine/BigFile/Asset/Asset.hpp>
#include <Engine/EngineAssertHandler.hpp>

#include <ankerl/unordered_dense.h>

namespace Bigfoot
{
template<typename ASSET>
concept BigfootAssetConcept = requires(ASSET p_asset, const ASSET p_constAsset) {
    requires FlatAssetConcept<typename ASSET::FLAT_ASSET>;
    requires std::constructible_from<ASSET, FlatAssetWrapper<typename ASSET::FLAT_ASSET>*>;
    { ASSET::ReflectionInfo() } -> std::convertible_to<eastl::span<const std::byte>>;
};

template<BigfootAssetConcept ASSET>
class AssetContainer
{
  public:
    AssetContainer() = default;

    AssetContainer(const AssetContainer& p_container) = delete;
    AssetContainer(AssetContainer&& p_container) = delete;

    [[nodiscard]]
    bool Add(const UUID& p_uuid)
    {
        return m_assets.emplace(p_uuid, AssetHandle {}).second;
    }

    [[nodiscard]]
    bool Load(const UUID& p_uuid, const eastl::span<const std::byte> p_flatBuffer)
    {
        if (!m_assets.contains(p_uuid))
        {
            const bool added = Add(p_uuid);
            ASSERT(EngineAssertHandler, added, "{} not added to AssetContainer! Already in container ?", p_uuid);
        }

        AssetHandle& assetHandle = m_assets.at(p_uuid);
        if (assetHandle.m_assetPair)
        {
            SOFT_ASSERT(EngineAssertHandler, false, "{} already loaded!", p_uuid);
            return true;
        }

        assetHandle.m_assetPair = eastl::make_unique<typename AssetHandle::Data>(p_flatBuffer);
        CRITICAL_ASSERT(EngineAssertHandler,
                        assetHandle.m_assetPair->m_flatAsset.Asset().asset_header->uuid == p_uuid,
                        "Mismatch between expected UUID and deserialized UUID ! Got {}; Expected {}",
                        assetHandle.m_assetPair->m_flatAsset.Asset().asset_header->uuid,
                        p_uuid);

        return true;
    }

    void UnloadUnreferenced()
    {
        for (auto& keyValue: m_assets)
        {
            if (keyValue.second.m_hardRefCount == 0 && keyValue.second.m_assetPair)
            {
                keyValue.second.m_assetPair.reset();
            }
        }
    }

    ~AssetContainer() = default;

    AssetContainer& operator=(const AssetContainer& p_container) = delete;
    AssetContainer& operator=(AssetContainer&& p_container) = delete;

  private:
    struct AssetHandle
    {
        struct Data
        {
            Data(const eastl::span<const std::byte> p_flatBuffer):
                m_flatAsset(p_flatBuffer),
                m_asset(&m_flatAsset)
            {
            }

            FlatAssetWrapper<typename ASSET::FLAT_ASSET> m_flatAsset;
            ASSET m_asset;
        };

        eastl::unique_ptr<Data> m_assetPair = nullptr;

        std::uint32_t m_hardRefCount = 0;
        std::uint32_t m_softRefCount = 0;
    };

    // We use a segmented_map here to keep stable references
    ankerl::unordered_dense::segmented_map<UUID, AssetHandle> m_assets;
};
}   // namespace Bigfoot

#endif
