/**
 * @file akgl.h
 * @brief The libakgl-backed implementations of the sink and the three devices.
 *
 * Everything declared here lives in the separate `akbasic_akgl` target, which is
 * the only part of this project that links SDL. The core library builds and its
 * whole test suite runs on a machine with no SDL on it; that is why the records
 * these initializers populate are plain function-pointer structs and why this
 * header is the only one that includes a libakgl header.
 *
 * **The interpreter owns no window, no renderer and no event loop.** Every one
 * of these takes something the host already created and draws or plays through
 * it. None of them creates a device, and none of them pumps events.
 *
 * Each initializer calls akgl_error_init() first. It reserves libakgl's 256-262
 * status band and names every AKGL_ERR_* code; akgl_game_init() calls it as its
 * first statement, but a program driving subsystems directly -- which is exactly
 * what an embedded interpreter does -- never goes through akgl_game_init() and
 * has to call it itself. Skip it and every AKGL_ERR_* that reaches a stack trace
 * prints "Unknown Error". It is idempotent, so a host that already called it
 * loses nothing.
 */

#ifndef _AKBASIC_AKGL_H_
#define _AKBASIC_AKGL_H_

#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>

#include <akerror.h>

#include <akgl/actor.h>
#include <akgl/character.h>
#include <akgl/collision.h>
#include <akgl/renderer.h>
#include <akgl/sprite.h>
#include <akgl/ui.h>
#include <akgl/version.h>

/*
 * **0.5.0 was a hard floor, API as well as ABI**, and it is worth keeping the
 * reason: it namespaced every exported symbol, so the spellings this target
 * compiles against did not exist before it. `akgl_render_bind2d` is
 * `akgl_render_2d_bind`, `akgl_sprite_sheet_coords_for_frame` is
 * `akgl_spritesheet_coords_for_frame`, and the `renderer`, `camera` and `window`
 * globals carry the prefix -- with `_akgl_renderer` and `_akgl_camera` now
 * `akgl_default_renderer` and `akgl_default_camera`.
 *
 * That rename was not cosmetic and libakgl's TODO.md says why at length: an
 * exported global called `renderer` collided with a test's own variable of the
 * same name, the executable's definition preempted the library's, and every
 * texture load in that suite failed while the suite reported success.
 *
 * **0.6.0 and 0.7.0 broke nothing here**, and the floor moves anyway. 0.6.0 is
 * three arcade-physics fixes and a `physics.max_timestep` property this target
 * does not use; 0.7.0 reports failures libakstdlib's wrappers were already
 * catching, and takes libakerror 2.0.1 so it can drop the exit-status trap its
 * own suites needed -- the same defect `include/akbasic/error.h` guards for this
 * band, fixed at the source rather than worked around twice.
 *
 * **0.8.0 broke nothing here either**, and is the largest of the three: a whole
 * collision subsystem -- shapes, pooled proxies, a pluggable broad phase, a
 * narrowphase answering with a contact -- none of which this target called on the
 * day the floor moved. It is compatible by luck rather than by design, which is
 * the argument for moving the floor rather than deciding for ourselves that a
 * minor release was really compatible.
 *
 * Two things about it a reader here has to know. `akgl_Actor` grew fields, so
 * its `sizeof` changed and a translation unit compiled against a 0.7 `actor.h`
 * writes `renderfunc` and `actorData` at the wrong offsets -- and
 * `src/sprite_akgl.c` writes exactly those two. The symptom is a jump through a
 * garbage pointer on the first frame, which is precisely the case this guard
 * exists to turn into a compile error. And libakgl's reserved status band grew
 * from five codes to six with `AKGL_ERR_COLLISION`, taking it to 256 to 261 --
 * 0.9.0 moved it again, below. `MAINTENANCE.md`'s coordinated range map and
 * `docs/15-error-codes.md` both carry the current band.
 *
 * **0.9.0 is the first one that is additive on its face**, and the floor moves
 * anyway. It is the `akgl_ui` subsystem -- a vendored clay, an arena, menus,
 * HUDs and dialogs -- reached through a new `akgl/ui.h`, plus three new entry
 * points in `akgl/draw.h` (`akgl_draw_filled_rounded_rect`, `akgl_draw_arc`,
 * `akgl_draw_set_clip`). Every header this target already compiled against --
 * `actor.h`, `sprite.h`, `renderer.h`, `text.h`, `registry.h`, `heap.h` -- is
 * byte-identical to 0.8.0, so unlike 0.8.0 there is no `sizeof` to get wrong.
 * The status band grew again regardless: `AKGL_ERR_UI` makes seven codes and
 * libakgl now owns 256 to 262.
 *
 * `src/ui_akgl.c` reaches the widget helpers -- `akgl_ui_dialog`,
 * `akgl_ui_label`, `akgl_ui_menu` and the frame bracket -- and nothing else.
 * The raw clay route is deliberately not exposed to a BASIC program; see
 * `docs/19-user-interface.md`.
 *
 * It also collided with the build rather than the code. 0.9.0 moved eight test
 * property calls from `set_tests_properties` to `set_property(TEST ...)`, which
 * errors on a test name that does not exist where the old command was silent --
 * and this repository suppresses `add_test()` while it pulls its dependencies
 * in, so those names do not exist. Eight configure failures, fixed by shadowing
 * `set_property` in its `TEST` form too; `MAINTENANCE.md`'s embedding section
 * carries the detail.
 *
 * The soname carries MAJOR.MINOR while the major is 0, so 0.5, 0.6 and 0.7 are
 * different ABIs *by declaration* -- libakgl's versioning policy says a 0.x minor
 * bump may break, and the soname is built to match. The floor moves with every
 * one of them, because the alternative is deciding for ourselves which of
 * libakgl's minor releases were really compatible, and that is exactly the
 * judgement the soname exists to take away from us.
 *
 * What it catches is the case the soname cannot: a build against 0.6 headers
 * that happens to find a 0.7 library, or the reverse. Refused here rather than
 * at link time, because a missing symbol names a function and this names the
 * release.
 */
