// Benchmarks for render_to_string
// Run with: moon bench --target js -p mizchi/luna/core/render
// moon bench --target wasm-gc -p mizchi/luna/core/render
//
// Compare JS FFI escape vs pure MoonBit escape impact on full rendering

// =============================================================================
// Test Data - VNode Trees
// =============================================================================

///|
/// Simple text node
fn vnode_text() -> @core.Node[Unit, String] {
  @core.text("Hello, World!")
}

///|
/// Text with HTML special characters (needs escaping)
fn vnode_text_escape() -> @core.Node[Unit, String] {
  @core.text(" & \"dangerous\" content")
}

///|
/// Simple element with no attrs
fn vnode_simple_element() -> @core.Node[Unit, String] {
  @core.h("div", [], [@core.text("content")])
}

///|
/// Element with multiple attributes (needs attr escaping)
fn vnode_with_attrs() -> @core.Node[Unit, String] {
  @core.h(
    "div",
    [
      ("id", @core.attr_static("main")),
      ("class", @core.attr_static("container mx-auto")),
      ("data-value", @core.attr_static("{\"count\":42,\"name\":\"test\"}")),
    ],
    [@core.text("content")],
  )
}

///|
/// Nested elements (typical component structure)
fn vnode_nested() -> @core.Node[Unit, String] {
  @core.h("div", [("class", @core.attr_static("card"))], [
    @core.h("header", [("class", @core.attr_static("card-header"))], [
      @core.h("h2", [], [@core.text("Title")]),
    ]),
    @core.h("div", [("class", @core.attr_static("card-body"))], [
      @core.h("p", [], [@core.text("This is the card body content.")]),
      @core.h("button", [("class", @core.attr_static("btn"))], [
        @core.text("Click me"),
      ]),
    ]),
  ])
}

///|
/// List of items (common pattern)
fn vnode_list() -> @core.Node[Unit, String] {
  let items : Array[@core.Node[Unit, String]] = []
  for i in 0..<10 {
    items.push(
      @core.h("li", [("class", @core.attr_static("item"))], [
        @core.text("Item \{i}"),
      ]),
    )
  }
  @core.h("ul", [("class", @core.attr_static("list"))], items)
}

///|
/// Large list (stress test)
fn vnode_large_list() -> @core.Node[Unit, String] {
  let items : Array[@core.Node[Unit, String]] = []
  for i in 0..<100 {
    items.push(
      @core.h("li", [("class", @core.attr_static("item"))], [
        @core.text("Item \{i} with some  & \"characters\""),
      ]),
    )
  }
  @core.h("ul", [("class", @core.attr_static("list"))], items)
}

///|
/// Typical page structure
fn vnode_page() -> @core.Node[Unit, String] {
  @core.h("html", [], [
    @core.h("head", [], [
      @core.h("meta", [("charset", @core.attr_static("utf-8"))], []),
      @core.h("title", [], [@core.text("Test Page")]),
    ]),
    @core.h("body", [], [
      @core.h("header", [], [
        @core.h("nav", [("class", @core.attr_static("navbar"))], [
          @core.h("a", [("href", @core.attr_static("/"))], [@core.text("Home")]),
          @core.h("a", [("href", @core.attr_static("/about"))], [
            @core.text("About"),
          ]),
        ]),
      ]),
      @core.h("main", [("class", @core.attr_static("container"))], [
        @core.h("h1", [], [@core.text("Welcome")]),
        @core.h("p", [], [
          @core.text("This is a test page with  & \"characters\"."),
        ]),
      ]),
      @core.h("footer", [], [@core.text("© 2024")]),
    ]),
  ])
}

// =============================================================================
// Render Benchmarks
// =============================================================================

