// Builders.
//
// The parsed model is deliberately permissive -- every field optional, unknown
// keys preserved -- because that is what reading Slack's own payloads requires.
// Building is the opposite problem: a caller wants the shortest spelling of the
// common case and no way to express nonsense. Hence a builder per element TYPE
// even though several types share a struct: `users_select` and `channels_select`
// are one `SelectElement`, but no builder will let you give a `users_select` an
// `initial_channel`.

///|
pub fn section(
  text? : TextObject,
  fields? : Array[TextObject],
  accessory? : BlockElement,
  block_id? : String,
  expand? : Bool,
) -> LayoutBlock {
  Section({ block_id, text, fields, accessory, expand, extra: Map([]) })
}

///|
/// A section holding one `mrkdwn` string -- by far the most common block.
pub fn section_text(text : String, block_id? : String) -> LayoutBlock {
  section(text=TextObject::mrkdwn(text), block_id?)
}

///|
pub fn divider(block_id? : String) -> LayoutBlock {
  Divider({ block_id, extra: Map([]) })
}

///|
pub fn header(text : String, block_id? : String) -> LayoutBlock {
  Header({ block_id, text: Some(TextObject::plain(text)), extra: Map([]) })
}

///|
/// `alt_text` is not optional here even though the parser tolerates its
/// absence: an image with no alternative text is unreadable to anyone using a
/// screen reader, and Slack rejects it anyway.
pub fn image(
  image_url : String,
  alt_text : String,
  title? : TextObject,
  block_id? : String,
) -> LayoutBlock {
  Image({
    block_id,
    image_url: Some(image_url),
    slack_file: None,
    alt_text: Some(alt_text),
    title,
    extra: Map([]),
  })
}

///|
pub fn actions(
  elements : Array[BlockElement],
  block_id? : String,
) -> LayoutBlock {
  Actions({ block_id, elements: Some(elements), extra: Map([]) })
}

///|
pub fn context(
  elements : Array[BlockElement],
  block_id? : String,
) -> LayoutBlock {
  Context({ block_id, elements: Some(elements), extra: Map([]) })
}

///|
pub fn input(
  label : TextObject,
  element : BlockElement,
  hint? : TextObject,
  optional? : Bool,
  dispatch_action? : Bool,
  block_id? : String,
) -> LayoutBlock {
  Input({
    block_id,
    label: Some(label),
    element: Some(element),
    hint,
    optional,
    dispatch_action,
    extra: Map([]),
  })
}

///|
pub fn markdown(text : String, block_id? : String) -> LayoutBlock {
  Markdown({ block_id, text: Some(text), extra: Map([]) })
}

///|
pub fn rich_text(
  elements : Array[RichTextBlockElement],
  block_id? : String,
) -> LayoutBlock {
  RichText({ block_id, elements: Some(elements), extra: Map([]) })
}

///|
pub fn button(
  text : String,
  action_id? : String,
  value? : String,
  url? : String,
  style? : String,
  confirm? : ConfirmationDialogObject,
  accessibility_label? : String,
) -> BlockElement {
  Button({
    text: Some(TextObject::plain(text)),
    action_id,
    url,
    value,
    style,
    confirm,
    accessibility_label,
    extra: Map([]),
  })
}

///|
pub fn image_element(image_url : String, alt_text : String) -> BlockElement {
  Image({
    image_url: Some(image_url),
    slack_file: None,
    alt_text: Some(alt_text),
    extra: Map([]),
  })
}

///|
/// A plain-text run inside a context block.
pub fn context_text(text : String) -> BlockElement {
  Text(TextObject::mrkdwn(text))
}

///|
pub fn static_select(
  options : Array[OptionObject],
  action_id? : String,
  placeholder? : TextObject,
  initial_option? : OptionObject,
  confirm? : ConfirmationDialogObject,
) -> BlockElement {
  Select({
    ..SelectElement::blank(Static),
    action_id,
    placeholder,
    options: Some(options),
    initial_option,
    confirm,
  })
}

///|
pub fn multi_static_select(
  options : Array[OptionObject],
  action_id? : String,
  placeholder? : TextObject,
  initial_options? : Array[OptionObject],
  max_selected_items? : Int,
) -> BlockElement {
  Select({
    ..SelectElement::blank(MultiStatic),
    action_id,
    placeholder,
    options: Some(options),
    initial_options,
    max_selected_items,
  })
}

///|
pub fn users_select(
  action_id? : String,
  placeholder? : TextObject,
  initial_user? : String,
) -> BlockElement {
  Select({ ..SelectElement::blank(Users), action_id, placeholder, initial_user })
}

///|
pub fn multi_users_select(
  action_id? : String,
  placeholder? : TextObject,
  initial_users? : Array[String],
  max_selected_items? : Int,
) -> BlockElement {
  Select({
    ..SelectElement::blank(MultiUsers),
    action_id,
    placeholder,
    initial_users,
    max_selected_items,
  })
}

///|
pub fn channels_select(
  action_id? : String,
  placeholder? : TextObject,
  initial_channel? : String,
  response_url_enabled? : Bool,
) -> BlockElement {
  Select({
    ..SelectElement::blank(Channels),
    action_id,
    placeholder,
    initial_channel,
    response_url_enabled,
  })
}

