defmodule Ash.Domain.Verifiers.ValidateRelatedResourceInclusion do
  @moduledoc """
  Ensures that all related resources are included in a domain.
  """
  use Spark.Dsl.Verifier
  alias Spark.Dsl.Verifier

  @impl true
  def verify(dsl) do
    resources =
      dsl
      |> Verifier.get_entities([:resources])
      |> Enum.map(& &1.resource)

    for resource <- resources do
      for relationship <- Ash.Resource.Info.relationships(resource) do
        if !Ash.Resource.Info.resource?(relationship.destination) do
          raise """
          Relationship #{inspect(resource)}.#{relationship.name} has an invalid destination: #{inspect(relationship.destination)}.
          """
        end

        domain = domain(relationship, dsl)

        if !domain do
          raise """
          Could not determine domain for relationship #{inspect(resource)}.#{relationship.name}.

          Please configure a domain on the relationship directly.
          """
        end

        if !Spark.Dsl.is?(domain, Ash.Domain) do
          raise """
          Domain #{inspect(domain)}, in #{inspect(resource)}.#{relationship.name} is not a valid `Ash.Domain`
          """
        end

        message = "is not accepted by domain `#{inspect(domain)}`"

        case Ash.Domain.Info.resource(domain, relationship.destination) do
          {:ok, _resource} ->
            :ok

          _ ->
            if relationship.type == :has_many && relationship.autogenerated_join_relationship_of do
              parent_relationship =
                Ash.Resource.Info.relationship(
                  resource,
                  relationship.autogenerated_join_relationship_of
                )

              raise """
              Error when compiling `#{inspect(relationship.source)}`: Resource `#{inspect(relationship.destination)}` #{message} for autogenerated join relationship: `:#{relationship.name}`

              Relationship was generated by the `many_to_many` relationship `#{inspect(parent_relationship.name)}`

              If the `through` resource `#{inspect(relationship.destination)}` is not accepted by the same
              domain as the destination resource `#{inspect(parent_relationship.destination)}`,
              then you must define that relationship manually. To define it manually, add the following to your
              relationships:

                  has_many :#{relationship.name}, #{inspect(relationship.destination)} do
                    # configure the relationship attributes
                    ...
                  end

              You can use a name other than `:#{relationship.name}`, but if you do, make sure to
              add that to `:#{parent_relationship.name}`, i.e

                  many_to_many :#{relationship.name}, #{inspect(relationship.destination)} do
                    ...
                    join_relationship :your_new_name
                  end
              """
            else
              raise """
              Error when compiling `#{inspect(relationship.source)}`: Resource `#{inspect(relationship.destination)}` in relationship `:#{relationship.name}` #{message}. Please do one of the following

              1. add the resource to the domain `#{inspect(domain)}`
              2. configure a different domain
              """
            end
        end
      end
    end

    :ok
  end

  defp domain(relationship, dsl) do
    if relationship.domain do
      relationship.domain
    else
      Ash.Resource.Info.domain(relationship.destination) || Verifier.get_persisted(dsl, :module)
    end
  end
end
