#pragma once
#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)

#include "esphome/core/component.h"
#include "esphome/core/event_pool.h"
#include "esphome/core/lock_free_queue.h"
#include "esphome/components/uart/uart_component.h"

#include <functional>
#include "freertos/ringbuf.h"
#include "tinyusb_cdc_acm.h"

namespace esphome::usb_cdc_acm {

static const uint8_t EVENT_QUEUE_SIZE = 12;

// Callback types for line coding and line state changes
using LineCodingCallback = std::function<void(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)>;
using LineStateCallback = std::function<void(bool dtr, bool rts)>;

// Event types
enum CDCEventType : uint8_t {
  CDC_EVENT_LINE_STATE_CHANGED,
  CDC_EVENT_LINE_CODING_CHANGED,
};

// Event structure for the queue
struct CDCEvent {
  CDCEventType type;
  union {
    struct {
      bool dtr;
      bool rts;
    } line_state;
    struct {
      uint32_t bit_rate;
      uint8_t stop_bits;
      uint8_t parity;
      uint8_t data_bits;
    } line_coding;
  } data;

  // Required by EventPool - called before returning to pool
  void release() {
    // No dynamic memory to clean up, data is stored inline
  }
};

// Forward declaration
class USBCDCACMComponent;

/// Represents a single CDC ACM interface instance
class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMComponent> {
 public:
  void setup();
  void loop();
  void dump_config();

  void set_interface_number(uint8_t itf) { this->itf_ = itf; }
  // Get the CDC port number for this instance
  uint8_t get_itf() const { return this->itf_; }
  // Ring buffer accessors for bridge components
  RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; }
  RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; }

  // Task handle accessor for notifying TX task
  TaskHandle_t get_tx_task_handle() const { return this->usb_tx_task_handle_; }

  // Callback registration for line coding and line state changes
  void set_line_coding_callback(LineCodingCallback callback) { this->line_coding_callback_ = std::move(callback); }
  void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); }

  // Called from USB core task context queues event for processing in main loop
  void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits);
  void queue_line_state_event(bool dtr, bool rts);

  static void usb_tx_task_fn(void *arg);
  void usb_tx_task();

  // UARTComponent interface implementation
  void write_array(const uint8_t *data, size_t len) override;
  bool peek_byte(uint8_t *data) override;
  bool read_array(uint8_t *data, size_t len) override;
  size_t available() override;
  uart::UARTFlushResult flush() override;

 protected:
  void check_logger_conflict() override;

  // Process queued events and invoke callbacks (called from main loop)
  void process_events_();
  TaskHandle_t usb_tx_task_handle_{nullptr};

  RingbufHandle_t usb_tx_ringbuf_{nullptr};
  RingbufHandle_t usb_rx_ringbuf_{nullptr};
  // RX buffer for peek functionality
  uint8_t peek_buffer_{0};
  bool has_peek_{false};
  uint8_t itf_{0};
  // User-registered callbacks (called from main loop)
  LineCodingCallback line_coding_callback_{nullptr};
  LineStateCallback line_state_callback_{nullptr};

  // Lock-free queue and event pool for cross-task event passing
  // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
  // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
  // before push() can fail, preventing both a pool slot leak and an SPSC
  // violation on the pool's internal free list.
  EventPool<CDCEvent, EVENT_QUEUE_SIZE - 1> event_pool_;
  LockFreeQueue<CDCEvent, EVENT_QUEUE_SIZE> event_queue_;
};

/// Main USB CDC ACM component that manages the USB device and all CDC interfaces
class USBCDCACMComponent : public Component {
 public:
  USBCDCACMComponent();

  void setup() override;
  void loop() override;
  void dump_config() override;
  float get_setup_priority() const override { return setup_priority::IO; }

  // Interface management
  void add_interface(USBCDCACMInstance *interface);
  USBCDCACMInstance *get_interface_by_number(uint8_t itf);

 protected:
  std::array<USBCDCACMInstance *, ESPHOME_MAX_USB_CDC_INSTANCES> interfaces_{};
};

extern USBCDCACMComponent *global_usb_cdc_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

}  // namespace esphome::usb_cdc_acm
#endif
