// Copyright 2025 Google LLC
//
// Licensed 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.

syntax = "proto3";

package google.cloud.retail.v2;

import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/cloud/retail/v2/common.proto";
import "google/cloud/retail/v2/import_config.proto";
import "google/protobuf/timestamp.proto";

option csharp_namespace = "Google.Cloud.Retail.V2";
option go_package = "cloud.google.com/go/retail/apiv2/retailpb;retailpb";
option java_multiple_files = true;
option java_outer_classname = "CatalogProto";
option java_package = "com.google.cloud.retail.v2";
option objc_class_prefix = "RETAIL";
option php_namespace = "Google\\Cloud\\Retail\\V2";
option ruby_package = "Google::Cloud::Retail::V2";

// Configures what level the product should be uploaded with regards to
// how users will be send events and how predictions will be made.
message ProductLevelConfig {
  // The type of [Product][google.cloud.retail.v2.Product]s allowed to be
  // ingested into the catalog. Acceptable values are:
  //
  // * `primary` (default): You can ingest
  // [Product][google.cloud.retail.v2.Product]s of all types. When
  //   ingesting a [Product][google.cloud.retail.v2.Product], its type will
  //   default to
  //   [Product.Type.PRIMARY][google.cloud.retail.v2.Product.Type.PRIMARY] if
  //   unset.
  // * `variant` (incompatible with Retail Search): You can only
  //   ingest
  //   [Product.Type.VARIANT][google.cloud.retail.v2.Product.Type.VARIANT]
  //   [Product][google.cloud.retail.v2.Product]s. This means
  //   [Product.primary_product_id][google.cloud.retail.v2.Product.primary_product_id]
  //   cannot be empty.
  //
  // If this field is set to an invalid value other than these, an
  // INVALID_ARGUMENT error is returned.
  //
  // If this field is `variant` and
  // [merchant_center_product_id_field][google.cloud.retail.v2.ProductLevelConfig.merchant_center_product_id_field]
  // is `itemGroupId`, an INVALID_ARGUMENT error is returned.
  //
  // See [Product
  // levels](https://cloud.google.com/retail/docs/catalog#product-levels)
  // for more details.
  string ingestion_product_type = 1;

  // Which field of [Merchant Center
  // Product](/bigquery-transfer/docs/merchant-center-products-schema) should be
  // imported as [Product.id][google.cloud.retail.v2.Product.id]. Acceptable
  // values are:
  //
  // * `offerId` (default): Import `offerId` as the product ID.
  // * `itemGroupId`: Import `itemGroupId` as the product ID. Notice that Retail
  //   API will choose one item from the ones with the same `itemGroupId`, and
  //   use it to represent the item group.
  //
  // If this field is set to an invalid value other than these, an
  // INVALID_ARGUMENT error is returned.
  //
  // If this field is `itemGroupId` and
  // [ingestion_product_type][google.cloud.retail.v2.ProductLevelConfig.ingestion_product_type]
  // is `variant`, an INVALID_ARGUMENT error is returned.
  //
  // See [Product
  // levels](https://cloud.google.com/retail/docs/catalog#product-levels)
  // for more details.
  string merchant_center_product_id_field = 2;
}

