#pragma once
#include <Arduino.h>
#include <esp_idf_version.h>
#include "Config.h"
#include "Core.h"

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
#define OPTICAL_USE_RMT_DMA 1
#include <driver/rmt_rx.h>
#else
#define OPTICAL_USE_RMT_DMA 0
#include <driver/gpio.h>
#endif

class PulseReceiver {
 public:
  bool begin();
  bool start(uint32_t expectedHz, float expectedDutyPct);
  void stop();
  void resetStream();
  size_t readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks = 0);
  bool overflowed();
  uint32_t takeDroppedItems();
  uint32_t tickHz() const;
  uint32_t plannedTickHz(uint32_t expectedHz, float expectedDutyPct) const;
  uint16_t receiveChunkSymbols() const { return receiveChunkSymbols_; }
  bool highRateBackend() const {
#if OPTICAL_USE_RMT_DMA && CONFIG_IDF_TARGET_ESP32S3
    return true;
#else
    return false;
#endif
  }
 private:
  struct Edge { uint32_t tick; uint8_t rising; };
  bool consumeEdge(const Edge &edge, PulsePeriod &period);

#if OPTICAL_USE_RMT_DMA
  static constexpr size_t BLOCK_SYMBOLS = RMT_MAX_RECEIVE_SYMBOLS;
  struct SymbolBlock { uint16_t count; rmt_symbol_word_t symbols[BLOCK_SYMBOLS]; };
  static bool IRAM_ATTR onRmt(rmt_channel_handle_t, const rmt_rx_done_event_data_t *, void *);
  bool configureRmt(uint32_t resolutionHz);
  bool nextRmtEdge(Edge &edge, TickType_t waitTicks);
  rmt_channel_handle_t channel_ = nullptr;
  uint32_t captureResolutionHz_ = 0;
  rmt_symbol_word_t receiveBuffer_[RMT_MAX_RECEIVE_SYMBOLS];
  uint16_t receiveChunkSymbols_ = 0;
  SymbolBlock isrBlock_ = {};
  SymbolBlock block_ = {};
  uint16_t blockIndex_ = 0;
  uint8_t phase_ = 0;
  bool haveLevel_ = false;
  bool level_ = false;
  uint32_t rmtTick_ = 0;
#else
  static void IRAM_ATTR onGpio(void *ctx);
  uint32_t cpuTickHz_ = 0;
#endif
  QueueHandle_t queue_ = nullptr;
  volatile bool overflow_ = false;
  volatile uint32_t droppedItems_ = 0;
  bool running_ = false;
  bool haveRise_ = false, haveFall_ = false, haveRawTick_ = false;
  uint32_t lastRawTick_ = 0;
  uint64_t tickEpoch_ = 0, rise_ = 0, fall_ = 0;
};
