// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "google/protobuf/symbol.h"

#include <string>
#include <utility>

#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/port.h"

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

namespace google {
namespace protobuf {
namespace internal {

const FileDescriptor* Symbol::GetFile() const {
  switch (type()) {
    case MESSAGE:
      return descriptor()->file();
    case FIELD:
      return field_descriptor()->file();
    case ONEOF:
      return oneof_descriptor()->containing_type()->file();
    case ENUM:
      return enum_descriptor()->file();
    case ENUM_VALUE:
      return enum_value_descriptor()->type()->file();
    case SERVICE:
      return service_descriptor()->file();
    case METHOD:
      return method_descriptor()->service()->file();
    case FULL_PACKAGE:
      return file_descriptor();
    case SUB_PACKAGE:
      return sub_package_file_descriptor()->file;
    default:
      return nullptr;
  }
}

absl::string_view Symbol::full_name() const {
  switch (type()) {
    case MESSAGE:
      return descriptor()->full_name();
    case FIELD:
      return field_descriptor()->full_name();
    case ONEOF:
      return oneof_descriptor()->full_name();
    case ENUM:
      return enum_descriptor()->full_name();
    case ENUM_VALUE:
      return enum_value_descriptor()->full_name();
    case SERVICE:
      return service_descriptor()->full_name();
    case METHOD:
      return method_descriptor()->full_name();
    case FULL_PACKAGE:
      return file_descriptor()->package();
    case SUB_PACKAGE:
      return sub_package_file_descriptor()->file->package().substr(
          0, sub_package_file_descriptor()->name_size);
    default:
      ABSL_CHECK(false);
  }
  return "";
}

std::pair<const void*, absl::string_view> Symbol::parent_name_key() const {
  const auto or_file = [&](const void* p) { return p ? p : GetFile(); };
  switch (type()) {
    case MESSAGE:
      return {or_file(descriptor()->containing_type()), descriptor()->name()};
    case FIELD: {
      auto* field = field_descriptor();
      return {or_file(field->is_extension() ? field->extension_scope()
                                            : field->containing_type()),
              field->name()};
    }
    case ONEOF:
      return {oneof_descriptor()->containing_type(),
              oneof_descriptor()->name()};
    case ENUM:
      return {or_file(enum_descriptor()->containing_type()),
              enum_descriptor()->name()};
    case ENUM_VALUE:
      return {or_file(enum_value_descriptor()->type()->containing_type()),
              enum_value_descriptor()->name()};
    case ENUM_VALUE_OTHER_PARENT:
      return {enum_value_descriptor()->type(), enum_value_descriptor()->name()};
    case SERVICE:
      return {GetFile(), service_descriptor()->name()};
    case METHOD:
      return {method_descriptor()->service(), method_descriptor()->name()};
    default:
      ABSL_CHECK(false);
  }
  return {};
}

const FeatureSet& Symbol::features() const {
  switch (type()) {
    case MESSAGE:
      return descriptor()->features();
    case FIELD:
      return field_descriptor()->features();
    case ONEOF:
      return oneof_descriptor()->features();
    case ENUM:
      return enum_descriptor()->features();
    case ENUM_VALUE:
      return enum_value_descriptor()->features();
    case SERVICE:
      return service_descriptor()->features();
    case METHOD:
      return method_descriptor()->features();
    case FULL_PACKAGE:
      return file_descriptor()->features();
    case SUB_PACKAGE:
    default:
      internal::Unreachable();
  }
}

bool Symbol::is_placeholder() const {
  switch (type()) {
    case MESSAGE:
      return descriptor()->is_placeholder();
    case ENUM:
      return enum_descriptor()->is_placeholder();
    case FULL_PACKAGE:
      return file_descriptor()->is_placeholder();
    default:
      return false;
  }
}

SymbolVisibility Symbol::visibility_keyword() const {
  switch (type()) {
    case MESSAGE:
      return descriptor()->visibility_keyword();
    case ENUM:
      return enum_descriptor()->visibility_keyword();
    default:
      return SymbolVisibility::VISIBILITY_UNSET;
  }
}

bool Symbol::IsNestedDefinition() const {
  switch (type()) {
    case MESSAGE:
      return descriptor()->containing_type() != nullptr;
    case ENUM:
      return enum_descriptor()->containing_type() != nullptr;
    case FIELD:  // For extension fields
      return field_descriptor()->containing_type() != nullptr;
    default:
      return false;
  }
}

SymbolVisibility Symbol::GetEffectiveVisibility() const {
  // Only Types have visibility
  if (!IsType()) {
    return SymbolVisibility::VISIBILITY_UNSET;
  }

  SymbolVisibility effective_visibility = visibility_keyword();

  // If our visibility is specifically set we can return that.  We'll validate
  // whether it's reasonable or not later.
  if (effective_visibility == SymbolVisibility::VISIBILITY_UNSET) {
    switch (features().default_symbol_visibility()) {
      case FeatureSet::VisibilityFeature::EXPORT_ALL:
        return SymbolVisibility::VISIBILITY_EXPORT;
      case FeatureSet::VisibilityFeature::EXPORT_TOP_LEVEL:
        return IsNestedDefinition() ? SymbolVisibility::VISIBILITY_LOCAL
                                    : SymbolVisibility::VISIBILITY_EXPORT;
      case FeatureSet::VisibilityFeature::LOCAL_ALL:
      case FeatureSet::VisibilityFeature::STRICT:
        return SymbolVisibility::VISIBILITY_LOCAL;

      // Unset shouldn't be possible from the compiler without there being an
      // error (recursive import, for example), but happens in unit
      // tests so assume it represents pre-edition 2024 defaults. In either
      // case we want to fail open.  We have a DCHECK here to make sure it can
      // fail in tests, but not released code.
      case FeatureSet::VisibilityFeature::DEFAULT_SYMBOL_VISIBILITY_UNKNOWN:
      default:
        ABSL_DCHECK(false);
        return SymbolVisibility::VISIBILITY_EXPORT;
    }
  }

  return effective_visibility;
}

/*
 * Calculate whether this symbol can be accessed from the given
 * FileDescriptor*.
 *
 * Returns true if the symbol is in the same file OR the symbol is `export`
 */
bool Symbol::IsVisibleFrom(FileDescriptor* other) const {
  if (other == nullptr || GetFile() == nullptr) {
    return false;
  }

  // Only Types (message/enum) have visibility.
  if (!IsType()) {
    return true;
  }

  // If we're dealing with a placeholder then just stop now, visibility can't
  // be determined and we have to rely on the proto-compiler previously having
  // checked the validity.
  if (is_placeholder()) {
    return true;
  }

  if (GetFile() == other) {
    return true;
  }

  SymbolVisibility effective_visibility = GetEffectiveVisibility();

  return effective_visibility == SymbolVisibility::VISIBILITY_EXPORT;
}

std::string Symbol::GetVisibilityError(FileDescriptor* other,
                                       absl::string_view usage) const {
  const absl::string_view file_path =
      GetFile() != nullptr ? GetFile()->name() : "unknown_file";
  const absl::string_view symbol_name = full_name();

  if (!IsType()) {
    return absl::StrCat(
        "Attempt to get a visibility error for a non-message/enum symbol \"",
        symbol_name, "\", defined in \"", file_path, "\"");
  }

  SymbolVisibility explicit_visibility = visibility_keyword();

  std::string reason =
      explicit_visibility == SymbolVisibility::VISIBILITY_LOCAL
          ? "It is explicitly marked 'local'"
          : absl::StrCat(
                "It defaulted to local from file-level 'option "
                "features.default_symbol_visibility = '",
                FeatureSet_VisibilityFeature_DefaultSymbolVisibility_Name(
                    features().default_symbol_visibility()),
                "';");

  return absl::StrCat("Symbol \"", symbol_name, "\", defined in \"", file_path,
                      "\" ", usage, " is ", "not visible from \"",
                      other->name(), "\". ", reason,
                      " and cannot be accessed outside its own file");
}

}  // namespace internal
}  // namespace protobuf
}  // namespace google

#include "google/protobuf/port_undef.inc"