#if !AKGL_VERSION_AT_LEAST(0, 9, 0)
#error "akbasic's libakgl adaptors require libakgl 0.9.0 or later"
#endif

#include <akbasic/audio.h>
#include <akbasic/graphics.h>
#include <akbasic/input.h>
#include <akbasic/sink.h>
#include <akbasic/sprite.h>
#include <akbasic/ui.h>

/**
 * @brief One full cursor blink in milliseconds, half on and half off.
 *
 * Half a second, which is about what a Commodore does and is slow enough to read
 * under.
 */
#define AKBASIC_SINK_CURSOR_BLINK_MS	500

/** @brief How many saved SSHAPE regions the graphics backend will hold at once. */
#define AKBASIC_AKGL_MAX_SHAPES		16

/**
 * @brief One frame of the host's own loop, borrowed by the sink's line editor.
 *
 * The sink has to be able to wait for a typed line without owning an event loop,
 * and those two requirements only meet in one place: the host hands over a
 * callback that does exactly one frame -- pump events, redraw, present -- and
 * the editor calls it between keystrokes. The loop is still the host's; the
 * editor just borrows it a frame at a time.
 *
 * Setting @p running false is how the host says the window closed. The editor
 * reports end of input rather than raising, because that is what running out of
 * input means everywhere else in sink.h.
 *
 * @param self Whatever the host passed to akbasic_sink_akgl_set_pump().
 * @param running Set false to stop waiting; the editor then reports EOF.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*akbasic_AkglPump)(void *self, bool *running);

/**
 * @brief State for the libakgl-backed text sink.
 *
 * The cursor, the wrap and the scroll live here rather than in the interpreter:
 * everything in the reference's basicruntime_graphics.go except Write and
 * Println, which are the sink interface itself.
 */
