/**
 * @file qdx_tcp_logic.c
 * @brief TCP Network Logic Implementation for MCU
 */

#include "qdx_tcp_logic.h"
#include "qdx_port.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>

/* ============================================================
 * Internal State & Configuration Cache
 * ============================================================ */

#define RECV_BUF_SIZE 2048
#define MAX_FRAGMENT_PAYLOAD 1400
#define HEARTBEAT_INTERVAL_MS 2000
#define SERVER_TIMEOUT_MS 6000
#define RECONNECT_DELAY_MS 3000

typedef struct {
  qdx_socket_t sock;
  uint32_t last_activity_ms;
  uint32_t last_heartbeat_ms;
  uint32_t last_reconnect_ms;
  uint16_t sequence;
  uint8_t is_connected;
  uint8_t is_running;
  const char *label;
  uint8_t recv_buffer[RECV_BUF_SIZE * 2];
  uint32_t recv_len;
} TcpStreamCtx_t;

static struct {
  uint8_t uuid[16];
  uint8_t auth_token[16];
  uint16_t dev_id;
  int32_t pending_new_dev_id;
  uint32_t frame_count;

  TcpStreamCtx_t control_stream;
  TcpStreamCtx_t data_stream;

  qdx_mutex_t config_mutex;
  uint8_t has_valid_config;
  ConfigCommon_t cached_common;
  Config2D_t cached_cfg2d;
  Config1D_t cached_cfg1d;

  ConfigUpdateCallback_t config_cb;
  DetectionResultCallback_t detect_cb;
  TempFrameRequestCallback_t temp_req_cb;
} g_TcpLogic;

/* Server endpoint prototype - user would configure these, but we map to demo
 * defaults */
static const char *SERVER_IP = "192.168.7.50";
static const uint16_t CONTROL_PORT = 5511;
static const uint16_t DATA_PORT = 5512;

/* ============================================================
 * Internal Helpers
 * ============================================================ */

static void tcp_stream_init(TcpStreamCtx_t *ctx, const char *label) {
  memset(ctx, 0, sizeof(TcpStreamCtx_t));
  ctx->label = label;
}

static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) {
  DBG_LOGIC("[%s] disconnecting\r\n", ctx->label);
  ctx->is_connected = 0;
  if (ctx->sock) {
    qdx_port_tcp_close(ctx->sock);
    ctx->sock = NULL;
  }
}

static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip,
                                 uint16_t port) {
  DBG_LOGIC("[%s] connecting %s:%d...\r\n", ctx->label, ip, port);
  ctx->sock = qdx_port_tcp_connect(ip, port);
  if (ctx->sock == NULL) {
    DBG_LOGIC("[%s] connect FAILED\r\n", ctx->label);
    return -1;
  }

  ctx->is_connected = 1;
  ctx->last_activity_ms = qdx_port_get_tick_ms();
  ctx->last_heartbeat_ms = ctx->last_activity_ms;
  ctx->recv_len = 0;
  DBG_LOGIC("[%s] connected OK\r\n", ctx->label);
  return 0;
}

static int32_t tcp_send_frame(TcpStreamCtx_t *ctx, uint8_t msg_class,
                              uint8_t tlv_type, const uint8_t *payload,
                              uint16_t payload_len, uint8_t flags) {
  if (!ctx->is_connected || ctx->sock == NULL)
    return -1;

  /* Max frame buffer for generic small control/heartbeat messages.
     Note: Images use BuildAndSendTemperatureFrame instead of this. */
  uint8_t buffer[1024];
  if (HEADER_SIZE + TLV_HEADER_SIZE + payload_len + CRC_SIZE > sizeof(buffer)) {
    return -1; /* Too large for generic send */
  }

  uint16_t seq = ctx->sequence++;
  int frame_len =
      qdx_build_frame(buffer, msg_class, tlv_type, payload, payload_len,
                      g_TcpLogic.dev_id, seq, qdx_port_get_tick_ms(), flags);

  int32_t sent = qdx_port_tcp_send(ctx->sock, buffer, frame_len);
  if (sent < 0) {
    tcp_stream_disconnect(ctx);
    return -1;
  }
  return sent;
}

