///|
/// Type-safe TUI DSL
/// Produces typed attributes using luna's Attr[E, A] with A = TuiAttrValue

// =============================================================================
// Attribute helpers
// =============================================================================

///|
/// Create a static typed attribute
pub fn attr(value : TuiAttrValue) -> TuiAttr {
  @luna.attr_static(value)
}

///|
/// Create a dynamic typed attribute
pub fn attr_dyn(getter : () -> TuiAttrValue) -> TuiAttr {
  @luna.attr_dynamic(getter)
}

///|
/// Create an event handler attribute
pub fn on(handler : (TuiEvent) -> Unit) -> TuiAttr {
  @luna.attr_handler(@luna.handler(handler))
}

///|
/// Convert typed attributes array to luna attrs array
fn typed_attrs_to_luna(attrs : Array[TuiAttrValue]) -> Array[(String, TuiAttr)] {
  attrs.map(fn(a) {
    let key = attr_key(a)
    (key, @luna.attr_static(a))
  })
}

///|
/// Get the attribute key name from TuiAttrValue
fn attr_key(attr : TuiAttrValue) -> String {
  match attr {
    TuiAttrValue::Direction(_) => "direction"
    TuiAttrValue::Width(_) => "width"
    TuiAttrValue::Height(_) => "height"
    TuiAttrValue::MinWidth(_) => "min_width"
    TuiAttrValue::MinHeight(_) => "min_height"
    TuiAttrValue::FlexGrow(_) => "flex_grow"
    TuiAttrValue::FlexShrink(_) => "flex_shrink"
    TuiAttrValue::Gap(_) => "gap"
    TuiAttrValue::Display(_) => "display"
    TuiAttrValue::GridTemplateColumns(_) => "grid_template_columns"
    TuiAttrValue::GridTemplateRows(_) => "grid_template_rows"
    TuiAttrValue::GridAutoFlow(_) => "grid_auto_flow"
    TuiAttrValue::GridColumn(_) => "grid_column"
    TuiAttrValue::GridRow(_) => "grid_row"
    TuiAttrValue::GridColumnSpan(_) => "grid_column_span"
    TuiAttrValue::GridRowSpan(_) => "grid_row_span"
    TuiAttrValue::GridTemplateAreas(_) => "grid_template_areas"
    TuiAttrValue::GridArea(_) => "grid_area"
    TuiAttrValue::Padding(_) => "padding"
    TuiAttrValue::PaddingX(_) => "padding_x"
    TuiAttrValue::PaddingY(_) => "padding_y"
    TuiAttrValue::Margin(_) => "margin"
    TuiAttrValue::MarginX(_) => "margin_x"
    TuiAttrValue::MarginY(_) => "margin_y"
    TuiAttrValue::Justify(_) => "justify"
    TuiAttrValue::Align(_) => "align"
    TuiAttrValue::Border(_) => "border"
    TuiAttrValue::BorderColor(_) => "border_color"
    TuiAttrValue::Fg(_) => "fg"
    TuiAttrValue::Bg(_) => "bg"
    TuiAttrValue::Bold(_) => "bold"
    TuiAttrValue::Underline(_) => "underline"
    TuiAttrValue::Id(_) => "id"
    TuiAttrValue::TabIndex(_) => "tab_index"
    TuiAttrValue::Role(_) => "role"
  }
}

// =============================================================================
// Type-safe container builders
// =============================================================================

///|
/// Box properties for type-safe construction
pub struct BoxProps {
  id : String
  direction : DirectionValue
  width : DimensionValue
  height : DimensionValue
  min_width : DimensionValue
  min_height : DimensionValue
  flex_grow : Double
  flex_shrink : Double
  gap : Double
  padding : Double
  padding_x : Double
  padding_y : Double
  margin : Double
  margin_x : Double
  margin_y : Double
  justify : AlignmentValue?
  align : AlignmentValue?
  border : BorderValue
  border_color : ColorValue?
  bg : ColorValue?
  tab_index : Int
  role : @core.Role?
}

///|
pub fn BoxProps::default() -> BoxProps {
  {
    id: "",
    direction: Row,
    width: Auto,
    height: Auto,
    min_width: Auto,
    min_height: Auto,
    flex_grow: 0.0,
    flex_shrink: 1.0,
    gap: 0.0,
    padding: 0.0,
    padding_x: 0.0,
    padding_y: 0.0,
    margin: 0.0,
    margin_x: 0.0,
    margin_y: 0.0,
    justify: None,
    align: None,
    border: None,
    border_color: None,
    bg: None,
    tab_index: -1,
    role: None,
  }
}

