#include "ch32v30x.h"
#include "string.h"
#include "eth_driver.h"
#include "dvp.h"
#include "mini212g2.h"
#include "qdx_port.h"
#include "qdx_preprocess.h"
#include "qdx_tcp_logic.h"

#include "FreeRTOS.h"
#include "task.h"

/* ============================================================
 * TEST MODE: Generate simulated sensor data without real hardware.
 * Set to 1 to enable test pattern generation (no sensor/DVP needed).
 * Set to 0 for normal operation with real Mini212G2 sensor.
 * ============================================================ */
#define TEST_PATTERN_MODE    1

/* Default burst parameters — used when server hasn't sent config yet */
#define DEFAULT_BURST_COUNT       3
#define DEFAULT_BURST_INTERVAL_MS 200

/* ============================================================
 * External Trigger DI Input
 * PA15 / EXTI15 / Rising Edge
 * Shared by 2D and 1D external trigger (mutually exclusive).
 * ============================================================ */
#define EXT_TRIG_GPIO_PORT    GPIOA
#define EXT_TRIG_GPIO_PIN     GPIO_Pin_15
#define EXT_TRIG_GPIO_CLK     RCC_APB2Periph_GPIOA
#define EXT_TRIG_EXTI_LINE    EXTI_Line15
#define EXT_TRIG_EXTI_PORT    GPIO_PortSourceGPIOA
#define EXT_TRIG_EXTI_PIN     GPIO_PinSource15
#define EXT_TRIG_IRQn         EXTI15_10_IRQn

/* NG output GPIO: PA8 push-pull, active-high when NG detected */
#define NG_GPIO_PORT   GPIOA
#define NG_GPIO_PIN    GPIO_Pin_8
#define NG_GPIO_CLK    RCC_APB2Periph_GPIOA
#define NG_PULSE_MS    200

/* ============================================================
 * TX Buffers (double-buffered for zero-copy)
 * ============================================================ */
#define MAX_TCP_PAYLOAD_SIZE 9216
uint8_t g_TxNetBuffer_A_Mem[MAX_TCP_PAYLOAD_SIZE];
uint8_t g_TxNetBuffer_B_Mem[MAX_TCP_PAYLOAD_SIZE];

TcpTxBuffer_t g_TxNetBuffer_A = {
    .pBuffer = g_TxNetBuffer_A_Mem,
    .TotalCapacity = MAX_TCP_PAYLOAD_SIZE,
    .HeadOffset = 64,
    .ValidPayloadLen = 0
};
TcpTxBuffer_t g_TxNetBuffer_B = {
    .pBuffer = g_TxNetBuffer_B_Mem,
    .TotalCapacity = MAX_TCP_PAYLOAD_SIZE,
    .HeadOffset = 64,
    .ValidPayloadLen = 0
};

/* ============================================================
 * Runtime State
 * ============================================================ */
extern volatile uint32_t sys_tick_ms;

/* External trigger flag — set in EXTI ISR */
static volatile uint8_t  g_ext_trigger_flag    = 0;
static volatile uint32_t g_ext_trigger_time_ms = 0;

/* TEMP_REQ auxiliary channel (server on-demand screenshot) */
static volatile uint8_t g_temp_req_pending = 0;
static volatile uint8_t g_temp_req_is2d   = 1;

/* NG output pulse timer */
static volatile uint32_t g_ng_off_time = 0;

/* ============================================================
 * 2D Burst State Machine
 * ============================================================ */
static uint8_t  burst_active           = 0;
static uint8_t  burst_remaining        = 0;
static uint32_t burst_next_time_ms     = 0;
static uint8_t  burst_delay_pending    = 0;   /* waiting for DelayMs */
static uint32_t burst_delay_until_ms   = 0;
static uint8_t  burst_debounce_pending = 0;   /* waiting for DebounceMs */
static uint32_t burst_debounce_until   = 0;

/* ============================================================
 * 1D Collection State Machine
 *
 * Protocol flow:
 *   External: GPIO → debounce(HighTimerLimit) → collect temp points
 *             → threshold start/stop → slice [LSizeStart, -RSizeStart] → send
 *   Internal: continuous scan → 3 consecutive hot → collect
 *             → threshold stop → slice → send
 *
 * Each DVP frame provides one temperature sample (center row max).
 * ============================================================ */
typedef enum {
    S1D_IDLE = 0,
    S1D_DEBOUNCE,
    S1D_COLLECTING,
} State1D_t;

#define MAX_1D_POINTS  512

static State1D_t s1d_state       = S1D_IDLE;
static uint16_t  s1d_temp_buf[MAX_1D_POINTS];
static uint16_t  s1d_time_buf[MAX_1D_POINTS];
static uint16_t  s1d_count       = 0;
static uint8_t   s1d_consec_hot  = 0;
static uint8_t   s1d_consec_cold = 0;
static uint32_t  s1d_start_time  = 0;
static uint32_t  s1d_debounce_until = 0;
static uint8_t   s1d_triggered   = 0;

/* Pre-trigger ring buffer (3 samples) for internal 1D trigger */
static uint16_t s1d_pre_ring[3];
static uint8_t  s1d_pre_idx   = 0;
static uint8_t  s1d_pre_count = 0;

