local function q(q,R,Y,h)if R then local X=R:ScreenPointToRay(q.X,q.Y)local P=workspace:Raycast(X.Origin,X.Direction*h)if P then local q=P.Instance local R=q:FindFirstAncestorOfClass("\077\111\100\101\108")if R then local q=false local h={}for q,R in pairs(R:GetDescendants())do if R:IsA("\066\097\115\101\080\097\114\116")or R:IsA("\068\101\099\097\108")then table.insert(h,R.Transparency)end end for R,Y in ipairs(h)do if Y==0 then q=true end end if q then local q=Y:GetPlayerFromCharacter(R)if q then return q end end end end end return nil end ProfileCard.defaultProps={Players=game:GetService("\080\108\097\121\101\114\115");MainFrame=MainFrame,Camera=workspace.CurrentCamera,UpdateProfileRemoteFunction=script.Parent.Parent.RemoteEvents.UpdateProfileRemoteFunction,PulsePrompt=require(script.Parent.PulsePrompt)}local R={KeyboardView=Vector3.new(0,6,0),CenterView=Vector3.new(0,1,0);OriginalView=Vector3.new(0,0,0)}local Y={Enum.UserInputType.Gamepad1;Enum.UserInputType.Gamepad2;Enum.UserInputType.Gamepad3,Enum.UserInputType.Gamepad4;Enum.UserInputType.Gamepad5,Enum.UserInputType.Gamepad6,Enum.UserInputType.Gamepad7;Enum.UserInputType.Gamepad8}function ProfileCard.init(Y)Y.state={userId=Y.props.userId,isEditing=false;showStatusError=false,showPrompt=true;viewType=R.OriginalView;activeGamepad=false}Y.setHumanoidMovementEnabled=function(q)local R=Y.props.Players.LocalPlayer local h=R.Character or R.CharacterAdded:Wait()local X=h:WaitForChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if X then X.Anchored=not q end end Y.setKeyboardMovementEnabled=function(q)if q then Y.props.controls:Enable()ContextActionService:BindActionAtPriority("\082\105\103\104\116\077\111\117\115\101\068\105\115\097\098\108\101",function()return Enum.ContextActionResult.Pass end,false,Enum.ContextActionPriority.Medium.Value,Enum.UserInputType.MouseButton2)else Y.props.controls:Disable()ContextActionService:BindActionAtPriority("\082\105\103\104\116\077\111\117\115\101\068\105\115\097\098\108\101",function()return Enum.ContextActionResult.Sink end,false,Enum.ContextActionPriority.Medium.Value,Enum.UserInputType.MouseButton2)end end Y.getOtherGuis=function()local q={}local R=Y.props.Players.LocalPlayer:FindFirstChild("\080\108\097\121\101\114\071\117\105")if R then for R,Y in pairs(R:GetDescendants())do if(Y:IsA("\083\099\114\101\101\110\071\117\105")or Y:IsA("\066\105\108\108\098\111\097\114\100\071\117\105"))and(not Y:GetAttribute("\083\104\111\119\073\110\080\114\111\102\105\108\101\067\097\114\100")and Y.Enabled)then table.insert(q,Y)end end end return q end Y.getShownCoreGuis=function()local q=Cryo.List.filter(Enum.CoreGuiType:GetEnumItems(),function(q)if Enum.CoreGuiType.PlayerList==q or Enum.CoreGuiType.All==q then return false end return StarterGui:GetCoreGuiEnabled(q)end)return q end Y.hideGuis=function()if callbacks.hideOtherGuis.isCallbackSet()then callbacks.hideOtherGuis.fireCallback()return end Y.disabledGuis=Y.getOtherGuis()for q,R in ipairs(Y.disabledGuis)do R.Enabled=false end Y.disabledCoreGuis=Y.getShownCoreGuis()for q,R in ipairs(Y.disabledCoreGuis)do StarterGui:SetCoreGuiEnabled(R,false)end Y.modalEnabled=UserInputService.ModalEnabled UserInputService.ModalEnabled=true end Y.showGuis=function()if callbacks.showOtherGuis.isCallbackSet()then callbacks.showOtherGuis.fireCallback()return end for q,R in ipairs(Y.disabledGuis)do R.Enabled=true end Y.disabledGuis={}for q,R in ipairs(Y.disabledCoreGuis)do StarterGui:SetCoreGuiEnabled(R,true)end Y.disabledCoreGuis={}UserInputService.ModalEnabled=Y.modalEabled end Y.toggleEdit=function(q)if not Y.state.isEditing then Y:setState({isEditing=true})Y.props.Camera.CameraType=Enum.CameraType.Scriptable Y.hideGuis()if UserInputService.TouchEnabled then Y:setState({viewType=R.CenterView})end Y.setHumanoidMovementEnabled(false)if UserInputService.KeyboardEnabled then Y.setKeyboardMovementEnabled(false)end else local h=Y.props.UpdateProfileRemoteFunction:InvokeServer(Y.props.Players.LocalPlayer,q.Parent.Status.Text)if h.success then Y.showGuis()Y:setState({isEditing=false;showStatusError=false;viewType=R.OriginalView})Y.props.Camera.CameraType=Enum.CameraType.Custom Y.setHumanoidMovementEnabled(true)if UserInputService.KeyboardEnabled then Y.setKeyboardMovementEnabled(true)end else Y:setState({showStatusError=true})if UserInputService.TouchEnabled then Y:setState({viewType=R.CenterView})end end end end Y.updateViewToKeyboard=function()Y:setState({viewType=R.KeyboardView})end Y.unmount=function()Y:setState({userId=Roact.None,isEditing=false})end Y.onInputBegan=function(h,X)local P=h.UserInputType==Enum.UserInputType.MouseButton1 or h.UserInputType==Enum.UserInputType.Touch if P and(not Y.state.isEditing and not X)then local X=h.Position local P=Y.props.Players.LocalPlayer:WaitForChild("\080\108\097\121\101\114\071\117\105")local B=P:FindFirstChild("\080\114\111\102\105\108\101\067\097\114\100")local U=true if B then local q=P:GetGuiObjectsAtPosition(X.X,X.Y)for q,R in ipairs(q)do local Y=R:FindFirstAncestorOfClass("\066\105\108\108\098\111\097\114\100\071\117\105")if Y and Y==B then U=false end end end if U then local h=q(X,Y.props.Camera,Y.props.Players,Y.props.configuration.maxClickDistance)if h then Y:setState({userId=h.UserId,showPrompt=false;viewType=R.OriginalView})else Y.unmount()end end end end Y.checkViewport=function(q)local R,h=Y.props.Camera:WorldToViewportPoint(q)return h end Y.determineStudOffset=function(q)if not q then return Vector3.new(0,0,0)elseif Y.state.viewType==R.KeyboardView or Y.state.viewType==R.CenterView then return Y.state.viewType end local h=q.CFrame local X=h+Vector3.new(Constants.STUD_OFFSET_X,1,0)local P=h+Vector3.new(-Constants.STUD_OFFSET_X,1,0)local B=Vector3.new(0,0,0)if Y.checkViewport(X.Position)then B=Vector3.new(Constants.STUD_OFFSET_X,1,0)elseif Y.checkViewport(P.Position)then B=Vector3.new(-Constants.STUD_OFFSET_X,1,0)end if Y.state.viewType==R.OriginalView then R.OriginalView=B end return B end Y.getAdornee=function(q)local R=q==Y.props.Players.LocalPlayer and q.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")or Anchor.createAnchor(q)return R end Y.profileCard=Roact.createRef()end function ProfileCard.render(q)if table.find(Y,UserInputService:GetLastInputType())~=nil or q.state.activeGamepad then return end local R=q.props.Players local h if q.state.userId then h=R:GetPlayerByUserId(q.state.userId)end return Roact.createFragment({Roact.createElement(ProfileCardContext.Provider,{userId=q.state.userId},{ProfileCard=h and Roact.createElement("\066\105\108\108\098\111\097\114\100\071\117\105",{Active=true,Size=UDim2.new(0,Constants.GUI_SIZE_X,0,Constants.GUI_SIZE_Y),AlwaysOnTop=q.props.configuration.alwaysOnTop,Adornee=q.getAdornee(h);StudsOffset=q.determineStudOffset(h.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116"));[Roact.Ref]=q.profileCard},{MainFrame=Roact.createElement(q.props.MainFrame,{controls=q.props.controls;isEditing=q.state.isEditing;toggleEdit=q.toggleEdit;showStatusError=q.state.showStatusError;updateViewToKeyboard=q.updateViewToKeyboard})}),ExternalEventConnection=Roact.createElement(ExternalEventConnection,{event=UserInputService.InputBegan;callback=q.onInputBegan});BlurEffectPortal=q.state.isEditing and(q.props.configuration.showBlur and Roact.createElement(Roact.Portal,{target=q.props.Camera},{BlurEffect=Roact.createElement("\066\108\117\114\069\102\102\101\099\116")}))});PersonalIndicator=Roact.createElement(q.props.PulsePrompt,{Players=R;adornee=q.props.adornee;isEnabled=q.state.showPrompt;userId=R.LocalPlayer.UserId})})end function ProfileCard.didMount(q)UserInputService.LastInputTypeChanged:Connect(function(h)if table.find(Y,h)~=nil then q:setState({activeGamepad=true;isEditing=false;showStatusError=false,viewType=R.OriginalView})q.setHumanoidMovementEnabled(true)q.props.Camera.CameraType=Enum.CameraType.Custom return end if q.state.activeGamepad then q:setState({activeGamepad=false})end end)end function ProfileCard.didUpdate(q)if q.state.userId then(q.profileCard:getValue()):SetAttribute("\083\104\111\119\073\110\080\114\111\102\105\108\101\067\097\114\100",true)local R=q.getAdornee(q.props.Players:GetPlayerByUserId(q.state.userId))local Y=q.determineStudOffset(R);(coroutine.wrap(function(R)local h=q.checkViewport(R.Position)while h and R do wait(1)local X=R.CFrame X=X*CFrame.new(Y)h=q.checkViewport(R.Position)end q.unmount()end))(R)end end return withConfiguration(ProfileCard)

