#pragma once
#include <Arduino.h>

/// Неблокирующий звуковой паттерн. Конкретные тайминги ниже (kAlertPeriodMs,
/// kConfirmPulseMs) — методический выбор для наглядности на уроке, а не значения
/// из даташита: у активного бузера нет "правильной" длительности сигнала.
enum class Pattern : uint8_t {
    None,
    Alert,   ///< Прерывистая тревога: пищит/молчит поочерёдно, пока не остановят turnOff()
    Confirm  ///< Два коротких писка подтверждения, затем сам выключается
};

/**
 * @brief Драйвер активного звукового излучателя (зуммера со встроенным генератором).
 *
 * В отличие от пассивного бузера, активный не нужно "раскачивать" через tone()/PWM —
 * внутри уже есть генератор, и достаточно подать на управляющий пин нужный логический
 * уровень, чтобы зуммер запищал на своей фиксированной частоте. Все сигналы (одиночный
 * писк, паттерны) реализованы через машину состояний в update() — вызовы НЕ используют
 * delay(), поэтому не блокируют остальную программу.
 *
 * @note В этом драйвере нет isAlive(). Бузер — исполнительное устройство без канала
 * обратной связи (только digitalWrite на пин), поэтому обрыв провода или неисправность
 * самого зуммера программно не детектируются — это физическое ограничение схемы, а не
 * упущение драйвера.
 */
class ActiveBuzzer {
public:
    /**
     * @param pin Пин управления бузером (по умолчанию GPIO 15, см. WIRING.md).
     * @param activeLow Полярность включения: true — бузер включается низким уровнем
     * (см. WIRING.md, раздел "Особенности логики").
     */
    explicit ActiveBuzzer(uint8_t pin = 15, bool activeLow = false);

    /** @brief Настройка пина на выход и гарантированное выключение бузера. Вызвать один раз в setup(). */
    void begin();

    /** @brief Включить бузер и держать включённым, пока не вызовут turnOff()/beep()/playPattern(). */
    void turnOn();

    /** @brief Немедленно выключить бузер и сбросить текущий паттерн. */
    void turnOff();

    /**
     * @brief Издать одиночный писк заданной длительности (неблокирующий вызов).
     * @param durationMs Длительность писка в миллисекундах. 0 игнорируется (бузер не включается) —
     * так вызывающий код не может случайно "подвесить" бузер во включённом состоянии навсегда.
     */
    void beep(uint32_t durationMs);

    /**
     * @brief Запустить неблокирующий звуковой паттерн.
     * @param pattern Alert — прерывистая тревога до явного turnOff(); Confirm — два коротких писка.
     * Повторный вызов с тем же паттерном, что уже играет, игнорируется (не перезапускает его с начала).
     */
    void playPattern(Pattern pattern);

    /** @brief Обновление машины состояний. Обязательно вызывать на каждой итерации loop(). */
    void update();

    /** @brief true, пока бузер издаёт звук (одиночный писк или паттерн). */
    bool isActive() const { return _active; }

private:
    void setPinState(bool on);

    static constexpr uint16_t kAlertPeriodMs = 500;   ///< Alert: 500мс вкл / 500мс выкл
    static constexpr uint16_t kConfirmPulseMs = 100;  ///< Confirm: писк-пауза-писк по 100мс

    uint8_t _pin;
    bool _activeLow;
    bool _active = false;
    Pattern _currentPattern = Pattern::None;

    unsigned long _lastUpdate = 0;
    unsigned long _singleBeepDurationMs = 0;
    uint8_t _step = 0;
};
