// mlnl.h - mLnL annotation chunk parser (schema v2).
// Walks a WAV file's RIFF chunks looking for the optional `mLnL` chunk
// (UTF-8 JSON Lines), parses each line into an MlnlEvent. Every event is a
// self-contained time range [t_start, t_end] (instantaneous events have
// t_start == t_end). See mlnl_chunk_spec.md.
#ifndef MLNL_H
#define MLNL_H

#include <stdbool.h>

typedef enum {
    MLNL_KIND_UNKNOWN = 0,
    MLNL_KIND_CHANNEL_UP,
    MLNL_KIND_CHANNEL_DOWN,
    MLNL_KIND_CONTROL,
    MLNL_KIND_TX_FRAME,   // v2 primary annotation: one daemon-sourced on-air frame
    MLNL_KIND_TX_BURST,   // deprecated, energy-detected guess; superseded by tx_frame
    MLNL_KIND_ASSERTION_PASSED,
    MLNL_KIND_ASSERTION_FAILED,
    MLNL_KIND_IMPAIRMENT_FIRE,
    MLNL_KIND_GAIN_CHANGE,
} MlnlKind;

typedef struct {
    // Required.
    double t_start, t_end;
    MlnlKind kind;
    char kindStr[32];   // original "kind" string (for unknown kinds + display)

    // Frequency annotation (resolved from f_lo/f_hi OR f_center/f_bw).
    double f_lo_hz, f_hi_hz;
    bool has_freq;

    // Optional human/visual hints.
    char note[96];
    bool has_note;
    unsigned char colorR, colorG, colorB;  // parsed from "#RRGGBB"
    bool has_color;

    // Kind-specific fields. The has_* flag says whether the producer set it.
    unsigned int node;
    bool has_node;
    char command[64];
    bool has_command;
    char name[128];                  // assertion name
    bool has_name;
    char reason[96];                 // assertion_failed reason
    bool has_reason;
    double peak, rms, papr_db;       // tx_burst
    bool has_stats;
    double slack_s;                  // assertion_passed
    bool has_slack;
    unsigned int id;                 // tx_burst: monotonic per-wav identifier
    bool has_id;
    char label[32];                  // tx_burst: optional frame tag (SEED/BULK/ACK/...)
    bool has_label;

    // tx_frame fields (schema v2 primary annotation). `node`/`has_node` above
    // carry the transmitting node_id (attribution + color).
    int seq, nFrames;                // 0-based index + total frames in the PTT ("seq of n")
    bool has_seqn;                   // set once `n` is present
    char frame[32];                  // mLink frame name (PRESENCE/SEED, BULK, BULK/RTS, ...)
    bool has_frame;
    char ch[16];                     // daemon channel (EMERGENCY / ANNOUNCE_0|1|2 / BULK)
    bool has_ch;
    char rate[8];                    // LDPC rate of a bulk OFDM frame ("1/2".."5/6"); else empty
    bool has_rate;
    // intent->air latency of this burst: air_time - modem render/intent time.
    // The producer already anchors t_start/t_end on the ACTUAL on-air edge, so
    // this is a profiling metric ("modem wants vs. actually does"), not a
    // plotting offset. Coarse/block-quantized. See mlnl_chunk_spec.md §4.2.
    double sched_offset_ms;
    bool has_sched_offset;
} MlnlEvent;

// Up to this many distinct MlnlKind values are tracked per-file (any new kinds
// added to the enum must bump this). Indexed by the MlnlKind value.
#define MLNL_KIND_MAX 16

typedef struct {
    MlnlEvent* events;
    int eventCount;
    bool truncated;                   // {"truncated":true} sentinel appeared
    bool loaded;                      // true iff the file contained a parseable mLnL chunk
    bool kindPresent[MLNL_KIND_MAX];  // which kinds the parser saw (drives the UI)
} MlnlAnnotations;

// Walk `path`'s RIFF chunks looking for `mLnL` and parse the JSONL payload.
// Returns true on success (out->loaded is set the same way). On failure
// (no chunk / unreadable / not WAV) returns false and zeroes *out.
bool LoadMlnlFromWav(const char* path, MlnlAnnotations* out);

void FreeMlnl(MlnlAnnotations* a);

// Display label for an MlnlKind.
const char* MlnlKindName(MlnlKind k);

#endif // MLNL_H
