#pragma once
#include "fl/stl/shared_ptr.h"  // For shared_ptr
#include "fl/stl/type_traits.h"
#include "fl/stl/compiler_control.h"
#include "fl/stl/variant.h"
#include "fl/stl/cstring.h"
#include "fl/stl/new.h"   // for placement new operator  // IWYU pragma: keep
#include "fl/stl/align.h"
#include "fl/stl/pair.h"
#include "fl/stl/vector.h"
#include "fl/stl/algorithm.h"  // for fl::sort
#include "fl/stl/noexcept.h"
#include "fl/stl/static_assert.h"
#include "fl/system/sketch_macros.h"  // for FL_PLATFORM_HAS_LARGE_MEMORY

// Low-memory targets (FL_PLATFORM_HAS_LARGE_MEMORY == 0) collapse the fl::function
// callable variant from 5 alternatives to 2 (free fn + inlined lambda). The dropped
// alternatives -- shared_ptr<CallableBase> heap fallback and NonConst/Const member
// callables -- are dead code on Low-memory targets (audited 2026-06-15: only
// tests/fl/stl/functional.cpp constructs from member functions; no production code
// path on LPC8xx / AVR / similar targets). Saves ~3 KB of per-signature codegen tax
// from per-alternative copy/move/destroy variant clones. See FastLED #3077.
#if FL_PLATFORM_HAS_LARGE_MEMORY
  #define FL_FUNCTION_FULL_VARIANT 1
#else
  #define FL_FUNCTION_FULL_VARIANT 0
#endif

#ifndef FASTLED_INLINE_LAMBDA_SIZE
  // Unified 64 B SBO across LargeMemory and Low-memory builds. The original
  // #3084 fix bumped this to 96 B on Low-memory to absorb UIButton::onClicked's
  // wrapping closure (which captures a 72 B fl::function<void()>), but the
  // bump regressed the base LowMemory build by ~3 KB because every
  // fl::function instance's variant storage / copy / move / destroy paths
  // grew. The no-op heap-fallback branch (see construct_lambda_or_functor
  // false_type below on Low-memory) lets large-lambda paths compile without
  // a static_assert, so SBO=64 stays safe -- unreached paths get linker-DCE'd.
  // See FastLED #3079.
  #define FASTLED_INLINE_LAMBDA_SIZE 64
#endif

FL_DISABLE_WARNING_PUSH
FL_DISABLE_WARNING(float-equal)

namespace fl {

//----------------------------------------------------------------------------
// is_function_pointer trait - detects function pointers like R(*)(Args...)
//----------------------------------------------------------------------------
template <typename T> struct is_function_pointer {
    static constexpr bool value = false;
};

template <typename R, typename... Args> 
struct is_function_pointer<R(*)(Args...)> {
    static constexpr bool value = true;
};

//----------------------------------------------------------------------------
// More or less a drop in replacement for std::function
// function<R(Args...)>: type‐erasing "std::function" replacement
// Supports free functions, lambdas/functors, member functions (const &
// non‑const)
// 
// NEW: Uses inline storage for member functions, free functions, and small
// lambdas/functors. Only large lambdas/functors use heap allocation.
//----------------------------------------------------------------------------
template <typename> class function;

template <typename R, typename... Args>
class FL_ALIGN function<R(Args...)> {
private:
#if FL_FUNCTION_FULL_VARIANT
    struct CallableBase {
        virtual R invoke(Args... args) FL_NOEXCEPT = 0;
        virtual ~CallableBase() FL_NOEXCEPT = default;
    };

    template <typename F>
    struct Callable : CallableBase {
        F f;
        Callable(F fn) FL_NOEXCEPT : f(fn) {}
        R invoke(Args... args) FL_NOEXCEPT override { return f(args...); }
    };
#endif  // FL_FUNCTION_FULL_VARIANT

    // Type-erased free function callable - stored inline!
    struct FreeFunctionCallable {
        R (*func_ptr)(Args...);
        