///|
/// Convert BoxProps to typed attributes
pub fn BoxProps::to_attrs(self : BoxProps) -> Array[TuiAttrValue] {
  let attrs : Array[TuiAttrValue] = [TuiAttrValue::Direction(self.direction)]
  if self.id.length() > 0 {
    attrs.push(TuiAttrValue::Id(self.id))
  }
  match self.width {
    Auto => ()
    _ => attrs.push(TuiAttrValue::Width(self.width))
  }
  match self.height {
    Auto => ()
    _ => attrs.push(TuiAttrValue::Height(self.height))
  }
  match self.min_width {
    Auto => ()
    _ => attrs.push(TuiAttrValue::MinWidth(self.min_width))
  }
  match self.min_height {
    Auto => ()
    _ => attrs.push(TuiAttrValue::MinHeight(self.min_height))
  }
  if self.flex_grow > 0.0 {
    attrs.push(TuiAttrValue::FlexGrow(self.flex_grow))
  }
  if self.flex_shrink != 1.0 {
    attrs.push(TuiAttrValue::FlexShrink(self.flex_shrink))
  }
  if self.gap > 0.0 {
    attrs.push(TuiAttrValue::Gap(self.gap))
  }
  if self.padding > 0.0 {
    attrs.push(TuiAttrValue::Padding(self.padding))
  }
  if self.padding_x > 0.0 {
    attrs.push(TuiAttrValue::PaddingX(self.padding_x))
  }
  if self.padding_y > 0.0 {
    attrs.push(TuiAttrValue::PaddingY(self.padding_y))
  }
  if self.margin > 0.0 {
    attrs.push(TuiAttrValue::Margin(self.margin))
  }
  if self.margin_x > 0.0 {
    attrs.push(TuiAttrValue::MarginX(self.margin_x))
  }
  if self.margin_y > 0.0 {
    attrs.push(TuiAttrValue::MarginY(self.margin_y))
  }
  match self.justify {
    Some(v) => attrs.push(TuiAttrValue::Justify(v))
    None => ()
  }
  match self.align {
    Some(v) => attrs.push(TuiAttrValue::Align(v))
    None => ()
  }
  match self.border {
    None => ()
    _ => attrs.push(TuiAttrValue::Border(self.border))
  }
  match self.border_color {
    Some(v) => attrs.push(TuiAttrValue::BorderColor(v))
    None => ()
  }
  match self.bg {
    Some(v) => attrs.push(TuiAttrValue::Bg(v))
    None => ()
  }
  if self.tab_index >= 0 {
    attrs.push(TuiAttrValue::TabIndex(self.tab_index))
  }
  match self.role {
    Some(v) => attrs.push(TuiAttrValue::Role(v))
    None => ()
  }
  attrs
}

///|
/// Create a box node from typed props
pub fn box_node(props : BoxProps, children : Array[TuiNode]) -> TuiNode {
  let attrs = typed_attrs_to_luna(props.to_attrs())
  @luna.h("box", attrs, children)
}

///|
/// Text properties
pub struct TextProps {
  content : String
  fg : ColorValue?
  bold : Bool
  underline : Bool
  role : @core.Role?
}

///|
pub fn TextProps::default(content : String) -> TextProps {
  { content, fg: None, bold: false, underline: false, role: None }
}

///|
pub fn TextProps::to_attrs(self : TextProps) -> Array[TuiAttrValue] {
  let attrs : Array[TuiAttrValue] = []
  match self.fg {
    Some(v) => attrs.push(TuiAttrValue::Fg(v))
    None => ()
  }
  if self.bold {
    attrs.push(TuiAttrValue::Bold(true))
  }
  if self.underline {
    attrs.push(TuiAttrValue::Underline(true))
  }
  match self.role {
    Some(v) => attrs.push(TuiAttrValue::Role(v))
    None => ()
  }
  attrs
}

///|
/// Create a text node from typed props
pub fn text_node(props : TextProps) -> TuiNode {
  let attrs = props.to_attrs()
  if attrs.length() > 0 {
    @luna.h("text", typed_attrs_to_luna(attrs), [@luna.text(props.content)])
  } else {
    @luna.text(props.content)
  }
}

// =============================================================================
// Convenience DSL functions (matching old string-based API)
// =============================================================================

