///|
/// ARIA Snapshot Generation
/// Generates Playwright-compatible YAML format from AccessibilityTree
/// https://playwright.dev/docs/aria-snapshots

// =============================================================================
// Public API - YAML Format
// =============================================================================

///|
/// Generate an ARIA snapshot in YAML format from an AccessibilityTree
pub fn AccessibilityTree::to_aria_snapshot(self : AccessibilityTree) -> String {
  self.root.to_aria_snapshot_with_indent(0)
}

///|
/// Generate an ARIA snapshot for a single node
pub fn AccessibilityNode::to_aria_snapshot(self : AccessibilityNode) -> String {
  self.to_aria_snapshot_with_indent(0)
}

// =============================================================================
// Public API - JSON Format
// =============================================================================

///|
/// Generate an ARIA snapshot in JSON format from an AccessibilityTree
pub fn AccessibilityTree::to_aria_json(self : AccessibilityTree) -> String {
  self.root.to_aria_json()
}

///|
/// Generate an ARIA snapshot in JSON format for a single node
pub fn AccessibilityNode::to_aria_json(self : AccessibilityNode) -> String {
  let buf = StringBuilder::new()
  render_node_json(self, buf, 0)
  buf.to_string()
}

///|
fn render_node_json(
  node : AccessibilityNode,
  buf : StringBuilder,
  indent : Int,
) -> Unit {
  write_indent(buf, indent)
  buf.write_string("{\n")

  // Role
  write_indent(buf, indent + 1)
  buf.write_string("\"role\": \"")
  buf.write_string(role_to_snapshot_string(node.role))
  buf.write_string("\"")

  // Ref ID (for AI - simple sequential identifier for element targeting)
  match node.ref_id {
    Some(ref_id) => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"ref\": \"")
      buf.write_string(escape_string(ref_id))
      buf.write_string("\"")
    }
    None => ()
  }

  // Name
  match node.name {
    Some(name) if !name.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"name\": \"")
      buf.write_string(escape_string(name))
      buf.write_string("\"")
    }
    _ => ()
  }

  // Level
  match node.level {
    Some(level) => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"level\": ")
      buf.write_string(level.to_string())
    }
    None => ()
  }

  // Focusable (for AI - helps identify interactive elements)
  if node.focusable {
    buf.write_string(",\n")
    write_indent(buf, indent + 1)
    buf.write_string("\"focusable\": true")
  }

  // Tabindex (for AI - helps understand navigation order)
  match node.tabindex {
    Some(tabindex) => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"tabindex\": ")
      buf.write_string(tabindex.to_string())
    }
    None => ()
  }

  // Source ID (for AI - reference to original HTML element)
  match node.source_id {
    Some(source_id) if !source_id.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"sourceId\": \"")
      buf.write_string(escape_string(source_id))
      buf.write_string("\"")
    }
    _ => ()
  }

  // href (for AI - link URL)
  match node.href {
    Some(href) if !href.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"href\": \"")
      buf.write_string(escape_string(href))
      buf.write_string("\"")
    }
    _ => ()
  }

  // Tag name (for AI - original HTML tag)
  match node.tag_name {
    Some(tag) if !tag.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"tag\": \"")
      buf.write_string(escape_string(tag))
      buf.write_string("\"")
    }
    _ => ()
  }

  // Selector (for AI - CSS selector for element targeting)
  match node.selector {
    Some(sel) if !sel.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"selector\": \"")
      buf.write_string(escape_string(sel))
      buf.write_string("\"")
    }
    _ => ()
  }

  // Visible flag (for AI - false for style/script/meta content)
  if !node.visible {
    buf.write_string(",\n")
    write_indent(buf, indent + 1)
    buf.write_string("\"visible\": false")
  }

  // Bounds (for AI - spatial information)
  match node.bounds {
    Some(bounds) => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"bounds\": { \"x\": ")
      buf.write_string(bounds.x.to_string())
      buf.write_string(", \"y\": ")
      buf.write_string(bounds.y.to_string())
      buf.write_string(", \"width\": ")
      buf.write_string(bounds.width.to_string())
      buf.write_string(", \"height\": ")
      buf.write_string(bounds.height.to_string())
      buf.write_string(" }")
    }
    None => ()
  }

  // Form input fields (for AI - form interaction)
  match node.input_type {
    Some(input_type) if !input_type.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"inputType\": \"")
      buf.write_string(escape_string(input_type))
      buf.write_string("\"")
    }
    _ => ()
  }
  match node.placeholder {
    Some(placeholder) if !placeholder.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"placeholder\": \"")
      buf.write_string(escape_string(placeholder))
      buf.write_string("\"")
    }
    _ => ()
  }
  match node.input_value {
    Some(value) if !value.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"value\": \"")
      buf.write_string(escape_string(value))
      buf.write_string("\"")
    }
    _ => ()
  }
  if node.required {
    buf.write_string(",\n")
    write_indent(buf, indent + 1)
    buf.write_string("\"required\": true")
  }

  // Aggregated text content (for paragraphs, headings, etc.)
  match node.text {
    Some(text) if !text.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"text\": \"")
      buf.write_string(escape_string(text))
      buf.write_string("\"")
    }
    _ => ()
  }

  // Select options
  if !node.options.is_empty() {
    buf.write_string(",\n")
    write_indent(buf, indent + 1)
    buf.write_string("\"options\": [\n")
    for i, option in node.options {
      if i > 0 {
        buf.write_string(",\n")
      }
      write_indent(buf, indent + 2)
      buf.write_string("{ \"value\": \"")
      buf.write_string(escape_string(option.value))
      buf.write_string("\", \"label\": \"")
      buf.write_string(escape_string(option.label))
      buf.write_string("\"")
      if option.selected {
        buf.write_string(", \"selected\": true")
      }
      buf.write_string(" }")
    }
    buf.write_string("\n")
    write_indent(buf, indent + 1)
    buf.write_string("]")
  }

  // Labelled by reference
  match node.labelled_by {
    Some(labelled_by) if !labelled_by.is_empty() => {
      buf.write_string(",\n")
      write_indent(buf, indent + 1)
      buf.write_string("\"labelledBy\": \"")
      buf.write_string(escape_string(labelled_by))
      buf.write_string("\"")
    }
    _ => ()
  }

  // States
  if !node.states.is_empty() {
    buf.write_string(",\n")
    write_indent(buf, indent + 1)
    buf.write_string("\"states\": [")
    for i, state in node.states {
      if i > 0 {
        buf.write_string(", ")
      }
      buf.write_string("\"")
      buf.write_string(state_to_json_string(state))
      buf.write_string("\"")
    }
    buf.write_string("]")
  }

  // Children
  if !node.children.is_empty() {
    buf.write_string(",\n")
    write_indent(buf, indent + 1)
    buf.write_string("\"children\": [\n")
    for i, child in node.children {
      if i > 0 {
        buf.write_string(",\n")
      }
      render_node_json(child, buf, indent + 2)
    }
    buf.write_string("\n")
    write_indent(buf, indent + 1)
    buf.write_string("]")
  }
  buf.write_string("\n")
  write_indent(buf, indent)
  buf.write_string("}")
}

