//
// Created by Dan Crosby on 10/5/20.
//

#ifndef EAGLE0_ACTIONPOINTDISTANCESCACHE_HPP
#define EAGLE0_ACTIONPOINTDISTANCESCACHE_HPP

#include <shared_mutex>
#include <unordered_map>

#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wthread-safety-analysis"
#pragma GCC diagnostic ignored "-Wunused-result"
#include <gtl/phmap.hpp>
#pragma GCC diagnostic pop

namespace shardok {

using std::shared_ptr;

struct MapId {
    uint64_t terrainTypesId;
    uint64_t modifierId;

    auto operator==(const MapId& other) const -> bool {
        return terrainTypesId == other.terrainTypesId && modifierId == other.modifierId;
    }
};

// Unified cache key for both thread-safe and thread-local caches
struct FullCacheKey {
    MapId mapId;
    int battalionTypeId;
    bool includeBravingWater;
    int braveWaterCost;

    bool operator==(const FullCacheKey& other) const {
        return mapId == other.mapId && battalionTypeId == other.battalionTypeId &&
               includeBravingWater == other.includeBravingWater &&
               braveWaterCost == other.braveWaterCost;
    }
};

// Hash function for FullCacheKey
struct FullCacheKeyHash {
    size_t operator()(const FullCacheKey& key) const {
        // Pack small fields into a single 64-bit value
        uint64_t packed = (static_cast<uint64_t>(key.battalionTypeId) << 32) |
                          (static_cast<uint64_t>(key.braveWaterCost) << 1) |
                          (key.includeBravingWater ? 1 : 0);

        // Hash MapId fields directly instead of going through hash_value(MapId)
        return gtl::HashState::combine(0, key.mapId.terrainTypesId, key.mapId.modifierId, packed);
    }
};

class ActionPointDistancesCache {
private:
    struct CacheEntry {
        shared_ptr<ActionPointDistances> sharedPtr;
        const ActionPointDistances* rawPtr;

        explicit CacheEntry(shared_ptr<ActionPointDistances> ptr)
            : sharedPtr(std::move(ptr)),
              rawPtr(sharedPtr.get()) {}
    };

    // Tier 1: persistent map. This is NOT safe to write to while reads may be happening.
    using PersistentMap = gtl::flat_hash_map<FullCacheKey, CacheEntry, FullCacheKeyHash>;

    PersistentMap persistentCache;

    using APDMap = gtl::parallel_flat_hash_map<
            FullCacheKey,
            shared_ptr<ActionPointDistances>,
            FullCacheKeyHash,
            std::equal_to<FullCacheKey>,
            std::allocator<std::pair<const FullCacheKey, shared_ptr<ActionPointDistances>>>,
            6,
            std::mutex>;

    APDMap sharedDistances;

    using TLSCache = gtl::flat_hash_map<FullCacheKey, CacheEntry, FullCacheKeyHash>;
    static thread_local TLSCache tlsCache;

    // Epoch system removed - TLS cache uses size-based eviction instead

    // Helper to build cache key
    static auto MakeCacheKey(
            const MapId& mapId,
            const BattalionTypeSPtr& battalionType,
            bool includeBravingWater,
            int braveWaterActionPointCost) -> FullCacheKey;

public:
    explicit ActionPointDistancesCache() {
        // Pre-size persistent cache to reduce hash collisions
        // Estimate: ~12 entries from pre-fetching + ~50-100 entries during gameplay
        persistentCache.reserve(128);
    }

    // Returns raw pointer for zero overhead access
    // Lifetime guaranteed by shared cache ownership
    auto GetRaw(
            const HexMap* map,
            const MapId& mapId,
            const BattalionTypeSPtr& battalionType,
            bool includeBravingWater,
            int braveWaterActionPointCost = -1) -> const ActionPointDistances*;

    static auto GetMapId(const HexMap* map) -> MapId;

    // Consolidate the thread-safe cache into the persistent cache and clear
    // the current thread's local cache. This is only safe if we know reads
    // are not happening from other threads.
    void ConsolidateThreadLocalCache_Racy();

    // Cache management methods
    static void ClearThreadLocalCache();
    static size_t GetThreadLocalCacheSize();
};

using APDCache = shared_ptr<ActionPointDistancesCache>;

}  // namespace shardok

#endif  // EAGLE0_ACTIONPOINTDISTANCESCACHE_HPP
