#pragma once

// WARNING: This component is EXPERIMENTAL. The API may change at any time
// without following the normal breaking changes policy. Use at your own risk.
// Once the API is considered stable, this warning will be removed.

#include "esphome/core/defines.h"

#ifdef USE_SERIAL_PROXY

#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/uart/uart.h"

// Include api_pb2.h only when the API is enabled. The full include is needed
// to hold SerialProxyDataReceived by value as a pre-allocated member.
// Guarding prevents pulling conflicting Zephyr logging macro names into
// translation units that include this header without USE_API defined.
#ifdef USE_API
#include "esphome/components/api/api_pb2.h"
#endif

// Forward-declare types needed outside the USE_API guard.
namespace esphome::api {
class APIConnection;
namespace enums {
enum SerialProxyPortType : uint32_t;
enum SerialProxyRequestType : uint32_t;
}  // namespace enums
}  // namespace esphome::api

namespace esphome::serial_proxy {

/// Bit flags for the line_states field exchanged with API clients.
/// Bit positions are stable API — new signals must use the next available bit.
enum SerialProxyLineStateFlag : uint32_t {
  SERIAL_PROXY_LINE_STATE_FLAG_RTS = 1 << 0,  ///< RTS (Request To Send)
  SERIAL_PROXY_LINE_STATE_FLAG_DTR = 1 << 1,  ///< DTR (Data Terminal Ready)
};

/// Maximum bytes to read from UART in a single loop iteration
inline constexpr size_t SERIAL_PROXY_MAX_READ_SIZE = 256;

class SerialProxy : public uart::UARTDevice, public Component {
 public:
  void setup() override;
  void loop() override;
  void dump_config() override;
  float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }

  /// Get the instance index (position in Application's serial_proxies_ vector)
  uint32_t get_instance_index() const { return this->instance_index_; }

  /// Set the instance index (called by Application::register_serial_proxy)
  void set_instance_index(uint32_t index) { this->instance_index_ = index; }

  /// Set the human-readable port name (from YAML configuration)
  void set_name(const char *name) { this->name_ = name; }

  /// Get the human-readable port name
  const char *get_name() const { return this->name_; }

  /// Set the port type (from YAML configuration)
  void set_port_type(api::enums::SerialProxyPortType port_type) { this->port_type_ = port_type; }

  /// Get the port type
  api::enums::SerialProxyPortType get_port_type() const { return this->port_type_; }

  /// Configure UART parameters and apply them
  /// @param baudrate Baud rate in bits per second
  /// @param flow_control True to enable hardware flow control
  /// @param parity Parity setting (0=none, 1=even, 2=odd)
  /// @param stop_bits Number of stop bits (1 or 2)
  /// @param data_size Number of data bits (5-8)
  void configure(uint32_t baudrate, bool flow_control, uint8_t parity, uint8_t stop_bits, uint8_t data_size);

  /// Get the currently subscribed API connection (nullptr if none)
  api::APIConnection *get_api_connection() { return this->api_connection_; }

  /// Handle a subscribe/unsubscribe request from an API client
  void serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type);

  /// Write data received from an API client to the serial device
  /// @param data Pointer to data buffer
  /// @param len Number of bytes to write
  void write_from_client(const uint8_t *data, size_t len);

  /// Set modem pin states from a bitmask of SerialProxyLineStateFlag values
  void set_modem_pins(uint32_t line_states);

  /// Get current modem pin states as a bitmask of SerialProxyLineStateFlag values
  uint32_t get_modem_pins() const;

  /// Flush the serial port (block until all TX data is sent)
  uart::FlushResult flush_port();

  /// Set the RTS GPIO pin (from YAML configuration)
  void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }

  /// Set the DTR GPIO pin (from YAML configuration)
  void set_dtr_pin(GPIOPin *pin) { this->dtr_pin_ = pin; }

 protected:
#ifdef USE_API
  /// Read from UART and send to API client (slow path with 256-byte stack buffer)
  void read_and_send_(size_t available);
#endif

  /// Instance index for identifying this proxy in API messages
  uint32_t instance_index_{0};

  /// Subscribed API client (only one allowed at a time)
  api::APIConnection *api_connection_{nullptr};

#ifdef USE_API
  /// Pre-allocated outgoing message; instance field is set once in setup()
  api::SerialProxyDataReceived outgoing_msg_;
#endif

  /// Human-readable port name (points to a string literal in flash)
  const char *name_{nullptr};

  /// Port type
  api::enums::SerialProxyPortType port_type_{};

  /// Optional GPIO pins for modem control
  GPIOPin *rts_pin_{nullptr};
  GPIOPin *dtr_pin_{nullptr};

  /// Current modem pin states
  bool rts_state_{false};
  bool dtr_state_{false};
};

}  // namespace esphome::serial_proxy

#endif  // USE_SERIAL_PROXY
