///|
/// Simple DOT (Graphviz) builder for visualizing MarshalValue structures
/// 
/// Provides utilities to construct directed graphs in DOT format for
/// rendering with Graphviz tools.

///|
priv struct DotNode {
  id : String
  label : String
  shape : String
  color : String
}

///|
priv struct DotEdge {
  src : String
  dst : String
  label : String
  style : String
  color : String
  bidirectional : Bool
}

///|
priv struct DotSubgraph {
  name : String
  label : String
  nodes : Array[String]
}

///|
struct DotBuilder {
  nodes : Array[DotNode]
  edges : Array[DotEdge]
  subgraphs : Array[DotSubgraph]
  mut next_id : Int
  graph_name : String
  rankdir : String // "TB", "LR", "BT", "RL"
  mut default_node_shape : String // "box", "circle", "ellipse", "diamond", etc.
  mut default_node_style : String // "rounded", "filled", etc.
}

///|
pub fn DotBuilder::new() -> DotBuilder {
  {
    nodes: [],
    edges: [],
    subgraphs: [],
    next_id: 0,
    graph_name: "Marshal",
    rankdir: "LR",
    default_node_shape: "box",
    default_node_style: "rounded",
  }
}

///|
/// Create a new DotBuilder with custom graph name and direction
/// rankdir can be: "TB" (top-bottom), "LR" (left-right), "BT" (bottom-top), "RL" (right-left)
pub fn DotBuilder::with_config(
  graph_name~ : String,
  rankdir~ : String,
) -> DotBuilder {
  {
    nodes: [],
    edges: [],
    subgraphs: [],
    next_id: 0,
    graph_name,
    rankdir,
    default_node_shape: "box",
    default_node_style: "rounded",
  }
}

///|
/// Set the default node shape
/// Common shapes: "box", "circle", "ellipse", "diamond", "rectangle", "square",
/// "triangle", "pentagon", "hexagon", "octagon", "doublecircle", "plaintext"
pub fn DotBuilder::set_node_shape(self : DotBuilder, shape : String) -> Unit {
  self.default_node_shape = shape
}

///|
/// Set the default node style
pub fn DotBuilder::set_node_style(self : DotBuilder, style : String) -> Unit {
  self.default_node_style = style
}

///|
/// Generate a unique node ID
pub fn DotBuilder::fresh_id(self : DotBuilder) -> String {
  let id = "n\{self.next_id}"
  self.next_id += 1
  id
}

///|
/// Add a node with a label
pub fn DotBuilder::add_node(
  self : DotBuilder,
  id~ : String,
  label~ : String,
  shape? : String = "",
  color? : String = "",
) -> Unit {
  self.nodes.push({ id, label, shape, color })
}

///|
/// Add a subgraph (cluster) to group nodes
pub fn DotBuilder::add_subgraph(
  self : DotBuilder,
  name~ : String,
  label~ : String,
  nodes~ : Array[String],
) -> Unit {
  self.subgraphs.push({ name, label, nodes })
}

///|
/// Add an edge from src to dst with optional label
pub fn DotBuilder::add_edge(
  self : DotBuilder,
  src~ : String,
  dst~ : String,
  label~ : String,
  style? : String = "",
  color? : String = "",
) -> Unit {
  self.edges.push({ src, dst, label, style, color, bidirectional: false })
}

///|
/// Add a bidirectional edge (two arrows)
pub fn DotBuilder::add_bidirectional_edge(
  self : DotBuilder,
  node1~ : String,
  node2~ : String,
  label~ : String,
) -> Unit {
  self.edges.push({
    src: node1,
    dst: node2,
    label,
    style: "",
    color: "",
    bidirectional: true,
  })
}

///|
/// Generate the complete DOT graph string
pub fn DotBuilder::to_dot(self : DotBuilder) -> String {
  let mut result = "digraph \{self.graph_name} {\n"
  result += "  rankdir=\{self.rankdir};\n"
  result += "  node [shape=\{self.default_node_shape}, style=\{self.default_node_style}];\n\n"

  // Add all subgraphs
  for subgraph in self.subgraphs {
    result += render_dot_subgraph(subgraph) + "\n\n"
  }

  // Add all nodes
  for node in self.nodes {
    result += render_dot_node(node) + "\n"
  }
  result += "\n"

  // Add all edges
  for edge in self.edges {
    result += render_dot_edge(edge) + "\n"
  }
  result += "}\n"
  result
}

///|
/// Generate Mermaid flowchart graph string (useful for Markdown rendering)
pub fn DotBuilder::to_mermaid(self : DotBuilder) -> String {
  let direction = rankdir_to_mermaid_direction(self.rankdir)
  let mut result = "flowchart \{direction}\n"
  if self.graph_name != "" {
    result += "%% graph: \{escape_mermaid_text(self.graph_name)}\n"
  }
  result += "\n"
  for subgraph in self.subgraphs {
    result += render_mermaid_subgraph(subgraph) + "\n\n"
  }
  for node in self.nodes {
    result += render_mermaid_node(node, self.default_node_shape) + "\n"
  }
  result += "\n"
  let link_styles : Array[String] = []
  let mut link_index = 0
  for edge in self.edges {
    let line = render_mermaid_edge(edge)
    result += line + "\n"
    let style = render_mermaid_link_style(edge)
    if style != "" {
      link_styles.push("linkStyle \{link_index} \{style}")
    }
    link_index += 1
  }
  let node_styles : Array[String] = []
  for node in self.nodes {
    if node.color != "" {
      node_styles.push(
        "style \{node.id} fill:\{node.color},stroke:#333,stroke-width:1px",
      )
    }
  }
  if node_styles.length() > 0 || link_styles.length() > 0 {
    result += "\n"
  }
  for line in node_styles {
    result += line + "\n"
  }
  for line in link_styles {
    result += line + "\n"
  }
  result
}

