extends Node2D

var editMode = true

#Node Reference
@onready var origin = $OriginMotion/Origin
@onready var camera = $Camera2D
@onready var controlPanel = $ControlPanel
@onready var editControls = $EditControls
@onready var tutorial = $Tutorial
@onready var spriteViewer = $EditControls/SpriteViewer
@onready var viewerArrows = $ViewerArrows
@onready var spriteList = $EditControls/SpriteList

@onready var fileDialog = $FileDialog
@onready var replaceDialog = $ReplaceDialog
@onready var saveDialog = $SaveDialog
@onready var loadDialog = $LoadDialog

@onready var lines = $Lines

@onready var settingsMenu = $ControlPanel/SettingsMenu

@onready var pushUpdates = $PushUpdates

@onready var shadow = $shadowSprite


#Scene Reference
@onready var spriteObject = preload("res://ui_scenes/selectedSprite/spriteObject.tscn")

var saveLoaded = false

#Motion
var yVel = 0
var bounceSlider = 250
var bounceGravity = 1000

# Physics timing multiplier - adjust this to fine-tune physics speed
# 1.0 = very slow, 10.0 = reasonable, 60.0 = old frame-based speed
var physicsTimeMultiplier = 6.0

#Costumes
var costume = 1
var bounceOnCostumeChange = false

#Zooming
var scaleOverall = 100

#Camera Panning
var isPanning = false
var initialPanPosition = Vector2.ZERO
var initialCameraOffset = Vector2.ZERO
var cameraOffset = Vector2.ZERO

#View Mode Panning
var viewModePanning = false
var initialViewPanPosition = Vector2.ZERO
var viewPanOffset = Vector2.ZERO
var viewPanLerpSpeed = 3.0

var bounceChange = 0.0

#IMPORTANT
var fileSystemOpen = false

#background input capture
signal emptiedCapture
signal pressedKey
var costumeKeys = ["1","2","3","4","5","6","7","8","9","0"]
signal spriteVisToggles(keysPressed:Array)
signal fatfuckingballs

func _ready():
	Global.main = self
	Global.fail = $Failed
	
	
	Global.connect("startSpeaking",onSpeak)
	
	# Connect to StreamDeck if available
	var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck")
	if streamdeck_node != null:
		streamdeck_node.on_key_down.connect(changeCostumeStreamDeck)
	
	if Saving.settings["newUser"]:
		print("DEBUG: MAIN._ready() - New user detected, loading default avatar")
		_on_load_dialog_file_selected("default")
		Saving.settings["newUser"] = false
		saveLoaded = true
	else:
		print("DEBUG: MAIN._ready() - Existing user, loading last avatar")
		var lastAvatar = Saving.settings["lastAvatar"]
		print("DEBUG: lastAvatar setting: ", lastAvatar, " (type: ", typeof(lastAvatar), ")")
		# Ensure lastAvatar is a string, not a Dictionary
		if typeof(lastAvatar) == TYPE_STRING and lastAvatar != "":
			print("DEBUG: Loading avatar from: ", lastAvatar)
			_on_load_dialog_file_selected(lastAvatar)
		else:
			print("WARNING: lastAvatar setting is invalid, loading default instead")
			_on_load_dialog_file_selected("default")
		
		$ControlPanel/volumeSlider.value = Saving.settings["volume"]
		$ControlPanel/sensitiveSlider.value = Saving.settings["sense"]
		
		get_window().size = str_to_var(Saving.settings["windowSize"])
		
		if Saving.settings.has("bounce"):
			bounceSlider = Saving.settings["bounce"]
		else:
			Saving.settings["bounce"] = 250
		
		if Saving.settings.has("maxFPS"):
			Engine.max_fps = Saving.settings["maxFPS"]
		else:
			Saving.settings["maxFPS"] = 60
		
		if Saving.settings.has("backgroundColor"):
			Global.backgroundColor = str_to_var(Saving.settings["backgroundColor"])
		else:
			Saving.settings["backgroundColor"] = var_to_str(Color(0.0,0.0,0.0,0.0))
		
		if Saving.settings.has("filtering"):
			Global.filtering = Saving.settings["filtering"]
		else:
			Saving.settings["filtering"] = false
			
		if Saving.settings.has("gravity"):
			bounceGravity = Saving.settings["gravity"]
		else:
			Saving.settings["gravity"] = 1000
		
		if Saving.settings.has("costumeKeys"):
			costumeKeys = Saving.settings["costumeKeys"]
		else:
			Saving.settings["costumeKeys"] = costumeKeys
		
		if Saving.settings.has("blinkSpeed"):
			Global.blinkSpeed = Saving.settings["blinkSpeed"]
		else:
			Saving.settings["blinkSpeed"] = 1.0
		
		if Saving.settings.has("blinkChance"):
			Global.blinkChance = Saving.settings["blinkChance"]
		else:
			Saving.settings["blinkChance"] = 200
		
		if Saving.settings.has("bounceOnCostumeChange"):
			bounceOnCostumeChange = Saving.settings["bounceOnCostumeChange"]
		else:
			Saving.settings["bounceOnCostumeChange"] = false
		
		saveLoaded = true
		
	RenderingServer.set_default_clear_color(Global.backgroundColor)
	swapMode()
	settingsMenu.setvalues()
	changeCostume(1)
	
	var s = get_viewport().get_visible_rect().size
	origin.position = s*0.5
	updateCameraPosition()
	
