defmodule Ash.Query.Function.Contains do
  @moduledoc """
  Returns true if the first string contains the second.

  Case insensitive strings are accounted for on either side.

     contains("foo", "fo")
     true

     contains(%Ash.CiString{:string "foo"}, "FoO")
     true

     contains("foo", %Ash.CiString{:string "FOO"})
     true
  """
  use Ash.Query.Function, name: :contains, predicate?: true

  alias Ash.CiString

  def args,
    do: [
      [:string, :string],
      [:string, :ci_string],
      [:ci_string, :string],
      [:ci_string, :ci_string]
    ]

  def returns, do: [:string, :ci_string, :ci_string, :ci_string]

  def evaluate(%{arguments: [nil, _]}), do: {:known, false}
  def evaluate(%{arguments: [_, nil]}), do: {:known, false}

  def evaluate(%{arguments: [%CiString{} = left, %CiString{} = right]}) do
    {:known,
     String.contains?(CiString.to_comparable_string(left), CiString.to_comparable_string(right))}
  end

  def evaluate(%{arguments: [left, %Ash.CiString{} = right]}) when is_binary(left) do
    {:known,
     left
     |> Ash.CiString.to_comparable_string()
     |> String.contains?(Ash.CiString.to_comparable_string(right))}
  end

  def evaluate(%{arguments: [%Ash.CiString{} = left, right]})
      when is_binary(right) do
    {:known,
     String.contains?(
       Ash.CiString.to_comparable_string(left),
       Ash.CiString.to_comparable_string(right)
     )}
  end

  def evaluate(%{arguments: [left, right]})
      when is_binary(left) and is_binary(right) do
    {:known, String.contains?(left, right)}
  end

  def evaluate(_other) do
    :unknown
  end

  def can_return_nil?(%{arguments: arguments}) do
    Enum.any?(arguments, &Ash.Expr.can_return_nil?/1)
  end
end
