///|
// Text node construction helpers.

///|
/// Collapse internal whitespace (newline/tab/CR and runs of spaces) into a
/// single space while preserving up to one leading/trailing space so adjacent
/// inline-level boxes still observe the implicit gap from collapsible
/// whitespace. Used only for CSS `white-space: normal | nowrap`; preserves the
/// original text for `pre`, `pre-wrap`, and `pre-line` whose semantics depend
/// on the segment-break characters.
fn collapse_inline_whitespace(text : String) -> String {
  if text.is_empty() {
    return text
  }
  let chars : Array[Char] = []
  for c in text.iter() {
    chars.push(c)
  }
  let mut leading = false
  let mut idx = 0
  while idx < chars.length() && is_collapsible_whitespace_char(chars[idx]) {
    leading = true
    idx = idx + 1
  }
  let mut trailing = false
  let mut end_idx = chars.length()
  while end_idx > idx && is_collapsible_whitespace_char(chars[end_idx - 1]) {
    trailing = true
    end_idx = end_idx - 1
  }
  let buf = StringBuilder::new()
  if leading {
    buf.write_char(' ')
  }
  let mut in_ws = false
  for i = idx; i < end_idx; i = i + 1 {
    let c = chars[i]
    if is_collapsible_whitespace_char(c) {
      if !in_ws {
        buf.write_char(' ')
        in_ws = true
      }
    } else {
      buf.write_char(c)
      in_ws = false
    }
  }
  if trailing {
    buf.write_char(' ')
  }
  buf.to_string()
}

///|
fn white_space_preserves_segment_breaks(
  white_space : @style.WhiteSpace,
) -> Bool {
  match white_space {
    @style.WhiteSpace::Pre
    | @style.WhiteSpace::PreWrap
    | @style.WhiteSpace::PreLine => true
    _ => false
  }
}

///|
/// Create a node for text content with inherited font properties
fn create_text_node(text : String, parent_style : @style.Style) -> @node.Node {
  let normalized_text = if parent_style.display == @types.TableCell &&
    has_line_break_char(text) {
    trim_trailing_collapsible_whitespace(text)
  } else {
    text
  }
  // CSS Text 3 ยง4.1.1: white-space:normal/nowrap collapse all whitespace
  // sequences (including segment breaks U+000A) into single spaces before
  // the line-layout / glyph-shaping stage. Without this collapse the painter
  // sees a leading `\n` token in text like "\n      Dashboard", treats it as
  // a hard line break, and renders the glyphs one line below the line box.
  // pre / pre-wrap / pre-line / break-spaces preserve segment breaks per
  // CSS so they bypass this collapse.
  let normalized_text = if white_space_preserves_segment_breaks(
      parent_style.white_space,
    ) {
    normalized_text
  } else {
    collapse_inline_whitespace(normalized_text)
  }
  let trimmed = trim_collapsible_whitespace_edges(normalized_text)
  let font_size = parent_style.font_size
  let font_weight = parent_style.font_weight
  let font_family = parent_style.font_family
  let text_line_height = parent_style.line_height
  let white_space = parent_style.white_space
  let writing_mode = parent_style.writing_mode
  let ls = parent_style.letter_spacing
  let ws = parent_style.word_spacing
  // Create text style with inherited font properties and color for rendering
  // Text nodes should be inline-level to flow with other inline content
  let text_style : @style.Style = {
    ..@style.Style::default(),
    display: @types.Inline,
    font_size,
    font_weight,
    font_family,
    line_height: text_line_height,
    white_space,
    writing_mode,
    direction: parent_style.direction,
    letter_spacing: ls,
    word_spacing: ws,
    color: parent_style.color,
    text_decoration_underline: parent_style.text_decoration_underline,
    text_decoration_line_through: parent_style.text_decoration_line_through,
    text_decoration_overline: parent_style.text_decoration_overline,
  }
  if trimmed.is_empty() {
    // Preserve collapsible whitespace as a single space so inline-block gaps match browser layout.
    if text.is_empty() {
      @node.Node::leaf("#text", @style.Style::default())
    } else {
      // Indentation-only whitespace in block formatting contexts should not
      // create measurable text runs. Keep whitespace for inline/flex/grid/
      // contents contexts where it can participate in layout.
      let keep_ws = match parent_style.display {
        @types.Inline
        | @types.InlineBlock
        | @types.Flex
        | @types.InlineFlex
        | @types.Grid
        | @types.InlineGrid
        | @types.Contents => true
        _ => false
      }
      if !keep_ws {
        return @node.Node::leaf("#text", @style.Style::default())
      }
      let collapsed = " "
      @node.Node::text(
        "#text",
        text_style,
        create_text_measure_with_provider(
          collapsed,
          font_size,
          text_line_height,
          white_space,
          writing_mode,
          font_weight~,
          font_family~,
          letter_spacing=ls,
          word_spacing=ws,
        ),
        collapsed,
      )
    }
  } else {
    // Use Node::text to store the text content for rendering
    @node.Node::text(
      "#text",
      text_style,
      create_text_measure_with_provider(
        normalized_text,
        font_size,
        text_line_height,
        white_space,
        writing_mode,
        font_weight~,
        font_family~,
        letter_spacing=ls,
        word_spacing=ws,
      ),
      normalized_text,
    )
  }
}