///|
/// Shared box attribute builder - extracts common logic from row/column
fn build_box_attrs(
  direction : DirectionValue,
  id : String,
  gap : Double,
  margin : Double,
  margin_x : Double,
  margin_y : Double,
  padding : Double,
  padding_x : Double,
  padding_y : Double,
  width : Double,
  height : Double,
  min_width : Double,
  min_height : Double,
  flex_grow : Double,
  justify : String,
  align : String,
  border : String,
  border_color : String,
  bg : String,
  role : String,
  tab_index : Int,
  on_click : ((TuiEvent) -> Unit)?,
) -> Array[(String, TuiAttr)] {
  let attrs : Array[(String, TuiAttr)] = [
    ("direction", attr(TuiAttrValue::Direction(direction))),
  ]
  if id.length() > 0 {
    attrs.push(("id", attr(TuiAttrValue::Id(id))))
  }
  if gap > 0.0 {
    attrs.push(("gap", attr(TuiAttrValue::Gap(gap))))
  }
  if margin > 0.0 {
    attrs.push(("margin", attr(TuiAttrValue::Margin(margin))))
  }
  if margin_x > 0.0 {
    attrs.push(("margin_x", attr(TuiAttrValue::MarginX(margin_x))))
  }
  if margin_y > 0.0 {
    attrs.push(("margin_y", attr(TuiAttrValue::MarginY(margin_y))))
  }
  if padding > 0.0 {
    attrs.push(("padding", attr(TuiAttrValue::Padding(padding))))
  }
  if padding_x > 0.0 {
    attrs.push(("padding_x", attr(TuiAttrValue::PaddingX(padding_x))))
  }
  if padding_y > 0.0 {
    attrs.push(("padding_y", attr(TuiAttrValue::PaddingY(padding_y))))
  }
  if width >= 0.0 {
    attrs.push(
      ("width", attr(TuiAttrValue::Width(DimensionValue::Length(width)))),
    )
  }
  if height >= 0.0 {
    attrs.push(
      ("height", attr(TuiAttrValue::Height(DimensionValue::Length(height)))),
    )
  }
  if min_width >= 0.0 {
    attrs.push(
      (
        "min_width",
        attr(TuiAttrValue::MinWidth(DimensionValue::Length(min_width))),
      ),
    )
  }
  if min_height >= 0.0 {
    attrs.push(
      (
        "min_height",
        attr(TuiAttrValue::MinHeight(DimensionValue::Length(min_height))),
      ),
    )
  }
  if flex_grow > 0.0 {
    attrs.push(("flex_grow", attr(TuiAttrValue::FlexGrow(flex_grow))))
  }
  if justify.length() > 0 {
    attrs.push(
      ("justify", attr(TuiAttrValue::Justify(parse_alignment_value(justify)))),
    )
  }
  if align.length() > 0 {
    attrs.push(
      ("align", attr(TuiAttrValue::Align(parse_alignment_value(align)))),
    )
  }
  if border.length() > 0 {
    attrs.push(
      ("border", attr(TuiAttrValue::Border(parse_border_value(border)))),
    )
  }
  if border_color.length() > 0 {
    attrs.push(
      (
        "border_color",
        attr(TuiAttrValue::BorderColor(parse_color_value(border_color))),
      ),
    )
  }
  if bg.length() > 0 {
    attrs.push(("bg", attr(TuiAttrValue::Bg(parse_color_value(bg)))))
  }
  if role.length() > 0 {
    attrs.push(("role", attr(TuiAttrValue::Role(parse_role_value(role)))))
  }
  // TabIndex: -1 means not focusable (excluded), 0+ sets tab order
  if tab_index >= 0 {
    attrs.push(("tab_index", attr(TuiAttrValue::TabIndex(tab_index))))
  }
  match on_click {
    Some(handler) => attrs.push(("on_click", on(handler)))
    None => ()
  }
  attrs
}

///|
/// Row container (horizontal flex)
pub fn row(
  id? : String = "",
  gap? : Double = 0.0,
  margin? : Double = 0.0,
  margin_x? : Double = 0.0,
  margin_y? : Double = 0.0,
  padding? : Double = 0.0,
  padding_x? : Double = 0.0,
  padding_y? : Double = 0.0,
  width? : Double = -1.0,
  height? : Double = -1.0,
  min_width? : Double = -1.0,
  min_height? : Double = -1.0,
  flex_grow? : Double = 0.0,
  justify? : String = "",
  align? : String = "",
  border? : String = "",
  border_color? : String = "",
  bg? : String = "",
  role? : String = "",
  tab_index? : Int = -1,
  on_click? : ((TuiEvent) -> Unit)? = None,
  children : Array[TuiNode],
) -> TuiNode {
  let attrs = build_box_attrs(
    DirectionValue::Row,
    id,
    gap,
    margin,
    margin_x,
    margin_y,
    padding,
    padding_x,
    padding_y,
    width,
    height,
    min_width,
    min_height,
    flex_grow,
    justify,
    align,
    border,
    border_color,
    bg,
    role,
    tab_index,
    on_click,
  )
  @luna.h("box", attrs, children)
}

