#pragma once

#include "fl/stl/cstddef.h"       // for size_t
#include "fl/stl/int.h"               // for u8
#include "fl/stl/type_traits.h" // for fl::enable_if, fl::is_same, etc.
#include "fl/stl/bit_cast.h"    // for safe type-punning
#include "fl/stl/move.h"          // for move
#include "fl/stl/new.h"          // for placement new operator (must be before namespace fl)  // IWYU pragma: keep
#include "fl/stl/align.h"        // for FL_ALIGN_AS_T macro (GCC 4.8.3 workaround)
#include "fl/stl/compiler_control.h"
#include "fl/stl/noexcept.h"

namespace fl {

// A variant that can hold any of N different types
template <typename... Types>
class FL_ALIGN_AS_T(max_align<Types...>::value) variant {
  public:
    using Tag = u8;
    static constexpr Tag Empty = 0;

    // –– ctors/dtors/assign as before …

    variant() FL_NOEXCEPT : _tag(Empty) {}

    template <typename T, typename = typename fl::enable_if<
                              contains_type<T, Types...>::value>::type>
    variant(const T &value) FL_NOEXCEPT : _tag(Empty) {
        construct<T>(value);
    }

    template <typename T, typename = typename fl::enable_if<
                              contains_type<T, Types...>::value>::type>
    variant(T &&value) FL_NOEXCEPT : _tag(Empty) {
        construct<T>(fl::move(value));
    }

    variant(const variant &other) FL_NOEXCEPT : _tag(Empty) {
        if (!other.empty()) {
            copy_construct_from(other);
        }
    }

    variant(variant &&other) FL_NOEXCEPT : _tag(Empty) {
        if (!other.empty()) {
            move_construct_from(other);
            // After moving, mark other as empty to prevent destructor calls on moved-from objects
            other._tag = Empty;
        }
    }

    ~variant() FL_NOEXCEPT { reset(); }

    variant &operator=(const variant &other) FL_NOEXCEPT {
        if (this != &other) {
            reset();
            if (!other.empty()) {
                copy_construct_from(other);
            }
        }
        return *this;
    }

    variant &operator=(variant &&other) FL_NOEXCEPT {
        if (this != &other) {
            reset();
            if (!other.empty()) {
                move_construct_from(other);
                // After moving, mark other as empty to prevent destructor calls on moved-from objects
                other._tag = Empty;
            }
        }
        return *this;
    }

    template <typename T, typename = typename fl::enable_if<
                              contains_type<T, Types...>::value>::type>
    variant &operator=(const T &value) FL_NOEXCEPT {
        // Copy first to handle self-referential values (e.g. assigning
        // from a reference into this variant's own storage)
        T value_copy(value);
        reset();
        construct<T>(fl::move(value_copy));
        return *this;
    }

    template <typename T, typename = typename fl::enable_if<
                              contains_type<T, Types...>::value>::type>
    variant &operator=(T &&value) FL_NOEXCEPT {
        reset();
        construct<T>(fl::move(value));
        return *this;
    }

    // –– modifiers, observers, ptr/get, etc. unchanged …

    template <typename T, typename... Args>
    typename fl::enable_if<contains_type<T, Types...>::value, T &>::type
    emplace(Args &&...args) FL_NOEXCEPT {
        reset();
        construct<T>(fl::forward<Args>(args)...);
        return *ptr<T>();
    }

    void reset() FL_NOEXCEPT {
        if (!empty()) {
            destroy_current();
            _tag = Empty;
        }
    }

    Tag tag() const FL_NOEXCEPT { return _tag; }
    bool empty() const FL_NOEXCEPT { return _tag == Empty; }

    template <typename T> bool is() const FL_NOEXCEPT {
        return _tag == type_to_tag<T>();
    }

    template <typename T> T *ptr() FL_NOEXCEPT {
        if (!is<T>()) return nullptr;
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        return fl::bit_cast_ptr<T>(&_storage[0]);
    }

    template <typename T> const T *ptr() const FL_NOEXCEPT {
        if (!is<T>()) return nullptr;
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        return fl::bit_cast_ptr<const T>(&_storage[0]);
    }

    /// @brief Get a reference to the stored value of type T
    /// @tparam T The type to retrieve
    /// @return Reference to the stored value
    /// @note Asserts if the variant doesn't contain type T. Use is<T>() to check first.
    /// @warning Will crash if called with wrong type - this is intentional for fast failure
    template <typename T> T &get() FL_NOEXCEPT {
        // Dereference ptr() directly - will crash with null pointer access if wrong type
        // This provides fast failure semantics similar to std::variant
        return *ptr<T>();
    }

    /// @brief Get a const reference to the stored value of type T
    /// @tparam T The type to retrieve  
    /// @return Const reference to the stored value
    /// @note Asserts if the variant doesn't contain type T. Use is<T>() to check first.
    /// @warning Will crash if called with wrong type - this is intentional for fast failure
    template <typename T> const T &get() const FL_NOEXCEPT {
        // Dereference ptr() directly - will crash with null pointer access if wrong type
        // This provides fast failure semantics similar to std::variant
        return *ptr<T>();
    }

    template <typename T> bool equals(const T &other) const FL_NOEXCEPT {
        if (auto p = ptr<T>()) {
            return *p == other;
        }
        return false;
    }