///|
fn state_to_json_string(state : State) -> String {
  match state {
    Busy => "busy"
    Checked => "checked"
    CheckedMixed => "checked-mixed"
    Disabled => "disabled"
    Expanded => "expanded"
    Grabbed => "grabbed"
    Hidden => "hidden"
    Invalid => "invalid"
    Pressed => "pressed"
    PressedMixed => "pressed-mixed"
    Selected => "selected"
  }
}

// =============================================================================
// Internal Implementation
// =============================================================================

///|
fn AccessibilityNode::to_aria_snapshot_with_indent(
  self : AccessibilityNode,
  indent : Int,
) -> String {
  let buf = StringBuilder::new()
  render_node(self, buf, indent)
  buf.to_string()
}

///|
fn render_node(
  node : AccessibilityNode,
  buf : StringBuilder,
  indent : Int,
) -> Unit {
  // Skip nodes with no role or generic role
  if node.role == Generic && node.name is None && node.children.is_empty() {
    return
  }

  // Write indentation
  write_indent(buf, indent)
  buf.write_string("- ")

  // Write role
  buf.write_string(role_to_snapshot_string(node.role))

  // Write name if present
  match node.name {
    Some(name) if !name.is_empty() => {
      buf.write_string(" \"")
      buf.write_string(escape_string(name))
      buf.write_string("\"")
    }
    _ => ()
  }

  // Write attributes
  render_attributes(node, buf)

  // Handle children
  if node.children.is_empty() {
    buf.write_string("\n")
  } else {
    // Check if all children are text-like (no nested structure)
    let has_complex_children = node.children
      .iter()
      .any(fn(child) { !child.children.is_empty() || child.role != Generic })
    if !has_complex_children && node.children.length() == 1 {
      // Single simple child - render inline
      match node.children[0].name {
        Some(text) if !text.is_empty() => {
          buf.write_string(": ")
          buf.write_string(escape_string(text))
        }
        _ => ()
      }
      buf.write_string("\n")
    } else {
      // Complex children - render nested
      buf.write_string(":\n")
      for child in node.children {
        render_node(child, buf, indent + 1)
      }
    }
  }
}