local function findSelectedPlayer(objectPosition, camera, playersService, maxClickDistance)
	if camera then
		local ray = camera:ScreenPointToRay(objectPosition.X, objectPosition.Y)
		local raycastResult = workspace:Raycast(ray.Origin, ray.Direction * maxClickDistance) -- sets the max click distance relative to camera
		if raycastResult then
			local hitPart = raycastResult.Instance
			local character = hitPart:FindFirstAncestorOfClass("Model")
			if character then
				-- Prevents selection of an invisible character
				local isVisible = false
				local transparencyValues = {}
				for _, descendant in pairs(character:GetDescendants()) do
					if descendant:IsA("BasePart") or descendant:IsA("Decal") then
						table.insert(transparencyValues, descendant.Transparency)
					end
				end

				-- Check that all character parts are invisible (Transparency = 1)
				for _, value in ipairs(transparencyValues) do
					if value == 0 then
						isVisible = true
					end
				end

				if isVisible then
					local targetPlayer = playersService:GetPlayerFromCharacter(character)
					if targetPlayer then
						return targetPlayer
					end
				end
			end
		end
	end
	return nil
end

ProfileCard.defaultProps = {
	Players = game:GetService("Players"),
	MainFrame = MainFrame,
	Camera = workspace.CurrentCamera,
	UpdateProfileRemoteFunction = script.Parent.Parent.RemoteEvents.UpdateProfileRemoteFunction,
	PulsePrompt = require(script.Parent.PulsePrompt),
}