///|
pub fn conversations_select(
  action_id? : String,
  placeholder? : TextObject,
  initial_conversation? : String,
  default_to_current_conversation? : Bool,
  filter? : ConversationFilterObject,
  response_url_enabled? : Bool,
) -> BlockElement {
  Select({
    ..SelectElement::blank(Conversations),
    action_id,
    placeholder,
    initial_conversation,
    default_to_current_conversation,
    filter,
    response_url_enabled,
  })
}

///|
pub fn multi_conversations_select(
  action_id? : String,
  placeholder? : TextObject,
  initial_conversations? : Array[String],
  default_to_current_conversation? : Bool,
  filter? : ConversationFilterObject,
  max_selected_items? : Int,
) -> BlockElement {
  Select({
    ..SelectElement::blank(MultiConversations),
    action_id,
    placeholder,
    initial_conversations,
    default_to_current_conversation,
    filter,
    max_selected_items,
  })
}

///|
pub fn external_select(
  action_id? : String,
  placeholder? : TextObject,
  min_query_length? : Int,
  initial_option? : OptionObject,
) -> BlockElement {
  Select({
    ..SelectElement::blank(External),
    action_id,
    placeholder,
    min_query_length,
    initial_option,
  })
}

///|
pub fn radio_buttons(
  options : Array[OptionObject],
  action_id? : String,
  initial_option? : OptionObject,
) -> BlockElement {
  Options({
    kind: RadioButtons,
    action_id,
    options: Some(options),
    initial_option,
    initial_options: None,
    confirm: None,
    focus_on_load: None,
    extra: Map([]),
  })
}

///|
pub fn checkboxes(
  options : Array[OptionObject],
  action_id? : String,
  initial_options? : Array[OptionObject],
) -> BlockElement {
  Options({
    kind: Checkboxes,
    action_id,
    options: Some(options),
    initial_option: None,
    initial_options,
    confirm: None,
    focus_on_load: None,
    extra: Map([]),
  })
}

///|
pub fn overflow(
  options : Array[OptionObject],
  action_id? : String,
  confirm? : ConfirmationDialogObject,
) -> BlockElement {
  Options({
    kind: Overflow,
    action_id,
    options: Some(options),
    initial_option: None,
    initial_options: None,
    confirm,
    focus_on_load: None,
    extra: Map([]),
  })
}

///|
fn picker(kind : PickerKind) -> PickerElement {
  {
    kind,
    action_id: None,
    placeholder: None,
    confirm: None,
    focus_on_load: None,
    initial_date: None,
    initial_time: None,
    initial_date_time: None,
    timezone: None,
    extra: Map([]),
  }
}

///|
pub fn datepicker(
  action_id? : String,
  placeholder? : TextObject,
  initial_date? : String,
) -> BlockElement {
  Picker({ ..picker(Date), action_id, placeholder, initial_date })
}

///|
pub fn timepicker(
  action_id? : String,
  placeholder? : TextObject,
  initial_time? : String,
  timezone? : String,
) -> BlockElement {
  Picker({ ..picker(Time), action_id, placeholder, initial_time, timezone })
}

///|
/// No placeholder: `datetimepicker` does not take one, which is the only reason
/// Slack keeps it a separate type from `datepicker`.
pub fn datetimepicker(
  action_id? : String,
  initial_date_time? : Int,
) -> BlockElement {
  Picker({ ..picker(DateTime), action_id, initial_date_time })
}

///|
pub fn plain_text_input(
  action_id? : String,
  placeholder? : TextObject,
  initial_value? : String,
  multiline? : Bool,
  min_length? : Int,
  max_length? : Int,
) -> BlockElement {
  PlainTextInput({
    action_id,
    placeholder,
    initial_value,
    multiline,
    min_length,
    max_length,
    dispatch_action_config: None,
    focus_on_load: None,
    extra: Map([]),
  })
}

///|
/// A rich-text paragraph.
pub fn rich_text_section(
  elements : Array[RichTextItem],
) -> RichTextBlockElement {
  Section(elements~, extra=Map([]))
}

///|
pub fn rich_text_text(text : String, style? : RichTextStyle) -> RichTextItem {
  Text(text=Some(text), style~, extra=Map([]))
}

///|
pub fn rich_text_link(
  url : String,
  text? : String,
  style? : RichTextStyle,
) -> RichTextItem {
  Link(url=Some(url), text~, unsafe_=None, style~, extra=Map([]))
}

///|
pub fn rich_text_user(user_id : String) -> RichTextItem {
  User(user_id=Some(user_id), style=None, extra=Map([]))
}

///|
pub fn rich_text_emoji(name : String, unicode? : String) -> RichTextItem {
  Emoji(name=Some(name), unicode~, skin_tone=None, style=None, extra=Map([]))
}

///|
pub fn rich_text_style(
  bold? : Bool,
  italic? : Bool,
  strike? : Bool,
  code? : Bool,
) -> RichTextStyle {
  {
    bold,
    italic,
    strike,
    code,
    highlight: None,
    client_highlight: None,
    unlink: None,
    extra: Map([]),
  }
}

///|
pub fn confirm(
  title? : String,
  text? : String,
  confirm_label? : String,
  deny_label? : String,
  style? : String,
) -> ConfirmationDialogObject {
  {
    title: title.map(t => TextObject::plain(t)),
    text: text.map(t => TextObject::mrkdwn(t)),
    confirm: confirm_label.map(t => TextObject::plain(t)),
    deny: deny_label.map(t => TextObject::plain(t)),
    style,
    extra: Map([]),
  }
}