// buzzer.c
#include "buzzer.h"

#include "evse_events.h"
#include "auth_events.h"
#include "network_events.h"

#include "esp_event.h"
#include "esp_log.h"
#include "esp_err.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"

#include "driver/gpio.h"
#include "driver/ledc.h"

#include <time.h>
#include <string.h>
#include <stddef.h>

// ===================== Configuração padrão =====================
#define CONFIG_BUZZER_GPIO GPIO_NUM_27

// 1 = PASSIVE (PWM), 0 = ACTIVE (on/off)
#define CONFIG_BUZZER_MODE_PASSIVE 1

#define CONFIG_BUZZER_FREQ_HZ 2400
#define CONFIG_BUZZER_DUTY_PCT 40

#define CONFIG_BUZZER_QUEUE_LEN 8
#define CONFIG_BUZZER_TASK_STACK 2048
#define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1)

// anti-spam (gap mínimo entre toques)
#define CONFIG_BUZZER_MIN_GAP_MS 70

#define CONFIG_BUZZER_ENABLE_DEFAULT 1

// quiet hours start (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_START_MIN (22 * 60)
// quiet hours end (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_END_MIN (7 * 60)

// ===================== Tipos e estado =====================
static const char *TAG = "Buzzer";

typedef enum
{
    BUZZER_MODE_ACTIVE = 0,
    BUZZER_MODE_PASSIVE
} buzzer_mode_t;

typedef struct
{
    uint16_t on_ms;
    uint16_t off_ms;
} buzzer_step_t;

typedef struct
{
    const buzzer_step_t *steps;
    size_t length;
} buzzer_pattern_t;

typedef struct
{
    buzzer_mode_t mode;
    gpio_num_t gpio;

    // PASSIVE (PWM)
    uint32_t freq_hz;
    uint8_t duty_percent;             // 0–100
    ledc_mode_t ledc_speed_mode;      // LEDC_LOW_SPEED_MODE
    ledc_timer_t ledc_timer;          // LEDC_TIMER_1
    ledc_channel_t ledc_channel;      // LEDC_CHANNEL_1
    ledc_timer_bit_t duty_resolution; // LEDC_TIMER_10_BIT
} buzzer_config_t;

// Controlo runtime
static struct
{
    bool enabled;
    uint16_t quiet_start_min; // [0..1440)
    uint16_t quiet_end_min;   // [0..1440)
    bool quiet_enabled;
} s_buzzer_ctl = {
    .enabled = CONFIG_BUZZER_ENABLE_DEFAULT,
    .quiet_start_min = CONFIG_BUZZER_QUIET_START_MIN,
    .quiet_end_min = CONFIG_BUZZER_QUIET_END_MIN,
    .quiet_enabled = false,
};

// Hardware cfg
static buzzer_config_t s_buzzer_cfg = {
    .mode = CONFIG_BUZZER_MODE_PASSIVE ? BUZZER_MODE_PASSIVE : BUZZER_MODE_ACTIVE,
    .gpio = CONFIG_BUZZER_GPIO,
    .freq_hz = CONFIG_BUZZER_FREQ_HZ,
    .duty_percent = CONFIG_BUZZER_DUTY_PCT,
    .ledc_speed_mode = LEDC_LOW_SPEED_MODE,
    .ledc_timer = LEDC_TIMER_1,
    .ledc_channel = LEDC_CHANNEL_1,
    .duty_resolution = LEDC_TIMER_10_BIT};

// Fila e task
static QueueHandle_t s_buzzer_q = NULL;
static TaskHandle_t s_buzzer_task = NULL;

// Anti-spam básico
static TickType_t s_last_play_tick = 0;
static const TickType_t s_min_gap_ticks = pdMS_TO_TICKS(CONFIG_BUZZER_MIN_GAP_MS);

// STOP / preempt (robusto em dual-core)
static EventGroupHandle_t s_buzzer_eg = NULL;
#define BUZZER_EG_STOP_BIT (1u << 0)

static bool s_inited = false;

static inline bool buzzer_should_stop(void)
{
    if (!s_buzzer_eg)
        return false;
    return (xEventGroupGetBits(s_buzzer_eg) & BUZZER_EG_STOP_BIT) != 0;
}

static inline void buzzer_clear_stop(void)
{
    if (s_buzzer_eg)
        xEventGroupClearBits(s_buzzer_eg, BUZZER_EG_STOP_BIT);
}

static inline void buzzer_set_stop(void)
{
    if (s_buzzer_eg)
        xEventGroupSetBits(s_buzzer_eg, BUZZER_EG_STOP_BIT);
}

