#pragma once

#if defined( __EMSCRIPTEN__ ) || defined( __ANDROID__ )
#include <GLES3/gl3.h>
#else
#include <glad/glad.h>
#endif

#include <entt/entt.hpp>
#include <SDL3/SDL_events.h>

#include "crArray.h"
#include "crEcsComponents.h"
#include "crRandom.h"

class crApp;

// exactly the fields the contact loop reads off a NEIGHBOUR, and nothing else — 16 bytes, four to a
// cache line. kept apart from prev/vel/delta/spin on purpose: those are never touched while walking
// neighbours, and letting them share the line would evict what is
struct CrowdHot
{
    float2 pos     = {};
    float  radius  = 0.0f;
    float  invMass = 0.0f;
};

enum class EGameState : uint8_t
{
    TITLE = 0,
    PLAYING,
    PAUSED,
    RESULT,
};

enum class ETowerKind : uint8_t
{
    TURRET = 0,   // burst-fire hitscan + knockback
    MORTAR,       // lobbed shell, AoE damage + radial shove
    LASER,        // beam that ricochets off canyon walls, damaging along its path
    TESLA,        // lightning that arcs from body to body — denser crowd, longer chain
    FROST,        // no damage: an aura that halves speed, which locally multiplies density

    _SIZE
};

struct PresetTower
{
    ETowerKind kind;
    int32_t    x;
    int32_t    y;
};

enum class EEnemyKind : uint8_t
{
    GRUNT = 0,
    BRUTE,
};

constexpr int32_t LASER_SEGMENTS = 3;   // beam origin + up to 2 wall bounces
constexpr int32_t TOWER_BEAMS    = 8;   // slot pool — the laser uses 3, a tesla chain uses all of them

// tag — a swarm walker: remaining hits, cruise speed, and hit-flash state.
// the moving parts live in the crowd solver's SoA arrays; `slot` is the link
struct CEnemy
{
    int32_t    slot     = -1;
    int32_t    hp       = 1;
    float      speed    = 2.5f;   // base — the gate-distance taper scales it down en route
    float      radius   = 0.34f;
    float      laneBias = 0.0f;   // [-1,1] preferred side of the lane — spreads the column into a band
    float      flash    = 0.0f;   // 1 on hit, decays — blends the primitive toward white
    uint64_t   slowUntilStep = 0; // frost aura; refreshed while inside, so it lapses on its own outside
    uint8_t    frosted  = 0;      // which tint the primitive currently carries — repaint only on a change
    color4     baseColor;
    EEnemyKind kind = EEnemyKind::GRUNT;
};

// a placed tower — towers stand on the plateau layer the swarm can never reach, so they have no
// physics body and no hp; this is game data, not an ECS component
struct Tower
{
    ETowerKind   kind = ETowerKind::TURRET;
    int2         cell = {};
    float3       pos  = {};              // head center, above the plateau top
    uint64_t     nextFireStep = 0;
    uint64_t     nextTickStep = 0;       // LASER/TESLA: damage tick / burst-round clock
    uint64_t     tracerStep = 0;         // TURRET/TESLA: when the current flash was fired — both fade off it
    float3       boltFrom = {};          // TURRET: the bolt flies this segment over TURRET_BOLT_STEPS
    float3       boltTo   = {};
    int32_t      burstLeft = 0;          // TURRET: rounds remaining in the current burst
    float2       aimDir = {};            // LASER: current beam direction (zero = idle)
    entt::entity target = entt::null;    // TURRET: held for the length of one burst
    entt::entity base = entt::null;
    entt::entity head = entt::null;
    entt::entity aura  = entt::null;     // FROST: floor ring, pulsed on the sim clock
    entt::entity patch = entt::null;     // FROST: procedural rime quad under the ring
    entt::entity beams[ TOWER_BEAMS ] = { entt::null, entt::null, entt::null, entt::null,
                                          entt::null, entt::null, entt::null, entt::null };
};

// a mortar shell in flight — parametric arc on the sim clock, detonates at impactStep
struct PendingShell
{
    float3       from = {};
    float3       to   = {};
    uint64_t     impactStep = 0;
    entt::entity shell  = entt::null;
    entt::entity marker = entt::null;
};

// a transient sim-animated visual (mortar shockwave hoop) — scales up and fades, then dies
struct FxRing
{
    uint64_t     bornStep = 0;
    entt::entity ring     = entt::null;
};