static void tcp_send_handshake(TcpStreamCtx_t *ctx) {
  DBG_LOGIC("[%s] sending handshake\r\n", ctx->label);
  uint8_t payload[54];
  memset(payload, 0, sizeof(payload));
  qdx_write_u16_le(payload + 0, 0x0200);
  memcpy(payload + 2, g_TcpLogic.uuid, 16);
  memcpy(payload + 18, g_TcpLogic.auth_token, 16);
  /* Safe string copy without relying on strncpy platform behavior */
  const char *hw = "V1.0";
  const char *fw = "V2.0";
  for (int i = 0; i < 8 && hw[i]; i++)
    payload[34 + i] = hw[i];
  for (int i = 0; i < 8 && fw[i]; i++)
    payload[42 + i] = fw[i];
  qdx_write_u32_le(payload + 50, 0x07);

  tcp_send_frame(ctx, CLASS_SYSTEM, TYPE_HANDSHAKE, payload, sizeof(payload),
                 FLAG_ACK_REQ);
}

static void tcp_send_heartbeat(TcpStreamCtx_t *ctx) {
  uint8_t payload[6];
  qdx_write_u32_le(payload + 0, qdx_port_get_tick_ms());
  payload[4] = 10; /* Placeholder CpuLoad */
  payload[5] = 20; /* Placeholder MemUsage */

  tcp_send_frame(ctx, CLASS_SYSTEM, TYPE_HEARTBEAT, payload, sizeof(payload),
                 0);
}

static void tcp_send_ack(TcpStreamCtx_t *ctx, uint16_t ack_seq, uint8_t status,
                         uint16_t error_code) {
  uint8_t payload[5];
  qdx_write_u16_le(payload + 0, ack_seq);
  payload[2] = status;
  qdx_write_u16_le(payload + 3, error_code);

  tcp_send_frame(ctx, CLASS_RESPONSE, TYPE_ACK_PAYLOAD, payload,
                 sizeof(payload), 0);
}

/* ============================================================
 * Receiving and Parsing
 * ============================================================ */

static void qdx_deserialize_config_common(ConfigCommon_t *cfg,
                                          const uint8_t *val) {
  /* PipelineId: 16 字节字符数组，逐字节拷贝 */
  for (int i = 0; i < 16; i++)
    cfg->PipelineId[i] = (char)val[i];
  cfg->PipelineType = val[16];
  cfg->WorkMode = val[17];
  cfg->ConfigTag = val[18];
  cfg->StrictnessLevel = val[19];
  cfg->IsCustomMode = val[20];
  cfg->Reserved[0] = val[21];
  cfg->Reserved[1] = val[22];
}

static void qdx_deserialize_config2d(Config2D_t *cfg, const uint8_t *val) {
  cfg->Enabled = val[0];
  cfg->IsLive = val[1];
  cfg->DeviceId = qdx_read_u16_le(val + 2);
  cfg->Width = qdx_read_u16_le(val + 4);
  cfg->Height = qdx_read_u16_le(val + 6);
  cfg->Fps = val[8];
  cfg->Exposure = qdx_read_u32_le(val + 9);
  cfg->AutoExposure = val[13];
  cfg->MaskEnabled = val[14];
  cfg->MaskThreshold = (int16_t)qdx_read_u16_le(val + 15);
  cfg->MaskWidth = qdx_read_u16_le(val + 17);
  cfg->MaskHeight = qdx_read_u16_le(val + 19);
  cfg->Angle = (int16_t)qdx_read_u16_le(val + 21);
  cfg->TargetWidth = qdx_read_u16_le(val + 23);
  cfg->TargetHeight = qdx_read_u16_le(val + 25);
  cfg->TriggerMode = val[27];
  cfg->TriggerGpioLine = val[28];
  cfg->TriggerDelayMs = qdx_read_u16_le(val + 29);
  cfg->TriggerBurstCount = val[31];
  cfg->TriggerInternalIntervalMs = qdx_read_u16_le(val + 32);
  cfg->TriggerTemperatureThreshold = (int16_t)qdx_read_u16_le(val + 34);
  cfg->TriggerDebounceIntervalMs = qdx_read_u16_le(val + 36);
  cfg->TriggerCondition = val[38];
  cfg->TriggerRoiX = qdx_read_u16_le(val + 39);
  cfg->TriggerRoiY = qdx_read_u16_le(val + 41);
  cfg->TriggerRoiW = qdx_read_u16_le(val + 43);
  cfg->TriggerRoiH = qdx_read_u16_le(val + 45);
  cfg->NGioDelay = qdx_read_u16_le(val + 47);
  cfg->OutputGpioLine = val[49];
  cfg->AlarmGpioLine = val[50];
  cfg->AlarmHoldMs = qdx_read_u16_le(val + 51);
  cfg->StoreNgImagesOnly = val[53];
  cfg->TrainingEnabled = val[54];
  cfg->TrainingSampleThreshold = qdx_read_u16_le(val + 55);
  cfg->ProcessingTimeoutMs = qdx_read_u16_le(val + 57);
  cfg->MaxProcessingQueueSize = val[59];
  cfg->Reserved = val[60];
}

