defmodule Oban.Pro.Limiters.Rate.Algorithm do
  @moduledoc false

  alias Oban.Pro.Limiters.Rate.{FixedWindow, SlidingWindow, TokenBucket}

  @type windows :: %{String.t() => map()}
  @type counts :: %{String.t() => non_neg_integer()}

  @doc """
  Get the callback module for a rate limit's algorithm.
  """
  @spec callback(%{:algorithm => atom(), optional(atom()) => term()}) :: module()
  def callback(%{algorithm: :fixed_window}), do: FixedWindow
  def callback(%{algorithm: :sliding_window}), do: SlidingWindow
  def callback(%{algorithm: :token_bucket}), do: TokenBucket
  def callback(_rate_limit), do: SlidingWindow

  @doc """
  Calculate available demand given current window state.
  """
  @callback demand(
              windows :: windows(),
              allowed :: pos_integer(),
              period :: pos_integer(),
              curr_time :: integer(),
              prev_time :: integer()
            ) :: non_neg_integer()

  @doc """
  Calculate per-partition demands for partitioned rate limiting.
  """
  @callback partition_demands(
              windows :: windows(),
              allowed :: pos_integer(),
              period :: pos_integer(),
              curr_time :: integer(),
              prev_time :: integer(),
              available_keys :: [String.t()]
            ) :: %{String.t() => non_neg_integer()}

  @doc """
  Flatten windows into a list with a single, compact value regardless of partitions.
  """
  @callback flatten(windows :: windows()) :: [map()]

  @doc """
  Merge window states from multiple producers.
  """
  @callback merge(windows_a :: windows(), windows_b :: windows()) :: windows()

  @doc """
  Track job execution by updating window state.

  Returns {updated_windows, next_window_time}.
  """
  @callback track(
              windows :: windows(),
              new_counts :: counts(),
              period :: pos_integer(),
              prev_time :: integer(),
              curr_time :: integer(),
              allowed :: pos_integer()
            ) :: {windows(), integer()}
end
