/*
 * Copyright 2024 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.
 */

package com.google.cloud.firestore;

import java.util.Objects;
import javax.annotation.Nullable;

/**
 * Specifies the behavior of the {@link VectorQuery} generated by a call to {@link
 * Query#findNearest}.
 */
public class VectorQueryOptions {
  private final @Nullable FieldPath distanceResultField;

  private final @Nullable Double distanceThreshold;

  @Nullable
  public FieldPath getDistanceResultField() {
    return distanceResultField;
  }

  @Nullable
  public Double getDistanceThreshold() {
    return distanceThreshold;
  }

  VectorQueryOptions(VectorQueryOptions.Builder builder) {
    this.distanceThreshold = builder.distanceThreshold;
    this.distanceResultField = builder.distanceResultField;
  }

  public static VectorQueryOptions.Builder newBuilder() {
    return new VectorQueryOptions.Builder();
  }

  public static final class Builder {
    /**
     * Returns the name of the field that will be set on each returned DocumentSnapshot, which will
     * contain the computed distance for the document. If `null`, then the computed distance will
     * not be returned. Default value: `null`.
     *
     * <p>Set this value with {@link VectorQueryOptions.Builder#setDistanceResultField(FieldPath)}
     * or {@link VectorQueryOptions.Builder#setDistanceResultField(String)}.
     */
    private @Nullable FieldPath distanceResultField;

    /**
     * Specifies a threshold for which no less similar documents will be returned. If `null`, then
     * the computed distance will not be returned. Default value: `null`.
     *
     * <p>Set this value with {@link VectorQueryOptions.Builder#setDistanceThreshold(Double)}.
     */
    private @Nullable Double distanceThreshold;

    private Builder() {
      distanceThreshold = null;
      distanceResultField = null;
    }

    private Builder(VectorQueryOptions options) {
      this.distanceThreshold = options.distanceThreshold;
      this.distanceResultField = options.distanceResultField;
    }

    /**
     * Specifies the name of the field that will be set on each returned DocumentSnapshot, which
     * will contain the computed distance for the document. If `null`, then the computed distance
     * will not be returned. Default value: `null`.
     *
     * @param fieldPath A string value specifying the distance result field.
     */
    public Builder setDistanceResultField(@Nullable String fieldPath) {
      this.distanceResultField = FieldPath.fromDotSeparatedString(fieldPath);
      return this;
    }

    /**
     * Specifies the name of the field that will be set on each returned DocumentSnapshot, which
     * will contain the computed distance for the document. If `null`, then the computed distance
     * will not be returned. Default value: `null`.
     *
     * @param fieldPath A {@link FieldPath} value specifying the distance result field.
     */
    public Builder setDistanceResultField(@Nullable FieldPath fieldPath) {
      this.distanceResultField = fieldPath;
      return this;
    }

    /**
     * Specifies a threshold for which no less similar documents will be returned. The behavior of
     * the specified `distanceMeasure` will affect the meaning of the distance threshold.
     *
     * <p>
     *
     * <ul>
     *   <li>For `distanceMeasure: "EUCLIDEAN"`, the meaning of `distanceThreshold` is: {@code
     *       SELECT docs WHERE euclidean_distance <= distanceThreshold}
     *   <li>For `distanceMeasure: "COSINE"`, the meaning of `distanceThreshold` is: {@code SELECT
     *       docs WHERE cosine_distance <= distanceThreshold}
     *   <li>For `distanceMeasure: "DOT_PRODUCT"`, the meaning of `distanceThreshold` is: {@code
     *       SELECT docs WHERE dot_product_distance >= distanceThreshold}
     * </ul>
     *
     * <p>If `null`, then the computed distance will not be returned. Default value: `null`.
     *
     * @param distanceThreshold A Double value specifying the distance threshold.
     */
    public Builder setDistanceThreshold(@Nullable Double distanceThreshold) {
      this.distanceThreshold = distanceThreshold;
      return this;
    }

    public VectorQueryOptions build() {
      return new VectorQueryOptions(this);
    }
  }

  /**
   * Returns true if this VectorQueryOptions is equal to the provided object.
   *
   * @param obj The object to compare against.
   * @return Whether this VectorQueryOptions is equal to the provided object.
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null || !(obj instanceof VectorQueryOptions)) {
      return false;
    }
    VectorQueryOptions otherOptions = (VectorQueryOptions) obj;
    return Objects.equals(distanceResultField, otherOptions.distanceResultField)
        && Objects.equals(distanceThreshold, otherOptions.distanceThreshold);
  }

  @Override
  public String toString() {
    return String.format(
        "VectorQueryOptions{distanceResultField=%s, distanceThreshold=%s}",
        distanceResultField, distanceThreshold);
  }

  /** Default VectorQueryOptions instance. */
  private static VectorQueryOptions DEFAULT = newBuilder().build();

  /**
   * Returns a default {@code FirestoreOptions} instance. Note: package private until API review can
   * be completed.
   */
  static VectorQueryOptions getDefaultInstance() {
    return DEFAULT;
  }
}
