defmodule Mobilizon.Service.ActivityPub.Audience do
  @moduledoc """
  Tools for calculating content audience
  """
  alias Mobilizon.Actors.Actor

  @ap_public "https://www.w3.org/ns/activitystreams#Public"

  @doc """
  Determines the full audience based on mentions for a public audience

  Audience is:
    * `to` : the mentioned actors, the eventual actor we're replying to and the public
    * `cc` : the actor's followers
  """
  @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
  def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :public) do
    to = [@ap_public | mentions]
    cc = [actor.followers_url]

    if in_reply_to do
      {Enum.uniq([in_reply_to.actor | to]), cc}
    else
      {to, cc}
    end
  end

  @doc """
  Determines the full audience based on mentions based on a unlisted audience

  Audience is:
    * `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
    * `cc` : public
  """
  @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
  def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :unlisted) do
    to = [actor.followers_url | mentions]
    cc = [@ap_public]

    if in_reply_to do
      {Enum.uniq([in_reply_to.actor | to]), cc}
    else
      {to, cc}
    end
  end

  @doc """
  Determines the full audience based on mentions based on a private audience

  Audience is:
    * `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
    * `cc` : none
  """
  @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
  def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :private) do
    {to, cc} = get_to_and_cc(actor, mentions, in_reply_to, :direct)
    {[actor.followers_url | to], cc}
  end

  @doc """
  Determines the full audience based on mentions based on a direct audience

  Audience is:
    * `to` : the mentioned actors and the eventual actor we're replying to
    * `cc` : none
  """
  @spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
  def get_to_and_cc(_actor, mentions, in_reply_to, :direct) do
    if in_reply_to do
      {Enum.uniq([in_reply_to.actor | mentions]), []}
    else
      {mentions, []}
    end
  end

  def get_to_and_cc(_actor, mentions, _in_reply_to, {:list, _}) do
    {mentions, []}
  end

  #  def get_addressed_actors(_, to) when is_list(to) do
  #    Actors.get(to)
  #  end

  def get_addressed_actors(mentioned_users, _), do: mentioned_users

  def calculate_to_and_cc_from_mentions(
        actor,
        mentions \\ [],
        in_reply_to \\ nil,
        visibility \\ :public
      ) do
    with mentioned_actors <- for({_, mentioned_actor} <- mentions, do: mentioned_actor.url),
         addressed_actors <- get_addressed_actors(mentioned_actors, nil),
         {to, cc} <- get_to_and_cc(actor, addressed_actors, in_reply_to, visibility) do
      %{"to" => to, "cc" => cc}
    end
  end
end