#pragma once

#include "ble_service.h"
#include "ble_characteristic.h"

#include "esphome/components/esp32_ble/ble.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"

#include <memory>
#include <vector>
#include <unordered_map>
#include <functional>

#ifdef USE_ESP32

#include <esp_gatts_api.h>

namespace esphome {
namespace esp32_ble_server {

using namespace esp32_ble;
using namespace bytebuffer;

class BLEServer : public Component, public Parented<ESP32BLE> {
 public:
  void setup() override;
  void loop() override;
  void dump_config() override;
  float get_setup_priority() const override;
  bool can_proceed() override;

  ESPHOME_ALWAYS_INLINE bool is_running() { return this->parent_->is_active() && this->state_ == RUNNING; }

  void set_manufacturer_data(const std::vector<uint8_t> &data) {
    this->manufacturer_data_ = data;
    this->restart_advertising_();
  }

  void set_max_clients(uint8_t max_clients) { this->max_clients_ = max_clients; }
  uint8_t get_max_clients() const { return this->max_clients_; }

  BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15);
  void remove_service(ESPBTUUID uuid, uint8_t inst_id = 0);
  BLEService *get_service(ESPBTUUID uuid, uint8_t inst_id = 0);
  void enqueue_start_service(BLEService *service) { this->services_to_start_.push_back(service); }
  void set_device_information_service(BLEService *service) { this->device_information_service_ = service; }

  esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
  uint32_t get_connected_client_count() { return this->client_count_; }
  const uint16_t *get_clients() const { return this->clients_; }
  uint8_t get_client_count() const { return this->client_count_; }

  void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);

  void ble_before_disabled_event_handler();

  // Direct callback registration - supports multiple callbacks
  void on_connect(std::function<void(uint16_t)> &&callback) {
    this->callbacks_.push_back({CallbackType::ON_CONNECT, std::move(callback)});
  }
  void on_disconnect(std::function<void(uint16_t)> &&callback) {
    this->callbacks_.push_back({CallbackType::ON_DISCONNECT, std::move(callback)});
  }

 protected:
  enum class CallbackType : uint8_t {
    ON_CONNECT,
    ON_DISCONNECT,
  };

  struct CallbackEntry {
    CallbackType type;
    std::function<void(uint16_t)> callback;
  };

  struct ServiceEntry {
    ESPBTUUID uuid;
    uint8_t inst_id;
    BLEService *service;
  };

  void restart_advertising_();

  int8_t find_client_index_(uint16_t conn_id) const;
  void add_client_(uint16_t conn_id);
  void remove_client_(uint16_t conn_id);
  void dispatch_callbacks_(CallbackType type, uint16_t conn_id);

  std::vector<CallbackEntry> callbacks_;

  std::vector<uint8_t> manufacturer_data_{};
  esp_gatt_if_t gatts_if_{0};
  bool registered_{false};

  uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{};
  uint8_t client_count_{0};
  uint8_t max_clients_{1};
  std::vector<ServiceEntry> services_{};
  std::vector<BLEService *> services_to_start_{};
  BLEService *device_information_service_{};

  enum State : uint8_t {
    INIT = 0x00,
    REGISTERING,
    STARTING_SERVICE,
    RUNNING,
  } state_{INIT};
};

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern BLEServer *global_ble_server;

}  // namespace esp32_ble_server
}  // namespace esphome

#endif
