#pragma once

#include <cmath>
#include <cstdint>
#include <functional>
#include <string>

#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/millis_internal.h"
#include "esphome/core/optional.h"

// Forward declarations for friend access from codegen-generated setup()
void setup();           // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h
void original_setup();  // NOLINT(readability-redundant-declaration)

namespace esphome {

// Forward declaration for LogString
struct LogString;

#ifdef USE_RUNTIME_STATS
namespace runtime_stats {
class RuntimeStatsCollector;
}  // namespace runtime_stats
#endif

/** Default setup priorities for components of different types.
 *
 * Components should return one of these setup priorities in get_setup_priority.
 */
namespace setup_priority {

/// For power supply components that must be on before buses like i2c can work.
inline constexpr float POWER = 1200.0f;
/// For communication buses like i2c/spi
inline constexpr float BUS = 1000.0f;
/// For components that represent GPIO pins like PCF8573
inline constexpr float IO = 900.0f;
/// For components that deal with hardware and are very important like GPIO switch
inline constexpr float HARDWARE = 800.0f;
/// For components that import data from directly connected sensors like DHT.
inline constexpr float DATA = 600.0f;
/// For components that use data from sensors like displays
inline constexpr float PROCESSOR = 400.0f;
inline constexpr float BLUETOOTH = 350.0f;
inline constexpr float AFTER_BLUETOOTH = 300.0f;
inline constexpr float WIFI = 250.0f;
inline constexpr float ETHERNET = 250.0f;
/// For components that should be initialized after WiFi and before API is connected.
inline constexpr float BEFORE_CONNECTION = 220.0f;
/// For components that should be initialized after WiFi is connected.
inline constexpr float AFTER_WIFI = 200.0f;
/// For components that should be initialized after a data connection (API/MQTT) is connected.
inline constexpr float AFTER_CONNECTION = 100.0f;
/// For components that should be initialized at the very end of the setup process.
inline constexpr float LATE = -100.0f;

}  // namespace setup_priority

inline constexpr uint32_t SCHEDULER_DONT_RUN = 4294967295UL;

/// Type-safe scheduler IDs for core base classes.
/// Uses a separate NameType (NUMERIC_ID_INTERNAL) so IDs can never collide
/// with component-level NUMERIC_ID values, even if the uint32_t values overlap.
enum class InternalSchedulerID : uint32_t {
  POLLING_UPDATE = 0,  // PollingComponent interval
};

// Forward declaration
class PollingComponent;

// Function declaration for LOG_UPDATE_INTERVAL
void log_update_interval(const char *tag, PollingComponent *component);

#define LOG_UPDATE_INTERVAL(this) log_update_interval(TAG, this)

// Component state uses bits 0-2 (8 states, 5 used)
inline constexpr uint8_t COMPONENT_STATE_MASK = 0x07;
inline constexpr uint8_t COMPONENT_STATE_CONSTRUCTION = 0x00;
inline constexpr uint8_t COMPONENT_STATE_SETUP = 0x01;
inline constexpr uint8_t COMPONENT_STATE_LOOP = 0x02;
inline constexpr uint8_t COMPONENT_STATE_FAILED = 0x03;
inline constexpr uint8_t COMPONENT_STATE_LOOP_DONE = 0x04;
// Status LED uses bits 3-4
inline constexpr uint8_t STATUS_LED_MASK = 0x18;
inline constexpr uint8_t STATUS_LED_OK = 0x00;
inline constexpr uint8_t STATUS_LED_WARNING = 0x08;
inline constexpr uint8_t STATUS_LED_ERROR = 0x10;
// Component loop override flag uses bit 5 (set at registration time)
inline constexpr uint8_t COMPONENT_HAS_LOOP = 0x20;
// Bit 6 on Application::app_state_ (ONLY) — set at the end of
// Application::setup(). Component::status_clear_*_slow_path_() uses this to
// decide whether to propagate clears to App.app_state_. Never set on a
// Component's component_state_.
inline constexpr uint8_t APP_STATE_SETUP_COMPLETE = 0x40;
// Remove before 2026.8.0
enum class RetryResult { DONE, RETRY };

inline constexpr uint8_t WARN_IF_BLOCKING_OVER_CS = 5U;  // 50ms in centiseconds (1cs = 10ms)

/// Lookup component source name by index (1-based). Generated by Python codegen.
/// Weak default returns "<unknown>" so builds without codegen still link.
const LogString *component_source_lookup(uint8_t index);

#ifdef USE_RUNTIME_STATS
/// Inline runtime statistics — eliminates std::map lookup on every loop iteration.
/// Only present when USE_RUNTIME_STATS is defined (profiling builds).
struct ComponentRuntimeStats {
  // Period stats (reset each logging interval)
  uint32_t period_count{0};
  uint32_t period_time_us{0};
  uint32_t period_max_time_us{0};
  // Total stats (persistent until reboot, uint64_t to avoid overflow)
  uint32_t total_count{0};
  uint64_t total_time_us{0};
  uint32_t total_max_time_us{0};

