#pragma once

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <stdexcept>

// ─── Protocol constants (matching UDPStreamer wire format) ──────────

constexpr uint32_t UDPS_MAGIC          = 0x53504455u; // 'UDPS' LE
constexpr uint8_t  UDPS_TYPE_DATA       = 0u;
constexpr uint8_t  UDPS_TYPE_CONFIG     = 1u;
constexpr uint8_t  UDPS_TYPE_ACK        = 2u;
constexpr uint8_t  UDPS_TYPE_CONNECT    = 3u;
constexpr uint8_t  UDPS_TYPE_DISCONNECT = 4u;
constexpr size_t   UDPS_HEADER_SIZE     = 17;
constexpr size_t   UDPS_SIGDESC_SIZE    = 136;

// Type codes (matching Go protocol.go)
constexpr uint8_t  TC_UINT8   = 0u;
constexpr uint8_t  TC_INT8    = 1u;
constexpr uint8_t  TC_UINT16  = 2u;
constexpr uint8_t  TC_INT16   = 3u;
constexpr uint8_t  TC_UINT32  = 4u;
constexpr uint8_t  TC_INT32   = 5u;
constexpr uint8_t  TC_UINT64  = 6u;
constexpr uint8_t  TC_INT64   = 7u;
constexpr uint8_t  TC_FLOAT32 = 8u;
constexpr uint8_t  TC_FLOAT64 = 9u;

// Quantisation types
constexpr uint8_t  QUANT_NONE   = 0u;
constexpr uint8_t  QUANT_UINT8  = 1u;
constexpr uint8_t  QUANT_INT8   = 2u;
constexpr uint8_t  QUANT_UINT16 = 3u;
constexpr uint8_t  QUANT_INT16  = 4u;

// Time modes
constexpr uint8_t  TM_PACKET       = 0u;
constexpr uint8_t  TM_FULL_ARRAY   = 1u;
constexpr uint8_t  TM_FIRST_SAMPLE = 2u;
constexpr uint8_t  TM_LAST_SAMPLE  = 3u;

constexpr uint32_t NO_TIME_SIGNAL = 0xFFFFFFFFu;

#pragma pack(push, 1)
struct UDPSPacketHeader {
    uint32_t magic;
    uint8_t  type;
    uint32_t counter;
    uint16_t fragment_idx;
    uint16_t total_fragments;
    uint32_t payload_bytes;
};

struct UDPSSignalInfo {
    char     name[64];
    uint8_t  type_code;
    uint8_t  quant_type;
    uint8_t  num_dimensions;
    uint32_t num_rows;
    uint32_t num_cols;
    double   range_min;
    double   range_max;
    uint8_t  time_mode;
    double   sampling_rate;
    uint32_t time_signal_idx;
    char     unit[32];
};
#pragma pack(pop)

static_assert(sizeof(UDPSPacketHeader) == 17, "Header must be 17 bytes");
static_assert(sizeof(UDPSSignalInfo) == 136, "Signal desc must be 136 bytes");

// ─── Parsed signal metadata ─────────────────────────────────────────

struct SignalDef {
    std::string name;
    uint8_t     type_code      = 0;
    uint8_t     quant_type     = 0;
    uint32_t    num_elements   = 0;  // rows * cols
    uint32_t    num_rows       = 1;
    uint32_t    num_cols       = 1;
    double      range_min      = 0.0;
    double      range_max      = 0.0;
    uint8_t     time_mode      = TM_PACKET;
    double      sampling_rate  = 0.0;
    uint32_t    time_signal_idx = NO_TIME_SIGNAL;
    std::string unit;

    int element_byte_size() const;
};

// ─── One DATA packet worth of samples ───────────────────────────────

struct DataPacket {
    uint64_t hrt_timestamp = 0;
    // Per-signal float values (always float64 after dequantisation)
    std::vector<std::vector<double>> values;
};

// ─── Parser functions ───────────────────────────────────────────────

/** Validate a UDP datagram header. Returns false if magic or size is wrong. */
bool parse_header(const uint8_t* buf, size_t len, UDPSPacketHeader& hdr);

/** Parse a reassembled CONFIG payload into a signal list. */
std::vector<SignalDef> parse_config(const uint8_t* payload, size_t len);

/** Parse a reassembled DATA payload into float64 values per signal. */
DataPacket parse_data(const uint8_t* payload, size_t len,
                      const std::vector<SignalDef>& sig_defs);

/** Build a 17-byte CONNECT packet. */
std::vector<uint8_t> build_connect();

/** Build a 17-byte DISCONNECT packet. */
std::vector<uint8_t> build_disconnect();
