// game_endpoint — REST surface for runtime game configuration.
//
// Slice 12 of the IDF migration. Today this exposes a single resource:
//
//   GET  /game/group_profile  — read the active hints group profile.
//   POST /game/group_profile  — set a new profile, persist to NVS, and
//                                push it to the hints_client so the
//                                next /hints/ask body carries it.
//
// The handlers are attached to the existing esp_http_server instance
// owned by the ota_server component (port 80) — same pattern as
// voice_hook_endpoint. No second TCP socket, no second worker pool.
//
// NVS persistence: namespace "zacus", key "group_profile". This is the
// same key main.c reads at boot to seed the hints client, so a
// successful POST survives reboot without any flash step.
//
// Validation is delegated to hints_client_set_group_profile() which
// already enforces the "TECH" / "NON_TECH" / "MIXED" / "BOTH" whitelist.
// On invalid input the NVS write is skipped and the client keeps its
// previous value.

#pragma once

#include "esp_err.h"
#include "esp_http_server.h"
#include "puzzle_state.h"
#include "puzzle_binding.h"   // scene_binding_t (display scene metadata)

#ifdef __cplusplus
extern "C" {
#endif

// Cap the request body so a malformed PLIP / dashboard client cannot
// blow up the worker stack. 256 bytes is plenty for {"group_profile":
// "NON_TECH"} (~30 bytes) plus future additive fields.
#define GAME_ENDPOINT_MAX_BODY_BYTES 256

// Larger cap for the Runtime 3 IR scenario blob. 64 KiB lets a
// reasonable escape-room scenario (~50 steps, dialogues + actions)
// fit comfortably. Scenarios that exceed this should be split
// across multiple boards or trimmed.
#define GAME_ENDPOINT_MAX_SCENARIO_BYTES (64 * 1024)

// LittleFS partition label declared in partitions.csv. game_endpoint
// mounts lazily on first scenario POST. media_manager may also mount
// the same label — esp_vfs_littlefs_register is idempotent per label.
#define GAME_ENDPOINT_STORAGE_LABEL  "storage"
// main.c mounts the storage partition at /littlefs at boot — we reuse the
// same mount point instead of registering a second base path for the same
// partition (which fails silently with INVALID_STATE).
#define GAME_ENDPOINT_STORAGE_BASE   "/littlefs"
#define GAME_ENDPOINT_SCENARIO_PATH  GAME_ENDPOINT_STORAGE_BASE "/scenario.json"
#define GAME_ENDPOINT_SCENARIO_BAK   GAME_ENDPOINT_STORAGE_BASE "/scenario.bak"

/**
 * @brief Attach all game endpoint handlers to an existing esp_http_server.
 *
 * Registers:
 *   - GET/POST /game/group_profile  (slice 12, runtime hints profile)
 *   - POST /game/scenario           (slice 13, Runtime 3 IR hot-load)
 *   - POST /game/step               (Task C, REST-driven puzzle arming)
 *   - GET  /game/puzzle_state       (Task C, puzzle state readout)
 *
 * Pass the handle returned by `ota_server_get_handle()`. Returns
 * ESP_ERR_INVALID_ARG if `server` is NULL, or any error propagated
 * from `httpd_register_uri_handler()`.
 */
esp_err_t game_endpoint_init(httpd_handle_t server);

/**
 * @brief Give the endpoint read access to the master's puzzle aggregation
 *        state and enable GET /game/puzzle_state + POST /game/step.
 *        Call once at boot, right after game_endpoint_init succeeds.
 */
void game_endpoint_set_puzzle_state(puzzle_state_t *state);

/**
 * @brief Thread-safe snapshot of the current step id and armed puzzle type.
 *
 * Copies the last step armed via POST /game/step into caller-supplied buffers.
 * Both buffers are NUL-terminated on return.  Either pointer may be NULL if
 * the caller does not need that field.
 *
 * Returns "" / "" when no step has been armed yet.
 *
 * @param step_id   Output buffer for the current step id.
 * @param step_cap  Capacity of step_id buffer (recommend 64).
 * @param armed     Output buffer: "qr" | "sound" | "none" (or "" = nothing yet).
 * @param armed_cap Capacity of armed buffer (recommend 8).
 */
void game_endpoint_get_puzzle_status(char *step_id, size_t step_cap,
                                     char *armed,   size_t armed_cap);

/**
 * @brief Snapshot the display scene metadata of the current step.
 *
 * Filled from the step's optional `scene` IR object at POST /game/step time
 * (lenient parse — see puzzle_binding.h). Zeroed when no step is armed or a
 * new scenario is pushed. Lock-free copy; cosmetic torn reads possible.
 */
void game_endpoint_get_scene(scene_binding_t *out);

/**
 * @brief Apply a scenario step internally (same logic as POST /game/step).
 *
 * Used by local triggers (e.g. shell app launch on the device display).
 * May block up to ~250 ms (QR re-arm retry). Error contract documented at
 * the definition (NOT_SUPPORTED=no scenario, NOT_FOUND=unknown step,
 * TIMEOUT=puzzle busy, INVALID_STATE=not ready, INVALID_ARG=bad IR puzzle).
 *
 * @param step_id    Step to arm (non-empty).
 * @param armed_out  Optional out: "qr"|"sound"|"none".
 * @param armed_cap  Capacity of armed_out.
 */
esp_err_t game_endpoint_apply_step(const char *step_id,
                                   char *armed_out, size_t armed_cap);

#ifdef __cplusplus
}
#endif
