using Theriapolis.Core.World.Generation;

namespace Theriapolis.Tests;

/// <summary>
/// Class-level fixture that memoizes worldgen pipeline runs so multiple tests
/// in the same class (all hitting e.g. seed 0xCAFEBABE) share one expensive run.
///
/// Each test class gets its own WorldCache via <see cref="Xunit.IClassFixture{T}"/>,
/// which keeps cross-class parallelism intact while collapsing within-class duplicate
/// runs. A full pipeline takes ~30s; a test class that previously did 9 runs now does 1.
///
/// Cache key is (seed, stageThroughIndex, variant):
///  - stageThroughIndex = -1 means RunAll; otherwise RunThrough(ctx, idx).
///  - variant is used by determinism tests that intentionally want TWO independent
///    runs of the same seed to compare: pass variant 0 and variant 1.
/// </summary>
public sealed class WorldCache : IDisposable
{
    private readonly Dictionary<(ulong Seed, int Stage, int Variant), WorldGenContext> _cache = new();
    private readonly object _lock = new();

    /// <summary>Full pipeline run for <paramref name="seed"/>, memoized.</summary>
    public WorldGenContext Get(ulong seed, int variant = 0) =>
        GetInternal(seed, stageThroughIndex: -1, variant);

    /// <summary>
    /// Partial pipeline run through stage index (0-based), memoized.
    /// E.g. <c>GetThrough(seed, 9)</c> runs stages 1–10 (HydrologyGen).
    /// </summary>
    public WorldGenContext GetThrough(ulong seed, int stageThroughIndex, int variant = 0) =>
        GetInternal(seed, stageThroughIndex, variant);

    private WorldGenContext GetInternal(ulong seed, int stageThroughIndex, int variant)
    {
        var key = (seed, stageThroughIndex, variant);
        lock (_lock)
        {
            if (_cache.TryGetValue(key, out var cached)) return cached;

            var ctx = new WorldGenContext(seed, TestHelpers.DataDirectory);
            if (stageThroughIndex < 0)
                WorldGenerator.RunAll(ctx);
            else
                WorldGenerator.RunThrough(ctx, stageThroughIndex);

            _cache[key] = ctx;
            return ctx;
        }
    }

    public void Dispose() => _cache.Clear();
}
