--[[feature ideas:
	- setting to auto-focus screen when character is close to a screen
]]

function setHumanoidControl(hasControl)
	local character = localPlayer.Character
	if character and character.Parent then
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		if not hasControl then
			humanoid.WalkSpeed = 0
			if humanoid.UseJumpPower then
				humanoid.JumpPower = 0
			else
				humanoid.JumpHeight = 0
			end
			humanoid.AutoRotate = false
		else
			humanoid.WalkSpeed = StarterPlayer.CharacterWalkSpeed
			if humanoid.UseJumpPower then
				humanoid.JumpPower = StarterPlayer.CharacterJumpPower
			else
				humanoid.JumpHeight = StarterPlayer.CharacterJumpHeight
			end
			humanoid.AutoRotate = true
		end
	end

	-- Find the jump button and adjust it's visibility
	if UserInputService.TouchEnabled then
		local playerGui = localPlayer:FindFirstChild("PlayerGui")
		if playerGui then
			local touchGui = playerGui:FindFirstChild("TouchGui")
			if touchGui then
				local controlFrame = touchGui:FindFirstChild("TouchControlFrame")
				if controlFrame then
					local jumpButton = controlFrame:FindFirstChild("JumpButton")
					if jumpButton then
						TweenService:Create(
							touchGui.TouchControlFrame.JumpButton,
							TweenInfo.new(0.5),
							{ ImageTransparency = hasControl and 0 or 1 }
						):Play()
					end
				end
			end
		end
	end
end

function setCameraCF(screenPart)
	local halfFOV = math.rad(camera.FieldOfView / 2)
	local halfScreenHeight = screenPart.Size.Y / 2
	local distanceOffset = halfScreenHeight / math.tan(halfFOV)
	local cameraAspectRatio = (camera.ViewportSize.X / camera.ViewportSize.Y)
	local screenAspectRatio = screenPart.Size.X / screenPart.Size.Y
	if cameraAspectRatio < screenAspectRatio then
		distanceOffset = distanceOffset / (cameraAspectRatio / screenAspectRatio)
	end
	local targetCF = screenPart.CFrame * CFrame.new(0, 0, -(distanceOffset + screenPart.Size.Z / 2))

	return CFrame.new(targetCF.Position, screenPart.Position)
end

function getCharacterPart()
	local character = localPlayer.Character
	if character and character.Parent and character:IsA("Model") then
		return character.PrimaryPart
	end
end

function updateButtonVisuals()
	if button and button.Parent then
		if fullscreenModule.FocusedScreen == buttonTargetScreen then
			button.Image = ZOOM_OUT_ICON
			button.GamepadPrompt.Image = BACKOUT_GAMEPAD_BUTTON_ICON
		else
			button.Image = ZOOM_IN_ICON
			button.GamepadPrompt.Image = FULLSCREEN_GAMEPAD_BUTTON_ICON
		end
	end
end

