lib/ash_sdui/registry.ex

defmodule AshSDUI.Registry do
  @moduledoc false

  @table :ash_sdui_registry_ets
  @key {__MODULE__, :components}

  def init_table do
    ensure_ets_table()
    discover_components()
  end

  defp ensure_ets_table do
    unless :ets.whereis(@table) do
      try do
        :ets.new(@table, [:set, :public, :named_table])
      rescue
        _ -> :ok
      catch
        _, _ -> :ok
      end
    end

    # Try to migrate entries from persistent_term to ETS
    # (but don't fail if anything goes wrong)
    try do
      case :persistent_term.get(@key, nil) do
        nil ->
          :ok

        map ->
          Enum.each(map, fn {name, entry} ->
            try do
              :ets.insert(@table, {name, entry})
            rescue
              _ -> :ok
            catch
              _, _ -> :ok
            end
          end)
      end
    rescue
      _ -> :ok
    catch
      _, _ -> :ok
    end

    :ets.whereis(@table)
  end

  def register(name, module, meta) do
    entry = Map.merge(meta, %{module: module, name: name})

    # Register in persistent_term (always available, even at compile-time)
    :global.set_lock({@key, self()})

    try do
      current = all_map()
      updated = Map.put(current, name, entry)
      :persistent_term.put(@key, updated)
    after
      :global.del_lock({@key, self()})
    end

    # Try to also add to ETS if table exists
    _ = safe_ets_insert(@table, {name, entry})

    :ok
  end

  defp safe_ets_insert(table, entry) do
    try do
      :ets.insert(table, entry)
    rescue
      _ -> :error
    catch
      _, _ -> :error
    end
  end

  def lookup(name) do
    ensure_ets_table()

    if registry_empty?() do
      discover_components()
    end

    case try_ets_lookup(name) do
      {:ok, entry} ->
        {:ok, entry}

      _ ->
        discover_components()

        case try_ets_lookup(name) do
          {:ok, entry} -> {:ok, entry}
          _ -> lookup_in_map(name)
        end
    end
  end

  def all do
    ensure_ets_table()

    if registry_empty?() do
      discover_components()
    end

    case try_ets_all() do
      {:ok, entries} -> entries
      _ -> Map.values(all_map())
    end
  end

  def discover_components do
    Application.loaded_applications()
    |> Enum.each(fn {app, _, _} ->
      app
      |> Application.spec(:modules)
      |> List.wrap()
      |> Enum.each(&discover_component_module/1)
    end)

    :ok
  end

  defp try_ets_all do
    try do
      entries =
        @table
        |> :ets.match({:_, :"$1"})
        |> List.flatten()

      {:ok, entries}
    rescue
      _ -> :error
    catch
      _, _ -> :error
    end
  end

  defp try_ets_lookup(name) do
    try do
      case :ets.lookup(@table, name) do
        [{^name, entry}] -> {:ok, entry}
        [] -> :error
      end
    rescue
      _ -> :error
    catch
      _, _ -> :error
    end
  end

  defp lookup_in_map(name) do
    map = all_map()

    case Map.get(map, name) do
      nil -> {:error, :not_found}
      entry -> {:ok, entry}
    end
  end

  defp all_map do
    case :persistent_term.get(@key, nil) do
      nil -> %{}
      map -> map
    end
  end

  defp registry_empty? do
    case try_ets_all() do
      {:ok, []} -> map_size(all_map()) == 0
      {:ok, _entries} -> false
      _ -> map_size(all_map()) == 0
    end
  end

  defp discover_component_module(module) do
    with {:module, module} <- Code.ensure_loaded(module),
         true <- function_exported?(module, :__ash_sdui_component_name__, 0),
         true <- function_exported?(module, :__ash_sdui_fragment__, 0),
         true <- function_exported?(module, :__ash_sdui_subject_types__, 0) do
      register(
        module.__ash_sdui_component_name__(),
        module,
        %{
          fragment: module.__ash_sdui_fragment__(),
          subject_types: module.__ash_sdui_subject_types__()
        }
      )
    else
      _ -> :ok
    end
  end
end