#pragma once

#include <stdx/compiler.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/utility.hpp>

#include <boost/mp11/algorithm.hpp>

namespace logging {
template <auto Query, auto Value> struct prop {
    [[nodiscard]] CONSTEVAL static auto query(decltype(Query)) noexcept {
        return Value;
    }
};

namespace detail {
template <typename Q, typename Env>
concept valid_query_for = requires { Env::query(Q{}); };

template <typename Q, typename... Envs>
concept valid_query_over = (... or valid_query_for<Q, Envs>);

template <typename Q> struct has_query {
    template <typename Env>
    using fn = std::bool_constant<valid_query_for<Q, Env>>;
};
} // namespace detail

template <typename... Envs> struct env {
    template <detail::valid_query_over<Envs...> Q>
    CONSTEVAL static auto query(Q) noexcept {
        using I = boost::mp11::mp_find_if_q<boost::mp11::mp_list<Envs...>,
                                            detail::has_query<Q>>;
        using E = boost::mp11::mp_at<boost::mp11::mp_list<Envs...>, I>;
        return Q{}(E{});
    }
};

namespace detail {
template <typename T> struct autowrap {
    // NOLINTNEXTLINE(google-explicit-constructor)
    CONSTEVAL autowrap(T t) : value(t) {}
    T value;
};

// NOLINTNEXTLINE(modernize-avoid-c-arrays)
template <std::size_t N> using str_lit_t = char const (&)[N];

template <std::size_t N> struct autowrap<str_lit_t<N>> {
    // NOLINTNEXTLINE(google-explicit-constructor)
    CONSTEVAL autowrap(str_lit_t<N> str) : value(str) {}
    stdx::ct_string<N> value;
};

template <typename T> autowrap(T) -> autowrap<T>;
template <std::size_t N> autowrap(str_lit_t<N>) -> autowrap<str_lit_t<N>>;

template <auto V> struct wrap {
    constexpr static auto value = V;
};

template <typename> struct for_each_pair;
template <std::size_t... Is> struct for_each_pair<std::index_sequence<Is...>> {
    template <auto... Args>
    using type = env<
        prop<boost::mp11::mp_at_c<boost::mp11::mp_list<wrap<Args>...>,
                                  2 * Is>::value.value,
             stdx::ct<boost::mp11::mp_at_c<boost::mp11::mp_list<wrap<Args>...>,
                                           2 * Is + 1>::value.value>()>...>;
};
} // namespace detail

template <typename Env = env<>>
constexpr auto make_env = []<detail::autowrap... Args> {
    using new_env_t = typename detail::for_each_pair<
        std::make_index_sequence<sizeof...(Args) / 2>>::template type<Args...>;
    return boost::mp11::mp_append<new_env_t, Env>{};
};

template <detail::autowrap... Args>
using make_env_t = decltype(make_env<>.template operator()<Args...>());
} // namespace logging

using cib_log_env_t = logging::env<>;

// NOLINTBEGIN(cppcoreguidelines-macro-usage)

#ifdef __clang__
#define CIB_PRAGMA_SEMI
#else
#define CIB_PRAGMA_SEMI ;
#endif

#define CIB_LOG_ENV_DECL(...)                                                  \
    [[maybe_unused]] typedef decltype([]<logging::detail::                     \
                                             autowrap... _env_args> {          \
        using new_env_t =                                                      \
            typename logging::detail::for_each_pair<std::make_index_sequence<  \
                sizeof...(_env_args) / 2>>::template type<_env_args...>;       \
        return boost::mp11::mp_append<new_env_t, cib_log_env_t>{};             \
    }.template operator()<__VA_ARGS__>()) cib_log_env_t

#define CIB_LOG_ENV(...)                                                       \
    STDX_PRAGMA(diagnostic push)                                               \
    STDX_PRAGMA(diagnostic ignored "-Wshadow")                                 \
    CIB_LOG_ENV_DECL(__VA_ARGS__)                                              \
    CIB_PRAGMA_SEMI                                                            \
    STDX_PRAGMA(diagnostic pop)

#define CIB_WITH_LOG_ENV(...)                                                  \
    STDX_PRAGMA(diagnostic push)                                               \
    STDX_PRAGMA(diagnostic ignored "-Wshadow")                                 \
    if constexpr (CIB_LOG_ENV_DECL(__VA_ARGS__); true)                         \
    STDX_PRAGMA(diagnostic pop)

// NOLINTEND(cppcoreguidelines-macro-usage)