func _process(delta):
	var hold = origin.get_parent().position.y
	
	origin.get_parent().position.y += yVel * delta * physicsTimeMultiplier
	if origin.get_parent().position.y > 0:
		origin.get_parent().position.y = 0
	bounceChange = hold - origin.get_parent().position.y
	
	yVel += bounceGravity * delta * physicsTimeMultiplier
	
	# Handle view mode panning lerp back to center
	if !editMode:
		if !viewModePanning:
			# Lerp back to center when not actively panning
			viewPanOffset = viewPanOffset.lerp(Vector2.ZERO, viewPanLerpSpeed * delta)
		
		# Apply view pan offset to the root character (affects physics)
		origin.position = (get_viewport().get_visible_rect().size * 0.5) + viewPanOffset
		updateCameraPosition()
	
	if Input.is_action_just_pressed("openFolder"):
		OS.shell_open(ProjectSettings.globalize_path("user://"))
	
	moveSpriteMenu(delta)
	zoomScene(delta)
	
	fileSystemOpen = isFileSystemOpen()
	
	followShadow()

## Handle input events, especially for camera panning
func _input(event):
	# Handle middle mouse button for camera panning/reset
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_MIDDLE:
		if editMode:
			if event.pressed:
				# Start panning in edit mode
				isPanning = true
				initialPanPosition = event.global_position
				initialCameraOffset = cameraOffset
			else:
				# Stop panning
				isPanning = false
		else:
			if event.pressed:
				# Start view mode panning
				viewModePanning = true
				initialViewPanPosition = event.global_position
			else:
				# Stop view mode panning
				viewModePanning = false
	
	# Handle mouse movement during edit mode panning
	elif editMode and event is InputEventMouseMotion and isPanning:
		var totalDelta = event.global_position - initialPanPosition
		
		# Scale pan distance based on zoom level for more intuitive panning
		# Higher zoom (closer) = less movement, lower zoom (farther) = more movement
		var zoomScale = 1.0 / camera.zoom.x
		var scaledDelta = totalDelta * zoomScale
		
		# Calculate new camera offset from initial position plus scaled movement
		cameraOffset = initialCameraOffset - scaledDelta
		
		# Update camera position immediately
		var s = get_viewport().get_visible_rect().size
		camera.position = origin.position + cameraOffset
		
		# Update UI positions
		controlPanel.position = camera.position + (s/(camera.zoom*2.0))
		tutorial.position = controlPanel.position
		editControls.position = camera.position - (s/(camera.zoom*2.0))
		viewerArrows.position = editControls.position
		pushUpdates.position.y = controlPanel.position.y
		pushUpdates.position.x = editControls.position.x
	
	# Handle mouse movement during view mode panning
	elif !editMode and event is InputEventMouseMotion and viewModePanning:
		var totalDelta = event.global_position - initialViewPanPosition
		
		# Scale pan distance for view mode (less sensitive than edit mode)
		# Invert the movement so dragging right moves character right (not left)
		var panSensitivity = 0.5
		viewPanOffset = -totalDelta * panSensitivity

