///|
pub(all) enum BubbleVariant {
  BubbleDefault
  BubbleSecondary
  BubbleMuted
  BubbleTinted
  BubbleOutline
  BubbleGhost
  BubbleDestructive
} derive(Debug, Eq)

///|
pub(all) enum BubbleReactionSide {
  ReactionTop
  ReactionBottom
} derive(Debug, Eq)

///|
fn bubble_variant_value(variant : BubbleVariant) -> String {
  match variant {
    BubbleDefault => "default"
    BubbleSecondary => "secondary"
    BubbleMuted => "muted"
    BubbleTinted => "tinted"
    BubbleOutline => "outline"
    BubbleGhost => "ghost"
    BubbleDestructive => "destructive"
  }
}

///|
fn bubble_variant_style(variant : BubbleVariant) -> String {
  match variant {
    BubbleDefault =>
      "--rui-bubble-bg:var(--rui-primary,oklch(0.205 0 0));--rui-bubble-fg:var(--rui-primary-foreground,oklch(0.985 0 0));--rui-bubble-border:transparent;--rui-bubble-radius:0.75rem;--rui-bubble-padding:0.5rem 0.75rem"
    BubbleSecondary =>
      "--rui-bubble-bg:var(--rui-secondary,oklch(0.97 0 0));--rui-bubble-fg:var(--rui-secondary-foreground,oklch(0.205 0 0));--rui-bubble-border:transparent;--rui-bubble-radius:0.75rem;--rui-bubble-padding:0.5rem 0.75rem"
    BubbleMuted =>
      "--rui-bubble-bg:var(--rui-muted,oklch(0.97 0 0));--rui-bubble-fg:var(--rui-foreground,oklch(0.145 0 0));--rui-bubble-border:transparent;--rui-bubble-radius:0.75rem;--rui-bubble-padding:0.5rem 0.75rem"
    BubbleTinted =>
      "--rui-bubble-bg:light-dark(oklch(from var(--rui-primary,oklch(0.205 0 0)) 0.93 calc(c * 0.4) h),oklch(from var(--rui-primary,oklch(0.922 0 0)) 0.3 calc(c * 0.4) h));--rui-bubble-fg:var(--rui-foreground,oklch(0.145 0 0));--rui-bubble-border:transparent;--rui-bubble-radius:0.75rem;--rui-bubble-padding:0.5rem 0.75rem"
    BubbleOutline =>
      "--rui-bubble-bg:var(--rui-background,white);--rui-bubble-fg:var(--rui-foreground,oklch(0.145 0 0));--rui-bubble-border:var(--rui-border,oklch(0.922 0 0));--rui-bubble-radius:0.75rem;--rui-bubble-padding:0.5rem 0.75rem"
    BubbleGhost =>
      "--rui-bubble-bg:transparent;--rui-bubble-fg:var(--rui-foreground,oklch(0.145 0 0));--rui-bubble-border:transparent;--rui-bubble-radius:0;--rui-bubble-padding:0"
    BubbleDestructive =>
      "--rui-bubble-bg:var(--rui-destructive-subtle,oklch(0.577 0.245 27.325 / 0.1));--rui-bubble-fg:var(--rui-destructive,oklch(0.577 0.245 27.325));--rui-bubble-border:transparent;--rui-bubble-radius:0.75rem;--rui-bubble-padding:0.5rem 0.75rem"
  }
}

///|
const BubbleContentBaseStyle : String = "width:fit-content;max-width:100%;min-width:0;align-self:var(--rui-bubble-content-align,flex-start);overflow:hidden;border:1px solid var(--rui-bubble-border,transparent);border-radius:var(--rui-bubble-radius,0.75rem);padding:var(--rui-bubble-padding,0.5rem 0.75rem);background:var(--rui-bubble-bg,var(--rui-primary,oklch(0.205 0 0)));color:var(--rui-bubble-fg,var(--rui-primary-foreground,oklch(0.985 0 0)));box-shadow:var(--rui-bubble-shadow,none);font-size:0.875rem;line-height:1.625;overflow-wrap:break-word;word-break:normal;outline:none"

///|
pub fn[C : @html.IsChildren] bubble_group(
  id? : String,
  class? : String,
  title? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  children : C,
) -> @html.Html {
  @html.div(
    id?,
    class?,
    title?,
    attrs=ui_attrs(attrs).data_set("slot", "bubble-group"),
    style=ui_styles(
      [
        UiBoxSizing,
        "display:flex;min-width:0;align-self:var(--rui-message-content-align,flex-start);flex-direction:column;gap:0.5rem",
      ],
      style,
    ),
    children,
  )
}

///|
pub fn[C : @html.IsChildren] bubble(
  variant? : BubbleVariant = BubbleDefault,
  align? : ContentAlignment = AlignStart,
  id? : String,
  class? : String,
  title? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  children : C,
) -> @html.Html {
  let align_value = content_alignment_value(align)
  @html.div(
    id?,
    class?,
    title?,
    attrs=ui_attrs(attrs)
      .data_set("slot", "bubble")
      .data_set("variant", bubble_variant_value(variant))
      .data_set("align", align_value),
    style=ui_styles(
      [
        UiBoxSizing,
        "position:relative;display:flex;width:fit-content;max-width:80%;min-width:0;align-self:var(--rui-message-content-align,flex-start);flex-direction:column;gap:0.25rem;--rui-bubble-content-align:flex-start",
        bubble_variant_style(variant),
        if variant is BubbleGhost {
          "max-width:100%"
        } else {
          ""
        },
        if align is AlignEnd {
          "align-self:flex-end;--rui-bubble-content-align:flex-end"
        } else {
          ""
        },
      ],
      style,
    ),
    children,
  )
}