typedef struct
{
    akgl_RenderBackend *renderer;
    TTF_Font *font;
    SDL_Color color;
    /**
     * What a text cell is erased to before its glyphs are drawn. Opaque black
     * by default, which is a Commodore console and is what makes the text layer
     * *authoritative* over the rows it occupies rather than transparent over
     * them -- see akbasic_sink_akgl_render().
     */
    SDL_Color background;

    int x;			/* pixel origin of the text area			*/
    int y;
    int width;			/* pixel size of the text area				*/
    int height;
    /*
     * The whole drawable area, kept so WINDOW can grow back out to it. Without
     * it a window could only ever shrink, since `x`/`width` have by then been
     * overwritten with the current window's.
     */
    int fullx;
    int fully;
    int fullwidth;
    int fullheight;
    int cellw;			/* one character cell, measured from the font		*/
    int cellh;
    int columns;		/* the character grid the cell size works out to	*/
    int rows;

    /* GRAPHIC chooses whether this text plane is visible and where it begins. */
    int graphicmode;
    int texttop;		/* first screen row the current text region owns	*/

    int cursorcol;
    int cursorrow;

    /*
     * The scrollback the sink redraws every frame. A fixed grid rather than a
     * list of lines: the interpreter allocates nothing, and neither does this.
     */
    char text[64][256];

    /**
     * Milliseconds for one full cursor blink, half on and half off. Zero holds
     * the cursor solid, which is what a test wanting a deterministic frame sets.
     */
    uint64_t cursorperiodms;

    /*
     * The line editor. Nothing here is live except while readline is running,
     * and `editing` is what says so -- render() draws a cursor only then, and
     * the scroll adjusts `editrow` only then.
     */
    akbasic_AkglPump pump;
    void *pumpself;
    bool editing;
    int editrow;		/* where the line being typed starts		*/
    int editcol;
    int editlen;		/* characters accepted so far			*/
    int echolen;		/* characters currently drawn, so a backspace	*/
				/* knows how much to erase			*/
    char editline[256];
} akbasic_AkglSink;

/**
 * @brief State for the libakgl-backed graphics backend.
 *
 * The shape pool is why this exists at all. SSHAPE hands the BASIC program a
 * handle rather than the pixels -- see TODO.md section 5 -- and these are the
 * surfaces those handles refer to.
 */
typedef struct
{
    akgl_RenderBackend *renderer;
    SDL_Surface *shapes[AKBASIC_AKGL_MAX_SHAPES];
    int shapecount;

    /**
     * The drawing layer: a texture the drawing verbs render into and the frame
     * composites, so a drawing stays on screen without the program redrawing it.
     *
     * **Without this a drawing lasts exactly one frame.** The verbs are
     * immediate and go to the renderer's back buffer, SDL double-buffers, and
     * the frontend never clears -- so what a program drew is gone the moment the
     * frame it was issued in is presented. The only way to keep a picture was to
     * capture it with `SSHAPE` and install it as a sprite, which is what
     * `examples/breakout/sprites/breakout.bas` spends two of its eight sprites
     * doing and what TODO.md section 9 item 9 was filed about.
     *
     * NULL until the first drawing verb runs. A program that never draws pays
     * for no texture, which matters: this is the size of the window.
     */
    SDL_Texture *layer;
    /**
     * What the target was before the layer was made current, so it can be put
     * back. The frontend brackets the whole step phase rather than each verb --
     * one pair of SDL_SetRenderTarget calls a frame instead of one per DRAW.
     */
    SDL_Texture *savedtarget;
    bool layeractive;
} akbasic_AkglGraphics;