func followShadow():
	# Shadow disabled for selected sprites
	shadow.visible = false
	

func isFileSystemOpen():
	for obj in [replaceDialog,fileDialog,saveDialog,loadDialog]:
		if obj.visible:
			if obj == replaceDialog:
				return true
			Global.heldSprite = null
			return true
	
	# Check wobble sync group dialog
	if Global.spriteEdit and Global.spriteEdit.wobbleSyncControl and Global.spriteEdit.wobbleSyncControl.createGroupDialog and Global.spriteEdit.wobbleSyncControl.createGroupDialog.visible:
		return true
	
	return false

#Displays control panel whether or not application is focused
func _notification(what):
	match what:
		SceneTree.NOTIFICATION_APPLICATION_FOCUS_OUT:
			controlPanel.visible = false
			pushUpdates.visible = false
		SceneTree.NOTIFICATION_APPLICATION_FOCUS_IN:
			if !editMode:
				controlPanel.visible = true
			pushUpdates.visible = true
		30:
			onWindowSizeChange()

func onWindowSizeChange():
	if !saveLoaded:
		return
	Saving.settings["windowSize"] = var_to_str(get_window().size)
	var s = get_viewport().get_visible_rect().size
	origin.position = s*0.5
	
	lines.position = s*0.5
	lines.drawLine()
	
	updateCameraPosition()
	spriteList.position.x = s.x - 233

func zoomScene(delta):
	#Handles Zooming
	if Input.is_action_pressed("control"):
		if Input.is_action_just_pressed("scrollUp"):
			if scaleOverall < 400:
				camera.zoom += Vector2(0.1,0.1)
				scaleOverall += 10
				changeZoom()
		if Input.is_action_just_pressed("scrollDown"):
			if scaleOverall > 10:
				camera.zoom -= Vector2(0.1,0.1)
				scaleOverall -= 10
				changeZoom()
	
	$ControlPanel/ZoomLabel.modulate.a = lerp($ControlPanel/ZoomLabel.modulate.a, 0.0, delta * (physicsTimeMultiplier * 1.2))
	
func changeZoom():
	var newZoom = Vector2(1.0,1.0) / camera.zoom
	controlPanel.scale = newZoom
	tutorial.scale = newZoom
	editControls.scale = newZoom
	viewerArrows.scale = newZoom
	lines.scale = newZoom
	pushUpdates.scale = newZoom
	Global.mouse.scale = newZoom

	# Update UI positions to account for new zoom level
	# Force update even during panning since zoom affects UI positioning calculations
	var s = get_viewport().get_visible_rect().size
	controlPanel.position = camera.position + (s/(camera.zoom*2.0))
	tutorial.position = controlPanel.position
	editControls.position = camera.position - (s/(camera.zoom*2.0))
	viewerArrows.position = editControls.position
	pushUpdates.position.y = controlPanel.position.y
	pushUpdates.position.x = editControls.position.x

	$ControlPanel/ZoomLabel.modulate.a = 6.0
	$ControlPanel/ZoomLabel.text = "Zoom : " + str(scaleOverall) + "%"
	
	Global.pushUpdate("Set zoom to " + str(scaleOverall) + "%")

## Update camera position with current offset
func updateCameraPosition():
	var s = get_viewport().get_visible_rect().size
	
	# Only update if not currently panning to avoid conflicts
	if !isPanning:
		camera.position = origin.position + cameraOffset
		
		# Update UI element positions relative to new camera position
		controlPanel.position = camera.position + (s/(camera.zoom*2.0))
		tutorial.position = controlPanel.position
		editControls.position = camera.position - (s/(camera.zoom*2.0))
		viewerArrows.position = editControls.position
		pushUpdates.position.y = controlPanel.position.y
		pushUpdates.position.x = editControls.position.x

## Reset camera position to center
func resetCameraPosition():
	cameraOffset = Vector2.ZERO
	isPanning = false
	viewPanOffset = Vector2.ZERO
	viewModePanning = false
	updateCameraPosition()
	Global.pushUpdate("Camera reset to center.")
	
