@tool
extends Control

#region Constants

const Constants = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")

# TODO - Should be in a central location
const _camera_2d_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg")
const _camera_3d_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg")
const _pcam_host_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/phantom_camera_host.svg")
const _pcam_2D_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/phantom_camera_2d.svg")
const _pcam_3D_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/phantom_camera_3d.svg")

const _overlay_color_alpha: float = 0.3

#endregion

#region @onready

@onready var dead_zone_center_hbox: VBoxContainer = %DeadZoneCenterHBoxContainer
@onready var dead_zone_center_center_panel: Panel = %DeadZoneCenterCenterPanel
@onready var dead_zone_left_center_panel: Panel = %DeadZoneLeftCenterPanel
@onready var dead_zone_right_center_panel: Panel = %DeadZoneRightCenterPanel
@onready var target_point: Panel = %TargetPoint

@onready var aspect_ratio_container: AspectRatioContainer = %AspectRatioContainer
@onready var camera_viewport_panel: Panel = aspect_ratio_container.get_child(0)
@onready var _framed_viewfinder: Control = %FramedViewfinder
@onready var _dead_zone_h_box_container: Control = %DeadZoneHBoxContainer
@onready var sub_viewport: SubViewport = %SubViewport

@onready var _empty_state_control: Control = %EmptyStateControl
@onready var _empty_state_icon: Control = %EmptyStateIcon
@onready var _empty_state_text: RichTextLabel = %EmptyStateText
@onready var _add_node_button: Button = %AddNodeButton
@onready var _add_node_button_text: RichTextLabel = %AddNodeTypeText

@onready var _priority_override_button: Button = %PriorityOverrideButton
@onready var _priority_override_name_label: Label = %PriorityOverrideNameLabel

@onready var _camera_2d: Camera2D = %Camera2D

#endregion

#region Private Variables
var _no_open_scene_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg")
var _no_open_scene_string: String = "[b]2D[/b] or [b]3D[/b] scene open"

var _selected_camera: Node
var _active_pcam: Node

var _is_2d: bool

var root_node: Node

#endregion

#region Public variables

var pcam_host_group: Array[PhantomCameraHost]

var is_scene: bool

var viewfinder_visible: bool

var min_horizontal: float
var max_horizontal: float
var min_vertical: float
var max_vertical: float

var pcam_host: PhantomCameraHost

#endregion


#region Private Functions

func _ready() -> void:
	if not Engine.is_editor_hint():
		set_process(true)
		camera_viewport_panel.self_modulate.a = 0

	root_node = get_tree().current_scene

	if root_node is Node2D || root_node is Node3D:
		%SubViewportContainer.set_visible(false)
		if root_node is Node2D:
			_is_2d = true
		else:
			_is_2d = false

		_set_viewfinder(root_node, false)

	if Engine.is_editor_hint():
		# BUG - Both signals below are called whenever a noe is selected in the scenetree
		# Should only be triggered whenever a node is added or removed.
		get_tree().node_added.connect(_node_added_or_removed)
		get_tree().node_removed.connect(_node_added_or_removed)
	else:
		_empty_state_control.set_visible(false)

	_priority_override_button.set_visible(false)

	# Triggered when viewport size is changed in Project Settings
	ProjectSettings.settings_changed.connect(_settings_changed)


func _exit_tree() -> void:
	if Engine.is_editor_hint():
		if get_tree().node_added.is_connected(_node_added_or_removed):
			get_tree().node_added.disconnect(_node_added_or_removed)
			get_tree().node_removed.disconnect(_node_added_or_removed)

	if aspect_ratio_container.resized.is_connected(_resized):
		aspect_ratio_container.resized.disconnect(_resized)

	if _add_node_button.pressed.is_connected(visibility_check):
		_add_node_button.pressed.disconnect(visibility_check)

	if is_instance_valid(_active_pcam):
		if _active_pcam.dead_zone_changed.is_connected(_on_dead_zone_changed):
			_active_pcam.dead_zone_changed.disconnect(_on_dead_zone_changed)

	if _priority_override_button.pressed.is_connected(_select_override_pcam):
		_priority_override_button.pressed.disconnect(_select_override_pcam)


