lib/selecto_components/colocated.ex

defmodule SelectoComponents.Colocated do
  @moduledoc """
  Configuration and utilities for colocated JavaScript and hooks in SelectoComponents.

  This module provides functions to:
  - List all available colocated hooks
  - Generate JavaScript imports for the host application
  - Configure Phoenix LiveView colocated assets
  """

  @hooks [
    {"SelectoComponents.Components.TreeBuilder", "TreeBuilder", "components/tree_builder.ex"},
    {"SelectoComponents.Views.Graph.Component", "GraphView", "views/graph/component.ex"}
  ]

  @doc """
  Returns a list of all available colocated hooks.

  Each hook is a tuple of {module_name, hook_name, file_path}
  """
  def hooks, do: @hooks

  @doc """
  Generates JavaScript code to import and export all colocated hooks.

  This is used by the setup task to create the hooks index file.
  """
  def generate_hooks_index do
    """
    // Auto-generated by SelectoComponents
    // This file exports all colocated hooks from selecto_components

    #{Enum.map_join(hooks(), "\n", &generate_hook_export/1)}
    """
  end

  defp generate_hook_export({module_name, _hook_name, _file_path}) do
    """
    export { default as #{module_to_hook_name(module_name)} } from "phoenix-live-view:#{module_name}";
    """
  end

  defp module_to_hook_name(module_name) do
    module_name
    |> String.split(".")
    |> List.last()
    |> then(fn name -> name <> "Hook" end)
  end

  @doc """
  Returns configuration for the host application's app.js file.

  This includes the necessary imports and hook registrations.
  """
  def app_js_config do
    hooks_map =
      Enum.map(hooks(), fn {module_name, _, _} ->
        hook_var = module_to_hook_name(module_name)
        ~s|  "#{module_name}": #{hook_var}|
      end)
      |> Enum.join(",\n")

    """
    // Import selecto_components colocated hooks
    #{Enum.map_join(hooks(), "\n", &generate_import/1)}

    // In your LiveSocket configuration, add these hooks:
    const liveSocket = new LiveSocket("/live", Socket, {
      // ... other config ...
      hooks: {
    #{hooks_map}
      }
    })
    """
  end

  defp generate_import({module_name, _, _}) do
    hook_var = module_to_hook_name(module_name)
    ~s|import { default as #{hook_var} } from "selecto_components/#{String.downcase(hook_var)}";|
  end

  @doc """
  Checks if Phoenix LiveView supports colocated assets.

  Returns {:ok, version} or {:error, reason}
  """
  def check_phoenix_live_view_support do
    case Application.spec(:phoenix_live_view, :vsn) do
      nil ->
        {:error, "Phoenix LiveView is not installed"}

      version ->
        version_string = to_string(version)

        if Version.match?(version_string, ">= 0.20.0") do
          {:ok, version_string}
        else
          {:error,
           "Phoenix LiveView #{version_string} does not support colocated assets (requires >= 0.20.0)"}
        end
    end
  end

  @doc """
  Returns the path where colocated hooks should be extracted.
  """
  def hooks_output_path do
    Path.join(["assets", "js", "selecto_components", "hooks"])
  end
end