///|
/// Component builders for TUI
/// Provides ink-like API for building reactive UI trees

///|
/// Component represents a renderable UI element with its layout, style, and content
pub struct Component {
  node : @node.Node
  styles : RenderStyleMap
  texts : TextContentMap
}

// Global ID counter for unique component IDs

///|
let component_id_counter : Ref[Int] = { val: 0 }

///|
fn next_id() -> String {
  let id = component_id_counter.val
  component_id_counter.val = id + 1
  "c" + id.to_string()
}

///|
/// Merge two components (for combining children)
fn merge_components(
  comps : Array[Component],
) -> (RenderStyleMap, TextContentMap) {
  let styles = RenderStyleMap::new()
  let texts = TextContentMap::new()
  for comp in comps {
    // Copy styles
    for entry in comp.styles.0 {
      styles.set(entry.0, entry.1)
    }
    // Copy texts
    for entry in comp.texts.0 {
      texts.set(entry.0, entry.1)
    }
  }
  (styles, texts)
}

///|
/// Create a box component
pub fn box(
  children : Array[Component],
  // ID
  id? : String = "",
  // Layout
  width? : @types.Dimension = @types.Dimension::Auto,
  height? : @types.Dimension = @types.Dimension::Auto,
  min_width? : @types.Dimension = @types.Dimension::Auto,
  min_height? : @types.Dimension = @types.Dimension::Auto,
  max_width? : @types.Dimension = @types.Dimension::Auto,
  max_height? : @types.Dimension = @types.Dimension::Auto,
  flex_grow? : Double = 0.0,
  flex_shrink? : Double = 1.0,
  flex_basis? : @types.Dimension = @types.Dimension::Auto,
  flex_direction? : @style.FlexDirection = @style.FlexDirection::Row,
  flex_wrap? : @style.FlexWrap = @style.FlexWrap::NoWrap,
  justify_content? : @style.Alignment = @style.Alignment::FlexStart,
  align_items? : @style.Alignment = @style.Alignment::Stretch,
  align_content? : @style.Alignment = @style.Alignment::Stretch,
  gap? : Double = 0.0,
  padding? : Double = 0.0,
  padding_x? : Double = 0.0,
  padding_y? : Double = 0.0,
  margin? : Double = 0.0,
  margin_x? : Double = 0.0,
  margin_y? : Double = 0.0,
  // Style
  border? : BorderChars? = None,
  border_color? : Color = Color::white(),
  bg? : Color = Color::transparent(),
  fg? : Color = Color::white(),
) -> Component {
  let actual_id = if id == "" { next_id() } else { id }

  // Merge children's styles and texts
  let (styles, texts) = merge_components(children)

  // Calculate padding (with x/y overrides)
  let pad_left = if padding_x > 0.0 { padding_x } else { padding }
  let pad_right = pad_left
  let pad_top = if padding_y > 0.0 { padding_y } else { padding }
  let pad_bottom = pad_top

  // Calculate margin (with x/y overrides)
  let margin_left = if margin_x > 0.0 { margin_x } else { margin }
  let margin_right = margin_left
  let margin_top = if margin_y > 0.0 { margin_y } else { margin }
  let margin_bottom = margin_top

  // Build style based on default with overrides
  let style = @style.Style::default()
  style.display = @style.Display::Flex
  style.flex_direction = flex_direction
  style.flex_wrap = flex_wrap
  style.justify_content = justify_content
  style.align_items = align_items
  style.align_content = align_content
  style.flex_grow = flex_grow
  style.flex_shrink = flex_shrink
  style.flex_basis = flex_basis
  style.width = width
  style.height = height
  style.min_width = min_width
  style.min_height = min_height
  style.max_width = max_width
  style.max_height = max_height
  style.row_gap = @types.Dimension::Length(gap)
  style.column_gap = @types.Dimension::Length(gap)
  style.padding = @types.Rect::new(
    @types.Dimension::Length(pad_top),
    @types.Dimension::Length(pad_right),
    @types.Dimension::Length(pad_bottom),
    @types.Dimension::Length(pad_left),
  )
  style.margin = @types.Rect::new(
    @types.Dimension::Length(margin_top),
    @types.Dimension::Length(margin_right),
    @types.Dimension::Length(margin_bottom),
    @types.Dimension::Length(margin_left),
  )
  // Add border padding if border is present
  if border is Some(_) {
    style.border = @types.Rect::new(
      @types.Dimension::Length(1.0),
      @types.Dimension::Length(1.0),
      @types.Dimension::Length(1.0),
      @types.Dimension::Length(1.0),
    )
  }

  // Build node
  let child_nodes = children.map(fn(c) { c.node })
  let node = @node.Node::new(actual_id, style, child_nodes)

  // Add this box's style
  styles.set(actual_id, {
    fg,
    bg,
    bold: false,
    underline: false,
    border,
    border_fg: border_color,
  })
  { node, styles, texts }
}

