#pragma once

#include <box3d/id.h>
#include <box3d/math_functions.h>

#include "crPrimitiveGeometry.h"   // EPrimitive — CPrimitive.shape names one of the shapes it builds
#include "crStruct.h"

struct CTransform
{
    // b3WorldTransform is what b3Body_GetTransform returns. Our builds are single precision,
    // where it is literally `typedef b3Transform b3WorldTransform` — the alias only diverges
    // (double-precision positions) if a lib is ever built with BOX3D_DOUBLE_PRECISION.
    b3WorldTransform current;
    b3WorldTransform previous;
};

struct CPhysicsBody
{
    b3BodyId bodyId;
};

// tag — this transform is decoration; crEcs::HashTransforms leaves it out of the determinism
// fingerprint. required by anything animated on the RENDER clock (view-facing billboards cannot be
// made deterministic at all), and wanted even for cosmetics that happen to be deterministic, since a
// fingerprint covering scenery is one an art tweak invalidates.
//
// opt-OUT by design: forgetting it on a sim entity keeps that entity hashed, which is correct, while
// forgetting it on a cosmetic one shows up as a loud mismatch. tagging what to INCLUDE instead would
// fail by quietly passing, which a verification tool must never do
struct CTag_NonHashTarget
{
};

struct CVelocity
{
    float3 linear  = {};
    float3 angular = {};
};

enum class EPrimitiveFlags : uint8_t
{
    NONE           = 0,
    CAST_SHADOW    = 1 << 0,
    RECEIVE_SHADOW = 1 << 1,

    DEFAULT        = CAST_SHADOW | RECEIVE_SHADOW,
};

// declared in draw order: opaque first, then darkening, then sorted alpha, glow last
enum class EBlendMode : uint8_t
{
    OPAQUE = 0,
    MULTIPLY,
    ALPHA,
    ADDITIVE,
};

// solid primitive instance — a unit primitive scaled per axis by the primitive pass
struct CPrimitive
{
    // the four one-byte fields share a single 4-byte slot, so CPrimitive lands on exactly 64 bytes —
    // one cache line. adding a float past custom5 breaks that; keep the budget
    EPrimitive shape    = EPrimitive::BOX;
    EPrimitiveFlags flags    = EPrimitiveFlags::DEFAULT;
    EBlendMode blend    = EBlendMode::OPAQUE;   // transparent modes never cast shadows (forced)
    uint8_t    material = 0;      // draw program: 0 = builtin lit primitive; >0 = a crBatchedPrimitives::RegisterMaterial id
    float3     scale    = {};     // BOX: half extents / SPHERE: radius (uniform xyz)
    color4     color;             // ALPHA: .a is the opacity
    float      emissive = 0.0f;   // self-illumination: adds color * emissive — bloom catches it past the threshold
    float      fresnel  = 0.0f;   // rim glow strength in the primitive's own hue (0 = off) — atmospheres, halos
    // free per-instance floats the builtin material never reads — a custom material assigns them
    // whatever meaning it likes. they arrive as location 6 .w, location 7 .xyzw, location 8 .x
    float      custom0 = 0.0f;
    float      custom1 = 0.0f;
    float      custom2 = 0.0f;
    float      custom3 = 0.0f;
    float      custom4 = 0.0f;
    float      custom5 = 0.0f;
};

// ribbon trail size-class — value is the pool index; the comment gives each class's point capacity
enum class ERibbonCapacity : int32_t
{
    SEC_1  = 0,   // 60 points  @ 60 Hz (~1 s)
    SEC_5  = 1,   // 300 points        (~5 s)
    SEC_10 = 2,   // 600 points        (~10 s)
};

