#include "deep_sleep_component.h"
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/wake.h"
#include <zephyr/sys/poweroff.h>
#include <algorithm>

namespace esphome::deep_sleep {

static const char *const TAG = "deep_sleep";

// The Zephyr watchdog has a short window (2s, or 10s with Zigbee) and
// WDT_OPT_PAUSE_IN_SLEEP only pauses it during true hardware sleep — not while a
// radio thread (e.g. the Zigbee stack) keeps the CPU busy in k_sem_take(). Feed
// it at least this often while waiting so it does not reset the device.
static const uint32_t WDT_FEED_INTERVAL_MS = 1000;

static bool wakeable_delay_feed_wdt(uint32_t ms) {
  while (ms > 0) {
    const uint32_t step = std::min(ms, WDT_FEED_INTERVAL_MS);
    esphome::internal::wakeable_delay(step);
    esphome::arch_feed_wdt();
    if (esphome::wake_request_take()) {
      return true;
    }
    if (ms != UINT32_MAX) {
      ms -= step;
    }
  }
  return false;
}

optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }

void DeepSleepComponent::dump_config_platform_() {}

bool DeepSleepComponent::prepare_to_sleep_() { return true; }

void DeepSleepComponent::deep_sleep_() {
  bool woke = false;
  if (this->sleep_duration_.has_value()) {
    woke = wakeable_delay_feed_wdt(static_cast<uint32_t>(*this->sleep_duration_ / 1000));
  } else {
#ifndef USE_ZIGBEE
    // the device can be woken up through one of the following signals:
    // - The DETECT signal, optionally generated by the GPIO peripheral.
    // - The ANADETECT signal, optionally generated by the LPCOMP module.
    // - The SENSE signal, optionally generated by the NFC module to wake-on-field.
    // - Detecting a valid USB voltage on the VBUS pin (VBUS,DETECT).
    // - A reset.
    //
    // The system is reset when it wakes up from System OFF mode.
    sys_poweroff();
#else
    woke = wakeable_delay_feed_wdt(UINT32_MAX);
#endif
  }
  if (woke) {
    ESP_LOGD(TAG, "Woken up by another thread");
  } else {
    ESP_LOGD(TAG, "Timeout expired (normal sleep)");
  }
}

bool DeepSleepComponent::should_teardown_() {
  if (this->sleep_duration_.has_value()) {
    return false;
  }
#ifdef USE_ZIGBEE
  return false;
#else
  return true;
#endif
}

}  // namespace esphome::deep_sleep

#endif
