#ifndef COMMAND_HANDLER_H
#define COMMAND_HANDLER_H

#include <cstdint>
#include <cstddef>
#include "config.h"

// ============================================================================
// T1 Sensor Command Handler
//
// Parses 0x05 COMMAND packets received over LoRa from T2 gateway, dispatches
// to the appropriate subsystem, and emits 0x06 CMD_ACK packets back to the
// controller. See t2-gateway/.../sensor_commander.py for the packet producer.
//
// All parsing is deterministic, non-blocking, and safe to call from inside
// the main loop RX path. No dispatcher blocks — REBOOT schedules via a flag
// that the main loop polls.
// ============================================================================

// ---- Wire format sizes ----
#define CMD_PACKET_SIZE       32   // full COMMAND packet on the wire
#define CMD_ACK_PACKET_SIZE   16   // full CMD_ACK packet on the wire
#define CMD_PARAMS_SIZE       20   // per-command parameter bytes
#define CMD_ACK_RESULT_SIZE   5    // per-ack compact result bytes

// ---- Broadcast sentinel (matches sensor_commander.py) ----
#define CMD_BROADCAST_SENSOR_ID 0xFFFFu

// ---- Flag bits ----
#define CMD_FLAG_REQUIRE_ACK  0x01u
#define CMD_FLAG_URGENT       0x02u
#define CMD_FLAG_BROADCAST    0x04u

// ---- Command type enum (must match T2 exactly) ----
enum class SensorCmdType : uint8_t {
    NOOP                 = 0,
    FOCUS_FREQ           = 1,
    SET_THRESHOLD        = 2,
    ENTER_STEALTH        = 3,
    EXIT_STEALTH         = 4,
    FULL_SPECTRUM_DUMP   = 5,
    SELF_TEST            = 6,
    REBOOT               = 7,
    UPDATE_MODEL_SLOT    = 8,
    UPDATE_WHITELIST     = 9,
    SET_TX_POWER         = 10,
    RESET_FFPS           = 11,
    REPORT_STATUS        = 12,
    RESET_MARKOV_TRACK   = 13,  // params: u32 track_id
    SET_MARKOV_THRESHOLD = 14,  // params: f32 threshold (nats)
    CACTUS_RELOAD        = 15,  // params: char path[20] (".cactus" file on LittleFS)
    WFX_DUMP_LATEST      = 16,  // params: u8 force_compute (0/1)
    DELIVER_PAYLOAD      = 17,  // params: u32 pid|6B bssid|u8 ch|u8 mode|u16 repeat|u16 dwell_ms|u32 reserved
    SET_LOCATION         = 19,  // params: i32 lat|i32 lon (NVS persist)
    NETWORK_SCAN_DUMP    = 21,  // params: u8 force (0/1) — emit MSG_TYPE_NETWORK_LIST now
    INTEROP_SEND         = 22,  // params: u8 proto|u8 plen|payload (proto: 1=Meshtastic, 2=RNode)
    RADIO_MODE_SET       = 23,  // params: u8 pdl_pct|u8 mesh_pct|u8 rnode_pct (sum=100)
    MQTT_PROVISION       = 24,  // params: u8 field_id|u8 vlen|value bytes (NVS pdl_mqtt/*)
    MESHTASTIC_PROVISION = 25,  // params: u8 enabled|char name[12]
    RNODE_PROVISION      = 26,  // params: u8 enabled
};

// ---- ACK status codes (must match T2) ----
enum class CmdAckStatus : uint8_t {
    OK       = 0,
    REJECTED = 1,  // target-valid but command refused (e.g. bad magic byte)
    INVALID  = 2,  // malformed parameters / unknown cmd_type
    FAILED   = 3,  // dispatcher attempted but subsystem errored
    BUSY     = 4,  // cannot process right now (e.g. FFPS in-flight)
};

// ---- In-memory parsed packet structures ----
struct CmdPacket {
    uint16_t target_sensor_id;  // 0xFFFF = broadcast
    uint16_t cmd_id;            // correlation id
    uint8_t  cmd_type;          // SensorCmdType
    uint8_t  flags;             // CMD_FLAG_*
    uint8_t  param_len;         // 0..CMD_PARAMS_SIZE
    uint8_t  params[CMD_PARAMS_SIZE];
};

struct AckPacket {
    uint16_t sensor_id;                         // this sensor's id
    uint16_t cmd_id;                            // echoed from CmdPacket
    uint8_t  status;                            // CmdAckStatus
    uint8_t  result_code;                       // command-specific
    uint8_t  result_data[CMD_ACK_RESULT_SIZE];  // command-specific payload
};

// ---------------------------------------------------------------------------
// Initialize the command handler. Must be called from setup() before any
// LoRa RX begins. own_sensor_id is used for target-check and as the sensor_id
// field in emitted ACK packets. For T1 sensors whose ID is a uint32, the low
// 16 bits are used.
// ---------------------------------------------------------------------------
void commandHandlerInit(uint16_t own_sensor_id);

// ---------------------------------------------------------------------------
// Parse a raw 32-byte packet into a CmdPacket. Returns false if:
//  - len != CMD_PACKET_SIZE
//  - first byte is not MSG_TYPE_COMMAND (0x05)
//  - embedded CRC32 doesn't match
//  - param_len > CMD_PARAMS_SIZE
// No side effects.
// ---------------------------------------------------------------------------
bool parseCmdPacket(const uint8_t* data, size_t len, CmdPacket& out);

// ---------------------------------------------------------------------------
// Serialize an AckPacket into a 16-byte buffer (including MsgType prefix
// and trailing CRC32). Returns the number of bytes written (16 on success, 0
// if buf_len is too small).
// ---------------------------------------------------------------------------
size_t serializeAckPacket(const AckPacket& ack, uint8_t* out_buf, size_t buf_len);

// ---------------------------------------------------------------------------
// Full RX entrypoint: parse, target-check, dispatch, and transmit an ACK on
// the shared LoRa radio (via mesh_publish_raw) if required by protocol rules.
// Call this from the LoRa RX path after checking data[0] == MSG_TYPE_COMMAND.
// Safe to call with any buffer; invalid packets are silently dropped.
// ---------------------------------------------------------------------------
void handleIncomingCommand(const uint8_t* data, size_t len);

// ---------------------------------------------------------------------------
// Dispatch a parsed command to the relevant subsystem and fill ack_out with
// the status + compact result. Returns true if the command was recognized
// and at least attempted (false only for unknown cmd_type, in which case
// ack_out.status == INVALID).
// ---------------------------------------------------------------------------
bool dispatchCommand(const CmdPacket& pkt, AckPacket& ack_out);

// ---------------------------------------------------------------------------
// Reboot scheduling. REBOOT command sets a pending flag + absolute millis
// deadline; the main loop polls these and calls esp_restart() when due.
// This keeps the command path non-blocking.
// ---------------------------------------------------------------------------
bool commandHandlerPendingReboot();
uint32_t commandHandlerRebootAt();  // millis() deadline; 0 if none pending

// ---------------------------------------------------------------------------
// Stealth mode helper. Public so other modules can react (e.g. mesh_publish
// could inspect current stealth state before higher-power TX). Called from
// within the command dispatcher when ENTER_STEALTH / EXIT_STEALTH fire.
// duration_ms == 0 on ENTER_STEALTH means "stay in stealth until EXIT_STEALTH".
// ---------------------------------------------------------------------------
void setStealthMode(uint8_t tx_power_dbm, uint32_t duration_ms);
bool stealthModeActive();

#endif // COMMAND_HANDLER_H
