#pragma once

#include "net.h"
#include "str.h"

// MQTT packet type codes. The received command is in mg_mqtt_message.cmd.
#define MQTT_CMD_CONNECT 1
#define MQTT_CMD_CONNACK 2
#define MQTT_CMD_PUBLISH 3
#define MQTT_CMD_PUBACK 4
#define MQTT_CMD_PUBREC 5
#define MQTT_CMD_PUBREL 6
#define MQTT_CMD_PUBCOMP 7
#define MQTT_CMD_SUBSCRIBE 8
#define MQTT_CMD_SUBACK 9
#define MQTT_CMD_UNSUBSCRIBE 10
#define MQTT_CMD_UNSUBACK 11
#define MQTT_CMD_PINGREQ 12
#define MQTT_CMD_PINGRESP 13
#define MQTT_CMD_DISCONNECT 14
#define MQTT_CMD_AUTH 15

// MQTT5 property IDs. Used in mg_mqtt_prop.id.
#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
#define MQTT_PROP_CONTENT_TYPE 0x03
#define MQTT_PROP_RESPONSE_TOPIC 0x08
#define MQTT_PROP_CORRELATION_DATA 0x09
#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
#define MQTT_PROP_AUTHENTICATION_DATA 0x16
#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
#define MQTT_PROP_SERVER_REFERENCE 0x1C
#define MQTT_PROP_REASON_STRING 0x1F
#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
#define MQTT_PROP_TOPIC_ALIAS 0x23
#define MQTT_PROP_MAXIMUM_QOS 0x24
#define MQTT_PROP_RETAIN_AVAILABLE 0x25
#define MQTT_PROP_USER_PROPERTY 0x26
#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A

// Property value types used internally to encode/decode MQTT5 properties.
enum {
  MQTT_PROP_TYPE_BYTE,
  MQTT_PROP_TYPE_STRING,
  MQTT_PROP_TYPE_STRING_PAIR,
  MQTT_PROP_TYPE_BINARY_DATA,
  MQTT_PROP_TYPE_VARIABLE_INT,
  MQTT_PROP_TYPE_INT,
  MQTT_PROP_TYPE_SHORT
};

// Return codes from mg_mqtt_parse().
enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };

// A single MQTT5 property. Use in mg_mqtt_opts.props / mg_mqtt_opts.will_props
// arrays, and iterate received properties with mg_mqtt_next_prop().
struct mg_mqtt_prop {
  uint8_t id;         // Property ID, one of MQTT_PROP_*
  uint32_t iv;        // Integer value for BYTE, SHORT, INT, VARIABLE_INT types
  struct mg_str key;  // Property key string; only set for USER_PROPERTY pairs
  struct mg_str val;  // String/binary value; set for STRING, BINARY_DATA, and USER_PROPERTY
};

// Options passed to mg_mqtt_connect(), mg_mqtt_pub(), mg_mqtt_sub(),
// mg_mqtt_unsub(), and mg_mqtt_disconnect(). Zero-initialise and set only
// the fields relevant to the operation being called.
struct mg_mqtt_opts {
  struct mg_str user;               // CONNECT: username; empty = omit
  struct mg_str pass;               // CONNECT: password; empty = omit
  struct mg_str client_id;          // CONNECT: client ID; empty = auto-generate
  struct mg_str topic;              // CONNECT: will topic; PUBLISH/SUBSCRIBE/UNSUBSCRIBE: topic
  struct mg_str message;            // CONNECT: will payload; PUBLISH: message payload
  uint8_t qos;                      // QoS level: 0, 1, or 2
  uint8_t version;                  // MQTT version: 4 (3.1.1) or 5; 0 defaults to 4
  uint16_t keepalive;               // CONNECT: keepalive interval in seconds; 0 = no timeout
  uint16_t retransmit_id;           // PUBLISH: packet ID for retransmit; init to 0 for new message
  bool retain;                      // PUBLISH/CONNECT will: set the retain flag
  bool clean;                       // CONNECT: start a clean session
  struct mg_mqtt_prop *props;       // MQTT5: properties array; NULL if none
  size_t num_props;                 // MQTT5: number of entries in props
  struct mg_mqtt_prop *will_props;  // MQTT5 CONNECT: will properties; NULL if none
  size_t num_will_props;            // MQTT5: number of entries in will_props
};

// Received MQTT message. Passed as ev_data for MG_EV_MQTT_MSG (PUBLISH),
// MG_EV_MQTT_CMD (all packets), and MG_EV_MQTT_OPEN (CONNACK).
// topic, data, and dgram are zero-copy slices into the receive buffer; they
// are only valid until the next mg_mgr_poll() call.
struct mg_mqtt_message {
  struct mg_str topic;  // PUBLISH: message topic; slice into dgram
  struct mg_str data;   // PUBLISH: message payload; slice into dgram
  struct mg_str dgram;  // Complete raw MQTT packet, including fixed header
  uint16_t id;          // Packet ID; set for QoS>0 PUBLISH, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK
  uint8_t cmd;          // Packet type, one of MQTT_CMD_*
  uint8_t qos;          // QoS level extracted from the PUBLISH fixed header
  uint8_t ack;          // CONNACK return code; 0 = success
  size_t props_start;   // MQTT5: byte offset from dgram.buf to start of properties
  size_t props_size;    // MQTT5: byte length of the properties section
};

