defmodule Absinthe.Type.Field do
  alias Absinthe.Type

  @moduledoc """
  Used to define a field.

  Usually these are defined using `Absinthe.Schema.Notation.field/4`

  See the `t` type below for details and examples of how to define a field.
  """

  alias Absinthe.Type
  alias Absinthe.Type.Deprecation

  @typedoc """
  A resolver function.

  See the `Absinthe.Type.Field.t` explanation of `:resolve` for more information.
  """
  @type resolver_t ::
          (Absinthe.Resolution.arguments(), Absinthe.Resolution.t() -> result)
          | (Absinthe.Resolution.source(),
             Absinthe.Resolution.arguments(),
             Absinthe.Resolution.t() ->
               result)
          | {module(), atom()}

  @typedoc """
  The result of a resolver.
  """
  @type result :: ok_result | error_result | middleware_result

  @typedoc """
  A complexity function.

  See the `Absinthe.Type.Field/t` explanation of `:complexity` for more
  information.
  """
  @type complexity_t ::
          (%{atom => any}, non_neg_integer -> non_neg_integer)
          | (%{atom => any}, non_neg_integer, Absinthe.Complexity.t() -> non_neg_integer)
          | {module, atom}
          | non_neg_integer

  @type ok_result :: {:ok, any}
  @type error_result :: {:error, error_value}
  @type middleware_result :: {:middleware, Absinthe.Middleware.spec(), term}

  @typedoc """
  An error message is a human-readable string describing the error that occurred.
  """
  @type error_message :: String.t()

  @typedoc """
  Any serializable value.
  """
  @type serializable :: any

  @typedoc """
  A custom error may be a `map` or a `Keyword.t`, but must contain a `:message` key.

  Note that the values that make up a custom error must be serializable.
  """
  @type custom_error ::
          %{required(:message) => error_message, optional(atom) => serializable} | Keyword.t()

  @typedoc """
  An error value is a simple error message, a custom error, or a list of either/both of them.
  """
  @type error_value ::
          error_message | custom_error | [error_message | custom_error] | serializable

  @typedoc """
  The configuration for a field.

  * `:name` - The name of the field, usually assigned automatically by
     the `Absinthe.Schema.Notation.field/4`. Including this option will bypass the snake_case to camelCase conversion.
  * `:description` - Description of a field, useful for introspection.
  * `:deprecation` - Deprecation information for a field, usually
     set-up using `Absinthe.Schema.Notation.deprecate/1`.
  * `:type` - The type the value of the field should resolve to
  * `:args` - The arguments of the field, usually created by using `Absinthe.Schema.Notation.arg/2`.
  * `:resolve` - The resolution function. See below for more information.
  * `:complexity` - The complexity function. See below for more information.
  * `:default_value` - The default value of a field. Note this is not used during resolution; only fields that are part of an `Absinthe.Type.InputObject` should set this value.

  ## Resolution Functions

  ### Default

  If no resolution function is given, the default resolution function is used,
  which is roughly equivalent to this:

      {:ok, Map.get(parent_object, field_name)}

  This is commonly use when listing the available fields on a
  `Absinthe.Type.Object` that models a data record. For instance:

  ```
  object :person do
    description "A person"

    field :first_name, :string
    field :last_name, :string
  end
  ```
  ### Custom Resolution

  When accepting arguments, however, you probably need to use them for
  something. Here's an example of defining a field that looks up a list of
  users for a given `location_id`:
  ```
  query do
    field :users, :person do
      arg :location_id, non_null(:id)

      resolve fn %{location_id: id}, _ ->
        {:ok, MyApp.users_for_location_id(id)}
      end
    end
  end
  ```

  Custom resolution functions are passed two arguments:

  1. A map of the arguments for the field, filled in with values from the
     provided query document/variables.
  2. An `Absinthe.Resolution` struct, containing the execution environment
     for the field (and useful for complex resolutions using the resolved source
     object, etc)

  ## Complexity function

  ### Default

  If no complexity function is given, the default complexity function is used,
  which is equivalent to:

      fn(_, child_complexity) -> 1 + child_complexity end

  ### Custom Complexity

  When accepting arguments, however, you probably need to use them for
  something. Here's an example of defining a field that looks up at most
  `limit` users:
  ```
  query do
    field :users, :person do
      arg :limit, :integer

      complexity fn %{limit: limit}, child_complexity ->
        10 + limit * child_complexity
      end
    end
  end
  ```

  An optional third argument, `Absinthe.Complexity` struct, provides extra
  information. Here's an example of changing the complexity using the context:
  ```
  query do
    field :users, :person do
      arg :limit, :integer

      complexity fn _, child_complexity, %{context: %{admin: admin?}} ->
        if admin?, do: 0, else: 10 + limit * child_complexity
      end
    end
  end
  ```

  Custom complexity functions are passed two or three arguments:

  1. A map of the arguments for the field, filled in with values from the
     provided query document/variables.
  2. A non negative integer, which is total complexity of the child fields.
  3. An `Absinthe.Complexity` struct with information about the context of the
     field. This argument is optional when using an anonymous function.

  Alternatively complexity can be an integer greater than or equal to 0:
  ```
  query do
    field :users, :person do
      complexity 10
    end
  end
  ```

  """
  @type t :: %__MODULE__{
          identifier: atom,
          name: binary,
          description: binary | nil,
          type: Type.identifier_t(),
          deprecation: Deprecation.t() | nil,
          default_value: any,
          args: %{(binary | atom) => Absinthe.Type.Argument.t()} | nil,
          middleware: [],
          complexity: complexity_t | nil,
          __private__: Keyword.t(),
          definition: module,
          __reference__: Type.Reference.t()
        }

  defstruct identifier: nil,
            name: nil,
            description: nil,
            type: nil,
            deprecation: nil,
            args: %{},
            # used by subscription fields
            config: nil,
            # used by mutation fields
            triggers: [],
            middleware: [],
            complexity: nil,
            default_value: nil,
            __private__: [],
            definition: nil,
            __reference__: nil

  @doc false
  defdelegate functions, to: Absinthe.Blueprint.Schema.FieldDefinition
end
