// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use {
  crate::{
    nodes::NodesWithState,
    prefix::Prefix,
    printer::PrinterInner,
    writer::Writer,
    Bluegum,
    BluegumWithState,
    Nodes,
  },
  std::borrow::BorrowMut,
};

pub(crate) type FieldDebug = Vec<(String, String)>;

/// Maximum tree depth rendered before a node is replaced with a
/// "depth limit reached" placeholder. Rendering recurses one frame-chain
/// per tree level, so unbounded depth (e.g. the CST of a deeply nested
/// expression) would otherwise overflow the stack.
const MAX_RENDER_DEPTH: usize = 256;

std::thread_local! {
  static RENDER_DEPTH: std::cell::Cell<usize> =
    const { std::cell::Cell::new(0) };
}

/// Runs `f` (which renders one tree level) with the render-depth counter
/// incremented. At [`MAX_RENDER_DEPTH`] the node is not rendered and the
/// builder is given a placeholder name instead.
fn with_render_depth(builder: &mut Builder, f: impl FnOnce(&mut Builder)) {
  let depth = RENDER_DEPTH.with(|d| d.get());
  if depth >= MAX_RENDER_DEPTH {
    builder.name("… depth limit reached");
    return;
  }
  RENDER_DEPTH.with(|d| d.set(depth + 1));
  f(builder);
  RENDER_DEPTH.with(|d| d.set(depth));
}

#[derive(Debug, Clone)]
pub(crate) struct BuilderName {
  pub(crate) name:  String,
  pub(crate) alt:   Option<String>,
  pub(crate) debug: FieldDebug,
}

#[derive(Debug, Clone)]
pub(crate) enum Field {
  KeyValue {
    name:  String,
    value: String,
    alt:   Option<String>,
    debug: FieldDebug,
  },
  Divider,
}

impl Field {
  pub(crate) fn set_alt(&mut self, alt: String) {
    match self {
      | Field::KeyValue { alt: ref mut a, .. } => *a = Some(alt),
      | Field::Divider => (),
    }
  }

  pub(crate) fn set_debug(&mut self, debug: (String, String)) {
    match self {
      | Field::KeyValue {
        debug: ref mut d, ..
      } => d.push(debug),
      | Field::Divider => (),
    }
  }
}

pub(crate) type Fields = Vec<Field>;

/// A fluent builder for constructing tree representations.
///
/// The `Builder` provides a fluent API for creating tree nodes with fields and
/// child nodes. It's used within [`Bluegum::node`] implementations to specify
/// the structure and content of tree nodes.
///
/// # Examples
///
/// ```rust
/// use bluegum::{
///   Bluegum,
///   Builder,
/// };
///
/// struct Person {
///   name:     String,
///   age:      u32,
///   children: Vec<Person>,
/// }
///
/// impl Bluegum for Person {
///   fn node(&self, b: &mut Builder) {
///     b.name("Person")
///       .field("name", &self.name)
///       .field("age", &self.age)
///       .add_nodes("children", &self.children);
///   }
/// }
/// ```
#[derive(Debug, Clone)]
pub struct Builder {
  pub(crate) name:            BuilderName,
  pub(crate) fields:          Fields,
  pub(crate) unlabled_nodes:  Vec<Builder>,
  pub(crate) nodes:           Vec<(String, Vec<Builder>)>,
  // the total len of this subtree
  pub(crate) total_nodes_len: usize,
}

impl Builder {
  /// Create a new empty builder.
  ///
  /// This creates a builder with no name, fields, or child nodes.
  /// Typically you don't need to call this directly as builders are
  /// created automatically when rendering [`Bluegum`] implementations.
  pub fn new() -> Self {
    Self {
      name:            BuilderName {
        name:  "".to_string(),
        alt:   None,
        debug: vec![],
      },
      fields:          Vec::new(),
      nodes:           Vec::new(),
      unlabled_nodes:  Vec::new(),
      total_nodes_len: 0,
    }
  }
}

impl Default for Builder {
  fn default() -> Self {
    Self::new()
  }
}