static void qdx_deserialize_config1d(Config1D_t *cfg, const uint8_t *val) {
  cfg->Enabled = val[0];
  cfg->RunMode = val[1];
  cfg->TriggerType = val[2];
  cfg->BufferSize = qdx_read_u16_le(val + 3);
  cfg->TriggerTempLimit = (int16_t)qdx_read_u16_le(val + 5);
  cfg->StartPointsToRemove = qdx_read_u16_le(val + 7);
  cfg->ReferenceLength = qdx_read_u16_le(val + 9);
  cfg->HighTimerLimit = qdx_read_u16_le(val + 11);
  cfg->TimerCLimit = qdx_read_u16_le(val + 13);
  cfg->NgCountLimit = val[15];
  cfg->LSizeStart = qdx_read_u16_le(val + 16);
  cfg->RSizeStart = qdx_read_u16_le(val + 18);
  cfg->NGioDelay = qdx_read_u16_le(val + 20);
  cfg->OutputGpioLine = val[22];
  cfg->AlarmGpioLine = val[23];
  cfg->AlarmHoldMs = qdx_read_u16_le(val + 24);
}

static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
                                   uint16_t pkt_len) {
  uint16_t hdr_seq = qdx_read_u16_le(packet + 5);
  uint8_t hdr_flags = packet[15];

  /* TLV Data starts after header (16 bytes) */
  int offset = HEADER_SIZE;
  int payload_len = pkt_len - HEADER_SIZE - CRC_SIZE;
  int parsed_len = 0;

  uint8_t cfg_updated = 0;

  DBG_LOGIC("[%s] TLV pkt: Seq=%d, PayloadLen=%d\r\n", ctx->label,
         hdr_seq, payload_len);

  while (parsed_len <= payload_len - 3) {
    uint8_t type = packet[offset];
    uint16_t len = qdx_read_u16_le(packet + offset + 1);

    DBG_LOGIC("[%s]  TLV: Type=0x%02X, Len=%d\r\n", ctx->label, type, len);

    if (parsed_len + 3 + len > payload_len) {
      DBG_LOGIC("[%s]  ! TLV truncated (need %d, have %d)\r\n",
               ctx->label, len, payload_len - parsed_len - 3);
      break; /* Malformed */
    }

    const uint8_t *value = packet + offset + 3;

    switch (type) {
    case TYPE_DEVID_ASSIGN: {
      if (len >= sizeof(DevIDAssignment_t)) {
        uint16_t new_id = qdx_read_u16_le(value);
        g_TcpLogic.pending_new_dev_id = new_id;
        tcp_send_ack(ctx, hdr_seq, 0, 0);
      }
      break;
    }
    case TYPE_CONFIG_COMMON: {
      if (len >= sizeof(ConfigCommon_t)) {
        qdx_port_mutex_lock(g_TcpLogic.config_mutex);
        qdx_deserialize_config_common(&g_TcpLogic.cached_common, value);
        g_TcpLogic.has_valid_config = 1;
        cfg_updated = 1;
        qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
      }
      break;
    }
    case TYPE_CONFIG_2D: {
      if (len >= sizeof(Config2D_t)) {
        DBG_LOGIC("[%s]  Config2D parsed OK\r\n", ctx->label);
        qdx_port_mutex_lock(g_TcpLogic.config_mutex);
        qdx_deserialize_config2d(&g_TcpLogic.cached_cfg2d, value);
        g_TcpLogic.has_valid_config = 1; /* 任意配置到达即标记有效 */
        cfg_updated = 1;
        qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
      } else {
        DBG_LOGIC("[%s]  ! Config2D bad len=%d (need %d)\r\n",
                  ctx->label, len, (int)sizeof(Config2D_t));
      }
      break;
    }
    case TYPE_CONFIG_1D: {
      if (len >= sizeof(Config1D_t)) {
        qdx_port_mutex_lock(g_TcpLogic.config_mutex);
        qdx_deserialize_config1d(&g_TcpLogic.cached_cfg1d, value);
        g_TcpLogic.has_valid_config = 1; /* 任意配置到达即标记有效 */
        cfg_updated = 1;
        qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
      }
      break;
    }
    case TYPE_TEMP_FRAME: {
      if (g_TcpLogic.temp_req_cb) {
        /* If payload length is >= 18 (TemperatureFrameHeader_t), we can peek
           at Is2D. Otherwise we pass 0 or a default value. For now let's pass
           an indicator if Is2D is set. */
        uint8_t is2d = 0;
        if (len >= 18) {
          is2d = value[18]; /* index 18 in TemperatureFrameHeader_t is Is2D */
        }
        g_TcpLogic.temp_req_cb(is2d);
      }
      break;
    }
    case TYPE_DETECTION_RESULT: {
      if (len >= sizeof(DetectionResult_t) && g_TcpLogic.detect_cb) {
        uint32_t frame_num = qdx_read_u32_le(value);
        uint8_t result_status = value[4];
        g_TcpLogic.detect_cb(frame_num, result_status);
      }
      break;
    }
    default:
      break;
    }

    offset += (3 + len);
    parsed_len += (3 + len);
  }

  if (cfg_updated && g_TcpLogic.config_cb && g_TcpLogic.has_valid_config) {
    /* Safely trigger callback. Passing pointers to cached config is ok
       during the context of this thread, but user must copy if they
       dispatch to another task. */
    qdx_port_mutex_lock(g_TcpLogic.config_mutex);
    g_TcpLogic.config_cb(&g_TcpLogic.cached_common, &g_TcpLogic.cached_cfg2d,
                         &g_TcpLogic.cached_cfg1d);
    qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
  }

  if (hdr_flags & FLAG_ACK_REQ) {
    tcp_send_ack(ctx, hdr_seq, 0, 0);
  }
}

