/*
 * kline_operations.c
 *
 * Non-blocking KWP1281 operations for the FSM architecture.
 *
 * Each operation (ReadDfi, WriteDfi, ReadVoltage, ReadAudiPin,
 * ClearFaultCodes) is implemented as a multi-step state machine driven
 * by KLineOp_Poll(), which is called every main-loop iteration.
 *
 * Design invariants:
 *   - No HAL_Delay anywhere.
 *   - All timing is deadline-based: deadline = HAL_GetTick() + N.
 *   - RX via KLine_Ring_Available() / KLine_Ring_Pop().
 *   - TX via KLine_FSM_TxByte() (synchronous, safe at 9600 baud).
 *   - Packet counter via KLine_FSM_GetNextCounter() (shared session).
 *   - Keep-alive suspended while an op runs (KLine_FSM_SuspendKA).
 */

#include "kline_operations.h"
#include "kline_fsm.h"
#include "kline_ring.h"
#include "IKW1281Connection.h"
#include "main.h"

#include <stdint.h>
#include <string.h>

/* ------------------------------------------------------------------ */
/*  Timing constants                                                    */
/* ------------------------------------------------------------------ */

#define OP_BYTE_TIMEOUT_MS     1000u
#define OP_INTERBYTE_TX_MS     5u

/* ------------------------------------------------------------------ */
/*  Internal operation types                                            */
/* ------------------------------------------------------------------ */

typedef enum {
    OPTYPE_NONE = 0,
    OPTYPE_READ_DFI,
    OPTYPE_WRITE_DFI,
    OPTYPE_READ_VOLTAGE,
    OPTYPE_READ_AUDI_PIN,
    OPTYPE_CLEAR_FAULT_CODES,
} OpType;

/* ------------------------------------------------------------------ */
/*  Sub-step states for the generic send/receive packet engine          */
/* ------------------------------------------------------------------ */

typedef enum {
    /* Idle / done */
    OPS_IDLE,

    /* Send packet bytes one at a time, wait for complement ACK */
    OPS_SEND_BYTE,
    OPS_SEND_WAIT_ACK,
    OPS_SEND_INTERBYTE_DELAY,
    OPS_SEND_END_BYTE,

    /* Receive a packet byte by byte */
    OPS_RECV_LENGTH,
    OPS_RECV_COUNTER,
    OPS_RECV_COMMAND,
    OPS_RECV_BODY,
    OPS_RECV_END,

    /* After receiving a non-ACK/NAK packet, send ACK then receive more */
    OPS_RECV_SEND_ACK,

    /* High-level sequencer decides what comes next */
    OPS_STEP_DONE,

} OpSubState;

/* ------------------------------------------------------------------ */
/*  Operation context                                                   */
/* ------------------------------------------------------------------ */

#define OP_MAX_SEND       24
#define OP_MAX_RECV_RAW   20
#define OP_MAX_RECV_PKTS   8

typedef struct {
    /* What operation */
    OpType          type;
    KLineOp_Status  result;

    /* Sub-state */
    OpSubState      sub;
    uint32_t        deadline;

    /* Send buffer */
    uint8_t         send_buf[OP_MAX_SEND];
    uint8_t         send_len;
    uint8_t         send_idx;
    uint8_t         last_sent;

    /* Receive scratch */
    ParsedPacket    cur_pkt;
    uint8_t         cur_body_idx;

    /* Completed receive packets for current burst */
    ParsedPacket    rx_pkts[OP_MAX_RECV_PKTS];
    int             rx_pkt_count;

    /* High-level step index (each op defines its own sequence) */
    int             step;

    /* Operation-specific params/results */
    float           dfi_value;
    int             dfi_version;
    uint16_t        u16_result;     /* voltage or audi pin */
    int             int_result;     /* WriteDfi / ClearFC success */

} OpCtx;

static OpCtx op;

/* ------------------------------------------------------------------ */
/*  Packet classification (same logic as kline_fsm.c)                  */
/* ------------------------------------------------------------------ */

