extends SceneTree

const BattleStateScript := preload("res://scripts/core/battle_state.gd")
const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd")


func _init() -> void:
	var failures: Array[String] = []
	_check_battle_visual_data(failures)
	_check_scene_texture_loading(failures)
	_check_hud_focus_text(failures)
	_check_attack_motion_signal(failures)

	if failures.is_empty():
		print("visual assets smoke ok")
		quit(0)
		return

	for failure in failures:
		push_error(failure)
	quit(1)


func _check_battle_visual_data(failures: Array[String]) -> void:
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("could not load opening battle")
		return

	var briefing := state.get_briefing()
	if (briefing.get("camp_dialogue", []) as Array).is_empty():
		failures.append("001 briefing should expose camp dialogue")
	var conversations: Array = briefing.get("camp_conversations", [])
	if conversations.size() < 3:
		failures.append("001 briefing should expose at least three camp conversations")
	else:
		_check_camp_conversation(failures, conversations[0], "cao_cao_strategy", "officer", "cao_cao")
		_check_camp_conversation(failures, conversations[1], "xiahou_dun_vanguard", "officer", "xiahou_dun")
		_check_camp_conversation(failures, conversations[2], "northern_woods_cache", "topic", "")
	var merchant := state.get_shop_merchant()
	if merchant.is_empty() or (merchant.get("lines", []) as Array).is_empty():
		failures.append("001 shop should expose merchant lines")

	_check_image_path(failures, state.get_map_background_path(), "001 map background")
	for unit_id in ["cao_cao", "xiahou_dun", "yellow_turban_1", "yellow_turban_2", "yellow_turban_3"]:
		var unit := state.get_unit(unit_id)
		if unit.is_empty():
			failures.append("missing expected unit: %s" % unit_id)
			continue
		_check_image_path(failures, str(unit.get("sprite", "")), "unit %s sprite" % unit_id)

	for item_id in ["bronze_sword", "training_spear", "short_bow", "hand_axe", "iron_armor", "panacea"]:
		var item := state.get_item_def(item_id)
		if item.is_empty():
			failures.append("missing expected item: %s" % item_id)
			continue
		_check_image_path(failures, str(item.get("icon", "")), "item %s icon" % item_id)


func _check_scene_texture_loading(failures: Array[String]) -> void:
	var scene = BattleSceneScript.new()
	if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("could not load battle scene state")
		scene.free()
		return
	if scene._current_battle_background_texture() == null:
		failures.append("battle scene should load a battlefield background texture")
	var archer: Dictionary = scene.state.get_unit("yellow_turban_3")
	if scene._load_art_texture(str(archer.get("sprite", ""))) == null:
		failures.append("battle scene should load archer sprite texture")
	var panacea: Dictionary = scene.state.get_item_def("panacea")
	if scene._load_art_texture(str(panacea.get("icon", ""))) == null:
		failures.append("battle scene should load panacea icon texture")
	var conversations: Array = scene.state.get_briefing().get("camp_conversations", [])
	if not conversations.is_empty():
		var first_lines: Array = (conversations[0] as Dictionary).get("lines", [])
		if not first_lines.is_empty():
			var first_line: Dictionary = first_lines[0]
			if scene._load_art_texture(str(first_line.get("portrait", ""))) == null:
				failures.append("battle scene should load camp conversation speaker portrait")
	var scene_conversations: Array = scene._camp_conversation_entries()
	if scene_conversations.size() < 3:
		failures.append("battle scene should expose camp conversation entries")
	var strategy := scene._camp_conversation_by_id("cao_cao_strategy")
	if strategy.is_empty():
		failures.append("battle scene should find Cao Cao camp conversation by id")
	elif not scene._format_camp_conversation_button_text(strategy).contains("Cao Cao"):
		failures.append("camp conversation button text should include label/speaker")
	scene.free()


