#pragma once

#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#include "esphome/core/scheduler.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"

#include <array>
#include <list>
#include <vector>

namespace esphome {

template<size_t N, typename... Ts> class AndCondition : public Condition<Ts...> {
 public:
  explicit AndCondition(std::initializer_list<Condition<Ts...> *> conditions) {
    init_array_from(this->conditions_, conditions);
  }
  bool check(const Ts &...x) override {
    for (auto *condition : this->conditions_) {
      if (!condition->check(x...))
        return false;
    }

    return true;
  }

 protected:
  std::array<Condition<Ts...> *, N> conditions_{};
};

template<size_t N, typename... Ts> class OrCondition : public Condition<Ts...> {
 public:
  explicit OrCondition(std::initializer_list<Condition<Ts...> *> conditions) {
    init_array_from(this->conditions_, conditions);
  }
  bool check(const Ts &...x) override {
    for (auto *condition : this->conditions_) {
      if (condition->check(x...))
        return true;
    }

    return false;
  }

 protected:
  std::array<Condition<Ts...> *, N> conditions_{};
};

template<typename... Ts> class NotCondition : public Condition<Ts...> {
 public:
  explicit NotCondition(Condition<Ts...> *condition) : condition_(condition) {}
  bool check(const Ts &...x) override { return !this->condition_->check(x...); }

 protected:
  Condition<Ts...> *condition_;
};

template<size_t N, typename... Ts> class XorCondition : public Condition<Ts...> {
 public:
  explicit XorCondition(std::initializer_list<Condition<Ts...> *> conditions) {
    init_array_from(this->conditions_, conditions);
  }
  bool check(const Ts &...x) override {
    size_t result = 0;
    for (auto *condition : this->conditions_) {
      result += condition->check(x...);
    }

    return result == 1;
  }

 protected:
  std::array<Condition<Ts...> *, N> conditions_{};
};

template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
 public:
  explicit LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {}
  bool check(const Ts &...x) override { return this->f_(x...); }

 protected:
  std::function<bool(Ts...)> f_;
};

/// Optimized lambda condition for stateless lambdas (no capture).
/// Uses function pointer instead of std::function to reduce memory overhead.
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
 public:
  explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
  bool check(const Ts &...x) override { return this->f_(x...); }

 protected:
  bool (*f_)(Ts...);
};

template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
 public:
  explicit ForCondition(Condition<> *condition) : condition_(condition) {}

  TEMPLATABLE_VALUE(uint32_t, time);

  void loop() override {
    // Safe to use cached time - only called from Application::loop()
    this->check_internal_(App.get_loop_component_start_time());
  }

  float get_setup_priority() const override { return setup_priority::DATA; }

  bool check(const Ts &...x) override {
    auto now = millis();
    if (!this->check_internal_(now))
      return false;
    return now - this->last_inactive_ >= this->time_.value(x...);
  }

 protected:
  bool check_internal_(uint32_t now) {
    bool cond = this->condition_->check();
    if (!cond)
      this->last_inactive_ = now;
    return cond;
  }

  Condition<> *condition_;
  uint32_t last_inactive_{0};
};

class StartupTrigger : public Trigger<>, public Component {
 public:
  explicit StartupTrigger(float setup_priority) : setup_priority_(setup_priority) {}
  void setup() override { this->trigger(); }
  float get_setup_priority() const override { return this->setup_priority_; }

 protected:
  float setup_priority_;
};

class ShutdownTrigger : public Trigger<>, public Component {
 public:
  explicit ShutdownTrigger(float setup_priority) : setup_priority_(setup_priority) {}
  void on_shutdown() override { this->trigger(); }
  float get_setup_priority() const override { return this->setup_priority_; }

 protected:
  float setup_priority_;
};

class LoopTrigger : public Trigger<>, public Component {
 public:
  void loop() override { this->trigger(); }
  float get_setup_priority() const override { return setup_priority::DATA; }
};

#ifdef ESPHOME_PROJECT_NAME
class ProjectUpdateTrigger : public Trigger<std::string>, public Component {
 public:
  void setup() override {
    uint32_t hash = fnv1_hash(ESPHOME_PROJECT_NAME);
    ESPPreferenceObject pref = global_preferences->make_preference<char[30]>(hash, true);
    char previous_version[30];
    char current_version[30] = ESPHOME_PROJECT_VERSION_30;
    if (pref.load(&previous_version)) {
      int cmp = strcmp(previous_version, current_version);
      if (cmp < 0) {
        this->trigger(previous_version);
      }
    }
    pref.save(&current_version);
    global_preferences->sync();
  }
  float get_setup_priority() const override { return setup_priority::PROCESSOR; }
};
#endif