///|
test "render: text" (b : @bench.T) {
  let node = vnode_text()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: text_escape" (b : @bench.T) {
  let node = vnode_text_escape()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: simple_element" (b : @bench.T) {
  let node = vnode_simple_element()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: with_attrs" (b : @bench.T) {
  let node = vnode_with_attrs()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: nested" (b : @bench.T) {
  let node = vnode_nested()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: list (10 items)" (b : @bench.T) {
  let node = vnode_list()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: large_list (100 items with escape)" (b : @bench.T) {
  let node = vnode_large_list()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

///|
test "render: page" (b : @bench.T) {
  let node = vnode_page()
  b.bench(fn() { b.keep(render_to_string(node).html) })
}

// =============================================================================
// Visitor Pattern Comparison (Experimental)
// =============================================================================
// Compare direct pattern matching vs visitor pattern for VNode traversal
// This helps decide whether to refactor for deduplication

///|
/// Visitor interface for VNode traversal
priv trait NodeVisitor {
  fn on_text(Self, String) -> Unit
  fn on_element_open(Self, String, Array[(String, @core.Attr[Unit, String])]) -> Unit
  fn on_element_close(Self, String, Bool) -> Unit // (tag, is_void)
  fn on_fragment_start(Self) -> Unit
  fn on_fragment_end(Self) -> Unit
}

///|
/// Visitor-based traversal (experimental)
fn visitor_traverse(
  node : @core.Node[Unit, String],
  visitor : &NodeVisitor,
) -> Unit {
  match node {
    Text(content) => visitor.on_text(content)
    DynamicText(getter) => visitor.on_text(getter())
    Fragment(children) => {
      visitor.on_fragment_start()
      for child in children {
        visitor_traverse(child, visitor)
      }
      visitor.on_fragment_end()
    }
    Element(elem) => {
      let tag = elem.tag
      let is_void = is_void_element(tag)
      visitor.on_element_open(tag, elem.attrs)
      if !is_void {
        for child in elem.children {
          visitor_traverse(child, visitor)
        }
      }
      visitor.on_element_close(tag, is_void)
    }
    // Skip other node types for this benchmark
    _ => ()
  }
}

///|
/// Simple StringBuilder visitor for comparison
priv struct SBVisitor {
  sb : StringBuilder
}

///|
fn SBVisitor::new() -> SBVisitor {
  { sb: StringBuilder(size_hint=256), }
}

///|
impl NodeVisitor for SBVisitor with fn on_text(self, content) {
  escape_html_to(self.sb, content)
}

///|
impl NodeVisitor for SBVisitor with fn on_element_open(self, tag, attrs) {
  self.sb.write_char('<')
  self.sb.write_string(tag)
  render_attrs_to(self.sb, attrs)
  if is_void_element(tag) {
    self.sb.write_string(" />")
  } else {
    self.sb.write_char('>')
  }
}

///|
impl NodeVisitor for SBVisitor with fn on_element_close(self, tag, is_void) {
  if !is_void {
    self.sb.write_string("')
  }
}

///|
impl NodeVisitor for SBVisitor with fn on_fragment_start(_self) {
  // No output for fragment
}

///|
impl NodeVisitor for SBVisitor with fn on_fragment_end(_self) {
  // No output for fragment
}

///|
/// Render using visitor pattern (for benchmark comparison)
fn render_with_visitor(node : @core.Node[Unit, String]) -> String {
  let visitor = SBVisitor::new()
  visitor_traverse(node, visitor)
  visitor.sb.to_string()
}

// =============================================================================
// Visitor vs Direct Comparison Benchmarks
// =============================================================================

///|
test "compare: nested - direct vs visitor" (b : @bench.T) {
  let node = vnode_nested()
  b.bench(name="direct", fn() { b.keep(render_to_string(node).html) })
  b.bench(name="visitor", fn() { b.keep(render_with_visitor(node)) })
}

///|
test "compare: list - direct vs visitor" (b : @bench.T) {
  let node = vnode_list()
  b.bench(name="direct", fn() { b.keep(render_to_string(node).html) })
  b.bench(name="visitor", fn() { b.keep(render_with_visitor(node)) })
}

///|
test "compare: large_list - direct vs visitor" (b : @bench.T) {
  let node = vnode_large_list()
  b.bench(name="direct", fn() { b.keep(render_to_string(node).html) })
  b.bench(name="visitor", fn() { b.keep(render_with_visitor(node)) })
}

///|
test "compare: page - direct vs visitor" (b : @bench.T) {
  let node = vnode_page()
  b.bench(name="direct", fn() { b.keep(render_to_string(node).html) })
  b.bench(name="visitor", fn() { b.keep(render_with_visitor(node)) })
}