impl Builder {
  /// Render a [`Bluegum`] node into a builder.
  ///
  /// This is a convenience method that creates a new builder and calls
  /// the node's [`Bluegum::node`] method to populate it.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│    · author:  Alice
  /// 02│    · content: Hello world!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   content: "Hello world!".to_string(),
  /// };
  /// let builder = Builder::render(&comment);
  /// let mut printer = Printer::default();
  /// printer.render_builder(builder);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn render<T: Bluegum>(node: &T) -> Self {
    let mut builder = Builder::new();
    with_render_depth(&mut builder, |b| Bluegum::node(node, b));
    builder
  }

  /// Render a [`BluegumWithState`] node into a builder with the given state.
  ///
  /// This is a convenience method that creates a new builder and calls
  /// the node's [`BluegumWithState::node_with_state`] method to populate it.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│    · author: alice                              Alice Smith
  /// 02│    · content: Hello world!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, BluegumWithState, Builder, Printer};
  ///
  /// struct UserTable {
  ///   users: Vec<(String, String)>, // (username, display_name)
  /// }
  ///
  /// impl UserTable {
  ///   fn get_display_name(&self, username: &str) -> Option<&str> {
  ///     self.users.iter()
  ///       .find(|(u, _)| u == username)
  ///       .map(|(_, d)| d.as_str())
  ///   }
  /// }
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Comment {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .option_alt(users.get_display_name(&self.author))
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// let users = UserTable {
  ///   users: vec![("alice".to_string(), "Alice Smith".to_string())],
  /// };
  /// let comment = Comment {
  ///   author: "alice".to_string(),
  ///   content: "Hello world!".to_string(),
  /// };
  /// let builder = Builder::render_with_state(&comment, &users);
  /// let mut printer = Printer::default();
  /// printer.render_builder(builder);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn render_with_state<State: ?Sized, T: BluegumWithState<State>>(
    node: &T,
    state: &State,
  ) -> Self {
    let mut builder = Builder::new();
    with_render_depth(&mut builder, |b| {
      <dyn BluegumWithState<State>>::node_with_state(node, b, state);
    });
    builder
  }
}

impl Builder {
  /// Set the name of this node.
  ///
  /// The name is displayed as the primary label for the node in the tree
  /// output. This is typically called first when building a node.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment");
  ///   }
  /// }
  ///
  /// let comment = Comment { author: "Alice".to_string() };
  /// let mut printer = Printer::default();
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn name(&mut self, name: &str) -> &mut Self {
    self.name.name = name.to_string();

    self
  }

  /// Get the name of this node.
  pub fn get_name(&self) -> &str {
    &self.name.name
  }

  /// Add a visual divider between fields.
  ///
  /// Dividers create visual separation between groups of fields within a node.
  /// They are displayed as horizontal lines in the output.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│    · author:  Alice
  /// 02│    · content: Great post!
  /// 03│    · --------
  /// 04│    · likes:   42
  /// 05│    · shares:  7
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  ///   likes: u32,
  ///   shares: u32,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .divider()
  ///       .field("likes", self.likes)
  ///       .field("shares", self.shares);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   likes: 42,
  ///   shares: 7,
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn divider(&mut self) -> &mut Self {
    self.fields.push(Field::Divider);

    self
  }

  /// Add a field (key-value pair) to this node.
  ///
  /// Fields are displayed as indented key-value pairs within the node.
  /// The value can be any type that implements [`std::fmt::Display`].
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│    · author:  Alice
  /// 02│    · content: Great post!
  /// 03│    · likes:   42
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  ///   likes: u32,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .field("likes", self.likes);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   likes: 42,
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn field(
    &mut self,
    name: &str,
    value: impl std::fmt::Display,
  ) -> &mut Self {
    self.fields.push(Field::KeyValue {
      name:  name.to_string(),
      value: format!("{value}"),
      alt:   None,
      debug: vec![],
    });

    self
  }

  /// Add a field only if the value is `Some`.
  ///
  /// This is a convenience method for optional fields. If the value is `None`,
  /// no field is added.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ User
  /// 01│    · username: alice
  /// 02│    · verified: true
  /// ```
  ///
  /// Note: The `bio` field is `None`, so it doesn't appear in the output.
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct User {
  ///   username: String,
  ///   verified: Option<bool>,
  ///   bio: Option<String>,
  /// }
  ///
  /// impl Bluegum for User {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("User")
  ///       .field("username", &self.username)
  ///       .option_field("verified", self.verified)
  ///       .option_field("bio", self.bio.as_ref());
  ///   }
  /// }
  ///
  /// let user = User {
  ///   username: "alice".to_string(),
  ///   verified: Some(true),
  ///   bio: None, // This field won't appear in output
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&user);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn option_field(
    &mut self,
    name: &str,
    value: Option<impl std::fmt::Display>,
  ) -> &mut Self {
    value.map(|val| self.field(name, val));

    self
  }

