class_name PauseMenu
extends Control

const INPUT_OWNER: StringName = &"game_menu"
const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
const COMPACT_HEIGHT_THRESHOLD: float = 560.0
const VISIBILITY_FADE_DURATION: float = 0.55
const PlayerType = preload("res://player/player.gd")
const SaveManagerType = preload("res://save/player_save_manager.gd")
const SettingsManagerType = preload(
	"res://settings/player_settings_manager.gd"
)
const SettingsPanelType = preload("res://ui/settings_panel.gd")
const BubbleConfirmationPageType = preload(
	"res://ui/components/bubble_menu/bubble_confirmation_page.gd"
)
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
const NetworkSessionType = preload("res://network/network_session.gd")
const SavedServerStoreType = preload("res://network/saved_server_store.gd")
const JoinGamePageType = preload("res://ui/network/join_game_page.gd")

signal return_to_title_requested
signal reset_progress_requested
signal quit_requested
signal menu_visibility_changed(is_open: bool)
signal join_game_requested(endpoint: String)

enum ConfirmationAction {
	NONE,
	RETURN_TO_TITLE,
	RESET_PROGRESS,
	QUIT_ANYWAY,
	JOIN_ANOTHER,
}

enum CloseReason {
	USER_RETURN,
	BITE_STARTED,
	WATER_RECOVERY,
	RETURN_TO_TITLE,
	RESET_PROGRESS,
	QUIT,
	TEARDOWN,
}

@onready var _presentation_scale_root: Control = %PausePresentationScaleRoot
@onready var _root_page: SettingsBubblePage = %RootPage
@onready var _settings_panel: SettingsPanelType = %SettingsPanel
@onready var _transition_flurry: BubbleTransitionFlurry = (
	%TransitionBubbleLayer
)
@onready var _confirmation_page: BubbleConfirmationPageType = (
	%ConfirmationPage
)
@onready var _feedback: Label = %FeedbackLabel
@onready var _save_button: BubbleButton = %SaveButton
@onready var _join_game_page: JoinGamePageType = %JoinGamePage

var _player: PlayerType
var _save_manager: SaveManagerType
var _settings_manager: SettingsManagerType
var _fishing_spot: FishingSpotType
var _network_session: NetworkSessionType
var _saved_servers: SavedServerStoreType
var _prior_movement_enabled: bool = true
var _prior_camera_enabled: bool = true
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
var _control_snapshot_stored: bool = false
var _mouse_snapshot_stored: bool = false
var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE
var _action_in_progress: bool = false
var _root_transition_active: bool = false
var _root_transition_generation: int = 0
var _closing_menu: bool = false
var _pending_join_endpoint: String = ""


func _ready() -> void:
	%ResumeButton.pressed.connect(resume)
	_save_button.pressed.connect(_save_now)
	%SettingsButton.pressed.connect(_open_settings)
	%JoinGameButton.pressed.connect(_open_join_game)
	%ReturnToTitleButton.pressed.connect(_confirm_return_to_title)
	%ResetProgressButton.pressed.connect(_confirm_reset_progress)
	%QuitButton.pressed.connect(_request_quit)
	_confirmation_page.confirmed.connect(_accept_confirmation)
	_confirmation_page.cancelled.connect(_close_confirmation)
	_settings_panel.applied.connect(_on_settings_applied)
	_settings_panel.closed.connect(_on_settings_closed)
	_settings_panel.closing.connect(_on_settings_closing)
	_settings_panel.navigation_transition_started.connect(
		_emit_transition_flurry
	)
	_join_game_page.join_requested.connect(_on_join_game_requested)
	_join_game_page.back_requested.connect(_close_join_game)
	_confirmation_page.hide_page()
	resized.connect(_update_responsive_pause_stage)
	call_deferred("_update_responsive_pause_stage")


