#pragma once

#include "esphome/core/defines.h"
#ifdef USE_API
#include "api_buffer.h"
// Must precede clients_ so APIConnection is complete for default_delete (libc++).
#include "api_connection.h"
#include "api_noise_context.h"
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "esphome/components/socket/socket.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
#endif
#ifdef USE_CAMERA
#include "esphome/components/camera/camera.h"
#endif

#include <array>
#include <memory>
#include <vector>

namespace esphome::api {

#ifdef USE_API_USER_DEFINED_ACTIONS
// Forward declaration - full definition in user_services.h
class UserServiceDescriptor;
#endif

#ifdef USE_API_NOISE
struct SavedNoisePsk {
  psk_t psk;
} PACKED;  // NOLINT
#endif

class APIServer final : public Component,
                        public Controller
#ifdef USE_CAMERA
    ,
                        public camera::CameraListener
#endif
{
 public:
  APIServer();
  void setup() override;
  uint16_t get_port() const;
  float get_setup_priority() const override;
  void loop() override;
  void dump_config() override;
  void on_shutdown() override;
  bool teardown() override;
#ifdef USE_LOGGER
  void on_log(uint8_t level, const char *tag, const char *message, size_t message_len);
#endif
#ifdef USE_CAMERA
  void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
#endif
  void set_port(uint16_t port);
  void set_reboot_timeout(uint32_t reboot_timeout);
  void set_batch_delay(uint16_t batch_delay);
  uint16_t get_batch_delay() const { return batch_delay_; }
  void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }

  // Get reference to shared buffer for API connections
  APIBuffer &get_shared_buffer_ref() { return shared_write_buffer_; }

#ifdef USE_API_NOISE
  bool save_noise_psk(psk_t psk, bool make_active = true);
  bool clear_noise_psk(bool make_active = true);
  void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); }
  APINoiseContext &get_noise_ctx() { return this->noise_ctx_; }
#endif  // USE_API_NOISE

  void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR
  void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
#endif
#ifdef USE_COVER
  void on_cover_update(cover::Cover *obj) override;
#endif
#ifdef USE_FAN
  void on_fan_update(fan::Fan *obj) override;
#endif
#ifdef USE_LIGHT
  void on_light_update(light::LightState *obj) override;
#endif
#ifdef USE_SENSOR
  void on_sensor_update(sensor::Sensor *obj) override;
#endif
#ifdef USE_SWITCH
  void on_switch_update(switch_::Switch *obj) override;
#endif
#ifdef USE_TEXT_SENSOR
  void on_text_sensor_update(text_sensor::TextSensor *obj) override;
#endif
#ifdef USE_CLIMATE
  void on_climate_update(climate::Climate *obj) override;
#endif
#ifdef USE_NUMBER
  void on_number_update(number::Number *obj) override;
#endif
#ifdef USE_DATETIME_DATE
  void on_date_update(datetime::DateEntity *obj) override;
#endif
#ifdef USE_DATETIME_TIME
  void on_time_update(datetime::TimeEntity *obj) override;
#endif
#ifdef USE_DATETIME_DATETIME
  void on_datetime_update(datetime::DateTimeEntity *obj) override;
#endif
#ifdef USE_TEXT
  void on_text_update(text::Text *obj) override;
#endif
#ifdef USE_SELECT
  void on_select_update(select::Select *obj) override;
#endif
#ifdef USE_LOCK
  void on_lock_update(lock::Lock *obj) override;
#endif
#ifdef USE_VALVE
  void on_valve_update(valve::Valve *obj) override;
#endif
#ifdef USE_MEDIA_PLAYER
  void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif
#ifdef USE_WATER_HEATER
  void on_water_heater_update(water_heater::WaterHeater *obj) override;
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
  void send_homeassistant_action(const HomeassistantActionRequest &call);

#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
  // Action response handling
  using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
  void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
  void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
  void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
                              size_t response_data_len);
#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif  // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
  void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
    this->user_services_.assign(services);
  }
#ifdef USE_API_CUSTOM_SERVICES
  // Only compile push_back method when custom_services: true (external components)
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
  // Action call context management - supports concurrent calls from multiple clients
  // Returns server-generated action_call_id to avoid collisions when clients use same call_id
  uint32_t register_active_action_call(uint32_t client_call_id, APIConnection *conn);
  void unregister_active_action_call(uint32_t action_call_id);
  void unregister_active_action_calls_for_connection(APIConnection *conn);
  // Send response for a specific action call (uses action_call_id, sends client_call_id in response)
  void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
  void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
                            const uint8_t *response_data, size_t response_data_len);
#endif  // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif  // USE_API_USER_DEFINED_ACTION_RESPONSES
#endif
#ifdef USE_HOMEASSISTANT_TIME
  void request_time();
#endif

#ifdef USE_ALARM_CONTROL_PANEL
  void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
#ifdef USE_EVENT
  void on_event(event::Event *obj) override;
#endif
#ifdef USE_UPDATE
  void on_update(update::UpdateEntity *obj) override;
#endif
#ifdef USE_ZWAVE_PROXY
  void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
#endif
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
  void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
