defmodule Chaperon.Action do
  @moduledoc """
  Helper functions to be used with `Chaperon.Actionable`.
  """

  @doc """
  Retries `action` within `session` by calling `Chaperon.Actionable.abort/2`
  followed by `Chaperon.Actionable.run/2`.
  """
  def retry(action, session) do
    with {:ok, action, session} <- Chaperon.Actionable.abort(action, session) do
      Chaperon.Actionable.run(action, session)
    end
  end

  @doc """
  Returns a `Chaperon.Action.Error` for the given arguments.
  """
  def error(action, session, reason) do
    %Chaperon.Action.Error{
      reason: reason,
      action: action,
      session: session
    }
  end

  @doc """
  Every `Chaperon.Actionable` can expose a `callback` field.

  `callback` can be either:

  - a callback function or atom naming the callback function inside the session's current scenario module:

      `atom | ((Chaperon.Session.t, any | {:error, any}) -> any)`

  - a map containing callback and error functions:

      ```
      %{
        ok:    atom | ((Chaperon.Session.t, any) -> any),
        error: atom | ((Chaperon.Session.t, any) -> any)
      }
      ```

  When defining just a single callback function, it will be called in both
  success and error cases (passed in as `{:error, reason}`).
  To handle each case individually, you can just use pattern matching:

      session
      |> post("/greet", json: [hello: "world!"], with_result: fn
        (session, {:error, reason}) ->
          # handle error case here
          session
          |> log_error("Failed to greet")

        (session, %HTTPoison.Response{body: response}) ->
          # do something with successful response here
          session
          |> log_info("Greeted successfully!")
      end)
  """
  def callback(%{callback: %{ok: cb}}), do: cb
  def callback(%{callback: cb}), do: cb

  def error_callback(%{callback: %{error: cb}}), do: cb

  def error_callback(%{callback: cb}),
    do: fn session, resp ->
      session
      |> Chaperon.Session.call_callback(cb, {:error, resp})
    end

  def error_callback(_), do: nil
end
