#pragma once

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

#include <cstdint>

#ifdef USE_OTA_STATE_LISTENER
#include <vector>
#endif

namespace esphome::ota {

enum OTAResponseTypes {
  OTA_RESPONSE_OK = 0x00,
  OTA_RESPONSE_REQUEST_AUTH = 0x01,
  OTA_RESPONSE_REQUEST_SHA256_AUTH = 0x02,

  OTA_RESPONSE_HEADER_OK = 0x40,
  OTA_RESPONSE_AUTH_OK = 0x41,
  OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42,
  OTA_RESPONSE_BIN_MD5_OK = 0x43,
  OTA_RESPONSE_RECEIVE_OK = 0x44,
  OTA_RESPONSE_UPDATE_END_OK = 0x45,
  OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46,
  OTA_RESPONSE_CHUNK_OK = 0x47,
  OTA_RESPONSE_FEATURE_FLAGS = 0x48,

  OTA_RESPONSE_ERROR_MAGIC = 0x80,
  OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81,
  OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82,
  OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83,
  OTA_RESPONSE_ERROR_UPDATE_END = 0x84,
  OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85,
  OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86,
  OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87,
  OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88,
  OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89,
  OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A,
  OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B,
  OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C,
  OTA_RESPONSE_ERROR_SIGNATURE_INVALID = 0x8D,
  OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE = 0x8E,
  OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY = 0x8F,
  OTA_RESPONSE_ERROR_PARTITION_TABLE_UPDATE = 0x90,
  OTA_RESPONSE_ERROR_BOOTLOADER_VERIFY = 0x91,
  OTA_RESPONSE_ERROR_BOOTLOADER_UPDATE = 0x92,
  OTA_RESPONSE_ERROR_UNKNOWN = 0xFF,
};

enum OTAState {
  OTA_COMPLETED = 0,
  OTA_STARTED,
  OTA_IN_PROGRESS,
  OTA_ABORT,
  OTA_ERROR,
};

enum OTAType : uint8_t {
  OTA_TYPE_UPDATE_APP = 0x00,
  OTA_TYPE_UPDATE_PARTITION_TABLE = 0x01,
  OTA_TYPE_UPDATE_BOOTLOADER = 0x02,
};

/** Listener interface for OTA state changes.
 *
 * Components can implement this interface to receive OTA state updates
 * without the overhead of std::function callbacks.
 */
class OTAStateListener {
 public:
  virtual ~OTAStateListener() = default;
  virtual void on_ota_state(OTAState state, float progress, uint8_t error) = 0;
};

class OTAComponent : public Component {
#ifdef USE_OTA_STATE_LISTENER
 public:
  void add_state_listener(OTAStateListener *listener) { this->state_listeners_.push_back(listener); }

 protected:
  void notify_state_(OTAState state, float progress, uint8_t error);

  /** Notify state with deferral to main loop (for thread safety).
   *
   * This should be used by OTA implementations that run in separate tasks
   * (like web_server OTA) to ensure listeners execute in the main loop.
   */
  void notify_state_deferred_(OTAState state, float progress, uint8_t error);

  std::vector<OTAStateListener *> state_listeners_;
#endif
};

#ifdef USE_OTA_STATE_LISTENER

/** Listener interface for global OTA state changes (includes OTA component pointer).
 *
 * Used by OTAGlobalCallback to aggregate state from multiple OTA components.
 */
class OTAGlobalStateListener {
 public:
  virtual ~OTAGlobalStateListener() = default;
  virtual void on_ota_global_state(OTAState state, float progress, uint8_t error, OTAComponent *component) = 0;
};

/** Global callback that aggregates OTA state from all OTA components.
 *
 * OTA components call notify_ota_state() directly with their pointer,
 * which forwards the event to all registered global listeners.
 */
class OTAGlobalCallback {
 public:
  void add_global_state_listener(OTAGlobalStateListener *listener) { this->global_listeners_.push_back(listener); }

  void notify_ota_state(OTAState state, float progress, uint8_t error, OTAComponent *component) {
    for (auto *listener : this->global_listeners_) {
      listener->on_ota_global_state(state, progress, error, component);
    }
  }

 protected:
  std::vector<OTAGlobalStateListener *> global_listeners_;
};

OTAGlobalCallback *get_global_ota_callback();

// OTA implementations should use:
// - notify_state_() when already in main loop (e.g., esphome OTA)
// - notify_state_deferred_() when in separate task (e.g., web_server OTA)
// This ensures proper listener execution in all contexts.
#endif
}  // namespace esphome::ota
