#pragma once

#include "fl/stl/thread.h"
#if FASTLED_USE_THREAD_LOCAL
// IWYU pragma: begin_keep
#include <pthread.h>  // ok include
#include "fl/stl/noexcept.h"
// IWYU pragma: end_keep
#endif

#if FASTLED_USE_THREAD_LOCAL
// Real thread-local implementation using POSIX threading
#else
// Fake thread-local implementation (globally shared data)
#if FASTLED_MULTITHREADED
#warning                                                                       \
    "ThreadLocal is not implemented, using the fake version with globally shared data"
#endif
#endif

namespace fl {

#if FASTLED_USE_THREAD_LOCAL
template <typename T> class ThreadLocalReal;
template <typename T> using ThreadLocal = ThreadLocalReal<T>;
#else
template <typename T> class ThreadLocalFake;
template <typename T> using ThreadLocal = ThreadLocalFake<T>;
#endif

///////////////////// REAL IMPLEMENTATION //////////////////////////////////////

#if FASTLED_USE_THREAD_LOCAL
template <typename T> class ThreadLocalReal {
  public:
    // Default: each thread's object is default-constructed
    ThreadLocalReal() FL_NOEXCEPT : mDefaultValue(), mHasDefault(false) {
        initializeKey();
    }

    // With default: each thread's object is copy-constructed from defaultVal
    template <typename U>
    explicit ThreadLocalReal(const U &defaultVal) : mDefaultValue(defaultVal), mHasDefault(true) {
        initializeKey();
    }

    // Destructor - cleanup pthread key
    ~ThreadLocalReal() FL_NOEXCEPT {
        if (mKeyInitialized) {
            // Manually clean up storage for the current thread.
            // pthread_key_delete does NOT call the cleanup function - it only
            // removes the key. Thread-specific data must be freed manually.
            ThreadStorage* storage = getStorage();
            if (storage) {
                pthread_setspecific(mKey, nullptr);
                delete storage;  // ok bare allocation
            }
            pthread_key_delete(mKey);
        }
    }

    // Copy constructor
    ThreadLocalReal(const ThreadLocalReal& other) FL_NOEXCEPT : mDefaultValue(other.mDefaultValue), mHasDefault(other.mHasDefault) {
        initializeKey();
    }

    // Assignment operator
    ThreadLocalReal& operator=(const ThreadLocalReal& other) FL_NOEXCEPT {
        if (this != &other) {
            mDefaultValue = other.mDefaultValue;
            mHasDefault = other.mHasDefault;
            // Key remains the same for this instance
        }
        return *this;
    }

    // Access the thread-local instance
    T &access() {
        ThreadStorage* storage = getStorage();
        if (!storage) {
            storage = createStorage();
        }
        if (mHasDefault && !storage->initialized) {
            copyValue(storage->value, mDefaultValue);
            storage->initialized = true;
        }
        return storage->value;
    }

    const T &access() const {
        ThreadStorage* storage = getStorage();
        if (!storage) {
            storage = createStorage();
        }
        if (mHasDefault && !storage->initialized) {
            copyValue(storage->value, mDefaultValue);
            storage->initialized = true;
        }
        return storage->value;
    }

    // Set the value for this thread
    void set(const T& value) {
        copyValue(access(), value);
    }

    // Convenience operators
    operator T &() { return access(); }
    operator const T &() const { return access(); }

    ThreadLocalReal &operator=(const T &v) FL_NOEXCEPT {
        set(v);
        return *this;
    }

  private:
    // Helper function to copy values, specialized for arrays
    template<typename U>
    static void copyValue(U& dest, const U& src) {
        dest = src;  // Default behavior for non-array types
    }

    // Specialization for array types
    template<typename U, size_t N>
    static void copyValue(U (&dest)[N], const U (&src)[N]) {
        for (size_t i = 0; i < N; ++i) {
            copyValue(dest[i], src[i]);  // Recursively handle nested arrays
        }
    }

    // Storage for thread-local data
    struct ThreadStorage {
        T value{};
        bool initialized = false;
    };

    // POSIX thread key for this instance
    pthread_key_t mKey;
    bool mKeyInitialized = false;
    T mDefaultValue{};
    bool mHasDefault = false;

    // Initialize the pthread key
    void initializeKey() {
        int result = pthread_key_create(&mKey, cleanupThreadStorage);
        if (result == 0) {
            mKeyInitialized = true;
        } else {
            // Handle error - for now just mark as not initialized
            mKeyInitialized = false;
        }
    }

    // Get thread-specific storage
    ThreadStorage* getStorage() const {
        if (!mKeyInitialized) {
            return nullptr;
        }
        return static_cast<ThreadStorage*>(pthread_getspecific(mKey));
    }

    // Create thread-specific storage
    ThreadStorage* createStorage() const {
        if (!mKeyInitialized) {
            return nullptr;
        }

        ThreadStorage* storage = new ThreadStorage();  // ok bare allocation
        int result = pthread_setspecific(mKey, storage);
        if (result != 0) {
            delete storage;  // ok bare allocation
            return nullptr;
        }
        return storage;
    }

    // Cleanup function called when thread exits
    static void cleanupThreadStorage(void* data) {
        if (data) {
            delete static_cast<ThreadStorage*>(data);  // ok bare allocation
        }
    }
};

#endif // FASTLED_USE_THREAD_LOCAL

///////////////////// FAKE IMPLEMENTATION //////////////////////////////////////

template <typename T> class ThreadLocalFake {
  public:
    // Default: each thread's object is default-constructed
    ThreadLocalFake() FL_NOEXCEPT : mValue() {}

    // With default: each thread's object is copy-constructed from defaultVal
    template <typename U>
    explicit ThreadLocalFake(const U &defaultVal) FL_NOEXCEPT : mValue(defaultVal) {}

    // Access the thread-local instance (not actually thread-local in fake version)
    T &access() FL_NOEXCEPT { return mValue; }
    const T &access() const FL_NOEXCEPT { return mValue; }

    // Set the value (globally shared in fake version)
    void set(const T& value) FL_NOEXCEPT {
        mValue = value;
    }

    // Convenience operators for "ThreadLocal<T> = x;"
    operator T &() FL_NOEXCEPT { return access(); }
    operator const T &() const FL_NOEXCEPT { return access(); }

    ThreadLocalFake &operator=(const T &v) FL_NOEXCEPT {
        set(v);
        return *this;
    }

  private:
    T mValue;
};

} // namespace fl