// Catalog level attribute config for an attribute. For example, if customers
// want to enable/disable facet for a specific attribute.
message CatalogAttribute {
  // Possible options for the facet that corresponds to the current attribute
  // config.
  message FacetConfig {
    // [Facet values][google.cloud.retail.v2.SearchResponse.Facet.values] to
    // ignore on [facets][google.cloud.retail.v2.SearchResponse.Facet] during
    // the specified time range for the given
    // [SearchResponse.Facet.key][google.cloud.retail.v2.SearchResponse.Facet.key]
    // attribute.
    message IgnoredFacetValues {
      // List of facet values to ignore for the following time range. The facet
      // values are the same as the attribute values. There is a limit of 10
      // values per instance of IgnoredFacetValues. Each value can have at most
      // 128 characters.
      repeated string values = 1;

      // Time range for the current list of facet values to ignore.
      // If multiple time ranges are specified for an facet value for the
      // current attribute, consider all of them. If both are empty, ignore
      // always. If start time and end time are set, then start time
      // must be before end time.
      // If start time is not empty and end time is empty, then will ignore
      // these facet values after the start time.
      google.protobuf.Timestamp start_time = 2;

      // If start time is empty and end time is not empty, then ignore these
      // facet values before end time.
      google.protobuf.Timestamp end_time = 3;
    }

    // Replaces a set of textual facet values by the same (possibly different)
    // merged facet value. Each facet value should appear at most once as a
    // value per [CatalogAttribute][google.cloud.retail.v2.CatalogAttribute].
    // This feature is available only for textual custom attributes.
    message MergedFacetValue {
      // All the facet values that are replaces by the same
      // [merged_value][google.cloud.retail.v2.CatalogAttribute.FacetConfig.MergedFacetValue.merged_value]
      // that follows. The maximum number of values per MergedFacetValue is 25.
      // Each value can have up to 128 characters.
      repeated string values = 1;

      // All the previous values are replaced by this merged facet value.
      // This merged_value must be non-empty and can have up to 128 characters.
      string merged_value = 2;
    }

    // The current facet key (i.e. attribute config) maps into the
    // [merged_facet_key][google.cloud.retail.v2.CatalogAttribute.FacetConfig.MergedFacet.merged_facet_key].
    // A facet key can have at most one child. The current facet key and the
    // merged facet key need both to be textual custom attributes or both
    // numerical custom attributes (same type).
    message MergedFacet {
      // The merged facet key should be a valid facet key that is different than
      // the facet key of the current catalog attribute. We refer this is
      // merged facet key as the child of the current catalog attribute. This
      // merged facet key can't be a parent of another facet key (i.e. no
      // directed path of length 2). This merged facet key needs to be either a
      // textual custom attribute or a numerical custom attribute.
      string merged_facet_key = 1;
    }

    // Options to rerank based on facet values engaged by the user for the
    // current key. That key needs to be a custom textual key and facetable.
    // To use this control, you also need to pass all the facet keys engaged by
    // the user in the request using the field [SearchRequest.FacetSpec]. In
    // particular, if you don't pass the facet keys engaged that you want to
    // rerank on, this control won't be effective. Moreover, to obtain better
    // results, the facet values that you want to rerank on should be close to
    // English (ideally made of words, underscores, and spaces).
    message RerankConfig {
      // If set to true, then we also rerank the dynamic facets based on the
      // facet values engaged by the user for the current attribute key during
      // serving.
      bool rerank_facet = 1;

      // If empty, rerank on all facet values for the current key. Otherwise,
      // will rerank on the facet values from this list only.
      repeated string facet_values = 2;
    }

    // If you don't set the facet
    // [SearchRequest.FacetSpec.FacetKey.intervals][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.intervals]
    // in the request to a numerical attribute, then we use the computed
    // intervals with rounded bounds obtained from all its product numerical
    // attribute values. The computed intervals might not be ideal for some
    // attributes. Therefore, we give you the option to overwrite them with the
    // facet_intervals field. The maximum of facet intervals per
    // [CatalogAttribute][google.cloud.retail.v2.CatalogAttribute] is 40. Each
    // interval must have a lower bound or an upper bound. If both bounds are
    // provided, then the lower bound must be smaller or equal than the upper
    // bound.
    repeated Interval facet_intervals = 1;

    // Each instance represents a list of attribute values to ignore as facet
    // values for a specific time range. The maximum number of instances per
    // [CatalogAttribute][google.cloud.retail.v2.CatalogAttribute] is 25.
    repeated IgnoredFacetValues ignored_facet_values = 2;

    // Each instance replaces a list of facet values by a merged facet
    // value. If a facet value is not in any list, then it will stay the same.
    // To avoid conflicts, only paths of length 1 are accepted. In other words,
    // if "dark_blue" merged into "BLUE", then the latter can't merge into
    // "blues" because this would create a path of length 2. The maximum number
    // of instances of MergedFacetValue per
    // [CatalogAttribute][google.cloud.retail.v2.CatalogAttribute] is 100. This
    // feature is available only for textual custom attributes.
    repeated MergedFacetValue merged_facet_values = 3;

    // Use this field only if you want to merge a facet key into another facet
    // key.
    MergedFacet merged_facet = 4;

    // Set this field only if you want to rerank based on facet values engaged
    // by the user for the current key. This option is only possible for custom
    // facetable textual keys.
    RerankConfig rerank_config = 5;
  }

  // The type of an attribute.
  enum AttributeType {
    // The type of the attribute is unknown.
    //
    // Used when type cannot be derived from attribute that is not
    // [in_use][google.cloud.retail.v2.CatalogAttribute.in_use].
    UNKNOWN = 0;

    // Textual attribute.
    TEXTUAL = 1;

    // Numerical attribute.
    NUMERICAL = 2;
  }

  // The status of the indexable option of a catalog attribute.
  enum IndexableOption {
    // Value used when unset.
    INDEXABLE_OPTION_UNSPECIFIED = 0;

    // Indexable option enabled for an attribute.
    INDEXABLE_ENABLED = 1;

    // Indexable option disabled for an attribute.
    INDEXABLE_DISABLED = 2;
  }

  // The status of the dynamic facetable option of a catalog attribute.
  enum DynamicFacetableOption {
    // Value used when unset.
    DYNAMIC_FACETABLE_OPTION_UNSPECIFIED = 0;

    // Dynamic facetable option enabled for an attribute.
    DYNAMIC_FACETABLE_ENABLED = 1;

    // Dynamic facetable option disabled for an attribute.
    DYNAMIC_FACETABLE_DISABLED = 2;
  }

  // The status of the searchable option of a catalog attribute.
  enum SearchableOption {
    // Value used when unset.
    SEARCHABLE_OPTION_UNSPECIFIED = 0;

    // Searchable option enabled for an attribute.
    SEARCHABLE_ENABLED = 1;

    // Searchable option disabled for an attribute.
    SEARCHABLE_DISABLED = 2;
  }

  // The status of the exact-searchable option of a catalog attribute.
  enum ExactSearchableOption {
    // Value used when unset.
    EXACT_SEARCHABLE_OPTION_UNSPECIFIED = 0;

    // Exact searchable option enabled for an attribute.
    EXACT_SEARCHABLE_ENABLED = 1;

    // Exact searchable option disabled for an attribute.
    EXACT_SEARCHABLE_DISABLED = 2;
  }

  // The status of the retrievable option of a catalog attribute.
  enum RetrievableOption {
    // Value used when unset.
    RETRIEVABLE_OPTION_UNSPECIFIED = 0;

    // Retrievable option enabled for an attribute.
    RETRIEVABLE_ENABLED = 1;

    // Retrievable option disabled for an attribute.
    RETRIEVABLE_DISABLED = 2;
  }

  // Required. Attribute name.
  // For example: `color`, `brands`, `attributes.custom_attribute`, such as
  // `attributes.xyz`.
  // To be indexable, the attribute name can contain only alpha-numeric
  // characters and underscores. For example, an attribute named
  // `attributes.abc_xyz` can be indexed, but an attribute named
  // `attributes.abc-xyz` cannot be indexed.
  //
  // If the attribute key starts with `attributes.`, then the attribute is a
  // custom attribute. Attributes such as `brands`, `patterns`, and `title` are
  // built-in and called system attributes.
  string key = 1 [(google.api.field_behavior) = REQUIRED];

  // Output only. Indicates whether this attribute has been used by any
  // products. `True` if at least one [Product][google.cloud.retail.v2.Product]
  // is using this attribute in
  // [Product.attributes][google.cloud.retail.v2.Product.attributes]. Otherwise,
  // this field is `False`.
  //
  // [CatalogAttribute][google.cloud.retail.v2.CatalogAttribute] can be
  // pre-loaded by using
  // [CatalogService.AddCatalogAttribute][google.cloud.retail.v2.CatalogService.AddCatalogAttribute]
  // or
  // [CatalogService.UpdateAttributesConfig][google.cloud.retail.v2.CatalogService.UpdateAttributesConfig]
  // APIs. This field is `False` for pre-loaded
  // [CatalogAttribute][google.cloud.retail.v2.CatalogAttribute]s.
  //
  // Only pre-loaded [catalog
  // attributes][google.cloud.retail.v2.CatalogAttribute] that are neither in
  // use by products nor predefined can be deleted. [Catalog
  // attributes][google.cloud.retail.v2.CatalogAttribute] that are
  // either in use by products or are predefined attributes cannot be deleted;
  // however, their configuration properties will reset to default values upon
  // removal request.
  //
  // After catalog changes, it takes about 10 minutes for this field to update.
  bool in_use = 9 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. The type of this attribute. This is derived from the attribute
  // in [Product.attributes][google.cloud.retail.v2.Product.attributes].
  AttributeType type = 10 [(google.api.field_behavior) = OUTPUT_ONLY];

  // When
  // [AttributesConfig.attribute_config_level][google.cloud.retail.v2.AttributesConfig.attribute_config_level]
  // is CATALOG_LEVEL_ATTRIBUTE_CONFIG, if INDEXABLE_ENABLED attribute values
  // are indexed so that it can be filtered, faceted, or boosted in
  // [SearchService.Search][google.cloud.retail.v2.SearchService.Search].
  //
  // Must be specified when
  // [AttributesConfig.attribute_config_level][google.cloud.retail.v2.AttributesConfig.attribute_config_level]
  // is CATALOG_LEVEL_ATTRIBUTE_CONFIG, otherwise throws INVALID_FORMAT error.
  IndexableOption indexable_option = 5;

  // If DYNAMIC_FACETABLE_ENABLED, attribute values are available for dynamic
  // facet. Could only be DYNAMIC_FACETABLE_DISABLED if
  // [CatalogAttribute.indexable_option][google.cloud.retail.v2.CatalogAttribute.indexable_option]
  // is INDEXABLE_DISABLED. Otherwise, an INVALID_ARGUMENT error is returned.
  //
  // Must be specified, otherwise throws INVALID_FORMAT error.
  DynamicFacetableOption dynamic_facetable_option = 6;

  // When
  // [AttributesConfig.attribute_config_level][google.cloud.retail.v2.AttributesConfig.attribute_config_level]
  // is CATALOG_LEVEL_ATTRIBUTE_CONFIG, if SEARCHABLE_ENABLED, attribute values
  // are searchable by text queries in
  // [SearchService.Search][google.cloud.retail.v2.SearchService.Search].
  //
  // If SEARCHABLE_ENABLED but attribute type is numerical, attribute values
  // will not be searchable by text queries in
  // [SearchService.Search][google.cloud.retail.v2.SearchService.Search], as
  // there are no text values associated to numerical attributes.
  //
  // Must be specified, when
  // [AttributesConfig.attribute_config_level][google.cloud.retail.v2.AttributesConfig.attribute_config_level]
  // is CATALOG_LEVEL_ATTRIBUTE_CONFIG, otherwise throws INVALID_FORMAT error.
  SearchableOption searchable_option = 7;

  // If EXACT_SEARCHABLE_ENABLED, attribute values will be exact searchable.
  // This property only applies to textual custom attributes and requires
  // indexable set to enabled to enable exact-searchable. If unset, the server
  // behavior defaults to
  // [EXACT_SEARCHABLE_DISABLED][google.cloud.retail.v2.CatalogAttribute.ExactSearchableOption.EXACT_SEARCHABLE_DISABLED].
  ExactSearchableOption exact_searchable_option = 11;

  // If RETRIEVABLE_ENABLED, attribute values are retrievable in the search
  // results. If unset, the server behavior defaults to
  // [RETRIEVABLE_DISABLED][google.cloud.retail.v2.CatalogAttribute.RetrievableOption.RETRIEVABLE_DISABLED].
  RetrievableOption retrievable_option = 12;

  // Contains facet options.
  FacetConfig facet_config = 13;
}

