##########################################################################################
##########################################################################################
# Title:    Reset Classification & Counters (Interval-based, no on_boot work)
# Target:   ESP8266/ESP32 (optimized for ESP8266 heap stability)
#
# Exposes four entities:
#   - <friendly> Restarts: Power       (sensor)
#   - <friendly> Restarts: Software    (sensor)
#   - <friendly> Restarts: Crashes     (sensor)
#   - <friendly> Restarts: Last Reason (text_sensor)
#
# Also exposes a button:
#   - <friendly> Restarts: Clear Counters
#
# Behavior:
#   - Does NOT run on_boot. Instead, an interval waits a short time after power-up, then:
#       * Reads ESP.getResetReason()
#       * Categorizes into one of: power, software, crash (exactly one bucket)
#       * Increments that counter and publishes all three sensors + the last reason.
#   - A 'run-once per boot' guard prevents re-incrementing on subsequent intervals.
#
# Notes:
#   - Uses Arduino String (not std::string) in lambdas to reduce heap churn.
#   - Sensors use update_interval: never; they’re pushed when we publish.
#   - If you prefer a different delay, change the interval: value below.
##########################################################################################
##########################################################################################

##########################################################################################
# GLOBALS
##########################################################################################
globals:
  # Cumulative counters (persist across reboots)
  - id: power_reset_count
    type: int
    restore_value: true
    initial_value: '0'
  - id: software_reset_count
    type: int
    restore_value: true
    initial_value: '0'
  - id: crash_reset_count
    type: int
    restore_value: true
    initial_value: '0'

  # Per-boot guard so the interval only classifies once each boot
  - id: reset_classified_this_boot
    type: bool
    restore_value: false       # always false at cold boot
    initial_value: 'false'

##########################################################################################
# SENSORS (push-only; never self-update)
##########################################################################################
sensor:
  - platform: template
    id: power_reset_count_sensor
    name: "${friendly_name} Restarts: Power"
    icon: mdi:power-plug
    unit_of_measurement: "restarts"
    accuracy_decimals: 0
    entity_category: diagnostic
    update_interval: never

  - platform: template
    id: software_reset_count_sensor
    name: "${friendly_name} Restarts: Software"
    icon: mdi:restart
    unit_of_measurement: "restarts"
    accuracy_decimals: 0
    entity_category: diagnostic
    update_interval: never

  - platform: template
    id: crash_reset_count_sensor
    name: "${friendly_name} Restarts: Crashes"
    icon: mdi:alert-decagram
    unit_of_measurement: "restarts"
    accuracy_decimals: 0
    entity_category: diagnostic
    update_interval: never

##########################################################################################
# TEXT SENSORS (push-only; never self-update)
##########################################################################################
text_sensor:
  - platform: template
    id: last_reset_reason_txt
    name: "${friendly_name} Restarts: Last Reason"
    icon: mdi:restart-alert
    entity_category: diagnostic
    update_interval: never

##########################################################################################
# BUTTONS
##########################################################################################
button:
  - platform: template
    id: clear_reset_counters_btn
    name: "${friendly_name} Restarts: Clear Counters"
    icon: mdi:backup-restore
    entity_category: diagnostic
    on_press:
      - lambda: |-
          id(power_reset_count) = 0;
          id(software_reset_count) = 0;
          id(crash_reset_count) = 0;
          id(power_reset_count_sensor).publish_state(0);
          id(software_reset_count_sensor).publish_state(0);
          id(crash_reset_count_sensor).publish_state(0);
          // Keep the last reason as-is; it describes the most recent boot.

##########################################################################################
# SCRIPTS
##########################################################################################
script:
  - id: classify_reset_once
    mode: restart
    then:
      - lambda: |-
          // Guard: only run once per boot
          if (id(reset_classified_this_boot)) return;

          // Build a human-readable reason string that's valid on both ESP8266 and ESP32
          std::string reason;
          int cat = 0;  // 1=power, 2=software, 3=crash

          #if defined(ARDUINO_ARCH_ESP8266)
            // ESP8266: use Arduino core helper, returns Arduino String
            reason = std::string(ESP.getResetReason().c_str());

            // Classify (string match like your original)
            if (reason.find("Watchdog") != std::string::npos ||
                reason.find("WDT")      != std::string::npos ||
                reason.find("Exception")!= std::string::npos ||
                reason.find("exception")!= std::string::npos) {
              cat = 3;
            } else if (reason.find("Software") != std::string::npos ||
                       reason.find("System")   != std::string::npos) {
              cat = 2;
            } else if (reason.find("Power")    != std::string::npos ||
                       reason.find("External") != std::string::npos ||
                       reason.find("Deep")     != std::string::npos) {
              cat = 1;
            } else {
              cat = 3;
            }

          #elif defined(ARDUINO_ARCH_ESP32)
            // ESP32: use IDF enum and map to text + categories directly
            esp_reset_reason_t r = esp_reset_reason();
            switch (r) {
              case ESP_RST_POWERON:   reason = "Power on";               cat = 1; break;
              case ESP_RST_EXT:       reason = "External reset";         cat = 1; break; // reset pin/button
              case ESP_RST_SW:        reason = "Software reset";         cat = 2; break; // OTA/API/HA/etc.
              case ESP_RST_PANIC:     reason = "Panic/exception";        cat = 3; break;
              case ESP_RST_INT_WDT:   reason = "Interrupt WDT";          cat = 3; break;
              case ESP_RST_TASK_WDT:  reason = "Task WDT";               cat = 3; break;
              case ESP_RST_WDT:       reason = "Other WDT";              cat = 3; break;
              case ESP_RST_DEEPSLEEP: reason = "Deep sleep wake";        cat = 1; break;
              case ESP_RST_BROWNOUT:  reason = "Brownout";               cat = 1; break; // power-ish
              case ESP_RST_SDIO:      reason = "SDIO reset";             cat = 3; break;
              default:                reason = "Unknown reset reason";   cat = 3; break;
            }
          #else
            // Fallback for any other arch (very unlikely in your setup)
            reason = "Unknown platform reset";
            cat = 3;
          #endif

          // Increment counters
          if (cat == 1) id(power_reset_count) += 1;
          if (cat == 2) id(software_reset_count) += 1;
          if (cat == 3) id(crash_reset_count) += 1;

          // Publish reason and mark done
          id(last_reset_reason_txt).publish_state(reason.c_str());
          id(reset_classified_this_boot) = true;

  # Publish the three counters (kept separate so we can reuse)
  - id: publish_reset_status
    mode: restart
    then:
      - lambda: |-
          id(power_reset_count_sensor).publish_state(id(power_reset_count));
          id(software_reset_count_sensor).publish_state(id(software_reset_count));
          id(crash_reset_count_sensor).publish_state(id(crash_reset_count));

##########################################################################################
# INTERVAL: run once shortly after boot, then no-op
##########################################################################################
interval:
  - interval: 10s
    then:
      - if:
          condition:
            lambda: 'return !id(reset_classified_this_boot);'
          then:
            - script.execute: classify_reset_once
            - script.execute: publish_reset_status
      # On later passes, the guard prevents re-counting and this block does nothing.