        FreeFunctionCallable(R (*fp)(Args...)) FL_NOEXCEPT : func_ptr(fp) {}
        
        R invoke(Args... args) const FL_NOEXCEPT {
            return func_ptr(args...);
        }
    };

    // Type-erased small lambda/functor callable - stored inline!
    // Size limit for inline storage - configurable via preprocessor define

    static constexpr fl::size kInlineLambdaSize = FASTLED_INLINE_LAMBDA_SIZE;
    
    struct InlinedLambda {
        // Storage for the lambda/functor object
        // Use aligned storage to ensure proper alignment for any type
        FL_ALIGN_MAX char bytes[kInlineLambdaSize];
        
        // Type-erased invoker and destructor function pointers
        R (*invoker)(const InlinedLambda& storage, Args... args);
        void (*destructor)(InlinedLambda& storage);
        
        template <typename Function>
        InlinedLambda(Function f) FL_NOEXCEPT {
            FL_STATIC_ASSERT(sizeof(Function) <= kInlineLambdaSize,
                         "Lambda/functor too large for inline storage");
            FL_STATIC_ASSERT(alignof(Function) <= alignof(max_align_t),
                         "Lambda/functor requires stricter alignment than storage provides");
            
            // Initialize the entire storage to zero to avoid copying uninitialized memory
            fl::memset(bytes, 0, kInlineLambdaSize);
            
            // Construct the lambda/functor in-place
            new (bytes) Function(fl::move(f));
            
            // Set up type-erased function pointers
            invoker = &invoke_lambda<Function>;
            destructor = &destroy_lambda<Function>;
        }
        
        // Copy constructor
        InlinedLambda(const InlinedLambda& other) FL_NOEXCEPT
            : invoker(other.invoker), destructor(other.destructor) {
            // This is tricky - we need to copy the stored object
            // For now, we'll use memcopy (works for trivially copyable types)
            fl::memcpy(bytes, other.bytes, kInlineLambdaSize);
        }
        
        // Move constructor
        InlinedLambda(InlinedLambda&& other) FL_NOEXCEPT
            : invoker(other.invoker), destructor(other.destructor) {
            fl::memcpy(bytes, other.bytes, kInlineLambdaSize);
            // Reset the other object to prevent double destruction
            other.destructor = nullptr;
        }
        
        ~InlinedLambda() FL_NOEXCEPT {
            if (destructor) {
                destructor(*this);
            }
        }
        
        template <typename FUNCTOR>
        static R invoke_lambda(const InlinedLambda& storage, Args... args) FL_NOEXCEPT {
            // Use placement new to safely access the stored lambda
            FL_ALIGN_AS(FUNCTOR) char temp_storage[sizeof(FUNCTOR)];
            // Copy the lambda from storage
            fl::memcpy(temp_storage, storage.bytes, sizeof(FUNCTOR));
            // Get a properly typed pointer to the copied lambda (non-const for mutable lambdas)
            FUNCTOR* f = static_cast<FUNCTOR*>(static_cast<void*>(temp_storage));
            // Invoke the lambda
            return (*f)(args...);
        }
        
        template <typename FUNCTOR>
        static void destroy_lambda(InlinedLambda& storage) FL_NOEXCEPT {
            // For destruction, we need to call the destructor on the actual object
            // that was constructed with placement new in storage.bytes
            // We use the standard library approach: create a properly typed pointer
            // using placement new, then call the destructor through that pointer
            
            // This is the standard-compliant way to get a properly typed pointer
            // to an object that was constructed with placement new
            FUNCTOR* obj_ptr = static_cast<FUNCTOR*>(static_cast<void*>(storage.bytes));
            obj_ptr->~FUNCTOR();
        }
        
