lib/mix/tasks/octa_star.install.ex

defmodule Mix.Tasks.OctaStar.Install do
  @shortdoc "Installs OctaStar into your project"

  @moduledoc """
  Installs OctaStar and optionally sets up recommended configurations.

  ## Options

    * `--no-stream-dedup` — Skip adding `OctaStar.Utility.StreamRegistry` to your supervision tree.
    * `--no-https` — Skip HTTPS dev configuration (Phoenix only).
    * `--no-example` — Skip generating the sample controller/handler.

  ## Examples

      mix igniter.install octa_star
      mix igniter.install octa_star --no-stream-dedup
      mix igniter.install octa_star --no-https --no-example
  """

  use Igniter.Mix.Task

  @impl Igniter.Mix.Task
  def info(_argv, _composing_task) do
    %Igniter.Mix.Task.Info{
      group: :octa_star,
      schema: [
        stream_dedup: :boolean,
        https: :boolean,
        example: :boolean
      ],
      defaults: [
        stream_dedup: true,
        https: true,
        example: true
      ],
      aliases: [],
      example: "mix igniter.install octa_star"
    }
  end

  @impl Igniter.Mix.Task
  def installer?(), do: true

  @impl Igniter.Mix.Task
  def igniter(igniter) do
    options = igniter.args.options
    stream_dedup? = Keyword.get(options, :stream_dedup, true)
    https? = Keyword.get(options, :https, true)
    example? = Keyword.get(options, :example, true)

    app_name = Igniter.Project.Application.app_name(igniter)
    web_module = Igniter.Project.Module.module_name(igniter, "Web")
    endpoint_module = Igniter.Project.Module.module_name(igniter, "Web.Endpoint")

    {phoenix?, igniter} = Igniter.Project.Module.module_exists(igniter, endpoint_module)

    igniter
    |> maybe_add_stream_registry(stream_dedup?)
    |> maybe_setup_https(https?, app_name, endpoint_module, phoenix?)
    |> maybe_generate_example(example?, web_module, phoenix?)
    |> maybe_patch_router(example?, web_module, phoenix?)
    |> maybe_print_post_install(phoenix?)
  end

  defp maybe_add_stream_registry(igniter, false), do: igniter

  defp maybe_add_stream_registry(igniter, true) do
    Igniter.Project.Application.add_new_child(
      igniter,
      OctaStar.Utility.StreamRegistry,
      after: [Phoenix.PubSub]
    )
  end

  defp maybe_setup_https(igniter, false, _, _, _), do: igniter
  defp maybe_setup_https(igniter, _https?, _app_name, _endpoint, false), do: igniter

  defp maybe_setup_https(igniter, true, app_name, endpoint_module, true) do
    Igniter.Project.Config.configure(
      igniter,
      "dev.exs",
      app_name,
      [endpoint_module, :https],
      [
        port: 4001,
        cipher_suite: :strong,
        keyfile: "priv/cert/selfsigned_key.pem",
        certfile: "priv/cert/selfsigned.pem"
      ]
    )
    |> Igniter.add_notice("""
    HTTPS configured for dev on port 4001.

    Run: mix phx.gen.cert
    Then: mix phx.server
    """)
  end

  defp maybe_generate_example(igniter, false, _web_module, _phoenix?), do: igniter

  defp maybe_generate_example(igniter, true, web_module, false) do
    module = Module.concat([web_module, OctaStarDemo])

    Igniter.Project.Module.create_module(
      igniter,
      module,
      """
      @moduledoc "Example Datastar handler using OctaStar with Plug."

      def handle_event(conn, "increment", signals) do
        count = Map.get(signals, "count", 0) + 1
        OctaStar.patch_signals(conn, %{count: count})
      end
      """
    )
  end

  defp maybe_generate_example(igniter, true, web_module, true) do
    controller = Module.concat([web_module, OctaStarDemoController])

    Igniter.Project.Module.create_module(
      igniter,
      controller,
      """
      @moduledoc "Example Phoenix controller demonstrating OctaStar with Datastar."

      use #{inspect(web_module)}, :controller

      @impl StarView
      def show(conn, _params) do
        conn
        |> signal(:count, 0)
        |> signal(:tabId, generate_tab_id())
      end

      @impl StarView
      def html(assigns) do
        ~H\"\"\"
        <div data-signals={init_signals(@conn)}>
          <button data-on:click={post("increment")}>+</button>
          <span data-text="$count">{@count}</span>
        </div>
        \"\"\"
      end

      @impl StarView
      def handle_event(conn, "increment", signals) do
        signal(conn, :count, Map.get(signals, "count", 0) + 1)
      end

      defp generate_tab_id do
        16
        |> :crypto.strong_rand_bytes()
        |> Base.encode16(case: :lower)
      end
      """
    )
  end

  defp maybe_patch_router(igniter, _example?, _web_module, false), do: igniter

  defp maybe_patch_router(igniter, example?, web_module, true) do
    {igniter, router} =
      Igniter.Libs.Phoenix.select_router(
        igniter,
        "Which Phoenix router should OctaStar add routes to?"
      )

    if router do
      do_patch_router(igniter, example?, web_module, router)
    else
      Igniter.add_warning(igniter, "No Phoenix router found. Skipping route setup.")
    end
  end

  defp do_patch_router(igniter, example?, web_module, router) do
    # Check if the route is already present to stay idempotent
    {_, _source, zipper} = Igniter.Project.Module.find_module!(igniter, router)

    already_has_route? =
      zipper
      |> Igniter.Code.Common.find_all(fn z ->
        case z.node do
          {:post, _, ["/ds/:module/:event" | _]} -> true
          _ -> false
        end
      end)
      |> Enum.any?()

    if already_has_route? do
      igniter
    else
      route_contents =
        if example? do
          controller = Module.concat([web_module, OctaStarDemoController])

          """
          get "/octa-star-demo", #{inspect(controller)}, :show
          post "/ds/:module/:event", OctaStar.Phoenix.Dispatch, []
          """
        else
          """
          post "/ds/:module/:event", OctaStar.Phoenix.Dispatch, []
          """
        end

      Igniter.Libs.Phoenix.append_to_scope(
        igniter,
        "/",
        route_contents,
        with_pipelines: [:browser],
        arg2: web_module,
        router: router
      )
    end
  end

  defp maybe_print_post_install(igniter, true) do
    Igniter.add_notice(igniter, """
    OctaStar installed!

    Routes have been added to your router automatically.

    Make sure your web module uses OctaStar:

        def controller do
          quote do
            use Phoenix.Controller, formats: [:html]
            use OctaStar, :controller
          end
        end
    """)
  end

  defp maybe_print_post_install(igniter, false) do
    Igniter.add_notice(igniter, """
    OctaStar installed!

    Wire the dispatch plug into your router:

        post "/ds/:module/:event", OctaStar.Plug.Dispatch,
          modules: [MyApp.HandlerModule]
    """)
  end
end