#pragma once
#include <Arduino.h>

/**
 * @brief Драйвер миллиметрового радара присутствия HMMD (24 ГГц, UART).
 * В отличие от PIR-датчиков (см. PIR_sensor_AM312), радар обнаруживает не только движение,
 * но и неподвижные цели по микро-движениям (дыхание) — подробности в THEORY.md.
 *
 * @note О протоколе: официальная документация Waveshare на момент написания драйвера неполна,
 * а по независимым источникам сообщения о конкретном формате вывода этого датчика расходятся —
 * либо текстовые ASCII-строки вида "Range: N", либо бинарные кадры в стиле семейства Hi-Link
 * LD2410 (заголовок FD FC FB FA / хвост 04 03 02 01). См. THEORY.md, раздел "Протокол — что
 * подтверждено, а что нет". Поэтому драйвер НЕ отправляет никаких HEX-команд настройки (они не
 * были подтверждены ни одним источником) и распознаёт оба формата вывода на приёме, полагаясь на
 * то, что датчик по умолчанию сам начинает передавать данные сразу после включения.
 */
class HmmdRadar {
public:
    /// Как был разобран последний валидный кадр — полезно для диагностики в тестовом стенде.
    enum class FrameFormat : uint8_t {
        Unknown,  ///< Ни одного валидного кадра ещё не было
        Ascii,    ///< Текстовая строка вида "Range: N"
        Binary    ///< Бинарный кадр с заголовком FD FC FB FA (см. @note выше)
    };

    /// Последнее известное состояние цели в контролируемой зоне.
    struct Data {
        bool targetDetected = false;      ///< true, пока цель числится в зоне (с учётом hold time)
        float distanceM = 0.0f;           ///< Дистанция до цели, метры; 0, если цели нет
        FrameFormat format = FrameFormat::Unknown;  ///< Какой формат кадра распознан
    };

    /**
     * @param serial Поток UART, к которому подключён радар (Dependency Injection, например Serial2)
     * @param maxDistanceM Дистанция отсечки: показания дальше игнорируются как шум/цель за зоной интереса
     * @param holdTimeMs Через сколько мс без валидных данных цель считается ушедшей из зоны
     */
    explicit HmmdRadar(Stream& serial, float maxDistanceM = 2.10f, uint32_t holdTimeMs = 2000);

    /// Ничего не настраивает на самом радаре (см. @note класса) — только сбрасывает внутреннее состояние.
    void begin();

    /// Считать и разобрать всё, что накопилось в UART, обновить Data. Вызывать в каждой итерации loop().
    void update();

    /// Последнее известное состояние цели.
    const Data& getData() const { return _data; }

    /**
     * @brief Вотчдог связи с радаром.
     * @param timeoutMs Через сколько мс без единого валидного кадра считать связь потерянной.
     * @return true, если за последние timeoutMs пришёл хотя бы один разобранный кадр (ASCII или бинарный).
     */
    bool isAlive(unsigned long timeoutMs = 3000) const;

private:
    bool tryParseAsciiLine(const String& line);
    bool tryParseBinaryFrame();
    void applyDistance(float distanceM, FrameFormat format);

    Stream* _serial;
    float _maxDistanceM;
    uint32_t _holdTimeMs;

    String _rxLine;
    uint8_t _binBuf[32];
    size_t _binLen = 0;

    Data _data;
    unsigned long _lastTargetMs = 0;
    unsigned long _lastRxMs = 0;
};
