#pragma once
#include <Arduino.h>
#include <Adafruit_AS7341.h>
#include <Wire.h>

/**
 * @brief Драйвер STEM-лаборатории на базе спектрометра AS7341 (11 каналов, 415–910 нм).
 * Инкапсулирует чтение спектра, расчёт производных величин (поглощение по Бугеру–
 * Ламберту–Беру, PAR-индекс, цветовую температуру CCT, ИК-индекс) и вотчдог связи.
 */
class As7341Lab {
public:
    /// Один снимок всех 10 каналов датчика (сырые "counts", зависят от Gain/ATIME/ASTEP)
    struct Data {
        uint16_t nm415, nm445, nm480, nm515; ///< F1–F4: фиолетовый..зелёно-голубой
        uint16_t nm555, nm590, nm630, nm680; ///< F5–F8: зелёный..красный
        uint16_t nir;                        ///< ближний ИК, ~910 нм
        uint16_t clear;                      ///< широкополосный канал без фильтра
    };

    /// Режим отображения в тестовом стенде (src/main.cpp)
    enum Mode { MODE_SPECTRUM, MODE_ABSORPTION, MODE_PAR, MODE_CCT, MODE_IR };

    /**
     * @param wire Ссылка на интерфейс I2C (Dependency Injection)
     */
    As7341Lab(TwoWire &wire = Wire);

    /**
     * @brief Инициализация датчика: старт I2C, проверка присутствия на шине,
     * настройка Gain/ATIME/ASTEP по умолчанию.
     * @param sda Пин SDA
     * @param scl Пин SCL
     * @return true, если датчик найден и все настройки применены успешно
     */
    bool begin(uint8_t sda = 21, uint8_t scl = 22);

    /**
     * @brief Считывает все 10 каналов и сохраняет их во внутренний буфер.
     * @return true при успешном обмене по I2C
     */
    bool read();

    /**
     * @brief Последний успешно считанный снимок спектра.
     */
    const Data& getData() const { return _data; }

    /**
     * @brief Установка усиления (Gain) по индексу таблицы GAINS (0 = 0.5x … 10 = 512x).
     * Выход за границы диапазона зажимается (clamp), а не приводит к неопределённому поведению.
     * @param idx Индекс в таблице усиления
     */
    void setGain(int idx);

    /// Увеличить Gain на одну ступень (зажимается на максимуме 512x)
    void gainUp();

    /// Уменьшить Gain на одну ступень (зажимается на минимуме 0.5x)
    void gainDown();

    /// Текстовое представление текущего Gain ("64x" и т.п.)
    const char* getGainStr() const;

    /**
     * @brief Сохраняет переданный снимок как опорный (reference, I0) для режима поглощения.
     * @param d Снимок, снятый при отсутствии образца (пустая кювета/чистый растворитель)
     */
    void takeReference(const Data &d);

    /// Установлен ли reference для расчёта поглощения (режим A)
    bool isRefSet() const { return _refSet; }

    /**
     * @brief Жив ли физический канал связи с датчиком.
     * true, если хотя бы одно успешное чтение по I2C было за последние timeoutMs.
     * Обрыв провода/питания должен быть заметен, а не тихо зависать в устаревших данных.
     * @param timeoutMs Порог "устаревания" последнего успешного чтения
     */
    bool isAlive(unsigned long timeoutMs = 2000) const {
        return (millis() - _lastReadTime) < timeoutMs;
    }

    // ─── Визуализация в Serial (формат совместим с Arduino Serial Plotter) ────────
    void printSpectrum(const Data &d);
    void printAbsorption(const Data &d);
    void printPAR(const Data &d);
    void printCCT(const Data &d);
    void printIR(const Data &d);
    void printHelp();

private:
    TwoWire* _wire;
    Adafruit_AS7341 _as7341;

    Data _data{};
    Data _refData{};
    bool _refSet = false;
    unsigned long _lastReadTime = 0;

    int _gainIdx = kDefaultGainIdx;

    // ─── Внутренние расчётные функции ──────────────────────────────────────────
    float calcCCT(const Data &d);
    float calcPAR(const Data &d);
    float absorbance(uint16_t I0, uint16_t I);

    /// Таблица усиления AS7341 (регистр AGAIN, датащит §"Register AGAIN 0xAA")
    /// — порядок и полнота строго соответствуют enum as7341_gain_t в Adafruit_AS7341.h
    static const as7341_gain_t GAINS[];
    static const char* GAIN_STR[];
    static constexpr int kGainCount = 11;
    static constexpr int kDefaultGainIdx = 7; // соответствует GAINS[7] = AS7341_GAIN_64X

    // Время интегрирования по умолчанию: T_int = (ATIME+1)*(ASTEP+1)*2.78мкс
    // (формула и множитель — из Adafruit_AS7341.cpp, комментарий к setASTEP/getTINT)
    // 100/999 ≈ 280 мс — компромисс между шумом и скоростью отклика для учебных опытов.
    static constexpr uint8_t  kDefaultAtime = 100;
    static constexpr uint16_t kDefaultAstep = 999;

    // Пороги ниже — авторская педагогическая калибровка этой лаборатории
    // (не из даташита AS7341), подобранная для качественной классификации
    // источников света на уроке. См. README.md разделы 3, 6, 7, 8.
    static constexpr uint16_t kAbsMinI0 = 10;   ///< ниже — reference в шуме, поглощение не считаем
    static constexpr uint16_t kAbsMinI  = 1;    ///< ниже — сигнал в шуме
    static constexpr float    kCctDarkSum = 500.0f; ///< X+Y+Z ниже — слишком темно для CCT
    static constexpr float    kParNorm = 3000.0f;   ///< нормировочный делитель PAR-индекса

    static constexpr float kParShade    = 0.10f;
    static constexpr float kParPenumbra = 0.30f;
    static constexpr float kParDiffuse  = 0.70f;

    static constexpr float kCctWarm      = 3000.0f;
    static constexpr float kCctWarmWhite = 4000.0f;
    static constexpr float kCctNeutral   = 5000.0f;
    static constexpr float kCctDaylight  = 6500.0f;

    static constexpr uint32_t kVisNoiseFloor = 200;  ///< сумма VIS ниже — NIR/VIS не считаем
    static constexpr float    kNirStrong  = 2.0f;
    static constexpr float    kNirSolar   = 0.8f;
    static constexpr float    kNirWarmLed = 0.3f;
};
