lib/block_elements.ex

defmodule BlockBox.BlockElements do
  @moduledoc """
  Defines generator functions for all [block elements](https://api.slack.com/reference/block-kit/block-elements).
  """

  alias BlockBox.CompositionObjects, as: CO
  alias BlockBox.Utils, as: Utils

  @type select_menu_type ::
          :static_select
          | :external_select
          | :users_select
          | :conversations_select
          | :channels_select

  @type multi_select_menu_type ::
          :multi_static_select
          | :multi_external_select
          | :multi_users_select
          | :multi_conversations_select
          | :multi_channels_select

  @doc """
  Creates a [button element](https://api.slack.com/reference/block-kit/block-elements#button).

  ## Options
  Options are not included by default.
  * `:url` - String
  * `:value` - String
  * `:style` - String
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  """
  @spec button(String.t() | CO.text_object(), String.t(), keyword()) :: map()
  def button(text, action_id, opts \\ [])

  def button(text, action_id, opts) when is_binary(text) do
    CO.text_object(text)
    |> button(action_id, opts)
  end

  def button(text, action_id, opts) do
    %{
      type: "button",
      text: text,
      action_id: action_id
    }
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates a [datepicker element](https://api.slack.com/reference/block-kit/block-elements#datepicker).

  ## Options
  Options are not included by default.
  * `:placeholder` - `t:BlockBox.CompositionObjects.plain_text_object/0` or String
  * `:initial_date` - String, "YYYY-MM-DD" format. Put "today" for the current date.
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  """
  @spec datepicker(String.t(), keyword()) :: map()
  def datepicker(action_id, opts \\ []) do
    opts = Utils.convert_text_opts(opts, [:placeholder])

    opts =
      case Keyword.get(opts, :initial_date) do
        "today" -> opts |> Keyword.put(:initial_date, Utils.today())
        _other -> opts
      end

    %{type: "datepicker", action_id: action_id}
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates an [image element](https://api.slack.com/reference/block-kit/block-elements#image).
  """
  @spec image(String.t(), String.t()) :: map()
  def image(image_url, alt_text) do
    %{
      type: "image",
      image_url: image_url,
      alt_text: alt_text
    }
  end

  @doc """
  Creates an [overflow menu element](https://api.slack.com/reference/block-kit/block-elements#overflow).

  ## Options
  Options are not included by default.
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  """
  @spec overflow_menu(String.t(), list(CO.option_object()), keyword()) :: map()
  def overflow_menu(action_id, options, opts \\ []) do
    %{
      type: "overflow",
      action_id: action_id,
      options: options
    }
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates a [plain text input element](https://api.slack.com/reference/block-kit/block-elements#input).

  ## Options
  Options are not included by default.
  * `:placeholder` - `t:BlockBox.CompositionObjects.plain_text_object/0` or String
  * `:initial_value` - String
  * `:multiline` - boolean
  * `:min_length` - non negative integer
  * `:max_length` - positive integer
  """
  @spec plain_text_input(String.t(), keyword()) :: map()
  def plain_text_input(action_id, opts \\ []) do
    opts = Utils.convert_text_opts(opts, [:placeholder])

    %{type: "plain_text_input", action_id: action_id}
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates a [radio button group element](https://api.slack.com/reference/block-kit/block-elements#radio).

  ## Options
  Options are not included by default.
  * `:initial_option` - `t:BlockBox.CompositionObjects.option_object/0` or an integer representing the index of the option you want to select
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  """
  @spec radio_buttons(String.t(), list(CO.option_object()), keyword()) :: map()
  def radio_buttons(action_id, options, opts \\ []) do
    opts = Utils.convert_initial_opts(opts)

    %{
      type: "radio_buttons",
      action_id: action_id,
      options: options
    }
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates a [checkbox group element](https://api.slack.com/reference/block-kit/block-elements#checkboxes).

  ## Options
  Options are not included by default.
  * `:initial_options` - list of `t:BlockBox.CompositionObjects.option_object/0`s, Also included is the ability to pass in a list of integers representing the index of the item you want to select in `:options`.
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  """
  @spec checkboxes(String.t(), list(CO.option_object()), keyword()) :: map()
  def checkboxes(action_id, options, opts \\ []) do
    opts = Utils.convert_initial_opts(opts)

    %{
      type: "checkboxes",
      action_id: action_id,
      options: options
    }
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates a [select menu element](https://api.slack.com/reference/block-kit/block-elements#select).

  *ONLY ONE* of the following k/v pairs must be included in the options:
  * `:options` - a list of `t:BlockBox.CompositionObjects.option_object/0`s
  * `:option_groups` - a list of `t:BlockBox.CompositionObjects.option_group_object/0`s


  ## Options
  Options are not included by default.
  * `:initial_option` - `t:BlockBox.CompositionObjects.option_object/0`, only available with [static_select](https://api.slack.com/reference/block-kit/block-elements#static_select) or [external_select](https://api.slack.com/reference/block-kit/block-elements#external_select) types. Also included is the ability to pass in an integer representing the index of the item you want to select in `:options` or a 2-tuple representing an index of the 2D-list that is your `:option_groups`, only available with [static_select](https://api.slack.com/reference/block-kit/block-elements#static_select).
  * `:min_query_length` - positive integer, only available with [external_select](https://api.slack.com/reference/block-kit/block-elements#external_select) type
  * `:initial_user` - slack user ID, only available with [users_select](https://api.slack.com/reference/block-kit/block-elements#users_select) type
  * `:initial_conversation` - slack conversation ID, only available with [conversations_select](https://api.slack.com/reference/block-kit/block-elements#conversation_select) type
  * `:initial_channel` -  slack channel ID, only available with [channels_select](https://api.slack.com/reference/block-kit/block-elements#channel_select) type
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  * `:response_url_enabled` - boolean, only works with menus in inputs blocks and modals. only available with [conversations_select](https://api.slack.com/reference/block-kit/block-elements#conversation_select) and [channels_select](https://api.slack.com/reference/block-kit/block-elements#channel_select)
  * `:filter` - `t:BlockBox.CompositionObjects.filter_object/0`, only available with [conversations_select](https://api.slack.com/reference/block-kit/block-elements#conversation_select) type
  """
  @spec select_menu(
          String.t() | CO.plain_text_object(),
          select_menu_type | multi_select_menu_type,
          String.t(),
          keyword()
        ) :: map()
  def select_menu(placeholder, type, action_id, opts \\ [])

  def select_menu(placeholder, type, action_id, opts) when is_binary(placeholder) do
    CO.text_object(placeholder)
    |> select_menu(type, action_id, opts)
  end

  def select_menu(placeholder, type, action_id, opts) do
    opts = Utils.convert_initial_opts(opts)

    %{type: type, placeholder: placeholder, action_id: action_id}
    |> Map.merge(Enum.into(opts, %{}))
  end

  @doc """
  Creates a [multi-select menu element](https://api.slack.com/reference/block-kit/block-elements#multi_select).

  *ONLY ONE* of the following k/v pairs must be included in the options:
  * `:options` - a list of `t:BlockBox.CompositionObjects.option_object/0`s
  * `:option_groups` - a list of `t:BlockBox.CompositionObjects.option_group_object/0`s

  ## Options
  Options are not included by default.
  * `:initial_options` - list of `t:BlockBox.CompositionObjects.option_object/0`s, only available with [multi_static_select](https://api.slack.com/reference/block-kit/block-elements#static_multi_select) or [multi_external_select](https://api.slack.com/reference/block-kit/block-elements#external_multi_select) types. Also included is the ability to pass in a list of integers representing the index of the item you want to select in `:options` or a list of 2-tuples representing an index of the 2D-list that is your `:option_groups`, only available with [multi_static_select](https://api.slack.com/reference/block-kit/block-elements#static_multi_select).
  * `:min_query_length` - positive integer, only available with [multi_external_select](https://api.slack.com/reference/block-kit/block-elements#external_multi_select) type
  * `:initial_users` - list of slack user IDs, only available with [multi_users_select](https://api.slack.com/reference/block-kit/block-elements#users_multi_select) type
  * `:initial_conversations` - list of slack conversation IDs, only available with [multi_conversations_select](https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select) type
  * `:initial_channels` - list of slack channel IDs, only available with [multi_channels_select](https://api.slack.com/reference/block-kit/block-elements#channel_multi_select) type
  * `:confirm` - `t:BlockBox.CompositionObjects.confirm_object/0`
  * `:filter` - `t:BlockBox.CompositionObjects.filter_object/0`, only available with [multi_conversations_select](https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select) type
  """
  @spec multi_select_menu(
          String.t() | CO.plain_text_object(),
          multi_select_menu_type,
          String.t(),
          keyword()
        ) :: map()
  def multi_select_menu(placeholder, type, action_id, opts \\ []) do
    select_menu(placeholder, type, action_id, opts)
  end
end