#When the user speaks!
func onSpeak():
	if origin.get_parent().position.y > -16:
		yVel = bounceSlider * -1

#Swaps between edit mode and view mode
func swapMode():
	
	Global.heldSprite = null
	
	editMode = !editMode
	Global.pushUpdate("Toggled editing mode.")
	
	# Reset camera when entering view mode
	if !editMode:
		resetCameraPosition()
	else:
		# Reset view mode panning when entering edit mode
		viewPanOffset = Vector2.ZERO
		viewModePanning = false
	
	get_viewport().transparent_bg = !editMode
	if Global.backgroundColor.a != 0.0:
		get_viewport().transparent_bg = false
	RenderingServer.set_default_clear_color(Global.backgroundColor)
	#processing
	editControls.set_process(editMode)
	controlPanel.set_process(!editMode)
	#visibility
	editControls.visible = editMode
	tutorial.visible = editMode
	controlPanel.visible = !editMode
	lines.visible = editMode
	spriteList.visible = editMode
	
#Adds sprite object to scene
func add_image(path):
	
	var rand = RandomNumberGenerator.new()
	var id = rand.randi()
	
	var sprite = spriteObject.instantiate()
	sprite.path = path
	sprite.id = id
	origin.add_child(sprite)
	sprite.position = Vector2.ZERO
	
	Global.spriteList.updateData()
	
	Global.pushUpdate("Added new sprite.")
	
#Opens File Dialog
func _on_add_button_pressed():
	fileDialog.visible = true

#Runs when selecting image in File Dialog
func _on_file_dialog_file_selected(path):
	add_image(path)

func _on_save_button_pressed():
	$SaveDialog.visible = true
	

func _on_load_button_pressed():
	$LoadDialog.visible = true

