///|
/// Benchmarks for fine-grained reactive store approaches

// =============================================================================
// Test data structures
// =============================================================================

///|
priv struct BenchRecord {
  count : Int
  name : String
}

// =============================================================================
// Approach 1: Split Signals (Fine-grained) - Recommended
// =============================================================================

///|
test "split: 2 fields - read (1000 ops)" (b : @bench.T) {
  let store = SplitStore2::new(0, "test")
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = store.field1.get()
      let _ = store.field2.get()

    }
  })
}

///|
test "split: 2 fields - update field1 only (100 ops)" (b : @bench.T) {
  let store = SplitStore2::new(0, "test")
  b.bench(fn() {
    for i = 0; i < 100; i = i + 1 {
      store.field1.set(i)
    }
  })
}

///|
test "split: 2 fields - effect on field1, update field2 (100 ops)" (
  b : @bench.T,
) {
  let store = SplitStore2::new(0, "test")
  let effect_runs = Ref::new(0)
  let _ = effect(fn() {
    let _ = store.field1.get()
    effect_runs.val = effect_runs.val + 1
  })
  b.bench(fn() {
    for i = 0; i < 100; i = i + 1 {
      // Update field2 - should NOT trigger effect on field1
      store.field2.set("updated")
    }
  })
}

///|
test "split: 10 fields - update 1 field (100 ops)" (b : @bench.T) {
  // Simulate 10-field store with individual signals
  let f1 = signal(1)
  let f2 = signal(2)
  let f3 = signal(3)
  let f4 = signal(4)
  let f5 = signal(5)
  let f6 = signal(6)
  let f7 = signal(7)
  let f8 = signal(8)
  let f9 = signal(9)
  let f10 = signal(10)

  // Effects on all fields
  let _ = effect(fn() {
    let _ = f1.get()

  })
  let _ = effect(fn() {
    let _ = f2.get()

  })
  let _ = effect(fn() {
    let _ = f3.get()

  })
  let _ = effect(fn() {
    let _ = f4.get()

  })
  let _ = effect(fn() {
    let _ = f5.get()

  })
  let _ = effect(fn() {
    let _ = f6.get()

  })
  let _ = effect(fn() {
    let _ = f7.get()

  })
  let _ = effect(fn() {
    let _ = f8.get()

  })
  let _ = effect(fn() {
    let _ = f9.get()

  })
  let _ = effect(fn() {
    let _ = f10.get()

  })
  b.bench(fn() {
    for i = 0; i < 100; i = i + 1 {
      // Only update field1 - only 1 effect should run
      f1.set(i)
    }
  })
}

// =============================================================================
// Approach 2: Lens-based Store
// =============================================================================

///|
test "lens: 2 fields - setup and read (1000 ops)" (b : @bench.T) {
  let store : LensStore[BenchRecord] = LensStore::new({ count: 0, name: "test" })
  let count_lens : Lens[BenchRecord, Int] = Lens::new(fn(s) { s.count }, fn(
    s,
    v,
  ) {
    { count: v, name: s.name }
  })
  let name_lens : Lens[BenchRecord, String] = Lens::new(fn(s) { s.name }, fn(
    s,
    v,
  ) {
    { count: s.count, name: v }
  })
  let (count_sig, _) = store.focus(count_lens)
  let (name_sig, _) = store.focus(name_lens)
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = count_sig.get()
      let _ = name_sig.get()

    }
  })
}

///|
test "lens: 2 fields - update via setter (100 ops)" (b : @bench.T) {
  let store : LensStore[BenchRecord] = LensStore::new({ count: 0, name: "test" })
  let count_lens : Lens[BenchRecord, Int] = Lens::new(fn(s) { s.count }, fn(
    s,
    v,
  ) {
    { count: v, name: s.name }
  })
  let (_, set_count) = store.focus(count_lens)
  b.bench(fn() {
    for i = 0; i < 100; i = i + 1 {
      set_count(i)
    }
  })
}

// =============================================================================
// Signal-based approach
// =============================================================================

///|
test "signal: create and read (1000 ops)" (b : @bench.T) {
  let counter = signal(0)
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = counter.get()

    }
  })
}

///|
test "signal: update (100 ops)" (b : @bench.T) {
  let counter = signal(0)
  b.bench(fn() {
    for i = 0; i < 100; i = i + 1 {
      counter.set(i)
    }
  })
}

///|
test "signal: memo (1000 reads)" (b : @bench.T) {
  let counter = signal(0)
  let doubled = memo(fn() { counter.get() * 2 })
  b.bench(fn() {
    for i = 0; i < 1000; i = i + 1 {
      let _ = doubled()

    }
  })
}

///|
test "signal: 10 signals, update 1 with 10 effects (100 ops)" (b : @bench.T) {
  let signals : Array[Signal[Int]] = []
  for i = 0; i < 10; i = i + 1 {
    signals.push(signal(i))
  }

  // Each signal has its own effect
  for s in signals {
    let _ = effect(fn() {
      let _ = s.get()

    })

  }
  b.bench(fn() {
    for i = 0; i < 100; i = i + 1 {
      // Only update first signal - only 1 effect runs
      signals[0].set(i)
    }
  })
}

// =============================================================================
// Setup cost comparison
// =============================================================================

///|
test "setup: create 100 signals" (b : @bench.T) {
  b.bench(fn() {
    let signals : Array[Signal[Int]] = []
    for i = 0; i < 100; i = i + 1 {
      signals.push(signal(i))
    }
  })
}

///|
test "setup: create SplitStore2 (2 fields)" (b : @bench.T) {
  b.bench(fn() {
    let _ = SplitStore2::new(0, "test")

  })
}

///|
test "setup: create SplitStore3 (3 fields)" (b : @bench.T) {
  b.bench(fn() {
    let _ = SplitStore3::new(0, "test", true)

  })
}