// sim-step cost breakdown. the framework profiler measures whole frames; which PART of a step is
// expensive is the game's own question, so the game answers it
enum class EProfSection : uint8_t
{
    COMMANDS = 0,   // build queue + economy
    SPAWNER,
    REFLOW,         // density bin + eikonal solve + gradient (periodic, so also tracked unamortized)
    STEER_PAR,      // the worker fan-out
    STEER_APPLY,    // the serial leak teardown
    CROWD_GRID,     // the counting sort that rebuilds the uniform grid + packs the sorted mirror
    CROWD_RELAX,    // contact accumulation, summed over every relax iteration
    CROWD_APPLY,    // delta application + terrain push-out, likewise summed
    CROWD_FINISH,   // derive velocity, publish CTransform
    TOWERS,
    SHELLS_FX,

    _SIZE
};

// a build order latched by the UI on the render clock — the sim validates and executes it in a
// fixed step, which keeps every registry create on the sim clock (see CLAUDE.determinism)
struct PendingBuild
{
    ETowerKind kind = ETowerKind::TURRET;
    int2       cell = {};
};

// the game module — the framework's single entry point into src/game/ (bound through Game.h).
//
// MassEntityDefense — canyon tower defense resolved by physical mass. FastNoiseLite carves a
// two-level map: the swarm floods the canyon floor along a static flow field while towers stand on
// the plateaus, so building never reroutes pathing. every enemy is a rigid body — the shoving,
// choke-point jams, and junction overflow ARE the game. defend the gate through 8 waves
class Game
{
private:
    static constexpr int32_t MAP_W      = 192;
    static constexpr int32_t MAP_H      = 108;
    static constexpr int32_t CELL_COUNT = MAP_W * MAP_H;

    static constexpr int32_t MAX_SPAWNS = 8;
    static constexpr int32_t WAVE_COUNT = 8;
    static constexpr int32_t STAIN_POOL = 512;   // gore ring buffer — fixed entity count, oldest slot reused

    crApp* _app = nullptr;

    EGameState _state          = EGameState::TITLE;
    EGameState _requestedState = EGameState::TITLE;   // latched — applied between sim steps (Update)

    crRandom _rng;   // session stream — consumed only inside fixed steps

    int32_t _mapSeed = 0;      // set to MAP_SEED in Init; console `seed` overrides for shape hunting
    bool    _reseed  = false;  // latched by CmdSeed — Update re-enters the session
    bool    _usePcg  = false;  // `seed` switches the session off the pinned map and back to generating

    // preset scenario: towers placed at entry, then the sim is wound forward to a fixed step so the
    // same battle is always inspected from the same moment. 0 = not winding
    bool     _pendingPreset   = false;
    uint64_t _prepareUntilStep = 0;

    // the map: two heights only. plateau (_high) is buildable, floor is walkable — disjoint layers
    uint8_t _high[ CELL_COUNT ]    = {};
    uint8_t _chamfer[ CELL_COUNT ] = {};   // 0 = none; 1..4 = which corner was cut (NE/NW/SW/SE)

    // "is anything solid within this cell's 3x3" — the exact question PushOutOfTerrain asks, but the
    // terrain is fixed for the session, so it is answered once and read back as a single byte
    uint8_t _nearSolid[ CELL_COUNT ] = {};

    // travel TIME to the gate, from an eikonal solve — a true euclidean field, so its gradient is a
    // continuous direction instead of one of eight. crowding raises the local slowness, which is
    // what makes jammed lanes get routed around
    float   _flowDist[ CELL_COUNT ] = {};
    float2  _flowDir[ CELL_COUNT ]  = {};   // -normalize(grad flowDist); zero where unreachable
    float   _density[ CELL_COUNT ]  = {};   // decayed occupancy per cell — feeds the slowness

    int16_t _towerAt[ CELL_COUNT ]  = {};   // index into _towers; -1 = empty

    uint64_t _nextReflowStep = 0;

    // generation scratch — map-build passes only (flood fills, BFS queue, smoothing temp).
    // members rather than locals: WASM stacks are small
    int32_t _workQueue[ CELL_COUNT ] = {};
    int32_t _workLabel[ CELL_COUNT ] = {};
    float2  _workDir[ CELL_COUNT ]   = {};

    int2   _gateCell = {};
    float3 _gatePos  = {};
    int2   _spawnCells[ MAX_SPAWNS ] = {};
    int32_t _spawnCount = 0;
    int32_t _spawnRR    = 0;   // round-robin cursor over spawn points