///|
pub fn[C : @html.IsChildren] bubble_content(
  id? : String,
  class? : String,
  title? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  children : C,
) -> @html.Html {
  @html.div(
    id?,
    class?,
    title?,
    attrs=ui_attrs(attrs).data_set("slot", "bubble-content"),
    style=ui_styles([UiBoxSizing, UiTransition, BubbleContentBaseStyle], style),
    children,
  )
}

///|
#cfg(target="js")
fn bubble_bind_disabled_link(attrs : @html.Attrs, disabled : Bool) -> Unit {
  if disabled {
    ignore(
      attrs.on_click(event => {
        event.prevent_default()
        @cmd.none
      }),
    )
  }
}

///|
#cfg(not(target="js"))
fn bubble_bind_disabled_link(attrs : @html.Attrs, disabled : Bool) -> Unit {
  ignore((attrs, disabled))
}

///|
/// Render an interactive bubble-content anchor without a Slot/runtime wrapper.
pub fn[C : @html.IsChildren] bubble_content_link(
  href~ : String,
  disabled? : Bool = false,
  aria_label? : String,
  target? : @html.Target = Self,
  rel? : String,
  download? : String,
  escape? : Bool = false,
  on_click? : @cmd.Cmd,
  id? : String,
  class? : String,
  title? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  children : C,
) -> @html.Html {
  let element_attrs = ui_attrs(attrs).data_set("slot", "bubble-content")
  if aria_label is Some(label) {
    ignore(element_attrs.aria_label(label))
  }
  if disabled {
    ignore(
      element_attrs.aria_disabled("true").data_set("disabled", "").tabindex(-1),
    )
  } else if on_click is Some(command) {
    ignore(element_attrs.on_click(_ => command))
  }
  bubble_bind_disabled_link(element_attrs, disabled)
  @html.a(
    href~,
    target~,
    rel?,
    download?,
    id?,
    class?,
    title?,
    attrs=element_attrs,
    style=ui_styles(
      [
        UiBoxSizing,
        UiTransition,
        BubbleContentBaseStyle,
        "text-decoration:none;cursor:pointer",
        ui_disabled_style(disabled),
      ],
      style,
    ),
    children,
    escape~,
  )
}

///|
/// Render the button branch of Vega's interactive bubble-content recipe.
pub fn[C : @html.IsChildren] bubble_content_button(
  disabled? : Bool = false,
  type_? : String = "button",
  aria_label? : String,
  on_click? : @cmd.Cmd,
  id? : String,
  class? : String,
  title? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  children : C,
) -> @html.Html {
  let element_attrs = ui_attrs(attrs).data_set("slot", "bubble-content")
  if aria_label is Some(label) {
    ignore(element_attrs.aria_label(label))
  }
  if disabled {
    ignore(element_attrs.aria_disabled("true").data_set("disabled", ""))
  }
  @html.button(
    type_~,
    disabled~,
    on_click?,
    id?,
    class?,
    title?,
    attrs=element_attrs,
    style=ui_styles(
      [
        UiBoxSizing,
        UiFontSans,
        UiTransition,
        BubbleContentBaseStyle,
        "margin:0;text-align:start;cursor:pointer;appearance:none;-webkit-appearance:none",
        ui_disabled_style(disabled),
      ],
      style,
    ),
    children,
  )
}

///|
pub fn[C : @html.IsChildren] bubble_reactions(
  side? : BubbleReactionSide = ReactionBottom,
  align? : ContentAlignment = AlignEnd,
  id? : String,
  class? : String,
  title? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  children : C,
) -> @html.Html {
  let side_value = if side is ReactionTop { "top" } else { "bottom" }
  let align_value = content_alignment_value(align)
  let placement = match (side, align) {
    (ReactionTop, AlignStart) =>
      "top:0;inset-inline-start:0.75rem;transform:translateY(-75%)"
    (ReactionTop, AlignEnd) =>
      "top:0;inset-inline-end:0.75rem;transform:translateY(-75%)"
    (ReactionBottom, AlignStart) =>
      "bottom:0;inset-inline-start:0.75rem;transform:translateY(75%)"
    (ReactionBottom, AlignEnd) =>
      "bottom:0;inset-inline-end:0.75rem;transform:translateY(75%)"
  }
  @html.div(
    id?,
    class?,
    title?,
    attrs=ui_attrs(attrs)
      .data_set("slot", "bubble-reactions")
      .data_set("side", side_value)
      .data_set("align", align_value),
    style=ui_styles(
      [
        UiBoxSizing,
        "position:absolute;z-index:10;display:flex;width:fit-content;flex-shrink:0;align-items:center;justify-content:center;gap:0.25rem;border-radius:9999px;background:var(--rui-muted,oklch(0.97 0 0));padding:0.125rem 0.375rem;font-size:0.875rem;box-shadow:0 0 0 3px var(--rui-card,var(--rui-background,white))",
        placement,
      ],
      style,
    ),
    children,
  )
}