static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) {
  while (ctx->recv_len >= HEADER_SIZE) {
    /* 1. Search for Magic 0x55AA */
    int start_idx = -1;
    for (uint32_t i = 0; i <= ctx->recv_len - 2; i++) {
      if (ctx->recv_buffer[i] == 0xAA && ctx->recv_buffer[i + 1] == 0x55) {
        start_idx = i;
        break;
      }
    }

    if (start_idx == -1) {
      ctx->recv_buffer[0] = ctx->recv_buffer[ctx->recv_len - 1];
      ctx->recv_len = 1;
      break;
    }

    if (start_idx > 0) {
      /* 缓冲区内部左移，源与目标重叠，必须使用 memmove */
      memmove(ctx->recv_buffer, ctx->recv_buffer + start_idx,
              ctx->recv_len - start_idx);
      ctx->recv_len -= start_idx;
      if (ctx->recv_len < HEADER_SIZE)
        break;
    }

    uint8_t version = ctx->recv_buffer[2];
    uint16_t length = qdx_read_u16_le(ctx->recv_buffer + 3);

    if (version != PROTO_VERSION || length < HEADER_SIZE + CRC_SIZE) {
      DBG_LOGIC("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n",
               ctx->label, version, PROTO_VERSION, length);
      memmove(ctx->recv_buffer, ctx->recv_buffer + 2, ctx->recv_len - 2);
      ctx->recv_len -= 2;
      continue;
    }

    if (length > sizeof(ctx->recv_buffer)) {
      DBG_LOGIC("[%s] frame too large: %d > %d\r\n", ctx->label,
               length, (int)sizeof(ctx->recv_buffer));
      ctx->recv_len = 0;
      break;
    }

    if (ctx->recv_len < length) {
      break; /* Need more data */
    }

    /* 2. Validate CRC */
    uint16_t received_crc = qdx_read_u16_le(ctx->recv_buffer + length - 2);
    uint16_t calculated_crc = qdx_crc16_modbus(ctx->recv_buffer, length - 2);

    if (received_crc == calculated_crc) {
      /* 3. Dispatch */
      parse_and_dispatch_tlv(ctx, ctx->recv_buffer, length);
    } else {
      DBG_LOGIC("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n",
               ctx->label, calculated_crc, received_crc, length);
    }

    /* 4. 移除已处理帧（缓冲区内部左移，必须 memmove） */
    memmove(ctx->recv_buffer, ctx->recv_buffer + length,
            ctx->recv_len - length);
    ctx->recv_len -= length;
  }
}

