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

///|
/// ARIA role for accessibility
/// Based on WAI-ARIA 1.2 roles
pub(all) enum Role {
  // Widget roles
  Button
  Checkbox
  Link
  Menuitem
  Option
  Radio
  Slider
  Switch
  Tab
  Textbox
  // Composite widget roles
  Listbox
  Menu
  Menubar
  Radiogroup
  Tablist
  Tree
  Treegrid
  // Document structure roles
  Article
  Cell
  Columnheader
  Definition
  Directory
  Document
  Group
  Heading
  Img
  List
  Listitem
  Row
  Rowgroup
  Rowheader
  Separator
  Table
  Term
  // Landmark roles
  Banner
  Complementary
  Contentinfo
  Form
  Main
  Navigation
  Region
  Search
  // Live region roles
  Alert
  Log
  Marquee
  Status
  Timer
  // Window roles
  Alertdialog
  Dialog
  // Custom role
  Custom(String)
} derive(Debug, Eq)

///|
fn[T : Debug] show_debug(value : T, logger : &Logger) -> Unit {
  value.to_repr().output(logger)
}

///|
pub impl Show for Role with fn output(self : Role, logger : &Logger) -> Unit {
  show_debug(self, logger)
}

///|
/// Role map (component ID -> role)
pub(all) struct RoleMap(Map[String, Role])

///|
pub fn RoleMap::new() -> RoleMap {
  RoleMap(Map([]))
}

///|
pub fn RoleMap::set(self : RoleMap, id : String, role : Role) -> Unit {
  self.0.set(id, role)
}

///|
pub fn RoleMap::get(self : RoleMap, id : String) -> Role? {
  self.0.get(id)
}

///|
pub fn RoleMap::iter(self : RoleMap) -> Iter[(String, Role)] {
  self.0.iter()
}

///|
/// Click handler type for component-level event handling
pub(all) struct ClickHandler(() -> Unit)

///|
/// Create a new click handler
pub fn ClickHandler::new(handler : () -> Unit) -> ClickHandler {
  ClickHandler(handler)
}

///|
/// Invoke click handler
pub fn ClickHandler::invoke(self : ClickHandler) -> Unit {
  let ClickHandler(handler) = self
  handler()
}

///|
/// Click handlers map (component ID -> handler)
pub(all) struct ClickHandlers(Map[String, ClickHandler])

///|
pub fn ClickHandlers::new() -> ClickHandlers {
  ClickHandlers(Map([]))
}

///|
pub fn ClickHandlers::set(
  self : ClickHandlers,
  id : String,
  handler : ClickHandler,
) -> Unit {
  self.0.set(id, handler)
}

///|
pub fn ClickHandlers::get(self : ClickHandlers, id : String) -> ClickHandler? {
  self.0.get(id)
}

///|
pub fn ClickHandlers::iter(
  self : ClickHandlers,
) -> Iter[(String, ClickHandler)] {
  self.0.iter()
}

///|
/// Component represents a renderable UI element with its layout, style, and content
/// Note: Fields are internal implementation details. Use accessor methods for external access.
pub struct Component {
  node : @node.Node
  styles : RenderStyleMap
  texts : TextContentMap
  click_handlers : ClickHandlers
  roles : RoleMap
}

// === Accessor methods for Component ===

///|
/// Get the component's ID
pub fn Component::id(self : Component) -> String {
  self.node.id
}

///|
/// Get the layout node (internal use)
pub fn Component::get_node(self : Component) -> @node.Node {
  self.node
}

///|
/// Get the styles map (internal use)
pub fn Component::get_styles(self : Component) -> RenderStyleMap {
  self.styles
}

///|
/// Get the text content map (internal use)
pub fn Component::get_texts(self : Component) -> TextContentMap {
  self.texts
}

///|
/// Get click handlers (internal use)
pub fn Component::get_click_handlers(self : Component) -> ClickHandlers {
  self.click_handlers
}

///|
/// Get roles map (internal use)
pub fn Component::get_roles(self : Component) -> RoleMap {
  self.roles
}

///|
/// Get text content by ID
pub fn Component::get_text(self : Component, id : String) -> String? {
  self.texts.get(id)
}

///|
/// Check if component has a specific role
pub fn Component::get_role(self : Component, id : String) -> Role? {
  self.roles.get(id)
}

// 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, ClickHandlers, RoleMap) {
  let styles = RenderStyleMap::new()
  let texts = TextContentMap::new()
  let click_handlers = ClickHandlers::new()
  let roles = RoleMap::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)
    }
    // Copy click handlers
    for entry in comp.click_handlers.iter() {
      click_handlers.set(entry.0, entry.1)
    }
    // Copy roles
    for entry in comp.roles.iter() {
      roles.set(entry.0, entry.1)
    }
  }
  (styles, texts, click_handlers, roles)
}

