#pragma once

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

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

class crApp;

enum class EFaction : uint8_t
{
    BLUE = 0,
    RED,

    _SIZE
};

enum class EHull : uint8_t
{
    INTERCEPTOR = 0,
    LANCER,
    BULWARK,

    _SIZE
};

enum class EPosture : uint8_t
{
    PUSH = 0,
    HOLD,
    REGROUP,
};

enum class EMatchState : uint8_t
{
    BATTLE = 0,
    RESOLVE,
};

struct CFighter
{
    color4       baseColor;
    entt::entity target       = entt::null;
    uint64_t     nextFireStep = 0;
    uint64_t     retargetStep = 0;
    float3       orbitAxis    = { 0.0f, 1.0f, 0.0f };
    float        hp           = 1.0f;
    float        flash        = 0.0f;   // hit whiten; set by the sim, decayed render-side
    EFaction     faction      = EFaction::BLUE;
    EHull        hull         = EHull::INTERCEPTOR;
};

struct CShot
{
    uint64_t expireStep = 0;
    float    damage     = 1.0f;
    EFaction faction    = EFaction::BLUE;
};

struct BlastRing
{
    entt::entity entity   = entt::null;
    color4       color;
    float3       pos      = {};
    float        age      = 0.0f;
    float        life     = 0.0f;   // 0 = the slot is free
    float        radius   = 0.0f;
    float        grow     = 0.1f;   // seconds to reach full radius
    float        emissive = 0.0f;
    EPrimitive   shape    = EPrimitive::RING_THIN;
};

struct Core
{
    entt::entity hull      = entt::null;
    entt::entity shield    = entt::null;
    entt::entity halo[ 2 ] = { entt::null, entt::null };   // crossed, so one is always presenting its face
    float3       pos       = {};
    float        hp        = 0.0f;
    float        hpMax     = 1.0f;
    float        flash     = 0.0f;
};

// the AI player. two of these face off; everything above the live-state break is doctrine, rolled
// fresh per match, and it is the only reason the two sides ever behave differently
struct Commander
{
    float aggression  = 0.5f;    // 0..1 — how readily it commits to a push
    float focusFire   = 0.5f;    // 0..1 — bias toward already-wounded targets
    float engageRange = 18.0f;   // how far a fighter breaks formation to chase
    float standoff    = 0.6f;    // 0..1 — fraction of weapon range it prefers to hold
    float intelBias   = 1.0f;    // scales its estimate of the enemy: < 1 underestimates, > 1 overestimates
    int32_t hullWeight[ static_cast<int32_t>( EHull::_SIZE ) ] = {};

    EHull backbone = EHull::INTERCEPTOR;

    int32_t  pool           = 0;   // reinforcements left this match
    int32_t  deployTarget   = 0;   // live fighters it wants on the field right now
    int32_t  live           = 0;
    int32_t  losses         = 0;
    int32_t  kills          = 0;
    float    pushLineX      = 0.0f;   // x a fighter advances to when it has no target
    EPosture posture         = EPosture::HOLD;
    uint64_t nextDeployStep  = 0;
    uint64_t nextThinkStep   = 0;
    uint64_t postureLockStep = 0;

    int32_t  ultimateNerve     = 0;   // enemy hulls that must share one footprint before it will fire
    int32_t  ultimateCharges   = 0;
    int32_t  ultimateGranted   = 0;   // what it started with — the scoreboard draws this many pips
    uint64_t ultimateReadyStep = 0;
    int32_t  cascadeLeft = 0;    // blasts still to fall in the walking chain (0 = not running)
    uint64_t cascadeStep = 0;
    float    cascadeT    = 0.0f; // 0..1 along the walk
    float3   cascadeFrom = {};
    float3   cascadeTo   = {};
};

// live-tunable knobs — edited from the dev panel (RenderDevUi)
struct Tuning
{
    float   simSpeed        = 1.00f;   // scales how OFTEN a step runs, never what one contains

    int32_t poolPerSide     = 800;     // the match's whole attrition budget per side; this and hull hp set match length
    int32_t deployStart     = 90;      // simultaneous fighters at match start
    int32_t deployMax       = 240;     // ...and at full escalation
    float   rampSeconds     = 150.0f;
    int32_t squadSize       = 6;       // hulls per launch, flown out as a wedge
    float   deployInterval  = 0.45f;
    float   thinkInterval   = 1.00f;
    float   pushCommit      = 3.00f;   // PUSH alone locks the posture this long; the other two stay re-decidable
    float   retargetInterval = 0.55f;
    float   suddenDeath     = 150.0f;  // seconds — past this both cores bleed, so no match can hang
    float   suddenRate      = 12.0f;   // core hp per second at onset, growing from there
    float   resolveSeconds  = 5.0f;

    float   coreHp          = 300.0f;
    float   damageScale     = 1.0f;