        R invoke(Args... args) const FL_NOEXCEPT {
            return invoker(*this, args...);
        }
    };

#if FL_FUNCTION_FULL_VARIANT
    // Type-erased member function callable base
    struct MemberCallableBase {
        virtual R invoke(Args... args) const FL_NOEXCEPT = 0;
        virtual ~MemberCallableBase() FL_NOEXCEPT = default;
    };

    // Type-erased non-const member function callable
    struct NonConstMemberCallable : MemberCallableBase {
        void* obj;
        // Union to store member function pointer as raw bytes
        union MemberFuncStorage {
            char bytes[sizeof(R (NonConstMemberCallable::*)(Args...))];
            // Ensure proper alignment
            void* alignment_dummy;
        } member_func_storage;
        
        // Type-erased invoker function - set at construction time
        R (*invoker)(void* obj, const MemberFuncStorage& mfp, Args... args);
        
        template <typename C>
        NonConstMemberCallable(C* o, R (C::*mf)(Args...)) FL_NOEXCEPT : obj(o) {
            // Store the member function pointer as raw bytes
            FL_STATIC_ASSERT(sizeof(mf) <= sizeof(member_func_storage),
                         "Member function pointer too large");
            fl::memcpy(member_func_storage.bytes, &mf, sizeof(mf));
            // Set the invoker to a function that knows how to cast back and call
            invoker = &invoke_nonconst_member<C>;
        }
        
        template <typename C>
        static R invoke_nonconst_member(void* obj, const MemberFuncStorage& mfp, Args... args) FL_NOEXCEPT {
            C* typed_obj = static_cast<C*>(obj);
            R (C::*typed_mf)(Args...);
            fl::memcpy(&typed_mf, mfp.bytes, sizeof(typed_mf));
            return (typed_obj->*typed_mf)(args...);
        }
        
        R invoke(Args... args) const FL_NOEXCEPT override {
            return invoker(obj, member_func_storage, args...);
        }
    };

    // Type-erased const member function callable  
    struct ConstMemberCallable : MemberCallableBase {
        const void* obj;
        // Union to store member function pointer as raw bytes
        union MemberFuncStorage {
            char bytes[sizeof(R (ConstMemberCallable::*)(Args...) const)];
            // Ensure proper alignment
            void* alignment_dummy;
        } member_func_storage;
        
        // Type-erased invoker function - set at construction time
        R (*invoker)(const void* obj, const MemberFuncStorage& mfp, Args... args);
        
        template <typename C>
        ConstMemberCallable(const C* o, R (C::*mf)(Args...) const) FL_NOEXCEPT : obj(o) {
            // Store the member function pointer as raw bytes
            FL_STATIC_ASSERT(sizeof(mf) <= sizeof(member_func_storage),
                         "Member function pointer too large");
            fl::memcpy(member_func_storage.bytes, &mf, sizeof(mf));
            // Set the invoker to a function that knows how to cast back and call
            invoker = &invoke_const_member<C>;
        }
        
        template <typename C>
        static R invoke_const_member(const void* obj, const MemberFuncStorage& mfp, Args... args) FL_NOEXCEPT {
            const C* typed_obj = static_cast<const C*>(obj);
            R (C::*typed_mf)(Args...) const;
            fl::memcpy(&typed_mf, mfp.bytes, sizeof(typed_mf));
            return (typed_obj->*typed_mf)(args...);
        }
        
        R invoke(Args... args) const FL_NOEXCEPT override {
            return invoker(obj, member_func_storage, args...);
        }
    };
#endif  // FL_FUNCTION_FULL_VARIANT

    // variant to store any of our callable types inline.
    // Low-memory targets drop the heap-fallback CallableBase and the two
    // MemberCallable alternatives (audited dead on those targets) to save
    // ~3 KB of per-signature variant codegen tax. See FastLED #3077.
#if FL_FUNCTION_FULL_VARIANT
    using Storage = variant<fl::shared_ptr<CallableBase>, FreeFunctionCallable, InlinedLambda, NonConstMemberCallable, ConstMemberCallable>;
#else
    using Storage = variant<FreeFunctionCallable, InlinedLambda>;
#endif
    Storage mStorage;