/**
 * @brief State for the libakgl-backed sprite backend.
 *
 * One libakgl actor per BASIC sprite, plus the sheet, sprite and character each
 * actor needs -- built by hand rather than loaded from a sprite document, since
 * a Commodore sprite has no animation, no frame list and no state map to
 * describe. Everything is pooled by libakgl; what is held here are the pointers
 * and the one texture per slot that this backend owns and must destroy.
 *
 * `graphics` is the graphics backend's state, borrowed so `SPRSAV A$, n` can
 * resolve a handle SSHAPE minted. The two devices are separate records on
 * purpose -- a host may supply one and withhold the other -- so this is a
 * pointer that may be NULL rather than an assumption that both exist.
 */
typedef struct
{
    akgl_RenderBackend *renderer;
    akbasic_AkglGraphics *graphics;

    akgl_SpriteSheet *sheets[AKBASIC_MAX_SPRITES];
    akgl_Sprite *sprites[AKBASIC_MAX_SPRITES];
    akgl_Character *characters[AKBASIC_MAX_SPRITES];
    akgl_Actor *actors[AKBASIC_MAX_SPRITES];

    /** Per-axis expansion, which an actor's single `scale` cannot carry. */
    bool xexpand[AKBASIC_MAX_SPRITES];
    bool yexpand[AKBASIC_MAX_SPRITES];

    /**
     * One collision proxy per slot, claimed once at init and held for the life
     * of the backend.
     *
     * Claimed up front rather than per scan for two reasons. The pool is shared
     * with whatever host this interpreter is embedded in -- a game with its own
     * shaped actors draws from the same `AKGL_MAX_HEAP_COLLISION_PROXY` -- so
     * running out is a real possibility, and it should be an init-time failure
     * naming the pool rather than a collision scan that starts refusing halfway
     * through a game. And a proxy carries a *copy* of its shape, so there is
     * nothing to rebuild per scan beyond the position.
     *
     * These are not registered with any partitioner. akgl_collision_test()
     * takes two positioned proxies and needs no world, which is what lets eight
     * sprites be tested pairwise without paying for a broad phase that eight
     * objects would not repay.
     */
    akgl_CollisionProxy *proxies[AKBASIC_MAX_SPRITES];
    /**
     * The shape each proxy is synced from.
     *
     * Kept here rather than read back off the proxy because
     * akgl_collision_proxy_sync() wants a live shape to copy from, and because
     * the shape is derived from the sprite's frame and expansion bits every scan
     * -- a sprite whose picture or expansion changed has a different box, and
     * the cheapest correct thing is to rebuild it rather than track what
     * invalidates it.
     */
    akgl_CollisionShape shapes[AKBASIC_MAX_SPRITES];
    /**
     * The rectangle each proxy was last synced from, so a scan can tell whether
     * there is anything to sync. See sync_proxy() in src/sprite_akgl.c for why
     * this is compared rather than flagged.
     */
    SDL_FRect syncedbox[AKBASIC_MAX_SPRITES];

    /**
     * SPRHIT's shape per slot, as the device was told it.
     *
     * A second copy of what akbasic_Sprite already carries, and deliberately: a
     * device is told about changes and never asked what they were
     * (include/akbasic/sprite.h), so the backend has to be able to answer from
     * its own state rather than reaching back into the runtime it does not have
     * a pointer to.
     */
    int shapekind[AKBASIC_MAX_SPRITES];
    float32_t shapex1[AKBASIC_MAX_SPRITES];
    float32_t shapey1[AKBASIC_MAX_SPRITES];
    float32_t shapex2[AKBASIC_MAX_SPRITES];
    float32_t shapey2[AKBASIC_MAX_SPRITES];
    bool shapeexplicit[AKBASIC_MAX_SPRITES];

    /**
     * SOLID's static geometry: a proxy and a shape per registered rectangle.
     *
     * **Not registered with any partitioner, and there is no
     * `akgl_CollisionWorld` here at all.** libakgl's uniform grid keeps its cell
     * heads, its cell size and its origin in file-scope statics
     * (`deps/libakgl/src/collision_grid.c`), so it is one index per process --
     * and `akgl_collision_world_init()` ends in a `reset()` that memsets those
     * heads *and* calls `akgl_heap_init_collision_cells()`. An interpreter
     * embedded in a game that has its own collision world would destroy every
     * registration that game had made, on the first `SOLID` a script ran.
     *
     * So the geometry is indexed here, by an ordinary array, and pairs go
     * straight to `akgl_collision_test()` -- which takes two positioned proxies
     * and needs no world. At sixty-four rectangles that is the right answer
     * anyway: libakgl's own measurements put a naive all-pairs sweep at 0.7% of
     * a frame at sixty-four objects, and a broad phase is bookkeeping that only
     * repays itself well above that.
     */
    akgl_CollisionProxy *solidproxies[AKBASIC_MAX_SOLIDS];
    akgl_CollisionShape solidshapes[AKBASIC_MAX_SOLIDS];
    SDL_FRect solidbox[AKBASIC_MAX_SOLIDS];
    bool solidactive[AKBASIC_MAX_SOLIDS];

    /**
     * What the last scan saw, so an unchanged scan can answer without redoing
     * the work.
     *
     * The scan runs at the top of every interpreter step -- up to 256 times a
     * rendered frame -- and its inputs are the sprites' boxes, which sprite
     * slots are collidable, and the static geometry. A sprite moves at most once
     * in that time. If none of those changed, the answer cannot have changed
     * either, and eight rectangle comparisons say so far more cheaply than
     * recomputing it.
     *
     * This is what makes static geometry affordable at all: eight sprites
     * against sixty-four rectangles is five hundred and twelve tests, which is
     * fine once a frame and is not fine two hundred and fifty-six times.
     */
    SDL_FRect lastbox[AKBASIC_MAX_SPRITES];
    bool lastlive[AKBASIC_MAX_SPRITES];
    uint16_t lastmask;
    uint16_t lastsolidmask;
    bool lastvalid;

    /**
     * The deepest contact each sprite was in when the scan last ran.
     *
     * Kept as a side effect of the pass that computes the masks, because that is
     * the only place the information exists -- `akgl_collision_test()` produces
     * it and a caller that discards it cannot get it back. Deepest wins, because
     * the deepest overlap is the one a program has to undo.
     */
    akbasic_Contact contacts[AKBASIC_MAX_SPRITES];
    bool hascontact[AKBASIC_MAX_SPRITES];
} akbasic_AkglSprites;

