#ifndef PREFERENCES_HPP
#define PREFERENCES_HPP

#include "Arduino.h"
#include <cstring>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>

class Preferences {
public:
  Preferences() = default;

  bool begin(const char *name, bool readOnly = false) {
    std::lock_guard<std::mutex> lock(mutex_);
    currentNamespace_ = name;
    return true;
  }

  void end() {
    std::lock_guard<std::mutex> lock(mutex_);
    currentNamespace_.clear();
  }

  bool clear() {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty())
      return false;
    data_[currentNamespace_].clear();
    return true;
  }

  bool remove(const char *key) {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty())
      return false;
    return data_[currentNamespace_].erase(key) > 0;
  }

  bool isKey(const char* key) const {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty()) return false;
    
    auto nsIt = data_.find(currentNamespace_);
    if (nsIt == data_.end()) return false;  // Namespace does not exist
    
    const auto& nsData = nsIt->second;
    return nsData.find(key) != nsData.end();
}

  // ----- PUT -----
  template <typename T> bool put(const char *key, const T &value) {
    static_assert(std::is_trivially_copyable<T>::value,
                  "Only trivially copyable types supported");
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty())
      return false;
    data_[currentNamespace_][key] =
        std::string(reinterpret_cast<const char *>(&value), sizeof(T));
    return true;
  }

  bool putBytes(const char *key, const void *bytes, size_t len) {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty() || !bytes)
      return false;
    data_[currentNamespace_][key] =
        std::string(static_cast<const char *>(bytes), len);
    return true;
  }

  bool putString(const char *key, const char* value) {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty())
      return false;
    data_[currentNamespace_][key] = value;
    return true;
  }  
  
  bool putString(const char *key, String value) {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty())
      return false;
    data_[currentNamespace_][key] = value.c_str();
    return true;
  }

  // ----- GET -----
  template <typename T> bool get(const char *key, T &value) const {
    static_assert(std::is_trivially_copyable<T>::value,
                  "Only trivially copyable types supported");
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty())
      return false;
    const auto &nsData = data_.at(currentNamespace_);
    auto it = nsData.find(key);
    if (it == nsData.end() || it->second.size() != sizeof(T))
      return false;
    std::memcpy(&value, it->second.data(), sizeof(T));
    return true;
  }

  size_t getBytes(const char *key, void *buf, size_t maxLen) const {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty() || !buf)
      return 0;
    const auto &nsData = data_.at(currentNamespace_);
    auto it = nsData.find(key);
    if (it == nsData.end())
      return 0;
    size_t copyLen = std::min(maxLen, it->second.size());
    std::memcpy(buf, it->second.data(), copyLen);
    return copyLen;
  }
  
  size_t getString(const char* key, char* buf, size_t maxLen) const {
    std::lock_guard<std::mutex> lock(mutex_);
    if (currentNamespace_.empty() || !buf || maxLen == 0) return 0;
    const auto& nsData = data_.at(currentNamespace_);
    auto it = nsData.find(key);
    if (it == nsData.end()) return 0;
    size_t copyLen = std::min(maxLen - 1, it->second.size());
    std::memcpy(buf, it->second.data(), copyLen);
    buf[copyLen] = '\0'; // null-terminate
    return copyLen;
}

String getString(const char *key, const char *defaultVal) const {
  std::lock_guard<std::mutex> lock(mutex_);
  if (currentNamespace_.empty())
    return String(defaultVal);
  const auto &nsData = data_.at(currentNamespace_);
  auto it = nsData.find(key);
  return String((it != nsData.end()) ? it->second.c_str() : defaultVal);
}


private:
  mutable std::mutex mutex_;
  std::string currentNamespace_;
  std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
      data_;
};

#endif // PREFERENCES_HPP
