// ============================================================================
// MRSH — Mesh Routing with Self-Healing
//
// Lightweight multi-hop mesh routing layer for T1 sensors (Heltec V4 /
// ESP32-S3 + SX1262). Sits on top of the existing LoRa packet plumbing;
// does NOT modify command_handler.* or uav_relay.* (owned elsewhere).
//
// Three LoRa msg_type bytes are introduced, deliberately above 0x0C to
// avoid colliding with the existing sensor protocol (0x01..0x09) and
// UAV relay (0x0A/0x0B):
//     MSG_TYPE_MESH_ROUTE     0x0D
//     MSG_TYPE_MESH_ACK       0x0E
//     MSG_TYPE_MESH_DISCOVER  0x0F
//
// Features:
//   - Neighbor table (up to 32 entries) in PSRAM, built from DISCOVER
//     broadcasts every 15 s.
//   - Per-packet forwarding with TTL, require-ack, store-and-forward,
//     urgent, and broadcast-if-orphan flags.
//   - End-to-end MESH_ACK with status byte + actual hop count + dest RSSI.
//   - Store-and-forward queue (64 slots, 30 s retry, 10 min max age).
//   - Self-assessment stats every 60 s.
//
// Build-out: define `PDL_ENABLE_MESH_ROUTING` in platformio.ini build_flags
// to include MRSH. When undefined, all entry points are no-ops.
// ============================================================================

#ifndef MESH_ROUTING_H
#define MESH_ROUTING_H

#include <cstdint>
#include <cstddef>

// ── Wire-protocol constants ─────────────────────────────────────────────
#define MSG_TYPE_MESH_ROUTE      0x0D
#define MSG_TYPE_MESH_ACK        0x0E
#define MSG_TYPE_MESH_DISCOVER   0x0F

#define MRSH_MAX_PAYLOAD         32     // bytes; matches STANAG 4586 frame size
// FIXED 2026-07-30: MRSH_ROUTE_HEADER_BYTES used to be defined as 23 while
// mesh_build_route_packet()/mesh_parse_route_packet() in mesh_routing.cpp
// actually always used a hardcoded 19 (msg_type[1]+packet_id[2]+src_hash[6]+
// dest_hash[6]+ttl[1]+flags[1]+next_hop_hint[1]+payload_len[1] = 19) for the
// real header/payload/CRC offsets, plus a "reserved padding" loop that
// zeroed bytes [19+payload_len, 23) and was then immediately overwritten by
// the CRC write at the same offset on every real call -- i.e. dead code
// that never had any observable effect. Every caller sizes its buffer off
// MRSH_ROUTE_MAX_BYTES (not the literal 23), so this was never an actual
// wire-format or overflow bug in this firmware -- just a constant/comment
// that didn't match the code, and 4 wasted bytes per stack buffer. Fixed by
// making this constant the actual header size (19) and having
// mesh_build_route_packet()/mesh_parse_route_packet() reference it instead
// of a separate hardcoded 19, so the two can no longer drift apart. The
// still-live risk this leaves: mesh_crc32() is documented as "public so the
// gateway Python side can match byte-for-byte" -- if an external gateway
// wire-format parser is ever written, it must match this ACTUAL 19-byte
// header, not the historical (wrong) 23-byte value some earlier note here
// may have been read from.
#define MRSH_ROUTE_HEADER_BYTES  19     // msg_type+packet_id+src_hash+dest_hash+ttl+flags+next_hop_hint+payload_len
#define MRSH_ROUTE_FOOTER_BYTES   4     // CRC32
#define MRSH_ROUTE_MAX_BYTES     (MRSH_ROUTE_HEADER_BYTES + MRSH_MAX_PAYLOAD + MRSH_ROUTE_FOOTER_BYTES)

#define MRSH_ACK_BYTES           16
#define MRSH_DISCOVER_BYTES      32

#define MRSH_TTL_DEFAULT          8
#define MRSH_NEIGHBOR_TABLE_SIZE 32
#define MRSH_STORE_FWD_SLOTS     64
#define MRSH_STORE_FWD_MAX_MS    (10UL * 60UL * 1000UL)

// Flag bits
#define MRSH_FLAG_REQUIRE_ACK       0x01
#define MRSH_FLAG_URGENT            0x02
#define MRSH_FLAG_STORE_AND_FORWARD 0x04
#define MRSH_FLAG_BROADCAST_ORPHAN  0x08

// Radio bitmap
#define MRSH_RADIO_LORA915        (1 << 0)
#define MRSH_RADIO_WIFI_2G4       (1 << 1)
#define MRSH_RADIO_SUB_GHZ_433    (1 << 2)
#define MRSH_RADIO_BT5            (1 << 3)
#define MRSH_RADIO_WIFI_5G        (1 << 4)

// ACK status codes
enum MrshAckStatus : uint8_t {
    MRSH_ACK_DELIVERED            = 0,
    MRSH_ACK_FORWARDED            = 1,
    MRSH_ACK_DROPPED_TTL          = 2,
    MRSH_ACK_NO_ROUTE             = 3,
    MRSH_ACK_STORE_FORWARD_QUEUED = 4,
};