// ===================== Padrões (afinados) =====================
// NOTA: os enums BUZZER_PATTERN_* vêm de buzzer_events.h

// Curtos e claros
static const buzzer_step_t pattern_plugged[] = {{180, 120}};          // ~300 ms
static const buzzer_step_t pattern_card_read[] = {{80, 80}, {80, 0}}; // 160 ms audível

// Um pouco mais longos, ritmo marcante
static const buzzer_step_t pattern_unplugged[] = {{140, 140}, {140, 140}, {140, 0}}; // ~700 ms
static const buzzer_step_t pattern_card_add[] = {{100, 100}, {100, 100}, {120, 0}};  // ~520 ms
static const buzzer_step_t pattern_ap_start[] = {{250, 150}, {250, 0}};              // ~650 ms

// Charging mais curta (evento frequente)
static const buzzer_step_t pattern_charging[] = {{80, 90}, {90, 80}, {100, 0}}; // ~440 ms

// Denied mais “duro”
static const buzzer_step_t pattern_card_denied[] = {{250, 120}, {300, 0}}; // ~670 ms

// Fault crítico (duas trincas, ~1.7 s)
static const buzzer_step_t pattern_fault[] = {
    {350, 120}, {350, 120}, {450, 250}, // bloco 1
    {350, 120},
    {350, 120},
    {450, 0} // bloco 2
};

// BUG importante corrigido: tamanho explícito = BUZZER_PATTERN_MAX
static const buzzer_pattern_t buzzer_patterns[BUZZER_PATTERN_MAX] = {
    [BUZZER_PATTERN_PLUGGED] = {pattern_plugged, sizeof(pattern_plugged) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_UNPLUGGED] = {pattern_unplugged, sizeof(pattern_unplugged) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_CHARGING] = {pattern_charging, sizeof(pattern_charging) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_AP_START] = {pattern_ap_start, sizeof(pattern_ap_start) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_CARD_READ] = {pattern_card_read, sizeof(pattern_card_read) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_CARD_ADD] = {pattern_card_add, sizeof(pattern_card_add) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_CARD_DENIED] = {pattern_card_denied, sizeof(pattern_card_denied) / sizeof(buzzer_step_t)},
    [BUZZER_PATTERN_FAULT] = {pattern_fault, sizeof(pattern_fault) / sizeof(buzzer_step_t)},
};

// ===================== Helpers HW =====================
static inline uint32_t duty_from_percent(uint8_t pct, ledc_timer_bit_t res)
{
    if (pct == 0)
        return 0;
    if (pct > 100)
        pct = 100;
    uint32_t max = (1U << res) - 1U;
    return (uint32_t)((pct * max) / 100U);
}

static inline void buzzer_set_duty_raw(uint32_t duty)
{
    ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, duty);
    ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
}

static inline void buzzer_set_duty_pct(uint8_t pct)
{
    buzzer_set_duty_raw(duty_from_percent(pct, s_buzzer_cfg.duty_resolution));
}

// Fade async (NO_WAIT) para permitir STOP responsivo (checamos por nós)
static inline void buzzer_fade_to_pct_async(uint8_t pct, uint32_t time_ms)
{
    uint32_t duty = duty_from_percent(pct, s_buzzer_cfg.duty_resolution);
    ledc_set_fade_with_time(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, duty, time_ms);
    ledc_fade_start(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, LEDC_FADE_NO_WAIT);
}

static inline bool is_pattern_valid(buzzer_pattern_id_t id)
{
    return (id > BUZZER_PATTERN_NONE && id < BUZZER_PATTERN_MAX);
}

static inline bool is_critical_pattern(buzzer_pattern_id_t id)
{
    return (id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED);
}

static void buzzer_off(void)
{
    if (!s_inited)
        return;

    if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
    {
        esp_err_t err1 = ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, 0);
        if (err1 != ESP_OK)
            ESP_LOGW(TAG, "ledc_set_duty(0) err=%s", esp_err_to_name(err1));

        esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
        if (err2 != ESP_OK)
            ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2));
    }
    else
    {
        gpio_set_level(s_buzzer_cfg.gpio, 0);
    }
}

static bool in_quiet_hours(void)
{
    if (!s_buzzer_ctl.quiet_enabled)
        return false;

    time_t now = time(NULL);

    // Se ainda não há tempo real (NTP não acertou), não aplicar quiet hours
    if (now < 1700000000) // ~2023-11
        return false;

    struct tm lt;
    if (localtime_r(&now, &lt) == NULL)
        return false;

    uint16_t minutes = (uint16_t)(lt.tm_hour * 60 + lt.tm_min);
    uint16_t start = s_buzzer_ctl.quiet_start_min;
    uint16_t end = s_buzzer_ctl.quiet_end_min;

    if (start == end)
        return false; // desativado

    if (start < end)
        return (minutes >= start && minutes < end);
    else
        return (minutes >= start || minutes < end); // cruza meia-noite
}