func setup(
	player: PlayerType,
	save_manager: SaveManagerType,
	settings_manager: SettingsManagerType,
	fishing_spot: FishingSpotType,
	network_session: NetworkSessionType,
	saved_servers: SavedServerStoreType,
	server_trust: ServerTrustStore,
	discovery: DiscoveryClient,
) -> void:
	_player = player
	_save_manager = save_manager
	_settings_manager = settings_manager
	_fishing_spot = fishing_spot
	_network_session = network_session
	_saved_servers = saved_servers
	_join_game_page.setup(
		network_session, saved_servers, true, server_trust, discovery
	)
	if not _fishing_spot.bite_activated.is_connected(_on_bite_activated):
		_fishing_spot.bite_activated.connect(_on_bite_activated)


func open_menu() -> void:
	if visible or _player == null:
		return
	_prior_movement_enabled = _player.is_movement_enabled()
	_prior_camera_enabled = _player.is_camera_input_enabled()
	_prior_mouse_mode = Input.mouse_mode
	_control_snapshot_stored = true
	_mouse_snapshot_stored = true
	_player.set_movement_enabled(false)
	_player.set_camera_input_enabled(false)
	_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
	Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	_feedback.text = ""
	_settings_panel.hide()
	_join_game_page.close_page()
	_confirmation_page.hide_page()
	_confirmation_action = ConfirmationAction.NONE
	_action_in_progress = false
	_root_page.hide_page()
	show()
	_update_responsive_pause_stage()
	_begin_root_entry(false)
	menu_visibility_changed.emit(true)


func resume() -> void:
	if (
		not visible
		or _action_in_progress
		or _root_transition_active
		or _settings_panel.visible
		or _confirmation_action != ConfirmationAction.NONE
	):
		return
	_begin_root_exit(_finish_user_close)


func close_for_title_transition() -> void:
	close_menu(CloseReason.RETURN_TO_TITLE, false)


func close_for_water_recovery() -> void:
	close_menu(CloseReason.WATER_RECOVERY, false)


func close_menu(
	reason: CloseReason,
	restore_controls: bool = true,
) -> void:
	if not visible:
		return
	_finish_close(reason, restore_controls)


func handle_escape() -> bool:
	if not visible:
		return false
	if _root_transition_active or _confirmation_page.is_transitioning():
		return true
	if _confirmation_page.visible:
		_close_confirmation()
	elif _join_game_page.visible:
		_close_join_game()
	elif _settings_panel.visible:
		_settings_panel.handle_back()
	else:
		resume()
	return true


func _save_now() -> void:
	if _action_in_progress or _root_transition_active:
		return
	_action_in_progress = true
	_save_button.disabled = true
	if _save_manager.save_now():
		_feedback.text = "game saved."
	else:
		_feedback.text = "save failed. previous save was preserved."
	_action_in_progress = false
	call_deferred("_reenable_save_button")


func _reenable_save_button() -> void:
	_save_button.disabled = false


func _open_settings() -> void:
	if (
		_action_in_progress
		or _root_transition_active
		or _confirmation_action != ConfirmationAction.NONE
	):
		return
	_begin_root_exit(_finish_open_settings)


func _open_join_game() -> void:
	if (
		_action_in_progress
		or _root_transition_active
		or _confirmation_action != ConfirmationAction.NONE
	):
		return
	_begin_root_exit(_finish_open_join_game)


func _finish_open_join_game() -> void:
	_join_game_page.open_page()


func _close_join_game() -> void:
	_join_game_page.close_page()
	_begin_root_entry(true)


func _on_join_game_requested(endpoint: String) -> void:
	if _action_in_progress or _root_transition_active:
		return
	_pending_join_endpoint = endpoint
	_open_confirmation(
		ConfirmationAction.JOIN_ANOTHER,
		"join another game?",
		(
			"your progression will be saved first. "
			+ "current players will be disconnected if you are hosting."
		),
		"save and join",
		false
	)


func report_network_error(message: String) -> void:
	_feedback.text = message
	if _join_game_page.visible:
		_join_game_page.set_status(message)


