defmodule Etso.Adapter do
  @moduledoc """
  Used as an Adapter in Repo modules, which transparently spins up one ETS table for each Schema
  used with the Repo, namespaced to the Repo to allow concurrent running of multiple Repositories.

  The Etso Adapter implements the `Ecto.Adapter.Schema` and `Ecto.Adapter.Queryable` behaviours.

  To use the Etso Adapter in your application, define a Repo like this:

      defmodule MyApp.Repo do
        @otp_app Mix.Project.config()[:app]
        use Ecto.Repo, otp_app: @otp_app, adapter: Etso.Adapter
      end
  """

  @behaviour Ecto.Adapter
  @behaviour Ecto.Adapter.Schema
  @behaviour Ecto.Adapter.Queryable

  @impl Ecto.Adapter
  defmacro __before_compile__(_opts), do: :ok

  @doc false
  @impl Ecto.Adapter
  def ensure_all_started(_config, _type), do: {:ok, []}

  @doc false
  @impl Ecto.Adapter
  def init(config) do
    {:ok, repo} = Keyword.fetch(config, :repo)
    child_spec = __MODULE__.Supervisor.child_spec(repo)
    adapter_meta = %__MODULE__.Meta{repo: repo}
    {:ok, child_spec, adapter_meta}
  end

  @doc false
  @impl Ecto.Adapter
  def checkout(_, _, fun), do: fun.()

  @doc false
  @impl Ecto.Adapter
  def checked_out?(_), do: false

  @doc false
  @impl Ecto.Adapter
  def loaders(:binary_id, type), do: [Ecto.UUID, type]
  def loaders(:embed_id, type), do: [Ecto.UUID, type]
  def loaders(_, type), do: [type]

  @doc false
  @impl Ecto.Adapter
  def dumpers(:binary_id, type), do: [type, Ecto.UUID]
  def dumpers(:embed_id, type), do: [type, Ecto.UUID]
  def dumpers(_, type), do: [type]

  for {implementation_module, behaviour_module} <- [
        {__MODULE__.Behaviour.Schema, Ecto.Adapter.Schema},
        {__MODULE__.Behaviour.Queryable, Ecto.Adapter.Queryable}
      ] do
    for {name, arity} <- implementation_module.__info__(:functions) do
      args = Enum.map(1..arity, &{:"arg_#{&1}", [], Elixir})

      @doc false
      @impl behaviour_module
      def unquote(name)(unquote_splicing(args)) do
        unquote(implementation_module).unquote(name)(unquote_splicing(args))
      end
    end
  end
end