/**
 * @brief Point a text sink at a renderer and a font the host already has.
 *
 * The character grid is derived by measuring one glyph with akgl_text_measure(),
 * which is the direct equivalent of the reference's font.SizeUTF8("A") -- and
 * which did not exist in libakgl until 42b60f7. A font that is not monospaced
 * still works; the grid is then sized by whatever "A" happens to measure, and
 * proportional glyphs simply do not line up in columns.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param state Storage for the sink's own state; must outlive the sink.
 * @param renderer The renderer the host already initialized; not created here.
 * @param font An open font; not opened or closed here.
 * @param w Width in pixels of the text area.
 * @param h Height in pixels of the text area.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any pointer argument is NULL.
 * @throws AKBASIC_ERR_VALUE When the font measures a zero-width cell, which would divide by zero.
 * @throws AKBASIC_ERR_BOUNDS When the area is too small for even one character.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSink *state, akgl_RenderBackend *renderer, TTF_Font *font, int w, int h);

/**
 * @brief Draw whatever the sink currently holds.
 *
 * Separate from the sink's write path because the interpreter does not own the
 * frame: a host calls this when it is drawing, not when the script happens to
 * PRINT. A driver that only wants stdout never calls it at all.
 *
 * @param obj The sink to draw.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 * @throws AKGL_ERR_SDL When the renderer refuses the text.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_akgl_render(akbasic_TextSink *obj);

/**
 * @brief Give the sink a line editor by lending it one frame of the host's loop.
 *
 * Without this, readline reports end of input immediately: a drawn text layer is
 * not a source of lines, and a sink that blocked on the keyboard with no way to
 * pump events would deadlock the process on its first INPUT. With it, readline
 * collects keystrokes from libakgl's ring -- the one the host's own event pump
 * fills -- echoes them into the grid, and returns the line on Return.
 *
 * **It is a real keyboard as of libakgl 0.3.0.** The ring carries the composed
 * UTF-8 text SDL worked out from the keystroke, so shifted characters, a
 * keyboard layout, a compose key and a dead key all do what the person typing
 * expects -- including the double quote, without which a BASIC string literal
 * cannot be typed at all. Until 0.3.0 the ring reported a keycode and nothing
 * else, so none of that was reachable and letters were folded to upper case;
 * that was libakgl API-gap item 10, filed from here.
 *
 * Composed text is taken over the keycode wherever there is any, which is what
 * makes the layout work: on an AZERTY keyboard the key SDL calls `SDLK_Q`
 * composes to "a", and "a" is what the program should see.
 *
 * Editing is Backspace to erase one character and Escape to abandon the line.
 * There is no cursor movement within the line: the ring reports the arrow keys,
 * but a script's own GET loop wants them.
 *
 * @param obj The sink to give an editor to.
 * @param pump One frame of the host's loop, or NULL to take the editor away.
 * @param self Passed back to @p pump untouched.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL or carries no sink state.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_akgl_set_pump(akbasic_TextSink *obj, akbasic_AkglPump pump, void *self);

/**
 * @brief Point a graphics backend at a renderer the host already has.
 * @param obj Object to initialize, inspect, or modify.
 * @param state Storage for the shape pool; must outlive the backend.
 * @param renderer The renderer the host already initialized; not created here.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_init_akgl(akbasic_GraphicsBackend *obj, akbasic_AkglGraphics *state, akgl_RenderBackend *renderer);

/**
 * @brief Make the drawing layer current, so what the program draws is kept.
 *
 * Called by a host **before** running the interpreter's steps, and paired with
 * akbasic_graphics_akgl_end(). Between the two, every drawing verb renders into
 * a texture that survives the frame instead of into a back buffer that does not.
 *
 * Bracketing the whole step phase rather than each verb is deliberate: it is one
 * pair of SDL_SetRenderTarget calls a frame instead of one per `DRAW`, and it is
 * also what makes `SSHAPE` read back what the program has just drawn rather than
 * whatever the previous frame left.
 *
 * A host that never calls these gets the old behaviour -- drawings live one
 * frame -- which is what `tools/screenshot.c` wants, since it presents nothing.
 *
 * @param obj The graphics backend, or NULL to do nothing.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_SDL When the layer texture cannot be created or made current.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_akgl_begin(akbasic_GraphicsBackend *obj);

/**
 * @brief Put the previous render target back. Pairs with akbasic_graphics_akgl_begin().
 *
 * @param obj The graphics backend, or NULL to do nothing.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_SDL When the previous target cannot be restored.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_akgl_end(akbasic_GraphicsBackend *obj);

/**
 * @brief Draw the layer onto the frame, under the text and the sprites.
 *
 * Called once a frame by a host, before the sink renders. Does nothing when no
 * program has drawn anything, so a text-only program composites nothing.
 *
 * @param obj The graphics backend, or NULL to do nothing.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_SDL When the layer cannot be drawn.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_akgl_render(akbasic_GraphicsBackend *obj);

/**
 * @brief Is the drawing layer currently the render target?
 *
 * A host needs this because SDL refuses to present while a target is current,
 * and the sink's line editor calls a host's pump from *inside* a step -- when
 * the layer is on -- while the frame loop calls it between steps, when it is
 * not. Answering the question is what lets one pump serve both.
 *
 * @param obj The graphics backend, or NULL for false.
 */