// ===================== Execução do padrão (apenas dentro da task) =====================
static void buzzer_execute(buzzer_pattern_id_t pattern_id)
{
    if (!is_pattern_valid(pattern_id))
        return;

    const buzzer_pattern_t *pattern = &buzzer_patterns[pattern_id];
    if (!pattern->steps || pattern->length == 0)
        return;

    // Afinar aqui (para “premium”)
    const uint32_t FADE_IN_MS = 10;
    const uint32_t FADE_OUT_MS = 20;

    // Não limpar STOP aqui — se alguém chamou buzzer_stop(), queremos respeitar já.
    for (size_t i = 0; i < pattern->length; i++)
    {
        if (buzzer_should_stop())
            break;

        uint32_t on_ms = pattern->steps[i].on_ms;
        uint32_t off_ms = pattern->steps[i].off_ms;

        if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
        {
            // Sempre começar em 0 para evitar "click"
            buzzer_set_duty_pct(0);

            uint32_t fi = (on_ms > (FADE_IN_MS + FADE_OUT_MS)) ? FADE_IN_MS : (on_ms / 3);
            uint32_t fo = (on_ms > (FADE_IN_MS + FADE_OUT_MS)) ? FADE_OUT_MS : (on_ms / 3);
            uint32_t sustain = (on_ms > (fi + fo)) ? (on_ms - fi - fo) : 0;

            // Fade in (async) + espera em passos para STOP rápido
            if (fi > 0)
                buzzer_fade_to_pct_async(s_buzzer_cfg.duty_percent, fi);
            for (uint32_t t = 0; t < fi; t += 10)
            {
                if (buzzer_should_stop())
                    break;
                vTaskDelay(pdMS_TO_TICKS(10));
            }

            // Sustain
            for (uint32_t t = 0; t < sustain; t += 10)
            {
                if (buzzer_should_stop())
                    break;
                vTaskDelay(pdMS_TO_TICKS(10));
            }

            if (buzzer_should_stop())
                break;

            // Fade out (async) + espera em passos
            if (fo > 0)
                buzzer_fade_to_pct_async(0, fo);
            for (uint32_t t = 0; t < fo; t += 10)
            {
                if (buzzer_should_stop())
                    break;
                vTaskDelay(pdMS_TO_TICKS(10));
            }

            buzzer_set_duty_pct(0);
        }
        else
        {
            // ACTIVE (liga/desliga simples)
            gpio_set_level(s_buzzer_cfg.gpio, 1);
            for (uint32_t t = 0; t < on_ms; t += 10)
            {
                if (buzzer_should_stop())
                    break;
                vTaskDelay(pdMS_TO_TICKS(10));
            }
            gpio_set_level(s_buzzer_cfg.gpio, 0);
        }

        if (buzzer_should_stop())
            break;

        for (uint32_t t = 0; t < off_ms; t += 10)
        {
            if (buzzer_should_stop())
                break;
            vTaskDelay(pdMS_TO_TICKS(10));
        }
    }

    buzzer_off();
}

// ===================== Task & Fila =====================
static void buzzer_task(void *arg)
{
    buzzer_pattern_id_t id;

    for (;;)
    {
        if (xQueueReceive(s_buzzer_q, &id, portMAX_DELAY) == pdTRUE)
        {
            ESP_LOGD(TAG, "dequeue pattern %d", id);

            if (!s_buzzer_ctl.enabled)
                continue;

            // Quiet hours: permitir apenas alertas críticos/negativos
            if (in_quiet_hours() && !is_critical_pattern(id))
                continue;

            // Anti-spam global (NÃO aplicar a padrões críticos)
            if (!is_critical_pattern(id))
            {
                TickType_t now = xTaskGetTickCount();
                if ((now - s_last_play_tick) < s_min_gap_ticks)
                    continue;
                s_last_play_tick = now;
            }

            // Se veio STOP pendente, limpamos antes de começar um novo padrão
            buzzer_clear_stop();

            buzzer_execute(id);

            // Se alguém pediu stop durante execução, já paramos; limpamos o bit.
            buzzer_clear_stop();
        }
    }
}