///|
/// Column container (vertical flex)
pub fn column(
  id? : String = "",
  gap? : Double = 0.0,
  margin? : Double = 0.0,
  margin_x? : Double = 0.0,
  margin_y? : Double = 0.0,
  padding? : Double = 0.0,
  padding_x? : Double = 0.0,
  padding_y? : Double = 0.0,
  width? : Double = -1.0,
  height? : Double = -1.0,
  min_width? : Double = -1.0,
  min_height? : Double = -1.0,
  flex_grow? : Double = 0.0,
  justify? : String = "",
  align? : String = "",
  border? : String = "",
  border_color? : String = "",
  bg? : String = "",
  role? : String = "",
  tab_index? : Int = -1,
  on_click? : ((TuiEvent) -> Unit)? = None,
  children : Array[TuiNode],
) -> TuiNode {
  let attrs = build_box_attrs(
    DirectionValue::Column,
    id,
    gap,
    margin,
    margin_x,
    margin_y,
    padding,
    padding_x,
    padding_y,
    width,
    height,
    min_width,
    min_height,
    flex_grow,
    justify,
    align,
    border,
    border_color,
    bg,
    role,
    tab_index,
    on_click,
  )
  @luna.h("box", attrs, children)
}

///|
/// Generic view container (flex layout with configurable direction)
/// Default direction is column (React Native style)
pub fn view(
  direction? : String = "column",
  id? : String = "",
  gap? : Double = 0.0,
  margin? : Double = 0.0,
  margin_x? : Double = 0.0,
  margin_y? : Double = 0.0,
  padding? : Double = 0.0,
  padding_x? : Double = 0.0,
  padding_y? : Double = 0.0,
  width? : Double = -1.0,
  height? : Double = -1.0,
  min_width? : Double = -1.0,
  min_height? : Double = -1.0,
  flex_grow? : Double = 0.0,
  justify? : String = "",
  align? : String = "",
  border? : String = "",
  border_color? : String = "",
  bg? : String = "",
  role? : String = "",
  tab_index? : Int = -1,
  on_click? : ((TuiEvent) -> Unit)? = None,
  children : Array[TuiNode],
) -> TuiNode {
  let dir = if direction == "row" {
    DirectionValue::Row
  } else {
    DirectionValue::Column
  }
  let attrs = build_box_attrs(
    dir, id, gap, margin, margin_x, margin_y, padding, padding_x, padding_y, width,
    height, min_width, min_height, flex_grow, justify, align, border, border_color,
    bg, role, tab_index, on_click,
  )
  @luna.h("box", attrs, children)
}

