extends Control

var selected_world := 0

@export var has_speedrun_stuff := false
@export var has_challenge_stuff := false
@export var has_disco_stuff := false

@export var world_offset := 0

@export var num_of_worlds := 7

signal world_selected
signal cancelled
var active := false

var cursor_index := 0

var starting_value := -1

const NUMBER_Y := [
	"Overworld",
	"Underground",
	"Castle",
	"Snow",
	"Space",
	"Volcano"
]

func _ready() -> void:
	for i in %SlotContainer.get_children():
		i.focus_entered.connect(slot_focused.bind(i.get_index()))
	for i in get_tree().get_nodes_in_group("Particles"):
		start_particle(i)

func start_particle(particle: GPUParticles2D) -> void:
	await get_tree().create_timer(randf_range(0, 5)).timeout
	particle.emitting = true

func _process(_delta: float) -> void:
	if active:
		handle_input()
		Global.world_num = selected_world + 1 + world_offset

func open() -> void:
	if starting_value == -1:
		starting_value = Global.world_num
	selected_world = Global.world_num - 1 - world_offset
	if has_speedrun_stuff and not Global.current_game_mode in [Global.GameMode.MARATHON, Global.GameMode.MARATHON_PRACTICE]: Global.current_game_mode = Global.GameMode.MARATHON
	setup_visuals()
	show()
	await get_tree().process_frame
	if Global.current_game_mode != Global.GameMode.CAMPAIGN:
		selected_world = clamp(selected_world, 0, 7)
	$%SlotContainer.get_child(selected_world).grab_focus()
	active = true

func setup_visuals() -> void:
	var idx := 0
	%Slot1.focus_neighbor_left = %Slot8.get_path()
	%Slot8.focus_neighbor_right = %Slot1.get_path()
	if Global.current_campaign == "SMBLL" && (Global.game_beaten or Global.debug_mode) && Global.current_game_mode == Global.GameMode.CAMPAIGN:
		%Slot1.focus_neighbor_left = %Slot13.get_path()
		%Slot8.focus_neighbor_right = %Slot9.get_path()
	for i in %SlotContainer.get_children():
		if idx >= 8:
			i.visible = Global.current_campaign == "SMBLL" && (Global.game_beaten or Global.debug_mode) && Global.current_game_mode == Global.GameMode.CAMPAIGN
		if i.visible == false:
			idx += 1
			continue
		var level_theme = Global.LEVEL_THEMES[Global.current_campaign][idx + world_offset]
		var world_visited = (SaveManager.visited_levels.substr((idx + world_offset) * 4, 4) != "0000" or Global.debug_mode or idx == 0)
		if world_visited == false:
			level_theme = "Mystery"
		var resource_getter = ResourceGetter.new() #Is it safe to be making a new one of these per icon?
		i.get_node("Icon").region_rect = CustomLevelContainer.THEME_RECTS[level_theme]
		i.get_node("Icon").texture = resource_getter.get_resource(CustomLevelContainer.ICON_TEXTURES[0 if (idx <= 3 or idx >= 8) and Global.current_campaign != "SMBANN" else 1])
		i.get_node("Icon/Number").position.y = 10 if has_challenge_stuff else 17
		i.get_node("Icon/Number").region_rect.position.y = clamp(NUMBER_Y.find(level_theme) * 12, 0, 9999)
		i.get_node("Icon/Number").region_rect.position.x = (idx + world_offset) * 12
		setup_challenge_mode_bits(i.get_node("Icon/RedCoins"), i.get_node("Icon/Egg"), i.get_node("Icon/Score"), i.get_node("Icon/RedCoins/Full"), i.get_node("Icon/Egg/Full"), i.get_node("Icon/Score/Full"), idx + world_offset)
		setup_marathon_bits(i.get_node("Icon/Medal"), i.get_node("Icon/Medal/Full"), idx + world_offset)
		setup_disco_bits(i.get_node("Icon/Medal"), i.get_node("Icon/Medal/Full"), i.get_node("Icon/Medal/Full/SRankParticles"), i.get_node("Icon/Medal/Full/PRankParticles"), idx + world_offset)
		idx += 1