    // Helper function to handle default return value for void and non-void types
    template<typename ReturnType>
    typename enable_if<!is_void<ReturnType>::value, ReturnType>::type
    default_return_helper() const FL_NOEXCEPT {
        return ReturnType{};
    }
    
    template<typename ReturnType>
    typename enable_if<is_void<ReturnType>::value, ReturnType>::type
    default_return_helper() const FL_NOEXCEPT {
        return;
    }

public:
    function() FL_NOEXCEPT = default;
    
    // Copy constructor - properly handle variant alignment
    function(const function& other) FL_NOEXCEPT : mStorage(other.mStorage) {}
    
    // Move constructor - properly handle variant alignment  
    function(function&& other) FL_NOEXCEPT : mStorage(fl::move(other.mStorage)) {}
    
    // Copy assignment
    function& operator=(const function& other) FL_NOEXCEPT {
        if (this != &other) {
            mStorage = other.mStorage;
        }
        return *this;
    }
    
    // Move assignment
    function& operator=(function&& other) FL_NOEXCEPT {
        if (this != &other) {
            mStorage = fl::move(other.mStorage);
        }
        return *this;
    }
    
    // 1) Free function constructor - stored inline!
    function(R (*fp)(Args...)) FL_NOEXCEPT {
        mStorage = FreeFunctionCallable(fp);
    }
    
    // 2) Lambda/functor constructor - inline if small, heap if large
    template <typename F, typename = enable_if_t<!is_member_function_pointer<F>::value && !is_function_pointer<F>::value>>
    function(F f) FL_NOEXCEPT {
        // Use template specialization instead of if constexpr for C++14 compatibility
        construct_lambda_or_functor(fl::move(f), typename conditional<sizeof(F) <= kInlineLambdaSize, true_type, false_type>::type{});
    }
    
#if FL_FUNCTION_FULL_VARIANT
    // 3) non‑const member function - stored inline!
    template <typename C>
    function(R (C::*mf)(Args...), C* obj) FL_NOEXCEPT {
        mStorage = NonConstMemberCallable(obj, mf);
    }

    // 4) const member function - stored inline!
    template <typename C>
    function(R (C::*mf)(Args...) const, const C* obj) FL_NOEXCEPT {
        mStorage = ConstMemberCallable(obj, mf);
    }
#endif  // FL_FUNCTION_FULL_VARIANT

