#pragma once

#include <cstdint>
#include <cstring>
#include <iterator>
#include <memory>
#include <string>
#include "esphome/core/defines.h"

#ifdef USE_JSON
#include "esphome/components/json/json_util.h"
#endif  // USE_JSON

#ifdef USE_ESP8266
#include <pgmspace.h>
#endif  // USE_ESP8266

namespace esphome {

/**
 * StringRef is a reference to a string owned by something else.  So it behaves like simple string, but it does not own
 * pointer.  When it is default constructed, it has empty string.  You can freely copy or move around this struct, but
 * never free its pointer.  str() function can be used to export the content as std::string. StringRef is adopted from
 * <https://github.com/nghttp2/nghttp2/blob/29cbf8b83ff78faf405d1086b16adc09a8772eca/src/template.h#L376>
 */
class StringRef {
 public:
  using traits_type = std::char_traits<char>;
  using value_type = traits_type::char_type;
  using allocator_type = std::allocator<char>;
  using size_type = std::allocator_traits<allocator_type>::size_type;
  using difference_type = std::allocator_traits<allocator_type>::difference_type;
  using const_reference = const value_type &;
  using const_pointer = const value_type *;
  using const_iterator = const_pointer;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;

  constexpr StringRef() : base_(""), len_(0) {}
  explicit StringRef(const std::string &s) : base_(s.c_str()), len_(s.size()) {}
  explicit StringRef(const char *s) : base_(s), len_(strlen(s)) {}
  constexpr StringRef(const char *s, size_t n) : base_(s), len_(n) {}
  template<typename CharT>
  constexpr StringRef(const CharT *s, size_t n) : base_(reinterpret_cast<const char *>(s)), len_(n) {}
  template<typename InputIt>
  StringRef(InputIt first, InputIt last)
      : base_(reinterpret_cast<const char *>(&*first)), len_(std::distance(first, last)) {}
  template<typename InputIt>
  StringRef(InputIt *first, InputIt *last)
      : base_(reinterpret_cast<const char *>(first)), len_(std::distance(first, last)) {}
  template<typename CharT, size_t N> constexpr static StringRef from_lit(const CharT (&s)[N]) {
    return StringRef{s, N - 1};
  }
  static StringRef from_maybe_nullptr(const char *s) {
    if (s == nullptr) {
      return StringRef();
    }

    return StringRef(s);
  }

  constexpr const_iterator begin() const { return base_; };
  constexpr const_iterator cbegin() const { return base_; };

  constexpr const_iterator end() const { return base_ + len_; };
  constexpr const_iterator cend() const { return base_ + len_; };

  const_reverse_iterator rbegin() const { return const_reverse_iterator{base_ + len_}; }
  const_reverse_iterator crbegin() const { return const_reverse_iterator{base_ + len_}; }

  const_reverse_iterator rend() const { return const_reverse_iterator{base_}; }
  const_reverse_iterator crend() const { return const_reverse_iterator{base_}; }

  constexpr const char *c_str() const { return base_; }
  constexpr size_type size() const { return len_; }
  constexpr size_type length() const { return len_; }
  constexpr bool empty() const { return len_ == 0; }
  constexpr const_reference operator[](size_type pos) const { return *(base_ + pos); }

  /// Copy characters to destination buffer (std::string::copy-like, but returns 0 instead of throwing on out-of-range)
  size_type copy(char *dest, size_type count, size_type pos = 0) const {
    if (pos >= len_)
      return 0;
    size_type actual = (count > len_ - pos) ? len_ - pos : count;
    std::memcpy(dest, base_ + pos, actual);
    return actual;
  }

  std::string str() const { return std::string(base_, len_); }
  const uint8_t *byte() const { return reinterpret_cast<const uint8_t *>(base_); }

  operator std::string() const { return str(); }

  /// Compare (compatible with std::string::compare)
  int compare(const StringRef &other) const {
    int result = std::memcmp(base_, other.base_, std::min(len_, other.len_));
    if (result != 0)
      return result;
    if (len_ < other.len_)
      return -1;
    if (len_ > other.len_)
      return 1;
    return 0;
  }
  int compare(const char *s) const { return compare(StringRef(s)); }
  int compare(const std::string &s) const { return compare(StringRef(s)); }

  /// Find first occurrence of substring, returns std::string::npos if not found.
  /// Note: Requires the underlying string to be null-terminated.
  size_type find(const char *s, size_type pos = 0) const {
    if (pos >= len_)
      return std::string::npos;
    const char *result = std::strstr(base_ + pos, s);
    // Verify entire match is within bounds (strstr searches to null terminator)
    if (result && result + std::strlen(s) <= base_ + len_)
      return static_cast<size_type>(result - base_);
    return std::string::npos;
  }
  size_type find(char c, size_type pos = 0) const {
    if (pos >= len_)
      return std::string::npos;
    const void *result = std::memchr(base_ + pos, static_cast<unsigned char>(c), len_ - pos);
    return result ? static_cast<size_type>(static_cast<const char *>(result) - base_) : std::string::npos;
  }

