// SPDX-License-Identifier: MIT
/*
 * Copyright (C) Mathieu Carbou
 */
#pragma once

#include "MycilaDimmer.h"
#include <driver/gptimer_types.h>

namespace Mycila {
  class CycleStealingDimmer : public Dimmer {
    public:
      virtual ~CycleStealingDimmer() { end(); }

      /**
       * @brief Set the GPIO pin to use for the dimmer
       */
      void setPin(gpio_num_t pin) { _pin = pin; }

      /**
       * @brief Get the GPIO pin used for the dimmer
       */
      gpio_num_t getPin() const { return _pin; }

      /**
       * @brief Enable a dimmer on a specific GPIO pin
       *
       * @warning Dimmer won't be enabled if pin is invalid
       */
      bool begin() override;

      /**
       * @brief Disable the dimmer
       *
       * @warning Dimmer won't be destroyed but won't turn on anymore even is a duty cycle is set.
       */
      void end() override;

      const char* type() const override { return "cycle-stealing"; }

      /**
       * Optional: Integration with a Zero-Cross Detection (ZCD) system
       *
       * Callback to be called when a zero-crossing event is detected.
       *
       * This is optional when using standard (sync) Solid State Relays (SSR) that only activate or deactivate when the AC voltage crosses zero.
       * This is usually the behavior of most SSRs available on the market.
       *
       * If you are using a Random Solid State Relay (SSR) or a TRIAC that can be triggered at any point in the AC cycle, then you need to set this callback
       * so that the library knows when to fire and stop the SSR/TRIAC.
       *
       * When using MycilaPulseAnalyzer library, this callback can be registered like this:
       *
       * pulseAnalyzer.onZeroCross(Mycila::CycleStealingDimmer::onZeroCross);
       *
       * - When using your own ISR with the RobotDyn ZCD,      you can call this method with delayUntilZero == 200 since the length of the ZCD pulse is about  400 us.
       * - When using your own ISR with the ZCd from Daniel S, you can call this method with delayUntilZero == 550 since the length of the ZCD pulse is about 1100 us.
       */
      static void onZeroCross(int16_t delayUntilZero, void* args);

#ifdef MYCILA_JSON_SUPPORT
      /**
       * @brief Serialize Dimmer information to a JSON object
       *
       * @param root: the JSON object to serialize to
       */
      void toJson(const JsonObject& root) const override {
        Dimmer::toJson(root);
        root["pin"] = _pin;
      }
#endif

    protected:
      bool _apply() override;

      bool _calculateDimmerHarmonics(float* array, size_t n) const override {
        // Unlike phase control (which distorts every cycle identically and creates standard odd harmonics like 3rd, 5th, 7th), cycle stealing creates sub-harmonics
        // and inter-harmonics (frequencies below 50/60Hz or between standard multiples).
        // Since the algorithm uses a dynamic Delta-Sigma modulator (Bresenham-like) rather than a fixed pattern length, the "period" of the repetition is not fixed
        // or easily predicted without a complex simulation.
        return false;
      }

    private:
      gpio_num_t _pin = GPIO_NUM_NC;
      uint16_t duty_milli = 0; // Duty cycle scaled 0–1000; updated from _apply() (avoids float in ISR)
      // Cycle stealing state tracking
      bool semi_period_odd = false; // Track odd/even semi-periods for balance
      int32_t density_error = 0;    // Bresenham accumulator, scaled ×1000 (threshold: 1000)
      int8_t dc_balance = 0;        // DC component balance (-1: owes positive, 1: owes negative)

      struct RegisteredDimmer {
          CycleStealingDimmer* dimmer = nullptr;
          RegisteredDimmer* prev = nullptr;
          RegisteredDimmer* next = nullptr;
      };

      static struct RegisteredDimmer* dimmers;
      static bool _fireTimerISR(gptimer_handle_t timer, const gptimer_alarm_event_data_t* event, void* arg);
      static void _registerDimmer(Mycila::CycleStealingDimmer* dimmer);
      static void _unregisterDimmer(Mycila::CycleStealingDimmer* dimmer);
  };
} // namespace Mycila
