#pragma once

#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"

#include <memory>
#include <span>

#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"

#define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \
 protected: \
  ld24xx::SensorWithDedup<dedup_type> name##_sensor_{}; \
\
 public: \
  void set_##name##_sensor(sensor::Sensor *sensor) { this->name##_sensor_.set_sensor(sensor); }
#endif

#define LOG_SENSOR_WITH_DEDUP_SAFE(tag, name, sensor) \
  if ((sensor).has_sensor()) { \
    LOG_SENSOR(tag, name, (sensor).get_sensor()); \
  }

#define SAFE_PUBLISH_SENSOR(sensor, value) (sensor).publish_state_if_not_dup(value)

#define SAFE_PUBLISH_SENSOR_UNKNOWN(sensor) (sensor).publish_state_unknown()

#define highbyte(val) (uint8_t)((val) >> 8)
#define lowbyte(val) (uint8_t)((val) &0xff)

namespace esphome::ld24xx {

// Helper to find index of value in constexpr array
template<size_t N> optional<size_t> find_index(const uint32_t (&arr)[N], uint32_t value) {
  for (size_t i = 0; i < N; i++) {
    if (arr[i] == value)
      return i;
  }
  return {};
}

static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";

// Helper function to format MAC address with stack allocation
// Returns pointer to UNKNOWN_MAC constant or formatted buffer
// Buffer must be exactly 18 bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator)
inline const char *format_mac_str(const uint8_t *mac_address, std::span<char, 18> buffer) {
  if (mac_address_is_valid(mac_address)) {
    format_mac_addr_upper(mac_address, buffer.data());
    return buffer.data();
  }
  return UNKNOWN_MAC;
}

// Helper function to format firmware version with stack allocation
// Buffer must be exactly 20 bytes (format: "x.xxXXXXXX" fits in 11 + null terminator, 20 for safety)
inline void format_version_str(const uint8_t *version, std::span<char, 20> buffer) {
  snprintf(buffer.data(), buffer.size(), VERSION_FMT, version[1], version[0], version[5], version[4], version[3],
           version[2]);
}

#ifdef USE_SENSOR
/// Sensor with deduplication — sensor may be null, null check is internal.
/// Stored inline, no heap allocation. Does nothing when no sensor is set.
template<typename T> class SensorWithDedup {
 public:
  void set_sensor(sensor::Sensor *sens) {
    this->sens_ = sens;
    this->dedup_ = {};
  }

  void publish_state_if_not_dup(T state) {
    if (this->sens_ != nullptr && this->dedup_.next(state)) {
      this->sens_->publish_state(static_cast<float>(state));
    }
  }

  void publish_state_unknown() {
    if (this->sens_ != nullptr && this->dedup_.next_unknown()) {
      this->sens_->publish_state(NAN);
    }
  }

  bool has_sensor() const { return this->sens_ != nullptr; }
  sensor::Sensor *get_sensor() const { return this->sens_; }

 protected:
  sensor::Sensor *sens_{nullptr};
  Deduplicator<T> dedup_;
};
#endif
}  // namespace esphome::ld24xx