template<typename... Ts> class DelayAction : public Action<Ts...> {
 public:
  explicit DelayAction() = default;

  TEMPLATABLE_VALUE(uint32_t, delay)

  void play_complex(const Ts &...x) override {
    this->num_running_++;

    // If num_running_ > 1, we have multiple instances running in parallel
    // In single/restart/queued modes, only one instance runs at a time
    // Parallel mode uses skip_cancel=true to allow multiple delays to coexist
    // WARNING: This can accumulate delays if scripts are triggered faster than they complete!
    // Users should set max_runs on parallel scripts to limit concurrent executions.
    // Issue #10264: This is a workaround for parallel script delays interfering with each other.

    // Optimization: For no-argument delays (most common case), use direct lambda
    // to avoid overhead from capturing arguments by value
    if constexpr (sizeof...(Ts) == 0) {
      App.scheduler.set_timer_common_(
          /* component= */ nullptr, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::SELF_POINTER,
          /* static_name= */ reinterpret_cast<const char *>(this), /* hash_or_id= */ 0, this->delay_.value(),
          [this]() { this->play_next_(); },
          /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1,
          // Record the owning script (if any) so the blocking warning can name it; propagates across
          // chained delays via the scheduler.
          /* source= */ App.get_current_source());
    } else {
      // For delays with arguments, capture by value to preserve argument values
      // Arguments must be copied because original references may be invalid after delay
      // `mutable` is required so captured copies of non-const reference args (e.g. std::string&)
      // are passed as non-const lvalues to play_next_(const Ts&...) where Ts may be `T&`
      auto f = [this, x...]() mutable { this->play_next_(x...); };
      App.scheduler.set_timer_common_(
          /* component= */ nullptr, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::SELF_POINTER,
          /* static_name= */ reinterpret_cast<const char *>(this), /* hash_or_id= */ 0, this->delay_.value(x...),
          std::move(f),
          /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1,
          // See the no-argument branch above: record the owning script for log attribution.
          /* source= */ App.get_current_source());
    }
  }

  void play(const Ts &...x) override { /* ignore - see play_complex */
  }

  void stop() override { App.scheduler.cancel_timeout(this); }
};

template<typename... Ts> class LambdaAction : public Action<Ts...> {
 public:
  explicit LambdaAction(std::function<void(Ts...)> &&f) : f_(std::move(f)) {}

  void play(const Ts &...x) override { this->f_(x...); }

 protected:
  std::function<void(Ts...)> f_;
};

/// Optimized lambda action for stateless lambdas (no capture).
/// Uses function pointer instead of std::function to reduce memory overhead.
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
 public:
  explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}

  void play(const Ts &...x) override { this->f_(x...); }

 protected:
  void (*f_)(Ts...);
};

/// Simple continuation action that calls play_next_ on a parent action.
/// Used internally by IfAction, WhileAction, RepeatAction, etc. to chain actions.
/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function).
template<typename... Ts> class ContinuationAction : public Action<Ts...> {
 public:
  explicit ContinuationAction(Action<Ts...> *parent) : parent_(parent) {}

  void play(const Ts &...x) override { this->parent_->play_next_(x...); }

 protected:
  Action<Ts...> *parent_;
};

// Forward declaration for WhileLoopContinuation
template<typename... Ts> class WhileAction;

/// Loop continuation for WhileAction that checks condition and repeats or continues.
/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function).
template<typename... Ts> class WhileLoopContinuation : public Action<Ts...> {
 public:
  explicit WhileLoopContinuation(WhileAction<Ts...> *parent) : parent_(parent) {}

  void play(const Ts &...x) override;

 protected:
  WhileAction<Ts...> *parent_;
};

// Wraps a ContinuationAction when Enabled, empty otherwise.
// Lets IfAction elide the else continuation when HasElse is false.
template<bool Enabled, typename... Ts> struct OptionalContinuation {
  ContinuationAction<Ts...> action;
  explicit OptionalContinuation(Action<Ts...> *parent) : action(parent) {}
};
template<typename... Ts> struct OptionalContinuation<false, Ts...> {
  explicit OptionalContinuation(Action<Ts...> * /*parent*/) {}
};

