#pragma once

#include <SDL3/SDL.h>

#include "crStruct.h"

class crDevApp;
class crDevAudio;
class crDevCamera;
class crDevConsole;
class crDevGizmos;
class crDevGraphics;
class crDevNet;
class crDevPhysics;
class crDevPostProcess;
class crDevProfiler;
class crDevText;

class Game;

class crAudio;
class crCamera;
class crEcs;
class crGraphics;
class crInputSystem;
class crNet;
class crParticleSystem;
class crPhysics;
class crRenderSystem;
class crRibbonTrailSystem;
class crUi;
class crUserData;

class crApp
{
public:
    enum class EAppStateFlags: uint32_t
    {
        NONE              = 0,
        INITTED           = 1 << 0,
        FILE_IO_PENDING   = 1 << 1,
        BACKGROUNDED      = 1 << 2,
        SIM_PAUSED        = 1 << 3,
        RUN_IN_BACKGROUND = 1 << 4,
    };

public:
    static constexpr float FIXED_TIMESTEP  = 1.0f / 60.0f;
    static constexpr float MAX_FRAME_DELTA = 0.25f;   // frame-delta clamp (seconds) — caps the fixed-step catch-up after a stall

    static constexpr float TARGET_FRAME_TIME_SLACK = 0.002f;   // vsync quantizes wake-ups; an exact compare would slip to the next refresh tick

    SDL_Window*   window    = nullptr;
    SDL_GLContext glContext = nullptr;

    float targetFps = 0.0f;   // 0 = uncapped (vsync only)
    bool  showDevUi = true;   // master switch for the dev ImGui panels

    crDevApp*         devApp         = nullptr;
    crDevAudio*       devAudio       = nullptr;
    crDevCamera*      devCamera      = nullptr;
    crDevConsole*     devConsole     = nullptr;
    crDevGizmos*      devGizmos      = nullptr;
    crDevGraphics*    devGraphics    = nullptr;
    crDevNet*         devNet         = nullptr;
    crDevPhysics*     devPhysics     = nullptr;
    crDevPostProcess* devPostProcess = nullptr;
    crDevProfiler*    devProfiler    = nullptr;
    crDevText*        devText        = nullptr;

    Game* game = nullptr;

    crAudio*        audio     = nullptr;
    crCamera*       camera    = nullptr;
    crEcs*          ecs       = nullptr;
    crGraphics*     graphics  = nullptr;
    crInputSystem*       inputSys     = nullptr;
    crNet*               net          = nullptr;
    crParticleSystem*    particles    = nullptr;
    crPhysics*           physics      = nullptr;
    crRenderSystem*      renderSys    = nullptr;
    crRibbonTrailSystem* ribbonTrails = nullptr;
    crUi*                ui           = nullptr;
    crUserData*          userData     = nullptr;

private:
    EAppStateFlags _appStateFlags = EAppStateFlags::SIM_PAUSED;

    uint64_t     _lastTicks      = 0;
    crFrameDelta _frameDelta;
    float        _simAccumulator = 0.0f;
    float        _simSpeed       = 1.0f;
    uint64_t     _simStepIndex   = 0;

public:
    void AppendState( EAppStateFlags flags )
    {
        _appStateFlags = static_cast<EAppStateFlags>( static_cast<uint32_t>( _appStateFlags ) | static_cast<uint32_t>( flags ) );
    }
    void RemoveState( EAppStateFlags flags )
    {
        SDL_assert( HasState( flags ) );
        _appStateFlags = static_cast<EAppStateFlags>( static_cast<uint32_t>( _appStateFlags ) & ~static_cast<uint32_t>( flags ) );
    }
    bool HasState( EAppStateFlags flags ) const
    {
        return ( static_cast<uint32_t>( _appStateFlags ) & static_cast<uint32_t>( flags ) ) != 0;
    }
    EAppStateFlags AppState() const
    {
        return _appStateFlags;
    }
    uint32_t AppStateRaw() const
    {
        return static_cast<uint32_t>( _appStateFlags );
    }
    uint64_t SimulationStepIndex() const
    {
        return _simStepIndex;
    }
    float SimulationAlpha() const   // render interpolation factor — accumulated fraction of the next fixed step
    {
        return _simAccumulator / FIXED_TIMESTEP;
    }
    crFrameDelta FrameDelta() const   // this frame's, from FrameBegin
    {
        return _frameDelta;
    }
    float SimulationSpeed() const
    {
        return _simSpeed;
    }
    void SetSimulationSpeed( float speed )
    {
        _simSpeed = speed;
    }
    bool IsRunInBackground() const
    {
        return HasState( EAppStateFlags::RUN_IN_BACKGROUND );
    }
    void SetRunInBackground( bool run )
    {
        if( run == IsRunInBackground() )
            return;

        if( run )
            AppendState( EAppStateFlags::RUN_IN_BACKGROUND );
        else
            RemoveState( EAppStateFlags::RUN_IN_BACKGROUND );
    }
    bool IsSimulationActive() const
    {
        return HasState( EAppStateFlags::SIM_PAUSED ) == false;
    }
    void SetSimulationActive( bool active )
    {
        if( active == IsSimulationActive() )
            return;

        if( active )
            RemoveState( EAppStateFlags::SIM_PAUSED );
        else
            AppendState( EAppStateFlags::SIM_PAUSED );
    }
    void ResetSimulation()
    {
        _simStepIndex   = 0;
        _simAccumulator = 0.0f;
    }

    SDL_AppResult Init( SDL_Window* window, SDL_GLContext glContext );
    SDL_AppResult HandleEvent( SDL_Event* event );
    void          Cleanup();

    void ResetSession();   // session teardown: sim halted + world/ecs/fx pools cleared; game-side state stays the caller's job

    bool         HasTargetFrameTimeElapsed();   // false = skip the whole iteration; gating render alone would unblock the vsync wait
    crFrameDelta FrameBegin();
    void         EarlyUpdate();

    void         FixedUpdateOnce();
    bool         FixedUpdate();
    void         Update( crFrameDelta frameDelta );
    bool         Render( crFrameDelta frameDelta );

    bool IsFullscreen() const;
    void SetFullscreen( bool enable );

    bool IsVSyncSupported() const;   // false where the swap interval cannot reach the pacing
    bool IsVSyncEnabled() const;
    void SetVSync( bool enable );
};