local ViewTypes = {
	KeyboardView = Vector3.new(0, 6, 0),
	CenterView = Vector3.new(0, 1, 0),
	OriginalView = Vector3.new(0, 0, 0),
}

local gamepadInputs = {
	Enum.UserInputType.Gamepad1,
	Enum.UserInputType.Gamepad2,
	Enum.UserInputType.Gamepad3,
	Enum.UserInputType.Gamepad4,
	Enum.UserInputType.Gamepad5,
	Enum.UserInputType.Gamepad6,
	Enum.UserInputType.Gamepad7,
	Enum.UserInputType.Gamepad8,
}

function ProfileCard:init()
	self.state = {
		userId = self.props.userId,
		isEditing = false,
		showStatusError = false,
		showPrompt = true,
		viewType = ViewTypes.OriginalView,
		activeGamepad = false,
	}

	self.setHumanoidMovementEnabled = function(canMoveBool)
		-- Prevents character movement
		local LocalPlayer = self.props.Players.LocalPlayer
		local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
		local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
		if HumanoidRootPart then
			HumanoidRootPart.Anchored = not canMoveBool
		end
	end

	self.setKeyboardMovementEnabled = function(enabledBool)
		-- Prevents character movement and camera rotation
		if enabledBool then
			self.props.controls:Enable()
			ContextActionService:BindActionAtPriority("RightMouseDisable", function()
				return Enum.ContextActionResult.Pass
			end, false, Enum.ContextActionPriority.Medium.Value, Enum.UserInputType.MouseButton2)
		else
			self.props.controls:Disable()
			ContextActionService:BindActionAtPriority("RightMouseDisable", function()
				return Enum.ContextActionResult.Sink
			end, false, Enum.ContextActionPriority.Medium.Value, Enum.UserInputType.MouseButton2)
		end
	end

	self.getOtherGuis = function()
		local res = {}
		local playerGui = self.props.Players.LocalPlayer:FindFirstChild("PlayerGui")
		if playerGui then
			for _, instance in pairs(playerGui:GetDescendants()) do
				if
					(instance:IsA("ScreenGui") or instance:IsA("BillboardGui"))
					and not instance:GetAttribute("ShowInProfileCard")
					and instance.Enabled
				then
					table.insert(res, instance)
				end
			end
		end

		return res
	end

	self.getShownCoreGuis = function()
		local shownCoreGuis = Cryo.List.filter(Enum.CoreGuiType:GetEnumItems(), function(coreGuiType)
			-- The inclusion of these GUIs will cause the entire screen to flash after an edit to the ProfileCard is saved. Filtering out for now.
			if Enum.CoreGuiType.PlayerList == coreGuiType or Enum.CoreGuiType.All == coreGuiType then
				return false
			end

			return StarterGui:GetCoreGuiEnabled(coreGuiType)
		end)
		return shownCoreGuis
	end

	self.hideGuis = function()
		if callbacks.hideOtherGuis.isCallbackSet() then
			callbacks.hideOtherGuis.fireCallback()
			return
		end

		-- Hide ScreenGuis
		self.disabledGuis = self.getOtherGuis()
		for _, gui in ipairs(self.disabledGuis) do
			gui.Enabled = false
		end

		-- Hide CoreGuis
		self.disabledCoreGuis = self.getShownCoreGuis()
		for _, gui in ipairs(self.disabledCoreGuis) do
			StarterGui:SetCoreGuiEnabled(gui, false)
		end

		-- Hide mobile controls
		self.modalEnabled = UserInputService.ModalEnabled
		UserInputService.ModalEnabled = true
	end

	self.showGuis = function()
		if callbacks.showOtherGuis.isCallbackSet() then
			callbacks.showOtherGuis.fireCallback()
			return
		end

		-- Show guis that were hidden by hideGuis()
		for _, gui in ipairs(self.disabledGuis) do
			gui.Enabled = true
		end
		self.disabledGuis = {}

		for _, gui in ipairs(self.disabledCoreGuis) do
			StarterGui:SetCoreGuiEnabled(gui, true)
		end
		self.disabledCoreGuis = {}

		UserInputService.ModalEnabled = self.modalEabled
	end

	self.toggleEdit = function(object)
		if not self.state.isEditing then
			self:setState({ isEditing = true })
			self.props.Camera.CameraType = Enum.CameraType.Scriptable
			self.hideGuis()

			if UserInputService.TouchEnabled then
				self:setState({ viewType = ViewTypes.CenterView })
			end

			-- Prevent character movement
			self.setHumanoidMovementEnabled(false)
			if UserInputService.KeyboardEnabled then
				self.setKeyboardMovementEnabled(false)
			end
		else
			local filteredTextTable = self.props.UpdateProfileRemoteFunction:InvokeServer(
				self.props.Players.LocalPlayer,
				object.Parent.Status.Text
			)

			if filteredTextTable.success then
				self.showGuis()
				self:setState({ isEditing = false, showStatusError = false, viewType = ViewTypes.OriginalView })
				self.props.Camera.CameraType = Enum.CameraType.Custom

				-- Restore movement
				self.setHumanoidMovementEnabled(true)
				if UserInputService.KeyboardEnabled then
					self.setKeyboardMovementEnabled(true)
				end
			else
				self:setState({ showStatusError = true })

				if UserInputService.TouchEnabled then
					self:setState({ viewType = ViewTypes.CenterView })
				end
			end
		end
	end

	self.updateViewToKeyboard = function()
		self:setState({ viewType = ViewTypes.KeyboardView })
	end

	self.unmount = function()
		self:setState({ userId = Roact.None, isEditing = false })
	end

	self.onInputBegan = function(input, gameProcessedEvent)
		local isValidInput = input.UserInputType == Enum.UserInputType.MouseButton1
			or input.UserInputType == Enum.UserInputType.Touch
		if isValidInput and not self.state.isEditing and not gameProcessedEvent then
			-- Prevents opening a new profile card while editing; also prevents profile card from closing when interacting with engine related UI (eg. chat button)
			local inputPos = input.Position
			local playerGui = self.props.Players.LocalPlayer:WaitForChild("PlayerGui")
			local profileCard = playerGui:FindFirstChild("ProfileCard")
			local shouldUnmount = true

			if profileCard then
				local guis = playerGui:GetGuiObjectsAtPosition(inputPos.X, inputPos.Y)
				for _, g in ipairs(guis) do
					local billboard = g:FindFirstAncestorOfClass("BillboardGui")
					if billboard and billboard == profileCard then
						shouldUnmount = false
					end
				end
			end

			if shouldUnmount then
				local player = findSelectedPlayer(
					inputPos,
					self.props.Camera,
					self.props.Players,
					self.props.configuration.maxClickDistance
				)
				if player then
					-- Opens a new profile card with the relevant player details
					self:setState({ userId = player.UserId, showPrompt = false, viewType = ViewTypes.OriginalView })
				else
					-- Hide an open profile card if input is not on the card itself
					self.unmount()
				end
			end
		end
	end

	self.checkViewport = function(testPos)
		local _, inViewport = self.props.Camera:WorldToViewportPoint(testPos)
		return inViewport
	end

	self.determineStudOffset = function(object)
		if not object then
			return Vector3.new(0, 0, 0)
		elseif self.state.viewType == ViewTypes.KeyboardView or self.state.viewType == ViewTypes.CenterView then
			return self.state.viewType
		end

		local copyCFrame = object.CFrame
		local rightCFrame = copyCFrame + Vector3.new(Constants.STUD_OFFSET_X, 1, 0)
		local leftCFrame = copyCFrame + Vector3.new(-Constants.STUD_OFFSET_X, 1, 0)
		local studsOffsetVector = Vector3.new(0, 0, 0)

		if self.checkViewport(rightCFrame.Position) then
			studsOffsetVector = Vector3.new(Constants.STUD_OFFSET_X, 1, 0)
		elseif self.checkViewport(leftCFrame.Position) then
			studsOffsetVector = Vector3.new(-Constants.STUD_OFFSET_X, 1, 0)
		end

		if self.state.viewType == ViewTypes.OriginalView then
			ViewTypes.OriginalView = studsOffsetVector
		end

		return studsOffsetVector
	end

	self.getAdornee = function(player)
		local adornee = player == self.props.Players.LocalPlayer and player.Character:FindFirstChild("HumanoidRootPart")
			or Anchor.createAnchor(player)
		return adornee
	end

	self.profileCard = Roact.createRef()
