namespace Theriapolis.Core.Tactical;

/// <summary>
/// One streamed chunk of the tactical world. Always
/// <see cref="C.TACTICAL_CHUNK_SIZE"/>² tactical tiles.
///
/// Chunks are produced by <see cref="TacticalChunkGen"/> from the deterministic
/// inputs (worldSeed, ChunkCoord, WorldState) plus an optional delta overlay
/// from the save layer. The chunk itself does not know whether deltas have
/// been applied — that's <see cref="ChunkStreamer"/>'s job.
/// </summary>
public sealed class TacticalChunk
{
    public ChunkCoord Coord { get; }

    /// <summary>Indexed [tx, ty] in chunk-local coordinates (0..CHUNK_SIZE-1).</summary>
    public TacticalTile[,] Tiles { get; } = new TacticalTile[C.TACTICAL_CHUNK_SIZE, C.TACTICAL_CHUNK_SIZE];

    /// <summary>Phase-4 spawn list. Stored, not yet acted on (Phase 5 reads it).</summary>
    public List<TacticalSpawn> Spawns { get; } = new();

    /// <summary>
    /// Phase 5 M5 difficulty tier (0..C.DANGER_ZONE_MAX). Drives which template
    /// each <see cref="TacticalSpawn.Kind"/> instantiates as a live NPC. Set
    /// in <see cref="TacticalChunkGen"/>'s spawn pass; folded into the hash
    /// so determinism tests catch zone-formula drift.
    /// </summary>
    public byte DangerZone { get; set; }

    /// <summary>Set true by ChunkStreamer when a delta is applied; flushed back on eviction.</summary>
    public bool Dirty { get; set; }

    /// <summary>True if any field has been modified relative to the deterministic baseline.</summary>
    public bool HasDelta { get; set; }

    public TacticalChunk(ChunkCoord coord) { Coord = coord; }

    /// <summary>Top-left tactical tile coordinate of this chunk in world-pixel space.</summary>
    public int OriginX => Coord.X * C.TACTICAL_CHUNK_SIZE;
    public int OriginY => Coord.Y * C.TACTICAL_CHUNK_SIZE;

    /// <summary>Returns the tile at chunk-local (lx, ly) — bounds-checked.</summary>
    public ref TacticalTile TileAt(int lx, int ly)
    {
        if ((uint)lx >= C.TACTICAL_CHUNK_SIZE || (uint)ly >= C.TACTICAL_CHUNK_SIZE)
            throw new ArgumentOutOfRangeException($"({lx},{ly}) outside chunk");
        return ref Tiles[lx, ly];
    }

    /// <summary>FNV-1a hash over every tile. Used by determinism tests.</summary>
    public ulong Hash()
    {
        const ulong FNV_PRIME  = 1099511628211UL;
        const ulong FNV_OFFSET = 14695981039346656037UL;
        ulong h = FNV_OFFSET;
        for (int y = 0; y < C.TACTICAL_CHUNK_SIZE; y++)
        for (int x = 0; x < C.TACTICAL_CHUNK_SIZE; x++)
        {
            ref var t = ref Tiles[x, y];
            h = (h ^ (byte)t.Surface) * FNV_PRIME;
            h = (h ^ (byte)t.Deco)    * FNV_PRIME;
            h = (h ^ t.Variant)       * FNV_PRIME;
            h = (h ^ t.Flags)         * FNV_PRIME;
        }
        // Fold spawn list into the hash so any non-determinism there shows up.
        foreach (var s in Spawns)
        {
            h = (h ^ (byte)s.Kind) * FNV_PRIME;
            h = (h ^ (uint)s.LocalX) * FNV_PRIME;
            h = (h ^ (uint)s.LocalY) * FNV_PRIME;
        }
        // Fold the DangerZone in so changes to the zone formula register as
        // a chunk-hash change in the determinism tests.
        h = (h ^ DangerZone) * FNV_PRIME;
        return h;
    }
}

public enum SpawnKind : byte
{
    None = 0,
    WildAnimal,
    Brigand,
    Merchant,
    Patrol,
    PoiGuard,
    /// <summary>
    /// Phase 6 M0 — emitted by <see cref="World.Settlements.SettlementStamper"/>
    /// for each occupied building role. Phase 6 M1 instantiates these as
    /// friendly <see cref="Entities.NpcActor"/>s with role-specific dialogue.
    /// </summary>
    Resident = 16,
}

/// <summary>Single spawn record in chunk-local coordinates. Phase 5 acts on these.</summary>
public readonly struct TacticalSpawn
{
    public readonly SpawnKind Kind;
    public readonly int       LocalX;
    public readonly int       LocalY;
    public TacticalSpawn(SpawnKind kind, int lx, int ly) { Kind = kind; LocalX = lx; LocalY = ly; }
}