func _check_hud_focus_text(failures: Array[String]) -> void:
	var scene = BattleSceneScript.new()
	if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("could not load battle scene state for HUD focus text")
		scene.free()
		return
	var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
	var cao_text := scene._format_unit_focus_text(cao_cao, "Selected")
	if not cao_text.contains("Command tactics"):
		failures.append("Cao Cao HUD focus should describe command role: %s" % cao_text)
	if not cao_text.contains("Tile 2,4"):
		failures.append("Cao Cao HUD focus should include current tile: %s" % cao_text)
	if not cao_text.contains("Cost 1"):
		failures.append("Cao Cao HUD focus should include terrain move cost: %s" % cao_text)
	if not cao_text.contains("DEF +0"):
		failures.append("Cao Cao HUD focus should include terrain defense: %s" % cao_text)
	var xiahou_dun: Dictionary = scene.state.get_unit("xiahou_dun")
	xiahou_dun["pos"] = Vector2i(3, 1)
	var forest_text := scene._unit_current_terrain_text(xiahou_dun)
	if not forest_text.contains("Forest") or not forest_text.contains("Cost 3"):
		failures.append("Cavalry movement should use mounted move_type cost on forest: %s" % forest_text)
	var archer: Dictionary = scene.state.get_unit("yellow_turban_3")
	var archer_text := scene._format_unit_focus_text(archer, "Hover")
	if not archer_text.contains("Ranged pressure"):
		failures.append("Archer HUD focus should describe ranged role: %s" % archer_text)
	if not scene._unit_current_terrain_text(archer).contains("Plain"):
		failures.append("Archer terrain text should reflect its current plain tile")
	scene.free()


func _check_attack_motion_signal(failures: Array[String]) -> void:
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("could not load battle for attack motion")
		return

	var attacker := state.get_unit("cao_cao")
	var target := state.get_unit("yellow_turban_1")
	if attacker.is_empty() or target.is_empty():
		failures.append("missing attacker or target for attack motion")
		return
	attacker["pos"] = Vector2i(6, 2)
	target["pos"] = Vector2i(7, 2)

	var motions: Array[String] = []
	state.unit_action_motion_requested.connect(func(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String) -> void:
		motions.append("%s|%s|%d,%d|%d,%d" % [unit_id, action_kind, from_cell.x, from_cell.y, to_cell.x, to_cell.y])
	)
	if not state.select_unit("cao_cao"):
		failures.append("could not select Cao Cao for attack motion")
		return
	if not state.try_attack_selected("yellow_turban_1"):
		failures.append("Cao Cao should be able to attack adjacent target")
		return
	if not motions.has("cao_cao|attack|6,2|7,2"):
		failures.append("attack motion signal missing expected Cao Cao attack: %s" % str(motions))


func _check_camp_conversation(failures: Array[String], conversation: Dictionary, expected_id: String, expected_group: String, expected_officer_id: String) -> void:
	if str(conversation.get("id", "")) != expected_id:
		failures.append("camp conversation id mismatch: expected %s got %s" % [expected_id, str(conversation.get("id", ""))])
	if str(conversation.get("group", "")) != expected_group:
		failures.append("camp conversation group mismatch for %s" % expected_id)
	if str(conversation.get("officer_id", "")) != expected_officer_id:
		failures.append("camp conversation officer mismatch for %s" % expected_id)
	if str(conversation.get("label", "")).is_empty():
		failures.append("camp conversation label missing for %s" % expected_id)
	var lines: Array = conversation.get("lines", [])
	if lines.is_empty():
		failures.append("camp conversation lines missing for %s" % expected_id)
		return
	var first_line: Dictionary = lines[0]
	if str(first_line.get("text", "")).is_empty():
		failures.append("camp conversation first text missing for %s" % expected_id)
	if expected_group == "officer" and str(first_line.get("portrait", "")).is_empty():
		failures.append("camp conversation officer portrait missing for %s" % expected_id)


func _check_image_path(failures: Array[String], path: String, context: String) -> void:
	if path.is_empty():
		failures.append("%s has an empty image path" % context)
		return
	if not path.begins_with("res://"):
		failures.append("%s must use a res:// path: %s" % [context, path])
		return
	if not FileAccess.file_exists(path):
		failures.append("%s references missing image: %s" % [context, path])
		return
	var image := Image.new()
	var err := image.load(path)
	if err != OK:
		failures.append("%s references unreadable image: %s" % [context, path])
		return
	if image.get_width() < 512 or image.get_height() < 512:
		failures.append("%s image should be at least 512x512: %s (%dx%d)" % [context, path, image.get_width(), image.get_height()])
