// puzzle_binding.h — extract a step's optional puzzle binding from Runtime 3 IR.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"

#define PB_MAX_CODES   16   // = SEQ_MAX_STEPS
#define PB_MAX_LABEL   32   // = SEQ_MAX_LABEL
#define PB_MAX_NOTES   32   // = MELODY_MAX_NOTES
#define PB_MAX_FRAG     4   // = PUZZLE_MAX_FRAG
#define PB_MAX_PUZZLE_ID 8  // = PUZZLE_MAX_ID

typedef enum { PB_NONE = 0, PB_QR, PB_SOUND, PB_MORSE, PB_NFC } puzzle_binding_type_t;

typedef struct {
    puzzle_binding_type_t type;     // PB_NONE if the step has no puzzle
    uint8_t id;                     // 1..8
    // qr
    char    codes[PB_MAX_CODES][PB_MAX_LABEL];
    size_t  code_count;
    // sound
    int     melody[PB_MAX_NOTES];
    size_t  note_count;
    int     tolerance;              // default 1
    // morse (P5): expected word in uppercase, e.g. "ZACUS"
    char    morse_expected[32];
    // nfc (P6): expected tag UID as colon-hex string, e.g. "A1:B2:C3:D4"
    char    nfc_uid[20];
    // common
    uint8_t fragment[PB_MAX_FRAG];
    uint8_t fragment_len;
} puzzle_binding_t;

// Parse `ir_json` (full Runtime 3 IR), locate step `step_id`, fill `out`.
// ESP_OK: step found ('out->type' PB_NONE if it has no/empty puzzle object).
// ESP_ERR_NOT_FOUND: no step with that id (or no steps array).
// ESP_ERR_INVALID_ARG: malformed JSON, or puzzle object violating constraints
//   (bad type string, id out of range, too many/zero codes|notes, label too
//   long, fragment missing/empty/too long/non-digit values, negative tolerance).
// NOTE: Parsing a full 64KB IR allocates transient heap via cJSON — call
//   sparingly (e.g. per step change, not per frame).
// Tolerance defaults to 1 when absent (sound only).
esp_err_t puzzle_binding_from_ir(const char *ir_json, const char *step_id,
                                 puzzle_binding_t *out);

// ─── Scene metadata (display) ────────────────────────────────────────────────
// Optional per-step `scene` object mirroring the original ui_manager scene
// frame: {"title": "...", "subtitle": "...", "symbol": "...",
//         "effect": "pulse"|"glitch"|"gyro"|"none"}.
// LENIENT parsing (unlike the strict puzzle constraints): scene fields are
// display decoration and must never block a step change — over-long strings
// are silently TRUNCATED, an unknown effect falls back to pulse.

#define SB_MAX_TITLE    48
#define SB_MAX_SUBTITLE 64
#define SB_MAX_SYMBOL   16
#define SB_MAX_SCENE_ID 40

typedef enum {
    SB_FX_PULSE = 0,   // default (original SceneEffect::kPulse)
    SB_FX_GLITCH,
    SB_FX_GYRO,
    SB_FX_NONE,
} scene_effect_t;

typedef struct {
    bool present;                     // step has a scene object
    char scene_id[SB_MAX_SCENE_ID];   // canonical SCENE_* id (sibling of step id;
                                      // set even when no display scene object)
    char title[SB_MAX_TITLE];
    char subtitle[SB_MAX_SUBTITLE];
    char symbol[SB_MAX_SYMBOL];
    scene_effect_t effect;
} scene_binding_t;

// Same return contract as puzzle_binding_from_ir for ESP_OK/NOT_FOUND/
// INVALID_ARG(malformed JSON only — scene content itself never fails).
esp_err_t scene_binding_from_ir(const char *ir_json, const char *step_id,
                                scene_binding_t *out);