///|
fn render_attributes(node : AccessibilityNode, buf : StringBuilder) -> Unit {
  let attrs : Array[String] = []

  // Level attribute (for headings)
  match node.level {
    Some(level) => attrs.push("level=\{level}")
    None => ()
  }

  // State attributes
  for state in node.states {
    match state {
      Checked => attrs.push("checked")
      CheckedMixed => attrs.push("checked=mixed")
      Disabled => attrs.push("disabled")
      Expanded => attrs.push("expanded")
      Pressed => attrs.push("pressed")
      PressedMixed => attrs.push("pressed=mixed")
      Selected => attrs.push("selected")
      _ => ()
    }
  }

  // Write attributes if any
  if !attrs.is_empty() {
    buf.write_string(" [")
    for i, attr in attrs {
      if i > 0 {
        buf.write_string("][")
      }
      buf.write_string(attr)
    }
    buf.write_string("]")
  }
}

///|
fn write_indent(buf : StringBuilder, level : Int) -> Unit {
  for _ in 0.. String {
  let buf = StringBuilder::new()
  for c in s {
    match c {
      '"' => buf.write_string("\\\"")
      '\\' => buf.write_string("\\\\")
      '\n' => buf.write_string("\\n")
      '\r' => buf.write_string("\\r")
      '\t' => buf.write_string("\\t")
      _ => buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
fn role_to_snapshot_string(role : Role) -> String {
  match role {
    // Landmarks
    Banner => "banner"
    Complementary => "complementary"
    ContentInfo => "contentinfo"
    Form => "form"
    Main => "main"
    Navigation => "navigation"
    Region => "region"
    Search => "search"

    // Document structure
    Article => "article"
    Cell => "cell"
    ColumnHeader => "columnheader"
    Definition => "definition"
    Directory => "directory"
    Document => "document"
    Feed => "feed"
    Figure => "figure"
    Generic => "generic"
    Group => "group"
    Heading => "heading"
    Img => "img"
    List => "list"
    ListItem => "listitem"
    Math => "math"
    None => "none"
    Note => "note"
    Paragraph => "paragraph"
    Presentation => "presentation"
    Row => "row"
    RowGroup => "rowgroup"
    RowHeader => "rowheader"
    Separator => "separator"
    Table => "table"
    Term => "term"
    Toolbar => "toolbar"
    Tooltip => "tooltip"

    // Widgets
    Alert => "alert"
    AlertDialog => "alertdialog"
    Application => "application"
    Button => "button"
    Checkbox => "checkbox"
    Combobox => "combobox"
    Dialog => "dialog"
    GridCell => "gridcell"
    Link => "link"
    Listbox => "listbox"
    Log => "log"
    Marquee => "marquee"
    Menu => "menu"
    MenuBar => "menubar"
    MenuItem => "menuitem"
    MenuItemCheckbox => "menuitemcheckbox"
    MenuItemRadio => "menuitemradio"
    Meter => "meter"
    Option => "option"
    Progressbar => "progressbar"
    Radio => "radio"
    RadioGroup => "radiogroup"
    Scrollbar => "scrollbar"
    SearchBox => "searchbox"
    Slider => "slider"
    SpinButton => "spinbutton"
    Status => "status"
    Switch => "switch"
    Tab => "tab"
    TabList => "tablist"
    TabPanel => "tabpanel"
    Textbox => "textbox"
    Timer => "timer"
    Tree => "tree"
    TreeGrid => "treegrid"
    TreeItem => "treeitem"

    // Text-level semantics
    Caption => "caption"
    Code => "code"
    Deletion => "deletion"
    Emphasis => "emphasis"
    Insertion => "insertion"
    Strong => "strong"
    Subscript => "subscript"
    Superscript => "superscript"
    Time => "time"
    Blockquote => "blockquote"
    Mark => "mark"

    // Window roles
    Window => "window"

    // HTML-specific mappings
    HtmlAudio => "audio"
    HtmlVideo => "video"
    HtmlCanvas => "canvas"
    HtmlLabel => "label"
    HtmlSummary => "summary"
  }
}