    // ── crowd solver ─────────────────────────────────────────────────────────
    // box3d used to own the swarm. a general rigid-body engine bills for quaternions, manifolds,
    // islands and substeps on a workload whose entire contact model is "two circles overlap, push
    // them apart" — this does only that. positions are relaxed Jacobi-style, so the result is
    // independent of both iteration order and thread scheduling, and the uniform grid it sorts into
    // doubles as the acceleration structure every weapon queries
    static constexpr int32_t MAX_AGENTS = 65536;
    static constexpr int32_t QUERY_MAX  = 4096;

    int32_t      _agentCount = 0;
    CrowdHot     _hot        [ MAX_AGENTS ] = {};   // pos + radius + invMass — the only thing RELAX reads
    float2       _agentPrev  [ MAX_AGENTS ] = {};   // position at the top of the step — velocity is derived against it
    float2       _agentVel   [ MAX_AGENTS ] = {};
    float3       _agentSpin  [ MAX_AGENTS ] = {};   // cosmetic tumble — nothing reads it back
    uint8_t      _agentFlags [ MAX_AGENTS ] = {};   // 0 = live, 1 = reached the gate
    entt::entity _agentOwner [ MAX_AGENTS ] = {};

    int32_t _gridStart [ CELL_COUNT + 1 ] = {};   // prefix-summed cell ranges into _gridAgents
    int32_t _gridAgents[ MAX_AGENTS ]     = {};

    // the same hot data, reordered so that agents in one cell are ADJACENT in memory. the grid tells
    // us who the neighbours are, but their slot numbers are arbitrary, so walking them off _hot
    // fetches a fresh cache line every time. packing a sorted mirror once per step turns that into
    // four neighbours per line. RELAX therefore runs in grid order and APPLY scatters back
    CrowdHot _hotSorted  [ MAX_AGENTS ] = {};
    float2   _deltaSorted[ MAX_AGENTS ] = {};
    int32_t _queryHits [ QUERY_MAX ]      = {};   // CrowdQuery output — sim-serial, so one buffer is enough
    // slots go stale the instant anything dies (a kill swap-removes one), so a caller that damages
    // what it found resolves the slots to entities FIRST and works from these
    entt::entity _queryOwners[ QUERY_MAX ] = {};

    // how many neighbours the contact loop LOOKS at, versus how many actually touch. this is the
    // number that decides where the remaining cost is, and guessing it from a density eyeball has
    // now been wrong three times. per worker, so no atomics — the sum is order-independent anyway
    static constexpr int32_t MAX_WORK_THREADS = 32;   // bound on enki's threadIndex; the real pool is far smaller
    uint32_t _relaxCandidates[ MAX_WORK_THREADS ] = {};
    uint32_t _relaxContacts  [ MAX_WORK_THREADS ] = {};
    float    _avgCandidates = 0.0f;   // per agent per relax iteration, smoothed
    float    _avgContacts   = 0.0f;

    crArray<Tower>        _towers;
    crArray<PendingShell> _shells;
    crArray<FxRing>       _fxRings;
    crArray<PendingBuild> _pendingBuilds;
    int32_t               _pendingCheatGold = 0;   // console `gold` — drained on the sim clock

    int32_t  _gold   = 0;
    int32_t  _gateHp = 0;
    uint64_t _nextTrickleStep = 0;

    int32_t  _wave       = -1;      // index of the running/last wave; -1 = none yet
    bool     _waveActive = false;
    uint64_t _nextWaveStep = 0;     // while inactive: when the next wave starts
    int32_t  _gruntsLeft   = 0;
    int32_t  _brutesLeft   = 0;
    float    _gruntRate    = 0.0f;  // spawns per step — continuous stream, no burst rhythm
    float    _bruteRate    = 0.0f;
    float    _gruntAcc     = 0.0f;  // fractional emission accumulators
    float    _bruteAcc     = 0.0f;

    // per-portal spawn queues — bursts land here, bodies trickle out a few per step. materializing
    // a whole burst in one step interpenetrates the pile and blows the solver's step cost up
    int32_t _gruntPending[ MAX_SPAWNS ] = {};
    int32_t _brutePending[ MAX_SPAWNS ] = {};

    int32_t _kills = 0;
    int32_t _leaks = 0;
    int32_t _built = 0;
    bool    _victory = false;

    // build placement — render-side selection; only the queued PendingBuild enters the sim
    int32_t      _buildKind = -1;   // ETowerKind, -1 = none
    entt::entity _ghostBox  = entt::null;
    entt::entity _ghostRing = entt::null;
    int2         _lastPaintCell = { -1, -1 };   // drag-paint dedupe — reset on every press