// ===================== API pública =====================
void buzzer_play(buzzer_pattern_id_t id)
{
    ESP_LOGD(TAG, "enqueue pattern %d", id);

    if (!s_inited || !is_pattern_valid(id) || s_buzzer_q == NULL)
        return;

    // Críticos: preempt + vão para a frente
    if (is_critical_pattern(id))
    {
        buzzer_stop();
        if (xQueueSendToFront(s_buzzer_q, &id, pdMS_TO_TICKS(10)) != pdTRUE)
            ESP_LOGW(TAG, "buzzer queue full, dropped critical pattern=%d", id);
        return;
    }

    // Não-críticos: pequeno timeout + log se drop
    if (xQueueSend(s_buzzer_q, &id, pdMS_TO_TICKS(10)) != pdTRUE)
        ESP_LOGW(TAG, "buzzer queue full, dropped pattern=%d", id);
}

void buzzer_stop(void)
{
    if (!s_inited)
        return;

    // Interrompe imediatamente: sinaliza stop, esvazia fila e desliga
    buzzer_set_stop();
    if (s_buzzer_q)
        xQueueReset(s_buzzer_q);
    buzzer_off();
}

void buzzer_set_enabled(bool enabled)
{
    s_buzzer_ctl.enabled = enabled;
    if (!enabled)
        buzzer_off();
}

bool buzzer_get_enabled(void)
{
    return s_buzzer_ctl.enabled;
}

void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min)
{
    s_buzzer_ctl.quiet_enabled = enabled;
    if (start_min < 1440)
        s_buzzer_ctl.quiet_start_min = start_min;
    if (end_min < 1440)
        s_buzzer_ctl.quiet_end_min = end_min;
}

// ledc_set_freq normalmente devolve a frequência efetiva (0 se falhar)
uint32_t buzzer_set_frequency(uint32_t hz)
{
    if (!s_inited)
        return 0;
    if (s_buzzer_cfg.mode != BUZZER_MODE_PASSIVE)
        return 0;
    if (hz < 50 || hz > 20000)
        return 0;

    s_buzzer_cfg.freq_hz = hz;

    uint32_t eff = ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz);
    if (eff == 0)
        ESP_LOGW(TAG, "ledc_set_freq failed (hz=%lu)", (unsigned long)hz);
    return eff;
}

void buzzer_set_duty_percent(uint8_t pct)
{
    if (pct > 100)
        pct = 100;
    s_buzzer_cfg.duty_percent = pct;
}

// ===================== Event Handlers =====================
static void buzzer_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    if (base != BUZZER_EVENTS || id != BUZZER_EVENT_PLAY_PATTERN || data == NULL)
        return;

    buzzer_event_data_t *evt = (buzzer_event_data_t *)data;
    if (!is_pattern_valid(evt->pattern))
    {
        ESP_LOGW(TAG, "Invalid buzzer pattern received: %d", evt->pattern);
        return;
    }
    buzzer_play(evt->pattern);
}

static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
        return;

    const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
    ESP_LOGD(TAG, "EVSE event: state=%d", evt->state);

    buzzer_event_data_t buzzer_evt = {0};
    switch (evt->state)
    {
    case EVSE_STATE_EVENT_IDLE:
        buzzer_evt.pattern = BUZZER_PATTERN_UNPLUGGED;
        break;
    case EVSE_STATE_EVENT_WAITING:
        buzzer_evt.pattern = BUZZER_PATTERN_PLUGGED;
        break;
    case EVSE_STATE_EVENT_CHARGING:
        buzzer_evt.pattern = BUZZER_PATTERN_CHARGING;
        break;
    case EVSE_STATE_EVENT_FAULT:
        buzzer_stop();
        buzzer_evt.pattern = BUZZER_PATTERN_FAULT;
        break;
    default:
        return;
    }

    esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY);
}

static void network_event_handler(void *handler_args, esp_event_base_t base, int32_t id, void *event_data)
{
    if (base != NETWORK_EVENTS)
        return;

    ESP_LOGD(TAG, "Network event id=%d", (int)id);

    buzzer_event_data_t evt = {0};

    switch ((network_event_id_t)id)
    {
    case NETWORK_EVENT_AP_STARTED:
    case NETWORK_EVENT_STA_CONNECTED:
        evt.pattern = BUZZER_PATTERN_AP_START;
        break;
    default:
        return;
    }

    esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY);
}