  // Cumulative sum of every record_time() duration since boot, across all
  // components. Used by Application::loop() to snapshot time spent inside
  // LoopBlockingGuard (including guards constructed by the
  // scheduler at scheduler.cpp) so main-loop overhead accounting can
  // subtract scheduled-callback time from the before_loop_tasks_ wall time.
  static uint64_t global_recorded_us;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

  void record_time(uint32_t duration_us) {
    this->period_count++;
    this->period_time_us += duration_us;
    if (duration_us > this->period_max_time_us)
      this->period_max_time_us = duration_us;
    this->total_count++;
    this->total_time_us += duration_us;
    if (duration_us > this->total_max_time_us)
      this->total_max_time_us = duration_us;
    global_recorded_us += duration_us;
  }
  void reset_period() {
    this->period_count = 0;
    this->period_time_us = 0;
    this->period_max_time_us = 0;
  }
};
#endif

class Component {
 public:
  /** Where the component's initialization should happen.
   *
   * Analogous to Arduino's setup(). This method is guaranteed to only be called once.
   * Defaults to doing nothing.
   */
  virtual void setup();

  /** This method will be called repeatedly.
   *
   * Analogous to Arduino's loop(). setup() is guaranteed to be called before this.
   * Defaults to doing nothing.
   */
  virtual void loop();

  virtual void dump_config();

  /** priority of setup(). higher -> executed earlier
   *
   * Defaults to setup_priority::DATA, i.e. 600.
   *
   * @return The setup priority of this component
   */
  virtual float get_setup_priority() const;

  float get_actual_setup_priority() const;

  void set_setup_priority(float priority);

  void call();

  virtual void on_shutdown() {}
  virtual void on_safe_shutdown() {}

  /** Called during teardown to allow component to gracefully finish operations.
   *
   * @return true if teardown is complete, false if more time is needed
   */
  virtual bool teardown() { return true; }

  /** Called after teardown is complete to power down hardware.
   *
   * This is called after all components have finished their teardown process,
   * making it safe to power down hardware like ethernet PHY.
   */
  virtual void on_powerdown() {}

  uint8_t get_component_state() const { return this->component_state_; }

  /** Reset this component back to the construction state to allow setup to run again.
   *
   * This can be used by components that have recoverable failures to attempt setup again.
   */
  void reset_to_construction_state();

  /** Check if this component has completed setup and is in the loop state.
   *
   * @return True if in loop state, false otherwise.
   */
  bool is_in_loop_state() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP; }