    // camera rig state — render-side (pan: WASD stick + MMB drag, zoom: wheel)
    float2 _camTarget = {};
    float  _camDist   = 100.0f;

    entt::entity _stains[ STAIN_POOL ] = {};   // set to entt::null in EnterSession/ExitSession
    int32_t      _stainNext = 0;

    GLuint  _frostProgram  = 0;   // primitive_mass_frost — procedural rime; game-owned, deleted in Cleanup
    uint8_t _frostMaterial = 0;

    int32_t _uiButtonSprite = -1;
    int32_t _uiDimSprite    = -1;
    int32_t _fxSprite       = -1;   // circle32 — soft round
    int32_t _fxSquare       = -1;   // sq16 — hard shard

    int32_t _sfxImpact = -1;

    int32_t _font16 = -1;
    int32_t _font12 = -1;

    // profiling — read-only instrumentation. it must never reach a branch or an rng draw, or the
    // sim would depend on wall-clock timing and stop being reproducible
    bool     _profile = true;
    double   _profMsPerTick = 0.0;   // cached in Init — SDL_GetPerformanceFrequency per call is waste
    uint64_t _profStart[ static_cast<int32_t>( EProfSection::_SIZE ) ] = {};
    float    _profStep[ static_cast<int32_t>( EProfSection::_SIZE ) ]  = {};   // accumulated within the current step
    float    _profEma[ static_cast<int32_t>( EProfSection::_SIZE ) ]   = {};   // smoothed per-step cost
    float    _profLast[ static_cast<int32_t>( EProfSection::_SIZE ) ]  = {};   // last non-zero occurrence — the spike, not the average
    uint64_t _profSpanStart = 0;
    float    _profSpanEma   = 0.0f;   // FixedUpdatePre entry -> FixedUpdatePost exit = game + physics + ecs sync

public:
    void Init( crApp* app );
    void Cleanup();

    void Update( crApp* app );                  // render frame, after the sim steps — state transitions apply here
    void FixedUpdatePre( const crApp* app );    // sim step — commands, economy, spawner, steering, towers, shells
    void FixedUpdatePost( const crApp* app );
    void HandleEvent( const crApp* app, const SDL_Event* event );
    void Render( const crApp* app );
    void RenderUi( crApp* app );
    void RenderDevUi( const crApp* app );

private:
    void RequestState( EGameState state );
    void EnterSession( crApp* app );
    void ExitSession( crApp* app );
    void StartPreset( crApp* app );   // place the baked towers, then hand the wind-up to Update

    static void CmdTitle( void* context, int32_t argc, const char** argv );
    static void CmdPlay( void* context, int32_t argc, const char** argv );
    static void CmdGold( void* context, int32_t argc, const char** argv );
    static void CmdSeed( void* context, int32_t argc, const char** argv );
    static void CmdDumpMap( void* context, int32_t argc, const char** argv );
    static void CmdDumpTowers( void* context, int32_t argc, const char** argv );

    void DumpMap();      // current terrain -> clipboard, as a paste-ready FIXED_MAP block
    void DumpTowers();   // current towers  -> clipboard, as a paste-ready PRESET_TOWERS block

    void GenerateMap();
    bool LoadFixedMap();   // false = no pinned map (or it does not fit) — the caller generates instead
    void ChamferPass();     // shared tail of both paths: mark staircase corners and open those cells
    void BuildNearSolid();  // derived from the finished terrain — must run after the last edit to it
    void CarveDisk( float2 cellPos, float radius );   // cell-space stamp; the rim stays intact
    void FillDisk( float2 cellPos, float radius );    // inverse stamp — obstacle pillars
    int32_t CarvePath( crRandom* rng, float2 from, float2 to, float wanderMax, int32_t sampleBase );   // wandering tunnel; samples land in _workDir, returns the count
    void CarvePocket( int2 center );                  // 5x5 clear with rim guard

    void   BuildFlowField( bool withDensity );        // eikonal solve + gradient; density off for the first build (no crowd yet)
    void   UpdateDensityField( const crApp* app );    // bin the swarm into cells, decayed
    float2 SampleFlow( float wx, float wy ) const;    // bilinear — neighbors in the same cell must not share one quantized vector
    void SpawnTerrain( const crApp* app );      // cliff bodies + floor / gate / portal visuals
    void SpawnGhost( const crApp* app );
    void SpawnStainPool( const crApp* app );

