///|
/// The kind of attachment a tile renders: a cover-fit image thumbnail, or a
/// gradient placeholder with a document glyph for everything else.
pub(all) enum AttachmentKind {
  Image
  Document
} derive(Eq)

///|
pub extend AttachmentKind with Eq::{equal, not_equal}

///|
/// One attachment in the strip.
pub(all) struct AttachmentItem {
  kind : AttachmentKind
  src : String
  label : String
} derive(Eq)

///|
pub extend AttachmentItem with Eq::{equal, not_equal}

///|
/// An image attachment, rendered cover-fit slightly overset so the thumbnail
/// hides the tile border.
pub fn AttachmentItem::image(
  src : String,
  label? : String = "Image attachment",
) -> AttachmentItem {
  { kind: Image, src, label, }
}

///|
/// A non-image attachment, rendered as a slate gradient placeholder with a
/// centered document glyph.
pub fn AttachmentItem::document(label : String) -> AttachmentItem {
  { kind: Document, src: "", label, }
}

///|
const AttachmentStripStyle : String = "display:inline-flex;align-items:flex-start;gap:8px;position:relative"

///|
const AttachmentTileStyle : String = "position:relative;flex:none;width:48px;height:48px;border:1px solid var(--sumi-stroke-primary,rgba(255,255,255,0.08));border-radius:8px;background:var(--sumi-block,rgba(255,255,255,0.08));overflow:hidden"

///|
const AttachmentImageStyle : String = "position:absolute;top:-1px;left:-1px;width:calc(100% + 2px);height:calc(100% + 2px);object-fit:cover;pointer-events:none"

///|
const AttachmentDocumentStyle : String = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(180.16deg,#4e6580 0.14%,#3d485d 99.3%);color:rgba(255,255,255,0.88)"

///|
const AttachmentRemoveStyle : String = "position:absolute;top:-2px;right:-2px;display:flex;align-items:center;justify-content:center;width:8px;height:8px;padding:0;margin:0;border:none;border-radius:11px;background:var(--sumi-canvas,#0d0d0d);color:#ffffff;cursor:pointer;appearance:none;-webkit-appearance:none;outline:none;opacity:0"

///|
const AttachmentFadeStyle : String = "position:absolute;top:0;right:0;width:91px;height:48px;background:linear-gradient(to left,var(--sumi-elevated,#222222) 60%,transparent);pointer-events:none"

///|
const AttachmentPinnedStyle : String = "position:absolute;top:0;right:0"

///|
fn attachment_tile(
  item : AttachmentItem,
  index : Int,
  on_remove? : @internal.Emit[Int],
) -> @html.Html {
  let tile_attrs = @html.Attrs::build()
    .data_set("slot", "attachment-tile")
    .data_set(
      "kind",
      match item.kind {
        Image => "image"
        Document => "document"
      },
    )
  let children : Array[@html.Html] = []
  match item.kind {
    Image =>
      children.push(
        @html.img(src=item.src, alt=item.label, style=[AttachmentImageStyle]),
      )
    Document =>
      children.push(
        @html.div(
          style=[AttachmentDocumentStyle],
          attrs=@html.Attrs::build().role("img").aria_label(item.label),
          [@primitives.icon_align_left(size=16)],
        ),
      )
  }
  if on_remove is Some(emit) {
    children.push(
      @html.button(
        style=[
          @internal.SumiBoxSizing,
          @internal.SumiTransition,
          AttachmentRemoveStyle,
        ],
        type_="button",
        on_click=emit(index),
        attrs=@html.Attrs::build()
          .data_set("slot", "attachment-remove")
          .aria_label("Remove \{item.label}"),
        [@primitives.icon_x_fill(size=4)],
      ),
    )
  }
  @html.div(
    style=[@internal.SumiBoxSizing, AttachmentTileStyle],
    attrs=tile_attrs,
    children,
  )
}

///|
/// The attachment strip from the design: a row of 48px tiles — image
/// thumbnails and document placeholders — followed by the add tile. Each
/// tile can carry an 8px remove badge that fades in on tile hover or
/// keyboard focus. Past `max_visible` attachments the row clips, a right-edge
/// fade appears, and the add tile pins itself over the fade.
///
/// ```moonbit nocheck
/// attachment_strip(
///   items=[
///     AttachmentItem::image("cover.png", label="Cover"),
///     AttachmentItem::document("Brief.pdf"),
///   ],
///   on_add=pick_files,
///   on_remove=set_items.map(i => items => items.filteri(fn(j, _) { j != i })),
/// )
/// ```
pub fn attachment_strip(
  items~ : Array[AttachmentItem],
  on_add? : @cmd.Cmd,
  on_remove? : @internal.Emit[Int],
  max_visible? : Int = 10,
  aria_label? : String = "Attachments",
  id? : String,
  class? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
) -> @html.Html {
  let element_attrs = @internal.sumi_attrs(attrs)
    .data_set("slot", "attachment-strip")
    .aria_label(aria_label)
  let overflow = items.length() > max_visible
  let visible_count = if overflow { max_visible } else { items.length() }
  let children : Array[@html.Html] = []
  for i in 0..