defmodule Absinthe.Phase.Document.Validation.UniqueInputFieldNames do
  @moduledoc false

  # Validates document to ensure that all input fields have unique names.

  alias Absinthe.{Blueprint, Phase}

  use Absinthe.Phase

  @doc """
  Run the validation.
  """
  @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
  def run(input, _options \\ []) do
    result = Blueprint.prewalk(input, &handle_node/1)
    {:ok, result}
  end

  # Find input objects
  @spec handle_node(Blueprint.node_t()) :: Blueprint.node_t()
  defp handle_node(%{normalized: %Blueprint.Input.Object{} = node} = parent) do
    fields = Enum.map(node.fields, &process(&1, node.fields))
    node = %{node | fields: fields}
    %{parent | normalized: node}
  end

  defp handle_node(node) do
    node
  end

  # Check an input field, finding any duplicates
  @spec process(Blueprint.Input.Field.t(), [Blueprint.Input.Field.t()]) ::
          Blueprint.Input.Field.t()
  defp process(field, fields) do
    check_duplicates(field, Enum.filter(fields, &(&1.name == field.name)))
  end

  # Add flags and errors if necessary for each input field
  @spec check_duplicates(Blueprint.Input.Field.t(), [Blueprint.Input.Field.t()]) ::
          Blueprint.Input.Field.t()
  defp check_duplicates(field, [_single]) do
    field
  end

  defp check_duplicates(field, _multiple) do
    field
    |> flag_invalid(:duplicate_name)
    |> put_error(error(field))
  end

  # Generate an error for an input field
  @spec error(Blueprint.Input.Field.t()) :: Phase.Error.t()
  defp error(node) do
    %Phase.Error{
      phase: __MODULE__,
      message: error_message(),
      locations: [node.source_location]
    }
  end

  @doc """
  Generate the error message.
  """
  @spec error_message :: String.t()
  def error_message do
    "Duplicate input field name."
  end
end
