defmodule Forum.Modules do
  @moduledoc """
  Snapshot of every loaded Erlang/Elixir module with its OTP app and
  source file. Powers the /modules endpoint.
  """

  @doc """
  Returns a list of maps, one per loaded module, sorted by module name.

  Each map has string keys (JSON-friendly):
    "module" — module name, e.g. "Elixir.Forum.Router"
    "app"    — OTP app the module belongs to, e.g. ":forum", or nil
    "source" — absolute path to the source file at compile time, or nil
    "mine"   — true if the module belongs to this app
  """
  def list do
    my_modules = MapSet.new(Application.spec(:forum, :modules) || [])

    :code.all_loaded()
    |> Enum.map(fn {mod, _beam} ->
      %{
        "module" => inspect(mod),
        "app" => format_app(Application.get_application(mod)),
        "source" => source_for(mod),
        "mine" => MapSet.member?(my_modules, mod)
      }
    end)
    |> Enum.sort_by(& &1["module"])
  end

  defp format_app(nil), do: nil
  defp format_app(app) when is_atom(app), do: inspect(app)

  defp source_for(mod) do
    case Keyword.get(mod.module_info(:compile), :source) do
      nil -> nil
      source when is_list(source) -> List.to_string(source)
      source when is_binary(source) -> source
      _ -> nil
    end
  rescue
    _ -> nil
  end
end
