#pragma once
#include <cstddef>
#include <cstdint>
#include <new>
#include <type_traits>
#include <utility>

namespace esphome::ratgdo {

void log_multiple_subscribers();
void log_observer_overflow();

// Lightweight type-erased callback (16 bytes on 32-bit).
// For small trivially-copyable callables (like [this], [this, f], or [this, f, id] lambdas),
// stores the callable inline — zero heap allocation.
// Supports up to 3 * sizeof(void*) bytes (12 bytes on 32-bit, 24 on 64-bit).
inline constexpr size_t CALLBACK_STORAGE_SIZE = 3 * sizeof(void*);

template <typename... Ts>
struct Callback {
    using fn_t = void (*)(const void*, Ts...);
    fn_t fn_ { nullptr };
    alignas(void*) uint8_t storage_[CALLBACK_STORAGE_SIZE] { };

    void call(Ts... args) const { this->fn_(this->storage_, args...); }
    explicit operator bool() const { return this->fn_ != nullptr; }

    template <typename F>
    static Callback create(F&& f)
    {
        Callback cb;
        using Decay = std::decay_t<F>;
        static_assert(!std::is_function_v<std::remove_reference_t<F>>,
            "Pass function pointers, not function references");
        static_assert(std::is_trivially_copyable_v<Decay>, "Observable callbacks must be trivially copyable (e.g. [this] lambdas)");
        static_assert(sizeof(Decay) <= CALLBACK_STORAGE_SIZE, "Observable callbacks must fit in storage (capture at most 3 pointers)");
        cb.fn_ = [](const void* storage, Ts... args) {
            alignas(Decay) char buf[sizeof(Decay)];
            __builtin_memcpy(buf, storage, sizeof(Decay));
            (*std::launder(reinterpret_cast<Decay*>(buf)))(args...);
        };
        __builtin_memcpy(cb.storage_, &f, sizeof(Decay));
        return cb;
    }
};

// Primary template for observable with subscribers.
template <typename T, uint8_t MaxObservers>
class observable {
public:
    observable(const T& value)
        : value_(value)
    {
    }

    template <typename U>
    observable& operator=(U value)
    {
        if (value != this->value_) {
            this->value_ = value;
            this->notify();
        }
        return *this;
    }

    T const* operator&() const { return &this->value_; }
    T const& operator*() const { return this->value_; }

    template <typename F>
    void subscribe(F&& observer)
    {
        if (this->count_ >= MaxObservers) {
            log_observer_overflow();
            return;
        }
        this->observers_[this->count_++] = Callback<T>::create(std::forward<F>(observer));
    }

    void notify() const
    {
        for (uint8_t i = 0; i < this->count_; i++) {
            this->observers_[i].call(this->value_);
        }
    }

private:
    T value_;
    Callback<T> observers_[MaxObservers] { };
    uint8_t count_ { 0 };
};

// Specialization for zero subscribers — no array, no count, notify is a no-op.
template <typename T>
class observable<T, 0> {
public:
    observable(const T& value)
        : value_(value)
    {
    }

    template <typename U>
    observable& operator=(U value)
    {
        if (value != this->value_) {
            this->value_ = value;
        }
        return *this;
    }

    T const* operator&() const { return &this->value_; }
    T const& operator*() const { return this->value_; }

    template <typename F>
    void subscribe(F&&)
    {
        log_observer_overflow();
    }

    void notify() const { }

private:
    T value_;
};

template <typename T>
class single_observable {
public:
    single_observable(const T& value)
        : value_(value)
    {
    }

    template <typename U>
    single_observable& operator=(U value)
    {
        if (value != this->value_) {
            this->value_ = value;
            this->notify();
        }
        return *this;
    }

    T const* operator&() const { return &this->value_; }
    T const& operator*() const { return this->value_; }

    template <typename F>
    void subscribe(F&& observer)
    {
        if (this->observer_) {
            log_multiple_subscribers();
        }
        this->observer_ = Callback<T>::create(std::forward<F>(observer));
    }

    void notify() const
    {
        if (this->observer_) {
            this->observer_.call(this->value_);
        }
    }

private:
    T value_;
    Callback<T> observer_ { };
};

} // namespace esphome::ratgdo
