--!nonstrict
--[[
	// FileName: PlayerCarousel.lua
	// Written by: darthskrill
	// Description: Module for building the UI for the player selection carousel
]]

local PlayerCarousel = {}
PlayerCarousel.__index = PlayerCarousel

-- SERVICES
local GuiService = game:GetService("GuiService")
local CoreGuiService = game:GetService("CoreGui")
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")

-- CONSTANTS
local BACKGROUND_SELECTED_COLOR = Color3.fromRGB(0,162,255)
local BACKGROUND_DEFAULT_COLOR = Color3.fromRGB(0,0,0)
local PAGE_LAYOUT_TWEEN_TIME = 0.25

local CAROUSEL_DIVIDER_NAME = "_CarouselDivider"

-- VARIABLES
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
local selectedPlayer = nil
local uiPageLayout = nil
local playerChangedEvent = nil
local buttonToPlayerMap = {}
local playerToButtonMap = {}

local function getSortedCarouselButtons()
	local buttonChildIndexs = {}
	local carouselButtons = {}
	for childIndex, child in ipairs(selectedPlayer:GetChildren()) do
		if child:IsA("GuiObject") and child.Name ~= CAROUSEL_DIVIDER_NAME then
			table.insert(carouselButtons, child)
			buttonChildIndexs[child] = childIndex
		end
	end
	table.sort(carouselButtons, function(a, b)
		if a.LayoutOrder == b.LayoutOrder then
			return buttonChildIndexs[a] < buttonChildIndexs[b]
		end
		return a.LayoutOrder < b.LayoutOrder
	end)
	return carouselButtons
end

