defmodule ExState.Ecto.Multi do
  @moduledoc """
  `ExState.Ecto.Multi` provides convencience functions for creating and
  transitioning workflows in Ecto.Multi operations.

  ## Example

  def create_sale() do
    Multi.new()
    |> Multi.create(:sale, Sale.new())
    |> ExState.Ecto.Multi.create(:sale)
    |> ExState.Ecto.Multi.transition(:sale, :packed)
    |> Repo.transaction()
  end
  """

  alias ExState
  alias Ecto.Multi

  @spec create(Multi.t(), struct() | atom()) :: Multi.t()
  def create(%Multi{} = multi, subject_key) when is_atom(subject_key) do
    Multi.merge(multi, fn results ->
      ExState.create_multi(Map.get(results, subject_key))
    end)
  end

  def create(%Multi{} = multi, subject) do
    Multi.merge(multi, fn _ ->
      ExState.create_multi(subject)
    end)
  end

  @spec transition(Multi.t(), struct() | atom(), any(), keyword()) :: Multi.t()
  def transition(multi, subject_or_key, event, opts \\ [])

  def transition(%Multi{} = multi, subject_key, event, opts) when is_atom(subject_key) do
    Multi.run(multi, event, fn _repo, results ->
      ExState.transition(Map.get(results, subject_key), event, opts)
    end)
  end

  def transition(%Multi{} = multi, subject, event, opts) do
    Multi.run(multi, event, fn _repo, _ ->
      ExState.transition(subject, event, opts)
    end)
  end

  @spec complete(Multi.t(), struct() | atom(), any(), keyword()) :: Multi.t()
  def complete(multi, subject_or_key, step_id, opts \\ [])

  def complete(%Multi{} = multi, subject_key, step_id, opts) when is_atom(subject_key) do
    Multi.run(multi, step_id, fn _repo, results ->
      ExState.complete(Map.get(results, subject_key), step_id, opts)
    end)
  end

  def complete(%Multi{} = multi, subject, step_id, opts) do
    Multi.run(multi, step_id, fn _repo, _ ->
      ExState.complete(subject, step_id, opts)
    end)
  end

  @spec decision(Multi.t(), struct() | atom(), any(), any(), keyword()) :: Multi.t()
  def decision(multi, subject_or_key, step_id, decision, opts \\ [])

  def decision(%Multi{} = multi, subject_key, step_id, decision, opts)
      when is_atom(subject_key) do
    Multi.run(multi, step_id, fn _repo, results ->
      ExState.decision(Map.get(results, subject_key), step_id, decision, opts)
    end)
  end

  def decision(%Multi{} = multi, subject, step_id, decision, opts) do
    Multi.run(multi, step_id, fn _repo, _ ->
      ExState.decision(subject, step_id, decision, opts)
    end)
  end
end
