// Copyright (c) 2022-2025 The pybind Community.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#pragma once

#include "detail/common.h"
#include "detail/native_enum_data.h"
#include "detail/type_caster_base.h"
#include "cast.h"

#include <cassert>
#include <limits>
#include <type_traits>
#include <typeindex>

PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)

/// Conversions between Python's native (stdlib) enum types and C++ enums.
template <typename EnumType>
class native_enum : public detail::native_enum_data {
public:
    using Underlying = typename std::underlying_type<EnumType>::type;

    native_enum(const object &parent_scope,
                const char *name,
                const char *native_type_name,
                const char *class_doc = "")
        : detail::native_enum_data(
              parent_scope, name, native_type_name, class_doc, std::type_index(typeid(EnumType))) {
        if (detail::get_local_type_info(typeid(EnumType)) != nullptr
            || detail::get_global_type_info(typeid(EnumType)) != nullptr) {
            pybind11_fail(
                "pybind11::native_enum<...>(\"" + enum_name_encoded
                + "\") is already registered as a `pybind11::enum_` or `pybind11::class_`!");
        }
        if (detail::global_internals_native_enum_type_map_contains(enum_type_index)) {
            pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded
                          + "\") is already registered!");
        }
        arm_finalize_check();
    }

    /// Export enumeration entries into the parent scope
    native_enum &export_values() {
        assert(!export_values_flag); // Catch redundant calls.
        export_values_flag = true;
        return *this;
    }

    /// Add an enumeration entry
    native_enum &value(char const *name, EnumType value, const char *doc = nullptr) {
        // Disarm for the case that the native_enum_data dtor runs during exception unwinding.
        disarm_finalize_check("value after finalize");
        members.append(make_tuple(name, static_cast<Underlying>(value)));
        if (doc) {
            member_docs.append(make_tuple(name, doc));
        }
        arm_finalize_check(); // There was no exception.
        return *this;
    }

    native_enum(const native_enum &) = delete;
    native_enum &operator=(const native_enum &) = delete;
};

PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