    R operator()(Args... args) const FL_NOEXCEPT {
        // Direct dispatch using type checking - efficient and simple
#if FL_FUNCTION_FULL_VARIANT
        if (auto* heap_callable = mStorage.template ptr<fl::shared_ptr<CallableBase>>()) {
            return (*heap_callable)->invoke(args...);
        } else if (auto* free_func = mStorage.template ptr<FreeFunctionCallable>()) {
#else
        if (auto* free_func = mStorage.template ptr<FreeFunctionCallable>()) {
#endif
            return free_func->invoke(args...);
        } else if (auto* inlined_lambda = mStorage.template ptr<InlinedLambda>()) {
            return inlined_lambda->invoke(args...);
#if FL_FUNCTION_FULL_VARIANT
        } else if (auto* nonconst_member = mStorage.template ptr<NonConstMemberCallable>()) {
            return nonconst_member->invoke(args...);
        } else if (auto* const_member = mStorage.template ptr<ConstMemberCallable>()) {
            return const_member->invoke(args...);
#endif
        }
        // This should never happen if the function is properly constructed
        return default_return_helper<R>();
    }
    
    explicit operator bool() const FL_NOEXCEPT {
        return !mStorage.empty();
    }
    
    void clear() FL_NOEXCEPT {
        mStorage = Storage{};  // Reset to empty variant
    }
    
    bool operator==(const function& o) const FL_NOEXCEPT {
        // For simplicity, just check if both are empty or both are non-empty
        // Full equality would require more complex comparison logic
        return mStorage.empty() == o.mStorage.empty();
    }
    
    bool operator!=(const function& o) const FL_NOEXCEPT {
        return !(*this == o);
    }

private:
    // Helper for small lambdas/functors - inline storage
    template <typename F>
    void construct_lambda_or_functor(F f, true_type /* small */) FL_NOEXCEPT {
        mStorage = InlinedLambda(fl::move(f));
    }

#if FL_FUNCTION_FULL_VARIANT
    // Helper for large lambdas/functors - heap storage
    template <typename F>
    void construct_lambda_or_functor(F f, false_type /* large */) FL_NOEXCEPT {
        mStorage = fl::shared_ptr<CallableBase>(fl::make_shared<Callable<F>>(fl::move(f)));
    }
#else
    // Low-memory targets: heap fallback dropped per FastLED #3077 to save flash.
    // Large lambdas (>kInlineLambdaSize) compile but leave the storage empty;
    // operator() returns the default-constructed R. This degrades gracefully
    // for link-dead code paths (e.g. UIButton::onClicked which the LPC845
    // LowMemory build never reaches), and the linker DCE's the unreached
    // instantiations. Code paths reachable at runtime should pre-validate that
    // their lambdas fit in kInlineLambdaSize.
    template <typename F>
    void construct_lambda_or_functor(F /*f*/, false_type /* large */) FL_NOEXCEPT {
        mStorage = Storage{};
    }
#endif
};

//----------------------------------------------------------------------------
// function_list: Container for managing multiple callbacks with add/remove
//----------------------------------------------------------------------------

// Primary template declaration with static assertion for invalid usage
// Only specializations for function signatures (e.g., function_list<void(Args...)>) are valid
template <typename T>
class function_list {
    FL_STATIC_ASSERT(fl::is_same<T, void>::value && !fl::is_same<T, void>::value,
                  "function_list requires a void returning function signature.");
};

// Partial specialization for function signature syntax: function_list<void(Args...)>
// Supports: function_list<void()>, function_list<void(float)>, function_list<void(u8, float, float)>, etc.
template <typename... Args>
class function_list<void(Args...)> {
  private:
    using FunctionType = function<void(Args...)>;

    struct FunctionEntry {
        int id;
        int priority;
        FunctionType fn;

        FunctionEntry() FL_NOEXCEPT : id(0), priority(0), fn() {}
        FunctionEntry(int idParam, int priorityParam, FunctionType fnParam) FL_NOEXCEPT
            : id(idParam), priority(priorityParam), fn(fnParam) {}
    };

    using FunctionVector = fl::vector<FunctionEntry>;

    FunctionVector mFunctions;
    int mIdCounter = 0;

    bool mNeedsCompact = false;  // True when functions have been cleared during invocation

  public:
    function_list() FL_NOEXCEPT : mFunctions(), mIdCounter(0), mNeedsCompact(false) {}
    function_list(const function_list& other) FL_NOEXCEPT
        : mFunctions(other.mFunctions), mIdCounter(other.mIdCounter), mNeedsCompact(other.mNeedsCompact) {}
    function_list(function_list&& other) FL_NOEXCEPT
        : mFunctions(fl::move(other.mFunctions)), mIdCounter(other.mIdCounter), mNeedsCompact(other.mNeedsCompact) {}
    function_list& operator=(const function_list& other) FL_NOEXCEPT {
        if (this != &other) {
            mFunctions = other.mFunctions;
            mIdCounter = other.mIdCounter;
            mNeedsCompact = other.mNeedsCompact;
        }
        return *this;
    }
    function_list& operator=(function_list&& other) FL_NOEXCEPT {
        if (this != &other) {
            mFunctions = fl::move(other.mFunctions);
            mIdCounter = other.mIdCounter;
            mNeedsCompact = other.mNeedsCompact;
        }
        return *this;
    }
    ~function_list() = default;