end

function ProfileCard:render()
	-- Prevent gamepad interaction
	if table.find(gamepadInputs, UserInputService:GetLastInputType()) ~= nil or self.state.activeGamepad then
		return
	end

	local Players = self.props.Players
	local player

	if self.state.userId then
		player = Players:GetPlayerByUserId(self.state.userId)
	end

	return Roact.createFragment({
		Roact.createElement(ProfileCardContext.Provider, { userId = self.state.userId }, {
			ProfileCard = player and Roact.createElement("BillboardGui", {
				Active = true,
				Size = UDim2.new(0, Constants.GUI_SIZE_X, 0, Constants.GUI_SIZE_Y),
				AlwaysOnTop = self.props.configuration.alwaysOnTop,
				Adornee = self.getAdornee(player),
				StudsOffset = self.determineStudOffset(player.Character:FindFirstChild("HumanoidRootPart")),

				[Roact.Ref] = self.profileCard,
			}, {
				MainFrame = Roact.createElement(self.props.MainFrame, {
					controls = self.props.controls,
					isEditing = self.state.isEditing,
					toggleEdit = self.toggleEdit,
					showStatusError = self.state.showStatusError,
					updateViewToKeyboard = self.updateViewToKeyboard,
				}),
			}),
			ExternalEventConnection = Roact.createElement(ExternalEventConnection, {
				event = UserInputService.InputBegan, -- Maybe use Touch/Tap in world, specifically for mobile
				callback = self.onInputBegan,
			}),
			-- Creates a blur effect when editing
			BlurEffectPortal = self.state.isEditing and self.props.configuration.showBlur and Roact.createElement(
				Roact.Portal,
				{ target = self.props.Camera },
				{ BlurEffect = Roact.createElement("BlurEffect") }
			),
		}),
		PersonalIndicator = Roact.createElement(self.props.PulsePrompt, {
			Players = Players,
			adornee = self.props.adornee,
			isEnabled = self.state.showPrompt,
			userId = Players.LocalPlayer.UserId,
		}),
	})
