defmodule Mix.Tasks.Ecsx.Gen.Component do
  @shortdoc "Generates a new ECSx Component type"

  @moduledoc """
  Generates a new Component type for an ECSx application.

      $ mix ecsx.gen.component Height integer

  The first argument is the name of the component, followed by the data type of the value.

  Valid types for the component's value are:

    * atom
    * binary
    * datetime
    * float
    * integer

  If you know you want components of this type to be indexed for improved `ECSx.Component.search/1` performance,
  you may include the `--index` option:

      $ mix ecsx.gen.component Name binary --index

  """

  use Mix.Task

  alias Mix.Tasks.ECSx.Helpers

  @valid_value_types ~w(atom binary datetime float integer)

  @doc false
  def run([]) do
    "Invalid arguments."
    |> message_with_help()
    |> Mix.raise()
  end

  def run([_component_type]) do
    "Invalid arguments - must provide value type. If you don't want to store a value, try `mix ecsx.gen.tag`"
    |> message_with_help()
    |> Mix.raise()
  end

  def run([component_type_name, value_type | opts]) do
    value_type = validate(value_type)
    {opts, _, _} = OptionParser.parse(opts, strict: [index: :boolean])
    Helpers.inject_component_module_into_manager(component_type_name)
    create_component_file(component_type_name, value_type, opts)
  end

  defp message_with_help(message) do
    """
    #{message}

    mix ecsx.gen.component expects a component module name (in PascalCase), followed by a valid value type.

    For example:

        mix ecsx.gen.component MyComponentType binary

    """
  end

  defp validate(type) when type in @valid_value_types, do: String.to_atom(type)

  defp validate(_),
    do: Mix.raise("Invalid value type. Possible types are: #{inspect(@valid_value_types)}")

  defp create_component_file(component_type_name, value_type, opts) do
    filename = Macro.underscore(component_type_name)
    target = "lib/#{Helpers.otp_app()}/components/#{filename}.ex"
    source = Application.app_dir(:ecsx, "/priv/templates/component.ex")

    binding = [
      app_name: Helpers.root_module(),
      index: Keyword.get(opts, :index, false),
      component_type: component_type_name,
      value: value_type
    ]

    Mix.Generator.create_file(target, EEx.eval_file(source, binding))
  end
end
