defmodule Mix.Tasks.Phx.Gen.Tailwind do
  @shortdoc "Adds Tailwind to an existing Phoenix application"

  @moduledoc """
  Adds Tailwind to an existing Phoenix application.

      mix phx.gen.tailwind

  It will do the following:

  * Create an `assets/package.json` file.
  * Install `tailwindcss`
  * Create a `tailwind.config.js` file with sane defaults
  * Add tailwind imports to your CSS file
  * Add a Tailwind watcher to your Phoenix endpoint
  * Update the `assets.deploy` mix task to include Tailwind processing
  """

  use Mix.Task

  # alias Mix.Phoenix.Context
  # alias Mix.Tasks.Phx.Gen
  alias Mix.Phx.Gen.Tailwind.Injector

  # @switches [alpinejs: :boolean]

  @impl true
  def run(_args) do
    if Mix.Project.umbrella?() do
      Mix.raise(
        "mix phx.gen.live must be invoked from within your *_web application root directory"
      )
    end

    # from here on, we can assume we are at the web root directory

    # {opts, _parsed} = OptionParser.parse!(args, strict: @switches)

    context = %{
      web_app_name: Mix.Phoenix.otp_app(),
      in_umbrella?: Mix.Phoenix.in_umbrella?(File.cwd!())
    }

    # 1. copy files
    paths = generator_paths()

    files = [
      {:text, "package.json", "assets/package.json"},
      {:text, "tailwind.config.js", "assets/tailwind.config.js"}
    ]

    Mix.Phoenix.copy_from(paths, "priv/templates/phx.gen.tailwind", [], files)

    # 2. add imports to CSS file
    inject_css_imports()

    # 3. add watcher to config/dev.exs
    inject_dev_config(context)

    # 4. update mix aliases
    inject_mix_aliases()

    # remove css import from app.js
    remove_js_css_import()

    # 5. run 'npm install'
    maybe_run_npm_install()
  end

  defp generator_paths do
    [".", :phx_gen_tailwind]
  end

  defp inject_dev_config(%{in_umbrella?: in_umbrella?} = context) do
    file_path =
      if in_umbrella? do
        Path.expand("../../")
      else
        File.cwd!()
      end
      |> Path.join("config/dev.exs")

    {:ok, file} = read_file(file_path)

    case Injector.dev_config_inject(file, context) do
      {:ok, new_file} ->
        print_injecting(file_path)
        File.write!(file_path, new_file)

      :already_injected ->
        :ok

      {:error, :unable_to_inject} ->
        Mix.shell().info("""
        UNABLE to inject dev/config.exs changes.
        """)
    end
  end

  defp inject_mix_aliases() do
    file_path = "mix.exs"
    file = File.read!(file_path)

    case Injector.mix_aliases_inject(file) do
      {:ok, new_file} ->
        print_injecting(file_path)
        File.write!(file_path, new_file)

      :already_injected ->
        :ok

      {:error, :unable_to_inject} ->
        Mix.shell().info("""
        Unable to inject updated 'assets.deploy' alias
        """)
    end
  end

  defp inject_css_imports() do
    file_path = "assets/css/app.css"
    file = File.read!(file_path)

    case Injector.css_import_inject(file) do
      {:ok, new_file} ->
        print_injecting(file_path)
        File.write!(file_path, new_file)

      :already_injected ->
        :ok

      {:error, :unable_to_inject} ->
        Mix.shell().info("""
        Unable to inject Tailwindcss imports into app.css
        """)
    end
  end

  defp remove_js_css_import() do
    file_path = "assets/js/app.js"
    file = File.read!(file_path)

    case Injector.js_css_import_remove(file) do
      {:ok, new_file} ->
        print_injecting(file_path)
        File.write!(file_path, new_file)

      :already_injected ->
        :ok

      {:error, :unable_to_inject} ->
        Mix.shell().info("""
        Unable to remove css import from app.js
        """)
    end
  end

  defp maybe_run_npm_install() do
    if !!System.find_executable("npm") and Mix.shell().yes?("\nNPM install new dependencies?") do
      cmd = "cd assets/ && npm install"
      Mix.shell().info([:green, "* running ", :reset, cmd])
      Mix.shell().cmd(cmd, quiet: true)
    end
  end

  defp read_file(file_path) do
    case File.read(file_path) do
      {:ok, file} -> {:ok, file}
      {:error, reason} -> {:error, {:file_read_error, reason}}
    end
  end

  defp print_injecting(file_path, suffix \\ []) do
    Mix.shell().info([:green, "* injecting ", :reset, Path.relative_to_cwd(file_path), suffix])
  end
end