func _process(_delta: float) -> void:
	if Engine.is_editor_hint() and not viewfinder_visible: return
	if not is_instance_valid(_active_pcam): return

	var unprojected_position_clamped: Vector2 = Vector2(
		clamp(_active_pcam.viewport_position.x, min_horizontal, max_horizontal),
		clamp(_active_pcam.viewport_position.y, min_vertical, max_vertical)
	)

	if not Engine.is_editor_hint():
		target_point.position = camera_viewport_panel.size * unprojected_position_clamped - target_point.size / 2

	if _is_2d:
		if not is_instance_valid(pcam_host): return
		if not is_instance_valid(pcam_host.camera_2d): return

		var window_size_height: float = ProjectSettings.get_setting("display/window/size/viewport_height")
		sub_viewport.size_2d_override = sub_viewport.size * (window_size_height / sub_viewport.size.y)

		_camera_2d.global_transform = pcam_host.camera_2d.global_transform
		_camera_2d.offset = pcam_host.camera_2d.offset
		_camera_2d.zoom = pcam_host.camera_2d.zoom
		_camera_2d.ignore_rotation = pcam_host.camera_2d.ignore_rotation
		_camera_2d.anchor_mode = pcam_host.camera_2d.anchor_mode
		_camera_2d.limit_left = pcam_host.camera_2d.limit_left
		_camera_2d.limit_top = pcam_host.camera_2d.limit_top
		_camera_2d.limit_right = pcam_host.camera_2d.limit_right
		_camera_2d.limit_bottom = pcam_host.camera_2d.limit_bottom


func _settings_changed() -> void:
	var viewport_width: float = ProjectSettings.get_setting("display/window/size/viewport_width")
	var viewport_height: float = ProjectSettings.get_setting("display/window/size/viewport_height")
	var ratio: float = viewport_width / viewport_height
	aspect_ratio_container.set_ratio(ratio)
	camera_viewport_panel.size.x = viewport_width / (viewport_height / sub_viewport.size.y)
	# TODO - Add resizer for Framed Viewfinder


func _node_added_or_removed(_node: Node) -> void:
	visibility_check()


func visibility_check() -> void:
	if not viewfinder_visible: return

	var phantom_camera_host: PhantomCameraHost
	var has_camera: bool = false
	if not PhantomCameraManager.get_phantom_camera_hosts().is_empty():
		has_camera = true
		phantom_camera_host = PhantomCameraManager.get_phantom_camera_hosts()[0]

	var root: Node = EditorInterface.get_edited_scene_root()

	if root is Node2D:
		var camera_2d: Camera2D

		if has_camera:
			camera_2d = phantom_camera_host.camera_2d
		else:
			camera_2d = _get_camera_2d()

		_is_2d = true
		is_scene = true
		_add_node_button.set_visible(true)
		_check_camera(root, camera_2d, true)
	elif root is Node3D:
		var camera_3d: Camera3D

		if has_camera:
			camera_3d = phantom_camera_host.camera_3d
		else:
			camera_3d = root.get_viewport().get_camera_3d()

		_is_2d = false
		is_scene = true
		_add_node_button.set_visible(true)
		_check_camera(root, camera_3d, false)
	else:
		is_scene = false
#		Is not a 2D or 3D scene
		_set_empty_viewfinder_state(_no_open_scene_string, _no_open_scene_icon)
		_add_node_button.set_visible(false)

	if not _priority_override_button.pressed.is_connected(_select_override_pcam):
		_priority_override_button.pressed.connect(_select_override_pcam)


func _get_camera_2d() -> Camera2D:
	var camerasGroupName = "__cameras_%d" % EditorInterface.get_edited_scene_root().get_viewport().get_viewport_rid().get_id()
	var cameras = get_tree().get_nodes_in_group(camerasGroupName)

	for camera in cameras:
		if camera is Camera2D and camera.is_current:
			return camera
	return null


func _check_camera(root: Node, camera: Node, is_2D: bool) -> void:
	var camera_string: String
	var pcam_string: String
	var color: Color
	var color_alpha: Color
	var camera_icon: CompressedTexture2D
	var pcam_icon: CompressedTexture2D

	if is_2D:
		camera_string = Constants.CAMERA_2D_NODE_NAME
		pcam_string = Constants.PCAM_2D_NODE_NAME
		color = Constants.COLOR_2D
		camera_icon = _camera_2d_icon
		pcam_icon = _pcam_2D_icon
	else:
		camera_string = Constants.CAMERA_3D_NODE_NAME
		pcam_string = Constants.PCAM_3D_NODE_NAME
		color = Constants.COLOR_3D
		camera_icon = _camera_3d_icon
		pcam_icon = _pcam_3D_icon

	if camera:
#		Has Camera
		if camera.get_children().size() > 0:
			for cam_child in camera.get_children():
				if cam_child is PhantomCameraHost:
					pcam_host = cam_child

				if pcam_host:
					if PhantomCameraManager.get_phantom_camera_2ds() or PhantomCameraManager.get_phantom_camera_3ds():
						# Pcam exists in tree
						_set_viewfinder(root, true)
#							if pcam_host.get_active_pcam().get_get_follow_mode():
#								_on_dead_zone_changed()

						_set_viewfinder_state()

						%NoSupportMsg.set_visible(false)

					else:
#						No PCam in scene
						_update_button(pcam_string, pcam_icon, color)
						_set_empty_viewfinder_state(pcam_string, pcam_icon)
				else:
#					No PCamHost in scene
					_update_button(Constants.PCAM_HOST_NODE_NAME, _pcam_host_icon, Constants.PCAM_HOST_COLOR)
					_set_empty_viewfinder_state(Constants.PCAM_HOST_NODE_NAME, _pcam_host_icon)
		else:
#			No PCamHost in scene
			_update_button(Constants.PCAM_HOST_NODE_NAME, _pcam_host_icon, Constants.PCAM_HOST_COLOR)
			_set_empty_viewfinder_state(Constants.PCAM_HOST_NODE_NAME, _pcam_host_icon)
	else:
#		No Camera
		_update_button(camera_string, camera_icon, color)
		_set_empty_viewfinder_state(camera_string, camera_icon)


func _update_button(text: String, icon: CompressedTexture2D, color: Color) -> void:
	_add_node_button_text.set_text("[center]Add [img=32]" + icon.resource_path + "[/img] [b]"+ text + "[/b][/center]");
	var button_theme_hover: StyleBoxFlat = _add_node_button.get_theme_stylebox("hover")
	button_theme_hover.border_color = color
	_add_node_button.add_theme_stylebox_override("hover", button_theme_hover)


func _set_viewfinder_state() -> void:
	_empty_state_control.set_visible(false)

	_framed_viewfinder.set_visible(true)

	if is_instance_valid(_active_pcam):
		if _active_pcam.get_follow_mode() == _active_pcam.FollowMode.FRAMED:
			_dead_zone_h_box_container.set_visible(true)
			target_point.set_visible(true)
		else:
			_dead_zone_h_box_container.set_visible(false)
			target_point.set_visible(false)


func _set_empty_viewfinder_state(text: String, icon: CompressedTexture2D) -> void:
	_framed_viewfinder.set_visible(false)
	target_point.set_visible(false)

	_empty_state_control.set_visible(true)
	_empty_state_icon.set_texture(icon)
	if icon == _no_open_scene_icon:
		_empty_state_text.set_text("[center]No " + text + "[/center]")
	else:
		_empty_state_text.set_text("[center]No [b]" + text + "[/b] in scene[/center]")

	if _add_node_button.pressed.is_connected(_add_node):
		_add_node_button.pressed.disconnect(_add_node)

	_add_node_button.pressed.connect(_add_node.bind(text))


func _add_node(node_type: String) -> void:
	var root: Node = EditorInterface.get_edited_scene_root()

	match node_type:
		_no_open_scene_string:
			pass
		Constants.CAMERA_2D_NODE_NAME:
			var camera: Camera2D = Camera2D.new()
			_instantiate_node(root, camera, Constants.CAMERA_2D_NODE_NAME)
		Constants.CAMERA_3D_NODE_NAME:
			var camera: Camera3D = Camera3D.new()
			_instantiate_node(root, camera, Constants.CAMERA_3D_NODE_NAME)
		Constants.PCAM_HOST_NODE_NAME:
			var pcam_host: PhantomCameraHost = PhantomCameraHost.new()
			pcam_host.set_name(Constants.PCAM_HOST_NODE_NAME)
			if _is_2d:
#				get_tree().get_edited_scene_root().get_viewport().get_camera_2d().add_child(pcam_host)
				_get_camera_2d().add_child(pcam_host)
				pcam_host.set_owner(get_tree().get_edited_scene_root())
			else:
#				var pcam_3D := get_tree().get_edited_scene_root().get_viewport().get_camera_3d()
				get_tree().get_edited_scene_root().get_viewport().get_camera_3d().add_child(pcam_host)
				pcam_host.set_owner(get_tree().get_edited_scene_root())
		Constants.PCAM_2D_NODE_NAME:
			var pcam_2D: PhantomCamera2D = PhantomCamera2D.new()
			_instantiate_node(root, pcam_2D, Constants.PCAM_2D_NODE_NAME)
		Constants.PCAM_3D_NODE_NAME:
			var pcam_3D: PhantomCamera3D = PhantomCamera3D.new()
			_instantiate_node(root, pcam_3D, Constants.PCAM_3D_NODE_NAME)


func _instantiate_node(root: Node, node: Node, name: String) -> void:
	node.set_name(name)
	root.add_child(node)
	node.set_owner(get_tree().get_edited_scene_root())