static void recv_thread_entry(void *arg) {
  TcpStreamCtx_t *ctx = (TcpStreamCtx_t *)arg;

  while (ctx->is_running) {
    if (!ctx->is_connected) {
      qdx_port_delay_ms(100);
      continue;
    }

    /* Leave space for maximum TCP MTU read */
    if (sizeof(ctx->recv_buffer) - ctx->recv_len > 0) {
      int32_t bytes =
          qdx_port_tcp_recv(ctx->sock, ctx->recv_buffer + ctx->recv_len,
                            sizeof(ctx->recv_buffer) - ctx->recv_len);

      if (bytes > 0) {
        ctx->recv_len += bytes;
        ctx->last_activity_ms = qdx_port_get_tick_ms();
        tcp_process_rx_buffer(ctx);
      } else if (bytes < 0) {
        /* Disconnected / Error */
        tcp_stream_disconnect(ctx);
      }
    } else {
      /* Buffer completely full but no valid packet found. Prevent overflow
       * lock. */
      ctx->recv_len = 0;
    }

    qdx_port_delay_ms(10);
  }
}

/* ============================================================
 * Main Background Manager
 * ============================================================ */

static void manager_thread_entry(void *arg) {
  while (1) {
    uint32_t now = qdx_port_get_tick_ms();

    /* DevID Reassignment Handling */
    if (g_TcpLogic.pending_new_dev_id >= 0) {
      g_TcpLogic.dev_id = (uint16_t)g_TcpLogic.pending_new_dev_id;
      g_TcpLogic.pending_new_dev_id = -1;

      tcp_stream_disconnect(&g_TcpLogic.control_stream);
      tcp_stream_disconnect(&g_TcpLogic.data_stream);

      g_TcpLogic.control_stream.last_reconnect_ms = 0;
      g_TcpLogic.data_stream.last_reconnect_ms = 0;
      qdx_port_delay_ms(500);
      continue;
    }

    /* Connection Management: Control Stream */
    if (!g_TcpLogic.control_stream.is_connected) {
      if (now - g_TcpLogic.control_stream.last_reconnect_ms >
          RECONNECT_DELAY_MS) {
        if (tcp_stream_connect(&g_TcpLogic.control_stream, SERVER_IP,
                               CONTROL_PORT) == 0) {
          tcp_send_handshake(&g_TcpLogic.control_stream);
        }
        g_TcpLogic.control_stream.last_reconnect_ms = qdx_port_get_tick_ms();
      }
    }

    /* Connection Management: Data Stream */
    if (!g_TcpLogic.data_stream.is_connected) {
      if (now - g_TcpLogic.data_stream.last_reconnect_ms > RECONNECT_DELAY_MS) {
        if (tcp_stream_connect(&g_TcpLogic.data_stream, SERVER_IP, DATA_PORT) ==
            0) {
          tcp_send_handshake(&g_TcpLogic.data_stream);
        }
        g_TcpLogic.data_stream.last_reconnect_ms = qdx_port_get_tick_ms();
      }
    }

    /* Heartbeat & Timeout checks */
    if (g_TcpLogic.control_stream.is_connected) {
      if (now - g_TcpLogic.control_stream.last_heartbeat_ms >
          HEARTBEAT_INTERVAL_MS) {
        tcp_send_heartbeat(&g_TcpLogic.control_stream);
        g_TcpLogic.control_stream.last_heartbeat_ms = now;
      }
    }

    if (g_TcpLogic.data_stream.is_connected) {
      if (now - g_TcpLogic.data_stream.last_heartbeat_ms >
          HEARTBEAT_INTERVAL_MS) {
        tcp_send_heartbeat(&g_TcpLogic.data_stream);
        g_TcpLogic.data_stream.last_heartbeat_ms = now;
      }
    }

    qdx_port_delay_ms(100);
  }
}

