#pragma once

#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"

namespace esphome::logger {

// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
static constexpr uint16_t MAX_HEADER_SIZE = 128;

// ANSI color code last digit (30-38 range, store only last digit to save RAM on ESP8266)
static const char LOG_LEVEL_COLOR_DIGIT[] PROGMEM = {
    '\0',  // NONE
    '1',   // ERROR (31 = red)
    '3',   // WARNING (33 = yellow)
    '2',   // INFO (32 = green)
    '5',   // CONFIG (35 = magenta)
    '6',   // DEBUG (36 = cyan)
    '7',   // VERBOSE (37 = gray)
    '8',   // VERY_VERBOSE (38 = white)
};

static const char LOG_LEVEL_LETTER_CHARS[] PROGMEM = {
    '\0',  // NONE
    'E',   // ERROR
    'W',   // WARNING
    'I',   // INFO
    'C',   // CONFIG
    'D',   // DEBUG
    'V',   // VERBOSE (VERY_VERBOSE uses two 'V's)
};

// Buffer wrapper for log formatting functions
struct LogBuffer {
  char *data;
  uint16_t size;
  uint16_t pos{0};
  // Replaces the null terminator with a newline for console output.
  // Must be called after notify_listeners_() since listeners need null-terminated strings.
  // Console output uses length-based writes (buf.pos), so null terminator is not needed.
  void terminate_with_newline() {
    if (this->pos < this->size) {
      this->data[this->pos++] = '\n';
    } else if (this->size > 0) {
      // Buffer was full - replace last char with newline to ensure it's visible
      this->data[this->size - 1] = '\n';
      this->pos = this->size;
    }
  }
  void HOT write_header(uint8_t level, const char *tag, int line, const char *thread_name) {
    // Early return if insufficient space - intentionally don't update pos to prevent partial writes
    if (this->pos + MAX_HEADER_SIZE > this->size)
      return;

    char *p = this->current_();

    // Write ANSI color
    this->write_ansi_color_(p, level);

    // Construct: [LEVEL][tag:line]
    *p++ = '[';
    if (level != 0) {
      if (level >= 7) {
        *p++ = 'V';  // VERY_VERBOSE = "VV"
        *p++ = 'V';
      } else {
        *p++ = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(&LOG_LEVEL_LETTER_CHARS[level])));
      }
    }
    *p++ = ']';
    *p++ = '[';

    // Copy tag
    this->copy_string_(p, tag);

    *p++ = ':';

    // Format line number using subtraction loops (no division - important for ESP8266 which lacks hardware divider)
    if (line > 999) [[unlikely]] {
      write_digit(p, line, 1000);
    }
    write_digit(p, line, 100);
    write_digit(p, line, 10);
    *p++ = '0' + line;
    *p++ = ']';

#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST)
    // Write thread name with bold red color
    if (thread_name != nullptr) {
      this->write_ansi_color_(p, 1);  // Bold red for thread name
      *p++ = '[';
      this->copy_string_(p, thread_name);
      *p++ = ']';
      this->write_ansi_color_(p, level);  // Restore original color
    }
#endif

    *p++ = ':';
    *p++ = ' ';

    this->pos = p - this->data;
  }
  void HOT format_body(const char *format, va_list args) {
    this->format_vsnprintf_(format, args);
    this->finalize_();
  }
#ifdef USE_STORE_LOG_STR_IN_FLASH
  void HOT format_body_P(PGM_P format, va_list args) {
    this->format_vsnprintf_P_(format, args);
    this->finalize_();
  }
#endif
  void write_body(const char *text, uint16_t text_length) {
    const uint16_t available = this->remaining_();
    const uint16_t copy_len = (text_length < available) ? text_length : available;
    if (copy_len > 0) {
      memcpy(this->current_(), text, copy_len);
      this->pos += copy_len;
    }
    this->finalize_();
  }

 private:
  bool full_() const { return this->pos >= this->size; }
  uint16_t remaining_() const { return this->size - this->pos; }
  char *current_() { return this->data + this->pos; }
  void finalize_() {
    this->write_ansi_reset_();
    // Null terminate
    this->data[this->full_() ? this->size - 1 : this->pos] = '\0';
  }
  // Write ANSI reset sequence inline ("\033[0m") - avoids write_() call overhead
  static constexpr uint16_t ANSI_RESET_LEN = 4;  // "\033[0m"
  void write_ansi_reset_() {
    if (this->remaining_() >= ANSI_RESET_LEN) {
      char *p = this->current_();
      *p++ = '\033';
      *p++ = '[';
      *p++ = '0';
      *p++ = 'm';
      this->pos += ANSI_RESET_LEN;
    }
  }
  void strip_trailing_newlines_() {
    while (this->pos > 0 && this->data[this->pos - 1] == '\n')
      this->pos--;
  }
  void process_vsnprintf_result_(int ret) {
    if (ret < 0)
      return;
    const uint16_t rem = this->remaining_();
    this->pos += (ret >= rem) ? (rem - 1) : static_cast<uint16_t>(ret);
    this->strip_trailing_newlines_();
  }
  void format_vsnprintf_(const char *format, va_list args) {
    if (this->full_())
      return;
    this->process_vsnprintf_result_(vsnprintf(this->current_(), this->remaining_(), format, args));
  }
#ifdef USE_STORE_LOG_STR_IN_FLASH
  void format_vsnprintf_P_(PGM_P format, va_list args) {
    if (this->full_())
      return;
    this->process_vsnprintf_result_(vsnprintf_P(this->current_(), this->remaining_(), format, args));
  }
#endif
  // Extract one decimal digit via subtraction (no division - important for ESP8266)
  static inline void ESPHOME_ALWAYS_INLINE write_digit(char *&p, int &value, int divisor) {
    char d = '0';
    while (value >= divisor) {
      d++;
      value -= divisor;
    }
    *p++ = d;
  }
  // Write ANSI color escape sequence to buffer, updates pointer in place
  // Caller is responsible for ensuring buffer has sufficient space
  void write_ansi_color_(char *&p, uint8_t level) {
    if (level == 0)
      return;
    // Direct buffer fill: "\033[{bold};3{color}m" (7 bytes)
    *p++ = '\033';
    *p++ = '[';
    *p++ = (level == 1) ? '1' : '0';  // Only ERROR is bold
    *p++ = ';';
    *p++ = '3';
    *p++ = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(&LOG_LEVEL_COLOR_DIGIT[level])));
    *p++ = 'm';
  }
  // Copy string without null terminator, updates pointer in place
  // Caller is responsible for ensuring buffer has sufficient space
  void copy_string_(char *&p, const char *str) {
    const size_t len = strlen(str);
    // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by
    // piece
    memcpy(p, str, len);
    p += len;
  }
};

}  // namespace esphome::logger