///|
/// Grid container with CSS Grid layout
/// Supports both explicit columns/rows and named areas
pub fn grid(
  columns? : Array[Double] = [],
  rows? : Array[Double] = [],
  areas? : Array[String] = [],
  auto_flow? : String = "row",
  id? : String = "",
  gap? : Double = 0.0,
  padding? : Double = 0.0,
  padding_x? : Double = 0.0,
  padding_y? : Double = 0.0,
  width? : Double = -1.0,
  height? : Double = -1.0,
  border? : String = "",
  border_color? : String = "",
  bg? : String = "",
  role? : String = "",
  tab_index? : Int = -1,
  children : Array[TuiNode],
) -> TuiNode {
  let attrs : Array[(String, TuiAttr)] = [
    ("display", attr(TuiAttrValue::Display(DisplayValue::Grid))),
  ]
  // Grid template areas (takes precedence for column/row count if provided)
  if areas.length() > 0 {
    attrs.push(
      ("grid_template_areas", attr(TuiAttrValue::GridTemplateAreas(areas))),
    )
    // Auto-generate rows based on areas count
    if rows.length() == 0 {
      let row_tracks = Array::makei(areas.length(), fn(_i) {
        @values.TrackSizingFunction::Fr(1.0)
      })
      attrs.push(
        ("grid_template_rows", attr(TuiAttrValue::GridTemplateRows(row_tracks))),
      )
    }
    // Auto-generate columns based on first area row
    if columns.length() == 0 && areas.length() > 0 {
      let first_row = areas[0]
        .split(" ")
        .filter(fn(s) { s.length() > 0 })
        .collect()
      let col_count = first_row.length()
      let col_tracks = Array::makei(col_count, fn(_i) {
        @values.TrackSizingFunction::Fr(1.0)
      })
      attrs.push(
        (
          "grid_template_columns",
          attr(TuiAttrValue::GridTemplateColumns(col_tracks)),
        ),
      )
    }
  }
  // Grid template columns (convert Double array to fr values)
  if columns.length() > 0 {
    let tracks = columns.map(fn(v) { @values.TrackSizingFunction::Fr(v) })
    attrs.push(
      ("grid_template_columns", attr(TuiAttrValue::GridTemplateColumns(tracks))),
    )
  }
  // Grid template rows
  if rows.length() > 0 {
    let tracks = rows.map(fn(v) { @values.TrackSizingFunction::Fr(v) })
    attrs.push(
      ("grid_template_rows", attr(TuiAttrValue::GridTemplateRows(tracks))),
    )
  }
  // Grid auto flow
  let flow = match auto_flow {
    "column" => GridAutoFlowValue::Column
    "row-dense" => GridAutoFlowValue::RowDense
    "column-dense" => GridAutoFlowValue::ColumnDense
    _ => GridAutoFlowValue::Row
  }
  attrs.push(("grid_auto_flow", attr(TuiAttrValue::GridAutoFlow(flow))))
  // Common attributes
  if id.length() > 0 {
    attrs.push(("id", attr(TuiAttrValue::Id(id))))
  }
  if gap > 0.0 {
    attrs.push(("gap", attr(TuiAttrValue::Gap(gap))))
  }
  if padding > 0.0 {
    attrs.push(("padding", attr(TuiAttrValue::Padding(padding))))
  }
  if padding_x > 0.0 {
    attrs.push(("padding_x", attr(TuiAttrValue::PaddingX(padding_x))))
  }
  if padding_y > 0.0 {
    attrs.push(("padding_y", attr(TuiAttrValue::PaddingY(padding_y))))
  }
  if width >= 0.0 {
    attrs.push(
      ("width", attr(TuiAttrValue::Width(DimensionValue::Length(width)))),
    )
  }
  if height >= 0.0 {
    attrs.push(
      ("height", attr(TuiAttrValue::Height(DimensionValue::Length(height)))),
    )
  }
  if border.length() > 0 {
    attrs.push(
      ("border", attr(TuiAttrValue::Border(parse_border_value(border)))),
    )
  }
  if border_color.length() > 0 {
    attrs.push(
      (
        "border_color",
        attr(TuiAttrValue::BorderColor(parse_color_value(border_color))),
      ),
    )
  }
  if bg.length() > 0 {
    attrs.push(("bg", attr(TuiAttrValue::Bg(parse_color_value(bg)))))
  }
  if role.length() > 0 {
    attrs.push(("role", attr(TuiAttrValue::Role(parse_role_value(role)))))
  }
  if tab_index >= 0 {
    attrs.push(("tab_index", attr(TuiAttrValue::TabIndex(tab_index))))
  }
  @luna.h("box", attrs, children)
}

///|
/// Grid item with placement control (explicit column/row positioning)
pub fn grid_item(
  column? : Int = 0,
  row? : Int = 0,
  column_span? : Int = 1,
  row_span? : Int = 1,
  tab_index? : Int = -1,
  child~ : TuiNode,
) -> TuiNode {
  let attrs : Array[(String, TuiAttr)] = []
  // Column placement (1-indexed)
  if column > 0 {
    attrs.push(("grid_column", attr(TuiAttrValue::GridColumn(column))))
  }
  // Row placement (1-indexed)
  if row > 0 {
    attrs.push(("grid_row", attr(TuiAttrValue::GridRow(row))))
  }
  // Column span
  if column_span > 1 {
    attrs.push(
      ("grid_column_span", attr(TuiAttrValue::GridColumnSpan(column_span))),
    )
  }
  // Row span
  if row_span > 1 {
    attrs.push(("grid_row_span", attr(TuiAttrValue::GridRowSpan(row_span))))
  }
  // Tab index for focus management
  if tab_index >= 0 {
    attrs.push(("tab_index", attr(TuiAttrValue::TabIndex(tab_index))))
  }
  @luna.h("box", attrs, [child])
}

///|
/// Grid area - place child in a named grid area
/// Used with grid(areas=[...]) for semantic layout
pub fn grid_area(name : String, child : TuiNode) -> TuiNode {
  let attrs : Array[(String, TuiAttr)] = [
    ("grid_area", attr(TuiAttrValue::GridArea(name))),
  ]
  @luna.h("box", attrs, [child])
}

///|
/// Static text node
pub fn text(
  content : String,
  fg? : String = "",
  bold? : Bool = false,
  underline? : Bool = false,
  role? : String = "",
) -> TuiNode {
  if fg.length() > 0 || bold || underline || role.length() > 0 {
    let attrs : Array[(String, TuiAttr)] = []
    if fg.length() > 0 {
      attrs.push(("fg", attr(TuiAttrValue::Fg(parse_color_value(fg)))))
    }
    if bold {
      attrs.push(("bold", attr(TuiAttrValue::Bold(true))))
    }
    if underline {
      attrs.push(("underline", attr(TuiAttrValue::Underline(true))))
    }
    if role.length() > 0 {
      attrs.push(("role", attr(TuiAttrValue::Role(parse_role_value(role)))))
    }
    @luna.h("text", attrs, [@luna.text(content)])
  } else {
    @luna.text(content)
  }
}