// Opens an MQTT client connection and sends a CONNECT packet.
//
// Returns:
//   Client connection, or NULL on error.
// Example:
//   struct mg_mqtt_opts opts = {.client_id = mg_str("device1")};
//   mg_mqtt_connect(&mgr, "mqtt://broker:1883", &opts, fn, NULL);
// Full examples:
//   tutorials/mqtt/mqtt-client, tutorials/mqtt/mqtt-client-aws-iot,
//   tutorials/mqtt/ota-over-mqtt
// Related APIs:
//   mg_mqtt_pub(), mg_mqtt_sub(), mg_tls_init(), mg_mgr_poll()
// Notes:
//   opts may be NULL to use MQTT 3.1.1 defaults with no auth and an auto client
//   ID. The user-supplied fn event handler receives normal connection events,
//   MG_EV_MQTT_OPEN when CONNACK is received, MG_EV_MQTT_MSG for incoming
//   PUBLISH packets, and MG_EV_MQTT_CMD for every received MQTT packet.
struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
                                      const struct mg_mqtt_opts *opts,
                                      mg_event_handler_t fn, void *fn_data);

// Creates an MQTT server listener on url.
// Fires MG_EV_MQTT_CMD for each complete packet received (ev_data: struct mg_mqtt_message *).
// Returns NULL on error.
struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
                                     mg_event_handler_t fn, void *fn_data);

// Sends a CONNECT packet on an already-open connection.
// Called automatically by mg_mqtt_connect(); exposed for advanced use such
// as re-authenticating on an existing connection.
void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts);

// Sends an MQTT PUBLISH packet.
//
// Returns:
//   0 for QoS 0; assigned packet ID for QoS 1 or 2.
// Example:
//   struct mg_mqtt_opts opts = {
//       .topic = mg_str("device/status"),
//       .message = mg_str("{\"ok\":true}"),
//       .qos = 1,
//   };
//   uint16_t id = mg_mqtt_pub(c, &opts);
// Full examples:
//   tutorials/mqtt/mqtt-client, tutorials/mqtt/mqtt-client-aws-iot,
//   tutorials/mqtt/ota-over-mqtt
// Related APIs:
//   mg_mqtt_connect(), mg_mqtt_sub(), mg_mqtt_send_header()
// Notes:
//   On success, opts.topic and opts.message are copied into c->send before the
//   function returns. To retransmit a QoS message, set opts.retransmit_id to the
//   packet ID returned by the previous call; use 0 for a new message.
uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts);

// Sends an MQTT SUBSCRIBE packet.
//
// Example:
//   struct mg_mqtt_opts opts = {.topic = mg_str("device/rx"), .qos = 1};
//   mg_mqtt_sub(c, &opts);
// Full examples:
//   tutorials/mqtt/mqtt-client, tutorials/mqtt/mqtt-client-aws-iot,
//   tutorials/mqtt/ota-over-mqtt
// Related APIs:
//   mg_mqtt_connect(), mg_mqtt_pub(), mg_mqtt_unsub()
// Notes:
//   Send after MG_EV_MQTT_OPEN reports a successful CONNACK. Incoming PUBLISH
//   messages are delivered to the user-supplied fn event handler as
//   MG_EV_MQTT_MSG with ev_data pointing to struct mg_mqtt_message.
void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts);

// Sends an UNSUBSCRIBE packet for opts.topic.
void mg_mqtt_unsub(struct mg_connection *c, const struct mg_mqtt_opts *opts);

// Parses a raw MQTT packet in buf/len. version must be 4 (MQTT 3.1.1) or 5.
// On success, fills *m; topic, data, and dgram slice into buf (zero-copy).
// Returns MQTT_OK, MQTT_INCOMPLETE (need more data), or MQTT_MALFORMED.
int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *);

// Low-level helper: sends the MQTT fixed header (command byte and
// variable-length remaining-length field). Call before writing the PDU body.
// cmd: MQTT_CMD_*; flags: low 4 bits of the first byte; len: remaining length.
void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags,
                         uint32_t len);

// Sends a PINGREQ packet.
void mg_mqtt_ping(struct mg_connection *);

// Sends a PINGRESP packet.
void mg_mqtt_pong(struct mg_connection *);

// Sends a DISCONNECT packet. For MQTT5, opts.props can carry reason
// properties. opts may be NULL for a plain disconnect with no properties.
void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *);

// Iterates MQTT5 properties in a received message. Start with ofs=0; pass
// the returned value back on each successive call. Returns 0 when done.
// Fills *prop with each property; key and val are zero-copy slices into msg->dgram.
size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *,
                         size_t ofs);