bool akbasic_graphics_akgl_layer_active(akbasic_GraphicsBackend *obj);

/** @brief Destroy the layer texture. Safe on a backend that never made one. */
void akbasic_graphics_akgl_shutdown(akbasic_GraphicsBackend *obj);

/**
 * @brief Point a sprite backend at a renderer the host already has.
 *
 * Requires akgl_heap_init() and akgl_registry_init() to have run -- every
 * akgl_*_initialize ends in a registry write -- and requires the global `camera`
 * to point somewhere, because akgl_actor_render() dereferences it without
 * checking. akgl_game_init() does all three; a host that skips it, which is
 * every host embedding this interpreter, does them itself.
 * src/frontend_akgl.c is the worked example.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param state Storage for the sprite pool; must outlive the backend.
 * @param renderer The renderer the host already initialized; not created here.
 * @param graphics The graphics backend's state, so SPRSAV can resolve an SSHAPE handle; may be NULL.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj`, `state` or `renderer` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sprite_init_akgl(akbasic_SpriteBackend *obj, akbasic_AkglSprites *state, akgl_RenderBackend *renderer, akbasic_AkglGraphics *graphics);

/**
 * @brief Draw every defined, enabled sprite.
 *
 * Separate from the verbs for the same reason akbasic_sink_akgl_render() is: the
 * interpreter does not own the frame. A host calls this when it is drawing.
 *
 * The actors are swept directly rather than through akgl_registry_iterate_actor(),
 * which ends in FINISH_NORETURN -- an unhandled error inside it terminates the
 * process, and goal 3 forbids anything in this library doing that. Sweeping our
 * own eight also leaves a host's own actors alone.
 *
 * @param obj The backend to draw.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL or carries no state.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sprite_akgl_render(akbasic_SpriteBackend *obj);

/**
 * @brief Release every texture the sprite backend created.
 *
 * The actors, sprites, sheets and characters are libakgl's pooled objects and go
 * back with akgl_heap_init(); the SDL textures behind the sheets are this
 * backend's own and are not anybody else's to free.
 *
 * @param obj The backend to tear down. NULL is not an error.
 */