  /// Add an alternate value to display alongside the node name or last field.
  ///
  /// Alternate values are displayed in a separate, right-aligned column and
  /// provide additional context. This is particularly useful for showing
  /// original source text alongside structured data, such as displaying
  /// usernames or reference IDs.
  ///
  /// If called before any fields are added, the alternate value is associated
  /// with the node name. Otherwise, it's associated with the most recently
  /// added field.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│    · author:  Alice                          @alice
  /// 02│    · content: Great post!
  /// 03│    · likes:   42
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  ///   username: String,
  ///   content: String,
  ///   likes: u32,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .alt(format!("@{}", self.username)) // Shows username handle
  ///       .field("content", &self.content)
  ///       .field("likes", self.likes);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   username: "alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   likes: 42,
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn alt(&mut self, value: impl std::fmt::Display) -> &mut Self {
    // when a field hasn't been set, we add the alt call to the name field
    if self.fields.is_empty() {
      self.name.alt = Some(format!("{value}"));
    } else if let Some(mut last) = self.fields.last_mut() {
      last.set_alt(format!("{value}"));
    }

    self
  }

  /// Add an alternate value only if the value is `Some`.
  ///
  /// This is a convenience method for optional alternate values. If the value
  /// is `None`, no alternate value is added.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ User
  /// 01│    · username: alice                          @alice
  /// 02│    · email:    alice@example.com
  /// ```
  ///
  /// Note: The email field has no alt because `display_email` is `None`.
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct User {
  ///   username: String,
  ///   handle: Option<String>,
  ///   email: String,
  ///   display_email: Option<String>,
  /// }
  ///
  /// impl Bluegum for User {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("User")
  ///       .field("username", &self.username)
  ///       .option_alt(self.handle.as_ref().map(|h| format!("@{}", h)))
  ///       .field("email", &self.email)
  ///       .option_alt(self.display_email.as_ref());
  ///   }
  /// }
  ///
  /// let user = User {
  ///   username: "alice".to_string(),
  ///   handle: Some("alice".to_string()),
  ///   email: "alice@example.com".to_string(),
  ///   display_email: None, // No alt shown for this field
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&user);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn option_alt(
    &mut self,
    value: Option<impl std::fmt::Display>,
  ) -> &mut Self {
    value.map(|val| self.alt(val));

    self
  }