// trail emitter — crRibbonTrailSystem owns the point history in a size-class pool; poolSlot links to it.
//
// 84 B, past the 64 B line — and it was 72 B before emitOffset, so it has never fit. the only fat
// left is the two color4s: packing both to RGBA8 lands it at 60 B with emitOffset kept, or 52 B if
// emitOffset also collapses to a scalar along local +X (every caller today offsets down that axis
// alone). precision is not what blocks that — the HDR push comes from intensity, not from color, and
// an additive ribbon under bloom swallows 8-bit steps — the cost is the pack call every assignment
// site then has to make. left unpacked deliberately; the per-step walk reads poolSlot and emitOffset
// and copies the rest once, so the miss is paid on allocation, not on every emitter every step
struct CRibbonTrail
{
    color4          color         = { 1.0f, 1.0f, 1.0f, 1.0f };   // head color
    color4          tailColor     = { 0.0f, 0.0f, 0.0f, 1.0f };   // tail color — lerped along the trail (black tail = fades out, additive)
    float3          emitOffset    = {};      // emitter position in the entity's LOCAL frame (rotated by its transform) — a nose-aligned body wants its trail leaving the tail, not the centre
    float           headHalfWidth = 0.1f;
    float           tailHalfWidth = 0.0f;    // absolute half-width; 0 = pointed tail
    float           intensity     = 1.0f;    // HDR brightness multiplier — >1 pushes past the bloom threshold (glow)
    float           emitDistance  = 0.1f;    // min emitter move before a new point is recorded
    float           fadeDuration  = 1.0f;    // seconds; counts down only once detached
    float           tileLength    = 0.0f;    // world meters per texture repeat (0 = stretch one repeat over the trail)
    uint32_t        textureId     = 0;       // crGraphics::Texture handle (0 = the renderer's default texture)
    float           duration      = 0.0f;    // seconds of tail to keep (0 = full slot capacity); must fit the chosen size-class
    ERibbonCapacity capacity      = ERibbonCapacity::SEC_1;   // size-class the caller picks by duration
    int32_t         poolSlot      = -1;      // handle assigned by the system on first sight (-1 = unallocated)
};

// particle emitter — crParticleSystem owns per-emitter state (RNG stream, spawn accumulator) in a
// slot pool; poolSlot links to it. removing the component just stops spawning — live particles are
// fire-and-forget and finish their lifetimes in the global pool
struct CParticleEmitter
{
    color4  color        = { 1.0f, 1.0f, 1.0f, 1.0f };   // start color
    color4  colorEnd     = { 0.0f, 0.0f, 0.0f, 1.0f };   // lerped over each particle's life (black = fades out, additive)
    float3  velocity     = {};      // base initial velocity (world)
    float3  gravity      = { 0.0f, -9.8f, 0.0f };   // per-particle acceleration (world)
    float   drag         = 0.4f;    // linear damping per second
    float   spread       = 0.0f;    // isotropic random speed added on top (uniform direction, 0..spread magnitude)
    float   rate         = 10.0f;   // particles per second
    float   lifetimeMin  = 1.0f;    // per-particle uniform ranges
    float   lifetimeMax  = 1.0f;
    float   sizeMin      = 0.1f;    // start size range
    float   sizeMax      = 0.1f;
    float   sizeEndScale = 1.0f;    // end size = start size x this (1 = constant, 0 = shrinks away, >1 = grows)
    float   intensityMin = 1.0f;    // HDR brightness multiplier — >1 pushes past the bloom threshold
    float   intensityMax = 1.0f;
    float   spinMin      = 0.0f;    // in-plane angular velocity (rad/s, sign = direction); the initial
    float   spinMax      = 0.0f;    // angle is always random — orientation variety breaks the clone look
    bool    alignToVelocity = false;   // initial angle faces the spawn velocity instead of random — directional shards
    int32_t spriteIndex  = 0;       // packed sprite handle the particles sample (crGraphics::FindSprite; 0 = builtin atlas, sprite 0)
    uint64_t killStep    = 0;       // != 0: the system DESTROYS the owning entity at this sim step —
                                    // for fire-and-forget burst entities that exist only to emit
    int32_t poolSlot     = -1;      // handle assigned by the system on first sight (-1 = unallocated)
};