end

function ProfileCard:didMount()
	UserInputService.LastInputTypeChanged:Connect(function(lastInputType)
		if table.find(gamepadInputs, lastInputType) ~= nil then
			self:setState({
				activeGamepad = true,
				isEditing = false,
				showStatusError = false,
				viewType = ViewTypes.OriginalView,
			})
			self.setHumanoidMovementEnabled(true)
			self.props.Camera.CameraType = Enum.CameraType.Custom
			return
		end

		if self.state.activeGamepad then
			self:setState({ activeGamepad = false })
		end
	end)
end

function ProfileCard:didUpdate()
	if self.state.userId then
		self.profileCard:getValue():SetAttribute("ShowInProfileCard", true)

		-- Hide the Profile Card from view once it leaves the screen
		local adornee = self.getAdornee(self.props.Players:GetPlayerByUserId(self.state.userId))
		local adorneeOffset = self.determineStudOffset(adornee)

		coroutine.wrap(function(object)
			local inViewport = self.checkViewport(object.Position)
			while inViewport and object do
				wait(1)
				local newCFrame = object.CFrame
				newCFrame = newCFrame * CFrame.new(adorneeOffset)
				inViewport = self.checkViewport(object.Position)
			end
			self.unmount()
		end)(adornee)
	end
end

return withConfiguration(ProfileCard)
