//! Per-frame dispatch hot path benches.
//!
//! The renderer rebuilds the widget tree on every frame, then hit-tests
//! pointer events against the resulting flat `Vec<LaidOutWidget>` and
//! looks up handlers by `flat_idx`. Both calls are O(N) in the slice
//! length. Run with `cargo bench --bench lookup`.

use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion };

use ltk::test_support::{ find_handlers, find_widget, find_widget_at, LaidOutWidget, WidgetHandlers };
use ltk::{ Point, Rect };

// ── Fixture builders ──────────────────────────────────────────────────────────

/// Build N widgets laid out in a vertical strip. Each widget is 100×30 with a
/// 1px gap, so `find_widget_at(x=50, y=k*31+15)` lands inside widget `k`. Half
/// of them carry a Button handler so `find_handlers` exercises the
/// match-and-clone path on roughly every other lookup.
fn build_widgets( n: usize ) -> Vec<LaidOutWidget<()>>
{
	( 0 .. n ).map( |i|
	{
		let rect = Rect
		{
			x:      0.0,
			y:      ( i as f32 ) * 31.0,
			width:  100.0,
			height: 30.0,
		};
		let handlers = if i % 2 == 0
		{
			WidgetHandlers::Button { on_press: Some( () ), on_long_press: None, on_drag_start: None, on_escape: None, repeating: false }
		} else {
			WidgetHandlers::None
		};
		LaidOutWidget
		{
			rect,
			flat_idx:           i,
			id:                 None,
			paint_rect:         rect,
			handlers,
			keyboard_focusable: true,
			cursor:             ltk::CursorShape::Default,
			tooltip:            None,
			accessible_label:   None,
			is_live_region:     false,
		}
	} ).collect()
}

// ── Benches ───────────────────────────────────────────────────────────────────

/// Pointer hit testing on a hot path. Three sub-benches:
///   - last:  hit the last widget (worst case under reverse iteration is the
///            *first* widget; the helper iterates from the end to honour
///            "topmost wins", so the last widget in slice order is the cheap
///            one and the first is the expensive one).
///   - first: hit the first widget — full-slice walk.
///   - miss:  point that lies outside every rect — also a full-slice walk
///            but never returns early.
fn bench_find_widget_at( c: &mut Criterion )
{
	let mut group = c.benchmark_group( "find_widget_at" );

	for &n in &[ 10usize, 100, 1000 ]
	{
		let widgets = build_widgets( n );

		// Hit on the last widget in slice order — `find_widget_at` walks in
		// reverse, so this returns on the first iteration.
		let last_y = ( n as f32 - 1.0 ) * 31.0 + 15.0;
		group.bench_with_input( BenchmarkId::new( "hit_last", n ), &n, |b, _|
		{
			b.iter( ||
			{
				let p = Point { x: 50.0, y: last_y };
				black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) )
			} );
		} );

		// Hit on the first widget — full reverse walk.
		group.bench_with_input( BenchmarkId::new( "hit_first", n ), &n, |b, _|
		{
			b.iter( ||
			{
				let p = Point { x: 50.0, y: 15.0 };
				black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) )
			} );
		} );

		// Miss — point sits to the right of every rect.
		group.bench_with_input( BenchmarkId::new( "miss", n ), &n, |b, _|
		{
			b.iter( ||
			{
				let p = Point { x: 9_999.0, y: 9_999.0 };
				black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) )
			} );
		} );
	}

	group.finish();
}

/// `flat_idx` lookup. Linear scan; we measure the worst case (last entry)
/// since it's what bounds latency on real frames.
fn bench_find_widget( c: &mut Criterion )
{
	let mut group = c.benchmark_group( "find_widget" );

	for &n in &[ 10usize, 100, 1000 ]
	{
		let widgets = build_widgets( n );
		let target  = n - 1;

		group.bench_with_input( BenchmarkId::new( "last", n ), &n, |b, _|
		{
			b.iter( ||
			{
				black_box( find_widget( black_box( &widgets ), black_box( target ) ) )
			} );
		} );
	}

	group.finish();
}

/// Same shape as `find_widget` but exercises the handler-cloning path on hit.
/// Useful as a separate measurement because the clone cost grows with the
/// handler payload, even though the scan itself does not.
fn bench_find_handlers( c: &mut Criterion )
{
	let mut group = c.benchmark_group( "find_handlers" );

	for &n in &[ 10usize, 100, 1000 ]
	{
		let widgets = build_widgets( n );
		let target  = n - 1;

		group.bench_with_input( BenchmarkId::new( "last", n ), &n, |b, _|
		{
			b.iter( ||
			{
				black_box( find_handlers( black_box( &widgets ), black_box( target ) ) )
			} );
		} );
	}

	group.finish();
}

criterion_group!( benches, bench_find_widget_at, bench_find_widget, bench_find_handlers );
criterion_main!( benches );