static void op_classify_packet(ParsedPacket *p)
{
    p->title = p->raw[2];
    switch (p->raw[2]) {
        case PACKET_CMD_ACK:
            p->type = PACKET_TYPE_ACK;
            p->isAckNak = 1;
            break;
        case PACKET_CMD_NAK:
            p->type = PACKET_TYPE_NAK;
            p->isAckNak = 1;
            break;
        case PACKET_CMD_AsciiData:
            p->type = (p->raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC
                                           : PACKET_TYPE_ASCII_DATA;
            break;
        case PACKET_CMD_ReadEepromResponse:
            p->type = PACKET_TYPE_READ_EEPROM_RESPONSE;
            break;
        case PACKET_CMD_ReadRomEepromResponse:
            p->type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE;
            break;
        default:
            p->type = PACKET_TYPE_UNKNOWN;
            break;
    }
}

/* ------------------------------------------------------------------ */
/*  Build a KWP1281 packet into op.send_buf                            */
/* ------------------------------------------------------------------ */

static void op_build_packet(const uint8_t *payload, uint8_t payload_len)
{
    op.send_len = payload_len + 2;        /* length + counter + payload */
    op.send_buf[0] = op.send_len;
    op.send_buf[1] = KLine_FSM_GetNextCounter();
    for (uint8_t i = 0; i < payload_len; i++)
        op.send_buf[2 + i] = payload[i];
    op.send_idx = 0;
}

/* ------------------------------------------------------------------ */
/*  Enter send / receive sub-states                                     */
/* ------------------------------------------------------------------ */

static void op_enter_send(const uint8_t *payload, uint8_t len)
{
    op_build_packet(payload, len);
    op.sub      = OPS_SEND_BYTE;
    op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
}

static void op_enter_recv(void)
{
    op.rx_pkt_count = 0;
    memset(&op.cur_pkt, 0, sizeof(op.cur_pkt));
    op.cur_body_idx = 0;
    op.sub      = OPS_RECV_LENGTH;
    op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
}

/* ------------------------------------------------------------------ */
/*  Byte-level RX helpers                                               */
/* ------------------------------------------------------------------ */

/* Read a byte and send complement ACK. Returns 1 if got byte. */
static uint8_t op_try_read_ack(uint8_t *out)
{
    if (!KLine_Ring_Available()) {
        if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) {
            op.result = KLOP_FAIL;
            op.sub    = OPS_IDLE;
        }
        return 0;
    }
    *out = KLine_Ring_Pop();
    KLine_FSM_TxByte((uint8_t)~(*out));
    op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
    return 1;
}

/* Read a byte with no ACK (for end byte). */
static uint8_t op_try_read_raw(uint8_t *out)
{
    if (!KLine_Ring_Available()) {
        if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) {
            op.result = KLOP_FAIL;
            op.sub    = OPS_IDLE;
        }
        return 0;
    }
    *out = KLine_Ring_Pop();
    op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
    return 1;
}

/* ------------------------------------------------------------------ */
/*  Send sub-machine                                                    */
/* ------------------------------------------------------------------ */

static void op_run_send(void)
{
    switch (op.sub)
    {
    case OPS_SEND_BYTE:
        if (op.send_idx >= op.send_len) {
            /* All data bytes sent — send end byte (no ACK expected) */
            KLine_FSM_TxByte(PACKET_END_EXPECTED);
            /* Packet fully transmitted */
            op.sub = OPS_STEP_DONE;
            return;
        }
        op.last_sent = op.send_buf[op.send_idx++];
        KLine_FSM_TxByte(op.last_sent);
        op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
        op.sub = OPS_SEND_WAIT_ACK;
        break;

    case OPS_SEND_WAIT_ACK:
        if (!KLine_Ring_Available()) {
            if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) {
                op.result = KLOP_FAIL;
                op.sub    = OPS_IDLE;
            }
            return;
        }
        (void)KLine_Ring_Pop();  /* consume complement ACK */
        op.deadline = HAL_GetTick() + OP_INTERBYTE_TX_MS;
        op.sub = OPS_SEND_INTERBYTE_DELAY;
        break;

    case OPS_SEND_INTERBYTE_DELAY:
        if ((int32_t)(HAL_GetTick() - op.deadline) < 0) return;
        op.sub = OPS_SEND_BYTE;
        break;

    default:
        break;
    }
}