template<bool HasElse, typename... Ts> class IfAction : public Action<Ts...> {
 public:
  explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}

  // Precondition: add_then/add_else must be called at most once per instance.
  // Codegen always batches the full action list into a single call. Calling
  // twice would re-append the same inline continuation pointer and form a
  // self-loop in the next_ chain.
  void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
    this->then_.add_actions(actions);
    this->then_.add_action(&this->then_continuation_);
  }

  void add_else(const std::initializer_list<Action<Ts...> *> &actions) requires(HasElse) {
    this->else_.add_actions(actions);
    this->else_.add_action(&this->else_continuation_.action);
  }

  void play_complex(const Ts &...x) override {
    this->num_running_++;
    if (this->condition_->check(x...)) {
      if (!this->then_.empty() && this->num_running_ > 0) {
        this->then_.play(x...);
        return;
      }
    } else if constexpr (HasElse) {
      if (!this->else_.empty() && this->num_running_ > 0) {
        this->else_.play(x...);
        return;
      }
    }
    this->play_next_(x...);
  }

  void play(const Ts &...x) override { /* ignore - see play_complex */
  }

  void stop() override {
    this->then_.stop();
    if constexpr (HasElse) {
      this->else_.stop();
    }
  }

 protected:
  Condition<Ts...> *condition_;
  ActionList<Ts...> then_;
  ContinuationAction<Ts...> then_continuation_{this};
  struct NoElse {};
  [[no_unique_address]] std::conditional_t<HasElse, ActionList<Ts...>, NoElse> else_;
  [[no_unique_address]] OptionalContinuation<HasElse, Ts...> else_continuation_{this};
};

template<typename... Ts> class WhileAction : public Action<Ts...> {
 public:
  WhileAction(Condition<Ts...> *condition) : condition_(condition) {}

  // Precondition: must be called at most once per instance (see IfAction::add_then).
  void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
    this->then_.add_actions(actions);
    this->then_.add_action(&this->loop_continuation_);
  }

  friend class WhileLoopContinuation<Ts...>;

  void play_complex(const Ts &...x) override {
    this->num_running_++;
    // Initial condition check
    if (!this->condition_->check(x...)) {
      // If new condition check failed, stop loop if running
      this->then_.stop();
      this->play_next_(x...);
      return;
    }

    if (this->num_running_ > 0) {
      this->then_.play(x...);
    }
  }

  void play(const Ts &...x) override { /* ignore - see play_complex */
  }

  void stop() override { this->then_.stop(); }

 protected:
  Condition<Ts...> *condition_;
  ActionList<Ts...> then_;
  WhileLoopContinuation<Ts...> loop_continuation_{this};
};

// Implementation of WhileLoopContinuation::play
template<typename... Ts> void WhileLoopContinuation<Ts...>::play(const Ts &...x) {
  if (this->parent_->num_running_ > 0 && this->parent_->condition_->check(x...)) {
    // play again
    this->parent_->then_.play(x...);
  } else {
    // condition false, play next
    this->parent_->play_next_(x...);
  }
}

// Forward declaration for RepeatLoopContinuation
template<typename... Ts> class RepeatAction;

/// Loop continuation for RepeatAction that increments iteration and repeats or continues.
/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function).
template<typename... Ts> class RepeatLoopContinuation : public Action<uint32_t, Ts...> {
 public:
  explicit RepeatLoopContinuation(RepeatAction<Ts...> *parent) : parent_(parent) {}

  void play(const uint32_t &iteration, const Ts &...x) override;

 protected:
  RepeatAction<Ts...> *parent_;
};

template<typename... Ts> class RepeatAction : public Action<Ts...> {
 public:
  TEMPLATABLE_VALUE(uint32_t, count)

  // Precondition: must be called at most once per instance (see IfAction::add_then).
  void add_then(const std::initializer_list<Action<uint32_t, Ts...> *> &actions) {
    this->then_.add_actions(actions);
    this->then_.add_action(&this->loop_continuation_);
  }

  friend class RepeatLoopContinuation<Ts...>;

  void play_complex(const Ts &...x) override {
    this->num_running_++;
    if (this->count_.value(x...) > 0) {
      this->then_.play(0, x...);
    } else {
      this->play_next_(x...);
    }
  }

  void play(const Ts &...x) override { /* ignore - see play_complex */
  }

  void stop() override { this->then_.stop(); }

 protected:
  ActionList<uint32_t, Ts...> then_;
  RepeatLoopContinuation<Ts...> loop_continuation_{this};
};

// Implementation of RepeatLoopContinuation::play
template<typename... Ts> void RepeatLoopContinuation<Ts...>::play(const uint32_t &iteration, const Ts &...x) {
  uint32_t next_iteration = iteration + 1;
  if (next_iteration >= this->parent_->count_.value(x...)) {
    this->parent_->play_next_(x...);
  } else {
    this->parent_->then_.play(next_iteration, x...);
  }
}

/** Wait until a condition is true to continue execution.
 *
 * Uses queue-based storage to safely handle concurrent executions.
 * While concurrent execution from the same trigger is uncommon, it's possible
 * (e.g., rapid button presses, high-frequency sensor updates), so we use
 * queue-based storage for correctness.
 */
