defmodule Ash.Info do
  @moduledoc """
  General introspection helpers for Ash applications.

  Use `Ash.Info.mermaid_overview(otp_app)` to generate a mermaid chart of the application's domains and resources.
  This offers a high level view, but will not include information from extensions. Use `resource_report/2`
  for a detailed report of a resource.

  Use `Ash.Info.mermaid_overview(otp_app, type: :entity_relationship)` to get a simplified entity relationship diagram.

  Use `Ash.Info.domains_and_resources(otp_app)` to get a map where the keys are the domains, and the value is the list
  of resources for that domain.
  """

  @spec domains(atom()) :: [Ash.Domain.t()]
  def domains(otp_app) do
    Application.get_env(otp_app, :ash_domains, [])
  end

  @doc """
  Returns a map where the keys are the domains, and the value is the list of resources for that domain.
  """
  @spec domains_and_resources(atom()) :: %{Ash.Domain.t() => [Ash.Resource.t()]}
  def domains_and_resources(otp_app) do
    otp_app
    |> Application.get_env(:ash_domains, [])
    |> Map.new(&{&1, Ash.Domain.Info.resources(&1)})
  end

  @doc """
  Generate a mermaid chart of the application's domains and resources.

  This offers a high level view, but will not include information from extensions.
  """
  def mermaid_overview(otp_app, type \\ :class) do
    case type do
      :class ->
        Ash.Domain.Info.Diagram.mermaid_class_diagram(domains(otp_app),
          show_private?: true,
          name: :full,
          include_source_links?: true
        )

      :entity_relationship ->
        Ash.Domain.Info.Diagram.mermaid_er_diagram(domains(otp_app),
          show_private?: true,
          name: :full,
          include_source_links?: true
        )
    end
  end

  @doc """
  Returns a list of extensions in use by all the domains and resources in the
  given application.
  """
  @spec extensions_in_use(app :: Application.app()) :: [module()]
  def extensions_in_use(app) do
    app
    |> domains()
    |> Enum.flat_map(&Ash.Domain.Info.extensions(&1, include_resource_extensions?: true))
    |> Enum.uniq()
  end

  @doc """
  Returns a list of all defined extensions in the application.

  > #### Speed {: .warning}
  >
  > This function will load all modules in the application, which can be slow.
  > Do not call this function in time sensitive code paths, It is intended for
  > mix tasks, introspection, and debugging purposes.
  """
  @spec defined_extensions(app :: Application.app()) :: [module()]
  def defined_extensions(app) do
    modules = Application.spec(app, :modules)
    # Preload the modules to improve performance.
    Code.ensure_all_loaded(modules)
    Enum.filter(modules, &Spark.implements_behaviour?(&1, Spark.Dsl.Extension))
  end
end