  /** Check if this component is idle.
   * Being idle means being in LOOP_DONE state.
   * This means the component has completed setup, is not failed, but its loop is currently disabled.
   *
   * @return True if the component is idle
   */
  bool is_idle() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE; }

  /** Mark this component as failed. Any future timeouts/intervals/setup/loop will no longer be called.
   *
   * This might be useful if a component wants to indicate that a connection to its peripheral failed.
   * For example, i2c based components can check if the remote device is responding and otherwise
   * mark the component as failed. Eventually this will also enable smart status LEDs.
   */
  void mark_failed();

  void mark_failed(const LogString *message) {
    this->status_set_error(message);
    this->mark_failed();
  }

  /** Disable this component's loop. The loop() method will no longer be called.
   *
   * This is useful for components that only need to run for a certain period of time
   * or when inactive, saving CPU cycles.
   *
   * @note Components should call this->disable_loop() on themselves, not on other components.
   *       This ensures the component's state is properly updated along with the loop partition.
   */
  void disable_loop();

  /** Enable this component's loop. The loop() method will be called normally.
   *
   * This is useful for components that transition between active and inactive states
   * and need to re-enable their loop() method when becoming active again.
   *
   * @note Components should call this->enable_loop() on themselves, not on other components.
   *       This ensures the component's state is properly updated along with the loop partition.
   */
  void enable_loop() {
    if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE)
      this->enable_loop_slow_path_();
  }

  /** Thread and ISR-safe version of enable_loop() that can be called from any context.
   *
   * This method defers the actual enable via enable_pending_loops_ to the main loop,
   * making it safe to call from ISR handlers, timer callbacks, other threads,
   * or any interrupt context.
   *
   * @note The actual loop enabling will happen on the next main loop iteration.
   * @note Only one pending enable request is tracked per component.
   * @note There is no disable_loop_soon_any_context() on purpose - it would race
   *       against enable calls and synchronization would get too complex
   *       to provide a safe version that would work for each component.
   *
   *       Use disable_loop() from the main thread only.
   *
   *       If you need to disable the loop from ISR, carefully implement
   *       it in the component itself, with an ISR safe approach, and call
   *       disable_loop() in its next ::loop() iteration. Implementations
   *       will need to carefully consider all possible race conditions.
   */
  void enable_loop_soon_any_context();

  bool is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }

  bool is_ready() const;

  virtual bool can_proceed();

  bool status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; }

  bool status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; }

  void status_set_warning();  // Set warning flag without message
  void status_set_warning(const char *message);
  void status_set_warning(const LogString *message);

  void status_set_error();  // Set error flag without message
  void status_set_error(const LogString *message);

  void status_clear_warning() {
    if ((this->component_state_ & STATUS_LED_WARNING) == 0)
      return;
    this->status_clear_warning_slow_path_();
  }

  void status_clear_error() {
    if ((this->component_state_ & STATUS_LED_ERROR) == 0)
      return;
    this->status_clear_error_slow_path_();
  }

  /** Set warning status flag and automatically clear it after a timeout.
   *
   * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name).
   *             Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string.
   *             This is NOT a message to display - use status_set_warning() with a message if logging is needed.
   * @param length Duration in milliseconds before the warning is automatically cleared.
   */
  void status_momentary_warning(const char *name, uint32_t length = 5000);

  /** Set error status flag and automatically clear it after a timeout.
   *
   * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name).
   *             Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string.
   *             This is NOT a message to display - use status_set_error() with a message if logging is needed.
   * @param length Duration in milliseconds before the error is automatically cleared.
   */
  void status_momentary_error(const char *name, uint32_t length = 5000);

  bool has_overridden_loop() const { return (this->component_state_ & COMPONENT_HAS_LOOP) != 0; }

  /** Get the integration where this component was declared as a LogString for logging.
   *
   * Returns LOG_STR("<unknown>") if source not set
   */
  inline const LogString *get_component_log_str() const ESPHOME_ALWAYS_INLINE {
    return component_source_lookup(this->component_source_index_);
  }

  bool should_warn_of_blocking(uint32_t blocking_time, uint32_t &threshold_ms_out);

 protected:
  friend class Application;
  friend void ::setup();
  friend void ::original_setup();

  /** Set where this component was loaded from for some debug messages.
   *
   * This is set by the ESPHome core during setup, and should not be called manually.
   * @param index 1-based index into the component source lookup table (0 = not set)
   */
  void set_component_source_(uint8_t index) { this->component_source_index_ = index; }

  virtual void call_setup();
  void call_dump_config_();

  void enable_loop_slow_path_();

  /// Helper to set component state (clears state bits and sets new state)
  inline void set_component_state_(uint8_t state) {
    this->component_state_ &= ~COMPONENT_STATE_MASK;
    this->component_state_ |= state;
  }

  /// Helper to set a status LED flag on both this component and the app.
  /// Returns true if the flag was newly set, false if it was already set.
  /// Note: Callers often use the return value to decide whether to log a warning/error,
  /// so once a flag is set, subsequent (potentially different) messages may be suppressed.
  bool set_status_flag_(uint8_t flag);

  /** Set an interval function with a const char* name. Empty name means no cancelling possible.
   *
   * This will call f every interval ms. Can be cancelled via cancel_interval().
   * Similar to javascript's setInterval().
   *
   * IMPORTANT NOTE:
   * The only guarantee offered by this call is that the callback will be called no *earlier* than
   * the specified interval after the previous call. Any given interval may be longer due to
   * other components blocking the loop() call.
   *
   * So do not rely on this having correct timing. If you need exact timing please
   * use hardware timers.
   *
   * Note also that the first call to f will not happen immediately, but after a random delay. This is
   * intended to prevent many interval functions from being called at the same time.
   *
   * IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item.
   * This means the name should be:
   *   - A string literal (e.g., "update")
   *   - A static const char* variable
   *   - A pointer with lifetime >= the scheduled task
   *
   * For dynamic names, use the uint32_t id overload instead.
   *
   * @param name The identifier for this interval function (must have static lifetime)
   * @param interval The interval in ms
   * @param f The function to call
   */
  void set_interval(const char *name, uint32_t interval, std::function<void()> &&f);  // NOLINT

  /** Set an interval function with a numeric ID (zero heap allocation).
   *
   * @param id The numeric identifier for this interval function
   * @param interval The interval in ms
   * @param f The function to call
   */
  void set_interval(uint32_t id, uint32_t interval, std::function<void()> &&f);  // NOLINT

  void set_interval(InternalSchedulerID id, uint32_t interval, std::function<void()> &&f);  // NOLINT

  void set_interval(uint32_t interval, std::function<void()> &&f);  // NOLINT

  /** Cancel an interval function.
   *
   * @param name The identifier for this interval function.
   * @return Whether an interval functions was deleted.
   */
  bool cancel_interval(const char *name);        // NOLINT
  bool cancel_interval(uint32_t id);             // NOLINT
  bool cancel_interval(InternalSchedulerID id);  // NOLINT

  /// @deprecated set_retry is deprecated. Use set_timeout or set_interval instead. Removed in 2026.8.0.
  // Remove before 2026.8.0
  ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
                "2026.2.0")
  void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,       // NOLINT
                 std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f);  // NOLINT

  // Remove before 2026.8.0
  ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
                "2026.2.0")
  void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts,              // NOLINT
                 std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f);  // NOLINT

  // Remove before 2026.8.0
  ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
                "2026.2.0")
  void set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,                   // NOLINT
                 std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f);  // NOLINT

  // Remove before 2026.8.0
  ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
                "2026.2.0")
  void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f,  // NOLINT
                 float backoff_increase_factor = 1.0f);                                                      // NOLINT

  // Remove before 2026.8.0
  ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
  bool cancel_retry(const std::string &name);  // NOLINT
  // Remove before 2026.8.0
  ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
  bool cancel_retry(const char *name);  // NOLINT
  // Remove before 2026.8.0
  ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
  bool cancel_retry(uint32_t id);  // NOLINT

  /** Set a timeout function with a const char* name.
   *
   * Similar to javascript's setTimeout(). Empty name means no cancelling possible.
   *
   * IMPORTANT: Do not rely on this having correct timing. This is only called from
   * loop() and therefore can be significantly delayed. If you need exact timing please
   * use hardware timers.
   *
   * IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item.
   * This means the name should be:
   *   - A string literal (e.g., "init")
   *   - A static const char* variable
   *   - A pointer with lifetime >= the timeout duration
   *
   * For dynamic names, use the uint32_t id overload instead.
   *
   * @see cancel_timeout()
   *
   * @param name The identifier for this timeout function (must have static lifetime)
   * @param timeout The timeout in ms
   * @param f The function to call
   */
  void set_timeout(const char *name, uint32_t timeout, std::function<void()> &&f);  // NOLINT

  /** Set a timeout function with a numeric ID (zero heap allocation).
   *
   * @param id The numeric identifier for this timeout function
   * @param timeout The timeout in ms
   * @param f The function to call
   */
  void set_timeout(uint32_t id, uint32_t timeout, std::function<void()> &&f);  // NOLINT

  void set_timeout(InternalSchedulerID id, uint32_t timeout, std::function<void()> &&f);  // NOLINT

  void set_timeout(uint32_t timeout, std::function<void()> &&f);  // NOLINT

  /** Cancel a timeout function.
   *
   * @param name The identifier for this timeout function.
   * @return Whether a timeout functions was deleted.
   */
  bool cancel_timeout(const char *name);        // NOLINT
  bool cancel_timeout(uint32_t id);             // NOLINT
  bool cancel_timeout(InternalSchedulerID id);  // NOLINT

  /** Defer a callback to the next loop() call with a const char* name.
   *
   * If name is specified and a defer() object with the same name exists, the old one is first removed.
   *
   * IMPORTANT: The provided name pointer must remain valid for the lifetime of the deferred task.
   * This means the name should be:
   *   - A string literal (e.g., "update")
   *   - A static const char* variable
   *   - A pointer with lifetime >= the deferred execution
   *
   * For dynamic names, use the uint32_t id overload instead.
   *
   * @param name The name of the defer function (must have static lifetime)
   * @param f The callback
   */
  void defer(const char *name, std::function<void()> &&f);  // NOLINT

  /// Defer a callback to the next loop() call.
  void defer(std::function<void()> &&f);  // NOLINT

  /// Defer a callback with a numeric ID (zero heap allocation)
  void defer(uint32_t id, std::function<void()> &&f);  // NOLINT

  /// Cancel a defer callback using the specified name, name must not be empty.
  bool cancel_defer(const char *name);  // NOLINT
  bool cancel_defer(uint32_t id);       // NOLINT

  void status_clear_warning_slow_path_();
  void status_clear_error_slow_path_();

  // Ordered for optimal packing on 32-bit systems (8 bytes total with vtable)
  uint8_t component_source_index_{0};  ///< Index into component source PROGMEM lookup table (0 = not set)
  uint8_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_CS};  ///< Warn threshold in centiseconds (max 2550ms)
  /// State of this component - each bit has a purpose:
  /// Bits 0-2: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED, 0x04=LOOP_DONE)
  /// Bit 3: STATUS_LED_WARNING
  /// Bit 4: STATUS_LED_ERROR
  /// Bit 5: Has overridden loop() (set at registration time)
  /// Bits 6-7: Unused - reserved for future expansion
  uint8_t component_state_{0x00};
  volatile bool pending_enable_loop_{false};  ///< ISR-safe flag for enable_loop_soon_any_context
