#ifndef ENTT_CORE_TYPE_INFO_HPP
#define ENTT_CORE_TYPE_INFO_HPP

#include <compare>
#include "../config/config.h"
#include "../stl/string_view.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "fwd.hpp"
#include "hashed_string.hpp"

namespace entt {

/*! @cond ENTT_INTERNAL */
namespace internal {

struct ENTT_API type_index final {
    [[nodiscard]] static id_type next() noexcept {
        static ENTT_MAYBE_ATOMIC(id_type) value{};
        return value++;
    }
};

template<typename Type>
[[nodiscard]] constexpr const char *pretty_function() noexcept {
#if defined ENTT_PRETTY_FUNCTION
    return static_cast<const char *>(ENTT_PRETTY_FUNCTION);
#else
    return "";
#endif
}

template<typename Type>
[[nodiscard]] constexpr auto stripped_type_name() noexcept {
#if defined ENTT_PRETTY_FUNCTION
    const stl::string_view full_name{pretty_function<Type>()};
    auto first = full_name.find_first_not_of(' ', full_name.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1);
    auto value = full_name.substr(first, full_name.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
    return value;
#else
    return stl::string_view{};
#endif
}

template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] ENTT_CONSTEVAL stl::string_view type_name(int) noexcept {
    constexpr auto value = stripped_type_name<Type>();
    return value;
}

template<typename Type>
[[nodiscard]] stl::string_view type_name(char) noexcept {
    static const auto value = stripped_type_name<Type>();
    return value;
}

template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] ENTT_CONSTEVAL id_type type_hash(int) noexcept {
    constexpr auto stripped = stripped_type_name<Type>();
    constexpr auto value = hashed_string::value(stripped.data(), stripped.size());
    return value;
}

template<typename Type>
[[nodiscard]] id_type type_hash(char) noexcept {
    static const auto value = [](const auto stripped) {
        return hashed_string::value(stripped.data(), stripped.size());
    }(stripped_type_name<Type>());
    return value;
}

} // namespace internal
/*! @endcond */

/**
 * @brief Type sequential identifier.
 * @tparam Type Type for which to generate a sequential identifier.
 */
template<typename Type>
struct ENTT_API type_index final {
    /**
     * @brief Returns the sequential identifier of a given type.
     * @return The sequential identifier of a given type.
     */
    [[nodiscard]] static id_type value() noexcept {
        static const id_type value = internal::type_index::next();
        return value;
    }

    /*! @copydoc value */
    [[nodiscard]] constexpr operator id_type() const noexcept {
        return value();
    }
};

/**
 * @brief Type hash.
 * @tparam Type Type for which to generate a hash value.
 */
template<typename Type>
struct type_hash final {
    /**
     * @brief Returns the numeric representation of a given type.
     * @return The numeric representation of the given type.
     */
#if defined ENTT_PRETTY_FUNCTION
    [[nodiscard]] static constexpr id_type value() noexcept {
        return internal::type_hash<Type>(0);
#else
    [[nodiscard]] static constexpr id_type value() noexcept {
        return type_index<Type>::value();
#endif
    }

    /*! @copydoc value */
    [[nodiscard]] constexpr operator id_type() const noexcept {
        return value();
    }
};

/**
 * @brief Type name.
 * @tparam Type Type for which to generate a name.
 */
template<typename Type>
struct type_name final {
    /**
     * @brief Returns the name of a given type.
     * @return The name of the given type.
     */
    [[nodiscard]] static constexpr stl::string_view value() noexcept {
        return internal::type_name<Type>(0);
    }

    /*! @copydoc value */
    [[nodiscard]] constexpr operator stl::string_view() const noexcept {
        return value();
    }
};

/*! @brief Implementation specific information about a type. */
struct type_info final {
    /**
     * @brief Constructs a type info object for a given type.
     * @tparam Type Type for which to construct a type info object.
     */
    template<typename Type>
    // NOLINTBEGIN(modernize-use-transparent-functors)
    constexpr type_info(stl::in_place_type_t<Type>) noexcept
        : seq{type_index<stl::remove_cvref_t<Type>>::value()},
          identifier{type_hash<stl::remove_cvref_t<Type>>::value()},
          alias{type_name<stl::remove_cvref_t<Type>>::value()} {}
    // NOLINTEND(modernize-use-transparent-functors)

    /**
     * @brief Type index.
     * @return Type index.
     */
    [[nodiscard]] constexpr id_type index() const noexcept {
        return seq;
    }

    /**
     * @brief Type hash.
     * @return Type hash.
     */
    [[nodiscard]] constexpr id_type hash() const noexcept {
        return identifier;
    }

    /**
     * @brief Type name.
     * @return Type name.
     */
    [[nodiscard]] constexpr stl::string_view name() const noexcept {
        return alias;
    }

    /**
     * @brief Compares two type info objects.
     * @param other A type info object.
     * @return True if the two type info objects are identical, false otherwise.
     */
    [[nodiscard]] constexpr bool operator==(const type_info &other) const noexcept {
        return identifier == other.identifier;
    }

    /**
     * @brief Lexicographically compares two type info objects.
     * @param other A type info object.
     * @return The relative order between the two type info objects.
     */
    [[nodiscard]] constexpr auto operator<=>(const type_info &other) const noexcept {
        return seq <=> other.seq;
    }

private:
    id_type seq;
    id_type identifier;
    stl::string_view alias;
};

/**
 * @brief Returns the type info object associated to a given type.
 *
 * The returned element refers to an object with static storage duration.<br/>
 * The type doesn't need to be a complete type. If the type is a reference, the
 * result refers to the referenced type. In all cases, top-level cv-qualifiers
 * are ignored.
 *
 * @tparam Type Type for which to generate a type info object.
 * @return A reference to a properly initialized type info object.
 */
template<typename Type>
[[nodiscard]] const type_info &type_id() noexcept {
    if constexpr(stl::is_same_v<Type, stl::remove_cvref_t<Type>>) {
        static const type_info instance{stl::in_place_type<Type>};
        return instance;
    } else {
        return type_id<stl::remove_cvref_t<Type>>();
    }
}

/*! @copydoc type_id */
template<typename Type>
[[nodiscard]] const type_info &type_id(const Type &) noexcept {
    return type_id<stl::remove_cvref_t<Type>>();
}

} // namespace entt

#endif