/* ============================================================
 * Public API Implementations
 * ============================================================ */

int8_t TcpLogic_Init(const uint8_t *deviceUUID, const uint8_t *authToken) {
  memset(&g_TcpLogic, 0, sizeof(g_TcpLogic));

  if (deviceUUID)
    memcpy(g_TcpLogic.uuid, deviceUUID, 16);
  if (authToken)
    memcpy(g_TcpLogic.auth_token, authToken, 16);

  /* Default DevID = 101 */
  g_TcpLogic.dev_id = 101;
  g_TcpLogic.pending_new_dev_id = -1;

  g_TcpLogic.config_mutex = qdx_port_mutex_create();
  if (g_TcpLogic.config_mutex == NULL)
    return -1;

  tcp_stream_init(&g_TcpLogic.control_stream, "Control");
  tcp_stream_init(&g_TcpLogic.data_stream, "Data");

  return 0;
}

void TcpLogic_Start(void) {
  g_TcpLogic.control_stream.is_running = 1;
  g_TcpLogic.data_stream.is_running = 1;

  qdx_port_thread_create("tcp_mgr", manager_thread_entry, NULL, 2048, 3);
  qdx_port_thread_create("tcp_rx_c", recv_thread_entry,
                         &g_TcpLogic.control_stream, 2048, 4);
  qdx_port_thread_create("tcp_rx_d", recv_thread_entry, &g_TcpLogic.data_stream,
                         2048, 4);
}

int8_t TcpLogic_GetLatestConfig(ConfigCommon_t *out_common,
                                Config2D_t *out_cfg2d, Config1D_t *out_cfg1d) {
  if (!out_common || !out_cfg2d || !out_cfg1d)
    return -2;

  qdx_port_mutex_lock(g_TcpLogic.config_mutex);
  if (!g_TcpLogic.has_valid_config) {
    qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
    return -1;
  }

  memcpy(out_common, &g_TcpLogic.cached_common, sizeof(ConfigCommon_t));
  memcpy(out_cfg2d, &g_TcpLogic.cached_cfg2d, sizeof(Config2D_t));
  memcpy(out_cfg1d, &g_TcpLogic.cached_cfg1d, sizeof(Config1D_t));
  qdx_port_mutex_unlock(g_TcpLogic.config_mutex);

  return 0;
}

void TcpLogic_RegisterConfigCallback(ConfigUpdateCallback_t cb) {
  g_TcpLogic.config_cb = cb;
}

void TcpLogic_RegisterDetectionCallback(DetectionResultCallback_t cb) {
  g_TcpLogic.detect_cb = cb;
}

void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb) {
  g_TcpLogic.temp_req_cb = cb;
}

/* ============================================================
 * Zero-Copy Frame Building & Fragmentation for Temperature Data
 * ============================================================ */