local function CreateMenuCarousel(theme)
	local playerSelection = Instance.new("Frame")
	playerSelection.Name = "PlayerCarousel"
	playerSelection.AnchorPoint = Vector2.new(0.5, 0.5)
	playerSelection.BackgroundTransparency = 1
	playerSelection.Position = UDim2.new(0.5,0,0.5,0)
	playerSelection.Size = UDim2.new(1, 0, 0.28, 0)
	playerSelection.ClipsDescendants = true

		local innerFrame = Instance.new("Frame")
		innerFrame.Name = "InnerFrame"
		innerFrame.AnchorPoint = Vector2.new(0.5, 0.5)
		innerFrame.BackgroundTransparency = 1
		innerFrame.Position = UDim2.new(0.5, 0, 0.5, 0)
		innerFrame.Size = UDim2.new(0.8, 0, 1, 0)
		innerFrame.ClipsDescendants = true
		innerFrame.Active = true
		innerFrame.Parent = playerSelection

			selectedPlayer = Instance.new("Frame")
			selectedPlayer.Name = "SelectedPlayer"
			selectedPlayer.AnchorPoint = Vector2.new(0.5, 0.5)
			selectedPlayer.BackgroundTransparency = 1
			selectedPlayer.Position = UDim2.new(0.5, 0, 0.5, 0)
			selectedPlayer.Size = UDim2.new(0, 100, 1, -10)
			selectedPlayer.Parent = innerFrame

				uiPageLayout = Instance.new("UIPageLayout")
				uiPageLayout.EasingDirection = Enum.EasingDirection.Out
				uiPageLayout.EasingStyle = Enum.EasingStyle.Quad
				uiPageLayout.Padding = UDim.new(0, 5)
				uiPageLayout.TweenTime = PAGE_LAYOUT_TWEEN_TIME
				uiPageLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
				uiPageLayout.VerticalAlignment = Enum.VerticalAlignment.Center
				uiPageLayout.TouchInputEnabled = false
				uiPageLayout.Circular = true
				uiPageLayout.SortOrder = Enum.SortOrder.LayoutOrder

				local aspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
				aspectRatioConstraint.DominantAxis = Enum.DominantAxis.Height
				aspectRatioConstraint.Parent = selectedPlayer

				playerChangedEvent = Instance.new("BindableEvent")
				playerChangedEvent.Name = "PlayerChanged"

				local lastPage = nil
				uiPageLayout:GetPropertyChangedSignal("CurrentPage"):Connect(function()
					if uiPageLayout.CurrentPage then
						if uiPageLayout.CurrentPage.Name == CAROUSEL_DIVIDER_NAME then
							local carouselButtons = getSortedCarouselButtons()
							if lastPage == carouselButtons[1] then
								uiPageLayout:Previous()
							else
								uiPageLayout:Next()
							end
						else
							uiPageLayout.CurrentPage.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR
							lastPage = uiPageLayout.CurrentPage
						end
					end
					if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject.Parent == uiPageLayout.Parent then
						GuiService.SelectedCoreObject.BackgroundColor3 = BACKGROUND_SELECTED_COLOR
					end
					GuiService.SelectedCoreObject = uiPageLayout.CurrentPage
					if uiPageLayout.CurrentPage then playerChangedEvent:Fire(buttonToPlayerMap[uiPageLayout.CurrentPage]) end
				end)
				uiPageLayout.Parent = selectedPlayer

		local nextButton = Instance.new("ImageButton")
		nextButton.Name = "NextButton"
		nextButton.Image = theme.ScrollRightImage
		nextButton.BackgroundTransparency = 1
		nextButton.AnchorPoint = Vector2.new(1,0.5)
		nextButton.Position = UDim2.new(1,-5,0.5,0)
		nextButton.Size = UDim2.new(0.3,0,0.3,0)
		nextButton.Selectable = false
		nextButton.Parent = playerSelection

			local nextButtonAspectRatio = Instance.new("UIAspectRatioConstraint")
			nextButtonAspectRatio.DominantAxis = Enum.DominantAxis.Width
			nextButtonAspectRatio.Parent = nextButton

		local prevButton = nextButton:Clone()
		prevButton.Name = "PrevButton"
		if theme.ScrollLeftImage ~= "" then
			prevButton.Image = theme.ScrollLeftImage
		else
			prevButton.Rotation = 180
		end
		prevButton.AnchorPoint = Vector2.new(0, 0.5)
		prevButton.Position = UDim2.new(0, 5, 0.5, 0)
		prevButton.Selectable = false
		prevButton.Parent = playerSelection

		local function moveChangePage(goToNext)
			if goToNext then
				uiPageLayout:Next()
			else
				uiPageLayout:Previous()
			end
		end

		nextButton.MouseButton1Click:Connect(function() moveChangePage(true) end)
		prevButton.MouseButton1Click:Connect(function() moveChangePage(false) end)

		local defaultChildrenSize = 3 -- aspectRatioConstraint, PageLayout, first button in pagelayout

		local function checkButtonVisibility()
			local lastInputIsTouch = UserInputService:GetLastInputType() == Enum.UserInputType.Touch
			prevButton.Visible = not lastInputIsTouch and (#selectedPlayer:GetChildren() > defaultChildrenSize)
			nextButton.Visible = not lastInputIsTouch and (#selectedPlayer:GetChildren() > defaultChildrenSize)
		end
		checkButtonVisibility()
		UserInputService.LastInputTypeChanged:Connect(checkButtonVisibility)

	return playerSelection
end

function PlayerCarousel:UpdateGuiTheme(theme)
	self.rbxGui.NextButton.Image = theme.ScrollRightImage

	if theme.ScrollLeftImage ~= "" then
		self.rbxGui.PrevButton.Image = theme.ScrollLeftImage
	else
		self.rbxGui.PrevButton.Image = theme.ScrollRightImage
		self.rbxGui.PrevButton.Rotation = 180
	end
end

function PlayerCarousel:FadeTowardsEdges()
	if not uiPageLayout.CurrentPage then
		return
	end

	local carouselButtons = getSortedCarouselButtons()
	local currentPageIndex = 0
	for index, button in ipairs(carouselButtons) do
		if button == uiPageLayout.CurrentPage then
			currentPageIndex = index
			break
		end
	end

	for index, button in ipairs(carouselButtons) do
		local distanceToLeft = (currentPageIndex - index) % #carouselButtons
		local distanceToRight = (index - currentPageIndex) % #carouselButtons
		local distance = math.min(distanceToLeft, distanceToRight)
		if distance >= 2 then
			button.ImageTransparency = 0.7
		elseif distance == 1 then
			button.ImageTransparency = 0.4
		else
			button.ImageTransparency = 0
		end
	end
end

function PlayerCarousel:AddCarouselDivider()
	local carouselDivider = selectedPlayer:FindFirstChild(CAROUSEL_DIVIDER_NAME)
	if carouselDivider then
		carouselDivider:Destroy()
	end

	local buttonCount = #selectedPlayer:GetChildren() - 1
	if buttonCount < 5 then
		return
	end

	carouselDivider = Instance.new("Frame")
	carouselDivider.Name = CAROUSEL_DIVIDER_NAME
	carouselDivider.Size = UDim2.new(1, 0, 1, 0)
	carouselDivider.BackgroundTransparency = 1
	carouselDivider.Parent = selectedPlayer
	carouselDivider.LayoutOrder = 1000000

	local line = Instance.new("Frame")
	line.Name = "line"
	line.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	line.BorderSizePixel = 0
	line.Position = UDim2.new(0.25, 0, 0, -3)
	line.Size = UDim2.new(0, 2, 1, 6)
	line.Parent = carouselDivider

	local line2 = line:Clone()
	line2.Position = UDim2.new(0.5, 0, 0, -3)
	line2.Parent = carouselDivider

	local line3 = line:Clone()
	line3.Position = UDim2.new(0.75, 0, 0, -3)
	line3.Parent = carouselDivider
end

function PlayerCarousel:RemovePlayerEntry(player)
	local button = playerToButtonMap[player]

	if button then
		playerToButtonMap[player] = nil
		buttonToPlayerMap[button] = nil

		button:Destroy()
		if uiPageLayout then
			uiPageLayout:ApplyLayout()
		end
		self:FadeTowardsEdges()
	end
end

function PlayerCarousel:ClearPlayerEntries()
	for button in pairs(buttonToPlayerMap) do
		button:Destroy()
	end

	buttonToPlayerMap = {}
	playerToButtonMap = {}
end

function PlayerCarousel:CreatePlayerEntry(player, distanceToLocalPlayer)
	local playerButton = playerToButtonMap[player]
	if playerButton then
		playerButton.LayoutOrder = distanceToLocalPlayer
		return
	end

	local button = Instance.new("ImageButton")
	button.Name = player.Name
	button.BorderSizePixel = 0
	button.LayoutOrder = distanceToLocalPlayer
	button.BackgroundColor3 = Color3.fromRGB(0,0,0)
	button.BackgroundTransparency = 0
	button.Size = UDim2.new(1, 0, 1, 0)

	button.SelectionLost:connect(function() button.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR end)
	button.SelectionGained:connect(function() button.BackgroundColor3 = BACKGROUND_SELECTED_COLOR end)

	local tweenStyle = TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, -1, true)
	local buttonLoadingTween
	buttonLoadingTween = TweenService:Create(
		button,
		tweenStyle,
		{BackgroundColor3 = Color3.fromRGB(255,255,255)}
	)
	buttonLoadingTween:Play()

	buttonToPlayerMap[button] = player
	playerToButtonMap[player] = button

	button.MouseButton1Click:Connect(function()
		uiPageLayout:JumpTo(button)
	end)

	button.Parent = selectedPlayer

	spawn(function()
		local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
		button.Image = ContextMenuUtil:GetHeadshotForPlayer(player)
		buttonLoadingTween:Cancel()
		buttonLoadingTween = nil
		if button == GuiService.SelectedCoreObject then
			button.BackgroundColor3 = BACKGROUND_SELECTED_COLOR
		else
			button.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR
		end
	end)
end

function PlayerCarousel:SwitchToPlayerEntry(player, dontTween)
	if not player then return end

	local button = playerToButtonMap[player]
	if not button then
		self:CreatePlayerEntry(player, 0)
		button = playerToButtonMap[player]
	end

	if dontTween then
		uiPageLayout.TweenTime = 0
	end
	uiPageLayout:JumpTo(button)
	spawn(function()
		uiPageLayout.TweenTime = PAGE_LAYOUT_TWEEN_TIME
	end)
end

function PlayerCarousel:OffsetPlayerEntry(offset)
	if offset == 0 then return end

	if offset > 0 then
		uiPageLayout:Next()
	else
		uiPageLayout:Previous()
	end
end

function PlayerCarousel:GetSelectedPlayer()
	return buttonToPlayerMap[uiPageLayout.CurrentPage]
end

function PlayerCarousel.new(theme)
	local obj = setmetatable({}, PlayerCarousel)

	obj.rbxGui = CreateMenuCarousel(theme)
	obj.PlayerChanged = playerChangedEvent.Event

	playerChangedEvent.Event:Connect(function()
		obj:FadeTowardsEdges()
	end)

	return obj
end

return PlayerCarousel
