lib/api/videos.ex

defmodule Holodex.Api.Videos do
  @moduledoc """
  Holodex `Videos` resource API module.

  Includes client functions for interacting with the `Video` resource.
  """

  alias Holodex.Api.Client
  alias Holodex.Model.{Channel, Comment, Video, Video}
  alias Holodex.Model.Video.LiveTLCount

  @type opts() ::
          %{
            channel_id: String.t() | nil,
            id: String.t() | nil,
            include: list(binary()) | String.t() | nil,
            lang: String.t() | nil,
            limit: pos_integer() | nil,
            max_upcoming_hours: number() | nil,
            mentioned_channel_id: String.t() | nil,
            offset: integer() | nil,
            order: String.t() | nil,
            org: String.t() | nil,
            paginated: String.t() | nil,
            sort: String.t() | nil,
            status: String.t() | nil,
            topic: String.t() | nil,
            type: String.t() | nil
          }
          | %{}

  @type single_opts() ::
          %{
            c: String.t() | nil,
            lang: String.t() | nil
          }
          | %{}

  @list_of_videos_p [
    %Video{
      channel: %Channel{},
      clips: [%Video{}],
      sources: [%Video{}],
      refers: [%Video{}],
      simulcasts: [%Video{}],
      mentions: [%Channel{}],
      live_tl_count: %LiveTLCount{}
    }
  ]

  @single_video_p %Video{
    clips: [%Video{}],
    sources: [%Video{}],
    refers: [%Video{}],
    simulcasts: [%Video{}],
    mentions: [%Channel{}],
    comments: [%Comment{}],
    recommendations: [%Video{}],
    live_tl_count: %LiveTLCount{}
  }

  defdelegate fetch(video_id), to: __MODULE__, as: :video_info
  defdelegate fetch(video_id, opts), to: __MODULE__, as: :video_info
  defdelegate fetch!(video_id), to: __MODULE__, as: :video_info!
  defdelegate fetch!(video_id, opts), to: __MODULE__, as: :video_info!

  @doc """
  Query videos from Holodex, optionally with given parameters.

  Raises an exception in case of failure.
  """
  @spec list_videos!(opts()) :: [Video.t()] | no_return()
  def list_videos!(opts \\ %{}) do
    with url <- build_videos_url(opts),
         body <- Client.get!(url).body do
      Poison.decode!(body, %{as: @list_of_videos_p, keys: :atoms!})
    end
  end

  @doc """
  Query videos from  Holodex, optionally with given parameters.

  Returns a tuple containing  the response body or an error.
  """
  @spec list_videos(opts()) ::
          {:ok, [Video.t()]} | {:error, HTTPoison.Error.t()} | {:error, Exception.t()}
  def list_videos(opts \\ %{}) do
    with url <- build_videos_url(opts),
         {:ok, response} <- Client.get(url),
         {:ok, decoded} <- Poison.decode(response.body, %{as: @list_of_videos_p, keys: :atoms!}) do
      {:ok, decoded}
    end
  end

  @doc """
  Returns a single `Video`, optionally with comments and recommendations.

  Raises an exception in case of failure.
  """
  @spec video_info!(String.t(), single_opts()) :: Video.t()
  def video_info!(video_id, opts \\ %{}) do
    with url <- build_videos_url(opts, "/videos/#{video_id}"),
         response <- Client.get!(url) do
      Poison.decode!(response.body, %{as: @single_video_p, keys: :atoms!})
    end
  end

  @doc """
  Returns a single `Video` in a tuple, optionally with  comments and recommendation.
  """
  @spec video_info(String.t(), single_opts()) ::
          {:ok, Video.t()} | {:error, HTTPoison.Error.t()} | {:error, Exception.t()}
  def video_info(video_id, opts \\ %{}) do
    with url <- build_videos_url(opts, "/videos/#{video_id}"),
         {:ok, response} <- Client.get(url),
         {:ok, decoded} <- Poison.decode(response.body, %{as: @single_video_p, keys: :atoms!}) do
      {:ok, decoded}
    end
  end

  defp build_videos_url(opts, path \\ "/videos") do
    query =
      if Map.has_key?(opts, :include) && is_list(opts[:include]) do
        Map.merge(opts, %{include: Enum.join(opts[:include], ",")})
      else
        opts
      end
      |> URI.encode_query()

    path
    |> URI.parse()
    |> Map.put(:query, query)
    |> URI.to_string()
  end
end