/* ------------------------------------------------------------------ */
/*  Receive sub-machine                                                 */
/* ------------------------------------------------------------------ */

static void op_run_recv(void)
{
    switch (op.sub)
    {
    case OPS_RECV_LENGTH:
    {
        uint8_t b;
        if (!op_try_read_ack(&b)) return;
        memset(&op.cur_pkt, 0, sizeof(op.cur_pkt));
        op.cur_body_idx = 0;
        op.cur_pkt.raw[0] = b;
        op.sub = OPS_RECV_COUNTER;
        break;
    }

    case OPS_RECV_COUNTER:
    {
        uint8_t b;
        if (!op_try_read_ack(&b)) return;
        op.cur_pkt.raw[1] = b;
        op.sub = OPS_RECV_COMMAND;
        break;
    }

    case OPS_RECV_COMMAND:
    {
        uint8_t b;
        if (!op_try_read_ack(&b)) return;
        op.cur_pkt.raw[2] = b;
        op.cur_body_idx = 3;
        if (op.cur_pkt.raw[0] <= 3) {
            op.sub = OPS_RECV_END;
        } else {
            op.sub = OPS_RECV_BODY;
        }
        break;
    }

    case OPS_RECV_BODY:
    {
        uint8_t b;
        if (!op_try_read_ack(&b)) return;
        if (op.cur_body_idx < OP_MAX_RECV_RAW)
            op.cur_pkt.raw[op.cur_body_idx] = b;
        op.cur_body_idx++;
        if (op.cur_body_idx >= op.cur_pkt.raw[0]) {
            op.sub = OPS_RECV_END;
        }
        break;
    }

    case OPS_RECV_END:
    {
        uint8_t b;
        if (!op_try_read_raw(&b)) return;
        op.cur_pkt.raw[op.cur_body_idx] = b;
        op.cur_pkt.length = op.cur_body_idx + 1;
        op_classify_packet(&op.cur_pkt);

        if (op.rx_pkt_count < OP_MAX_RECV_PKTS)
            op.rx_pkts[op.rx_pkt_count++] = op.cur_pkt;

        if (op.cur_pkt.isAckNak) {
            /* Burst complete */
            op.sub = OPS_STEP_DONE;
        } else {
            /* More packets — need to send ACK then continue receiving */
            op.sub = OPS_RECV_SEND_ACK;
        }
        break;
    }

    case OPS_RECV_SEND_ACK:
    {
        /* Build and send an ACK packet, then loop back to recv */
        uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK;
        op_build_packet(&ack_payload, 1);
        /* We'll send this packet then re-enter recv */
        op.sub = OPS_SEND_BYTE;
        /* Mark that after this send completes, we go back to receiving.
         * We use a special trick: after send completes (OPS_STEP_DONE),
         * the sequencer checks if we were mid-burst and loops back. */
        break;
    }

    default:
        break;
    }
}

/* ------------------------------------------------------------------ */
/*  Helper: send a packet then receive the response burst              */
/*  Returns 1 when the full send+receive is done, 0 while busy.       */
/*  The sequencer uses op.step to track phases:                        */
/*    step  N  → sending packet                                        */
/*    step N+1 → receiving response                                    */
/* ------------------------------------------------------------------ */

/* Send-then-receive sequencer states per step:
 *  phase 0 = start send
 *  phase 1 = finish send → start recv
 *  phase 2 = finish recv → done
 */
typedef enum {
    PHASE_SEND_START,
    PHASE_SENDING,
    PHASE_RECV_START,
    PHASE_RECEIVING,
    PHASE_COMPLETE,
} SendRecvPhase;

static SendRecvPhase sr_phase;
static uint8_t sr_was_mid_burst;  /* flag: we were sending an ACK mid-burst */