function fullscreenModule.FocusScreen(screenPart, manageLockingCharacterControl)
	-- If screenPart is nil or the character, the camera will return to the character
	if screenPart and screenPart == localPlayer.Character then
		screenPart = nil
	end

	if fullscreenModule.FocusedScreen == screenPart then
		return
	end
	fullscreenModule.FocusedScreen = screenPart

	transitionCount += 1
	local thisTransitionCount = transitionCount

	updateButtonVisuals()

	-- Tween camera into position
	local targetCF = nil
	local camTweenRelativeTargetPart = nil
	if screenPart then
		if manageLockingCharacterControl then
			setHumanoidControl(false)
		end

		-- If focusing a screen, and no currently saved relative info for the character, then capture relative character info.
		if not characterCamCF then
			local cameraSubjectPart = getCharacterPart()
			if cameraSubjectPart then
				characterCamCF = CFrame.new(cameraSubjectPart.Position):ToObjectSpace(camera.CFrame)
				characterCamCFIsRelative = true
			else
				characterCamCF = camera.CFrame
				characterCamCFIsRelative = false
			end
		end

		camera.CameraType = Enum.CameraType.Scriptable
		targetCF = setCameraCF(screenPart)
	else
		targetCF = characterCamCF
		if characterCamCFIsRelative then
			camTweenRelativeTargetPart = getCharacterPart()
		end
	end

	if targetCF then
		-- Tweening an object so that we can apply the tweened cframe overtop a camera subject
		local cfObject = Instance.new("CFrameValue")
		if camTweenRelativeTargetPart and camTweenRelativeTargetPart.Parent then
			cfObject.Value = CFrame.new(camTweenRelativeTargetPart.Position * -1) * camera.CFrame
		else
			cfObject.Value = camera.CFrame
		end
		local cameraTween = TweenService:Create(
			cfObject,
			TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut),
			{ Value = targetCF }
		)
		local cfChangedConn = cfObject.Changed:connect(function()
			if transitionCount == thisTransitionCount then
				camera.CameraType = Enum.CameraType.Scriptable
				if camTweenRelativeTargetPart and camTweenRelativeTargetPart.Parent then
					camera.CFrame = CFrame.new(camTweenRelativeTargetPart.Position) * cfObject.Value
				else
					camera.CFrame = cfObject.Value
				end
			end
		end)
		cameraTween:Play()
		cameraTween.Completed:Wait()
		cfChangedConn:Disconnect()
		cfChangedConn = nil
		cfObject = nil
	end

	-- Camera finished tweening
	if transitionCount == thisTransitionCount then
		if screenPart then
			-- Lock camera to screen
			if lockCameraConnection then
				lockCameraConnection:Disconnect()
				lockCameraConnection = nil
			end
			lockCameraConnection = RunService.RenderStepped:Connect(function()
				if transitionCount == thisTransitionCount then
					camera.CFrame = setCameraCF(screenPart)
				end
			end)
		else
			-- Return camera to character
			camera.CameraType = Enum.CameraType.Custom
			characterCamCF = nil
			characterCamCFIsRelative = false
			local character = localPlayer.Character
			if character and character.Parent then
				local humanoid = character:FindFirstChildOfClass("Humanoid")
				if humanoid then
					camera.CameraSubject = humanoid
				end
			end
			if manageLockingCharacterControl then
				setHumanoidControl(true)
			end
		end
	end
end

function toggleFullscreen()
	if button then -- Only toggle if button exists, as a user control
		if fullscreenModule.FocusedScreen ~= buttonTargetScreen then
			fullscreenModule.FocusScreen(buttonTargetScreen)
		else
			fullscreenModule.FocusScreen()
		end
	end
end

function backoutFullscreen()
	if button and fullscreenModule.FocusedScreen == buttonTargetScreen then
		-- Only backout if there is a button for usercontrol and it is hooked up to the current screen
		fullscreenModule.FocusScreen()
	end
end

function updateGamepadPromptVisibility()
	if button and button.Parent then
		local gamepadPrompt = button:FindFirstChild("GamepadPrompt")
		if gamepadPrompt then
			if UserInputService.GamepadEnabled then
				button.GamepadPrompt.Visible = false
				button.Active = false
			else
				button.GamepadPrompt.Visible = true
				button.Active = true
			end
		end
	end
end
UserInputService.GamepadConnected:connect(updateGamepadPromptVisibility)
UserInputService.GamepadDisconnected:connect(updateGamepadPromptVisibility)

function fullscreenModule.RemoveButton()
	if buttonConnection then
		buttonConnection:Disconnect()
		buttonConnection = nil
	end
	if gamepadButtonConnection then
		gamepadButtonConnection:Disconnect()
		gamepadButtonConnection = nil
	end
	if button and button.Parent then
		button:Destroy()
	end
	if buttonGui and buttonGui.Parent then
		buttonGui:Destroy()
	end
	button = nil
	buttonGui = nil
	buttonTargetScreen = nil
end

function fullscreenModule.CreateButtonToFocusScreen(screenPart)
	local playerGui = localPlayer:FindFirstChild("PlayerGui")
	if playerGui then
		if button or buttonGui then
			fullscreenModule.RemoveButton()
		end
		buttonTargetScreen = screenPart
		buttonGui = buttonGuiTemplate:clone()
		button = buttonGui.Button
		buttonConnection = button.Activated:connect(toggleFullscreen)

		gamepadButtonConnection = UserInputService.InputBegan:Connect(function(input)
			if input.UserInputType == Enum.UserInputType.Gamepad1 then
				if input.KeyCode == FULLSCREEN_GAMEPAD_BUTTON then
					toggleFullscreen()
				elseif input.KeyCode == BACKOUT_GAMEPAD_BUTTON then
					backoutFullscreen()
				end
			end
		end)

		updateButtonVisuals()
		buttonGui.Parent = playerGui
	end
end

return fullscreenModule