#LOAD AVATAR
func _on_load_dialog_file_selected(path):
	print("DEBUG: _on_load_dialog_file_selected called with path: ", path, " (type: ", typeof(path), ")")
	
	# Handle case where path might be a Dictionary instead of string
	if typeof(path) == TYPE_DICTIONARY:
		print("ERROR: Expected string path but got Dictionary: ", path)
		return
	
	print("DEBUG: About to read save data from: ", path)
	var data = Saving.read_save(path)
	print("DEBUG: Save data read. Data type: ", typeof(data), ", null check: ", data == null)
	
	if data == null:
		print("DEBUG: Save data is null, returning")
		return
	
	print("DEBUG: Save data keys: ", data.keys() if typeof(data) == TYPE_DICTIONARY else "not a dictionary")
	print("DEBUG: Save data size: ", data.size() if typeof(data) == TYPE_DICTIONARY else "not a dictionary")
	
	# Check the structure of the first few items
	if typeof(data) == TYPE_DICTIONARY:
		var count = 0
		for key in data.keys():
			if count < 3:  # Just show first 3 items for debugging
				print("DEBUG: data[", key, "] = ", data[key])
				print("DEBUG: data[", key, "] type = ", typeof(data[key]))
				if typeof(data[key]) == TYPE_DICTIONARY and data[key].has("path"):
					print("DEBUG: data[", key, "][\"path\"] = ", data[key]["path"])
				else:
					print("DEBUG: data[", key, "] doesn't have 'path' key or isn't a dictionary")
				count += 1
	
	origin.queue_free()
	var new = Node2D.new()
	$OriginMotion.add_child(new)
	origin = new
	
	for item in data:
		print("DEBUG: Processing item: ", item, ", data type: ", typeof(data[item]))
		if typeof(data[item]) != TYPE_DICTIONARY:
			print("DEBUG: Skipping item ", item, " - not a dictionary")
			continue
		
		if not data[item].has("path"):
			print("DEBUG: Skipping item ", item, " - no 'path' key. Keys: ", data[item].keys())
			continue
		
		var sprite = spriteObject.instantiate()
		sprite.path = data[item]["path"]
		sprite.id = data[item]["identification"]
		sprite.parentId = data[item]["parentId"]
		
		sprite.offset = str_to_var(data[item]["offset"])
		sprite.z = data[item]["zindex"]
		sprite.dragSpeed = data[item]["drag"]
		
		sprite.xFrq = data[item]["xFrq"]
		sprite.xAmp = data[item]["xAmp"]
		sprite.yFrq = data[item]["yFrq"]
		sprite.yAmp = data[item]["yAmp"]
		
		# Load rotation wobble properties (with backward compatibility)
		if data[item].has("rFrq"):
			sprite.rFrq = data[item]["rFrq"]
		if data[item].has("rAmp"):
			sprite.rAmp = data[item]["rAmp"]
		
		sprite.rdragStr = data[item]["rotDrag"]
		sprite.showOnTalk = data[item]["showTalk"]
		
		sprite.showOnBlink = data[item]["showBlink"]
		
		if data[item].has("rLimitMin"):
			sprite.rLimitMin = data[item]["rLimitMin"]
		if data[item].has("rLimitMax"):
			sprite.rLimitMax = data[item]["rLimitMax"]
		
		if data[item].has("costumeLayers"):
			sprite.costumeLayers = str_to_var(data[item]["costumeLayers"]).duplicate()
			if sprite.costumeLayers.size() < 8:
				for i in range(5):
					sprite.costumeLayers.append(1)

		if data[item].has("stretchAmount"):
			sprite.stretchAmount = data[item]["stretchAmount"]
		
		if data[item].has("ignoreBounce"):
			sprite.ignoreBounce = data[item]["ignoreBounce"]
		
		if data[item].has("frames"):
			sprite.frames = data[item]["frames"]
		if data[item].has("animSpeed"):
			sprite.animSpeed = data[item]["animSpeed"]
		if data[item].has("imageData"):
			sprite.loadedImageData = data[item]["imageData"]
		if data[item].has("clipped"):
			sprite.clipped = data[item]["clipped"]
		if data[item].has("toggle"):
			sprite.toggle = data[item]["toggle"]
		if data[item].has("spriteOpacity"):
			sprite.spriteOpacity = data[item]["spriteOpacity"]
		if data[item].has("affectChildrenOpacity"):
			sprite.affectChildrenOpacity = data[item]["affectChildrenOpacity"]
		
		# Load wobble sync group (with backward compatibility)
		if data[item].has("wobbleSyncGroup"):
			sprite.wobbleSyncGroup = data[item]["wobbleSyncGroup"]
		
		origin.add_child(sprite)
		sprite.position = str_to_var(data[item]["pos"])
	
	changeCostume(1)
	Saving.settings["lastAvatar"] = path
	Global.spriteList.updateData()
	
	# Load wobble sync groups
	print("DEBUG: About to check for wobble sync groups...")
	print("DEBUG: data type: ", typeof(data), ", data.keys(): ", data.keys() if typeof(data) == TYPE_DICTIONARY else "not a dictionary")
	print("DEBUG: data.has('wobbleSyncGroups'): ", data.has("wobbleSyncGroups"))
	print("DEBUG: WobbleSyncManager exists: ", WobbleSyncManager != null)
	if WobbleSyncManager != null:
		print("DEBUG: WobbleSyncManager type: ", typeof(WobbleSyncManager))
	
	# Show save data content related to wobble sync
	if data.has("wobbleSyncGroups"):
		print("DEBUG: wobbleSyncGroups data found: ", data["wobbleSyncGroups"])
	else:
		print("DEBUG: No wobbleSyncGroups found in save data")
	
	if data.has("wobbleSyncGroups") and WobbleSyncManager:
		print("DEBUG: Loading wobble sync groups. Data: ", data["wobbleSyncGroups"])
		await WobbleSyncManager.loadSaveData(data["wobbleSyncGroups"])
		print("DEBUG: Finished loading wobble sync groups")
		# Refresh wobble sync UI after groups are loaded
		if Global.spriteEdit and Global.spriteEdit.wobbleSyncControl:
			Global.spriteEdit.wobbleSyncControl.updateUI()
			# Force refresh in case timing issues
			await get_tree().process_frame
			Global.spriteEdit.wobbleSyncControl.forceRefresh()
			# Force refresh in case timing issues
			await get_tree().process_frame
			Global.spriteEdit.wobbleSyncControl.forceRefresh()
	else:
		print("DEBUG: Not loading wobble sync groups - missing data or manager")
	
	# Final debug check
	if WobbleSyncManager:
		print("DEBUG: Final check - WobbleSyncManager has ", WobbleSyncManager.getGroupNames().size(), " groups: ", WobbleSyncManager.getGroupNames())
	else:
		print("DEBUG: No wobble sync groups to load. has key: ", data.has("wobbleSyncGroups"), ", WobbleSyncManager: ", WobbleSyncManager != null)
	
	# Update opacity shaders for all loaded sprites after wobble sync data is applied
	await get_tree().process_frame  # Wait for all sprites to be fully initialized
	var allSprites = get_tree().get_nodes_in_group("saved")
	for sprite in allSprites:
		if sprite.has_method("updateOpacity"):
			sprite.updateOpacity()
	
	Global.pushUpdate("Loaded avatar at: " + path)
	
	onWindowSizeChange()
	