    void SpawnEnemy( const crApp* app, EEnemyKind kind, float2 pos );
    void BuildTower( const crApp* app, ETowerKind kind, int2 cell, bool announce = true );   // announce: puff + sfx, off for bulk preset placement
    void PlaceStain( const crApp* app, float3 at, bool brute );   // rotate the gore ring buffer onto this spot

    void UpdateCommands( const crApp* app );    // drain PendingBuild queue + cheat gold
    void UpdateEconomy( const crApp* app );
    void UpdateSpawner( const crApp* app );
    bool PortalCrowded( const crApp* app, float3 at );   // pocket occupancy gate — a jammed portal stops pouring
    void UpdateEnemies( const crApp* app );     // parallel steering -> serial leak teardown
    void CrowdStep( const crApp* app );         // integrate -> relax -> terrain -> publish

    // worker-thread kernels. each writes ONLY to the slots its own slice owns, so the result does
    // not depend on how the pool split the work. the statics are the crParallelForFn entry points —
    // they unpack `this` and forward, nothing more
    void SteerRange( int32_t begin, int32_t end );
    void IntegrateRange( int32_t begin, int32_t end );
    void RelaxRange( int32_t begin, int32_t end, uint32_t threadIndex );
    void ApplyRange( int32_t begin, int32_t end );
    void FinishRange( int32_t begin, int32_t end );

    static void SteerTask( int32_t begin, int32_t end, uint32_t threadIndex, void* context );
    static void IntegrateTask( int32_t begin, int32_t end, uint32_t threadIndex, void* context );
    static void RelaxTask( int32_t begin, int32_t end, uint32_t threadIndex, void* context );
    static void ApplyTask( int32_t begin, int32_t end, uint32_t threadIndex, void* context );
    static void FinishTask( int32_t begin, int32_t end, uint32_t threadIndex, void* context );

    int32_t CrowdAlloc( entt::entity owner, float2 pos, float radius, float3 spin );
    void    CrowdFree( int32_t slot );
    void    CrowdBuildGrid();
    int32_t CrowdQuery( float2 center, float radius );   // -> _queryHits, returns the count (capped at QUERY_MAX)
    float2  PushOutOfTerrain( float2 pos, float radius ) const;

    // grid DDA against the plateau mask — what the laser bounces off now that there are no shapes
    bool RaycastTerrain( float2 from, float2 dir, float maxDist, float2* hitPos, float2* hitNormal ) const;
    void UpdateTowers( const crApp* app );
    void UpdateShells( const crApp* app );
    void CheckEndConditions( const crApp* app );

    void FireTurret( const crApp* app, Tower* tower );    // burst scheduler + per-round hitscan
    void FireMortar( const crApp* app, Tower* tower );
    void UpdateLaser( const crApp* app, Tower* tower );
    void FireTesla( const crApp* app, Tower* tower );
    void UpdateFrost( const crApp* app, Tower* tower );
    void UpdateFxRings( const crApp* app );
    // a soft round core flash. shards alone are sub-pixel at overview zoom — every impact needs one
    void EmitFlash( const crApp* app, float3 at, color4 color, float size, int32_t count );
    // stretch one beam entity over a segment. 3D on purpose: a muzzle sits on the plateau and its
    // target on the canyon floor, so a flat beam would start inside the hill and get clipped by it
    void SetBeam( const crApp* app, entt::entity beam, float3 a, float3 b, float halfWidth );
    void HideBeams( const crApp* app, Tower* tower, int32_t fromIndex );

    // nearest-to-core targeting for free: priority = the flow field's BFS distance at the enemy's
    // cell, candidates from the physics broadphase — no per-entity euclidean scan
    entt::entity FindTarget( const crApp* app, float2 from, float range, float minRange );

    bool DamageEnemy( const crApp* app, entt::entity e, int32_t damage );   // flash + hp; true = killed
    void DestroyEnemy( const crApp* app, entt::entity e, bool reward );     // burst, gold/kills, body + entity teardown
    void DamageGate( const crApp* app, int32_t amount );

    bool ValidBuildCell( int2 cell ) const;
    bool PickBuildCell( const crApp* app, float2 posPx, int2* cell ) const;   // mouse px -> plateau-top plane -> cell

    void UpdateGhost( crApp* app );     // render-side placement preview
    void UpdateCamera( crApp* app );

    void ProfileBegin( EProfSection section );
    void ProfileEnd( EProfSection section );
    void ProfileStepEnd();

    static int32_t CellIndex( int32_t x, int32_t y )
    {
        return ( y * MAP_W ) + x;
    }
    static float3 CellCenter( int2 cell );
    static int2   WorldToCell( float wx, float wy );   // clamped to the grid
};
