defmodule Services.Approvals do
  use GenServer

  # ----------------------------------------------------------------------------
  # Types
  # ----------------------------------------------------------------------------
  defstruct [:session]

  @type t :: %__MODULE__{
          session: [any]
        }

  # ----------------------------------------------------------------------------
  # Globals
  # ----------------------------------------------------------------------------
  @default_impl %{
    edit: Services.Approvals.Edit,
    shell: Services.Approvals.Shell
  }

  # ----------------------------------------------------------------------------
  # Client API
  # ----------------------------------------------------------------------------
  def start_link(opts \\ []) do
    name = Keyword.get(opts, :name, __MODULE__)
    GenServer.start_link(__MODULE__, nil, name: name)
  end

  @spec confirm(term, atom) ::
          {:ok, :approved}
          | {:denied, binary}
          | {:error, binary}
  def confirm(args, kind) do
    GenServer.call(__MODULE__, {:confirm, kind, args}, :infinity)
  end

  # ----------------------------------------------------------------------------
  # Server API
  # ----------------------------------------------------------------------------
  @impl GenServer
  def init(_) do
    {:ok, %__MODULE__{session: []}}
  end

  @impl GenServer
  def handle_call({:confirm, kind, args}, _from, state) do
    impl = impl_for(kind)

    # Execute the confirmation in a UI.Queue context to avoid deadlocks
    result =
      UI.Queue.run_from_genserver(fn ->
        impl.confirm(state, args)
      end)

    case result do
      {:approved, new_state} -> {:reply, {:ok, :approved}, new_state}
      {:denied, reason, new_state} -> {:reply, {:denied, reason}, new_state}
      {:error, reason, new_state} -> {:reply, {:error, reason}, new_state}
    end
  end

  # ----------------------------------------------------------------------------
  # Internals
  # ----------------------------------------------------------------------------
  defp impl_for(kind) do
    Services.Globals.get_env(:fnord, :approvals, @default_impl)
    |> Map.fetch!(kind)
  end
end