void akbasic_sprite_akgl_shutdown(akbasic_SpriteBackend *obj);

/**
 * @brief Wire an audio backend to libakgl's tone generator.
 *
 * Calls akgl_audio_init(), which opens an SDL audio device. A host that owns its
 * own audio pipeline can call akgl_audio_init() itself beforehand -- it is
 * idempotent in the sense that mattered upstream: the voice table works whether
 * or not a device is open.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 * @throws AKGL_ERR_SDL When no audio device can be opened.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_init_akgl(akbasic_AudioBackend *obj);

/**
 * @brief Wire an input backend to libakgl's keystroke ring.
 *
 * Reads only. The ring is filled by akgl_controller_handle_event(), which the
 * *host* calls as it pumps SDL events -- so a script gets keystrokes without the
 * interpreter owning the event loop.
 *
 * Worth knowing before lending this to a script: that ring is process-global and
 * the host's own control maps read the same events. A script sitting in a GET
 * loop drains keystrokes the game will then never see. A host that cares either
 * withholds the input backend or supplies a filtered one of its own.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_input_init_akgl(akbasic_InputBackend *obj);

/**
 * @brief State for the libakgl-backed UI backend.
 *
 * **This one is retained where the others are not, and that is the point.**
 * libakgl's UI is immediate mode: widgets are declared inside a frame bracket
 * and clay borrows their text until the bracket closes. A BASIC program declares
 * a menu once and expects it to survive several hundred frames. So the verbs
 * write here, and akbasic_ui_akgl_render() re-declares the lot once a frame from
 * what it finds. The text is copied on the way in for the same reason.
 *
 * Element ids are generated once at init -- "menu1", "hud1" and so on -- because
 * clay identifies an element by a string that has to be the same every frame for
 * hover and scroll state to survive between them.
 */