static void sr_begin_send(const uint8_t *payload, uint8_t len)
{
    op_enter_send(payload, len);
    sr_phase = PHASE_SENDING;
    sr_was_mid_burst = 0;
}

static void sr_begin_send_then_recv(const uint8_t *payload, uint8_t len)
{
    op_enter_send(payload, len);
    sr_phase = PHASE_SENDING;
    sr_was_mid_burst = 0;
}

/* Returns 1 when the send-then-receive cycle is complete */
static uint8_t sr_poll(void)
{
    if (op.result == KLOP_FAIL) return 1;  /* error abort */

    switch (sr_phase)
    {
    case PHASE_SENDING:
        if (op.sub == OPS_STEP_DONE) {
            if (sr_was_mid_burst) {
                /* We just finished sending an intra-burst ACK — go back to recv */
                sr_was_mid_burst = 0;
                op_enter_recv();
                sr_phase = PHASE_RECEIVING;
                /* But we keep the existing rx_pkt list intact — don't reset count */
                /* Actually op_enter_recv resets count. We need a variant. */
                /* Fix: manually set sub to recv length without resetting pkt list */
                /* We'll inline it: */
                memset(&op.cur_pkt, 0, sizeof(op.cur_pkt));
                op.cur_body_idx = 0;
                op.sub      = OPS_RECV_LENGTH;
                op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
                return 0;
            }
            /* Normal send complete — start receiving */
            op_enter_recv();
            sr_phase = PHASE_RECEIVING;
            return 0;
        }
        /* Still sending */
        if (op.sub >= OPS_SEND_BYTE && op.sub <= OPS_SEND_INTERBYTE_DELAY) {
            op_run_send();
        }
        return 0;

    case PHASE_RECEIVING:
        if (op.sub == OPS_STEP_DONE) { //AQUI ACABA DE ENVIAR EL MENSAJE
            /* Burst complete */
            sr_phase = PHASE_COMPLETE;
            return 1;
        }
        if (op.sub == OPS_RECV_SEND_ACK) {
            /* Need to send ACK mid-burst, then continue recv */
            uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK;
            op_build_packet(&ack_payload, 1);
            op.sub = OPS_SEND_BYTE;
            op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
            sr_was_mid_burst = 1;
            sr_phase = PHASE_SENDING;
            return 0;
        }
        if (op.sub >= OPS_RECV_LENGTH && op.sub <= OPS_RECV_END) {
            op_run_recv();
        }
        return 0;

    case PHASE_COMPLETE:
        return 1;

    default:
        return 1;
    }
}

/* ------------------------------------------------------------------ */
/*  Send-only sequencer (no receive expected, or we handle it inline)  */
/* ------------------------------------------------------------------ */

static void send_only_begin(const uint8_t *payload, uint8_t len)
{
    op_enter_send(payload, len);
    sr_phase = PHASE_SENDING;
    sr_was_mid_burst = 0;
}

static uint8_t send_only_poll(void)
{
    if (op.result == KLOP_FAIL) return 1;
    if (op.sub == OPS_STEP_DONE) return 1;
    if (op.sub >= OPS_SEND_BYTE && op.sub <= OPS_SEND_INTERBYTE_DELAY) {
        op_run_send();
    }
    return 0;
}

/* ------------------------------------------------------------------ */
/*  Helper: find first non-ACK/NAK packet in rx list                   */
/* ------------------------------------------------------------------ */

static ParsedPacket* op_find_data_packet(void)
{
    for (int i = 0; i < op.rx_pkt_count; i++) {
        if (!op.rx_pkts[i].isAckNak)
            return &op.rx_pkts[i];
    }
    return (ParsedPacket*)0;
}

/* ------------------------------------------------------------------ */
/*  Common init/finish for all operations                               */
/* ------------------------------------------------------------------ */

static int op_begin(OpType t)
{
    if (op.type != OPTYPE_NONE) return -1;   /* already busy */
    memset(&op, 0, sizeof(op));
    op.type   = t;
    op.result = KLOP_BUSY;
    op.sub    = OPS_IDLE;
    op.step   = 0;
    sr_phase  = PHASE_SEND_START;
    KLine_FSM_SuspendKA(1);
    return 0;
}

