// Copyright 2004-present Facebook. All Rights Reserved.

package com.facebook.react.uimanager.annotations;

import javax.annotation.Nullable;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Use this annotation to annotate group of properties of native views that should be exposed to JS.
 * This annotation should only be used for setter methods of subclasses of
 * {@link com.facebook.react.uimanager.ViewManager}. It's a batched version of {@link ReactProp}
 * annotation (please see documentation of {@link ReactProp} for more details about how this
 * annotation can be used).
 *
 * This annotation is meant to be used for a group of similar properties. That's why it support only
 * a set of properties of the same type. A good example is supporting "border", where we have 7
 * variations of that property ("borderLeft", "borderHorizontal", etc.) and very similar code for
 * handling each of those.
 *
 * Each annotated method should return {@code void} and take exactly three arguments: first being
 * a view instance to be updated, second should be of type int and will represent index in the
 * group of the property being updated. Last, third argument represent the value that should be set.
 *
 *
 * Currently only {@code int}, {@code float}, {@code double} and {@link String} value types are
 * supported.
 *
 * In case when property has been removed from the corresponding react component annotated setter
 * will be called and default value will be provided as a value parameter. Default value can be
 * customize using {@link #defaultInt} or {@link #defaultFloat} in the case when property is of
 * one of primitive types. In case when {@link String} is the property type {@code null} value will
 * be provided as a default.
 */
@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface ReactPropGroup {

  // Used as a default value for "customType" property as "null" is not allowed. Moreover, when this
  // const is used in annotation declaration compiler will actually create a copy of it, so
  // comparing it using "==" with this filed doesn't work either. We need to compare using "equals"
  // which means that this value needs to be unique.
  String USE_DEFAULT_TYPE = "__default_type__";

  /**
   * Array of names of properties exposed to JS that will be updated using setter method annotated
   * with the given instance of {@code ReactPropGroup} annotation
   */
  String[] names();

  /**
   * Type of property that will be send to JS. In most of the cases {@code customType} should not be
   * set in which case default type will be send to JS based on the type of value argument from the
   * setter method (e.g. for {@code int}, {@code float} default is "number"). Custom type may be
   * used when additional processing of the value needs to be done in JS before sending it over the
   * bridge. A good example of that would be backgroundColor property, which is expressed as a
   * {@code String} in JS, but we use {@code processColor} JS module to convert it to {@code int}
   * before sending over the bridge.
   */
  @Nullable String customType() default USE_DEFAULT_TYPE;

  /**
   * Default value for property of type {@code float}. This value will be provided to property
   * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
   * from the component description in JS
   */
  float defaultFloat() default 0.0f;

  /**
   * Default value for property of type {@code double}. This value will be provided to property
   * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
   * from the component description in JS
   */
  double defaultDouble() default 0.0;

  /**
   * Default value for property of type {@code int}. This value will be provided to property
   * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
   * from the component description in JS
   */
  int defaultInt() default 0;
}
