#pragma once

#include <stdint.h>

#include "crBatchedLines2D.h"
#include "crBatchedSprites.h"
#include "crBatchedTexts.h"
#include "crStruct.h"

class crApp;

// mirrors crInputSystem::MAX_BUTTONS — asserted in crUi.cpp rather than including that header here
constexpr int32_t MAX_ACTION_SLOTS = 8;

// anchor points on the virtual canvas; a widget's pivot matches its anchor, so corner/edge widgets
// extend inward automatically (offset is plain virtual units, x-right / y-up)
enum class EUiAnchor : uint8_t
{
    BOTTOM_LEFT = 0,
    BOTTOM,
    BOTTOM_RIGHT,
    LEFT,
    CENTER,
    RIGHT,
    TOP_LEFT,
    TOP,
    TOP_RIGHT,
};

// immediate-mode game UI. virtual canvas: height LOGICAL_HEIGHT (720) fixed, width follows the
// aspect, Y-up (engine screen convention / PixelProjection). widgets are re-declared every frame
// between Begin/End; End flushes the UI's own sprite/text batches after post-processing, so the UI
// stays tonemap/bloom-clean. ids are caller-chosen (nonzero, unique within a frame).
// spriteIndex params are crGraphics::FindSprite handles; each atlas switch in declaration order
// cuts a draw run
class crUi
{
private:
    // rects of the interactive widgets declared last frame. the canvas itself only ever tracks one
    // pointer, so anything reading raw touches (sticks) has to test its own fingers against these
    struct WidgetRect
    {
        float2 min;
        float2 max;
        float  hitRadius;   // 0 = the whole rect; a round widget must exclude only what it can be hit on
    };
    static constexpr int32_t MAX_WIDGET_RECTS = 64;

    WidgetRect _widgetRects[ MAX_WIDGET_RECTS ];
    int32_t    _widgetRectCount = 0;

    // where each action slot was drawn this frame. crInputSystem pulls this to resolve touches, so a
    // slot that stopped being drawn simply stops claiming them — no separate enable to forget
    struct ActionRegion
    {
        float2 min;
        float2 max;
        float  hitRadius;   // 0 = the whole rect; otherwise distance from its center
        bool   drawn;
    };
    ActionRegion _actionRegions[ MAX_ACTION_SLOTS ] = {};

    crBatchedSprites _sprites;   // UI-owned instances — the graphics-owned ones stay dev/overlay
    crBatchedTexts   _texts;
    crBatchedLines2D _lines;     // flushed last, so lines land on top of sprites and text

    const crApp* _app = nullptr;   // valid between Begin and End

    float  _scale       = 1.0f;    // pixels per virtual unit
    float2 _virtualSize = float2( 0.0f, 0.0f );

    float2 _pointerV;              // pointer in virtual coords (bottom-left, Y-up)
    bool   _pointerDown     = false;
    bool   _pointerPressed  = false;
    bool   _pointerReleased = false;

    uint32_t _activeId = 0;        // widget the pointer went down on (held)

    bool _capturedThis = false;
    bool _capturedPrev = false;    // exposed via PointerCaptured() — one frame late (im-mode classic)

public:
    void Init();
    void Cleanup();

    // events arrive before the frame that rebuilds the UI, so gating uses LAST frame's capture
    bool PointerCaptured() const
    {
        return _capturedPrev;
    }
    bool PointerHeld() const
    {
        return ( _activeId != 0 );
    }

    float2 CanvasSize() const   // virtual units — valid between Begin and End
    {
        return _virtualSize;
    }

    // did this canvas point land on an interactive widget? answered from last frame's layout, so a
    // caller that acts on a touch must let it survive one frame first
    // menu widgets only — an action face is not one of these (see ActionButton)
    bool IsOverWidget( float2 virtualPos ) const;

    // which action slot was drawn under this canvas point, or -1. also from last frame's layout
    int32_t ActionSlotAt( float2 virtualPos ) const;

    // the font atlas rasters at this same scale — crApp rebuilds through it, never its own formula
    static float RasterScale( int2 viewport );

    int32_t SpriteRunsLastFlush() const   // 1 = fully batched
    {
        return _sprites.RunsLastFlush();
    }

    void Begin( const crApp* app );
    void Image( EUiAnchor anchor, float2 offset, float2 size, int32_t spriteIndex, color4 color );
    // 9-slice: corners stay border x border (virtual units = source px at 1:1), edges/center stretch
    void ImageSliced( EUiAnchor anchor, float2 offset, float2 size, int32_t spriteIndex, float border, color4 color );
    void Label( EUiAnchor anchor, float2 offset, const char* utf8, int32_t font, color4 color );
    // true on click (release inside); border > 0 draws 9-sliced. hitRadius 0 uses the whole rect —
    // it changes the hit shape only, never the art, so a round face wants a round sprite as well
    bool Button( uint32_t id, EUiAnchor anchor, float2 offset, float2 size, int32_t spriteIndex, float border, const char* label, int32_t labelFont, color4 color, float hitRadius = 0.0f );

    // the on-screen face of a crInputSystem button slot. it returns nothing: the press arrives
    // through the slot at sim time, from touch or a key or a pad, so the game reads one place for
    // all of them. tint follows the slot, which is why a keyboard press lights this up too.
    // hitRadius 0 uses the whole rect — give it a radius for a round face
    void ActionButton( int32_t slot, EUiAnchor anchor, float2 offset, float2 size, int32_t spriteIndex, float border, const char* label, int32_t labelFont, color4 color, float hitRadius = 0.0f );

    // anti-aliased line strip in virtual coords — closed joins last to first, thickness in virtual units
    void Polyline( const float2* points, int32_t count, bool closed, float thickness, color4 color );
    void End();   // resolve capture + flush the UI batches (PixelProjection)

private:
    static bool HitTest( float2 rectMin, float2 rectMax, float hitRadius, float2 at );   // radius measures from the rect center

    float2 ResolveRectMin( EUiAnchor anchor, float2 offset, float2 size ) const;   // virtual-space min corner (pivot = anchor)
    float2 ToPixel( float2 virtualPos ) const;
    void   AddSprite( float2 rectMin, float2 size, int32_t spriteIndex, color4 color );
    void   AddSpriteSliced( float2 rectMin, float2 size, int32_t spriteIndex, float border, color4 color );   // 9 quads: 3x3 grid, corner cells unstretched

    static float2 AnchorNorm( EUiAnchor anchor );   // anchor as normalized canvas position (0..1)
};
