lib/lti_1p3/tool/context_roles.ex

defmodule Lti_1p3.Tool.ContextRoles do
  alias Lti_1p3.Tool.ContextRole
  alias Lti_1p3.Tool.Lti_1p3_User

  # Core context roles
  @context_administrator %ContextRole{
    id: 1,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"
  }

  @context_content_developer %ContextRole{
    id: 2,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper"
  }

  @context_instructor %ContextRole{
    id: 3,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"
  }

  @context_learner %ContextRole{
    id: 4,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"
  }

  @context_mentor %ContextRole{
    id: 5,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor"
  }

  # Non‑core context roles
  @context_manager %ContextRole{
    id: 6,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Manager"
  }

  @context_member %ContextRole{
    id: 7,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Member"
  }

  @context_officer %ContextRole{
    id: 8,
    uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Officer"
  }

  def list_roles(), do: [
    @context_administrator,
    @context_content_developer,
    @context_instructor,
    @context_learner,
    @context_mentor,
    @context_manager,
    @context_member,
    @context_officer,
  ]

  @doc """
  Returns a role from a given atom if it is valid, otherwise returns nil
  """
  def get_role(:context_administrator), do: @context_administrator
  def get_role(:context_content_developer), do: @context_content_developer
  def get_role(:context_instructor), do: @context_instructor
  def get_role(:context_learner), do: @context_learner
  def get_role(:context_mentor), do: @context_mentor
  def get_role(:context_manager), do: @context_manager
  def get_role(:context_member), do: @context_member
  def get_role(:context_officer), do: @context_officer
  def get_role(_invalid), do: nil

  @doc """
  Returns a role from a given uri if it is valid, otherwise returns nil
  """
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"), do: @context_administrator
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper"), do: @context_content_developer
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"), do: @context_instructor
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"), do: @context_learner
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor"), do: @context_mentor
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Manager"), do: @context_manager
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Member"), do: @context_member
  def get_role_by_uri("http://purl.imsglobal.org/vocab/lis/v2/membership#Officer"), do: @context_officer
  def get_role_by_uri(_invalid), do: nil

  @doc """
  Returns all valid roles from a list of uris
  """
  @spec get_roles_by_uris([String.t()]) :: [ContextRole.t()]
  def get_roles_by_uris(uris) do
    # create a list only containing valid roles
    uris
      |> Enum.map(&(get_role_by_uri(&1)))
      |> Enum.filter(&(&1 != nil))
  end

  @doc """
  Returns true if a list of roles contains a given role
  """
  @spec contains_role?([ContextRole.t()], ContextRole.t()) :: boolean()
  def contains_role?(roles, role) when is_list(roles) do
    Enum.any?(roles, fn r -> r.uri == role.uri end)
  end

  @doc """
  Returns the highest level role from a list of roles. This function assumes roles have an
  ordinality which is defined by list_roles()
  """
  @spec get_highest_role([ContextRole.t()]) :: ContextRole.t()
  def get_highest_role(roles) when is_list(roles) do
    roles_map = context_roles_as_map(roles)
    Enum.find(list_roles(), fn r -> roles_map[r.uri] == true end)
  end

  @doc """
  Returns true if a user has a given role
  """
  @spec has_role?(Lti_1p3_User.t(), any(), ContextRole.t()) :: boolean()
  def has_role?(user, context, role) when is_struct(user) do
    roles = Lti_1p3_User.get_context_roles(user, context)
    Enum.any?(roles, fn r -> r.uri == role.uri end)
  end

  @doc """
  Returns true if a user has any of the given roles
  """
  @spec has_roles?(Lti_1p3_User.t(), any(), [ContextRole.t()], :any) :: boolean()
  def has_roles?(user, context, roles, :any) when is_struct(user) and is_list(roles) do
    context_roles = Lti_1p3_User.get_context_roles(user, context)
    context_roles_map = context_roles_as_map(context_roles)
    Enum.any?(roles, fn r -> context_roles_map[r.uri] == true end)
  end

  # Returns true if a user has all of the given roles
  @spec has_roles?(Lti_1p3_User.t(), any(), [ContextRole.t()], :all) :: boolean()
  def has_roles?(user, context, roles, :all) when is_struct(user) and is_list(roles) do
    context_roles = Lti_1p3_User.get_context_roles(user, context)
    context_roles_map = context_roles_as_map(context_roles)
    Enum.all?(roles, fn r -> context_roles_map[r.uri] == true end)
  end

  # Returns a map with keys of all role uris with value true if the user has the role, false otherwise
  defp context_roles_as_map(context_roles) do
    Enum.reduce(list_roles(), %{}, fn r, acc -> Map.put_new(acc, r.uri, Enum.any?(context_roles, &(&1.uri == r.uri))) end)
  end

end