    int add(function<void(Args...)> fn, int priority = 0) FL_NOEXCEPT {
        int id = mIdCounter++;
        mFunctions.push_back(FunctionEntry(id, priority, fn));
        return id;
    }

    void remove(int id) FL_NOEXCEPT {
        // During invocation: clear the function for deferred removal
        for (size_t i = 0; i < mFunctions.size(); ++i) {
            if (mFunctions[i].id == id) {
                mFunctions[i].fn.clear();
                mNeedsCompact = true;
            }
        }
    }

    void clear() FL_NOEXCEPT {
        mFunctions.clear();
    }

    // Compact the storage by removing invalid (cleared) callbacks
    // Used internally after invocation if removals occurred
    void compact() FL_NOEXCEPT {
        if (!mNeedsCompact) return;
        size_t write_pos = 0;
        for (size_t read_pos = 0; read_pos < mFunctions.size(); ++read_pos) {
            if (mFunctions[read_pos].fn) {  // Check if function is still valid
                if (write_pos != read_pos) {
                    mFunctions[write_pos] = mFunctions[read_pos];
                }
                write_pos++;
            }
        }
        mFunctions.resize(write_pos);
        mNeedsCompact = false;
    }

    // Size information - counts only valid (non-cleared) functions
    fl::size size() const FL_NOEXCEPT {
        fl::size count = 0;
        for (const auto& entry : mFunctions) {
            if (entry.fn) {
                ++count;
            }
        }
        return count;
    }
    bool empty() const FL_NOEXCEPT { return size() == 0; }

    // Boolean conversion for if (callback) checks
    explicit operator bool() const FL_NOEXCEPT { return !empty(); }

    void invoke(Args... args) FL_NOEXCEPT {
        if (mFunctions.empty()) return;
        // Compact the storage by removing invalid (cleared) callbacks
        compact();
        // Save size at start - newly added callbacks won't execute until next call
        const size_t invoke_size = mFunctions.size();
        // Early return if no callbacks
        if (invoke_size == 0) {
            return;
        }

        // Collect unique priorities into a small vector (usually very few unique values)
        fl::vector_inlined<int, 16> priorities;
        for (size_t i = 0; i < invoke_size; ++i) {
            if (!mFunctions[i].fn) continue;  // Skip already-cleared functions

            int p = mFunctions[i].priority;
            // Only add if not already present
            if (priorities.find(p) == priorities.end()) {
                priorities.push_back(p);
            }
        }

        // Sort priorities (higher priority first) - cheap since there are usually very few unique priorities
        fl::sort(priorities.begin(), priorities.end(), [](int a, int b) FL_NOEXCEPT { return a > b; });

        // Iterate through priorities (highest first), then through functions matching each priority
        for (size_t p_idx = 0; p_idx < priorities.size(); ++p_idx) {
            int current_priority = priorities[p_idx];
            for (size_t i = 0; i < invoke_size; ++i) {
                if (mFunctions.empty()) {
                    return;  // Clear happened.
                }
                if (mFunctions[i].fn && mFunctions[i].priority == current_priority) {
                    mFunctions[i].fn(args...);
                }
            }
        }
        compact();
    }

    // Call operator - syntactic sugar for invoke()
    void operator()(Args... args) FL_NOEXCEPT {
        invoke(args...);
    }
};

// Partial specialization for non-void return types: function_list<R(Args...)> where R != void
// Triggers a compile-time error when attempting to use non-void return types
template <typename R, typename... Args>
class function_list<R(Args...)> {
    FL_STATIC_ASSERT(fl::is_same<R, void>::value,
                  "function_list only supports void return type. "
                  "Use function_list<void(Args...)> instead of function_list<ReturnType(Args...)>.");
};

} // namespace fl

FL_DISABLE_WARNING_POP