static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
    if (base != AUTH_EVENTS || event_data == NULL)
        return;

    buzzer_event_data_t buzzer_evt = {0};

    if (id == AUTH_EVENT_TAG_PROCESSED)
    {
        const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)event_data;
        ESP_LOGD(TAG, "AUTH processed: tag=%s authorized=%d", evt->tag, evt->authorized);
        buzzer_evt.pattern = evt->authorized ? BUZZER_PATTERN_CARD_READ : BUZZER_PATTERN_CARD_DENIED;
    }
    else if (id == AUTH_EVENT_TAG_SAVED)
    {
        buzzer_evt.pattern = BUZZER_PATTERN_CARD_ADD;
    }
    else
    {
        return;
    }

    esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY);
}

// ===================== Inicialização / Deinit =====================
void buzzer_init(void)
{
    if (s_inited)
    {
        ESP_LOGW(TAG, "buzzer_init called twice");
        return;
    }

    // Config HW
    if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
    {
        ledc_timer_config_t tcfg = {
            .speed_mode = s_buzzer_cfg.ledc_speed_mode,
            .duty_resolution = s_buzzer_cfg.duty_resolution,
            .timer_num = s_buzzer_cfg.ledc_timer,
            .freq_hz = s_buzzer_cfg.freq_hz,
            .clk_cfg = LEDC_AUTO_CLK};
        ESP_ERROR_CHECK(ledc_timer_config(&tcfg));

        ledc_channel_config_t ccfg = {
            .gpio_num = s_buzzer_cfg.gpio,
            .speed_mode = s_buzzer_cfg.ledc_speed_mode,
            .channel = s_buzzer_cfg.ledc_channel,
            .intr_type = LEDC_INTR_DISABLE,
            .timer_sel = s_buzzer_cfg.ledc_timer,
            .duty = 0,
            .hpoint = 0};
        ESP_ERROR_CHECK(ledc_channel_config(&ccfg));

        // Evita crash se já estiver instalado (init repetido em app maior)
        esp_err_t fe = ledc_fade_func_install(0);
        if (fe != ESP_OK && fe != ESP_ERR_INVALID_STATE)
            ESP_ERROR_CHECK(fe);
    }
    else
    {
        gpio_config_t io = {
            .pin_bit_mask = (1ULL << s_buzzer_cfg.gpio),
            .mode = GPIO_MODE_OUTPUT,
            .pull_down_en = 0,
            .pull_up_en = 0,
            .intr_type = GPIO_INTR_DISABLE};
        ESP_ERROR_CHECK(gpio_config(&io));
        gpio_set_level(s_buzzer_cfg.gpio, 0);
    }

    // STOP event group
    s_buzzer_eg = xEventGroupCreate();
    configASSERT(s_buzzer_eg);

    // Fila + Task
    s_buzzer_q = xQueueCreate(CONFIG_BUZZER_QUEUE_LEN, sizeof(buzzer_pattern_id_t));
    configASSERT(s_buzzer_q != NULL);

    BaseType_t ok = xTaskCreatePinnedToCore(
        buzzer_task,
        "buzzer_task",
        CONFIG_BUZZER_TASK_STACK,
        NULL,
        CONFIG_BUZZER_TASK_PRIO,
        &s_buzzer_task,
        tskNO_AFFINITY);
    configASSERT(ok == pdPASS);

    // Runtime defaults
    buzzer_set_quiet_hours(false, 0, 0);
    buzzer_set_enabled(true);

    s_inited = true;
    buzzer_off();

    // Handlers
    ESP_ERROR_CHECK(esp_event_handler_register(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler, NULL));

    ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s), freq=%lu Hz, duty=%u%%, enabled=%d",
             s_buzzer_cfg.gpio,
             s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE ? "passive/PWM" : "active/ON-OFF",
             (unsigned long)s_buzzer_cfg.freq_hz,
             (unsigned)s_buzzer_cfg.duty_percent,
             (int)s_buzzer_ctl.enabled);
}

void buzzer_deinit(void)
{
    if (!s_inited)
        return;

    (void)esp_event_handler_unregister(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler);
    (void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler);
    (void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler);
    (void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler);
    (void)esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler);

    // Para já
    buzzer_stop();

    // Mata task ANTES de apagar a queue (evita uso-after-free raro)
    if (s_buzzer_task)
    {
        vTaskDelete(s_buzzer_task);
        s_buzzer_task = NULL;
    }

    if (s_buzzer_q)
    {
        vQueueDelete(s_buzzer_q);
        s_buzzer_q = NULL;
    }

    if (s_buzzer_eg)
    {
        vEventGroupDelete(s_buzzer_eg);
        s_buzzer_eg = NULL;
    }

    s_inited = false;
    buzzer_off();

    ESP_LOGI(TAG, "Buzzer deinitialized");
}