/* ============================================================
 * Callbacks
 * ============================================================ */
void OnConfigUpdate(const ConfigCommon_t *common, const Config2D_t *cfg2d, const Config1D_t *cfg1d)
{
    Preprocess_Settings_Change(cfg2d, cfg1d, common);
}

void OnTempFrameRequest(uint8_t is2dRequest)
{
    g_temp_req_is2d   = is2dRequest;
    g_temp_req_pending = 1;
    DBG_CFG("TempFrameReq is2d=%d\r\n", (int)is2dRequest);
}

void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus)
{
    (void)frameNumber;
    if (resultStatus == 0) {
        ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
        uint32_t pulse_ms = NG_PULSE_MS;
        if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0 && t2.NGioDelay > 0)
            pulse_ms = t2.NGioDelay;
        GPIO_SetBits(NG_GPIO_PORT, NG_GPIO_PIN);
        g_ng_off_time = sys_tick_ms + pulse_ms;
    }
}

#define KEEPALIVE_ENABLE    1

/* ============================================================
 * FLASH / SRAM 分配配置 (Option Bytes RAM_CODE_MOD[2:0])
 * 修改后需复位才生效
 * ============================================================ */
typedef enum {
    FLASH_192_SRAM_128 = 0,   /* 00x  默认 */
    FLASH_224_SRAM_96,        /* 01x */
    FLASH_256_SRAM_64,        /* 10x */
    FLASH_128_SRAM_192,       /* 110 */
    FLASH_288_SRAM_32         /* 111 */
} FLASH_SRAM_DEFIN;

static void Config_Flash_SRAM(FLASH_SRAM_DEFIN mode)
{
    uint8_t UserByte = FLASH_GetUserOptionByte() & 0xFF;
    uint8_t newByte  = UserByte & ~0xE0;   /* clear bits [7:5] */

    switch (mode) {
    case FLASH_192_SRAM_128:                          break; /* 000 */
    case FLASH_224_SRAM_96:  newByte |= 0x40;         break; /* 010 */
    case FLASH_256_SRAM_64:  newByte |= 0x80;         break; /* 100 */
    case FLASH_128_SRAM_192: newByte |= 0xC0;         break; /* 110 */
    case FLASH_288_SRAM_32:  newByte |= 0xE0;         break; /* 111 */
    default: return;
    }

    if (newByte == UserByte) return;   /* already configured */

    FLASH_Unlock();
    FLASH_ProgramOptionByteData(0x1FFFF802, newByte);
    FLASH_Lock();
    DBG_INIT("Flash/SRAM changed to %d, resetting...\r\n", mode);
    NVIC_SystemReset();
}

u8  MACAddr[6];
u8  IPAddr[4]   = {192, 168, 7, 10};
u8  GWIPAddr[4] = {192, 168, 7,  1};
u8  IPMask[4]   = {255, 255, 255, 0};
u8  DESIP[4]    = {192, 168, 7, 50};
u16 desport     = 5512;
u16 srcport     = 5511;

u8 SocketId;
u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN];

void mStopIfError(u8 iError)
{
    if (iError == WCHNET_ERR_SUCCESS) return;
    DBG_ERR("WCHNET: %02X\r\n", (u16)iError);
}

void TIM2_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = {0};
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    TIM_TimeBaseStructure.TIM_Period        = WCHNETTIMERPERIOD * 1000 - 1;
    TIM_TimeBaseStructure.TIM_Prescaler     = SystemCoreClock / 1000000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    NVIC_EnableIRQ(TIM2_IRQn);
}


/* qdx_port.c notification hooks */
extern void qdx_port_sock_recv_notify(uint8_t sockid);
extern void qdx_port_sock_connect_notify(uint8_t sockid);
extern void qdx_port_sock_disconnect_notify(uint8_t sockid);
extern void qdx_port_init(void);
extern void qdx_port_net_lock(void);
extern void qdx_port_net_unlock(void);


void WCHNET_HandleSockInt(u8 socketid, u8 intstat)
{
    DBG_NET("SockInt: id=%d stat=0x%02X\r\n", socketid, intstat);
    if (intstat & SINT_STAT_RECV)
    {
        qdx_port_sock_recv_notify(socketid);
    }
    if (intstat & SINT_STAT_CONNECT)
    {
        WCHNET_ModifyRecvBuf(socketid, (u32)SocketRecvBuf[socketid], RECE_BUF_LEN);
        qdx_port_sock_connect_notify(socketid);
        DBG_NET("TCP Connected, socket %d\r\n", socketid);
    }
    if (intstat & SINT_STAT_DISCONNECT)
    {
        qdx_port_sock_disconnect_notify(socketid);
        DBG_NET("TCP Disconnected, socket %d\r\n", socketid);
    }
    if (intstat & SINT_STAT_TIM_OUT)
    {
        qdx_port_sock_disconnect_notify(socketid);
        DBG_NET("TCP Timeout, socket %d\r\n", socketid);
    }
}

