/**
 * @file sidescroller.h
 * @brief Shared declarations for the sidescroller tutorial game.
 *
 * The game is four translation units and this is the seam between them:
 *
 *   main.c       startup, asset loading, the frame loop, teardown
 *   player.c     the player's hooks and its control bindings
 *   actors.c     the coins, the patrolling blob, the flying moth
 *
 * Nothing here is prefixed `akgl_`. That prefix belongs to the library; a game
 * built on it takes its own, and this one is `ss_`.
 */

#ifndef _SIDESCROLLER_H_
#define _SIDESCROLLER_H_

#include <stdbool.h>

#include <SDL3/SDL.h>

#include <akerror.h>

#include <akgl/actor.h>
#include <akgl/collision.h>
#include <akgl/error.h>
#include <akgl/game.h>
#include <akgl/physics.h>
#include <akgl/tilemap.h>
#include <akgl/types.h>

/*
 * The level's geometry, in the units the map is authored in. level1.tmj is 40x15
 * tiles of 16 pixels, so the world is 640x240 pixels and the view is a 480x240
 * window onto it that scrolls horizontally.
 */
#define SS_TILE_SIZE            16      /* Pixels per map cell, from level1.tmj */
#define SS_VIEW_WIDTH           480     /* Camera width in map pixels */
#define SS_VIEW_HEIGHT          240     /* Camera height; the whole map is this tall */
#define SS_WINDOW_SCALE         2       /* Integer upscale from the view to the window */

/* The player's collision box, as an offset into its 32x32 sprite frame. The art
 * does not fill the frame edge to edge, and a box the full width of the frame
 * catches on doorways the character visibly clears. */
#define SS_PLAYER_BOX_X         8.0f
#define SS_PLAYER_BOX_Y         0.0f
#define SS_PLAYER_BOX_W         16.0f
#define SS_PLAYER_BOX_H         32.0f

/* Upward velocity a jump installs directly into the actor's environmental term,
 * in pixels per second. Under the map's 900 px/s^2 gravity this peaks a little
 * under 100 px -- six tiles -- which is what the platform heights are cut to. */
#define SS_JUMP_SPEED           420.0f

/* How fast thrust bleeds away when no direction is held, as a fraction per
 * second. The library has no friction at all: see ss_player_friction. */
#define SS_FRICTION_GROUND      12.0f
#define SS_FRICTION_AIR         1.5f

/* How many of each kind of thing level1.tmj places. */
#define SS_COIN_COUNT           4
#define SS_HAZARD_COUNT         2

/* How far the autoplay script waits between jumps, in frames. */
#define SS_AUTOPLAY_JUMP_PERIOD 45
/* How long it holds the jump key, in frames. A tap jumps lower than a hold. */
#define SS_AUTOPLAY_JUMP_HOLD   8
/*
 * The device id the autoplay script stamps on its synthetic key events.
 * Deliberately not 0: the control map binds keyboard 0 meaning "any keyboard",
 * and driving it from a non-zero id is what proves that binding works.
 */
#define SS_AUTOPLAY_KBID        11

/**
 * @brief Per-actor game data, hung off akgl_Actor::actorData.
 *
 * The library never reads or frees `actorData`, so it is the place a game puts
 * what the library has no field for. These live in a fixed table in actors.c
 * rather than being allocated, which is the same discipline the library's own
 * pools follow.
 */
typedef struct {
    float32_t home_x;   /**< Where the map placed this actor. The moth orbits it; the player respawns at it. */
    float32_t home_y;
    float32_t phase;    /**< Seconds of flight, for the moth's orbit. */
    float32_t facing;   /**< -1.0 walking left, +1.0 walking right. The blob's patrol direction. */
} ss_ActorData;

/** @brief The whole game's state. One player, one level, no menus. */
typedef struct {
    akgl_Actor *player;                     /**< Borrowed from the actor pool; the map created it. */
    akgl_Actor *coins[SS_COIN_COUNT];       /**< Cleared to NULL as each one is collected. */
    akgl_Actor *hazards[SS_HAZARD_COUNT];   /**< The blob and the moth. Borrowed, never released. */
    int coins_taken;
    int deaths;
    bool jump_requested;                /**< Set by the jump binding, consumed by the movement logic. */
    bool grounded;                      /**< Last step's verdict; what gates the next jump. */
    bool autoplay;                      /**< Drive the player from a script instead of the keyboard. */
    int frame;                          /**< Frames drawn so far. */
    float32_t final_x;                  /**< Where the player finished, stamped before teardown for the summary line. */
    float32_t final_y;
} ss_Game;

extern ss_Game ss_game;

/**
 * @brief The collision world, owned by main.c.
 *
 * The physics backend resolves through it after every sub-move; the game only
 * touches it to ask questions -- a ledge probe, a wall probe, a spawn point that
 * needs lifting clear.
 */
extern akgl_CollisionWorld ss_collision;

/**
 * @brief Is there solid geometry one pixel under this shape?
 *
 * The predicate a jump is gated on, and it is deliberately *not* the same
 * question as "did the last step's resolution stop me falling". A contact says
 * something pushed back this step; this says there is a floor to push off, which
 * stays true through the frame after landing and is what makes a jump feel like
 * it registered.
 */
akerr_ErrorContext AKERR_NOIGNORE *ss_grounded(akgl_CollisionShape *shape, float32_t x, float32_t y, bool *dest);

/* player.c */
akerr_ErrorContext AKERR_NOIGNORE *ss_player_bind(akgl_Actor *obj);
akerr_ErrorContext AKERR_NOIGNORE *ss_player_controls(int controlmapid, char *actorname);
akerr_ErrorContext AKERR_NOIGNORE *ss_player_autoplay(int frame);

/* actors.c */
akerr_ErrorContext AKERR_NOIGNORE *ss_actors_bind(void);

#endif // _SIDESCROLLER_H_