  /// Return substring as std::string
  std::string substr(size_type pos = 0, size_type count = std::string::npos) const {
    if (pos >= len_)
      return std::string();
    size_type actual_count = (count == std::string::npos || pos + count > len_) ? len_ - pos : count;
    return std::string(base_ + pos, actual_count);
  }

 private:
  const char *base_;
  size_type len_;
};

inline bool operator==(const StringRef &lhs, const StringRef &rhs) {
  return lhs.size() == rhs.size() && std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
}

inline bool operator==(const StringRef &lhs, const std::string &rhs) {
  return lhs.size() == rhs.size() && std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
}

inline bool operator==(const std::string &lhs, const StringRef &rhs) { return rhs == lhs; }

inline bool operator==(const StringRef &lhs, const char *rhs) {
  return lhs.size() == strlen(rhs) && std::equal(std::begin(lhs), std::end(lhs), rhs);
}

inline bool operator==(const char *lhs, const StringRef &rhs) { return rhs == lhs; }

inline bool operator!=(const StringRef &lhs, const StringRef &rhs) { return !(lhs == rhs); }

inline bool operator!=(const StringRef &lhs, const std::string &rhs) { return !(lhs == rhs); }

inline bool operator!=(const std::string &lhs, const StringRef &rhs) { return !(rhs == lhs); }

inline bool operator!=(const StringRef &lhs, const char *rhs) { return !(lhs == rhs); }

inline bool operator!=(const char *lhs, const StringRef &rhs) { return !(rhs == lhs); }

#ifdef USE_ESP8266
inline bool operator==(const StringRef &lhs, const __FlashStringHelper *rhs) {
  PGM_P p = reinterpret_cast<PGM_P>(rhs);
  size_t rhs_len = strlen_P(p);
  if (lhs.size() != rhs_len) {
    return false;
  }
  return memcmp_P(lhs.c_str(), p, rhs_len) == 0;
}

inline bool operator==(const __FlashStringHelper *lhs, const StringRef &rhs) { return rhs == lhs; }

inline bool operator!=(const StringRef &lhs, const __FlashStringHelper *rhs) { return !(lhs == rhs); }

inline bool operator!=(const __FlashStringHelper *lhs, const StringRef &rhs) { return !(rhs == lhs); }
#endif  // USE_ESP8266

inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
  return std::lexicographical_compare(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs));
}

inline std::string &operator+=(std::string &lhs, const StringRef &rhs) {
  lhs.append(rhs.c_str(), rhs.size());
  return lhs;
}

inline std::string operator+(const char *lhs, const StringRef &rhs) {
  auto str = std::string(lhs);
  str.append(rhs.c_str(), rhs.size());
  return str;
}

inline std::string operator+(const StringRef &lhs, const char *rhs) {
  auto str = lhs.str();
  str.append(rhs);
  return str;
}

inline std::string operator+(const StringRef &lhs, const std::string &rhs) {
  auto str = lhs.str();
  str.append(rhs);
  return str;
}

inline std::string operator+(const std::string &lhs, const StringRef &rhs) {
  std::string str(lhs);
  str.append(rhs.c_str(), rhs.size());
  return str;
}
// String conversion functions for ADL compatibility (allows stoi(x) where x is StringRef)
// Must be in esphome namespace for ADL to find them. Uses strtol/strtod directly to avoid heap allocation.
namespace internal {
// NOLINTBEGIN(google-runtime-int)
template<typename R, typename F> inline R parse_number(const StringRef &str, size_t *pos, F conv) {
  char *end;
  R result = conv(str.c_str(), &end);
  // Set pos to 0 on conversion failure (when no characters consumed), otherwise index after number
  if (pos)
    *pos = (end == str.c_str()) ? 0 : static_cast<size_t>(end - str.c_str());
  return result;
}
template<typename R, typename F> inline R parse_number(const StringRef &str, size_t *pos, int base, F conv) {
  char *end;
  R result = conv(str.c_str(), &end, base);
  // Set pos to 0 on conversion failure (when no characters consumed), otherwise index after number
  if (pos)
    *pos = (end == str.c_str()) ? 0 : static_cast<size_t>(end - str.c_str());
  return result;
}
// NOLINTEND(google-runtime-int)
}  // namespace internal
// NOLINTBEGIN(readability-identifier-naming,google-runtime-int)
inline int stoi(const StringRef &str, size_t *pos = nullptr, int base = 10) {
  return static_cast<int>(internal::parse_number<long>(str, pos, base, std::strtol));
}
inline long stol(const StringRef &str, size_t *pos = nullptr, int base = 10) {
  return internal::parse_number<long>(str, pos, base, std::strtol);
}
inline float stof(const StringRef &str, size_t *pos = nullptr) {
  return internal::parse_number<float>(str, pos, std::strtof);
}
inline double stod(const StringRef &str, size_t *pos = nullptr) {
  return internal::parse_number<double>(str, pos, std::strtod);
}
// NOLINTEND(readability-identifier-naming,google-runtime-int)

#ifdef USE_JSON
// NOLINTNEXTLINE(readability-identifier-naming)
inline void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); }
#endif  // USE_JSON

}  // namespace esphome