    // RED spends its charge all at once, BLUE over a walking chain whose numbers are these halved
    float   ultArmSeconds   = 15.0f;
    float   ultCooldown     = 10.0f;
    float   ultSloMo        = 0.55f;   // fraction of sim speed dropped on the firing beat (0 = off)
    float   ultRadius       = 22.0f;
    float   ultDamage       = 14.0f;
    float   ultKick         = 30.0f;   // m/s at the centre; past ~38 a hull reaches the arena rim
    int32_t ultChainCount   = 6;
    float   ultChainPeriod  = 0.45f;

    float   camTravelRate   = 0.070f;  // rad/s along the great circle (~90 s per circuit)
    float   camPrecessRate  = 0.010f;  // rad/s of that circle's plane (~10 min)
    float   camShake        = 1.0f;    // 0 disables the impact shake outright
    float   camFollow       = 1.8f;
    float   camDistMin      = 20.0f;
    float   camDistMax      = 120.0f;
    float   camMargin       = 0.95f;   // slack around the RMS spread; under 1 crops the stragglers
};

// dev placeholder — stands in for the parked game while it's out of the build (see Game.h).
//
// SKIRMISH — two AI commanders fight an endless war with no player in it. each side deploys from a
// finite reinforcement pool into a shared spherical arena; that pool is the match's entire attrition
// budget, so every match resolves on its own and the next one rolls fresh doctrines. hundreds of
// live rigid bodies, shots, trails and bursts keep the engine's dev panels loaded.
//
// framework scaffolding, not part of the battle: it configures the input slots, logs button edges
// and draws the live touch/stick readout over whatever the game is doing. kept a separate type so
// the game stays readable — nothing in SKIRMISH reaches into it and it reaches into nothing
struct InputProbe
{
    // crInputSystem slots; what they mean is ours to decide. two shapes so the round hit test and
    // the rect one can be told apart by hand
    static constexpr int32_t ROUND_SLOT = 0;   // Z
    static constexpr int32_t RECT_SLOT  = 1;   // X

    void Configure( const crApp* app, int32_t font, int32_t panelSprite );
    void FixedStep( const crApp* app );   // button edges — must land on exactly one sim step
    void Render( crApp* app );            // touch slot readout — the cancel path can only be checked by hand

private:
    int32_t _font        = -1;
    int32_t _panelSprite = -1;
    int32_t _clicks[ 2 ] = {};   // proves the unified pointer still reaches the UI — round face, then rect
    int32_t _presses[ 2 ] = {};   // proves a button edge fires once per press, not per render frame
};

// the input probe (touch slots, sticks, button edges) rides along at the bottom of Game_Dev.cpp —
// framework test scaffolding layered over the battle, deliberately not part of it
class Game
{
private:
    // filtering carries the faction, so friendly fire is impossible by construction — there is no
    // shooter-identity test anywhere in the hit path
    static constexpr uint64_t CAT_SHIP_BLUE = 1 << 1;
    static constexpr uint64_t CAT_SHIP_RED  = 1 << 2;
    static constexpr uint64_t CAT_SHOT_BLUE = 1 << 3;
    static constexpr uint64_t CAT_SHOT_RED  = 1 << 4;
    static constexpr uint64_t CAT_CORE_BLUE = 1 << 5;
    static constexpr uint64_t CAT_CORE_RED  = 1 << 6;

    // box3d has no inside-out sphere collider, so the boundary is a restoring PD rather than a body.
    // GAIN * SOFT_MARGIN must out-pull a hull steering flat-out at the rim, whose worst case is
    // accel * 2 * speed = 7.8 * 2 * 16.9 = 264 — raising any hull's speed or accel means redoing this
    static constexpr float ARENA_RADIUS = 44.0f;
    static constexpr float SOFT_MARGIN  = 4.0f;
    static constexpr float CONTAIN_GAIN = 90.0f;
    static constexpr float CONTAIN_DAMP = 19.0f;   // ~2*sqrt(GAIN), so the rim absorbs instead of bouncing
    static constexpr float CORE_X       = 32.0f;
    static constexpr float CORE_RADIUS  = 3.2f;

    // hulls within this of their OWN core are still in the hangar. the ultimate's aim scan skips them,
    // because a launch cluster is otherwise the densest knot on the field and it would be aimed at
    static constexpr float ULT_HOME_GUARD = 22.0f;

    static constexpr int32_t FACTION_COUNT = static_cast<int32_t>( EFaction::_SIZE );
    static constexpr int32_t HULL_COUNT    = static_cast<int32_t>( EHull::_SIZE );

    static constexpr int32_t STAR_COUNT      = 190;
    static constexpr int32_t RING_COUNT      = 3;
    static constexpr int32_t BLAST_RING_POOL = 28;   // three slots per blast — core flash, front, echo
    static constexpr int32_t ULT_MAX_CHARGES = 3;