func _finish_open_settings() -> void:
	_settings_panel.open_panel(
		_settings_manager,
		SettingsPanelType.PresentationMode.GAMEPLAY_MODAL,
		true
	)


func _on_settings_applied() -> void:
	if _closing_menu:
		return
	_feedback.text = "settings saved."


func _on_settings_closed() -> void:
	pass


func _on_settings_closing() -> void:
	if _closing_menu:
		return
	await get_tree().create_timer(
		UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY
	).timeout
	if _settings_panel.visible:
		_begin_root_entry(true)


func _confirm_return_to_title() -> void:
	_open_confirmation(
		ConfirmationAction.RETURN_TO_TITLE,
		"return to title",
		"unsaved progress will be saved first.",
		"save and return",
		false
	)


func _confirm_reset_progress() -> void:
	_open_confirmation(
		ConfirmationAction.RESET_PROGRESS,
		"reset all progression?",
		(
			"this permanently deletes your fish, discoveries, "
			+ "favorites, and wallet balance.\n\n"
			+ "your settings will be preserved."
		),
		"delete all progress",
		true
	)


func _request_quit() -> void:
	if _action_in_progress or _root_transition_active:
		return
	_action_in_progress = true
	var settings_saved: bool = _settings_manager.save_if_dirty()
	var progression_saved: bool = _save_manager.save_if_dirty()
	_action_in_progress = false
	if settings_saved and progression_saved:
		quit_requested.emit()
		return
	_open_confirmation(
		ConfirmationAction.QUIT_ANYWAY,
		"save failed",
		"some progress or settings could not be saved. quit anyway?",
		"quit anyway",
		true
	)


func _open_confirmation(
	action: ConfirmationAction,
	title: String,
	message: String,
	confirm_text: String,
	dangerous: bool,
) -> void:
	if _action_in_progress or _root_transition_active:
		return
	_confirmation_action = action
	_confirmation_page.configure(
		"%s\n\n%s" % [title, message],
		confirm_text,
		"cancel",
		(
			BubbleConfirmationPageType.InitialFocus.CANCEL
			if dangerous
			else BubbleConfirmationPageType.InitialFocus.CONFIRM
		)
	)
	_begin_root_exit(_show_confirmation)


func _show_confirmation() -> void:
	_confirmation_page.transition_in(
		0.0,
		_finish_confirmation_open
	)


func _finish_confirmation_open() -> void:
	pass


func _close_confirmation() -> void:
	if _root_transition_active or _confirmation_page.is_transitioning():
		return
	_confirmation_action = ConfirmationAction.NONE
	_emit_transition_flurry()
	_confirmation_page.transition_out(
		0.0,
		_finish_confirmation_cancel
	)


func _finish_confirmation_cancel() -> void:
	_begin_root_entry(true)


func _accept_confirmation() -> void:
	if (
		_action_in_progress
		or _root_transition_active
		or _confirmation_page.is_transitioning()
	):
		return
	var action: ConfirmationAction = _confirmation_action
	_confirmation_action = ConfirmationAction.NONE
	_action_in_progress = true
	_confirmation_page.transition_out(
		VISIBILITY_FADE_DURATION,
		_finish_confirmation_accept.bind(action)
	)


func _finish_confirmation_accept(action: ConfirmationAction) -> void:
	match action:
		ConfirmationAction.RETURN_TO_TITLE:
			if _save_manager.save_now():
				return_to_title_requested.emit()
			else:
				_feedback.text = "save failed. previous save was preserved."
				_action_in_progress = false
				_begin_root_entry(false)
				return
		ConfirmationAction.RESET_PROGRESS:
			reset_progress_requested.emit()
		ConfirmationAction.QUIT_ANYWAY:
			quit_requested.emit()
		ConfirmationAction.JOIN_ANOTHER:
			_join_game_page.close_page()
			join_game_requested.emit(_pending_join_endpoint)
			_pending_join_endpoint = ""
		_:
			_action_in_progress = false
			_begin_root_entry(false)
			return
	_action_in_progress = false