///|
/// Create a text component
pub fn text(
  content : String,
  fg? : Color = Color::white(),
  bg? : Color = Color::transparent(),
  bold? : Bool = false,
  underline? : Bool = false,
) -> Component {
  let id = next_id()

  // Build style for text measurement
  let style = @style.Style::default()

  // Build node with measure function
  let node = @node.Node::with_measure(id, style, text_measure_func(content))

  // Create style and text maps
  let styles = RenderStyleMap::new()
  let texts = TextContentMap::new()
  styles.set(id, {
    fg,
    bg,
    bold,
    underline,
    border: None,
    border_fg: Color::white(),
  })
  texts.set(id, content)
  { node, styles, texts }
}

///|
/// Create a styled text (alias for text with labeled arguments)
pub fn styled_text(
  content : String,
  fg? : Color = Color::white(),
  bg? : Color = Color::transparent(),
  bold? : Bool = false,
  underline? : Bool = false,
) -> Component {
  text(content, fg~, bg~, bold~, underline~)
}

///|
/// Create a box with text content that wraps within the box's bounds
/// Unlike text(), this renders the text directly within the box's layout dimensions
pub fn text_box(
  content : String,
  // ID
  id? : String = "",
  // Size
  width? : @types.Dimension = @types.Dimension::Auto,
  height? : @types.Dimension = @types.Dimension::Auto,
  min_width? : @types.Dimension = @types.Dimension::Auto,
  min_height? : @types.Dimension = @types.Dimension::Auto,
  max_width? : @types.Dimension = @types.Dimension::Auto,
  max_height? : @types.Dimension = @types.Dimension::Auto,
  // Flex properties
  flex_grow? : Double = 0.0,
  flex_shrink? : Double = 1.0,
  flex_basis? : @types.Dimension = @types.Dimension::Auto,
  // Padding & Margin
  padding? : Double = 0.0,
  padding_x? : Double = 0.0,
  padding_y? : Double = 0.0,
  margin? : Double = 0.0,
  margin_x? : Double = 0.0,
  margin_y? : Double = 0.0,
  // Border
  border? : BorderChars? = None,
  border_color? : Color = Color::white(),
  // Style
  bg? : Color = Color::transparent(),
  fg? : Color = Color::white(),
  bold? : Bool = false,
  underline? : Bool = false,
) -> Component {
  let actual_id = if id == "" { next_id() } else { id }

  // Build style
  let style = @style.Style::default()
  style.width = width
  style.height = height
  style.min_width = min_width
  style.min_height = min_height
  style.max_width = max_width
  style.max_height = max_height
  style.flex_grow = flex_grow
  style.flex_shrink = flex_shrink
  style.flex_basis = flex_basis
  // Padding
  let pad = padding
  let pad_x = if padding_x > 0.0 { padding_x } else { pad }
  let pad_y = if padding_y > 0.0 { padding_y } else { pad }
  style.padding = @types.Rect::new(
    @types.Dimension::Length(pad_y),
    @types.Dimension::Length(pad_x),
    @types.Dimension::Length(pad_y),
    @types.Dimension::Length(pad_x),
  )
  // Margin
  let mar = margin
  let mar_x = if margin_x > 0.0 { margin_x } else { mar }
  let mar_y = if margin_y > 0.0 { margin_y } else { mar }
  style.margin = @types.Rect::new(
    @types.Dimension::Length(mar_y),
    @types.Dimension::Length(mar_x),
    @types.Dimension::Length(mar_y),
    @types.Dimension::Length(mar_x),
  )

  // Build node (no children, text rendered directly in this box)
  let node = @node.Node::new(actual_id, style, [])

  // Create style and text maps
  let styles = RenderStyleMap::new()
  let texts = TextContentMap::new()
  styles.set(actual_id, {
    fg,
    bg,
    bold,
    underline,
    border,
    border_fg: border_color,
  })
  texts.set(actual_id, content)
  { node, styles, texts }
}

///|
/// Spacer component - flexible empty space
pub fn spacer(flex_grow? : Double = 1.0) -> Component {
  let id = next_id()
  let style = @style.Style::default()
  style.flex_grow = flex_grow
  let node = @node.Node::new(id, style, [])
  { node, styles: RenderStyleMap::new(), texts: TextContentMap::new() }
}