    crApp*   _app = nullptr;
    crRandom _rng;        // sim stream — consumed only inside fixed steps
    crRandom _decorRng;   // cosmetic stream — starfield layout only, never touched by the sim

    Tuning _tune;

    EMatchState _matchState     = EMatchState::BATTLE;
    int32_t     _matchIndex     = 0;
    uint64_t    _matchStartStep = 0;
    uint64_t    _resolveStep    = 0;
    EFaction    _winner         = EFaction::BLUE;
    int32_t     _wins[ FACTION_COUNT ]       = {};
    int32_t     _lossStreak[ FACTION_COUNT ] = {};   // buys extra ultimate charges

    bool _hashStampPending = false;

    Core      _cores[ FACTION_COUNT ];
    Commander _commanders[ FACTION_COUNT ];

    crArray<entt::entity> _fighters[ FACTION_COUNT ];
    crArray<entt::entity> _shots;
    crArray<entt::entity> _dead;   // scratch for deferred teardown — reused every step

    entt::entity _boundaryRings[ RING_COUNT ] = {};
    float3       _ringAxis[ RING_COUNT ]      = {};   // angular velocity (axis * rate)
    entt::entity _glowRing                    = entt::null;

    BlastRing _blastRings[ BLAST_RING_POOL ];

    // render-side presentation state — the sim never reads any of it
    float  _renderDt   = 0.0f;
    float  _camTime    = 0.0f;
    float3 _camTarget  = {};
    float  _camDist    = 60.0f;
    float  _camTravel  = 0.35f;
    float  _camPrecess = 0.0f;
    bool   _camValid   = false;
    float  _shakeMag   = 0.0f;
    float  _shakePhase = 0.0f;
    float  _hitPulse   = 0.0f;
    float  _sloMo      = 0.0f;
    float  _blastFlash = 0.0f;
    float3 _blastFocus = {};      // world point the radial blur pulls from

    uint64_t _ultBannerStep    = 0;
    EFaction _ultBannerFaction = EFaction::BLUE;

    int32_t _fxSpark = -1;
    int32_t _uiFill  = -1;
    int32_t _uiPanel = -1;

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

    int32_t _sfxId[ 8 ] = {};

    InputProbe _probe;

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

    void Update( crApp* app );
    void FixedUpdatePre( const crApp* app );
    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 SpawnBoundary( const crApp* app );
    void SpawnBlastRings( const crApp* app );   // the fixed FX pool, parked at scale zero
    void SpawnStarfield( const crApp* app );
    void SpawnCore( const crApp* app, EFaction faction );

    void StartMatch( const crApp* app );
    void ClearBattlefield( const crApp* app );
    void RollDoctrine( EFaction faction );

    void UpdateCommanders( const crApp* app );   // posture + push line — the actual two CPUs
    void UpdateUltimates( const crApp* app );
    void Detonate( const crApp* app, EFaction owner, float3 pos, float radius, float damage, float kick );
    void PushBlastRing( float3 pos, float radius, float life, float grow, color4 color, float emissive, EPrimitive shape );
    void PushBlastLook( float3 pos, float radius, float ringLife, color4 color );   // core flash + front + echo
    void UpdateDeployment( const crApp* app );
    void UpdateFighters( const crApp* app );
    void UpdateImpacts( const crApp* app );
    void UpdateExpiry( const crApp* app );
    void UpdateMatchEnd( const crApp* app );     // sudden death, core loss, mutual exhaustion
    void StampDeterminism( const crApp* app );   // cross-platform fingerprint — desktop and wasm logs must diff clean

    void SpawnSquadron( const crApp* app, EFaction faction, EHull hull, int32_t count );
    void SpawnFighter( const crApp* app, EFaction faction, EHull hull, float3 pos );
    void FireShot( const crApp* app, const CFighter& shooter, float3 from, float3 dir );
    void DamageFighter( const crApp* app, entt::entity e, float damage );
    void DestroyFighter( const crApp* app, entt::entity e );
    void DamageCore( const crApp* app, EFaction faction, float damage, float3 at );

    entt::entity PickTarget( const crApp* app, const CFighter& fighter, float3 pos );
    void         Compact( const crApp* app, crArray<entt::entity>* list );
    void         SpawnBurst( const crApp* app, float3 pos, color4 color, float scale, float seconds );

    void UpdateCamera( crApp* app );        // render-side action director
    void UpdatePresentation( crApp* app );

    void RenderScoreboard( crApp* app );


    static const char* FactionName( EFaction faction );
    static const char* DoctrineName( const Commander& commander );   // backbone hull + aggression tier
    static const char* UltimateName( EFaction faction );
    static color4      EnergyColor( EFaction faction );   // what a side EMITS: tracers, blasts, core rings
    static color4      FactionColor( EFaction faction );  // what a side IS: hulls, cores, UI
    static EFaction    Enemy( EFaction faction );
};