void WCHNET_HandleGlobalInt(void)
{
    u8  intstat;
    u16 i;
    u8  socketint;
    intstat = WCHNET_GetGlobalInt();
    DBG_NET("GlobalInt: 0x%02X\r\n", intstat);
    if (intstat & GINT_STAT_UNREACH)    DBG_NET("UNREACH\r\n");
    if (intstat & GINT_STAT_IP_CONFLI)  DBG_ERR("IP_CONFLICT\r\n");
    if (intstat & GINT_STAT_PHY_CHANGE) {
        i = WCHNET_GetPHYStatus();
        DBG_NET("PHY: status=0x%04X %s\r\n", i,
                (i & PHY_Linked_Status) ? "LINK_UP" : "LINK_DOWN");
    }
    if (intstat & GINT_STAT_SOCKET) {
        for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++) {
            socketint = WCHNET_GetSocketInt(i);
            if (socketint) WCHNET_HandleSockInt(i, socketint);
        }
    }
}

/* ============================================================
 * Hardware Init: External Trigger GPIO (PA15 / EXTI15)
 * ============================================================ */
static void ExtTrigger_GPIO_Init(void)
{
    GPIO_InitTypeDef gpio = {0};
    EXTI_InitTypeDef exti = {0};

    RCC_APB2PeriphClockCmd(EXT_TRIG_GPIO_CLK | RCC_APB2Periph_AFIO, ENABLE);

    gpio.GPIO_Pin  = EXT_TRIG_GPIO_PIN;
    gpio.GPIO_Mode = GPIO_Mode_IPD;  /* Pull-down: idle low */
    GPIO_Init(EXT_TRIG_GPIO_PORT, &gpio);

    GPIO_EXTILineConfig(EXT_TRIG_EXTI_PORT, EXT_TRIG_EXTI_PIN);

    exti.EXTI_Line    = EXT_TRIG_EXTI_LINE;
    exti.EXTI_Mode    = EXTI_Mode_Interrupt;
    exti.EXTI_Trigger = EXTI_Trigger_Rising;
    exti.EXTI_LineCmd = ENABLE;
    EXTI_Init(&exti);

    NVIC_EnableIRQ(EXT_TRIG_IRQn);
}

static void NG_GPIO_Init(void)
{
    GPIO_InitTypeDef gpio = {0};
    RCC_APB2PeriphClockCmd(NG_GPIO_CLK, ENABLE);
    gpio.GPIO_Pin   = NG_GPIO_PIN;
    gpio.GPIO_Mode  = GPIO_Mode_Out_PP;
    gpio.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(NG_GPIO_PORT, &gpio);
    GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
}

/* EXTI15 ISR — external trigger rising edge (PA15) */
void EXTI15_10_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXT_TRIG_EXTI_LINE) != RESET) {
        EXTI_ClearITPendingBit(EXT_TRIG_EXTI_LINE);
        g_ext_trigger_flag    = 1;
        g_ext_trigger_time_ms = sys_tick_ms;
    }
}

/* ============================================================
 * RTOS Task: Heartbeat (periodic debug print)
 * ============================================================ */