func setup_challenge_mode_bits(red_coins_outline: TextureRect, egg_outline: TextureRect, score_outline: TextureRect, red_coins: NinePatchRect, egg: NinePatchRect, score: NinePatchRect, world_num := 1) -> void:
	if has_challenge_stuff == false: return
	var red_coins_collected = []
	var eggs_collected = []
	var scores_collected = []
	for level in 4:
		for i in 5:
			red_coins_collected.append(ChallengeModeHandler.is_coin_collected(i, ChallengeModeHandler.red_coins_collected[world_num][level]))
		eggs_collected.append(ChallengeModeHandler.is_coin_collected(ChallengeModeHandler.CoinValues.YOSHI_EGG, ChallengeModeHandler.red_coins_collected[world_num][level]))
		scores_collected.append(ChallengeModeHandler.top_challenge_scores[world_num][level] >= ChallengeModeHandler.CHALLENGE_TARGETS[Global.current_campaign][world_num][level])
	for i in [red_coins_outline, egg_outline, score_outline]:
		i.visible = true
	red_coins.visible = not red_coins_collected.has(false)
	egg.visible = not eggs_collected.has(false)
	var egg_frame = 10 * (world_num % 4)
	egg.region_rect = Rect2(egg_frame, 0, 10, 10)
	score.visible = not scores_collected.has(false)

func setup_marathon_bits(medal_outline: TextureRect, medal: NinePatchRect, world_num := 1) -> void:
	if has_speedrun_stuff == false: return
	var saved_medal_ids = []
	for i in 4:
		var best_warpless_time = SpeedrunHandler.best_level_warpless_times[world_num][i]
		var best_any_time = SpeedrunHandler.best_level_any_times.get(str(world_num + 1) + "-" + str(i + 1), -1)
		var gold_warpless_time = SpeedrunHandler.LEVEL_GOLD_WARPLESS_TIMES[Global.current_campaign][world_num][i]
		var gold_any_time := -1.0
		if SpeedrunHandler.LEVEL_GOLD_ANY_TIMES[Global.current_campaign].has(str(world_num + 1) + "-" + str(i + 1)):
			gold_any_time = SpeedrunHandler.LEVEL_GOLD_ANY_TIMES[Global.current_campaign][str(world_num + 1) + "-" + str(i + 1)]
		var medal_id = -1
		for o in SpeedrunHandler.MEDAL_CONVERSIONS:
			var target_time = gold_warpless_time * SpeedrunHandler.MEDAL_CONVERSIONS[o]
			medal_id += 1 if SpeedrunHandler.met_target_time(best_warpless_time, target_time) else 0
		saved_medal_ids.append(medal_id)
		if gold_any_time != -1:
			medal_id = -1
			for o in SpeedrunHandler.MEDAL_CONVERSIONS:
				var target_time = gold_any_time * SpeedrunHandler.MEDAL_CONVERSIONS[o]
				medal_id += 1 if SpeedrunHandler.met_target_time(best_any_time, target_time) else 0
			saved_medal_ids.append(medal_id)
	medal_outline.visible = true
	medal.visible = saved_medal_ids.min() >= 0
	var medal_rect_x = saved_medal_ids.min() * 10
	medal.region_rect = Rect2(10 + medal_rect_x, 10, 10, 10)

func setup_disco_bits(medal_outline: TextureRect, medal: NinePatchRect, s_rank_pfx: GPUParticles2D, p_rank_pfx: GPUParticles2D, world_num := 1) -> void:
	if has_disco_stuff == false: return
	var saved_rank_ids = []
	var lowest_rank = -1
	for i in 4:
		saved_rank_ids.append(DiscoLevel.level_ranks[SaveManager.get_level_idx(world_num + 1, i + 1)])
		for rank in DiscoLevel.RANK_IDs.size():
			if DiscoLevel.RANK_IDs[rank] == saved_rank_ids[i] and (lowest_rank > rank + 1 or lowest_rank < 0):
				lowest_rank = rank + 1
	medal_outline.visible = true
	medal.visible = lowest_rank != -1
	var medal_rect_x = lowest_rank * 10
	medal.region_rect = Rect2(medal_rect_x, 20, 10, 10)
	s_rank_pfx.visible = lowest_rank == 6
	p_rank_pfx.visible = lowest_rank == 7

func handle_input() -> void:
	if Input.is_action_just_pressed("ui_accept"):
		if SaveManager.visited_levels.substr((selected_world + world_offset) * 4, 4) == "0000" and not Global.debug_mode and selected_world != 0:
			AudioManager.play_sfx("bump")
		else:
			select_world()
	elif Input.is_action_just_pressed("ui_back"):
		close()
		cleanup()
		cancelled.emit()
		return

func slot_focused(idx := 0) -> void:
	selected_world = idx
	if Settings.file.audio.extra_sfx == 1:
		AudioManager.play_global_sfx("menu_move")

func select_world() -> void:
	if owner is Level:
		owner.world_id = selected_world + world_offset + 1
	Global.world_num = selected_world + world_offset + 1
	world_selected.emit()
	close()

func cleanup() -> void:
	await get_tree().physics_frame
	Global.world_num = starting_value
	starting_value = -1
	Global.world_num = clamp(Global.world_num, 1, Level.get_world_count())
	if owner is Level:
		owner.world_id = clamp(owner.world_id, 1, Level.get_world_count())

func close() -> void:
	active = false
	Global.world_num = 1
	hide()
