defmodule Dagger.Codegen.ElixirGenerator.Renderer do
  @moduledoc """
  Provides functions to render small part of Elixir code.
  """

  alias Dagger.Codegen.ElixirGenerator.Formatter
  alias Dagger.Codegen.Introspection.Types.Type
  alias Dagger.Codegen.Introspection.Types.Field

  @doc """
  Render the module.
  """
  def render_module(type, body) do
    module = Formatter.format_module(type.name)

    [
      "# This file generated by `dagger_codegen`. Please DO NOT EDIT.",
      ?\n,
      "defmodule #{module} do",
      ?\n,
      render_moduledoc(type),
      ?\n,
      ?\n,
      body,
      "end"
    ]
  end

  @doc """
  Render the string.
  """
  def render_string(s) do
    [
      "\"\"\"",
      ?\n,
      escape(s),
      ?\n,
      "\"\"\""
    ]
  end

  @doc "Render atom value."
  def render_atom(string), do: ":#{string}"

  @doc """
  Render moduledoc from `type`.

  Uses the module name when no description in the type.
  """
  def render_moduledoc(type)

  def render_moduledoc(%Type{description: desc, name: name} = type)
      when desc == "" or is_nil(desc) do
    # Prevent ExDoc ignore documentation when moduledoc were not found.
    render_moduledoc(%{type | description: Formatter.format_module(name)})
  end

  def render_moduledoc(%Type{} = type) do
    [
      "@moduledoc",
      ~c" ",
      render_string(type.description)
    ]
  end

  @doc """
  Render function document.
  """
  def render_doc(field_or_enum)

  def render_doc(%{description: ""}), do: ""

  def render_doc(%{description: description, directives: directives}) do
    Enum.reduce(directives, description, &append_doc/2)
    |> render_doc()
  end

  def render_doc(text) do
    ["@doc", ~c" ", render_string(text)]
  end

  defp append_doc(%{name: "experimental", args: [%{name: "reason", value: reason}]}, doc) do
    [doc, ?\n, ?\n, render_warning("Experimental", escape(reason))]
  end

  defp append_doc(_, doc), do: doc

  @doc """
  Render function deprecation message.
  """
  def render_deprecated(field)

  def render_deprecated(%Field{deprecation_reason: ""}), do: ""
  def render_deprecated(%Field{deprecation_reason: nil}), do: ""

  def render_deprecated(%Field{deprecation_reason: reason}) do
    ["@deprecated", ~c" ", render_string(Formatter.format_doc(reason))]
  end

  @doc """
  Build the warning documentation block.
  """
  def render_warning(title, text) when is_binary(text) do
    [
      """
      > #### #{title} {: .warning}
      >
      """,
      text |> String.split("\n") |> Enum.map_intersperse(?\n, fn line -> "> #{line}" end)
    ]
  end

  defp escape(iodata) when is_list(iodata), do: iodata |> IO.iodata_to_binary() |> escape
  defp escape(s) when is_binary(s), do: String.replace(s, "\\", "\\\\")
end