func report_reset_failure() -> void:
	_action_in_progress = false
	_confirmation_action = ConfirmationAction.NONE
	_feedback.text = "reset failed. your progression was preserved."
	if visible:
		_begin_root_entry(false)


func _begin_root_entry(flurry_already_emitted: bool) -> void:
	if _root_transition_active:
		return
	_root_transition_generation += 1
	_root_transition_active = true
	if not flurry_already_emitted:
		_emit_transition_flurry()
	var generation: int = _root_transition_generation
	_root_page.transition_in(
		true,
		_finish_root_entry.bind(generation),
		0.0
	)


func _finish_root_entry(generation: int) -> void:
	if generation != _root_transition_generation or not visible:
		return
	_root_transition_active = false


func _begin_root_exit(completed: Callable) -> void:
	if _root_transition_active or not _root_page.visible:
		return
	_root_transition_generation += 1
	_root_transition_active = true
	_emit_transition_flurry()
	var generation: int = _root_transition_generation
	_root_page.transition_out(
		_finish_root_exit.bind(generation),
		0.0
	)
	await get_tree().create_timer(
		UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY
	).timeout
	if generation != _root_transition_generation or not visible:
		return
	completed.call()


func _finish_root_exit(
	generation: int,
) -> void:
	if generation != _root_transition_generation or not visible:
		return
	_root_transition_active = false


func _finish_user_close() -> void:
	_finish_close(CloseReason.USER_RETURN, true)


func _finish_close(reason: CloseReason, restore_controls: bool) -> void:
	_closing_menu = true
	if _settings_panel.visible:
		_settings_panel.close_panel(true)
	_closing_menu = false
	_root_transition_generation += 1
	_root_transition_active = false
	_settings_panel.hide()
	_join_game_page.close_page()
	_root_page.hide_page()
	_confirmation_page.hide_page()
	_confirmation_action = ConfirmationAction.NONE
	_action_in_progress = false
	_transition_flurry.clear_flurries()
	hide()
	if _fishing_spot != null and is_instance_valid(_fishing_spot):
		_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false)
	var current_viewport: Viewport = get_viewport()
	if current_viewport != null:
		current_viewport.gui_release_focus()
	if restore_controls:
		_restore_controls()
	else:
		_control_snapshot_stored = false
	_apply_mouse_close_policy(reason)
	menu_visibility_changed.emit(false)


func _emit_transition_flurry() -> void:
	_transition_flurry.emit_flurry()


func _update_responsive_pause_stage() -> void:
	if not is_node_ready():
		return
	_presentation_scale_root.size = PAUSE_DESKTOP_REFERENCE_SIZE
	_presentation_scale_root.scale = Vector2.ONE
	_presentation_scale_root.position = Vector2.ZERO


func _restore_controls() -> void:
	if not _control_snapshot_stored:
		return
	if _player != null and is_instance_valid(_player):
		_player.set_movement_enabled(_prior_movement_enabled)
		_player.set_camera_input_enabled(_prior_camera_enabled)
	_control_snapshot_stored = false


func _apply_mouse_close_policy(reason: CloseReason) -> void:
	if not _mouse_snapshot_stored:
		return
	match reason:
		CloseReason.USER_RETURN, CloseReason.BITE_STARTED:
			Input.mouse_mode = _prior_mouse_mode
		CloseReason.WATER_RECOVERY:
			# Recovery owns local input until it finishes. Keep the current
			# visible gameplay cursor policy without restoring stale state.
			pass
		CloseReason.RETURN_TO_TITLE, CloseReason.RESET_PROGRESS:
			Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
		CloseReason.QUIT, CloseReason.TEARDOWN:
			pass
	_mouse_snapshot_stored = false


func _on_bite_activated() -> void:
	if visible:
		close_menu(CloseReason.BITE_STARTED)


func _exit_tree() -> void:
	if visible:
		close_menu(CloseReason.TEARDOWN)
