defmodule Commanded.EventStore.Adapter do
  @moduledoc """
  Defines the behaviour to be implemented by an event store adapter to be used by Commanded.
  """

  alias Commanded.EventStore.{EventData, RecordedEvent, SnapshotData}

  @type adapter_meta :: map
  @type application :: Commanded.Application.t()
  @type config :: Keyword.t()
  @type stream_uuid :: String.t()
  @type start_from :: :origin | :current | integer
  @type expected_version :: :any_version | :no_stream | :stream_exists | non_neg_integer
  @type subscription_name :: String.t()
  @type subscription :: any
  @type subscriber :: pid
  @type source_uuid :: String.t()
  @type error :: term

  @doc """
  Return a child spec defining all processes required by the event store.
  """
  @callback child_spec(application, config) ::
              {:ok, [:supervisor.child_spec() | {module, term} | module], adapter_meta}

  @doc """
  Append one or more events to a stream atomically.
  """
  @callback append_to_stream(
              adapter_meta,
              stream_uuid,
              expected_version,
              events :: list(EventData.t()),
              opts :: Keyword.t()
            ) ::
              :ok
              | {:error, :wrong_expected_version}
              | {:error, error}

  @doc """
  Streams events from the given stream, in the order in which they were
  originally written.
  """
  @callback stream_forward(
              adapter_meta,
              stream_uuid,
              start_version :: non_neg_integer,
              read_batch_size :: non_neg_integer
            ) ::
              Enumerable.t()
              | {:error, :stream_not_found}
              | {:error, error}

  @doc """
  Create a transient subscription to a single event stream.

  The event store will publish any events appended to the given stream to the
  `subscriber` process as an `{:events, events}` message.

  The subscriber does not need to acknowledge receipt of the events.
  """
  @callback subscribe(adapter_meta, stream_uuid | :all) ::
              :ok | {:error, error}

  @doc """
  Create a persistent subscription to an event stream.
  """
  @callback subscribe_to(
              adapter_meta,
              stream_uuid | :all,
              subscription_name,
              subscriber,
              start_from,
              opts :: Keyword.t()
            ) ::
              {:ok, subscription}
              | {:error, :subscription_already_exists}
              | {:error, error}

  @doc """
  Acknowledge receipt and successful processing of the given event received from
  a subscription to an event stream. Note that if the event is part of a batch,
  all events that precede this event in the batch are considered to be acknowledged
  as well.
  """
  @callback ack_event(adapter_meta, pid, RecordedEvent.t()) :: :ok

  @doc """
  Unsubscribe an existing subscriber from event notifications.

  This should not delete the subscription.
  """
  @callback unsubscribe(adapter_meta, subscription) :: :ok

  @doc """
  Delete an existing subscription.
  """
  @callback delete_subscription(
              adapter_meta,
              stream_uuid | :all,
              subscription_name
            ) ::
              :ok | {:error, :subscription_not_found} | {:error, error}

  @doc """
  Read a snapshot, if available, for a given source.
  """
  @callback read_snapshot(adapter_meta, source_uuid) ::
              {:ok, SnapshotData.t()} | {:error, :snapshot_not_found}

  @doc """
  Record a snapshot of the data and metadata for a given source
  """
  @callback record_snapshot(adapter_meta, SnapshotData.t()) ::
              :ok | {:error, error}

  @doc """
  Delete a previously recorded snapshot for a given source
  """
  @callback delete_snapshot(adapter_meta, source_uuid) ::
              :ok | {:error, error}
end