static void op_finish(KLineOp_Status s)
{
    op.result = s;
    op.type   = OPTYPE_NONE;
    op.sub    = OPS_IDLE;
    KLine_FSM_SuspendKA(0);
    KLine_FSM_ResetKATimer();
}

/* ================================================================== */
/*  ReadRomEeprom: send {0x03, count, addr_hi, addr_lo} → recv        */
/*  Extracts body bytes raw[3 .. length-2]                             */
/* ================================================================== */

/* Shared result buffer for ROM EEPROM reads */
static uint8_t rom_eeprom_data[16];
static int     rom_eeprom_len;

/* ================================================================== */
/*  Operation: ReadVoltage                                              */
/*  ReadRomEeprom(0x0142, 2) → (data[1]<<8)|data[0]                   */
/* ================================================================== */

static void poll_read_voltage(void)
{
    switch (op.step)
    {
    case 0:
    {
        uint8_t req[4] = {
            (uint8_t)PACKET_CMD_ReadRomEeprom,
            2,                              /* count */
            (uint8_t)(0x0142 >> 8),         /* addr hi */
            (uint8_t)(0x0142 & 0xFF)        /* addr lo */
        };
        sr_begin_send_then_recv(req, 4);
        op.step = 1;
        break;
    }
    case 1:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) {op_finish(KLOP_FAIL);	return; }
        {
            /* Check for NAK */
            if (op.rx_pkt_count == 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) {
                op_finish(KLOP_FAIL);
                return;
            }
            ParsedPacket *p = op_find_data_packet();
            if (!p || p->length < 5) {
                op_finish(KLOP_FAIL);
                return;
            }
            int data_len = p->length - 4;
            if (data_len >= 2) {
                op.u16_result = (uint16_t)((p->raw[4] << 8) | p->raw[3]);
            } else {
                op_finish(KLOP_FAIL);
                return;
            }
        }
        op_finish(KLOP_OK);
        break;
    }
}

/* ================================================================== */
/*  Operation: ReadAudiPin                                              */
/*  ReadRomEeprom(0x04FA, 2) → (data[1]<<8)|data[0]                   */
/* ================================================================== */

static void poll_read_audi_pin(void)
{
    switch (op.step)
    {
    case 0:
    {
        uint8_t req[4] = {
            (uint8_t)PACKET_CMD_ReadRomEeprom,
            2,
            (uint8_t)(0x04FA >> 8),
            (uint8_t)(0x04FA & 0xFF)
        };
        sr_begin_send_then_recv(req, 4);
        op.step = 1;
        break;
    }
    case 1:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
        {
            if (op.rx_pkt_count == 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) {
                op_finish(KLOP_FAIL);
                return;
            }
            ParsedPacket *p = op_find_data_packet();
            if (!p || p->length < 5) {
                op_finish(KLOP_FAIL);
                return;
            }
            int data_len = p->length - 4;
            if (data_len >= 2) {
                op.u16_result = (uint16_t)((p->raw[4] << 8) | p->raw[3]);
            } else {
                op_finish(KLOP_FAIL);
                return;
            }
        }
        op_finish(KLOP_OK);
        break;
    }
}

/* ================================================================== */
/*  Operation: ClearFaultCodes                                          */
/*  Send {0x05}, expect ACK or NAK back                                */
/* ================================================================== */

static void poll_clear_fault_codes(void)
{
    switch (op.step)
    {
    case 0:
    {
        uint8_t req[1] = { (uint8_t)PACKET_CMD_FaultCodesDelete };
        sr_begin_send_then_recv(req, 1);
        op.step = 1;
        break;
    }
    case 1:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
        {
            if (op.rx_pkt_count >= 1 && op.rx_pkts[0].type == PACKET_TYPE_ACK) {
                op.int_result = 1;
                op_finish(KLOP_OK);
            } else {
                op.int_result = 0;
                op_finish(KLOP_FAIL);
            }
        }
        break;
    }
}