///|
/// Dynamic text node
pub fn text_dyn(
  getter : () -> String,
  fg? : String = "",
  bold? : Bool = false,
  underline? : Bool = false,
  role? : String = "",
) -> TuiNode {
  if fg.length() > 0 || bold || underline || role.length() > 0 {
    let attrs : Array[(String, TuiAttr)] = []
    if fg.length() > 0 {
      attrs.push(("fg", attr(TuiAttrValue::Fg(parse_color_value(fg)))))
    }
    if bold {
      attrs.push(("bold", attr(TuiAttrValue::Bold(true))))
    }
    if underline {
      attrs.push(("underline", attr(TuiAttrValue::Underline(true))))
    }
    if role.length() > 0 {
      attrs.push(("role", attr(TuiAttrValue::Role(parse_role_value(role)))))
    }
    @luna.h("text", attrs, [@luna.text_dyn(getter)])
  } else {
    @luna.text_dyn(getter)
  }
}

///|
/// Flexible spacer
pub fn spacer() -> TuiNode {
  @luna.h("spacer", [("flex_grow", attr(TuiAttrValue::FlexGrow(1.0)))], [])
}

///|
/// Horizontal space with fixed width
pub fn hspace(width : Double) -> TuiNode {
  @luna.h(
    "spacer",
    [
      ("width", attr(TuiAttrValue::Width(DimensionValue::Length(width)))),
      ("flex_grow", attr(TuiAttrValue::FlexGrow(0.0))),
      ("flex_shrink", attr(TuiAttrValue::FlexShrink(0.0))),
    ],
    [],
  )
}

///|
/// Vertical space with fixed height
pub fn vspace(height : Double) -> TuiNode {
  @luna.h(
    "spacer",
    [
      ("height", attr(TuiAttrValue::Height(DimensionValue::Length(height)))),
      ("flex_grow", attr(TuiAttrValue::FlexGrow(0.0))),
      ("flex_shrink", attr(TuiAttrValue::FlexShrink(0.0))),
    ],
    [],
  )
}

///|
/// Conditional rendering
pub fn show(when : () -> Bool, child : () -> TuiNode) -> TuiNode {
  @luna.show(when, child)
}

///|
/// List rendering
pub fn for_each(items : () -> Array[TuiNode]) -> TuiNode {
  @luna.for_each(items)
}

///|
/// Component boundary
pub fn component(render : () -> TuiNode) -> TuiNode {
  @luna.component(render)
}

///|
/// Fragment
pub fn fragment(children : Array[TuiNode]) -> TuiNode {
  @luna.fragment(children)
}

// =============================================================================
// String parsing helpers (for backward compatibility with string-based API)
// =============================================================================

///|
fn parse_alignment_value(s : String) -> AlignmentValue {
  match s {
    "start" => AlignmentValue::Start
    "center" => AlignmentValue::Center
    "end" => AlignmentValue::End
    "stretch" => AlignmentValue::Stretch
    "space-between" => AlignmentValue::SpaceBetween
    "space-around" => AlignmentValue::SpaceAround
    "space-evenly" => AlignmentValue::SpaceEvenly
    "flex-start" => AlignmentValue::FlexStart
    "flex-end" => AlignmentValue::FlexEnd
    _ => AlignmentValue::FlexStart
  }
}

///|
fn parse_border_value(s : String) -> BorderValue {
  match s {
    "single" => BorderValue::Single
    "double" => BorderValue::Double
    "rounded" => BorderValue::Rounded
    "ascii" => BorderValue::Ascii
    "none" => BorderValue::None
    _ => BorderValue::Single
  }
}

///|
fn parse_color_value(s : String) -> ColorValue {
  if s.has_prefix("rgb(") && s.has_suffix(")") && s.length() > 5 {
    let inner = s.view(start_offset=4, end_offset=s.length() - 1)
    let parts = inner.split(",").collect()
    if parts.length() == 3 {
      let r = try? @string.parse_int(parts[0].to_owned().trim(chars=" "))
      let g = try? @string.parse_int(parts[1].to_owned().trim(chars=" "))
      let b = try? @string.parse_int(parts[2].to_owned().trim(chars=" "))
      match (r, g, b) {
        (Ok(r), Ok(g), Ok(b)) => return ColorValue::Rgb(r, g, b)
        _ => ()
      }
    }
  }
  ColorValue::Named(s)
}

