defmodule Dagger.Mod.Object.Defn do
  @moduledoc false

  @doc false
  def define(name, args, return, block) do
    typespec_args =
      case args do
        {_self, args} ->
          [quote(do: t()) | Enum.map(args, &typespec_arg/1)]

        args ->
          Enum.map(args, &typespec_arg/1)
      end

    args =
      case args do
        {self, args} ->
          [var(self) | Enum.map(args, &var/1)]

        args ->
          Enum.map(args, &var/1)
      end

    quote do
      @spec unquote(name)(unquote_splicing(typespec_args)) :: unquote(return)
      def unquote(name)(unquote_splicing(args)) do
        unquote(block)
      end
    end
  end

  # {var, {type, opts}}
  defp var({name, {_, opts}}) do
    if Keyword.has_key?(opts, :default) do
      {:\\, [], [Macro.var(name, nil), opts[:default]]}
    else
      Macro.var(name, nil)
    end
  end

  # {var, type}
  defp var({name, _}) do
    Macro.var(name, nil)
  end

  # var
  defp var({name, _, nil}) do
    Macro.var(name, nil)
  end

  # pattern = var
  defp var({:=, _, [_, v]}) do
    var(v)
  end

  defp typespec_arg({name, {type, _}}) do
    typespec_arg({name, type})
  end

  defp typespec_arg({_, type} = arg) do
    arg = var(arg)

    quote do
      unquote(arg) :: unquote(type)
    end
  end
end
