defmodule Ash.Reactor.DestroyStep do
  @moduledoc """
  The Reactor step which is used to execute update actions.
  """
  use Reactor.Step
  import Ash.Reactor.StepUtils

  alias Ash.{Changeset, DataLayer}

  @doc false
  @impl true
  def run(arguments, context, options) do
    return_destroyed? = can?(context.current_step, :undo) || options[:return_destroyed?]

    changeset_options =
      options
      |> maybe_set_kw(:authorize?, context[:authorize?])
      |> maybe_set_kw(:actor, context[:actor])
      |> maybe_set_kw(:tenant, context[:tenant])
      |> maybe_set_kw(:tracer, context[:tracer])
      |> maybe_set_kw(:authorize?, options[:authorize?])
      |> maybe_set_kw(:actor, arguments[:actor])
      |> maybe_set_kw(:tenant, arguments[:tenant])
      |> maybe_set_kw(:return_destroyed?, return_destroyed?)

    action_options =
      [return_notifications?: true, domain: options[:domain]]
      |> maybe_set_kw(:authorize?, options[:authorize?])
      |> maybe_set_kw(:return_destroyed?, return_destroyed?)
      |> maybe_set_kw(:load, arguments[:load])
      |> maybe_set_kw(:context, arguments[:context])

    arguments[:initial]
    |> Changeset.for_destroy(options[:action], arguments[:input], changeset_options)
    |> Ash.destroy(action_options)
    |> case do
      :ok ->
        {:ok, :ok}

      {:ok, record} when is_struct(record) ->
        maybe_return_destroyed?(record, return_destroyed?)

      {:ok, notifications} when is_list(notifications) ->
        with :ok <- Ash.Reactor.Notifications.enqueue_notifications(context, notifications),
             do: {:ok, :ok}

      {:ok, record, notifications} ->
        with :ok <- Ash.Reactor.Notifications.enqueue_notifications(context, notifications),
             do: maybe_return_destroyed?(record, return_destroyed?)

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc false
  @impl true
  def undo(record, arguments, context, options) do
    changeset_options =
      options
      |> maybe_set_kw(:authorize?, context[:authorize?])
      |> maybe_set_kw(:actor, context[:actor])
      |> maybe_set_kw(:tenant, context[:tenant])
      |> maybe_set_kw(:tracer, context[:tracer])
      |> maybe_set_kw(:authorize?, options[:authorize?])
      |> maybe_set_kw(:actor, arguments[:actor])
      |> maybe_set_kw(:tenant, arguments[:tenant])

    action_options =
      [return_notifications?: false, domain: options[:domain]]
      |> maybe_set_kw(:authorize?, options[:authorize?])

    options[:resource]
    |> Changeset.for_create(options[:undo_action], %{record: record}, changeset_options)
    |> Ash.create(action_options)
    |> case do
      {:ok, _record} -> :ok
      {:ok, _record, _notifications} -> :ok
      {:error, reason} -> {:error, reason}
    end
  end

  @doc false
  @impl true
  def can?(%{impl: {_, options}}, :undo) do
    case options[:undo] do
      :always -> true
      :never -> false
      :outside_transaction -> !DataLayer.in_transaction?(options[:resource])
    end
  end

  def can?(_, :compensate), do: false

  @doc false
  @impl true
  def async?(%{impl: {_, options}, async?: async}) do
    cond do
      DataLayer.in_transaction?(options[:resource]) ->
        false

      is_function(async, 1) ->
        async.(options)

      true ->
        async
    end
  end

  defp maybe_return_destroyed?(record, true), do: {:ok, record}
  defp maybe_return_destroyed?(_record, false), do: {:ok, :ok}
end
