#pragma once
#include "esphome/core/defines.h"
#ifdef USE_MDNS
#include <string>
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
// IP state listener events on whichever network interface is configured.
#if (defined(USE_ESP8266) || defined(USE_RP2)) && \
    ((defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)) || \
     (defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS)))
#include "esphome/components/network/ip_address.h"
#define USE_MDNS_EVENT_DRIVEN_POLLING
#if defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)
#include "esphome/components/wifi/wifi_component.h"
#define USE_MDNS_WIFI_LISTENER
#endif
#if defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS)
#include "esphome/components/ethernet/ethernet_component.h"
#define USE_MDNS_ETHERNET_LISTENER
#endif
#endif

// Device info TXT records (version, mac, config_hash) are published on the _esphomelib service
// when the native API is enabled, otherwise on the _http service (web_server's or the fallback one).
// When neither applies (only prometheus, sendspin or user-defined services are configured), no
// device info records are published and the buffers below are not needed.
#if defined(USE_API) || defined(USE_WEBSERVER) || \
    (!defined(USE_PROMETHEUS) && !defined(USE_SENDSPIN) && !defined(USE_MDNS_EXTRA_SERVICES))
#define USE_MDNS_DEVICE_INFO_TXT
#endif

namespace esphome::mdns {

// Helper struct that identifies strings that may be stored in flash storage (similar to LogString)
struct MDNSString;

// Macro to cast string literals to MDNSString* (works on all platforms)
#define MDNS_STR(name) (reinterpret_cast<const esphome::mdns::MDNSString *>(name))

#ifdef USE_ESP8266
#include <pgmspace.h>
#define MDNS_STR_ARG(s) ((PGM_P) (s))
#else
#define MDNS_STR_ARG(s) (reinterpret_cast<const char *>(s))
#endif

// Service count is calculated at compile time by Python codegen
// MDNS_SERVICE_COUNT will always be defined

struct MDNSTXTRecord {
  const MDNSString *key;
  const MDNSString *value;
};

struct MDNSService {
  // service name _including_ underscore character prefix
  // as defined in RFC6763 Section 7
  const MDNSString *service_type;
  // second label indicating protocol _including_ underscore character prefix
  // as defined in RFC6763 Section 7, like "_tcp" or "_udp"
  const MDNSString *proto;
  TemplatableFn<uint16_t> port;
  FixedVector<MDNSTXTRecord> txt_records;
};

class MDNSComponent final : public Component
#ifdef USE_MDNS_WIFI_LISTENER
    ,
                            public wifi::WiFiIPStateListener
#endif
#ifdef USE_MDNS_ETHERNET_LISTENER
    ,
                            public ethernet::EthernetIPStateListener
#endif
{
 public:
  void setup() override;
  void dump_config() override;

  /// Size of buffer required for config hash hex string (8 hex chars + null terminator)
  static constexpr size_t CONFIG_HASH_STR_SIZE = format_hex_size(sizeof(uint32_t));

#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
  // LEAmDNS has meaningful work only during the probe+announce phase (3×250ms probes +
  // 8×1000ms announces, ~9s). Afterwards every internal timer is resetToNeverExpires()
  // and update() becomes pure overhead. We arm a bounded polling window from IP state
  // listener events so update() runs only during that phase.
  static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50;
  // Must exceed LEAmDNS's longest restart-to-announce-complete path:
  //   MDNS_PROBE_DELAY (250ms) × MDNS_PROBE_COUNT (3) = 750ms probing
  // + MDNS_ANNOUNCE_DELAY (1000ms) × MDNS_ANNOUNCE_COUNT (8) = 8000ms announcing
  // + rand() % MDNS_PROBE_DELAY jitter on first probe (0–250ms)
  // + debounced schedule_function() hop when statusChangeCB fires on ESP8266
  // ≈ 9s nominal. 15s gives ~6s margin to absorb main-loop blocking (long
  // component setup, WiFi scan, flash writes) that could stretch the deadlines
  // between our polls. If LEAmDNS ever extends its phase (upstream library
  // update) this constant needs to grow. Constants defined in LEAmDNS_Priv.h
  // (ESP8266 core 3.1.2 / arduino-pico 5.5.1).
  static constexpr uint32_t MDNS_POLL_WINDOW_MS = 15000;
  static constexpr uint32_t MDNS_POLL_ID = 0;
  static constexpr uint32_t MDNS_POLL_STOP_ID = 1;
#endif
  float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }

#ifdef USE_MDNS_EXTRA_SERVICES
  void add_extra_service(MDNSService service) { this->services_.emplace_next() = std::move(service); }
#endif

#ifdef USE_MDNS_STORE_SERVICES
  const StaticVector<MDNSService, MDNS_SERVICE_COUNT> &get_services() const { return this->services_; }
#endif

  void on_shutdown() override;

#ifdef USE_MDNS_DYNAMIC_TXT
  /// Add a dynamic TXT value and return pointer to it for use in MDNSTXTRecord
  const char *add_dynamic_txt_value(const std::string &value) {
    this->dynamic_txt_values_.push_back(value);
    return this->dynamic_txt_values_[this->dynamic_txt_values_.size() - 1].c_str();
  }
#endif

#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
  void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
                   const network::IPAddress &dns2) override;
#endif

 protected:
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
  /// Arm a fresh MDNS_POLL_WINDOW_MS polling window. Idempotent — re-arming replaces
  /// the previous window via the scheduler's atomic cancel-and-add on matching IDs.
  void start_polling_window_();
#endif
  /// Helper to set up services and MAC buffers, then call platform-specific registration
  using PlatformRegisterFn = void (*)(MDNSComponent *, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &);

  void setup_buffers_and_register_(PlatformRegisterFn platform_register);

#ifdef USE_MDNS_DYNAMIC_TXT
  /// Storage for runtime-generated TXT values from user lambdas
  /// Pre-sized at compile time via MDNS_DYNAMIC_TXT_COUNT to avoid heap allocations.
  /// Static/compile-time values (version, board, etc.) are stored directly in flash and don't use this.
  StaticVector<std::string, MDNS_DYNAMIC_TXT_COUNT> dynamic_txt_values_;
#endif

#if defined(USE_MDNS_DEVICE_INFO_TXT) && defined(USE_MDNS_STORE_SERVICES)
  /// Fixed buffer for MAC address (only needed when services are stored)
  char mac_address_[MAC_ADDRESS_BUFFER_SIZE];
  /// Fixed buffer for config hash hex string (only needed when services are stored)
  char config_hash_str_[CONFIG_HASH_STR_SIZE];
#endif
#ifdef USE_MDNS_STORE_SERVICES
  StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
#endif
#if defined(USE_RP2) && defined(USE_MDNS_EVENT_DRIVEN_POLLING)
  // RP2040 defers MDNS.begin() until the first IP-up event; this tracks that.
  bool initialized_{false};
#endif
  void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services, const char *mac_address_buf,
                        const char *config_hash_buf);
};

}  // namespace esphome::mdns
#endif