// ── Neighbor-table entry ────────────────────────────────────────────────
struct NeighborEntry {
    char    node_id[16];        // 16-byte truncated SHA-1 text form
    int8_t  rssi_avg;           // rolling EWMA
    uint8_t snr_avg;            // scaled 0..63
    uint32_t last_seen_ms;
    uint8_t link_quality;       // 0..100 derived
    uint8_t hop_to_gateway;
    uint8_t radios_bitmap;      // MRSH_RADIO_* bits
    uint8_t active;             // 1 if entry occupied
};

// ── Self-assessment stats ───────────────────────────────────────────────
struct MrshStats {
    uint32_t pkts_forwarded_last_60s;
    uint32_t pkts_dropped_last_60s;
    uint32_t pkts_delivered_last_60s;
    uint8_t  active_neighbor_count;
    uint8_t  own_link_quality_avg;      // 0..100
    uint32_t store_forward_depth;
    uint32_t updated_at_ms;
};

// ── Public API ──────────────────────────────────────────────────────────

// Initialize mesh routing. Call once in setup() after LoRa is up.
// own_node_id must be a printable 16-char id (truncated SHA-1 hex).
// own_radios_bitmap is a bitwise OR of MRSH_RADIO_*.
void mesh_routing_init(const char* own_node_id, uint8_t own_radios_bitmap);

// Deliver a received LoRa packet to MRSH. Call from the main RX dispatcher.
// The caller passes msg_type (first byte) already examined — this fn expects
// the full buffer (including the msg_type byte at offset 0).
void mesh_handle_route_packet(const uint8_t* data, size_t len);
void mesh_handle_ack(const uint8_t* data, size_t len);
void mesh_handle_discover(const uint8_t* data, size_t len);

// Periodic ticks — call every loop() iteration. Internally no-op unless
// the relevant interval has elapsed.
void mesh_routing_tick();

// Get a read-only snapshot of local stats (copies into out).
void mesh_get_stats(MrshStats* out);

// Read neighbor table (returns active count, fills out up to max entries).
size_t mesh_get_neighbors(NeighborEntry* out, size_t max);

// Enqueue an application-layer payload for mesh delivery. Returns a
// packet_id (0 if enqueue failed). The mesh layer handles framing,
// next-hop selection, and ACK correlation.
uint16_t mesh_send(const char* dest_node_id,
                   const uint8_t* payload, size_t payload_len,
                   uint8_t flags);

// ── Framing helpers (public for unit tests) ─────────────────────────────

// Truncate a printable node_id (e.g., "heltec-v4-0a1b2c") to a stable
// 6-byte hash via simple FNV-1a. Writes exactly 6 bytes to out.
void mesh_node_hash(const char* node_id, uint8_t out[6]);

// CRC32 helper (public so the gateway Python side can match byte-for-byte).
uint32_t mesh_crc32(const uint8_t* data, size_t len);

// Build a MESH_ROUTE packet into out. Returns bytes written, 0 on error.
// out must be at least MRSH_ROUTE_MAX_BYTES. payload_len must be <= 32.
size_t mesh_build_route_packet(
    uint8_t* out, size_t out_max,
    uint16_t packet_id,
    const char* src_id, const char* dest_id,
    uint8_t ttl, uint8_t flags, uint8_t next_hop_hint,
    const uint8_t* payload, size_t payload_len);

// Build a MESH_ACK packet into out (must be >= 16 bytes). Returns 16 on success.
size_t mesh_build_ack_packet(
    uint8_t* out, size_t out_max,
    uint16_t packet_id,
    const char* acknowledger_id,
    uint8_t status, uint8_t hop_count_actual,
    int8_t rssi_at_dest);

// Build a MESH_DISCOVER broadcast into out (>= 32 bytes). Returns 32.
size_t mesh_build_discover_packet(
    uint8_t* out, size_t out_max,
    const char* own_id, uint8_t radios_bitmap,
    uint8_t hop_to_gateway, uint8_t own_link_quality);

// Verify + parse a MESH_ROUTE header. Returns true on success; fills fields.
struct MrshRouteHeader {
    uint16_t packet_id;
    uint8_t  src_hash[6];
    uint8_t  dest_hash[6];
    uint8_t  ttl;
    uint8_t  flags;
    uint8_t  next_hop_hint;
    uint8_t  payload_len;
    const uint8_t* payload;     // pointer into input buffer
    uint32_t crc32_claimed;
    uint32_t crc32_computed;
};

bool mesh_parse_route_packet(const uint8_t* data, size_t len, MrshRouteHeader* out);

// Public entry point that higher layers register to receive delivered payloads.
// The default handler tries uav_relay_handle(), then returns.
typedef void (*MrshDeliverCb)(const uint8_t* payload, size_t len);
void mesh_set_deliver_callback(MrshDeliverCb cb);

#endif // MESH_ROUTING_H