    // –– visitor using O(1) function‐pointer table
    template <typename Visitor> void visit(Visitor &visitor) FL_NOEXCEPT {
        if (_tag == Empty)
            return;

        // Fn is "a pointer to function taking (void* storage, Visitor&)"
        using Fn = void (*)(void *, Visitor &);

        // Build a constexpr array of one thunk per type in Types...
        // Each thunk casts the storage back to the right T* and calls
        // visitor.accept
        static constexpr Fn table[] = {
            &variant::template visit_fn<Types, Visitor>...};

        // _tag is 1-based, so dispatch in O(1) via one indirect call:
        // Check bounds to prevent out-of-bounds access
        size_t index = _tag - 1;
        if (index < sizeof...(Types)) {
            table[index](&_storage, visitor);
        }
    }

    template <typename Visitor> void visit(Visitor &visitor) const FL_NOEXCEPT {
        if (_tag == Empty)
            return;

        // Fn is "a pointer to function taking (const void* storage, Visitor&)"
        using Fn = void (*)(const void *, Visitor &);

        // Build a constexpr array of one thunk per type in Types...
        static constexpr Fn table[] = {
            &variant::template visit_fn_const<Types, Visitor>...};

        // _tag is 1-based, so dispatch in O(1) via one indirect call:
        // Check bounds to prevent out-of-bounds access
        size_t index = _tag - 1;
        if (index < sizeof...(Types)) {
            table[index](&_storage, visitor);
        }
    }

  private:
    // –– helper for the visit table
    template <typename T, typename Visitor>
    static void visit_fn(void *storage, Visitor &v) FL_NOEXCEPT {
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        T* typed_ptr = fl::bit_cast_ptr<T>(storage);
        v.accept(*typed_ptr);
    }

    template <typename T, typename Visitor>
    static void visit_fn_const(const void *storage, Visitor &v) FL_NOEXCEPT {
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        const T* typed_ptr = fl::bit_cast_ptr<const T>(storage);
        v.accept(*typed_ptr);
    }

    // –– destroy via table
    void destroy_current() FL_NOEXCEPT {
        using Fn = void (*)(void *);
        FL_DISABLE_WARNING_PUSH
        FL_DISABLE_WARNING(array-bounds)
        static constexpr Fn table[] = {&variant::template destroy_fn<Types>...};
        if (_tag != Empty) {
            table[_tag - 1](&_storage);
        }
        FL_DISABLE_WARNING_POP
    }

    template <typename T> static void destroy_fn(void *storage) FL_NOEXCEPT {
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        T* typed_ptr = fl::bit_cast_ptr<T>(storage);
        typed_ptr->~T();
    }

    // –– copy‐construct via table
    void copy_construct_from(const variant &other) FL_NOEXCEPT {
        using Fn = void (*)(void *, const variant &);
        FL_DISABLE_WARNING_PUSH
        FL_DISABLE_WARNING(array-bounds)
        static constexpr Fn table[] = {&variant::template copy_fn<Types>...};
        table[other._tag - 1](&_storage, other);
        FL_DISABLE_WARNING_POP
        _tag = other._tag;
    }

    template <typename T>
    static void copy_fn(void *storage, const variant &other) FL_NOEXCEPT {
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        const T* source_ptr = fl::bit_cast_ptr<const T>(&other._storage[0]);
        new (storage) T(*source_ptr);
    }

    // –– move‐construct via table
    void move_construct_from(variant &other) FL_NOEXCEPT {
        using Fn = void (*)(void *, variant &);
        FL_DISABLE_WARNING_PUSH
        FL_DISABLE_WARNING(array-bounds)
        static constexpr Fn table[] = {&variant::template move_fn<Types>...};
        table[other._tag - 1](&_storage, other);
        FL_DISABLE_WARNING_POP
        _tag = other._tag;
        other.reset();
    }

    template <typename T> static void move_fn(void *storage, variant &other) FL_NOEXCEPT {
        // Use bit_cast_ptr for safe type-punning on properly aligned storage
        // The storage is guaranteed to be properly aligned by alignas(max_align<Types...>::value)
        T* source_ptr = fl::bit_cast_ptr<T>(&other._storage[0]);
        new (storage) T(fl::move(*source_ptr));
    }

    // –– everything below here (type_traits, construct<T>, type_to_tag,
    // storage)
    //    stays exactly as you wrote it:

    // … max_size, max_align, contains_type, type_to_tag_impl, etc. …

    // Helper to map a type to its tag value
    template <typename T> static constexpr Tag type_to_tag() FL_NOEXCEPT {
        return type_to_tag_impl<T, Types...>::value;
    }

    // Implementation details for type_to_tag
    template <typename T, typename... Ts> struct type_to_tag_impl;

    template <typename T> struct type_to_tag_impl<T> {
        static constexpr Tag value = 0; // Not found
    };

    template <typename T, typename U, typename... Rest>
    struct type_to_tag_impl<T, U, Rest...> {
        static constexpr Tag value =
            fl::is_same<T, U>::value
                ? 1
                : (type_to_tag_impl<T, Rest...>::value == 0
                       ? 0
                       : type_to_tag_impl<T, Rest...>::value + 1);
    };

    template <typename T, typename... Args> void construct(Args &&...args) FL_NOEXCEPT {
        new (&_storage) T(fl::forward<Args>(args)...);
        _tag = type_to_tag<T>();
    }

    FL_ALIGN_AS_T(max_align<Types...>::value) char _storage[max_size<Types...>::value];

    Tag _tag;
};

} // namespace fl
