readme_path = Path.join(__DIR__, "../README.md")

readme_content =
  readme_path
  |> File.read!()
  |> String.replace(~r/<!-- ex_doc_ignore_start -->.*?<!-- ex_doc_ignore_end -->/s, "")

defmodule Clarity do
  @moduledoc """
  #{readme_content}
  """

  @external_resource readme_path

  @type unsubscribe() :: (-> :ok)

  @type queue_info() :: %{
          future_queue: non_neg_integer(),
          requeue_queue: non_neg_integer(),
          in_progress: non_neg_integer(),
          total_vertices: non_neg_integer()
        }

  @typedoc """
  - `:work_started` - Emitted when work starts
  - `:work_completed` - Emitted when all work is completed
  - `{:work_progress, queue_info()}` - Emitted periodically with current queue info

  Topics starting with `:__` are internal.
  """
  @type event() ::
          :work_started
          | :work_completed
          | {:work_progress, queue_info()}
          | :__restart_pulling__

  @type status() :: :working | :done

  @type modules_diff() :: %{
          changed: [module()],
          added: [module()],
          removed: [module()]
        }

  @type t() :: %__MODULE__{
          graph: Clarity.Graph.t(),
          status: status(),
          queue_info: queue_info()
        }

  @typedoc """
  - `:work_started` - Emitted when work starts
  - `:work_completed` - Emitted when all work is completed
  - `:work_progress` - Emitted periodically with current queue info

  Topics starting with `:__` are internal.
  """
  @type subscription_topic() ::
          :work_started
          | :work_completed
          | :work_progress
          | :__restart_pulling__

  @enforce_keys [:graph, :status, :queue_info]
  defstruct [:graph, :status, :queue_info]

  @doc """
  Subscribe to clarity events. Returns an unsubscribe function.

  ## Examples

      # Subscribe to specific events only
      unsubscribe = Clarity.subscribe([:work_started, :work_completed])

      # Unsubscribe later
      unsubscribe.()
  """
  @spec subscribe(
          GenServer.server(),
          subscription_topic() | [subscription_topic()],
          reference() | nil
        ) ::
          unsubscribe()
  def subscribe(server \\ Clarity.Server, topics, ref \\ nil) do
    topics = List.wrap(topics)

    Enum.each(topics, fn topic ->
      {:ok, _} = Registry.register(Clarity.PubSub, {server, topic}, ref)
    end)

    fn ->
      Enum.each(topics, fn
        topic when ref == nil ->
          Registry.unregister(Clarity.PubSub, {server, topic})

        topic ->
          Registry.unregister_match(Clarity.PubSub, {server, topic}, ref)
      end)
    end
  end

  @doc """
  Start introspection process.

  ## Options

  - `:full` - Full introspection, clears graph and re-introspects everything
  - `{:incremental, app, modules_diff}` - Incremental introspection based on module changes for specific app
  - `[module()]` - Introspect specific modules (legacy support)
  """
  @spec introspect(
          GenServer.server(),
          :full | {:incremental, Application.app(), modules_diff()} | [module()]
        ) :: :ok
  def introspect(server \\ Clarity.Server, scope \\ :full) do
    GenServer.cast(server, {:introspect, scope})
  end

  @doc """
  Get current clarity state.

  For `:partial` mode, returns the current state immediately.
  For `:complete` mode, waits for all work to complete before returning the final state.
  """
  @spec get(GenServer.server(), :partial | :complete) :: t()
  def get(server \\ Clarity.Server, mode \\ :partial)

  def get(server, :partial) do
    GenServer.call(server, :get)
  end

  def get(server, :complete) do
    ref = make_ref()

    unsubscribe = subscribe(server, :work_completed, ref)

    try do
      case GenServer.call(server, :get) do
        %Clarity{status: :done} = clarity ->
          clarity

        _clarity ->
          receive do
            {:clarity, ^ref, :work_completed} ->
              GenServer.call(server, :get)
          after
            60_000 ->
              raise "Timeout waiting for work to complete"
          end
      end
    after
      unsubscribe.()
    end
  end
end