  /// Add debug information to display alongside the node name or last field.
  ///
  /// Debug information appears as key-value pairs in brackets on the far right
  /// of the output. Multiple debug entries can be added to the same field or
  /// node, and they will all be displayed together. This is ideal for showing
  /// ancillary information like internal IDs, source locations, or metadata
  /// that's useful for debugging but not part of the primary structure.
  ///
  /// If called before any fields are added, the debug information is associated
  /// with the node name. Otherwise, it's associated with the most recently
  /// added field.
  ///
  /// Note: Debug output requires `Styles::show_debug` to be `true` (default).
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment                                            [id: 42][flagged: false]
  /// 01│    · author:  Alice                          @alice   [user_id: 1]
  /// 02│    · content: Great post!
  /// 03│    · likes:   100
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer, Styles};
  ///
  /// struct Comment {
  ///   id: u32,
  ///   author: String,
  ///   username: String,
  ///   user_id: u32,
  ///   content: String,
  ///   likes: u32,
  ///   flagged: bool,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .debug("id", self.id)
  ///       .debug("flagged", self.flagged)
  ///       .field("author", &self.author)
  ///       .alt(format!("@{}", self.username))
  ///       .debug("user_id", self.user_id)
  ///       .field("content", &self.content)
  ///       .field("likes", self.likes);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   id: 42,
  ///   author: "Alice".to_string(),
  ///   username: "alice".to_string(),
  ///   user_id: 1,
  ///   content: "Great post!".to_string(),
  ///   likes: 100,
  ///   flagged: false,
  /// };
  /// let styles = Styles { show_debug: true, ..Default::default() };
  /// let mut printer = Printer::new(styles);
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn debug(
    &mut self,
    name: &str,
    value: impl std::fmt::Display,
  ) -> &mut Self {
    // when a field hasn't been set, we add the debug call to the name field
    if self.fields.is_empty() {
      self.name.debug.push((name.to_string(), format!("{value}")));
    } else if let Some(mut last) = self.fields.last_mut() {
      last.set_debug((name.to_string(), format!("{value}")));
    }

    self
  }

  /// Add debug information only if the value is `Some`.
  ///
  /// This is a convenience method for optional debug information. If the value
  /// is `None`, no debug entry is added.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment                                            [id: 42]
  /// 01│    · author:  Alice
  /// 02│    · content: Great post!
  /// ```
  ///
  /// Note: The `moderation_note` debug is `None`, so it doesn't appear.
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer, Styles};
  ///
  /// struct Comment {
  ///   id: u32,
  ///   author: String,
  ///   content: String,
  ///   moderation_note: Option<String>,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .option_debug("id", Some(self.id))
  ///       .option_debug("moderation_note", self.moderation_note.as_ref())
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   id: 42,
  ///   author: "Alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   moderation_note: None, // Won't appear in debug output
  /// };
  /// let styles = Styles { show_debug: true, ..Default::default() };
  /// let mut printer = Printer::new(styles);
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn option_debug(
    &mut self,
    name: &str,
    value: Option<impl std::fmt::Display>,
  ) -> &mut Self {
    value.map(|val| self.debug(name, val));

    self
  }

  /// Add a single child node with a label.
  ///
  /// The child node is rendered by calling its [`Bluegum::node`] method.
  /// This is useful for adding individual nodes, especially when they're
  /// behind trait objects or in boxes.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│  │ · author:  Alice
  /// 02│  │ · content: Great post!
  /// 03│  └─○ reply
  /// 04│    └─▣ Reply
  /// 05│        · author:  Bob
  /// 06│        · content: Thanks!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  ///   reply: Reply,
  /// }
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .add_node("reply", &self.reply);
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   reply: Reply {
  ///     author: "Bob".to_string(),
  ///     content: "Thanks!".to_string(),
  ///   },
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_node(&mut self, name: &str, node: &dyn Bluegum) -> &mut Self {
    let mut builder = Builder::new();

    with_render_depth(&mut builder, |b| Bluegum::node(node, b));

    self.total_nodes_len += 1;

    self.nodes.push((name.to_string(), vec![builder]));

    self
  }

  /// Add a child node only if the value is `Some`.
  ///
  /// This is a convenience method for optional child nodes. If the value is
  /// `None`, no node is added.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│  │ · author:  Alice
  /// 02│  │ · content: Great post!
  /// 03│  └─○ pinned_reply
  /// 04│    └─▣ Reply
  /// 05│        · author:  Moderator
  /// 06│        · content: Pinned for visibility
  /// ```
  ///
  /// Note: If `pinned_reply` were `None`, the `pinned_reply` section wouldn't appear.
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  ///   pinned_reply: Option<Reply>,
  /// }
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .add_option_node(
  ///         "pinned_reply",
  ///         self.pinned_reply.as_ref().map(|r| r as &dyn Bluegum),
  ///       );
  ///   }
  /// }
  ///
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   pinned_reply: Some(Reply {
  ///     author: "Moderator".to_string(),
  ///     content: "Pinned for visibility".to_string(),
  ///   }),
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&comment);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_option_node(
    &mut self,
    name: &str,
    node: Option<&dyn Bluegum>,
  ) -> &mut Self {
    node.map(|n| self.add_node(name, n));

    self
  }

  /// Add a single child node with state context.
  ///
  /// This method is for adding nodes that implement [`BluegumWithState`] and
  /// need access to external state during rendering. The state is passed
  /// through to the node's rendering method.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│  │ · author: alice                               Alice Smith
  /// 02│  │ · content: Great post!
  /// 03│  └─○ reply
  /// 04│    └─▣ Reply
  /// 05│        · author: bob                             Bob Jones
  /// 06│        · content: Thanks!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, BluegumWithState, Builder, Printer};
  ///
  /// // User lookup table passed as state
  /// struct UserTable {
  ///   users: Vec<(String, String)>, // (username, display_name)
  /// }
  ///
  /// impl UserTable {
  ///   fn get_display_name(&self, username: &str) -> Option<&str> {
  ///     self.users.iter()
  ///       .find(|(u, _)| u == username)
  ///       .map(|(_, d)| d.as_str())
  ///   }
  /// }
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Reply {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .option_alt(users.get_display_name(&self.author))
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  ///   reply: Reply,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .add_node("reply", &self.reply);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Comment {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .option_alt(users.get_display_name(&self.author))
  ///       .field("content", &self.content)
  ///       .add_node_with_state(users, "reply", &self.reply);
  ///   }
  /// }
  ///
  /// let users = UserTable {
  ///   users: vec![
  ///     ("alice".to_string(), "Alice Smith".to_string()),
  ///     ("bob".to_string(), "Bob Jones".to_string()),
  ///   ],
  /// };
  /// let comment = Comment {
  ///   author: "alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   reply: Reply {
  ///     author: "bob".to_string(),
  ///     content: "Thanks!".to_string(),
  ///   },
  /// };
  /// let builder = Builder::render_with_state(&comment, &users);
  /// let mut printer = Printer::default();
  /// printer.render_builder(builder);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_node_with_state<State: ?Sized, T: BluegumWithState<State>>(
    &mut self,
    state: &State,
    name: &str,
    c: &T,
  ) -> &mut Self {
    let mut builder = Builder::new();

    with_render_depth(&mut builder, |b| c.node_with_state(b, state));

    self.total_nodes_len += 1;

    self.nodes.push((name.to_string(), vec![builder]));

    self
  }

  /// Add an unlabeled child node.
  ///
  /// This method adds a child node without a label, meaning it will be
  /// displayed directly as part of the current node's structure rather than
  /// under a named subsection. This is useful for flattening hierarchies
  /// or including content inline.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Rust Discussion
  /// 02│  └─▣ Comment
  /// 03│      · author:  Alice
  /// 04│      · content: Great language!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Thread {
  ///   topic: String,
  ///   featured_comment: Comment,
  /// }
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_unlabled_node(&self.featured_comment);
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Rust Discussion".to_string(),
  ///   featured_comment: Comment {
  ///     author: "Alice".to_string(),
  ///     content: "Great language!".to_string(),
  ///   },
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_unlabled_node(&mut self, node: &dyn Bluegum) -> &mut Self {
    let mut builder = Builder::new();

    with_render_depth(&mut builder, |b| node.node(b));

    self.total_nodes_len += 1;

    self.unlabled_nodes.push(builder);

    self
  }

  /// Add an unlabeled child node with state context.
  ///
  /// This method adds a child node without a label, meaning it will be
  /// displayed directly as part of the current node's structure rather than
  /// under a named subsection. This is useful for flattening hierarchies
  /// or including content that doesn't need its own grouping.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Rust Discussion
  /// 02│  └─▣ Comment
  /// 03│      · author: alice                              Alice Smith
  /// 04│      · content: Great language!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, BluegumWithState, Builder, Printer};
  ///
  /// struct UserTable {
  ///   users: Vec<(String, String)>,
  /// }
  ///
  /// impl UserTable {
  ///   fn get_display_name(&self, username: &str) -> Option<&str> {
  ///     self.users.iter()
  ///       .find(|(u, _)| u == username)
  ///       .map(|(_, d)| d.as_str())
  ///   }
  /// }
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Comment {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .option_alt(users.get_display_name(&self.author))
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// struct Thread {
  ///   topic: String,
  ///   featured: Comment,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_unlabled_node(&self.featured);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Thread {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_unlabled_node_with_state(users, &self.featured);
  ///   }
  /// }
  ///
  /// let users = UserTable {
  ///   users: vec![("alice".to_string(), "Alice Smith".to_string())],
  /// };
  /// let thread = Thread {
  ///   topic: "Rust Discussion".to_string(),
  ///   featured: Comment {
  ///     author: "alice".to_string(),
  ///     content: "Great language!".to_string(),
  ///   },
  /// };
  /// let builder = Builder::render_with_state(&thread, &users);
  /// let mut printer = Printer::default();
  /// printer.render_builder(builder);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_unlabled_node_with_state<State: ?Sized, T: BluegumWithState<State>>(
    &mut self,
    state: &State,
    c: &T,
  ) -> &mut Self {
    let mut builder = Builder::new();

    with_render_depth(&mut builder, |b| c.node_with_state(b, state));

    self.total_nodes_len += 1;

    self.unlabled_nodes.push(builder);

    self
  }

  /// Add a child node with state context only if the value is `Some`.
  ///
  /// This is a convenience method that combines optional handling with
  /// state-based rendering. If the node is `None`, nothing is added.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Comment
  /// 01│  │ · author:  Alice
  /// 02│  │ · content: Great post!
  /// 03│  └─○ pinned_reply
  /// 04│    └─▣ Reply
  /// 05│        · author: moderator                        Mod Team
  /// 06│        · content: Pinned for visibility
  /// ```
  ///
  /// Note: If `pinned_reply` is `None`, no child node is rendered.
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, BluegumWithState, Builder, Printer};
  ///
  /// struct UserTable {
  ///   users: Vec<(String, String)>,
  /// }
  ///
  /// impl UserTable {
  ///   fn get_display_name(&self, username: &str) -> Option<&str> {
  ///     self.users.iter()
  ///       .find(|(u, _)| u == username)
  ///       .map(|(_, d)| d.as_str())
  ///   }
  /// }
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Reply {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .option_alt(users.get_display_name(&self.author))
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// struct Comment {
  ///   author: String,
  ///   content: String,
  ///   pinned_reply: Option<Reply>,
  /// }
  ///
  /// impl Bluegum for Comment {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .add_option_node(
  ///         "pinned_reply",
  ///         self.pinned_reply.as_ref().map(|r| r as &dyn Bluegum),
  ///       );
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Comment {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Comment")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content)
  ///       .add_option_node_with_state(users, "pinned_reply", self.pinned_reply.as_ref());
  ///   }
  /// }
  ///
  /// let users = UserTable {
  ///   users: vec![("moderator".to_string(), "Mod Team".to_string())],
  /// };
  /// let comment = Comment {
  ///   author: "Alice".to_string(),
  ///   content: "Great post!".to_string(),
  ///   pinned_reply: Some(Reply {
  ///     author: "moderator".to_string(),
  ///     content: "Pinned for visibility".to_string(),
  ///   }),
  /// };
  /// let builder = Builder::render_with_state(&comment, &users);
  /// let mut printer = Printer::default();
  /// printer.render_builder(builder);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_option_node_with_state<State: ?Sized, T: BluegumWithState<State>>(
    &mut self,
    state: &State,
    name: &str,
    node: Option<&T>,
  ) -> &mut Self {
    node.map(|n| self.add_node_with_state(state, name, n));

    self
  }

  /// Add multiple child nodes with a label.
  ///
  /// This method accepts any type that implements [`Nodes`], such as
  /// `Vec<T>` or `&[T]` where `T: Bluegum`. All nodes are rendered
  /// and grouped under the given label.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Rust Discussion
  /// 02│  └─○ replies [len=3]
  /// 03│    ├─▣ 0: Reply
  /// 04│    │   · author:  Alice
  /// 05│    │   · content: Great post!
  /// 06│    │
  /// 07│    ├─▣ 1: Reply
  /// 08│    │   · author:  Bob
  /// 09│    │   · content: Thanks!
  /// 10│    │
  /// 11│    └─▣ 2: Reply
  /// 12│        · author:  Charlie
  /// 13│        · content: Interesting.
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// struct Thread {
  ///   topic: String,
  ///   replies: Vec<Reply>,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_nodes("replies", &self.replies);
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Rust Discussion".to_string(),
  ///   replies: vec![
  ///     Reply { author: "Alice".to_string(), content: "Great post!".to_string() },
  ///     Reply { author: "Bob".to_string(), content: "Thanks!".to_string() },
  ///     Reply { author: "Charlie".to_string(), content: "Interesting.".to_string() },
  ///   ],
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_nodes<'a>(
    &mut self,
    name: &str,
    nodes: impl Nodes<'a>,
  ) -> &mut Self {
    let c = nodes.render();

    self.total_nodes_len += c.len();

    self.nodes.push((name.to_string(), c));

    self
  }

  /// Add multiple child nodes only if the collection is `Some`.
  ///
  /// This is a convenience method for optional collections of nodes.
  /// If the collection is `None`, no nodes are added.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Rust Discussion
  /// 02│  ├─○ replies [len=2]
  /// 03│  │ ├─▣ 0: Reply
  /// 04│  │ │   · author:  Alice
  /// 05│  │ │   · content: Hello!
  /// 06│  │ │
  /// 07│  │ └─▣ 1: Reply
  /// 08│  │     · author:  Bob
  /// 09│  │     · content: Hi!
  /// 10│  │
  /// 11│  └─○ pinned_replies [len=1]
  /// 12│    └─▣ 0: Reply
  /// 13│        · author:  Moderator
  /// 14│        · content: Welcome!
  /// ```
  ///
  /// Note: If `pinned_replies` is `None`, that section won't appear.
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// struct Thread {
  ///   topic: String,
  ///   replies: Vec<Reply>,
  ///   pinned_replies: Option<Vec<Reply>>,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_nodes("replies", &self.replies)
  ///       .add_option_nodes("pinned_replies", self.pinned_replies.as_ref());
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Rust Discussion".to_string(),
  ///   replies: vec![
  ///     Reply { author: "Alice".to_string(), content: "Hello!".to_string() },
  ///     Reply { author: "Bob".to_string(), content: "Hi!".to_string() },
  ///   ],
  ///   pinned_replies: Some(vec![
  ///     Reply { author: "Moderator".to_string(), content: "Welcome!".to_string() },
  ///   ]),
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_option_nodes<'a>(
    &mut self,
    name: &str,
    nodes: Option<impl Nodes<'a>>,
  ) -> &mut Self {
    nodes.map(|nds| self.add_nodes(name, nds));

    self
  }

  /// Add multiple child nodes with state context.
  ///
  /// This method accepts collections of items that implement
  /// [`BluegumWithState`] and renders them all with access to the provided
  /// state. This is useful when you have collections of nodes that need
  /// external context for rendering, such as user lookups or configuration.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Rust Discussion
  /// 02│  └─○ replies [len=2]
  /// 03│    ├─▣ 0: Reply
  /// 04│    │   · author: alice                            Alice Smith
  /// 05│    │   · content: Great post!
  /// 06│    │
  /// 07│    └─▣ 1: Reply
  /// 08│        · author: bob                              Bob Jones
  /// 09│        · content: Thanks!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, BluegumWithState, Builder, Printer};
  ///
  /// struct UserTable {
  ///   users: Vec<(String, String)>, // (username, display_name)
  /// }
  ///
  /// impl UserTable {
  ///   fn get_display_name(&self, username: &str) -> Option<&str> {
  ///     self.users.iter()
  ///       .find(|(u, _)| u == username)
  ///       .map(|(_, d)| d.as_str())
  ///   }
  /// }
  ///
  /// struct Reply {
  ///   author: String,
  ///   content: String,
  /// }
  ///
  /// impl Bluegum for Reply {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Reply {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Reply")
  ///       .field("author", &self.author)
  ///       .option_alt(users.get_display_name(&self.author))
  ///       .field("content", &self.content);
  ///   }
  /// }
  ///
  /// struct Thread {
  ///   topic: String,
  ///   replies: Vec<Reply>,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_nodes("replies", &self.replies);
  ///   }
  /// }
  ///
  /// impl BluegumWithState<UserTable> for Thread {
  ///   fn node_with_state(&self, b: &mut Builder, users: &UserTable) {
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_nodes_with_state(users, "replies", &self.replies);
  ///   }
  /// }
  ///
  /// let users = UserTable {
  ///   users: vec![
  ///     ("alice".to_string(), "Alice Smith".to_string()),
  ///     ("bob".to_string(), "Bob Jones".to_string()),
  ///   ],
  /// };
  /// let thread = Thread {
  ///   topic: "Rust Discussion".to_string(),
  ///   replies: vec![
  ///     Reply { author: "alice".to_string(), content: "Great post!".to_string() },
  ///     Reply { author: "bob".to_string(), content: "Thanks!".to_string() },
  ///   ],
  /// };
  /// let builder = Builder::render_with_state(&thread, &users);
  /// let mut printer = Printer::default();
  /// printer.render_builder(builder);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_nodes_with_state<'a, State: ?Sized>(
    &mut self,
    state: &State,
    name: &str,
    nodes: impl NodesWithState<'a, State>,
  ) -> &mut Self {
    let c = nodes.render_with_state(state);

    self.total_nodes_len += c.len();

    self.nodes.push((name.to_string(), c));

    self
  }

  /// Add multiple child nodes from pre-built builders.
  ///
  /// This method allows you to add a collection of manually constructed
  /// builders as child nodes. This is useful when you need fine-grained
  /// control over the tree structure or when combining builders from
  /// different sources.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Announcements
  /// 02│  └─○ highlighted [len=2]
  /// 03│    ├─▣ 0: Comment
  /// 04│    │   · author:  Admin
  /// 05│    │   · content: Welcome!
  /// 06│    │
  /// 07│    └─▣ 1: Comment
  /// 08│        · author:  Moderator
  /// 09│        · content: Please read the rules.
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Thread {
  ///   topic: String,
  ///   highlighted_authors: Vec<String>,
  ///   highlighted_contents: Vec<String>,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     // Manually create builders for each highlighted comment
  ///     let builders: Vec<Builder> = self.highlighted_authors.iter()
  ///       .zip(self.highlighted_contents.iter())
  ///       .map(|(author, content)| {
  ///         let mut builder = Builder::new();
  ///         builder.name("Comment")
  ///           .field("author", author)
  ///           .field("content", content);
  ///         builder
  ///       })
  ///       .collect();
  ///
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_nodes_of_builders("highlighted", builders);
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Announcements".to_string(),
  ///   highlighted_authors: vec!["Admin".to_string(), "Moderator".to_string()],
  ///   highlighted_contents: vec!["Welcome!".to_string(), "Please read the rules.".to_string()],
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_nodes_of_builders(
    &mut self,
    name: &str,
    nodes: Vec<Builder>,
  ) -> &mut Self {
    self.total_nodes_len += nodes.len();

    self.nodes.push((name.to_string(), nodes));

    self
  }

  /// Add multiple unlabeled builders to this node.
  ///
  /// This method adds a collection of builders as unlabeled child nodes,
  /// meaning they will be displayed directly as part of the current node's
  /// structure without being grouped under a named section. This is useful
  /// for flattening hierarchies or merging content from multiple sources.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Discussion
  /// 02│  ├─▣ Comment
  /// 03│  │   · author:  Alice
  /// 04│  │   · content: First!
  /// 05│  │
  /// 06│  └─▣ Comment
  /// 07│      · author:  Bob
  /// 08│      · content: Second!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Thread {
  ///   topic: String,
  ///   inline_comments: Vec<(String, String)>, // (author, content)
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     // Create builders for inline comments
  ///     let builders: Vec<Builder> = self.inline_comments.iter()
  ///       .map(|(author, content)| {
  ///         let mut builder = Builder::new();
  ///         builder.name("Comment")
  ///           .field("author", author)
  ///           .field("content", content);
  ///         builder
  ///       })
  ///       .collect();
  ///
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_builders(builders);
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Discussion".to_string(),
  ///   inline_comments: vec![
  ///     ("Alice".to_string(), "First!".to_string()),
  ///     ("Bob".to_string(), "Second!".to_string()),
  ///   ],
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_builders(&mut self, builders: Vec<Builder>) -> &mut Self {
    self.total_nodes_len += builders.len();
    self.unlabled_nodes.extend(builders);

    self
  }

  /// Add an unlabeled builder to this node.
  ///
  /// This method adds a single builder as an unlabeled child node,
  /// meaning it will be displayed directly as part of the current node's
  /// structure without being grouped under a named section. This is useful
  /// for flattening hierarchies or merging content from another source.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Discussion
  /// 02│  └─▣ Comment
  /// 03│      · author:  Featured
  /// 04│      · content: This is featured!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Thread {
  ///   topic: String,
  ///   featured_author: String,
  ///   featured_content: String,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     // Create a builder for the featured comment
  ///     let mut featured = Builder::new();
  ///     featured.name("Comment")
  ///       .field("author", &self.featured_author)
  ///       .field("content", &self.featured_content);
  ///
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_builder(featured);
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Discussion".to_string(),
  ///   featured_author: "Featured".to_string(),
  ///   featured_content: "This is featured!".to_string(),
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_builder(&mut self, builder: Builder) -> &mut Self {
    self.total_nodes_len += 1;
    self.unlabled_nodes.push(builder);

    self
  }

  /// Add a single pre-built builder as a named child node.
  ///
  /// This method takes a manually constructed builder and adds it as a
  /// labeled child node. This is useful when you need to build complex
  /// child nodes with custom logic before adding them to the parent.
  ///
  /// # Output
  ///
  /// ```text
  /// 00│◈─▣ Thread
  /// 01│  │ · topic: Discussion
  /// 02│  └─○ pinned_comment
  /// 03│    └─▣ Comment
  /// 04│        · author:  Moderator
  /// 05│        · content: Please read the rules!
  /// ```
  ///
  /// # Examples
  ///
  /// ```rust
  /// use bluegum::{Bluegum, Builder, Printer};
  ///
  /// struct Thread {
  ///   topic: String,
  ///   pinned_author: String,
  ///   pinned_content: String,
  /// }
  ///
  /// impl Bluegum for Thread {
  ///   fn node(&self, b: &mut Builder) {
  ///     // Build the pinned comment with custom logic
  ///     let mut pinned = Builder::new();
  ///     pinned.name("Comment")
  ///       .field("author", &self.pinned_author)
  ///       .field("content", &self.pinned_content);
  ///
  ///     b.name("Thread")
  ///       .field("topic", &self.topic)
  ///       .add_named_builder("pinned_comment", pinned);
  ///   }
  /// }
  ///
  /// let thread = Thread {
  ///   topic: "Discussion".to_string(),
  ///   pinned_author: "Moderator".to_string(),
  ///   pinned_content: "Please read the rules!".to_string(),
  /// };
  /// let mut printer = Printer::default();
  /// printer.render(&thread);
  /// println!("{}", printer.strip_color());
  /// ```
  pub fn add_named_builder(&mut self, name: &str, node: Builder) -> &mut Self {
    self.total_nodes_len += 1;

    self.nodes.push((name.to_string(), vec![node]));

    self
  }
}