///|
/// Create a box component
pub fn box(
  children : Array[Component],
  // ID
  id? : String = "",
  // Layout
  width? : @values.Dimension = @values.Dimension::Auto,
  height? : @values.Dimension = @values.Dimension::Auto,
  min_width? : @values.Dimension = @values.Dimension::Auto,
  min_height? : @values.Dimension = @values.Dimension::Auto,
  max_width? : @values.Dimension = @values.Dimension::Auto,
  max_height? : @values.Dimension = @values.Dimension::Auto,
  flex_grow? : Double = 0.0,
  flex_shrink? : Double = 1.0,
  flex_basis? : @values.Dimension = @values.Dimension::Auto,
  flex_direction? : @values.FlexDirection = @values.FlexDirection::Row,
  flex_wrap? : @values.FlexWrap = @values.FlexWrap::NoWrap,
  justify_content? : @values.Alignment = @values.Alignment::FlexStart,
  align_items? : @values.Alignment = @values.Alignment::Stretch,
  align_content? : @values.Alignment = @values.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,
  // Position (for overlay support)
  position? : @values.Position = @values.Position::Static,
  inset_top? : @values.Dimension = @values.Dimension::Auto,
  inset_right? : @values.Dimension = @values.Dimension::Auto,
  inset_bottom? : @values.Dimension = @values.Dimension::Auto,
  inset_left? : @values.Dimension = @values.Dimension::Auto,
  // Style
  border? : BorderChars? = None,
  border_color? : Color = Color::white(),
  bg? : Color = Color::transparent(),
  fg? : Color = Color::white(),
  // Event handler
  on_click? : (() -> Unit)? = None,
  // Accessibility
  role? : Role? = None,
) -> Component {
  let actual_id = if id == "" { next_id() } else { id }

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

  // Register click handler if provided
  match on_click {
    Some(handler) => click_handlers.set(actual_id, ClickHandler::new(handler))
    None => ()
  }

  // Register role if provided
  match role {
    Some(r) => roles.set(actual_id, r)
    None => ()
  }

  // 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 = @values.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 = @values.Dimension::Length(gap)
  style.column_gap = @values.Dimension::Length(gap)
  style.padding = @values.Rect::new(
    @values.Dimension::Length(pad_top),
    @values.Dimension::Length(pad_right),
    @values.Dimension::Length(pad_bottom),
    @values.Dimension::Length(pad_left),
  )
  style.margin = @values.Rect::new(
    @values.Dimension::Length(margin_top),
    @values.Dimension::Length(margin_right),
    @values.Dimension::Length(margin_bottom),
    @values.Dimension::Length(margin_left),
  )
  // Add border padding if border is present
  if border is Some(_) {
    style.border = @values.Rect::new(
      @values.Dimension::Length(1.0),
      @values.Dimension::Length(1.0),
      @values.Dimension::Length(1.0),
      @values.Dimension::Length(1.0),
    )
  }
  // Position and inset for overlay support
  style.position = position
  style.inset = @values.Rect::new(
    inset_top, inset_right, inset_bottom, inset_left,
  )

  // 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, click_handlers, roles }
}

///|
/// 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,
    click_handlers: ClickHandlers::new(),
    roles: RoleMap::new(),
  }
}

///|
/// 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? : @values.Dimension = @values.Dimension::Auto,
  height? : @values.Dimension = @values.Dimension::Auto,
  min_width? : @values.Dimension = @values.Dimension::Auto,
  min_height? : @values.Dimension = @values.Dimension::Auto,
  max_width? : @values.Dimension = @values.Dimension::Auto,
  max_height? : @values.Dimension = @values.Dimension::Auto,
  // Flex properties
  flex_grow? : Double = 0.0,
  flex_shrink? : Double = 1.0,
  flex_basis? : @values.Dimension = @values.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 = @values.Rect::new(
    @values.Dimension::Length(pad_y),
    @values.Dimension::Length(pad_x),
    @values.Dimension::Length(pad_y),
    @values.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 = @values.Rect::new(
    @values.Dimension::Length(mar_y),
    @values.Dimension::Length(mar_x),
    @values.Dimension::Length(mar_y),
    @values.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,
    click_handlers: ClickHandlers::new(),
    roles: RoleMap::new(),
  }
}

///|
/// 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(),
    click_handlers: ClickHandlers::new(),
    roles: RoleMap::new(),
  }
}