/* ================================================================== */
/*  Operation: ReadDfi                                                  */
/*  Step 0: send group reading unlock {0x18,0x00,0x03,0xFF,0xFF}       */
/*  Step 1: recv response                                               */
/*  Step 2: send ReadEeprom {0x19,0x02,0x00,0x44}                      */
/*  Step 3: recv response → extract DFI                                 */
/* ================================================================== */

static void poll_read_dfi(void)
{
    switch (op.step)
    {
    case 0:
    {
        /* Group reading unlock */
        uint8_t req[5] = { 0x18, 0x00, 0x03, 0xFF, 0xFF };
        sr_begin_send_then_recv(req, 5);
        op.step = 1;
        break;
    }
    case 1:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
        /* Don't care about the unlock response content — move on */
        op.step = 2;
        break;

    case 2:
    {
        /* ReadEeprom at 0x0044 */
        uint8_t req[4] = {
            (uint8_t)PACKET_CMD_ReadEeprom,
            0x02, 0x00, 0x44
        };
        sr_begin_send_then_recv(req, 4);
        op.step = 3;
        break;
    }
    case 3:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
        {
            ParsedPacket *p = op_find_data_packet();
            if (!p || p->length < 5) {
                op.dfi_value = 0.0f;
                op_finish(KLOP_FAIL);
                return;
            }
            /* DFI = (int8_t)raw[3] * 3.0 / 256.0 */
            int8_t raw_val = (int8_t)p->raw[3];
            op.dfi_value = ((float)raw_val * 3.0f) / 256.0f;
        }
        op_finish(KLOP_OK);
        break;
    }
}

/* ================================================================== */
/*  Operation: WriteDfi                                                 */
/*  Step 0: send version-dependent password (14 bytes)                  */
/*  Step 1: recv response                                               */
/*  Step 2: send WriteEeprom {0x1A,0x02,0x00,0x44,val,~val,0x03}      */
/*  Step 3: recv response                                               */
/* ================================================================== */

static const uint8_t pass_default[14] = {
    0x18, 0x00, 0x03, 0x2F, 0xFF,
    0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
};
static const uint8_t pass_v2[14] = {
    0x18, 0x00, 0x03, 0x2F, 0xF2,
    0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
};
static const uint8_t pass_v3v4[14] = {
    0x18, 0x00, 0x03, 0xFF, 0xF2,
    0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
};

static void poll_write_dfi(void)
{
    switch (op.step)
    {
    case 0:
    {
        /* Select password based on version */
        const uint8_t *password = pass_default;
        if (op.dfi_version == 1) {
            password = pass_v2;
        } else if (op.dfi_version == 2 || op.dfi_version == 3) {
            password = pass_v3v4;
        }
        sr_begin_send_then_recv(password, 14);
        op.step = 1;
        break;
    }
    case 1:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
        /* Password response received — move on to write */
        op.step = 2;
        break;

    case 2:
    {
        /* Compute value byte */
        int vi = (int)((op.dfi_value * 256.0f) / 3.0f);
        if (vi >  127) vi =  127;
        if (vi < -128) vi = -128;
        int8_t value = (int8_t)vi;
        if (value == 0) value = 1;

        uint8_t value_u    = (uint8_t)value;
        uint8_t value_csum = (uint8_t)(0u - value_u);

        uint8_t req[7] = {
            (uint8_t)PACKET_CMD_WriteEeprom,
            0x02, 0x00, 0x44,
            value_u, value_csum, 0x03
        };
        sr_begin_send_then_recv(req, 7);
        op.step = 3;
        break;
    }
    case 3:
        if (!sr_poll()) return;
        if (op.result == KLOP_FAIL) {
            op.int_result = 0;
            op_finish(KLOP_FAIL);
            return;
        }
        /* Check for NAK */
        if (op.rx_pkt_count >= 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) {
            op.int_result = 0;
            op_finish(KLOP_FAIL);
            return;
        }
        op.int_result = 1;
        op_finish(KLOP_OK);
        break;
    }
}

/* ================================================================== */
/*  Public API: Start functions                                         */
/* ================================================================== */