#endif

  bool is_connected() const { return this->api_connection_count_ != 0; }
  bool is_connected_with_state_subscription() const;

  // Range-for view over the populated slice [0, api_connection_count_). Read-only with respect
  // to ownership; callers get `const unique_ptr&` so they can invoke non-const methods on the
  // APIConnection but cannot reset/move the slot and break the count invariant.
  using APIConnectionPtr = std::unique_ptr<APIConnection>;
  class ActiveClientsView {
    const APIConnectionPtr *begin_;
    const APIConnectionPtr *end_;

   public:
    ActiveClientsView(const APIConnectionPtr *b, const APIConnectionPtr *e) : begin_(b), end_(e) {}
    const APIConnectionPtr *begin() const { return this->begin_; }
    const APIConnectionPtr *end() const { return this->end_; }
  };
  ActiveClientsView active_clients() const {
    return {this->clients_.data(), this->clients_.data() + this->api_connection_count_};
  }

#ifdef USE_API_HOMEASSISTANT_STATES
  struct HomeAssistantStateSubscription {
    const char *entity_id;  // Pointer to flash (internal) or heap (external)
    const char *attribute;  // Pointer to flash or nullptr (nullptr means no attribute)
    std::function<void(StringRef)> callback;
    bool once;

    // Dynamic storage for external components using std::string API (custom_api_device.h)
    // These are only allocated when using the std::string overload (nullptr for const char* overload)
    std::unique_ptr<std::string> entity_id_dynamic_storage;
    std::unique_ptr<std::string> attribute_dynamic_storage;
  };

  // New const char* overload (for internal components - zero allocation)
  void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f);
  void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f);

  // std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
  void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
                                      std::function<void(StringRef)> &&f);
  void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
                                std::function<void(StringRef)> &&f);

  // Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
  void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
                                      std::function<void(const std::string &)> &&f);
  void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
                                std::function<void(const std::string &)> &&f);

  const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
  const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
#endif

#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
  Trigger<std::string, std::string> *get_client_connected_trigger() { return &this->client_connected_trigger_; }
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
  Trigger<std::string, std::string> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; }
#endif

 protected:
  // Accept incoming socket connections. Only called when socket has pending connections.
  void __attribute__((noinline)) accept_new_connections_();
  // Remove a disconnected client by index. Swaps with the last populated slot and resets it.
  void __attribute__((noinline)) remove_client_(uint8_t client_index);

#ifdef USE_API_NOISE
  bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
                         bool make_active);
  // Load saved PSK from preferences and apply it. Returns true on success.
  bool load_and_apply_noise_psk_();
#endif  // USE_API_NOISE
#ifdef USE_API_HOMEASSISTANT_STATES
  // Helper methods to reduce code duplication
  void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f,
                               bool once);
  void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
                               std::function<void(StringRef)> &&f, bool once);
  // Legacy helper: wraps std::string callback and delegates to StringRef version
  void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
                               std::function<void(const std::string &)> &&f, bool once);
#endif  // USE_API_HOMEASSISTANT_STATES
  // No explicit close() needed — listen sockets have no active connections on
  // failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
  inline void destroy_socket_() {
    delete this->socket_;
    this->socket_ = nullptr;
  }
  void socket_failed_(const LogString *msg);
  // Pointers and pointer-like types first (4 bytes each)
  socket::ListenSocket *socket_{nullptr};
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
  Trigger<std::string, std::string> client_connected_trigger_;
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
  Trigger<std::string, std::string> client_disconnected_trigger_;
#endif

  // 4-byte aligned types
  uint32_t reboot_timeout_{300000};
  uint32_t last_connected_{0};

  // Slots [0, api_connection_count_) are populated; trailing slots are always nullptr.
  std::array<APIConnectionPtr, MAX_API_CONNECTIONS> clients_{};
  // Vectors and strings (12 bytes each on 32-bit)
  // Shared proto write buffer for all connections.
  // Not pre-allocated: all send paths call prepare_first_message_buffer() which
  // reserves the exact needed size. Pre-allocating here would cause heap fragmentation
  // since the buffer would almost always reallocate on first use.
  APIBuffer shared_write_buffer_;
#ifdef USE_API_HOMEASSISTANT_STATES
  std::vector<HomeAssistantStateSubscription> state_subs_;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
  std::vector<UserServiceDescriptor *> user_services_;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
  // Active action calls - supports concurrent calls from multiple clients
  // Uses server-generated action_call_id to avoid collisions when multiple clients use same call_id
  struct ActiveActionCall {
    uint32_t action_call_id;  // Server-generated unique ID (passed to actions)
    uint32_t client_call_id;  // Client's original call_id (used in response)
    APIConnection *connection;
  };
  std::vector<ActiveActionCall> active_action_calls_;
  uint32_t next_action_call_id_{1};  // Counter for generating unique action_call_ids
#endif                               // USE_API_USER_DEFINED_ACTION_RESPONSES
#endif
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
  struct PendingActionResponse {
    uint32_t call_id;
    ActionResponseCallback callback;
  };
  std::vector<PendingActionResponse> action_response_callbacks_;
#endif

  // Group smaller types together
  uint16_t port_{6053};
  uint16_t batch_delay_{100};
  // Connection limits - these defaults will be overridden by config values
  // from cv.SplitDefault in __init__.py which sets platform-specific defaults.
  uint8_t listen_backlog_{4};
  bool shutting_down_ = false;
  uint8_t api_connection_count_{0};
  // 7 bytes used, 1 byte padding

#ifdef USE_API_NOISE
  APINoiseContext noise_ctx_;
  ESPPreferenceObject noise_pref_;
#endif  // USE_API_NOISE
};

extern APIServer *global_api_server;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
  TEMPLATABLE_VALUE(bool, state_subscription_only)
 public:
  bool check(const Ts &...x) override {
    if (this->state_subscription_only_.value(x...)) {
      return global_api_server->is_connected_with_state_subscription();
    }
    return global_api_server->is_connected();
  }
};

}  // namespace esphome::api
#endif
