#include "debug_component.h"

#include <algorithm>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#include <cinttypes>
#include <climits>

namespace esphome::debug {

static const char *const TAG = "debug";

void DebugComponent::dump_config() {
  ESP_LOGCONFIG(TAG, "Debug component:");
#ifdef USE_TEXT_SENSOR
  LOG_TEXT_SENSOR("  ", "Device info", this->device_info_);
#endif  // USE_TEXT_SENSOR
#ifdef USE_SENSOR
  LOG_SENSOR("  ", "Free space on heap", this->free_sensor_);
  LOG_SENSOR("  ", "Largest free heap block", this->block_sensor_);
  LOG_SENSOR("  ", "CPU frequency", this->cpu_frequency_sensor_);
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
  LOG_SENSOR("  ", "Heap fragmentation", this->fragmentation_sensor_);
#endif  // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
#endif  // USE_SENSOR

  char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
  ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
  size_t pos = buf_append_str(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, ESPHOME_VERSION);

  this->free_heap_ = get_free_heap_();
  ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);

  pos = get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>(device_info_buffer), pos);

#ifdef USE_TEXT_SENSOR
  if (this->device_info_ != nullptr) {
    this->device_info_->publish_state(device_info_buffer, pos);
  }
  if (this->reset_reason_ != nullptr) {
    char reset_reason_buffer[RESET_REASON_BUFFER_SIZE];
    this->reset_reason_->publish_state(
        get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reset_reason_buffer)));
  }
#endif  // USE_TEXT_SENSOR

#if defined(USE_ESP32) || defined(USE_ZEPHYR)
  this->log_partition_info_();  // Log partition information
#endif
}

void DebugComponent::loop() {
  // log when free heap space has halved
  uint32_t new_free_heap = get_free_heap_();
  if (new_free_heap < this->free_heap_ / 2) {
    this->free_heap_ = new_free_heap;
    ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
    this->status_momentary_warning("heap", 1000);
  }

#ifdef USE_SENSOR
  // calculate loop time - from last call to this one
  if (this->loop_time_sensor_ != nullptr) {
    uint32_t now = App.get_loop_component_start_time();
    uint32_t loop_time = now - this->last_loop_timetag_;
    this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
    this->last_loop_timetag_ = now;
  }
#endif  // USE_SENSOR
}

void DebugComponent::update() {
#ifdef USE_SENSOR
  if (this->free_sensor_ != nullptr) {
    this->free_sensor_->publish_state(get_free_heap_());
  }

  if (this->loop_time_sensor_ != nullptr) {
    this->loop_time_sensor_->publish_state(this->max_loop_time_);
    this->max_loop_time_ = 0;
  }
  if (this->cpu_frequency_sensor_ != nullptr) {
    this->cpu_frequency_sensor_->publish_state(arch_get_cpu_freq_hz());
  }

#endif  // USE_SENSOR
  update_platform_();
}

float DebugComponent::get_setup_priority() const { return setup_priority::LATE; }

}  // namespace esphome::debug
