defmodule LastFm.API.ErrorResponse do
  defstruct [:error, :message]

  @type error_atom ::
          :invalid_service
          | :invalid_method
          | :authentication_failed
          | :invalid_format
          | :invalid_parameters
          | :invalid_resource
          | :operation_failed
          | :invalid_session_key
          | :invalid_api_key
          | :service_offline
          | :invalid_method_signature
          | :transient_error
          | :suspended_api_key
          | :rate_limit_exceeded

  @type t :: %__MODULE__{
          error: error_atom(),
          message: String.t()
        }

  @spec new(integer(), String.t()) :: t()
  def new(error_code, message) do
    %__MODULE__{error: map_error(error_code), message: message}
  end

  defp map_error(2), do: :invalid_service
  defp map_error(3), do: :invalid_method
  defp map_error(4), do: :authentication_failed
  defp map_error(5), do: :invalid_format
  defp map_error(6), do: :invalid_parameters
  defp map_error(7), do: :invalid_resource
  defp map_error(8), do: :operation_failed
  defp map_error(9), do: :invalid_session_key
  defp map_error(10), do: :invalid_api_key
  defp map_error(11), do: :service_offline
  defp map_error(13), do: :invalid_method_signature
  defp map_error(16), do: :transient_error
  defp map_error(26), do: :suspended_api_key
  defp map_error(29), do: :rate_limit_exceeded

  @doc """
  Returns true if the error is retryable, false otherwise.
  """
  @spec retryable_error?(atom()) :: boolean()
  def retryable_error?(error)
      when error in [:transient_error, :service_offline, :rate_limit_exceeded, :operation_failed],
      do: true

  def retryable_error?(_error), do: false

  @doc """
  Returns the recommended retry delay in milliseconds for retryable errors.
  Returns nil for non-retryable errors.
  """
  @spec retry_delay(atom()) :: pos_integer() | nil
  # 1 minute
  def retry_delay(:rate_limit_exceeded), do: 60_000
  # 30 seconds
  def retry_delay(:service_offline), do: 30_000
  # 5 seconds
  def retry_delay(:transient_error), do: 5_000
  # 5 seconds
  def retry_delay(:operation_failed), do: 5_000
  def retry_delay(_), do: nil
end
