#ifndef GOOGLE_PROTOBUF_INTERNAL_FEATURE_HELPER_H__
#define GOOGLE_PROTOBUF_INTERNAL_FEATURE_HELPER_H__

#include <cstdint>

#include "absl/strings/string_view.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/extension_set.h"

// Must be included last.
#include "google/protobuf/port_def.inc"

namespace google {
namespace protobuf {

namespace compiler::generator_internal {
template <typename DescriptorT, typename ExtType, uint8_t field_type,
          bool is_packed>
auto GetResolvedFeatureExtension(
    const DescriptorT& descriptor,
    const google::protobuf::internal::ExtensionIdentifier<
        FeatureSet, internal::MessageTypeTraits<ExtType>, field_type,
        is_packed>& extension);

}  // namespace compiler::generator_internal

namespace internal {
class InternalFeatureHelperTest;
// This class is for internal use only and provides access to the resolved
// runtime FeatureSets of any descriptor.  These features are not designed
// to be stable, and depending directly on them (vs the public descriptor APIs)
// is not safe.
class PROTOBUF_EXPORT InternalFeatureHelper {
 public:
  template <typename DescriptorT>
  static const FeatureSet& GetFeatures(const DescriptorT& desc) {
    return desc.features();
  }

 private:
  friend class ::google::protobuf::compiler::CodeGenerator;
  friend class ::google::protobuf::compiler::CommandLineInterface;
  friend class ::google::protobuf::internal::InternalFeatureHelperTest;

  template <typename DescriptorT, typename ExtType, uint8_t field_type,
            bool is_packed>
  friend auto ::google::protobuf::compiler::generator_internal::
      GetResolvedFeatureExtension(
          const DescriptorT& descriptor,
          const google::protobuf::internal::ExtensionIdentifier<
              FeatureSet, internal::MessageTypeTraits<ExtType>, field_type,
              is_packed>& extension);

  static const DescriptorPool& GetDescriptorPool(const FileDescriptor& file) {
    return *file.pool();
  }

  template <typename DescriptorT>
  static const DescriptorPool& GetDescriptorPool(const DescriptorT& desc) {
    return GetDescriptorPool(*desc.file());
  }

  // Provides a restricted view exclusively to code generators to query their
  // own unresolved features.  Unresolved features are virtually meaningless to
  // everyone else. Code generators will need them to validate their own
  // features, and runtimes may need them internally to be able to properly
  // represent the original proto files from generated code.
  template <typename DescriptorT, typename TypeTraitsT, uint8_t field_type,
            bool is_packed>
  static typename TypeTraitsT::ConstType GetUnresolvedFeatures(
      const DescriptorT& descriptor,
      const google::protobuf::internal::ExtensionIdentifier<
          FeatureSet, TypeTraitsT, field_type, is_packed>& extension) {
    return descriptor.proto_features_->GetExtension(extension);
  }

  // Provides a restricted view exclusively to code generators to query the
  // edition of files being processed.  While most people should never write
  // edition-dependent code, generators frequently will need to.
  static Edition GetEdition(const FileDescriptor& desc) {
    return desc.edition();
  }

  template <typename DescriptorT>
  static Edition GetEdition(const DescriptorT& desc) {
    return GetEdition(*desc.file());
  }

  // Parses the serialized FeatureSetDefaults and returns the resolved
  // FeatureSet for a given edition.
  static FeatureSet ParseAndGetEditionResolvedFeatureSet(absl::string_view data,
                                                         Edition edition);

  // Gets the resolved FeatureSet extension for a given descriptor.
  //
  // If the descriptor's pool has already provided the resolved feature default
  // for the edition and the language FeatureSet extension, then the default
  // will be returned directly. Otherwise, the function will parse the
  // serialized FeatureSetDefaults data provided by the language FeatureSet
  // extension, and merge it with the original FeatureSet extension so that the
  // resolved feature set defaults will always be present.
  template <typename DescriptorT, typename ExtType, uint8_t field_type,
            bool is_packed>
  static auto GetResolvedFeatureExtension(
      const DescriptorT& descriptor,
      const google::protobuf::internal::ExtensionIdentifier<
          FeatureSet, MessageTypeTraits<ExtType>, field_type, is_packed>&
          extension) {
    auto lang_features = GetFeatures(descriptor).GetExtension(extension);
    if (GetDescriptorPool(descriptor).ResolvesFeaturesFor(extension)) {
      return lang_features;
    }

    auto lang_features_ret =
        ParseAndGetEditionResolvedFeatureSet(
            ::google::protobuf::internal::GetFeatureSetDefaultsData<ExtType>(),
            GetEdition(descriptor))
            .GetExtension(extension);
    lang_features_ret.MergeFrom(lang_features);
    return lang_features_ret;
  }
};
}  // namespace internal
}  // namespace protobuf
}  // namespace google

#include "google/protobuf/port_undef.inc"

#endif  // GOOGLE_PROTOBUF_INTERNAL_FEATURE_HELPER_H__