static void task_heartbeat_entry(void *pvParameters)
{
    (void)pvParameters;
    uint32_t cnt = 0;
    while (1)
    {
#if TEST_PATTERN_MODE
        DBG_HB("%d tick=%d TEST_MODE frm=%d\r\n",
               (int)cnt++, (int)xTaskGetTickCount(),
               (int)dvp_frame_count);
#else
        DBG_HB("%d tick=%d dvp_frm=%d row_irq=%d\r\n",
               (int)cnt++, (int)xTaskGetTickCount(),
               (int)dvp_frame_count, (int)dvp_row_irq_cnt);
#endif
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

/* ============================================================
 * RTOS Task: WCHNET protocol stack driver (highest priority)
 * ============================================================ */
static void task_wchnet_entry(void *pvParameters)
{
    (void)pvParameters;
    while (1)
    {
        qdx_port_net_lock();
        WCHNET_MainTask();
        if (WCHNET_QueryGlobalInt())
            WCHNET_HandleGlobalInt();
        qdx_port_net_unlock();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

#if TEST_PATTERN_MODE
/* ============================================================
 * Test Pattern Generator
 * Generates simulated 256x192 Y16 thermal images at ~10 FPS.
 * Pattern cycles through several types so all pipeline paths
 * can be exercised.
 *
 * Pattern 0: Gradient (25.00~45.00°C) — baseline, no trigger
 * Pattern 1: Hot center spot (90.00°C) — should trigger alarm
 * Pattern 2: Uniform warm (35.00°C) — no trigger
 * Pattern 3: Checkerboard with hot cells — tests ROI search
 * ============================================================ */
#define TEST_FPS_DELAY_MS   100   /* ~10 FPS */
#define TEMP_RAW(deg_c)     ((uint16_t)((deg_c) * 10))   /* e.g. 35.0°C → 350, 0.1°C/LSB */

static void test_fill_gradient(uint16_t *buf, uint16_t w, uint16_t h)
{
    for (uint16_t y = 0; y < h; y++) {
        for (uint16_t x = 0; x < w; x++) {
            uint32_t pos = (uint32_t)y * w + (uint32_t)x * h;
            uint16_t temp = TEMP_RAW(25.0) + (uint16_t)(pos * TEMP_RAW(20.0) / ((uint32_t)w * h));
            buf[y * w + x] = temp;
        }
    }
}

static void test_fill_hotspot(uint16_t *buf, uint16_t w, uint16_t h)
{
    for (uint16_t y = 0; y < h; y++)
        for (uint16_t x = 0; x < w; x++)
            buf[y * w + x] = TEMP_RAW(28.0) + (uint16_t)((uint32_t)x * TEMP_RAW(4.0) / w);
    uint16_t cx = w / 2, cy = h / 2;
    for (uint16_t y = cy - 16; y < cy + 16; y++)
        for (uint16_t x = cx - 16; x < cx + 16; x++)
            buf[y * w + x] = TEMP_RAW(90.0);
}

static void test_fill_uniform(uint16_t *buf, uint16_t w, uint16_t h)
{
    for (uint16_t y = 0; y < h; y++)
        for (uint16_t x = 0; x < w; x++)
            buf[y * w + x] = TEMP_RAW(35.0);
}

static void test_fill_checker(uint16_t *buf, uint16_t w, uint16_t h)
{
    for (uint16_t y = 0; y < h; y++)
        for (uint16_t x = 0; x < w; x++) {
            uint8_t cell = ((y / 32) + (x / 32)) & 1;
            buf[y * w + x] = cell ? TEMP_RAW(85.0) : TEMP_RAW(28.0);
        }
}

static void task_test_pattern_entry(void *pvParameters)
{
    (void)pvParameters;
    uint32_t frame_num = 0;
    uint8_t pattern_idx = 0;
    /* Cycle: gradient x20, hotspot x5, uniform x10, checker x5 */
    const uint8_t pattern_seq[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                   1,1,1,1,1,
                                   2,2,2,2,2,2,2,2,2,2,
                                   3,3,3,3,3};
    const uint8_t seq_len = sizeof(pattern_seq);

    while (1)
    {
        uint16_t *buf = (uint16_t *)FrameBuffer;
        switch (pattern_seq[pattern_idx % seq_len]) {
            case 0: test_fill_gradient(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break;
            case 1: test_fill_hotspot(buf, SENSOR_WIDTH, SENSOR_HEIGHT);  break;
            case 2: test_fill_uniform(buf, SENSOR_WIDTH, SENSOR_HEIGHT);  break;
            case 3: test_fill_checker(buf, SENSOR_WIDTH, SENSOR_HEIGHT);  break;
        }
        pattern_idx++;
        if (pattern_idx >= seq_len) pattern_idx = 0;

        frame_num++;
        dvp_frame_count = frame_num;
        Ready_Frame_Count = frame_num;
        Frame_Ready_Flag = 1;

        vTaskDelay(pdMS_TO_TICKS(TEST_FPS_DELAY_MS));
    }
}
#endif /* TEST_PATTERN_MODE */

/* ============================================================
 * Business Logic Helpers
 * ============================================================ */

/*
 * Extract one representative temperature from a raw frame for 1D.
 * Returns the global maximum temperature across the entire frame.
 */
static uint16_t get_1d_sample(const RawImageBuffer_t *raw)
{
    uint16_t w = raw->Width;
    uint16_t h = raw->Height;
    uint16_t *src = raw->pData;
    uint16_t max_temp = 0;
    uint32_t total = (uint32_t)w * h;
    for (uint32_t i = 0; i < total; i++) {
        if (src[i] > max_temp) max_temp = src[i];
    }
    return max_temp;
}

/*
 * Reset 1D state machine to idle.
 */
static void reset_1d_state(void)
{
    s1d_state       = S1D_IDLE;
    s1d_count       = 0;
    s1d_consec_hot  = 0;
    s1d_consec_cold = 0;
    s1d_triggered   = 0;
    s1d_pre_idx     = 0;
    s1d_pre_count   = 0;
}

/*
 * Pack collected 1D samples and send via TCP.
 * Applies slicing: [LSizeStart, count - RSizeStart]
 */
static void send_1d_collection(const Config1D_t *cfg, uint8_t *use_buf_a)
{
    uint16_t start = cfg->LSizeStart;
    uint16_t end   = s1d_count;
    if (cfg->RSizeStart > 0 && cfg->RSizeStart < end)
        end -= cfg->RSizeStart;
    /* Clamp: ensure valid slice even if server sends bad L/R values */
    if (start >= end) {
        if (s1d_count > 0) {
            DBG_ERR("1D: slice clamp L=%d R=%d cnt=%d\r\n",
                    (int)cfg->LSizeStart, (int)cfg->RSizeStart, (int)s1d_count);
            start = 0;
            end = s1d_count;
        } else {
            DBG_ERR("1D: no data to send\r\n");
            return;
        }
    }
    uint16_t n = end - start;

    TcpTxBuffer_t *tx_buf = (*use_buf_a) ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
    *use_buf_a = !(*use_buf_a);
    tx_buf->ValidPayloadLen = 0;
    uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset;
    uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset;
    if ((uint32_t)n * 4 > capacity)
        n = (uint16_t)(capacity / 4);

    int16_t min_t = 32767, max_t = -32768;
    int32_t sum_t = 0;

    for (uint16_t i = 0; i < n; i++) {
        uint16_t idx  = start + i;
        uint16_t temp = s1d_temp_buf[idx];
        uint16_t toff = s1d_time_buf[idx];
        int16_t t = (int16_t)temp;
        if (t < min_t) min_t = t;
        if (t > max_t) max_t = t;
        sum_t += t;
        dest[0] = (uint8_t)(toff & 0xFF);
        dest[1] = (uint8_t)((toff >> 8) & 0xFF);
        dest[2] = (uint8_t)(temp & 0xFF);
        dest[3] = (uint8_t)((temp >> 8) & 0xFF);
        dest += 4;
    }
    tx_buf->ValidPayloadLen = (uint32_t)n * 4;

    PreprocessResult_t meta;
    memset(&meta, 0, sizeof(meta));
    meta.pValidData  = tx_buf->pBuffer + tx_buf->HeadOffset;
    meta.DataLength  = tx_buf->ValidPayloadLen;
    meta.ValidWidth  = n;
    meta.ValidHeight = 1;
    meta.MinTemp     = min_t;
    meta.MaxTemp     = max_t;
    meta.AvgTemp     = (int16_t)(sum_t / (n > 0 ? n : 1));
    meta.RoiTemp     = meta.AvgTemp;
    meta.FrameNumber = Ready_Frame_Count;

    int8_t ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0);
    DBG_DATA("1D SEND n=%d slice=[%d,%d) ret=%d\r\n",
            (int)n, (int)start, (int)end, (int)ret);
}

/*
 * Spatial 1D snapshot for TEMP_REQ — samples points across center row.
 */
#define SNAPSHOT_1D_POINTS  30

static void send_1d_snapshot(const RawImageBuffer_t *raw, uint8_t *use_buf_a)
{
    uint16_t w = raw->Width;
    uint16_t h = raw->Height;
    uint16_t *src = raw->pData;
    uint16_t row = h / 2;

    TcpTxBuffer_t *tx_buf = (*use_buf_a) ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
    *use_buf_a = !(*use_buf_a);
    tx_buf->ValidPayloadLen = 0;
    uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset;
    uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset;
    uint16_t points = SNAPSHOT_1D_POINTS;
    if (points > w) points = w;
    if ((uint32_t)points * 4 > capacity)
        points = (uint16_t)(capacity / 4);

    int16_t min_t = 32767, max_t = -32768;
    int32_t sum_t = 0;

    for (uint16_t i = 0; i < points; i++) {
        uint16_t col = (uint16_t)((uint32_t)i * (w - 1) / (points > 1 ? points - 1 : 1));
        uint16_t temp = src[row * w + col];
        uint16_t time_offset = (uint16_t)((uint32_t)i * 600 / (points > 1 ? points - 1 : 1));
        int16_t t = (int16_t)temp;
        if (t < min_t) min_t = t;
        if (t > max_t) max_t = t;
        sum_t += t;
        dest[0] = (uint8_t)(time_offset & 0xFF);
        dest[1] = (uint8_t)((time_offset >> 8) & 0xFF);
        dest[2] = (uint8_t)(temp & 0xFF);
        dest[3] = (uint8_t)((temp >> 8) & 0xFF);
        dest += 4;
    }
    tx_buf->ValidPayloadLen = (uint32_t)points * 4;

    PreprocessResult_t meta;
    memset(&meta, 0, sizeof(meta));
    meta.pValidData  = tx_buf->pBuffer + tx_buf->HeadOffset;
    meta.DataLength  = tx_buf->ValidPayloadLen;
    meta.ValidWidth  = points;
    meta.ValidHeight = 1;
    meta.MinTemp     = min_t;
    meta.MaxTemp     = max_t;
    meta.AvgTemp     = (int16_t)(sum_t / (points > 0 ? points : 1));
    meta.RoiTemp     = meta.AvgTemp;
    meta.FrameNumber = raw->FrameNumber;

    int8_t ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0);
    DBG_DATA("1D snapshot n=%d ret=%d\r\n", (int)points, (int)ret);
}

/*
 * Capture and send one 2D frame (used during burst sequence).
 */
static void do_2d_capture_send(const RawImageBuffer_t *raw, uint8_t *use_buf_a)
{
    TcpTxBuffer_t *tx_buf = (*use_buf_a) ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
    *use_buf_a = !(*use_buf_a);
    tx_buf->ValidPayloadLen = 0;

    PreprocessResult_t meta;
    int8_t pp_ret = Preprocess_Execute(raw, tx_buf, &meta);
    if (pp_ret == 0) {
        int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1);
        DBG_DATA("2D SEND frm=%d %dx%d ret=%d\r\n",
                (int)meta.FrameNumber, (int)meta.ValidWidth,
                (int)meta.ValidHeight, (int)tx_ret);
    } else {
        DBG_ERR("2D PP fail ret=%d\r\n", (int)pp_ret);
    }
}

/*
 * Start a 2D burst: capture first frame, then queue remaining.
 */
static void start_2d_burst(const RawImageBuffer_t *raw, const Config2D_t *t2, uint8_t *use_buf_a)
{
    DBG_TRIG("TRIGGER frm=%d\r\n", (int)raw->FrameNumber);
    do_2d_capture_send(raw, use_buf_a);

    uint8_t total = DEFAULT_BURST_COUNT;
    uint16_t interval = DEFAULT_BURST_INTERVAL_MS;
    if (t2->TriggerBurstCount >= 1)
        total = t2->TriggerBurstCount;
    if (t2->TriggerInternalIntervalMs > 0)
        interval = t2->TriggerInternalIntervalMs;

    if (total > 1) {
        burst_active = 1;
        burst_remaining = total - 1;
        burst_next_time_ms = sys_tick_ms + interval;
        DBG_TRIG("Burst start: %d frames, interval=%d ms\r\n",
                (int)total, (int)interval);
    }
}

/* ============================================================
 * 2D Trigger Handler — called once per frame when Config2D enabled
 *
 * State machine phases:
 *   1. Debounce wait (external trigger: DebounceIntervalMs)
 *   2. Delay wait (DelayMs after trigger confirmation)
 *   3. Burst capture (BurstCount frames at InternalIntervalMs)
 *   4. Idle — check for new trigger (external GPIO or internal temp)
 * ============================================================ */
static void handle_2d_trigger(const RawImageBuffer_t *raw, const Config2D_t *t2, uint8_t *use_buf_a)
{
    /* Phase 1: debounce wait (external trigger only) */
    if (burst_debounce_pending) {
        if (sys_tick_ms < burst_debounce_until)
            return;
        burst_debounce_pending = 0;
        if (t2->TriggerDelayMs > 0) {
            burst_delay_pending  = 1;
            burst_delay_until_ms = sys_tick_ms + t2->TriggerDelayMs;
        } else {
            start_2d_burst(raw, t2, use_buf_a);
        }
        return;
    }

    /* Phase 2: delay wait */
    if (burst_delay_pending) {
        if (sys_tick_ms < burst_delay_until_ms)
            return;
        burst_delay_pending = 0;
        start_2d_burst(raw, t2, use_buf_a);
        return;
    }

    /* Phase 3: ongoing burst — send remaining frames */
    if (burst_active) {
        if (sys_tick_ms >= burst_next_time_ms) {
            do_2d_capture_send(raw, use_buf_a);
            burst_remaining--;
            if (burst_remaining == 0) {
                burst_active = 0;
                DBG_TRIG("Burst complete\r\n");
            } else {
                uint16_t interval = DEFAULT_BURST_INTERVAL_MS;
                if (t2->TriggerInternalIntervalMs > 0)
                    interval = t2->TriggerInternalIntervalMs;
                burst_next_time_ms = sys_tick_ms + interval;
            }
        }
        return;
    }

    /* Phase 4: check for new trigger */
    if (t2->TriggerMode == 1) {
        /* --- External trigger (TriggerMode=1) --- */
        if (g_ext_trigger_flag) {
            g_ext_trigger_flag = 0;
            DBG_TRIG("2D ext trigger\r\n");
            if (t2->TriggerDebounceIntervalMs > 0) {
                burst_debounce_pending = 1;
                burst_debounce_until   = sys_tick_ms + t2->TriggerDebounceIntervalMs;
            } else if (t2->TriggerDelayMs > 0) {
                burst_delay_pending  = 1;
                burst_delay_until_ms = sys_tick_ms + t2->TriggerDelayMs;
            } else {
                start_2d_burst(raw, t2, use_buf_a);
            }
        }
    } else {
        /* --- Internal trigger (TriggerMode=0): temperature threshold in TriggerRoi --- */
        if (Preprocess_CheckInternalTrigger2D(raw) == 1) {
            DBG_TRIG("2D internal trigger\r\n");
            if (t2->TriggerDelayMs > 0) {
                burst_delay_pending  = 1;
                burst_delay_until_ms = sys_tick_ms + t2->TriggerDelayMs;
            } else {
                start_2d_burst(raw, t2, use_buf_a);
            }
        }
    }
}

/* ============================================================
 * 1D Trigger Handler — called once per frame when Config1D enabled
 *
 * Each DVP frame yields one temperature sample (center row max).
 * The state machine collects samples over time, starting/stopping
 * based on temperature threshold crossings.
 * ============================================================ */
static void handle_1d_trigger(const RawImageBuffer_t *raw, const Config1D_t *t1, uint8_t *use_buf_a)
{
    /* RunMode: 0=STOP, 1=RUN. Only collect when running. */
    if (t1->RunMode != 1)
        return;

    uint16_t sample = get_1d_sample(raw);
    int16_t thresh  = t1->TriggerTempLimit;
    uint8_t is_hot  = ((int16_t)sample >= thresh) ? 1 : 0;

    switch (s1d_state) {
    case S1D_IDLE:
        if (t1->TriggerType == 2) {
            /* --- External trigger (TriggerType=2) --- */
            if (g_ext_trigger_flag) {
                g_ext_trigger_flag = 0;
                s1d_debounce_until = sys_tick_ms + t1->HighTimerLimit;
                s1d_state = S1D_DEBOUNCE;
                DBG_TRIG("1D: ext trigger -> debounce %d ms\r\n",
                        (int)t1->HighTimerLimit);
            }
        } else if (t1->TriggerType == 1) {
            /* --- Internal trigger (TriggerType=1): scan with pre-ring buffer --- */
            s1d_pre_ring[s1d_pre_idx] = sample;
            s1d_pre_idx = (s1d_pre_idx + 1) % 3;
            if (s1d_pre_count < 3) s1d_pre_count++;

            if (s1d_pre_count >= 3) {
                uint8_t all_hot = 1;
                for (uint8_t i = 0; i < 3; i++) {
                    if ((int16_t)s1d_pre_ring[i] < thresh) {
                        all_hot = 0;
                        break;
                    }
                }
                if (all_hot) {
                    /* 3 consecutive hot samples → start collecting */
                    s1d_count = 0;
                    s1d_start_time = sys_tick_ms;
                    /* Copy pre-ring into buffer (oldest first) */
                    for (uint8_t i = 0; i < 3; i++) {
                        uint8_t ri = (s1d_pre_idx + i) % 3;
                        s1d_temp_buf[s1d_count] = s1d_pre_ring[ri];
                        s1d_time_buf[s1d_count] = 0;
                        s1d_count++;
                    }
                    s1d_consec_cold = 0;
                    s1d_consec_hot  = 3;
                    s1d_triggered   = 1;
                    s1d_state = S1D_COLLECTING;
                    DBG_TRIG("1D: internal trigger start\r\n");
                }
            }
        }
        break;

    case S1D_DEBOUNCE:
        if (sys_tick_ms >= s1d_debounce_until) {
            s1d_count       = 0;
            s1d_start_time  = sys_tick_ms;
            s1d_consec_hot  = 0;
            s1d_consec_cold = 0;
            s1d_triggered   = 0;
            s1d_state = S1D_COLLECTING;
            DBG_TRIG("1D: debounce done -> collecting\r\n");
        }
        break;

    case S1D_COLLECTING: {
        if (s1d_count < MAX_1D_POINTS) {
            s1d_temp_buf[s1d_count] = sample;
            s1d_time_buf[s1d_count] = (uint16_t)(sys_tick_ms - s1d_start_time);
            s1d_count++;
        }

        if (is_hot) {
            s1d_consec_hot++;
            s1d_consec_cold = 0;
            if (!s1d_triggered && s1d_consec_hot >= 3)
                s1d_triggered = 1;
        } else {
            s1d_consec_cold++;
            s1d_consec_hot = 0;
        }

        /* Stop conditions */
        uint8_t stop = 0;
        if (s1d_count >= MAX_1D_POINTS)
            stop = 1;
        if (t1->BufferSize > 0 && s1d_count >= t1->BufferSize)
            stop = 1;
        if (s1d_triggered && t1->NgCountLimit > 0 &&
            s1d_consec_cold >= t1->NgCountLimit)
            stop = 1;

        if (stop) {
            if (s1d_triggered) {
                send_1d_collection(t1, use_buf_a);
            } else {
                DBG_DATA("1D: buf full, not triggered — discard %d pts\r\n",
                        (int)s1d_count);
            }
            reset_1d_state();
        }
        break;
    }
    }
}

/* ============================================================
 * RTOS Task: Business logic (DVP + trigger + send)
 *
 * Main pipeline — behavior is entirely driven by server config:
 *   Config2D.Enabled → 2D autonomous trigger pipeline (priority)
 *   Config1D.Enabled → 1D autonomous collection pipeline
 *   TEMP_REQ → auxiliary on-demand screenshot (always available)
 * ============================================================ */
static void task_business_entry(void *pvParameters)
{
    (void)pvParameters;
    static uint8_t use_buffer_A = 1;

    while (1)
    {
        /* NG pulse off check */
        if (g_ng_off_time && sys_tick_ms >= g_ng_off_time) {
            GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
            g_ng_off_time = 0;
        }

#if !TEST_PATTERN_MODE
        DVP_Task();
#endif

        /* -------- Auxiliary: TEMP_REQ (server on-demand screenshot) -------- */
        if (g_temp_req_pending)
        {
            g_temp_req_pending = 0;

            ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
            uint8_t ok = 0;
            if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) {
                if (g_temp_req_is2d && t2.Enabled)  ok = 1;
                if (!g_temp_req_is2d && t1.Enabled)  ok = 1;
            }

            if (!ok) {
                DBG_ERR("TEMP_REQ ignored: %s not enabled\r\n",
                        g_temp_req_is2d ? "2D" : "1D");
            } else {
                RawImageBuffer_t raw_img;
                raw_img.pData = (uint16_t *)FrameBuffer;
                raw_img.Width = SENSOR_WIDTH;
                raw_img.Height = SENSOR_HEIGHT;
                raw_img.FrameNumber = Ready_Frame_Count;

                if (g_temp_req_is2d) {
                    TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
                    use_buffer_A = !use_buffer_A;
                    tx_buf->ValidPayloadLen = 0;
                    PreprocessResult_t meta;
                    int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta);
                    if (pp_ret == 0) {
                        int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(
                            tx_buf, &meta, 0x02, 1);
                        DBG_DATA("TEMP_REQ 2D frm=%d %dx%d ret=%d\r\n",
                                (int)meta.FrameNumber, (int)meta.ValidWidth,
                                (int)meta.ValidHeight, (int)tx_ret);
                    } else {
                        DBG_ERR("TEMP_REQ PP fail ret=%d\r\n", (int)pp_ret);
                    }
                } else {
                    send_1d_snapshot(&raw_img, &use_buffer_A);
                    DBG_DATA("TEMP_REQ 1D frm=%d\r\n", (int)raw_img.FrameNumber);
                }
            }
        }

        /* -------- Main pipeline: autonomous trigger -------- */
        if (Frame_Ready_Flag)
        {
            Frame_Ready_Flag = 0;

            RawImageBuffer_t raw_img;
            raw_img.pData = (uint16_t *)FrameBuffer;
            raw_img.Width = SENSOR_WIDTH;
            raw_img.Height = SENSOR_HEIGHT;
            raw_img.FrameNumber = Ready_Frame_Count;

            ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
            int8_t has_cfg = TcpLogic_GetLatestConfig(&tc, &t2, &t1);

            if (has_cfg == 0 && t2.Enabled) {
                /* 2D mode — autonomous trigger pipeline */
                handle_2d_trigger(&raw_img, &t2, &use_buffer_A);
            } else if (has_cfg == 0 && t1.Enabled) {
                /* 1D mode — autonomous collection pipeline */
                handle_1d_trigger(&raw_img, &t1, &use_buffer_A);
            }
        }
        else
        {
            vTaskDelay(pdMS_TO_TICKS(2));
        }
    }
}