int8_t
TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer,
                                      const PreprocessResult_t *processMeta,
                                      uint8_t frameType, uint8_t is2D) {

  if (!g_TcpLogic.data_stream.is_connected || !io_buffer || !processMeta)
    return -1;
  if (io_buffer->ValidPayloadLen == 0)
    return -2;

  g_TcpLogic.frame_count++;

  /* We need to prepend: TLV Header (3) + TemperatureFrameHeader_t (18) */
  uint32_t tlv_wrapper_len = TLV_HEADER_SIZE + sizeof(TemperatureFrameHeader_t);

  /* Ensure application left enough head room */
  if (io_buffer->HeadOffset < HEADER_SIZE + tlv_wrapper_len) {
    return -3; /* Not enough offset space allocated by user memory pool */
  }

  /* Start writing right before the application payload */
  uint8_t *tlv_start =
      io_buffer->pBuffer + io_buffer->HeadOffset - tlv_wrapper_len;

  /* 1. Fill TLV Header manually via shift */
  uint32_t tlv_value_len =
      sizeof(TemperatureFrameHeader_t) + io_buffer->ValidPayloadLen;
  tlv_start[0] = TYPE_TEMP_FRAME;
  qdx_write_u16_le(tlv_start + 1, (uint16_t)tlv_value_len);

  /* 2. Fill TemperatureFrameHeader manually via shift to avoid alignment
   * faults
   */
  uint8_t *temp_hdr = tlv_start + TLV_HEADER_SIZE;
  qdx_write_u32_le(temp_hdr + 0, processMeta->FrameNumber);
  qdx_write_u16_le(temp_hdr + 4, processMeta->ValidWidth);
  qdx_write_u16_le(temp_hdr + 6, processMeta->ValidHeight);
  qdx_write_u16_le(temp_hdr + 8, (uint16_t)processMeta->MinTemp);
  qdx_write_u16_le(temp_hdr + 10, (uint16_t)processMeta->MaxTemp);
  qdx_write_u16_le(temp_hdr + 12, (uint16_t)processMeta->AvgTemp);
  qdx_write_u16_le(temp_hdr + 14, (uint16_t)processMeta->RoiTemp);
  temp_hdr[16] = frameType;
  temp_hdr[17] = processMeta->Status;
  temp_hdr[18] = is2D;
  temp_hdr[19] = 0; /* Reserved */

  /* Total payload length is the entire TLV block */
  uint32_t total_tlv_len = TLV_HEADER_SIZE + tlv_value_len;

  /* 3. Handle Fragmentation if necessary */
  if (total_tlv_len <= MAX_FRAGMENT_PAYLOAD) {
    /* No fragmentation needed, build frame in place at the front */
    uint8_t *frame_start = tlv_start - HEADER_SIZE;
    uint16_t seq = g_TcpLogic.data_stream.sequence++;

    int final_len = qdx_build_frame_inplace(
        frame_start, CLASS_DATA, (uint16_t)total_tlv_len, g_TcpLogic.dev_id,
        seq, qdx_port_get_tick_ms(), 0);

    int32_t sent =
        qdx_port_tcp_send(g_TcpLogic.data_stream.sock, frame_start, final_len);
    return (sent >= 0) ? 0 : -1;
  }

  /* Fragmentation required.
     Note: For zero-copy fragmentation, we send piece by piece.
     We need an external buffer for each piece's frame header + CRC.
     We can't easily prepend headers to later fragments inline. */

  uint32_t offset = 0;
  uint8_t frag_buf[HEADER_SIZE + MAX_FRAGMENT_PAYLOAD + CRC_SIZE];
  uint32_t frag_count =
      (total_tlv_len + MAX_FRAGMENT_PAYLOAD - 1) / MAX_FRAGMENT_PAYLOAD;

  for (uint32_t i = 0; i < frag_count; i++) {
    uint32_t chunk_len = total_tlv_len - offset;
    if (chunk_len > MAX_FRAGMENT_PAYLOAD)
      chunk_len = MAX_FRAGMENT_PAYLOAD;

    uint8_t flags = (i == frag_count - 1) ? FLAG_LAST_FRAGMENT : 0;
    uint16_t seq = g_TcpLogic.data_stream.sequence++;

    /* We copy the chunk into frag_buf to append Header/CRC.
       This involves ONE copy of the chunk, but it's small (1400 bytes at a
       time), and ensures we don't need additional memory pools. */
    int frame_len = qdx_build_fragment_frame(
        frag_buf, CLASS_DATA, tlv_start + offset, (uint16_t)chunk_len,
        g_TcpLogic.dev_id, seq, qdx_port_get_tick_ms(), flags);

    int32_t sent =
        qdx_port_tcp_send(g_TcpLogic.data_stream.sock, frag_buf, frame_len);
    if (sent < 0) {
      tcp_stream_disconnect(&g_TcpLogic.data_stream);
      return -1;
    }

    offset += chunk_len;
  }

  return 0;
}
