///|
/// Benchmarks for comparing () -> T vs Signal[T] for DSL design
/// Question: Should dyn_class accept () -> String or Signal[String]?

// =============================================================================
// Direct function call vs Signal.get() vs wrapped Signal
// =============================================================================

///|
test "dyn: direct function call (1000 reads)" (b : @bench.T) {
  // Simulating a pure getter function
  let value = "static-class-name"
  let getter : () -> String = fn() { value }
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = getter()

    }
  })
}

///|
test "dyn: Signal.get (1000 reads)" (b : @bench.T) {
  let sig = signal("static-class-name")
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = sig.get()

    }
  })
}

///|
test "dyn: Signal.peek (1000 reads, no tracking)" (b : @bench.T) {
  let sig = signal("static-class-name")
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = sig.peek()

    }
  })
}

///|
test "dyn: wrapped Signal in closure (1000 reads)" (b : @bench.T) {
  // This is what users would write if dyn_ accepts () -> T
  let sig = signal("static-class-name")
  let getter : () -> String = fn() { sig.get() }
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = getter()

    }
  })
}

// =============================================================================
// With computation (more realistic case)
// =============================================================================

///|
test "dyn: computed function (1000 reads)" (b : @bench.T) {
  let completed = signal(true)
  let editing = signal(false)
  // TodoMVC-style class computation
  let class_getter : () -> String = fn() {
    match (completed.get(), editing.get()) {
      (true, true) => "completed editing"
      (true, false) => "completed"
      (false, true) => "editing"
      (false, false) => ""
    }
  }
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = class_getter()

    }
  })
}

///|
test "dyn: memo computed (1000 reads)" (b : @bench.T) {
  let completed = signal(true)
  let editing = signal(false)
  let class_memo = memo(fn() {
    match (completed.get(), editing.get()) {
      (true, true) => "completed editing"
      (true, false) => "completed"
      (false, true) => "editing"
      (false, false) => ""
    }
  })
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = class_memo()

    }
  })
}

// =============================================================================
// API design comparison: () -> T vs Signal[T]
// =============================================================================

// Simulating DSL parameter handling

///|
/// If dyn_class accepts () -> String
fn apply_dyn_getter(getter : () -> String) -> String {
  getter()
}

///|
/// If dyn_class accepts Signal[String]
fn apply_dyn_signal(sig : Signal[String]) -> String {
  sig.get()
}

///|
test "api: () -> String parameter (1000 calls)" (b : @bench.T) {
  let sig = signal("class-name")
  let getter : () -> String = fn() { sig.get() }
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = apply_dyn_getter(getter)

    }
  })
}

///|
test "api: Signal[String] parameter (1000 calls)" (b : @bench.T) {
  let sig = signal("class-name")
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = apply_dyn_signal(sig)

    }
  })
}

// =============================================================================
// Bool case (for checked, disabled, etc.)
// =============================================================================

///|
test "dyn bool: () -> Bool (1000 reads)" (b : @bench.T) {
  let sig = signal(true)
  let getter : () -> Bool = fn() { sig.get() }
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = getter()

    }
  })
}

///|
test "dyn bool: Signal[Bool].get (1000 reads)" (b : @bench.T) {
  let sig = signal(true)
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = sig.get()

    }
  })
}