defmodule Mix.Tasks.Pyro.Generators.Components do
  @moduledoc false

  import Pyro.ComponentLibrary.TemplateHelpers

  require EEx

  def generate(module, opts \\ []) do
    path = Pyro.Info.component_output_path(module)
    embedded? = Keyword.get(opts, :embedded?, !path)
    components = Pyro.Info.components(module)
    module_name = append_module_name(module, "Components")
    source_file = Path.relative_to_cwd(module.__info__(:compile)[:source])
    transformer_hook = Pyro.Info.transformer_hook(module)

    code =
      render_module(
        components: components,
        module_name: module_name,
        source_file: source_file,
        embedded?: embedded?,
        transformer_hook: transformer_hook
      )

    if path do
      path = Path.relative_to_cwd(path)
      File.mkdir_p!(path)

      generated_file =
        Path.join(
          path,
          "#{module |> Module.split() |> List.last() |> Macro.underscore()}_components.ex"
        )

      {formatter_fun, _formatter_opts} = Mix.Tasks.Format.formatter_for_file(generated_file)

      File.write!(generated_file, formatter_fun.(code))
      code
    else
      {formatter_fun, _formatter_opts} = Mix.Tasks.Format.formatter_for_file(source_file)
      formatter_fun.(code)
    end
  end

  def render_attr(assigns) do
    EEx.eval_string(
      ~E'''
      attr <%= inspect @attr.name %>, <%= inspect @attr.type %><%=
        if @attr.default do %>, default: <%= inspect @attr.default %><% end %><%=
        if @attr.required do %>, required: true<% end %><%=
        if @attr.values do %>, values: <%= inspect @attr.values %><% end %><%=
        if @attr.doc do %>, doc: <%= inspect @attr.doc %><% end %>
      ''',
      assigns: assigns
    )
  end

  def render_variant(assigns) do
    EEx.eval_string(
      ~E'''
      attr <%= inspect @attr.name %>, <%= inspect @attr.type %><%=
        if @attr.default do %>, default: <%= inspect @attr.default %><% end %><%=
        if @attr.required do %>, required: true<% end %><%=
        if @attr.values do %>, values: <%= inspect @attr.values %><% end %><%=
        if @attr.doc do %>, doc: <%= inspect @attr.doc %><% end %>
      ''',
      assigns: assigns
    )
  end

  def render_global(assigns) do
    EEx.eval_string(
      ~E'''
      attr <%= inspect @attr.name %>, :global<%=
      if @attr.default do %>, default: <%= inspect @attr.default %><% end %><%=
        if @attr.include do %>, include: <%= inspect @attr.include %><% end %><%=
        if @attr.doc do %>, doc: <%= inspect @attr.doc %><% end %>
      ''',
      assigns: assigns
    )
  end

  def render_module(assigns) do
    EEx.eval_string(
      ~E'''
      <% import Mix.Tasks.Pyro.Generators.Components %>
      <%= if !@embedded? do %># NOTE: THIS FILE WAS AUTOMATICALLY GENERATED BY PYRO. DO NOT EDIT DIRECTLY.
        # Instead, configure <%= @source_file %><% end %>
      defmodule <%= @module_name %> do
        use Phoenix.Component
      <%= if !@embedded? do %>
        def __using__(_) do
          quote do
            import __MODULE__
          end
        end
      <% end %>
      <%= for component <- @components do %>
      <%= if component.doc do %>@doc """
      <%= component.doc %>
      """
      @doc type: :component<% end %>
        <%= if !component.doc && !component.private? do %>@doc false<% end%>
        <%= for %Pyro.ComponentLibrary.Dsl.Prop{} = attr <- component.assigns do %><%= render_attr(attr: attr) %><% end %>
        <%= for %Pyro.ComponentLibrary.Dsl.Variant{} = attr <- component.assigns do %><%= if @transformer_hook == attr.hook, do: render_variant(attr: attr) %><% end %>
        <%= for %Pyro.ComponentLibrary.Dsl.Global{} = attr <- component.assigns do %><%= render_global(attr: attr) %><% end %>
        <%= for render <- component.render do %>def<%= if component.private? do %>p<% end %> <%= component.name %>(<%= Macro.to_string(render.args) %>) do
            <%= Macro.to_string(render.expr) %>
        end
        <% end %>
        <% end %>
      end
      ''',
      assigns: assigns
    )
  end

  def append_module_name(module, append) do
    module |> Module.split() |> Kernel.++([append]) |> Enum.join(".")
  end
end