///|
fn parse_role_value(s : String) -> @core.Role {
  let v = s.to_lower()
  if v.has_prefix("custom:") && v.length() > 7 {
    return @core.Role::Custom(v.view(start_offset=7).to_owned())
  }
  match v {
    "button" => @core.Role::Button
    "checkbox" => @core.Role::Checkbox
    "link" => @core.Role::Link
    "menuitem" => @core.Role::Menuitem
    "option" => @core.Role::Option
    "radio" => @core.Role::Radio
    "slider" => @core.Role::Slider
    "switch" => @core.Role::Switch
    "tab" => @core.Role::Tab
    "textbox" => @core.Role::Textbox
    "listbox" => @core.Role::Listbox
    "menu" => @core.Role::Menu
    "menubar" => @core.Role::Menubar
    "radiogroup" => @core.Role::Radiogroup
    "tablist" => @core.Role::Tablist
    "tree" => @core.Role::Tree
    "treegrid" => @core.Role::Treegrid
    "article" => @core.Role::Article
    "cell" => @core.Role::Cell
    "columnheader" => @core.Role::Columnheader
    "definition" => @core.Role::Definition
    "directory" => @core.Role::Directory
    "document" => @core.Role::Document
    "group" => @core.Role::Group
    "heading" => @core.Role::Heading
    "img" => @core.Role::Img
    "list" => @core.Role::List
    "listitem" => @core.Role::Listitem
    "row" => @core.Role::Row
    "rowgroup" => @core.Role::Rowgroup
    "rowheader" => @core.Role::Rowheader
    "separator" => @core.Role::Separator
    "table" => @core.Role::Table
    "term" => @core.Role::Term
    "banner" => @core.Role::Banner
    "complementary" => @core.Role::Complementary
    "contentinfo" => @core.Role::Contentinfo
    "form" => @core.Role::Form
    "main" => @core.Role::Main
    "navigation" => @core.Role::Navigation
    "region" => @core.Role::Region
    "search" => @core.Role::Search
    "alert" => @core.Role::Alert
    "log" => @core.Role::Log
    "marquee" => @core.Role::Marquee
    "status" => @core.Role::Status
    "timer" => @core.Role::Timer
    "alertdialog" => @core.Role::Alertdialog
    "dialog" => @core.Role::Dialog
    _ => @core.Role::Custom(s)
  }
}

///|
fn role_to_string(role : @core.Role) -> String {
  match role {
    Button => "button"
    Checkbox => "checkbox"
    Link => "link"
    Menuitem => "menuitem"
    Option => "option"
    Radio => "radio"
    Slider => "slider"
    Switch => "switch"
    Tab => "tab"
    Textbox => "textbox"
    Listbox => "listbox"
    Menu => "menu"
    Menubar => "menubar"
    Radiogroup => "radiogroup"
    Tablist => "tablist"
    Tree => "tree"
    Treegrid => "treegrid"
    Article => "article"
    Cell => "cell"
    Columnheader => "columnheader"
    Definition => "definition"
    Directory => "directory"
    Document => "document"
    Group => "group"
    Heading => "heading"
    Img => "img"
    List => "list"
    Listitem => "listitem"
    Row => "row"
    Rowgroup => "rowgroup"
    Rowheader => "rowheader"
    Separator => "separator"
    Table => "table"
    Term => "term"
    Banner => "banner"
    Complementary => "complementary"
    Contentinfo => "contentinfo"
    Form => "form"
    Main => "main"
    Navigation => "navigation"
    Region => "region"
    Search => "search"
    Alert => "alert"
    Log => "log"
    Marquee => "marquee"
    Status => "status"
    Timer => "timer"
    Alertdialog => "alertdialog"
    Dialog => "dialog"
    Custom(name) => name
  }
}

// =============================================================================
// Attribute to string conversion (for debugging/testing)
// =============================================================================

