defmodule Ash.Resource.Verifiers.ValidateAggregatesSupported do
  @moduledoc """
  Confirms that all aggregates are supported by the data layer
  """
  use Spark.Dsl.Verifier

  alias Spark.Dsl.Entity
  alias Spark.Dsl.Verifier
  alias Spark.Error.DslError

  @impl true
  def verify(dsl_state) do
    resource = Verifier.get_persisted(dsl_state, :module)

    dsl_state
    |> Ash.Resource.Info.aggregates()
    |> Enum.each(fn aggregate ->
      if Map.get(aggregate, :related?, true) do
        check_aggregatable(dsl_state, resource, resource, aggregate, aggregate.relationship_path)
      else
        check_unrelated_aggregate_supported(dsl_state, resource, aggregate)
      end
    end)

    :ok
  end

  defp check_unrelated_aggregate_supported(_dsl_state, resource, aggregate) do
    name = aggregate.name
    kind = aggregate.kind
    location = Entity.anno(aggregate)
    can_do_aggregate_kind? = Ash.DataLayer.data_layer_can?(resource, {:aggregate, kind})
    can_do_unrelated? = Ash.DataLayer.data_layer_can?(resource, {:aggregate, :unrelated})

    cond do
      not can_do_aggregate_kind? ->
        raise DslError,
          module: resource,
          location: location,
          message: "data layer does not support #{kind} aggregates",
          path: [:aggregates, name]

      not can_do_unrelated? ->
        raise DslError,
          module: resource,
          location: location,
          message: "data layer does not support unrelated aggregates",
          path: [:aggregates, name]

      true ->
        :ok
    end
  end

  defp check_aggregatable(_dsl_state, _resource, _root_resource, _aggregate, []), do: :ok

  defp check_aggregatable(dsl_state, resource, root_resource, aggregate, [
         relationship_name | rest
       ]) do
    name = aggregate.name
    location = Entity.anno(aggregate)
    relationship = Ash.Resource.Info.relationship(resource, relationship_name)

    if !relationship do
      raise DslError,
        module: root_resource,
        location: location,
        message:
          "relationship referenced in aggregate `#{inspect(resource)}.#{relationship_name}` does not exist",
        path: [:aggregates, name]
    end

    if Ash.DataLayer.data_layer_can?(
         resource,
         {:aggregate_relationship, relationship}
       ) do
      check_aggregatable(dsl_state, relationship.destination, root_resource, aggregate, rest)
    else
      raise DslError,
        module: root_resource,
        location: location,
        message: "#{inspect(resource)}.#{relationship.name} is not aggregatable",
        path: [:aggregates, name]
    end
  end
end