// Catalog level attribute config.
message AttributesConfig {
  option (google.api.resource) = {
    type: "retail.googleapis.com/AttributesConfig"
    pattern: "projects/{project}/locations/{location}/catalogs/{catalog}/attributesConfig"
  };

  // Required. Immutable. The fully qualified resource name of the attribute
  // config. Format: `projects/*/locations/*/catalogs/*/attributesConfig`
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.field_behavior) = IMMUTABLE
  ];

  // Enable attribute(s) config at catalog level.
  // For example, indexable, dynamic_facetable, or searchable for each
  // attribute.
  //
  // The key is catalog attribute's name.
  // For example: `color`, `brands`, `attributes.custom_attribute`, such as
  // `attributes.xyz`.
  //
  // The maximum number of catalog attributes allowed in a request is 1000.
  map<string, CatalogAttribute> catalog_attributes = 2;

  // Output only. The
  // [AttributeConfigLevel][google.cloud.retail.v2.AttributeConfigLevel] used
  // for this catalog.
  AttributeConfigLevel attribute_config_level = 3
      [(google.api.field_behavior) = OUTPUT_ONLY];
}

// Catalog level autocomplete config for customers to customize autocomplete
// feature's settings.
message CompletionConfig {
  option (google.api.resource) = {
    type: "retail.googleapis.com/CompletionConfig"
    pattern: "projects/{project}/locations/{location}/catalogs/{catalog}/completionConfig"
  };

  // Required. Immutable. Fully qualified name
  // `projects/*/locations/*/catalogs/*/completionConfig`
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.field_behavior) = IMMUTABLE
  ];

  // Specifies the matching order for autocomplete suggestions, e.g., a query
  // consisting of 'sh' with 'out-of-order' specified would suggest "women's
  // shoes", whereas a query of 'red s' with 'exact-prefix' specified would
  // suggest "red shoes". Currently supported values:
  //
  // * 'out-of-order'
  // * 'exact-prefix'
  //
  // Default value: 'exact-prefix'.
  string matching_order = 2;

  // The maximum number of autocomplete suggestions returned per term. Default
  // value is 20. If left unset or set to 0, then will fallback to default
  // value.
  //
  // Value range is 1 to 20.
  int32 max_suggestions = 3;

  // The minimum number of characters needed to be typed in order to get
  // suggestions. Default value is 2. If left unset or set to 0, then will
  // fallback to default value.
  //
  // Value range is 1 to 20.
  int32 min_prefix_length = 4;

  // If set to true, the auto learning function is enabled. Auto learning uses
  // user data to generate suggestions using ML techniques. Default value is
  // false. Only after enabling auto learning can users use `cloud-retail`
  // data in
  // [CompleteQueryRequest][google.cloud.retail.v2.CompleteQueryRequest].
  bool auto_learning = 11;

  // Output only. The source data for the latest import of the autocomplete
  // suggestion phrases.
  CompletionDataInputConfig suggestions_input_config = 5
      [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. Name of the LRO corresponding to the latest suggestion terms
  // list import.
  //
  // Can use [GetOperation][google.longrunning.Operations.GetOperation] API
  // method to retrieve the latest state of the Long Running Operation.
  string last_suggestions_import_operation = 6
      [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. The source data for the latest import of the autocomplete
  // denylist phrases.
  CompletionDataInputConfig denylist_input_config = 7
      [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. Name of the LRO corresponding to the latest denylist import.
  //
  // Can use [GetOperation][google.longrunning.Operations.GetOperation] API to
  // retrieve the latest state of the Long Running Operation.
  string last_denylist_import_operation = 8
      [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. The source data for the latest import of the autocomplete
  // allowlist phrases.
  CompletionDataInputConfig allowlist_input_config = 9
      [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. Name of the LRO corresponding to the latest allowlist import.
  //
  // Can use [GetOperation][google.longrunning.Operations.GetOperation] API to
  // retrieve the latest state of the Long Running Operation.
  string last_allowlist_import_operation = 10
      [(google.api.field_behavior) = OUTPUT_ONLY];
}

// The catalog configuration.
message Catalog {
  option (google.api.resource) = {
    type: "retail.googleapis.com/Catalog"
    pattern: "projects/{project}/locations/{location}/catalogs/{catalog}"
  };

  // Required. Immutable. The fully qualified resource name of the catalog.
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.field_behavior) = IMMUTABLE
  ];

  // Required. Immutable. The catalog display name.
  //
  // This field must be a UTF-8 encoded string with a length limit of 128
  // characters. Otherwise, an INVALID_ARGUMENT error is returned.
  string display_name = 2 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.field_behavior) = IMMUTABLE
  ];

  // Required. The product level configuration.
  ProductLevelConfig product_level_config = 4
      [(google.api.field_behavior) = REQUIRED];
}