#SAVE AVATAR
func _on_save_dialog_file_selected(path):
	var data = {}
	var nodes = get_tree().get_nodes_in_group("saved")
	var id = 0
	for child in nodes:
		
		if child.type == "sprite":
			data[id] = {}
			data[id]["type"] = "sprite"
			data[id]["path"] = child.path
			data[id]["imageData"] = Marshalls.raw_to_base64(child.imageData.save_png_to_buffer())
			data[id]["identification"] = child.id
			data[id]["parentId"] = child.parentId
			
			data[id]["pos"] = var_to_str(child.position)
			data[id]["offset"] = var_to_str(child.offset)
			data[id]["zindex"] = child.z
			
			data[id]["drag"] = child.dragSpeed
			
			data[id]["xFrq"] = child.xFrq
			data[id]["xAmp"] = child.xAmp
			data[id]["yFrq"] = child.yFrq
			data[id]["yAmp"] = child.yAmp
			
			data[id]["rFrq"] = child.rFrq
			data[id]["rAmp"] = child.rAmp
			
			data[id]["rotDrag"] = child.rdragStr
			
			data[id]["showTalk"] = child.showOnTalk
			data[id]["showBlink"] = child.showOnBlink
			
			data[id]["rLimitMin"] = child.rLimitMin
			data[id]["rLimitMax"] = child.rLimitMax
			
			data[id]["costumeLayers"] = var_to_str(child.costumeLayers)
			
			data[id]["stretchAmount"] = child.stretchAmount
			
			data[id]["ignoreBounce"] = child.ignoreBounce
			
			data[id]["frames"] = child.frames
			data[id]["animSpeed"] = child.animSpeed
			
			data[id]["clipped"] = child.clipped
			
			data[id]["toggle"] = child.toggle
			
			data[id]["spriteOpacity"] = child.spriteOpacity
			data[id]["affectChildrenOpacity"] = child.affectChildrenOpacity
			
			# Save wobble sync group
			data[id]["wobbleSyncGroup"] = child.wobbleSyncGroup
			
		id += 1
	
	# Save wobble sync groups
	if WobbleSyncManager:
		data["wobbleSyncGroups"] = WobbleSyncManager.getSaveData()
	
	Saving.settings["lastAvatar"] = path
	
	Saving.data = data.duplicate()
	Saving.write_save(path)
	
	Global.pushUpdate("Saved avatar at: " + path)

func _on_link_button_pressed():
	Global.reparentMode = true
	Global.chain.enable(Global.reparentMode)
	
	Global.pushUpdate("Linking sprite...")


func _on_kofi_pressed():
	OS.shell_open("https://ko-fi.com/kaiakairos")
	Global.pushUpdate("Support me on ko-fi!")


func _on_bluesky_pressed():
	OS.shell_open("https://bsky.app/profile/kaiakairos.net")
	Global.pushUpdate("Follow me on bluesky!")


func _on_replace_button_pressed():
	if Global.heldSprite == null:
		return
	$ReplaceDialog.visible = true

func _on_replace_dialog_file_selected(path):
	Global.heldSprite.replaceSprite(path)
	Global.spriteList.updateData()
	Global.pushUpdate("Replacing sprite with: " + path)

