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

#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
#define OPTICAL_USE_MCPWM_CAPTURE 1
#include <driver/mcpwm_cap.h>
#else
#define OPTICAL_USE_MCPWM_CAPTURE 0
#endif

enum class CaptureSource : uint8_t { RX, TX, WINDOW_END };

struct CaptureEvent {
  uint64_t tick;
  bool rising;
  CaptureSource source;
};

class PulseReceiver {
 public:
  bool begin();
  bool start(uint32_t expectedHz, float expectedDutyPct, bool activeLightOn);
  bool startDriver(uint32_t frequencyHz, uint32_t pulseNs,
                   bool activeTxLightOn);
  void stop();
  void resetStream();
  size_t readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks = 0);
  size_t readEvents(CaptureEvent *events, size_t capacity, TickType_t waitTicks = 0);
  uint32_t takeDroppedItems();
  uint32_t tickHz() const;
  uint32_t pulseTickHz() const { return tickHz(); }
  uint32_t plannedTickHz(uint32_t expectedHz, float expectedDutyPct) const;
  uint32_t plannedPulseTickHz(uint32_t expectedHz, float expectedDutyPct) const {
    return plannedTickHz(expectedHz, expectedDutyPct);
  }
  uint16_t receiveChunkSymbols() const { return 1; }
  bool highRateBackend() const { return OPTICAL_USE_MCPWM_CAPTURE; }

 private:
  struct Edge { uint32_t tick; uint8_t rising; uint8_t source; };
  static constexpr uint16_t DRIVER_RING_CAPACITY = 512;
  static_assert((DRIVER_RING_CAPACITY & (DRIVER_RING_CAPACITY - 1U)) == 0,
    "driver capture ring must be a power of two");
  struct TimedEdge { uint64_t tick; bool rising; };
  bool startCapture(bool withTx);
  bool consumeEdge(const Edge &edge, PulsePeriod &period);
  bool nextOrderedEdge(Edge &edge, TickType_t waitTicks);
  TimedEdge extendEdge(const Edge &edge);
#if OPTICAL_USE_MCPWM_CAPTURE
  bool configureDriverTxCapture(bool risingEdge);
  static bool IRAM_ATTR onCapture(mcpwm_cap_channel_handle_t,
    const mcpwm_capture_event_data_t *, void *);
  mcpwm_cap_timer_handle_t captureTimer_ = nullptr;
  mcpwm_cap_channel_handle_t risingChannel_ = nullptr;
  mcpwm_cap_channel_handle_t fallingChannel_ = nullptr;
  mcpwm_cap_channel_handle_t txChannel_ = nullptr;
  uint32_t captureResolutionHz_ = 0;
#else
  static void IRAM_ATTR onGpio(void *ctx);
  uint32_t cpuTickHz_ = 0;
#endif
  QueueHandle_t queue_ = nullptr;
  Edge driverRing_[DRIVER_RING_CAPACITY] = {};
  volatile uint16_t driverRingWrite_ = 0;
  volatile uint16_t driverRingRead_ = 0;
  portMUX_TYPE driverRingMux_ = portMUX_INITIALIZER_UNLOCKED;
  Edge lastDriverEdge_ = {};
  bool haveLastDriverEdge_ = false;
  uint32_t driverPulseTicks_ = 0;
  uint32_t driverReleaseSlackTicks_ = 0;
  Edge reorderEdge_ = {};
  bool haveReorderEdge_ = false;
  volatile uint32_t droppedItems_ = 0;
  volatile bool running_ = false;
  volatile bool txCaptureEnabled_ = false;
  uint32_t expectedHz_ = 0;
  float expectedDutyPct_ = 50.0f;
  bool polarityKnown_ = false, activeStartRising_ = false;
  TimedEdge polarityEdges_[3] = {};
  uint8_t polarityEdgeCount_ = 0;
  bool waitingForActiveEnd_ = true;
  uint64_t activeStart_ = 0, activeEnd_ = 0;
  bool haveRawTick_ = false;
  uint32_t lastRawTick_ = 0;
  uint64_t tickEpoch_ = 0;
};