int main(void)
{
    u8 i;
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(921600);
    DBG_INIT("TCPClient SystemClk:%d\r\n", SystemCoreClock);
    DBG_INIT("TEST_PATTERN=%d\r\n", TEST_PATTERN_MODE);
    DBG_INIT("UserByte: %02x\r\n", FLASH_GetUserOptionByte() & 0xFF);
    Config_Flash_SRAM(FLASH_128_SRAM_192);
    DBG_INIT("net version:%x\r\n", WCHNET_GetVer());
    if (WCHNET_LIB_VER != WCHNET_GetVer()) DBG_ERR("net version mismatch\r\n");

    WCHNET_GetMacAddr(MACAddr);
    DBG_INIT("mac addr: %x:%x:%x:%x:%x:%x\r\n",
            MACAddr[0], MACAddr[1], MACAddr[2],
            MACAddr[3], MACAddr[4], MACAddr[5]);

#if TEST_PATTERN_MODE
    DBG_INIT("=== TEST PATTERN MODE ===\r\n");
#else
    /* 模组已通过 USB 预配置，无需 MCU UART 配置，直接初始化 DVP */
    DVP_Init();
#endif
    TIM2_Init();
    ExtTrigger_GPIO_Init();
    NG_GPIO_Init();

    i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr);
    mStopIfError(i);
    if (i == WCHNET_ERR_SUCCESS) DBG_INIT("WCHNET_LibInit OK\r\n");