func _on_replace_dialog_visibility_changed():
	$EditControls/ScreenCover/CollisionShape2D.disabled = !$ReplaceDialog.visible


func _on_duplicate_button_pressed():
	if Global.heldSprite == null:
		return
	var rand = RandomNumberGenerator.new()
	var id = rand.randi()
	
	var sprite = spriteObject.instantiate()
	sprite.path = Global.heldSprite.path
	sprite.id = id
	sprite.parentId = Global.heldSprite.parentId
	
	sprite.dragSpeed = Global.heldSprite.dragSpeed
	sprite.showOnTalk = Global.heldSprite.showOnTalk
	sprite.showOnBlink = Global.heldSprite.showOnBlink
	sprite.z = Global.heldSprite.z
	
	sprite.xFrq = Global.heldSprite.xFrq
	sprite.xAmp = Global.heldSprite.xAmp
	sprite.yFrq = Global.heldSprite.yFrq
	sprite.yAmp = Global.heldSprite.yAmp
	
	sprite.rFrq = Global.heldSprite.rFrq
	sprite.rAmp = Global.heldSprite.rAmp
	
	sprite.rdragStr = Global.heldSprite.rdragStr
	
	sprite.offset = Global.heldSprite.offset
	
	sprite.rLimitMin = Global.heldSprite.rLimitMin
	sprite.rLimitMax = Global.heldSprite.rLimitMax
	
	sprite.frames = Global.heldSprite.frames
	sprite.animSpeed = Global.heldSprite.animSpeed
	
	sprite.costumeLayers = Global.heldSprite.costumeLayers
	
	sprite.spriteOpacity = Global.heldSprite.spriteOpacity
	sprite.affectChildrenOpacity = Global.heldSprite.affectChildrenOpacity
	
	# Copy wobble sync group
	sprite.wobbleSyncGroup = Global.heldSprite.wobbleSyncGroup
	
	origin.add_child(sprite)
	sprite.position = Global.heldSprite.position + Vector2(16,16)
	
	# Update opacity shader for the duplicated sprite
	await get_tree().process_frame  # Wait for sprite to be fully initialized
	sprite.updateOpacity()
	
	Global.heldSprite = sprite
	
	Global.spriteList.updateData()
	
	Global.pushUpdate("Duplicated sprite.")

func changeCostumeStreamDeck(id: String):
	match id:
		"1":changeCostume(1)
		"2":changeCostume(2)
		"3":changeCostume(3)
		"4":changeCostume(4)
		"5":changeCostume(5)
		"6":changeCostume(6)
		"7":changeCostume(7)
		"8":changeCostume(8)
		"9":changeCostume(9)
		"10":changeCostume(10)

func changeCostume(newCostume):
	costume = newCostume
	Global.heldSprite = null
	var nodes = get_tree().get_nodes_in_group("saved")
	for sprite in nodes:
		if sprite.costumeLayers[newCostume-1] == 1:
			sprite.visible = true
			sprite.changeCollision(true)
		else:
			sprite.visible = false
			sprite.changeCollision(false)
	Global.spriteEdit.layerSelected()
	spriteList.updateAllVisible()
	
	if bounceOnCostumeChange:
		onSpeak()
	
	Global.pushUpdate("Change costume: " + str(newCostume))
	
func moveSpriteMenu(delta):
	
	#moves sprite viewer editor thing around
	
	var size = get_viewport().get_visible_rect().size
	
	var windowLength = 1800
	
	$ViewerArrows/Arrows.position.y =  size.y - 25
	
	if !Global.spriteEdit.visible:
		$ViewerArrows/Arrows.visible = false
		$ViewerArrows/Arrows2.visible = false
		return
	
	if size.y > windowLength+50:
		Global.spriteEdit.position.y = 66
		
		$ViewerArrows/Arrows.visible = false
		$ViewerArrows/Arrows2.visible = false
		
		return
	
	if Global.spriteEdit.position.y < 16:
		$ViewerArrows/Arrows2.visible = true
	else:
		$ViewerArrows/Arrows2.visible = false
	if Global.spriteEdit.position.y > size.y-windowLength+2:
		$ViewerArrows/Arrows.visible = true
	else:
		$ViewerArrows/Arrows.visible = false

	
	if $EditControls/MoveMenuUp.overlaps_area(Global.mouse.area):
		Global.spriteEdit.position.y += (delta * 400.0)
	elif $EditControls/MoveMenuDown.overlaps_area(Global.mouse.area):
		Global.spriteEdit.position.y -= (delta * 400.0)
	
	if Global.spriteEdit.position.y > 66:
		Global.spriteEdit.position.y = 66
	elif Global.spriteEdit.position.y < size.y-windowLength:
		Global.spriteEdit.position.y = size.y-windowLength
	

	