func _set_viewfinder(root: Node, editor: bool) -> void:
	pcam_host_group = PhantomCameraManager.get_phantom_camera_hosts()
	if pcam_host_group.size() != 0:
		if pcam_host_group.size() == 1:
			var pcam_host: PhantomCameraHost = pcam_host_group[0]
			if _is_2d:
				_selected_camera = pcam_host.camera_2d
				_active_pcam = pcam_host.get_active_pcam() as PhantomCamera2D
				if editor:
					var camera_2d_rid: RID = _selected_camera.get_canvas()
					sub_viewport.disable_3d = true
					_camera_2d.zoom = pcam_host.camera_2d.zoom
					_camera_2d.offset = pcam_host.camera_2d.offset
					_camera_2d.ignore_rotation = pcam_host.camera_2d.ignore_rotation

					sub_viewport.world_2d = pcam_host.camera_2d.get_world_2d()
					sub_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
					sub_viewport.render_target_clear_mode = SubViewport.CLEAR_MODE_ALWAYS
					sub_viewport.size_2d_override_stretch = true
			else:
				_selected_camera = pcam_host.camera_3d
				_active_pcam = pcam_host.get_active_pcam() as PhantomCamera3D
				if editor:
					var camera_3d_rid: RID = _selected_camera.get_camera_rid()
					sub_viewport.disable_3d = false
					sub_viewport.world_3d = pcam_host.camera_3d.get_world_3d()
					RenderingServer.viewport_attach_camera(sub_viewport.get_viewport_rid(), camera_3d_rid)

				if _selected_camera.keep_aspect == Camera3D.KeepAspect.KEEP_HEIGHT:
					aspect_ratio_container.set_stretch_mode(AspectRatioContainer.STRETCH_HEIGHT_CONTROLS_WIDTH)
				else:
					aspect_ratio_container.set_stretch_mode(AspectRatioContainer.STRETCH_WIDTH_CONTROLS_HEIGHT)

			_on_dead_zone_changed()
			set_process(true)

			if not pcam_host.update_editor_viewfinder.is_connected(_on_update_editor_viewfinder):
				pcam_host.update_editor_viewfinder.connect(_on_update_editor_viewfinder.bind(pcam_host))

			if not aspect_ratio_container.resized.is_connected(_resized):
				aspect_ratio_container.resized.connect(_resized)

			if not _active_pcam.dead_zone_changed.is_connected(_on_dead_zone_changed):
				_active_pcam.dead_zone_changed.connect(_on_dead_zone_changed)


func _resized() -> void:
	_on_dead_zone_changed()


func _on_dead_zone_changed() -> void:
	if not is_instance_valid(_active_pcam): return
	if not _active_pcam.follow_mode == _active_pcam.FollowMode.FRAMED: return

	# Waits until the camera_viewport_panel has been resized when launching the game
	if camera_viewport_panel.size.x == 0:
		await camera_viewport_panel.resized

	#print(_active_pcam.get_pcam_host_owner())
	if is_instance_valid(_active_pcam.get_pcam_host_owner()):
		pcam_host = _active_pcam.get_pcam_host_owner()
		if not _active_pcam == pcam_host.get_active_pcam():
			_active_pcam == pcam_host.get_active_pcam()
			print("Active pcam in viewfinder: ", _active_pcam)

	var dead_zone_width: float = _active_pcam.dead_zone_width * camera_viewport_panel.size.x
	var dead_zone_height: float = _active_pcam.dead_zone_height * camera_viewport_panel.size.y
	dead_zone_center_hbox.set_custom_minimum_size(Vector2(dead_zone_width, 0))
	dead_zone_center_center_panel.set_custom_minimum_size(Vector2(0, dead_zone_height))
	dead_zone_left_center_panel.set_custom_minimum_size(Vector2(0, dead_zone_height))
	dead_zone_right_center_panel.set_custom_minimum_size(Vector2(0, dead_zone_height))

	min_horizontal = 0.5 - _active_pcam.dead_zone_width / 2
	max_horizontal = 0.5 + _active_pcam.dead_zone_width / 2
	min_vertical = 0.5 - _active_pcam.dead_zone_height / 2
	max_vertical = 0.5 + _active_pcam.dead_zone_height / 2


####################
## Priority Override
####################
func _on_update_editor_viewfinder(pcam_host: PhantomCameraHost) -> void:
	if pcam_host.get_active_pcam().priority_override:
		_active_pcam = pcam_host.get_active_pcam()
		_priority_override_button.set_visible(true)
		_priority_override_name_label.set_text(_active_pcam.name)
		_priority_override_button.set_tooltip_text(_active_pcam.name)
	else:
		_priority_override_button.set_visible(false)

func _select_override_pcam() -> void:
	EditorInterface.get_selection().clear()
	EditorInterface.get_selection().add_node(_active_pcam)

#endregion


#region Public Functions

func update_dead_zone() -> void:
	_set_viewfinder(root_node, true)


func scene_changed(scene_root: Node) -> void:
	if not scene_root is Node2D and not scene_root is Node3D:
		is_scene = false
		_set_empty_viewfinder_state(_no_open_scene_string, _no_open_scene_icon)
		_add_node_button.set_visible(false)

#endregion