///|
/// Convert TuiAttrValue to (key, value) string pair
pub fn attr_to_string(attr : TuiAttrValue) -> (String, String) {
  let key = attr_key(attr)
  let value = match attr {
    TuiAttrValue::Direction(v) =>
      match v {
        DirectionValue::Row => "row"
        DirectionValue::Column => "column"
      }
    TuiAttrValue::Width(v) => dimension_to_string(v)
    TuiAttrValue::Height(v) => dimension_to_string(v)
    TuiAttrValue::MinWidth(v) => dimension_to_string(v)
    TuiAttrValue::MinHeight(v) => dimension_to_string(v)
    TuiAttrValue::FlexGrow(v) => v.to_int().to_string()
    TuiAttrValue::FlexShrink(v) => v.to_int().to_string()
    TuiAttrValue::Gap(v) => v.to_int().to_string()
    TuiAttrValue::Display(v) => display_to_string(v)
    TuiAttrValue::GridTemplateColumns(v) => track_list_to_string(v)
    TuiAttrValue::GridTemplateRows(v) => track_list_to_string(v)
    TuiAttrValue::GridAutoFlow(v) => grid_auto_flow_to_string(v)
    TuiAttrValue::GridColumn(v) => v.to_string()
    TuiAttrValue::GridRow(v) => v.to_string()
    TuiAttrValue::GridColumnSpan(v) => v.to_string()
    TuiAttrValue::GridRowSpan(v) => v.to_string()
    TuiAttrValue::GridTemplateAreas(v) =>
      v.map(fn(s) { "\"" + s + "\"" }).join(" ")
    TuiAttrValue::GridArea(v) => v
    TuiAttrValue::Padding(v) => v.to_int().to_string()
    TuiAttrValue::PaddingX(v) => v.to_int().to_string()
    TuiAttrValue::PaddingY(v) => v.to_int().to_string()
    TuiAttrValue::Margin(v) => v.to_int().to_string()
    TuiAttrValue::MarginX(v) => v.to_int().to_string()
    TuiAttrValue::MarginY(v) => v.to_int().to_string()
    TuiAttrValue::Justify(v) => alignment_to_string(v)
    TuiAttrValue::Align(v) => alignment_to_string(v)
    TuiAttrValue::Border(v) => border_to_string(v)
    TuiAttrValue::BorderColor(v) => color_to_string(v)
    TuiAttrValue::Fg(v) => color_to_string(v)
    TuiAttrValue::Bg(v) => color_to_string(v)
    TuiAttrValue::Bold(v) => if v { "true" } else { "false" }
    TuiAttrValue::Underline(v) => if v { "true" } else { "false" }
    TuiAttrValue::Id(v) => v
    TuiAttrValue::TabIndex(v) => v.to_string()
    TuiAttrValue::Role(v) => role_to_string(v)
  }
  (key, value)
}

///|
fn dimension_to_string(v : DimensionValue) -> String {
  match v {
    DimensionValue::Auto => "auto"
    DimensionValue::Length(n) => n.to_int().to_string()
    DimensionValue::Percent(n) => n.to_int().to_string() + "%"
  }
}

///|
fn alignment_to_string(v : AlignmentValue) -> String {
  match v {
    AlignmentValue::Start => "start"
    AlignmentValue::Center => "center"
    AlignmentValue::End => "end"
    AlignmentValue::Stretch => "stretch"
    AlignmentValue::SpaceBetween => "space-between"
    AlignmentValue::SpaceAround => "space-around"
    AlignmentValue::SpaceEvenly => "space-evenly"
    AlignmentValue::FlexStart => "flex-start"
    AlignmentValue::FlexEnd => "flex-end"
  }
}

///|
fn border_to_string(v : BorderValue) -> String {
  match v {
    BorderValue::None => "none"
    BorderValue::Single => "single"
    BorderValue::Double => "double"
    BorderValue::Rounded => "rounded"
    BorderValue::Ascii => "ascii"
  }
}

///|
fn color_to_string(v : ColorValue) -> String {
  match v {
    ColorValue::Transparent => "transparent"
    ColorValue::Named(name) => name
    ColorValue::Rgb(r, g, b) =>
      "rgb(" + r.to_string() + "," + g.to_string() + "," + b.to_string() + ")"
  }
}

///|
fn display_to_string(v : DisplayValue) -> String {
  match v {
    DisplayValue::Flex => "flex"
    DisplayValue::Grid => "grid"
  }
}

///|
fn grid_auto_flow_to_string(v : GridAutoFlowValue) -> String {
  match v {
    GridAutoFlowValue::Row => "row"
    GridAutoFlowValue::Column => "column"
    GridAutoFlowValue::RowDense => "row-dense"
    GridAutoFlowValue::ColumnDense => "column-dense"
  }
}

///|
fn track_list_to_string(tracks : Array[@values.TrackSizingFunction]) -> String {
  tracks.map(fn(t) { track_sizing_to_string(t) }).join(" ")
}

///|
fn track_sizing_to_string(t : @values.TrackSizingFunction) -> String {
  match t {
    @values.TrackSizingFunction::Length(v) => v.to_int().to_string() + "px"
    @values.TrackSizingFunction::Percent(v) =>
      (v * 100.0).to_int().to_string() + "%"
    @values.TrackSizingFunction::Fr(v) => v.to_string() + "fr"
    @values.TrackSizingFunction::Auto => "auto"
    @values.TrackSizingFunction::MinContent => "min-content"
    @values.TrackSizingFunction::MaxContent => "max-content"
    _ => "auto"
  }
}