int KLineOp_Start_ReadDfi(void)
{
    return op_begin(OPTYPE_READ_DFI);
}

int KLineOp_Start_WriteDfi(float dfi, int version)
{
    if (op_begin(OPTYPE_WRITE_DFI) != 0) return -1;
    op.dfi_value   = dfi;
    op.dfi_version = version;
    return 0;
}

int KLineOp_Start_ReadVoltage(void)
{
    return op_begin(OPTYPE_READ_VOLTAGE);
}

int KLineOp_Start_ReadAudiPin(void)
{
    return op_begin(OPTYPE_READ_AUDI_PIN);
}

int KLineOp_Start_ClearFaultCodes(void)
{
    return op_begin(OPTYPE_CLEAR_FAULT_CODES);
}

/* ================================================================== */
/*  Public API: Poll                                                    */
/* ================================================================== */

KLineOp_Status KLineOp_Poll(void)
{
    if (op.result != KLOP_BUSY) return op.result;

    switch (op.type)
    {
    case OPTYPE_READ_DFI:           poll_read_dfi();            break;
    case OPTYPE_WRITE_DFI:          poll_write_dfi();           break;
    case OPTYPE_READ_VOLTAGE:       poll_read_voltage();        break;
    case OPTYPE_READ_AUDI_PIN:      poll_read_audi_pin();       break;
    case OPTYPE_CLEAR_FAULT_CODES:  poll_clear_fault_codes();   break;
    default:
        op.result = KLOP_IDLE;
        break;
    }

    return op.result;
}

/* ================================================================== */
/*  Public API: Result accessors                                        */
/* ================================================================== */

float    KLineOp_Result_Dfi(void)             { return op.dfi_value;   }
uint16_t KLineOp_Result_Voltage(void)         { return op.u16_result;  }
uint16_t KLineOp_Result_AudiPin(void)         { return op.u16_result;  }
int      KLineOp_Result_WriteDfi(void)        { return op.int_result;  }
int      KLineOp_Result_ClearFaultCodes(void) { return op.int_result;  }

/* ================================================================== */
/*  Legacy shim functions (called from hc_06.c)                         */
/*                                                                      */
/*  These are the OLD blocking API signatures. They now kick off the    */
/*  async operation and BLOCK by polling in a tight loop. This is a     */
/*  transitional shim so hc_06.c keeps working without changes.         */
/*                                                                      */
/*  For a fully non-blocking architecture, hc_06.c should be updated    */
/*  to use KLineOp_Start_XXX() + KLineOp_Poll() directly.              */
/* ================================================================== */

float ReadDfi(void)
{
    if (KLineOp_Start_ReadDfi() != 0) return 0.0f;
    KLineOp_Status s;
    do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
    return (s == KLOP_OK) ? KLineOp_Result_Dfi() : 0.0f;
}

int WriteDfi(float dfi, int version)
{
    if (KLineOp_Start_WriteDfi(dfi, version) != 0) return 0;
    KLineOp_Status s;
    do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
    return (s == KLOP_OK) ? KLineOp_Result_WriteDfi() : 0;
}

uint8_t ReadVoltage(uint16_t *volt)
{
    if (volt) *volt = 0;
    if (KLineOp_Start_ReadVoltage() != 0) return 0;
    KLineOp_Status s;
    do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
    if (s == KLOP_OK) {
        if (volt) *volt = KLineOp_Result_Voltage();
        return 1;
    }
    return 0;
}

uint8_t ReadAudiPin(uint16_t *pin)
{
    if (pin) *pin = 0;
    if (KLineOp_Start_ReadAudiPin() != 0) return 0;
    KLineOp_Status s;
    do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
    if (s == KLOP_OK) {
        if (pin) *pin = KLineOp_Result_AudiPin();
        return 1;
    }
    return 0;
}

int ClearFaultCodes(void)
{
    if (KLineOp_Start_ClearFaultCodes() != 0) return 0;
    KLineOp_Status s;
    do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
    return (s == KLOP_OK) ? KLineOp_Result_ClearFaultCodes() : 0;
}
