lib/blunt/context.ex

defmodule Blunt.Context do
  alias Blunt.Context
  alias Blunt.Context.Proxy

  defmodule Error do
    defexception [:message]
  end

  defmacro __using__(_opts) do
    quote do
      use Blunt.Message.Compilation

      Module.register_attribute(__MODULE__, :proxies, accumulate: true)
      Module.register_attribute(__MODULE__, :messages, accumulate: true, persist: true)

      @before_compile Blunt.Context
      @after_compile Blunt.Context

      import Blunt.Context, only: :macros
    end
  end

  defmacro command(message_module, opts \\ []) do
    quote bind_quoted: [message_module: message_module, opts: opts] do
      {function_name, _opts} = Proxy.function_name(message_module, opts)

      @messages {:command, message_module, function_name}
      @proxies {{:command, message_module, opts}, {__ENV__.file, __ENV__.line}}
    end
  end

  defmacro query(message_module, opts \\ []) do
    quote bind_quoted: [message_module: message_module, opts: opts] do
      {function_name, _opts} = Proxy.function_name(message_module, opts)

      @messages {:query, message_module, function_name}
      @proxies {{:query, message_module, opts}, {__ENV__.file, __ENV__.line}}
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      Enum.map(@proxies, fn {message_info, {file, line}} ->
        code = Proxy.generate(message_info)

        __ENV__
        |> Map.put(:file, file)
        |> Map.put(:line, line)
        |> Module.eval_quoted(code)
      end)
    end
  end

  defmacro __after_compile__(%{module: module}, _bytecode) do
    module
    |> Context.proxied_messages()
    |> Enum.each(&Proxy.validate!(&1, module))

    nil
  end

  @doc false
  def proxied_messages(bounded_context_module) do
    :attributes
    |> bounded_context_module.__info__()
    |> Keyword.get_values(:messages)
    |> List.flatten()
  end
end