typedef struct
{
    akgl_RenderBackend *renderer;
    char fontname[64];		/* registry name this loaded the UI font under	*/
    uint16_t fontid;		/* what clay calls it				*/
    bool ready;			/* akgl_ui_init succeeded and shutdown has not run */

    /*
     * The one style, and whether UISTYLE has set it. When it has not, every
     * widget is handed NULL and gets libakgl's default rather than a copy of it
     * made here -- so "never styled" and "styled back to the default" stay the
     * same thing.
     */
    akgl_UiStyle style;
    bool styled;

    char dialogtext[AKBASIC_MAX_STRING_LENGTH];
    bool dialogopen;

    char labelid[AKBASIC_UI_MAX_LABELS][8];
    char labeltext[AKBASIC_UI_MAX_LABELS][AKBASIC_MAX_STRING_LENGTH];
    int labelanchor[AKBASIC_UI_MAX_LABELS];
    bool labelset[AKBASIC_UI_MAX_LABELS];

    char menuid[AKBASIC_UI_MAX_MENUS][8];
    char menuitems[AKBASIC_UI_MAX_MENUS][AKBASIC_UI_MAX_MENU_ITEMS][AKBASIC_MAX_STRING_LENGTH];
    akgl_UiMenu menus[AKBASIC_UI_MAX_MENUS];
} akbasic_AkglUi;

/**
 * @brief Bring the UI subsystem up and point a UI backend at it.
 *
 * Loads @p fontpath into the font registry under a name of its own and registers
 * that name with clay. **It calls akgl_registry_init_font(), which
 * akgl_registry_init() deliberately does not** -- nothing else in this
 * repository has needed the font registry, because the text sink opens its font
 * with a bare TTF_OpenFont.
 *
 * @warning akgl_ui_init() is process-global and refuses a second call. One UI
 *          per process: two runtimes in one process share it.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param state Storage for the retained widget set; must outlive the backend.
 * @param renderer The renderer the host already initialized; not created here.
 * @param fontpath Font file the widgets draw with. Required.
 * @param fontsize Point size to load it at.
 * @param width Layout width, in pixels. Usually the window's.
 * @param height Layout height, in pixels.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj`, `state`, `renderer` or `fontpath` is NULL.
 * @throws AKBASIC_ERR_VALUE When `fontsize`, `width` or `height` is not positive.
 * @throws AKGL_ERR_UI When the subsystem is already up, or clay refuses.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_init_akgl(akbasic_UiBackend *obj, akbasic_AkglUi *state, akgl_RenderBackend *renderer, const char *fontpath, int fontsize, int width, int height);

/**
 * @brief Declare and draw the whole retained widget set, once, for this frame.
 *
 * Separate from the verbs for the same reason akbasic_sprite_akgl_render() is:
 * the interpreter does not own the frame. A host calls this when it is drawing,
 * after everything else -- the UI goes on top.
 *
 * A frame that fails still closes its bracket, so one bad frame is one bad frame
 * rather than a wedged subsystem.
 *
 * @param obj The backend to draw.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL or carries no state.
 * @throws AKGL_ERR_UI When clay reported a layout error.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_akgl_render(akbasic_UiBackend *obj);

/**
 * @brief Offer one SDL event to the UI, and say whether it was taken.
 *
 * The mouse goes to the subsystem, which never consumes a keystroke. The
 * keyboard and gamepad go to each menu that has entries, and those **do**
 * consume Up, Down and Return -- so a host must route this before it feeds the
 * keystroke ring, and a menu that is up owns those keys. Retiring the menu gives
 * them back.
 *
 * @param obj The backend to offer the event to.
 * @param event The event; not modified.
 * @param consumed Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL, or `obj` carries no state.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_ui_akgl_handle_event(akbasic_UiBackend *obj, SDL_Event *event, bool *consumed);

/**
 * @brief Take the UI subsystem down and unload the font it loaded.
 *
 * Idempotent, and never fails: it is called from teardown paths that are already
 * unwinding. Call it before the renderer goes.
 *
 * @param obj The backend to shut down; NULL is a no-op.
 */
void akbasic_ui_akgl_shutdown(akbasic_UiBackend *obj);

#endif // _AKBASIC_AKGL_H_
