lib/composition_objects.ex

defmodule BlockBox.CompositionObjects do
  @moduledoc """
  Defines types and generator functions for all [composition objects](https://api.slack.com/reference/block-kit/composition-objects).
  """

  alias BlockBox.Utils, as: Utils

  @type text_type() :: :plain_text | :mrkdwn

  @type text_object() :: %{
          required(:text) => String.t(),
          required(:type) => text_type(),
          optional(:emoji) => boolean(),
          optional(:verbatim) => boolean()
        }

  @type plain_text_object() :: %{
          required(:text) => String.t(),
          required(:type) => :plain_text,
          optional(:emoji) => boolean()
        }

  @type confirm_object() :: %{
          required(:title) => plain_text_object(),
          required(:text) => text_object(),
          required(:confirm) => plain_text_object(),
          required(:deny) => plain_text_object()
        }

  @type option_object() :: %{
          required(:text) => text_object(),
          required(:value) => String.t(),
          optional(:description) => plain_text_object(),
          optional(:url) => String.t()
        }

  @type option_group_object() :: %{
          required(:label) => plain_text_object(),
          required(:options) => list(option_object())
        }

  @type filter_object() :: %{
          optional(:include) => list(String.t()),
          optional(:exclude_external_shared_channels) => boolean(),
          optional(:exclude_bot_users) => boolean()
        }

  @doc """
  Function that generates a [text object](https://api.slack.com/reference/block-kit/composition-objects#text)

  ## Options
  Options are not included by default.
  * `:emoji` - boolean, only usable when type is `:plain_text`
  * `:verbatim` - boolean, only usable when type is `:mrkdwn`
  """
  @spec text_object(String.t(), text_type(), keyword()) :: text_object()
  def text_object(text, type \\ :plain_text, opts \\ []) do
    %{
      type: type,
      text: text
    }
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Function that generates a [confirmation dialog object](https://api.slack.com/reference/block-kit/composition-objects#confirm)
  """
  @spec confirm_object(String.t(), String.t(), String.t(), String.t()) :: confirm_object()
  def confirm_object(title, text, confirm \\ "Confirm", deny \\ "Deny") do
    %{
      title: text_object(title),
      text: text_object(text),
      confirm: text_object(confirm),
      deny: text_object(deny)
    }
  end

  @doc """
  Function that generates an [option object](https://api.slack.com/reference/block-kit/composition-objects#option) for select menus
  ## Options
  Options are not included by default.
  * `:url` - boolean, only available in overflow menus
  * `:description` - String, max 75 chars
  """
  def option_object(text, value, opts \\ [])

  @spec option_object(String.t() | plain_text_object(), String.t(), keyword()) :: option_object()
  def option_object(text_str, value, opts) when is_binary(text_str) do
    text_object(text_str)
    |> option_object(value, opts)
  end

  def option_object(text_object, value, opts) do
    opts = Utils.convert_text_opts(opts, [:description])

    %{
      text: text_object,
      value: value
    }
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Function that generates an [option group object](https://api.slack.com/reference/block-kit/composition-objects#option_group) for select menus
  """
  @spec option_group_object(String.t() | plain_text_object(), String.t()) :: option_group_object()
  def option_group_object(label, options) when is_binary(label) do
    text_object(label)
    |> option_group_object(options)
  end

  def option_group_object(label, options) do
    %{
      label: label,
      options: options
    }
  end

  @doc """
  Function that generates a [filter object](https://api.slack.com/reference/block-kit/composition-objects#filter_conversations) for conversation lists.

  All fields are optional but AT LEAST ONE MUST BE INCLUDED.
  ## Options
  Options are not included by default.
  * `:include` - non empty list of strings from the following options: "im", "mpim", "private", "public"
  * `:exclude_external_shared_channels` - boolean, defaults to false
  * `:exclude_bot_users` - boolean, defaults to false
  """
  def filter_object(opts) do
    Enum.into(opts, %{})
  end
end