///|
/// Escape special characters for DOT labels
fn escape_dot_label(s : String) -> String {
  let mut result = ""
  for i = 0; i < s.length(); i = i + 1 {
    let c = s.code_unit_at(i).to_int()
    match c {
      0x22 => result += "\\\"" // "
      0x5C => result += "\\\\" // \
      0x0A => result += "\\n" // \n
      0x0D => result += "\\r" // \r
      0x09 => result += "\\t" // \t
      _ => result += c.unsafe_to_char().to_string()
    }
  }
  result
}

///|
fn render_dot_subgraph(sg : DotSubgraph) -> String {
  let escaped = escape_dot_label(sg.label)
  let mut subgraph = "  subgraph cluster_\{sg.name} {\n"
  subgraph += "    label=\"\{escaped}\";\n"
  subgraph += "    style=filled;\n"
  subgraph += "    color=lightgrey;\n"
  for node in sg.nodes {
    subgraph += "    \{node};\n"
  }
  subgraph += "  }"
  subgraph
}

///|
fn render_dot_node(node : DotNode) -> String {
  let escaped = escape_dot_label(node.label)
  if node.shape == "" && node.color == "" {
    "  \{node.id} [label=\"\{escaped}\"];"
  } else {
    let mut attrs = "label=\"\{escaped}\""
    if node.shape != "" {
      attrs += ", shape=\{node.shape}"
    }
    if node.color != "" {
      attrs += ", color=\"\{node.color}\", style=filled"
    }
    "  \{node.id} [\{attrs}];"
  }
}

///|
fn render_dot_edge(edge : DotEdge) -> String {
  if edge.bidirectional {
    if edge.label == "" {
      "  \{edge.src} -> \{edge.dst} [dir=both];"
    } else {
      let escaped = escape_dot_label(edge.label)
      "  \{edge.src} -> \{edge.dst} [label=\"\{escaped}\", dir=both];"
    }
  } else if edge.style != "" || edge.color != "" {
    let mut attrs = ""
    if edge.style != "" {
      attrs += "style=\{edge.style}"
    }
    if edge.color != "" {
      if attrs != "" {
        attrs += ", "
      }
      attrs += "color=\"\{edge.color}\""
    }
    if edge.label != "" {
      let escaped = escape_dot_label(edge.label)
      if attrs != "" {
        attrs += ", "
      }
      attrs += "label=\"\{escaped}\""
    }
    "  \{edge.src} -> \{edge.dst} [\{attrs}];"
  } else if edge.label == "" {
    "  \{edge.src} -> \{edge.dst};"
  } else {
    let escaped = escape_dot_label(edge.label)
    "  \{edge.src} -> \{edge.dst} [label=\"\{escaped}\"];"
  }
}

///|
fn rankdir_to_mermaid_direction(rankdir : String) -> String {
  match rankdir {
    "TB" => "TB"
    "BT" => "BT"
    "RL" => "RL"
    _ => "LR"
  }
}

///|
fn escape_mermaid_text(s : String) -> String {
  let mut result = ""
  for i = 0; i < s.length(); i = i + 1 {
    let c = s.code_unit_at(i).to_int()
    match c {
      0x26 => result += "&" // &
      0x3C => result += "<" // <
      0x3E => result += ">" // >
      0x22 => result += """ // "
      0x7C => result += "|" // |
      0x5B => result += "[" // [
      0x5D => result += "]" // ]
      0x28 => result += "(" // (
      0x29 => result += ")" // )
      0x7B => result += "{" // {
      0x7D => result += "}" // }
      0x0A => result += "
" // \n 0x0D => result += "
" // \r 0x09 => result += " " // \t _ => result += c.unsafe_to_char().to_string() } } result } ///| fn render_mermaid_subgraph(sg : DotSubgraph) -> String { let escaped = escape_mermaid_text(sg.label) let mut result = "subgraph cluster_\{sg.name}[\"\{escaped}\"]\n" for node in sg.nodes { result += " \{node}\n" } result += "end" result } ///| fn render_mermaid_node(node : DotNode, default_shape : String) -> String { let shape = if node.shape == "" { default_shape } else { node.shape } let label = escape_mermaid_text(node.label) match shape { "circle" => " \{node.id}((\{label}))" "doublecircle" => " \{node.id}(((\{label})))" "diamond" => " \{node.id}{\{label}}" "ellipse" => " \{node.id}([\{label}])" _ => " \{node.id}[\"\{label}\"]" } } ///| fn render_mermaid_edge(edge : DotEdge) -> String { let arrow = if edge.bidirectional { "<-->" } else { "-->" } if edge.label == "" { " \{edge.src} \{arrow} \{edge.dst}" } else { let label = escape_mermaid_text(edge.label) " \{edge.src} \{arrow}|\{label}| \{edge.dst}" } } ///| fn render_mermaid_link_style(edge : DotEdge) -> String { let mut styles = "" if edge.style != "" { match edge.style { "bold" => styles += "stroke-width:3px" "dashed" => styles += "stroke-dasharray:5 5" "dotted" => styles += "stroke-dasharray:2 2" _ => () } } if edge.color != "" { if styles != "" { styles += "," } styles += "stroke:\{edge.color}" } styles }