defmodule Absinthe.Phase.Document.Arguments.FlagInvalid do
  @moduledoc false

  # Marks arguments as bad if they have any invalid children.
  #
  # This is later used by the ArgumentsOfCorrectType phase.

  alias Absinthe.{Blueprint, Phase, Type}

  use Absinthe.Phase

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

  defp handle_node(%{schema_node: nil, flags: %{}} = node) do
    node |> flag_invalid(:extra)
  end

  defp handle_node(%Blueprint.Input.Argument{} = node) do
    check_child(node, node.input_value.normalized, :bad_argument)
  end

  defp handle_node(%Blueprint.Input.Field{} = node) do
    check_child(node, node.input_value.normalized, :bad_field)
  end

  defp handle_node(%Blueprint.Input.List{} = node) do
    check_children(node, node.items |> Enum.map(& &1.normalized), :bad_list)
  end

  defp handle_node(%Blueprint.Input.Object{schema_node: %Type.Scalar{open_ended: true}} = node) do
    node
  end

  defp handle_node(%Blueprint.Input.Object{} = node) do
    check_children(node, node.fields, :bad_object)
  end

  defp handle_node(node), do: node

  defp check_child(node, %{flags: %{invalid: _}}, flag) do
    flag_invalid(node, flag)
  end

  defp check_child(node, _, _) do
    node
  end

  defp check_children(node, children, flag) do
    invalid? = fn
      %{flags: %{invalid: _}} -> true
      _ -> false
    end

    if Enum.any?(children, invalid?) do
      flag_invalid(node, flag)
    else
      node
    end
  end
end