#UNAMED BUT THIS IS THE MICROPHONE MENU BUTTON
func _on_button_pressed():
	$ControlPanel/MicInputSelect.visible = !$ControlPanel/MicInputSelect.visible
	settingsMenu.visible = false


func _on_settings_buttons_pressed():
	settingsMenu.visible = !settingsMenu.visible


func _on_background_input_capture_bg_key_pressed(node, keys_pressed):
	var keyStrings = []
	
	# Check for modifier keys first (cross-platform)
	var modifiers = []
	var has_ctrl = keys_pressed.has(KEY_CTRL) and keys_pressed[KEY_CTRL]
	var has_shift = keys_pressed.has(KEY_SHIFT) and keys_pressed[KEY_SHIFT]
	var has_alt = keys_pressed.has(KEY_ALT) and keys_pressed[KEY_ALT]
	var has_meta = keys_pressed.has(KEY_META) and keys_pressed[KEY_META]  # Command key on macOS
	
	# Build modifier prefix once
	if has_ctrl:
		modifiers.append("Ctrl")
	if has_shift:
		modifiers.append("Shift")
	if has_alt:
		modifiers.append("Alt")
	if has_meta:
		modifiers.append("Cmd")  # Command key on macOS
	
	var modifier_prefix = ""
	if modifiers.size() > 0:
		modifier_prefix = "+".join(modifiers) + "+"
	
	# Single pass: build key strings directly
	for i in keys_pressed:
		if keys_pressed[i]:
			# Skip modifier keys
			if i == KEY_CTRL or i == KEY_SHIFT or i == KEY_ALT or i == KEY_META:
				continue
			
			# Skip mouse button keycodes (common source of phantom inputs)
			if i == MOUSE_BUTTON_LEFT or i == MOUSE_BUTTON_RIGHT or i == MOUSE_BUTTON_MIDDLE:
				continue
			
			# Skip phantom keypad inputs if no actual keypad connected
			var key_name = OS.get_keycode_string(i)
			if key_name.begins_with("kp ") or key_name.begins_with("Kp "):
				# Skip keypad inputs unless they're from actual key presses
				continue
			
			if !key_name.strip_edges().is_empty():
				keyStrings.append(modifier_prefix + key_name)
			else:
				keyStrings.append(modifier_prefix + "Keycode" + str(i))
	
	if fileSystemOpen:
		return
	
	if keyStrings.size() <= 0:
		emit_signal("emptiedCapture")
		emit_signal("fatfuckingballs")
		return
	
	if settingsMenu.awaitingCostumeInput >= 0:
		
		# Cancel key assignment if user clicks outside the settings menu  
		if keyStrings[0] == "Keycode1" and !settingsMenu.hasMouse:
			# Don't assign the key, just complete the flow
			emit_signal("pressedKey")
			return
		
		var currentButton = costumeKeys[settingsMenu.awaitingCostumeInput]
		costumeKeys[settingsMenu.awaitingCostumeInput] = keyStrings[0]
		Saving.settings["costumeKeys"] = costumeKeys
		Global.pushUpdate("Changed costume " + str(settingsMenu.awaitingCostumeInput+1) + " hotkey from \"" + currentButton + "\" to \"" + keyStrings[0] + "\"")
		emit_signal("pressedKey")
		return
	
	# Handle sprite visibility toggles if not waiting for costume input
	spriteVisToggles.emit(keyStrings)
	
	for key in keyStrings:
		var i = costumeKeys.find(key)
		if i >= 0:
			changeCostume(i+1)
	
