/**
 * @file jrpg.h
 * @brief Shared declarations for the JRPG tutorial game.
 *
 * Every function with external linkage in this program is declared here, the
 * way AGENTS.md requires of the library itself. It is a three-translation-unit
 * program and it would compile without the header; declaring them anyway is
 * what keeps a signature from drifting between the definition and the call.
 *
 * Everything this program exports carries the `jrpg_` prefix. `static` helpers
 * drop it, because the prefix exists only to avoid collisions with libakgl,
 * SDL and libc -- the same rule, for the same reason.
 */

#ifndef _JRPG_JRPG_H_
#define _JRPG_JRPG_H_

#include <stdbool.h>
#include <SDL3/SDL.h>
#include <akerror.h>
#include <akgl/actor.h>
#include <akgl/collision.h>
#include <akgl/types.h>

/*
 * Where the game's data lives. Both are absolute paths baked in by
 * examples/jrpg/CMakeLists.txt, because the tutorial has to run from the build
 * tree, from the source tree, and from wherever CTest happens to put its
 * working directory. The fallbacks are what a reader compiling this by hand
 * from the repository root would want.
 */
#ifndef JRPG_ASSET_DIR
#define JRPG_ASSET_DIR          "docs/tutorials/assets/jrpg"
#endif
#ifndef JRPG_FONT_FILE
#define JRPG_FONT_FILE          "tests/assets/akgl_test_mono.ttf"
#endif

/*
 * The screen size is written as text because that is the only form
 * akgl_set_property takes, and akgl_render_2d_init copies it onto `akgl_camera`
 * on the way past. Everything below reads the camera rather than a second copy
 * of the same two numbers.
 */
#define JRPG_SCREEN_WIDTH       "320"
#define JRPG_SCREEN_HEIGHT      "240"

/** @brief Registry name of the one font this game loads. */
#define JRPG_FONT_NAME          "jrpg"
/** @brief Point size the font is rasterized at. A size is baked into the handle. */
#define JRPG_FONT_SIZE          12

/** @brief Longest line an NPC can say, terminator included. */
#define JRPG_TEXTBOX_MAX_TEXT   256

/** @brief Registry name of the party member this game attaches to the player. */
#define JRPG_FOLLOWER_NAME      "companion"

/**
 * @brief One entry in the scripted demo the headless smoke run drives.
 *
 * The keystrokes go in through akgl_controller_handle_event like any other
 * event, so the smoke run exercises the same binding, state and physics path a
 * player does rather than a separate one written to be testable.
 */
typedef struct {
    long frame;         /**< Frame number this step fires on. */
    SDL_Keycode key;    /**< Key to synthesize. */
    bool down;          /**< True for a press, false for a release. */
} jrpg_ScriptStep;

/**
 * @brief Load every sprite, then every character, then the town map.
 *
 * In that order, and the order is not a preference: akgl_character_load_json
 * resolves each sprite name through #AKGL_REGISTRY_SPRITE, and
 * akgl_tilemap_load resolves each `character` property through
 * #AKGL_REGISTRY_CHARACTER while it spawns the map's actor objects.
 *
 * @return `NULL` on success, otherwise an error context owned by the caller.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_world_load(void);

/**
 * @brief The collision world, owned by world.c.
 *
 * The physics backend resolves through it after every sub-move. Nothing else in
 * the game touches it: the town is walls and people, and the walls are the
 * map's own decoration layer plus four proxies around the edge.
 */
extern akgl_CollisionWorld jrpg_collision;
/**
 * @brief Fix up the actors the map spawned, and attach the party member.
 *
 * Clears `movement_controls_face` on everything -- without which the default
 * `facefunc` erases the facing bit the map set and every NPC stops being drawn
 * on frame one -- installs the blocking movement logic on the player, and
 * builds the follower as a child actor.
 *
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_REGISTRY If the map spawned no actor named "player".
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_world_populate(void);
/**
 * @brief Centre the camera on the player, clamped to the edges of the map.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_REGISTRY If there is no actor named "player".
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_camera_follow(void);
/**
 * @brief The player's `movementlogicfunc`: the library default, plus the freeze.
 *
 * Walls are the library's now -- see chapter 15 -- so all this adds to
 * akgl_actor_logic_movement is raising AKGL_ERR_LOGICINTERRUPT while the text
 * box is open, which is the documented way to tell the simulator to skip an
 * actor's tick. That also skips `collide`, since the CATCH around
 * movementlogicfunc jumps past the whole rest of the step.
 *
 * @param obj The actor to compute acceleration for. Required, along with its
 *            `basechar`.
 * @param dt  Seconds this step covers.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
 * @throws AKGL_ERR_LOGICINTERRUPT While a conversation is open.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_actor_logic_walk(akgl_Actor *obj, float32_t dt);
/**
 * @brief The follower's `renderfunc`: akgl_actor_render with the parent detached.
 *
 * The guard for a defect. akgl_physics_simulate writes a child's `x` as
 * `parent->x + vx` -- an absolute world position -- and akgl_actor_render then
 * draws it at `parent->x + obj->x`, adding the parent's position a second time.
 * Nulling `parent` for the duration of the draw takes the branch that does not
 * add it. See chapter 20.
 *
 * @param obj The child actor to draw. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p obj is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_follower_render(akgl_Actor *obj);
/**
 * @brief Control handler: talk to whoever is standing nearby, or close the box.
 * @param obj   The actor the control map drives -- the player. Required.
 * @param event The event that triggered this. Required, but not read.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_cmhf_talk(akgl_Actor *obj, SDL_Event *event);
/**
 * @brief Control handler that does nothing, for the release half of a binding.
 *
 * akgl_controller_handle_event does not check a matched binding's handler
 * pointer before calling it, so a binding with a `NULL` `handler_off` is a
 * crash rather than an error. Every binding gets both halves.
 *
 * @param obj   The actor the control map drives. Required.
 * @param event The event that triggered this. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_cmhf_ignore(akgl_Actor *obj, SDL_Event *event);

/**
 * @brief Put a line of dialogue on screen and freeze the world.
 * @param text The line to show. Required. Truncated at #JRPG_TEXTBOX_MAX_TEXT.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p text is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_textbox_open(char *text);
/** @brief Dismiss the text box. */
void jrpg_textbox_close(void);
/** @brief True while a line of dialogue is on screen. */
bool jrpg_textbox_showing(void);
/**
 * @brief Draw the panel and its text, if there is anything to draw.
 *
 * Called after akgl_game_update, so it lands on top of the world rather than
 * under it, and in screen coordinates rather than world ones --
 * akgl_text_rendertextat does not go through the camera, which is what a HUD
 * wants.
 *
 * @return `NULL` on success -- including when the box is closed -- otherwise an
 *         error context owned by the caller.
 * @throws AKGL_ERR_REGISTRY If #JRPG_FONT_NAME is not a loaded font.
 */
akerr_ErrorContext AKERR_NOIGNORE *jrpg_textbox_draw(void);

#endif // _JRPG_JRPG_H_