#ifdef USE_RUNTIME_STATS
  friend class runtime_stats::RuntimeStatsCollector;
  friend class LoopBlockingGuard;
  ComponentRuntimeStats runtime_stats_;
#endif
};

/** This class simplifies creating components that periodically check a state.
 *
 * You basically just need to implement the update() function, it will be called every update_interval ms
 * after startup. Note that this class cannot guarantee a correct timing, as it's not using timers, just
 * a software polling feature with set_interval() from Component.
 */
class PollingComponent : public Component {
 public:
  PollingComponent() : PollingComponent(SCHEDULER_DONT_RUN) {}

  /** Initialize this polling component with the given update interval in ms.
   *
   * @param update_interval The update interval in ms.
   */
  explicit PollingComponent(uint32_t update_interval);

  /** Manually set the update interval in ms for this polling object.
   *
   * @param update_interval The update interval in ms.
   */
  void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }

  // ========== OVERRIDE METHODS ==========
  // (You'll only need this when creating your own custom sensor)
  virtual void update() = 0;

  // ========== INTERNAL METHODS ==========
  // (In most use cases you won't need these)
  void call_setup() override;

  /// Get the update interval in ms of this sensor
  virtual uint32_t get_update_interval() const;

  // Start the poller, used for component.suspend
  void start_poller();

  // Stop the poller, used for component.suspend
  void stop_poller();

 protected:
  uint32_t update_interval_;
};

// LoopBlockingGuard lives in application.h because it reads its state from App.

// Function to clear setup priority overrides after all components are set up
// Only has an implementation when USE_SETUP_PRIORITY_OVERRIDE is defined
void clear_setup_priority_overrides();

}  // namespace esphome
