extends SceneTree

## Asset-free contract, dependency and timing regression for M2 batch planning.

const PLANNER_SCRIPT := preload("res://src/render/m2/m2_build_batch_planner.gd")
const PLANNER_PATH := "res://src/render/m2/m2_build_batch_planner.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_static_batch_limit(failures)
	_verify_animated_batch_limit(failures)
	_verify_non_positive_limit_clamp(failures)
	_verify_remaining_count_cap(failures)
	_verify_exact_group_completion(failures)
	_verify_empty_and_past_end(failures)
	_verify_historical_negative_offset(failures)
	_verify_source_boundaries(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("M2_BUILD_BATCH_PLANNER: %s" % failure)
		quit(1)
		return
	print(
		"M2_BUILD_BATCH_PLANNER PASS cases=8 iterations=20000 elapsed_ms=%.3f"
		% elapsed_milliseconds
	)
	quit(0)


func _verify_static_batch_limit(failures: Array[String]) -> void:
	var plan := _plan(300, 0, false, 8, 128)
	_expect_plan(plan, 128, 128, 128, false, "static batch", failures)


func _verify_animated_batch_limit(failures: Array[String]) -> void:
	var plan := _plan(300, 0, true, 8, 128)
	_expect_plan(plan, 8, 8, 8, false, "animated batch", failures)


func _verify_non_positive_limit_clamp(failures: Array[String]) -> void:
	var animated_plan := _plan(4, 0, true, 0, 128)
	_expect_plan(animated_plan, 1, 1, 1, false, "zero animated limit", failures)
	var static_plan := _plan(4, 0, false, 8, -10)
	_expect_plan(static_plan, 1, 1, 1, false, "negative static limit", failures)


func _verify_remaining_count_cap(failures: Array[String]) -> void:
	var plan := _plan(140, 128, false, 8, 128)
	_expect_plan(plan, 128, 12, 0, true, "remaining cap", failures)


func _verify_exact_group_completion(failures: Array[String]) -> void:
	var plan := _plan(16, 8, true, 8, 128)
	_expect_plan(plan, 8, 8, 0, true, "exact completion", failures)


func _verify_empty_and_past_end(failures: Array[String]) -> void:
	var empty_plan := _plan(0, 0, false, 8, 128)
	_expect_plan(empty_plan, 128, 0, 0, true, "empty group", failures)
	var past_end_plan := _plan(10, 11, false, 8, 128)
	_expect_plan(past_end_plan, 128, 0, 0, true, "past end", failures)


func _verify_historical_negative_offset(failures: Array[String]) -> void:
	var plan := _plan(10, -2, false, 8, 4)
	_expect_plan(plan, 4, 4, 2, false, "negative offset", failures)


func _verify_source_boundaries(failures: Array[String]) -> void:
	var loader_source := _read_text(LOADER_PATH, failures)
	var planner_source := _read_text(PLANNER_PATH, failures)
	var build_loop_source := _source_between(
		loader_source,
		"func _process_m2_build_jobs()",
		"func _materialize_m2_group_batch(",
		failures
	)
	_expect_equal(
		build_loop_source.count("_m2_build_batch_planner.plan_batch("),
		1,
		"one loader planner adapter",
		failures
	)
	_expect_true(
		not build_loop_source.contains("var batch_size:"),
		"loader omits batch-size formula",
		failures
	)
	_expect_true(
		not build_loop_source.contains("mini(batch_size"),
		"loader omits batch-count formula",
		failures
	)
	for forbidden_dependency in [
		"WorkerThreadPool",
		"RenderingServer",
		"ResourceLoader",
		"MultiMesh",
		"Node",
		"Mutex",
	]:
		_expect_true(
			not planner_source.contains(forbidden_dependency),
			"planner omits %s" % forbidden_dependency,
			failures
		)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var planner := PLANNER_SCRIPT.new()
	var started_microseconds := Time.get_ticks_usec()
	for iteration in range(20000):
		var plan: Dictionary = planner.plan_batch(
			512,
			iteration % 512,
			iteration % 3 == 0,
			8,
			128
		)
		if int(plan.get("batch_count", -1)) < 0:
			failures.append("timing iteration returned negative batch count")
			break
	var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	_expect_true(elapsed_milliseconds < 1000.0, "20000 batch plans bounded", failures)
	return elapsed_milliseconds


func _plan(
		transform_count: int,
		current_offset: int,
		has_animated_prototype: bool,
		animated_batch_limit: int,
		static_batch_limit: int
) -> Dictionary:
	var planner := PLANNER_SCRIPT.new()
	return planner.plan_batch(
		transform_count,
		current_offset,
		has_animated_prototype,
		animated_batch_limit,
		static_batch_limit
	)


func _expect_plan(
		plan: Dictionary,
		expected_batch_size: int,
		expected_batch_count: int,
		expected_next_offset: int,
		expected_group_complete: bool,
		label: String,
		failures: Array[String]
) -> void:
	_expect_equal(
		int(plan.get("effective_batch_size", -1)),
		expected_batch_size,
		"%s effective batch size" % label,
		failures
	)
	_expect_equal(
		int(plan.get("batch_count", -1)),
		expected_batch_count,
		"%s batch count" % label,
		failures
	)
	_expect_equal(
		int(plan.get("next_offset", -1)),
		expected_next_offset,
		"%s next offset" % label,
		failures
	)
	_expect_true(
		bool(plan.get("group_complete", not expected_group_complete))
		== expected_group_complete,
		"%s group complete" % label,
		failures
	)


func _read_text(path: String, failures: Array[String]) -> String:
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		failures.append("cannot open %s" % path)
		return ""
	return file.get_as_text()


func _source_between(
		source: String,
		start_marker: String,
		end_marker: String,
		failures: Array[String]
) -> String:
	var start_offset := source.find(start_marker)
	var end_offset := source.find(end_marker, start_offset + start_marker.length())
	if start_offset < 0 or end_offset <= start_offset:
		failures.append("cannot isolate source between %s and %s" % [start_marker, end_marker])
		return ""
	return source.substr(start_offset, end_offset - start_offset)


func _expect_equal(
		actual_value: int,
		expected_value: int,
		label: String,
		failures: Array[String]
) -> void:
	if actual_value != expected_value:
		failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])


func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
	if not actual_value:
		failures.append("%s expected true" % label)