#if KEEPALIVE_ENABLE
    {
        struct _KEEP_CFG cfg = {20000, 15000, 9};
        WCHNET_ConfigKeepLive(&cfg);
    }
#endif

    qdx_port_init();
    Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT);

    TcpLogic_Init(MACAddr, NULL);
    TcpLogic_RegisterConfigCallback(OnConfigUpdate);
    TcpLogic_RegisterDetectionCallback(OnDetectionResult);
    TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest);

#if TEST_PATTERN_MODE
    /* Inject test config so autonomous trigger works without server.
     * TcpLogic_InjectTestConfig → caches config + fires OnConfigUpdate
     * → Preprocess_Settings_Change. */
    {
        Config2D_t test_cfg2d;
        memset(&test_cfg2d, 0, sizeof(test_cfg2d));
        test_cfg2d.Enabled      = 0;
        test_cfg2d.TriggerMode  = 0;   /* Internal trigger (0=内部, 1=外部) */
        test_cfg2d.TargetWidth  = 64;
        test_cfg2d.TargetHeight = 64;
        test_cfg2d.TriggerRoiX  = 0;
        test_cfg2d.TriggerRoiY  = 0;
        test_cfg2d.TriggerRoiW  = SENSOR_WIDTH;
        test_cfg2d.TriggerRoiH  = SENSOR_HEIGHT;
        test_cfg2d.TriggerCondition = 1;         /* Max temperature */
        test_cfg2d.TriggerTemperatureThreshold = 800; /* 80.0°C */
        test_cfg2d.TriggerBurstCount = 3;
        test_cfg2d.TriggerInternalIntervalMs = 200;
        test_cfg2d.NGioDelay = 200;
        TcpLogic_InjectTestConfig(NULL, &test_cfg2d, NULL);
        DBG_INIT("TestCfg injected: En=1 IntTrig thresh=800 Tgt=64x64 burst=3\r\n");
    }
#endif

    DBG_INIT("TcpLogic_Start...\r\n");
    TcpLogic_Start();

    DBG_INIT("Creating RTOS tasks...\r\n");
    xTaskCreate(task_wchnet_entry,    "wchnet",   512, NULL, 6, NULL);
    xTaskCreate(task_business_entry,  "business", 512, NULL, 5, NULL);
    xTaskCreate(task_heartbeat_entry, "hb",       256, NULL, 3, NULL);
#if TEST_PATTERN_MODE
    xTaskCreate(task_test_pattern_entry, "testpat", 256, NULL, 4, NULL);
#endif
    DBG_INIT("Starting scheduler\r\n");
    vTaskStartScheduler();

    /* Should never reach here */
    while (1) {}
}
