/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.polaris.core.config;

import java.util.List;
import java.util.Optional;
import org.apache.polaris.core.admin.model.AuthenticationParameters;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.connection.ConnectionType;
import org.apache.polaris.core.persistence.cache.EntityWeigher;

/**
 * Configurations for features within Polaris. These configurations are intended to be customized
 * and many expose user-facing catalog-level configurations. These configurations are stable over
 * time.
 *
 * @param <T>
 */
public class FeatureConfiguration<T> extends PolarisConfiguration<T> {
  protected FeatureConfiguration(
      String key,
      String description,
      T defaultValue,
      Optional<String> catalogConfig,
      Optional<String> catalogConfigUnsafe) {
    super(key, description, defaultValue, catalogConfig, catalogConfigUnsafe);
  }

  /**
   * Helper for the common scenario of gating a feature with a boolean FeatureConfiguration, where
   * we want to throw an UnsupportedOperationException if it's not enabled.
   */
  public static void enforceFeatureEnabledOrThrow(
      RealmConfig realmConfig, FeatureConfiguration<Boolean> featureConfig) {
    boolean enabled = realmConfig.getConfig(featureConfig);
    if (!enabled) {
      throw new UnsupportedOperationException("Feature not enabled: " + featureConfig.key());
    }
  }

  public static final FeatureConfiguration<Boolean>
      ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING =
          PolarisConfiguration.<Boolean>builder()
              .key("ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING")
              .description(
                  "If set to true, require that principals must rotate their credentials before being used "
                      + "for anything else.")
              .defaultValue(false)
              .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION =
      PolarisConfiguration.<Boolean>builder()
          .key("SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION")
          .description(
              "If set to true, skip credential-subscoping indirection entirely whenever trying\n"
                  + "   to obtain storage credentials for instantiating a FileIO. If 'true', no attempt is made\n"
                  + "   to use StorageConfigs to generate table-specific storage credentials, but instead the default\n"
                  + "   fallthrough of table-level credential properties or else provider-specific APPLICATION_DEFAULT\n"
                  + "   credential-loading will be used for the FileIO.\n"
                  + "   Typically this setting is used in single-tenant server deployments that don't rely on\n"
                  + "   \"credential-vending\" and can use server-default environment variables or credential config\n"
                  + "   files for all storage access, or in test/dev scenarios.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> INCLUDE_PRINCIPAL_NAME_IN_SUBSCOPED_CREDENTIAL =
      PolarisConfiguration.<Boolean>builder()
          .key("INCLUDE_PRINCIPAL_NAME_IN_SUBSCOPED_CREDENTIAL")
          .description(
              "If set to true, principal name will be included in temporary subscoped credentials.\n"
                  + "Currently only AWS credentials are supported for which session name of the generated credentials \n"
                  + "will look like 'polaris-<principal>' rather than simple 'polaris'.\n"
                  + "Note that enabling this feature leads to degradation in temporary credential caching as \n"
                  + "catalog will no longer be able to reuse credentials for multiple principals.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  /**
   * The set of fields that are supported as AWS STS session tag labels in credential vending.
   *
   * <p>Supported values:
   *
   * <ul>
   *   <li>{@code realm} - The realm identifier for the request
   *   <li>{@code catalog} - The name of the catalog vending credentials
   *   <li>{@code namespace} - The namespace being accessed (dot-separated)
   *   <li>{@code table} - The table name being accessed
   *   <li>{@code principal} - The principal name requesting credentials
   *   <li>{@code roles} - Comma-separated list of activated principal roles
   *   <li>{@code trace_id} - OpenTelemetry trace ID (WARNING: disables credential caching)
   * </ul>
   */
  public static final List<String> SUPPORTED_SESSION_TAG_FIELDS =
      List.<String>of("realm", "catalog", "namespace", "table", "principal", "roles", "trace_id");

  /**
   * The config name for the trace ID session tag field. Included as a constant because callers need
   * to check for its presence specifically — enabling it populates the trace ID into the credential
   * vending context, which effectively eliminates credential cache reuse since each request has a
   * unique trace ID.
   */
  public static final String SESSION_TAG_FIELD_TRACE_ID = "trace_id";

  public static final FeatureConfiguration<List<String>> SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL =
      PolarisConfiguration.<List<String>>builder()
          .key("SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL")
          .description(
              "A comma-separated list of fields to include as session tags in AWS STS AssumeRole requests\n"
                  + "for credential vending. These tags appear in CloudTrail events, enabling correlation between\n"
                  + "catalog operations and S3 data access. An empty list (default) disables session tags entirely.\n"
                  + "Requires the IAM role trust policy to allow sts:TagSession action.\n"
                  + "\n"
                  + "Supported fields: "
                  + String.join(", ", SUPPORTED_SESSION_TAG_FIELDS)
                  + "\n"
                  + "\n"
                  + "Note: each additional field may contribute to AWS STS packed policy size (max 2048 characters).\n"
                  + "Fields with long values (e.g. deeply nested namespaces) can cause STS policy size limit errors.\n"
                  + "Choose only the fields needed for your CloudTrail correlation requirements.\n"
                  + "\n"
                  + "WARNING: Including 'trace_id' effectively eliminates credential cache reuse because every\n"
                  + "request has a unique trace ID. This may significantly increase latency and STS API costs.")
          .defaultValue(List.<String>of())
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_SETTING_S3_ENDPOINTS =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_SETTING_S3_ENDPOINTS")
          .description(
              "If set to true (default), Polaris will permit S3 storage configurations to have custom endpoints.\n"
                  + "If set to false, Polaris will not accept catalog create and update requests that contain \n"
                  + "S3 endpoint properties.")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_TABLE_LOCATION_OVERLAP =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_TABLE_LOCATION_OVERLAP")
          .catalogConfig("polaris.config.allow.overlapping.table.location")
          .legacyCatalogConfig("allow.overlapping.table.location")
          .description(
              "If set to true, allow one table's location to reside within another table's location. "
                  + "This is only enforced within a given namespace.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_NAMESPACE_LOCATION_OVERLAP =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_NAMESPACE_LOCATION_OVERLAP")
          .description(
              "If set to true, allow one namespace's location to reside within another namespace's location. "
                  + "This is only enforced within a parent catalog or namespace.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_EXTERNAL_METADATA_FILE_LOCATION =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_EXTERNAL_METADATA_FILE_LOCATION")
          .description(
              "If set to true, allows metadata files to be located outside the default metadata directory.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_OVERLAPPING_CATALOG_URLS =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_OVERLAPPING_CATALOG_URLS")
          .description("If set to true, allows catalog URLs to overlap.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_UNSTRUCTURED_TABLE_LOCATION =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_UNSTRUCTURED_TABLE_LOCATION")
          .catalogConfig("polaris.config.allow.unstructured.table.location")
          .legacyCatalogConfig("allow.unstructured.table.location")
          .description("If set to true, allows unstructured table locations.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_EXTERNAL_TABLE_LOCATION =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_EXTERNAL_TABLE_LOCATION")
          .catalogConfig("polaris.config.allow.external.table.location")
          .legacyCatalogConfig("allow.external.table.location")
          .description(
              "If set to true, allows tables to have external locations outside the default structure.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING")
          .catalogConfig("polaris.config.enable.credential.vending")
          .legacyCatalogConfig("enable.credential.vending")
          .description("If set to true, allow credential vending for external catalogs.")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_WILDCARD_LOCATION =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_WILDCARD_LOCATION")
          .description(
              "Indicates whether asterisks ('*') in configuration values defining allowed"
                  + " storage locations are processed as meaning 'any location'.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<List<String>> SUPPORTED_CATALOG_STORAGE_TYPES =
      PolarisConfiguration.<List<String>>builder()
          .key("SUPPORTED_CATALOG_STORAGE_TYPES")
          .catalogConfig("polaris.config.supported.storage.types")
          .legacyCatalogConfig("supported.storage.types")
          .description("The list of supported storage types for a catalog")
          .defaultValue(
              List.of(
                  StorageConfigInfo.StorageTypeEnum.S3.name(),
                  StorageConfigInfo.StorageTypeEnum.AZURE.name(),
                  StorageConfigInfo.StorageTypeEnum.GCS.name()))
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> CLEANUP_ON_NAMESPACE_DROP =
      PolarisConfiguration.<Boolean>builder()
          .key("CLEANUP_ON_NAMESPACE_DROP")
          .catalogConfig("polaris.config.cleanup.on.namespace.drop")
          .legacyCatalogConfig("cleanup.on.namespace.drop")
          .description("If set to true, clean up data when a namespace is dropped")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> CLEANUP_ON_CATALOG_DROP =
      PolarisConfiguration.<Boolean>builder()
          .key("CLEANUP_ON_CATALOG_DROP")
          .catalogConfig("polaris.config.cleanup.on.catalog.drop")
          .legacyCatalogConfig("cleanup.on.catalog.drop")
          .description("If set to true, clean up data when a catalog is dropped")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> DROP_WITH_PURGE_ENABLED =
      PolarisConfiguration.<Boolean>builder()
          .key("DROP_WITH_PURGE_ENABLED")
          .catalogConfig("polaris.config.drop-with-purge.enabled")
          .legacyCatalogConfig("drop-with-purge.enabled")
          .description(
              "If set to true, allows tables to be dropped with the purge parameter set to true.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> PURGE_VIEW_METADATA_ON_DROP =
      PolarisConfiguration.<Boolean>builder()
          .key("PURGE_VIEW_METADATA_ON_DROP")
          .catalogConfig("polaris.config.purge-view-metadata-on-drop")
          .description(
              "If set to true, Polaris will attempt to delete view metadata files when a view is dropped.")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Long> POLARIS_TASK_TIMEOUT_MILLIS =
      PolarisConfiguration.<Long>builder()
          .key("POLARIS_TASK_TIMEOUT_MILLIS")
          .description(
              "Polaris task expiry timeout (milliseconds). Older unfinished tasks may not be processed.")
          .defaultValue(300_000L)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> STORAGE_CREDENTIAL_DURATION_SECONDS =
      PolarisConfiguration.<Integer>builder()
          .key("STORAGE_CREDENTIAL_DURATION_SECONDS")
          .description(
              "The duration of time that vended storage credentials are valid for. Support for"
                  + " longer (or shorter) durations is dependent on the storage provider. GCS"
                  + " current does not respect this value.")
          .defaultValue(60 * 60) // 1 hour
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS =
      PolarisConfiguration.<Integer>builder()
          .key("STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS")
          .description(
              "How long to store storage credentials in the local cache. This should be less than "
                  + STORAGE_CREDENTIAL_DURATION_SECONDS.key())
          .defaultValue(30 * 60) // 30 minutes
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> MAX_METADATA_REFRESH_RETRIES =
      PolarisConfiguration.<Integer>builder()
          .key("MAX_METADATA_REFRESH_RETRIES")
          .description(
              "How many times to retry refreshing metadata when the previous error was retryable")
          .defaultValue(2)
          .buildFeatureConfiguration();

  public static final PolarisConfiguration<Boolean> LIST_PAGINATION_ENABLED =
      PolarisConfiguration.<Boolean>builder()
          .key("LIST_PAGINATION_ENABLED")
          .catalogConfig("polaris.config.list-pagination-enabled")
          .description("If set to true, pagination for APIs like listTables is enabled.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ENABLE_GENERIC_TABLES =
      PolarisConfiguration.<Boolean>builder()
          .key("ENABLE_GENERIC_TABLES")
          .description("If true, the generic-tables endpoints are enabled")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Long> ENTITY_CACHE_WEIGHER_TARGET =
      PolarisConfiguration.<Long>builder()
          .key("ENTITY_CACHE_WEIGHER_TARGET")
          .description(
              "The maximum weight for the entity cache. This is a heuristic value without any particular"
                  + " unit of measurement. It roughly correlates with the total heap size of cached values. Fine-tuning"
                  + " requires experimentation in the specific deployment environment")
          .defaultValue(100 * EntityWeigher.WEIGHT_PER_MB)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ENABLE_CATALOG_FEDERATION =
      PolarisConfiguration.<Boolean>builder()
          .key("ENABLE_CATALOG_FEDERATION")
          .description(
              "If true, allows creating and using ExternalCatalogs containing ConnectionConfigInfos"
                  + " to perform federation to remote catalogs.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS =
      PolarisConfiguration.<Boolean>builder()
          .key("ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS")
          .description(
              "When enabled, allows RBAC operations to create synthetic entities for"
                  + " entities in federated catalogs that don't exist in the local metastore.")
          .catalogConfig("polaris.config.enable-sub-catalog-rbac-for-federated-catalogs")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ENABLE_POLICY_STORE =
      PolarisConfiguration.<Boolean>builder()
          .key("ENABLE_POLICY_STORE")
          .description("If true, the policy-store endpoints are enabled")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<List<String>> SUPPORTED_CATALOG_CONNECTION_TYPES =
      PolarisConfiguration.<List<String>>builder()
          .key("SUPPORTED_CATALOG_CONNECTION_TYPES")
          .description("The list of supported catalog connection types for federation")
          .defaultValue(List.of(ConnectionType.ICEBERG_REST.name()))
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<List<String>>
      SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES =
          PolarisConfiguration.<List<String>>builder()
              .key("SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES")
              .description("The list of supported authentication types for catalog federation")
              .defaultValue(
                  List.of(
                      AuthenticationParameters.AuthenticationTypeEnum.OAUTH.name(),
                      AuthenticationParameters.AuthenticationTypeEnum.BEARER.name(),
                      AuthenticationParameters.AuthenticationTypeEnum.SIGV4.name()))
              .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> ICEBERG_COMMIT_MAX_RETRIES =
      PolarisConfiguration.<Integer>builder()
          .key("ICEBERG_COMMIT_MAX_RETRIES")
          .catalogConfig("polaris.config.iceberg-commit-max-retries")
          .description("The max number of times to try committing to an Iceberg table")
          .defaultValue(4)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_SPECIFYING_FILE_IO_IMPL =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_SPECIFYING_FILE_IO_IMPL")
          .description(
              "Config key for whether to allow setting the FILE_IO_IMPL using catalog properties. "
                  + "Must only be enabled in dev/test environments, should not be in production systems.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_INSECURE_STORAGE_TYPES =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_INSECURE_STORAGE_TYPES")
          .description(
              "Allow usage of FileIO implementations that are considered insecure. "
                  + "Enabling this setting may expose the service to possibly severe security risks! "
                  + "This should only be set to 'true' for tests!")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ICEBERG_ROLLBACK_COMPACTION_ON_CONFLICTS =
      PolarisConfiguration.<Boolean>builder()
          .key("ICEBERG_ROLLBACK_COMPACTION_ON_CONFLICTS")
          .catalogConfig("polaris.config.rollback.compaction.on-conflicts.enabled")
          .description(
              "Rollback replace snapshots created by compaction which have "
                  + "polaris.internal.conflict-resolution.by-operation-type.replace property set to rollback "
                  + "in their snapshot summary")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ADD_TRAILING_SLASH_TO_LOCATION =
      PolarisConfiguration.<Boolean>builder()
          .key("ADD_TRAILING_SLASH_TO_LOCATION")
          .catalogConfig("polaris.config.add-trailing-slash-to-location")
          .description(
              "When set, the base location for a table or namespace will have `/` added as a suffix if not present")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_OPTIMIZED_SIBLING_CHECK =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_OPTIMIZED_SIBLING_CHECK")
          .description(
              "When set to true, Polaris will permit enabling the feature OPTIMIZED_SIBLING_CHECK "
                  + "for catalogs, this is done to prevent accidental enabling the feature in cases such as schema migrations, without backfill and hence leading to potential data integrity issues.\n"
                  + "This will be removed in 2.0.0 when polaris ships with the necessary migrations to backfill the index.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> OPTIMIZED_SIBLING_CHECK =
      PolarisConfiguration.<Boolean>builder()
          .key("OPTIMIZED_SIBLING_CHECK")
          .description(
              "When set, an index is used to perform the sibling check between tables, views, and namespaces. New "
                  + "locations will be checked against previous ones based on components, so the new location "
                  + "/foo/bar/ will check for a sibling at /, /foo/ and /foo/bar/%. In order for this check to "
                  + "be correct, locations should end with a slash. See ADD_TRAILING_SLASH_TO_LOCATION for a way "
                  + "to enforce this when new locations are added. Only supported by the JDBC metastore.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> DEFAULT_LOCATION_OBJECT_STORAGE_PREFIX_ENABLED =
      PolarisConfiguration.<Boolean>builder()
          .key("DEFAULT_LOCATION_OBJECT_STORAGE_PREFIX_ENABLED")
          .catalogConfig("polaris.config.default-table-location-object-storage-prefix.enabled")
          .description(
              "When enabled, Iceberg tables and views created without a location specified will have a prefix "
                  + "applied to the location within the catalog's base location, rather than a location directly "
                  + "inside the parent namespace. Note that this requires ALLOW_EXTERNAL_TABLE_LOCATION to be "
                  + "enabled, but with OPTIMIZED_SIBLING_CHECK enabled "
                  + "it is still possible to enforce the uniqueness of table locations within a catalog.")
          .defaultValue(false)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ENABLE_CREDENTIAL_RESET =
      PolarisConfiguration.<Boolean>builder()
          .key("ENABLE_CREDENTIAL_RESET")
          .description(
              "Flag to enable or disable the API to reset principal credentials. "
                  + "Defaults to enabled, but service providers may want to disable it.")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean>
      ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS =
          PolarisConfiguration.<Boolean>builder()
              .key("ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS")
              .description(
                  "If set to true (default), Polaris will allow setting or changing "
                      + "catalog property polaris.config.enable-sub-catalog-rbac-for-federated-catalogs."
                      + "If set to false, Polaris will disallow setting or changing the above catalog property")
              .defaultValue(true)
              .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean>
      ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG =
          PolarisConfiguration.<Boolean>builder()
              .key("ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG")
              .description(
                  "If enabled, allow dropping a passthrough-facade catalog even if it contains namespaces or tables. "
                      + "passthrough-facade catalogs may contain leftover entities when syncing with source catalog."
                      + "In the short term these entities will be ignored, in the long term there will be method/background job to clean them up.")
              .catalogConfig("polaris.config.allow-dropping-non-empty-passthrough-facade-catalog")
              .defaultValue(false)
              .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ENABLE_FINE_GRAINED_UPDATE_TABLE_PRIVILEGES =
      PolarisConfiguration.<Boolean>builder()
          .key("ENABLE_FINE_GRAINED_UPDATE_TABLE_PRIVILEGES")
          .catalogConfig("polaris.config.enable-fine-grained-update-table-privileges")
          .description(
              "When true, enables finer grained update table privileges which are passed to the authorizer for update table operations")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> ALLOW_FEDERATED_CATALOGS_CREDENTIAL_VENDING =
      PolarisConfiguration.<Boolean>builder()
          .key("ALLOW_FEDERATED_CATALOGS_CREDENTIAL_VENDING")
          .catalogConfig("polaris.config.allow-federated-catalogs-credential-vending")
          .description(
              "If set to true (default), allow credential vending for external catalogs. Note this requires ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING to be true first.")
          .defaultValue(true)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> AZURE_TIMEOUT_MILLIS =
      PolarisConfiguration.<Integer>builder()
          .key("AZURE_TIMEOUT_MILLIS")
          .description(
              "Timeout in milliseconds for Azure API requests. "
                  + "Prevents indefinite blocking when Azure endpoints are slow or unresponsive. "
                  + "Used internally by Azure storage integration for credential vending and other operations.")
          .defaultValue(15000)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> AZURE_RETRY_COUNT =
      PolarisConfiguration.<Integer>builder()
          .key("AZURE_RETRY_COUNT")
          .description(
              "Number of retry attempts for Azure API requests. "
                  + "Uses exponential backoff with jitter to handle transient failures.")
          .defaultValue(3)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> AZURE_RETRY_DELAY_MILLIS =
      PolarisConfiguration.<Integer>builder()
          .key("AZURE_RETRY_DELAY_MILLIS")
          .description(
              "Initial delay in milliseconds before first retry for Azure API requests. "
                  + "Delay doubles with each retry (exponential backoff).")
          .defaultValue(2000)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Double> AZURE_RETRY_JITTER_FACTOR =
      PolarisConfiguration.<Double>builder()
          .key("AZURE_RETRY_JITTER_FACTOR")
          .description(
              "Jitter factor (0.0 to 1.0) applied to retry delays for Azure API requests. "
                  + "The jitter is applied as a random percentage of the computed exponential backoff delay. "
                  + "For example, 0.5 means up to 50%% random jitter will be added to each retry delay. "
                  + "Helps prevent thundering herd when multiple requests fail simultaneously.")
          .defaultValue(0.5)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Integer> TABLE_METADATA_CLEANUP_BATCH_SIZE =
      PolarisConfiguration.<Integer>builder()
          .key("TABLE_METADATA_CLEANUP_BATCH_SIZE")
          .description("Metadata batch size for tasks that clean up dropped tables' files.")
          .defaultValue(10)
          .buildFeatureConfiguration();

  public static final FeatureConfiguration<Boolean> RESOLVE_CREDENTIALS_BY_STORAGE_NAME =
      PolarisConfiguration.<Boolean>builder()
          .key("RESOLVE_CREDENTIALS_BY_STORAGE_NAME")
          .description(
              "If set to true, resolve AWS credentials based on the storageName field "
                  + "of the storage configuration. "
                  + "When disabled, the default AWS credentials are used for all storages.")
          .defaultValue(false)
          .buildFeatureConfiguration();
}