template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Component {
 public:
  WaitUntilAction(Condition<Ts...> *condition) : condition_(condition) {}

  TEMPLATABLE_VALUE(uint32_t, timeout_value)

  void setup() override {
    // Start with loop disabled - only enable when there's work to do
    // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already
    // called before our setup() (e.g., from on_boot trigger at same priority level)
    // and we must not undo its enable_loop() call
    if (this->num_running_ == 0) {
      this->disable_loop();
    }
  }

  void play_complex(const Ts &...x) override {
    this->num_running_++;
    // Check if we can continue immediately.
    if (this->condition_->check(x...)) {
      if (this->num_running_ > 0) {
        this->play_next_(x...);
      }
      return;
    }

    // Store for later processing
    auto now = millis();
    auto timeout = this->timeout_value_.optional_value(x...);
    this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...));

    // Do immediate check with fresh timestamp - don't call loop() synchronously!
    // Let the event loop call it to avoid reentrancy issues
    if (this->process_queue_(now)) {
      // Only enable loop if we still have pending items
      this->enable_loop();
    }
  }

  void loop() override {
    // Safe to use cached time - only called from Application::loop()
    if (this->num_running_ > 0 && !this->process_queue_(App.get_loop_component_start_time())) {
      // If queue is now empty, disable loop until next play_complex
      this->disable_loop();
    }
  }

  void stop() override {
    this->var_queue_.clear();
    // Tell any process_queue_() call further down the stack that the items it is
    // still holding were cancelled
    this->stop_generation_++;
    this->disable_loop();
  }

  float get_setup_priority() const override { return setup_priority::DATA; }

  void play(const Ts &...x) override { /* ignore - see play_complex */
  }

 protected:
  using QueueItem = std::tuple<uint32_t, optional<uint32_t>, std::tuple<Ts...>>;

  // Helper: Process queue, triggering completed items and removing them
  // Returns true if queue still has pending items
  bool process_queue_(uint32_t now) {
    // Completed items run the rest of the action chain synchronously, and that chain
    // can re-enter this same action (e.g. a script with mode: restart that executes
    // itself) and add to or clear var_queue_. Iterating the member list directly would
    // then corrupt it, so move it aside and iterate a local list instead.
    std::list<QueueItem> queue;
    queue.swap(this->var_queue_);
    std::list<QueueItem> pending;
    while (!queue.empty()) {
      auto it = queue.begin();
      auto start = std::get<uint32_t>(*it);
      auto timeout = std::get<optional<uint32_t>>(*it);

      // Check if timeout has expired
      auto expired = timeout && (now - start) >= *timeout;

      // Keep waiting if not expired and condition not met
      if (!expired && !this->condition_->check_tuple(std::get<std::tuple<Ts...>>(*it))) {
        pending.splice(pending.end(), queue, it);
        continue;
      }

      // Condition met or timed out - trigger the next action. Keep the item in a local
      // holder so its arguments stay valid while the chain runs, without any nested
      // process_queue_() call being able to see (and fire) it again.
      std::list<QueueItem> completed;
      completed.splice(completed.begin(), queue, it);
      uint8_t generation = this->stop_generation_;
      this->play_next_tuple_(std::get<std::tuple<Ts...>>(completed.front()));
      if (generation != this->stop_generation_) {
        // stop() ran inside the chain - the items still held locally were cancelled
        pending.clear();
        break;
      }
    }

    // Re-entrant continuations may have enqueued new waits into var_queue_; put the
    // older still-waiting items back in front of them to keep FIFO firing order
    this->var_queue_.splice(this->var_queue_.begin(), pending);

    return !this->var_queue_.empty();
  }

  Condition<Ts...> *condition_;
  std::list<QueueItem> var_queue_{};
  // Bumped by stop() so process_queue_() can detect a stop from inside play_next_tuple_()
  uint8_t stop_generation_{0};
};

template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
 public:
  UpdateComponentAction(PollingComponent *component) : component_(component) {}

  void play(const Ts &...x) override {
    if (!this->component_->is_ready())
      return;
    this->component_->update();
  }

 protected:
  PollingComponent *component_;
};

template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
 public:
  SuspendComponentAction(PollingComponent *component) : component_(component) {}

  void play(const Ts &...x) override {
    if (!this->component_->is_ready())
      return;
    this->component_->stop_poller();
  }

 protected:
  PollingComponent *component_;
};

template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
 public:
  ResumeComponentAction(PollingComponent *component) : component_(component) {}
  TEMPLATABLE_VALUE(uint32_t, update_interval)

  void play(const Ts &...x) override {
    if (!this->component_->is_ready()) {
      return;
    }
    optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
    if (update_interval.has_value()) {
      this->component_->set_update_interval(update_interval.value());
    }
    this->component_->start_poller();
  }

 protected:
  PollingComponent *component_;
};

}  // namespace esphome
