local T=game:GetService("\080\104\121\115\105\099\115\083\101\114\118\105\099\101")local O=game:GetService("\082\117\110\083\101\114\118\105\099\101")local a=script.Parent.Parent local D=require(a.Constants)local w=require(a.Packages.Roact)local v=require(a.Packages.StudioComponents)local H=v.Button local t=v.MainButton local A=require(script.Parent.ListView)local c=require(script.Parent.GridView)local I=require(script.Parent.TabButton)local Q=require(script.Parent.DeleteGroupWidget)local q=require(script.Parent.GroupNameWidget)local C=require(script.Parent.AddToGroup)local V=T:GetMaxCollisionGroups()local B=D.PollInterval local L=w.Component:extend("\065\112\112")local P={game:GetService("\087\111\114\107\115\112\097\099\101"),game:GetService("\076\105\103\104\116\105\110\103");game:GetService("\077\097\116\101\114\105\097\108\083\101\114\118\105\099\101");game:GetService("\082\101\112\108\105\099\097\116\101\100\070\105\114\115\116");game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101");game:GetService("\083\101\114\118\101\114\083\099\114\105\112\116\083\101\114\118\105\099\101");game:GetService("\083\101\114\118\101\114\083\116\111\114\097\103\101");game:GetService("\083\116\097\114\116\101\114\071\117\105"),game:GetService("\083\116\097\114\116\101\114\080\097\099\107");game:GetService("\083\116\097\114\116\101\114\080\108\097\121\101\114");game:GetService("\083\111\117\110\100\083\101\114\118\105\099\101")}function L.init(O)local a=T:GetRegisteredCollisionGroups()O:setState({View="\076\105\115\116",Groups=a,SelectedGroupName=a[1].name;CreatingGroup=false,RenamingGroup=false,DeletingGroup=false})O.setGroupsCollidable=function(a,D,w)local v,H=pcall(function()return T:CollisionGroupSetCollidable(a,D,w)end)if not v then warn(H)end O:updateGroups()end O.batchSetCollisionGroup=function(T,a)local D,w=pcall(function()for T,O in ipairs(T)do O.CollisionGroup=a end end)if not D then warn(w)end O:updateGroups()end O.createCollisionGroup=function(a)local D,w=pcall(function()return T:RegisterCollisionGroup(a)end)if not D then warn(w)end O:updateGroups()end O.renameCollisionGroup=function(a,D)local w,v=pcall(function()return T:RenameCollisionGroup(a,D)end)if not w then warn(v)end for T,O in P do for T,O in O:GetDescendants()do if O:IsA("\066\097\115\101\080\097\114\116")and O.CollisionGroup==a then O.CollisionGroup=D end end end if O.state.SelectedGroupName==a then O:setState({SelectedGroupName=D})end O:updateGroups()end O.deleteCollisionGroup=function(a)local D,w=pcall(function()return T:UnregisterCollisionGroup(a)end)if not D then warn(w)end for T,O in P do for T,O in O:GetDescendants()do if O:IsA("\066\097\115\101\080\097\114\116")and O.CollisionGroup==a then O.CollisionGroup="\068\101\102\097\117\108\116"end end end O:updateGroups()end end function L.updateGroups(O)local a=T:GetRegisteredCollisionGroups()local D=O.state.SelectedGroupName local w for T,O in ipairs(a)do if O.name==D then w=D break end end O:setState({Groups=a,SelectedGroupName=w or a[1].name})end local function Z(T,O)if#T~=#O then return true end for a=1,#T,1 do local D=T[a]local w=O[a]if D.name~=w.name then return true elseif D.mask~=w.mask then return true end end return false end function L.didMount(a)local D=os.clock()+B a.pollConnection=O.Heartbeat:Connect(function()if os.clock()>=D then local O=a.state.Groups local w=T:GetRegisteredCollisionGroups()if Z(O,w)then a:updateGroups()end D+=B end end)end function L.willUnmount(T)T.pollConnection:Disconnect()end function L.render(T)local O=#T.state.Groups>=V local a=T.state.CreatingGroup or T.state.RenamingGroup or T.state.DeletingGroup local D=T.state.SelectedGroupName return w.createFragment({CreateWidget=T.state.CreatingGroup and w.createElement(q,{Title="\067\114\101\097\116\101 \071\114\111\117\112";Action="\067\114\101\097\116\101",OnClosed=function()T:setState({CreatingGroup=false})end;OnSubmitted=function(O)T.createCollisionGroup(O)T:setState({CreatingGroup=false})end});RenameWidget=T.state.RenamingGroup and w.createElement(q,{Title=string.format("\082\101\110\097\109\101 \071\114\111\117\112 \040\037\115\041",D),Action="\082\101\110\097\109\101";OnClosed=function()T:setState({RenamingGroup=false})end;OnSubmitted=function(O)T.renameCollisionGroup(D,O)T:setState({RenamingGroup=false})end});DeleteWidget=T.state.DeletingGroup and w.createElement(Q,{Title=string.format("\068\101\108\101\116\101 \071\114\111\117\112 \040\037\115\041",D),GroupName=D,OnClosed=function()T:setState({DeletingGroup=false})end,OnActivated=function()T.deleteCollisionGroup(D)T:setState({DeletingGroup=false})end}),Tabs=w.createElement("\070\114\097\109\101",{Size=UDim2.new(1,0,0,30),BackgroundTransparency=1},{Layout=w.createElement("\085\073\076\105\115\116\076\097\121\111\117\116",{SortOrder=Enum.SortOrder.LayoutOrder,FillDirection=Enum.FillDirection.Horizontal}),ListViewButton=w.createElement(I,{LayoutOrder=0,Size=UDim2.fromScale(.5,1);Text="\076\105\115\116 \086\105\101\119";Selected=T.state.View=="\076\105\115\116",OnActivated=function()T:updateGroups()T:setState({View="\076\105\115\116"})end,Disabled=a});GridViewButton=w.createElement(I,{LayoutOrder=1,Size=UDim2.fromScale(.5,1);Text="\071\114\105\100 \086\105\101\119",Selected=T.state.View=="\071\114\105\100";OnActivated=function()T:updateGroups()T:setState({View="\071\114\105\100"})end;Disabled=a})}),Inner=w.createElement("\070\114\097\109\101",{AnchorPoint=Vector2.new(0,1),Position=UDim2.fromScale(0,1),Size=UDim2.new(1,0,1,-31);BackgroundTransparency=1},{Padding=w.createElement("\085\073\080\097\100\100\105\110\103",{PaddingLeft=UDim.new(0,4),PaddingRight=UDim.new(0,4);PaddingTop=UDim.new(0,4);PaddingBottom=UDim.new(0,4)}),Views=w.createElement("\070\114\097\109\101",{Size=UDim2.new(1,0,1,-32);BackgroundTransparency=1},{List=T.state.View=="\076\105\115\116"and w.createElement(A,{Groups=T.state.Groups,SelectedGroupName=T.state.SelectedGroupName,SetSelectedGroupName=function(O)T:setState({SelectedGroupName=O})end;SetGroupsCollidable=T.setGroupsCollidable,Disabled=a});Grid=T.state.View=="\071\114\105\100"and w.createElement(c,{Groups=T.state.Groups,SetGroupsCollidable=T.setGroupsCollidable;Disabled=a})}),Actions=w.createElement("\070\114\097\109\101",{AnchorPoint=Vector2.new(0,1),Position=UDim2.fromScale(0,1),Size=UDim2.new(1,0,0,28);BackgroundTransparency=1},{Layout=w.createElement("\085\073\076\105\115\116\076\097\121\111\117\116",{SortOrder=Enum.SortOrder.LayoutOrder,FillDirection=Enum.FillDirection.Horizontal,Padding=UDim.new(0,4)}),Create=w.createElement(t,{LayoutOrder=0;Size=UDim2.new(0,28,1,0);Text="";OnActivated=function()T:setState({CreatingGroup=true})end;Disabled=a or O},{Icon=w.createElement("\073\109\097\103\101\076\097\098\101\108",{AnchorPoint=Vector2.new(.5,.5);Position=UDim2.fromScale(.5,.5),Size=UDim2.fromOffset(16,16);BackgroundTransparency=1;Image="\114\098\120\097\115\115\101\116\105\100\058\047\047\054\054\056\056\056\051\057\051\052\051",ImageTransparency=(a or O)and.75 or 0})}),AddTo=T.state.View=="\076\105\115\116"and w.createElement(C,{BatchSetCollisionGroup=T.batchSetCollisionGroup,SelectedGroupName=T.state.SelectedGroupName,Disabled=a}),Rename=T.state.View=="\076\105\115\116"and w.createElement(H,{LayoutOrder=2;Size=UDim2.new(.32,-18,1,0);Text="\082\101\110\097\109\101",OnActivated=function()T:setState({RenamingGroup=true})end,Disabled=T.state.SelectedGroupName=="\068\101\102\097\117\108\116"or a});Delete=T.state.View=="\076\105\115\116"and w.createElement(H,{LayoutOrder=3;Size=UDim2.new(.3,-18,1,0),Text="\068\101\108\101\116\101",OnActivated=function()T:setState({DeletingGroup=true})end;Disabled=T.state.SelectedGroupName=="\068\101\102\097\117\108\116"or a})})})})end return L
local PhysicsService = game:GetService("PhysicsService")
local RunService = game:GetService("RunService")

local Plugin = script.Parent.Parent
local Constants = require(Plugin.Constants)

local Roact = require(Plugin.Packages.Roact)
local StudioComponents = require(Plugin.Packages.StudioComponents)

local Button = StudioComponents.Button
local MainButton = StudioComponents.MainButton
local ListView = require(script.Parent.ListView)
local GridView = require(script.Parent.GridView)
local TabButton = require(script.Parent.TabButton)
local DeleteGroupWidget = require(script.Parent.DeleteGroupWidget)
local GroupNameWidget = require(script.Parent.GroupNameWidget)
local AddToGroup = require(script.Parent.AddToGroup)

local MAX_GROUPS = PhysicsService:GetMaxCollisionGroups()
local POLL_INTERVAL = Constants.PollInterval

local App = Roact.Component:extend("App")

local renameTargetAncestors = {
	game:GetService("Workspace"),
	game:GetService("Lighting"),
	game:GetService("MaterialService"),
	game:GetService("ReplicatedFirst"),
	game:GetService("ReplicatedStorage"),
	game:GetService("ServerScriptService"),
	game:GetService("ServerStorage"),
	game:GetService("StarterGui"),
	game:GetService("StarterPack"),
	game:GetService("StarterPlayer"),
	game:GetService("SoundService"),
}

function App:init()
	local groups = PhysicsService:GetRegisteredCollisionGroups()
	self:setState({
		View = "List",
		Groups = groups,
		SelectedGroupName = groups[1].name,
		CreatingGroup = false,
		RenamingGroup = false,
		DeletingGroup = false,
	})
	self.setGroupsCollidable = function(name0, name1, collidable)
		local success, response = pcall(function()
			return PhysicsService:CollisionGroupSetCollidable(name0, name1, collidable)
		end)
		if not success then
			warn(response)
		end
		self:updateGroups()
	end
	self.batchSetCollisionGroup = function(instances, name)
		local success, response = pcall(function()
			for _, instance in ipairs(instances) do
				instance.CollisionGroup = name
			end
		end)
		if not success then
			warn(response)
		end
		self:updateGroups()
	end
	self.createCollisionGroup = function(name)
		local success, response = pcall(function()
			return PhysicsService:RegisterCollisionGroup(name)
		end)
		if not success then
			warn(response)
		end
		self:updateGroups()
	end
	self.renameCollisionGroup = function(oldName, newName)
		local success, response = pcall(function()
			return PhysicsService:RenameCollisionGroup(oldName, newName)
		end)
		if not success then
			warn(response)
		end
		for _, ancestor in renameTargetAncestors do
			for _, descendant in ancestor:GetDescendants() do
				if descendant:IsA("BasePart") and descendant.CollisionGroup == oldName then
					descendant.CollisionGroup = newName
				end
			end
		end
		if self.state.SelectedGroupName == oldName then
			self:setState({ SelectedGroupName = newName })
		end
		self:updateGroups()
	end
	self.deleteCollisionGroup = function(name)
		local success, response = pcall(function()
			return PhysicsService:UnregisterCollisionGroup(name)
		end)
		if not success then
			warn(response)
		end
		for _, ancestor in renameTargetAncestors do
			for _, descendant in ancestor:GetDescendants() do
				if descendant:IsA("BasePart") and descendant.CollisionGroup == name then
					descendant.CollisionGroup = "Default"
				end
			end
		end
		self:updateGroups()
	end
end

function App:updateGroups()
	local groups = PhysicsService:GetRegisteredCollisionGroups()
	local prevSelectedGroupName = self.state.SelectedGroupName
	local nextSelectedGroupName
	for _, group in ipairs(groups) do
		if group.name == prevSelectedGroupName then
			nextSelectedGroupName = prevSelectedGroupName
			break
		end
	end
	self:setState({
		Groups = groups,
		SelectedGroupName = nextSelectedGroupName or groups[1].name,
	})
end

local function areGroupsDifferent(groups0, groups1)
	if #groups0 ~= #groups1 then
		return true
	end
	for i = 1, #groups0 do
		local item0 = groups0[i]
		local item1 = groups1[i]
		if item0.name ~= item1.name then
			return true
		elseif item0.mask ~= item1.mask then
			return true
		end
	end
	return false
end

function App:didMount()
	local nextCheck = os.clock() + POLL_INTERVAL
	self.pollConnection = RunService.Heartbeat:Connect(function()
		if os.clock() >= nextCheck then
			local lastGroups = self.state.Groups
			local nextGroups = PhysicsService:GetRegisteredCollisionGroups()
			if areGroupsDifferent(lastGroups, nextGroups) then
				self:updateGroups()
			end
			nextCheck += POLL_INTERVAL
		end
	end)
end

function App:willUnmount()
	self.pollConnection:Disconnect()
end

function App:render()
	local isMaxGroups = #self.state.Groups >= MAX_GROUPS
	local isModalActive = self.state.CreatingGroup or self.state.RenamingGroup or self.state.DeletingGroup
	local selectedGroupName = self.state.SelectedGroupName

	return Roact.createFragment({
		CreateWidget = self.state.CreatingGroup and Roact.createElement(GroupNameWidget, {
			Title = "Create Group",
			Action = "Create",
			OnClosed = function()
				self:setState({ CreatingGroup = false })
			end,
			OnSubmitted = function(name)
				self.createCollisionGroup(name)
				self:setState({ CreatingGroup = false })
			end,
		}),
		RenameWidget = self.state.RenamingGroup and Roact.createElement(GroupNameWidget, {
			Title = string.format("Rename Group (%s)", selectedGroupName),
			Action = "Rename",
			OnClosed = function()
				self:setState({ RenamingGroup = false })
			end,
			OnSubmitted = function(name)
				self.renameCollisionGroup(selectedGroupName, name)
				self:setState({ RenamingGroup = false })
			end,
		}),
		DeleteWidget = self.state.DeletingGroup and Roact.createElement(DeleteGroupWidget, {
			Title = string.format("Delete Group (%s)", selectedGroupName),
			GroupName = selectedGroupName,
			OnClosed = function()
				self:setState({ DeletingGroup = false })
			end,
			OnActivated = function()
				self.deleteCollisionGroup(selectedGroupName)
				self:setState({ DeletingGroup = false })
			end,
		}),
		Tabs = Roact.createElement("Frame", {
			Size = UDim2.new(1, 0, 0, 30),
			BackgroundTransparency = 1,
		}, {
			Layout = Roact.createElement("UIListLayout", {
				SortOrder = Enum.SortOrder.LayoutOrder,
				FillDirection = Enum.FillDirection.Horizontal,
			}),
			ListViewButton = Roact.createElement(TabButton, {
				LayoutOrder = 0,
				Size = UDim2.fromScale(0.5, 1),
				Text = "List View",
				Selected = self.state.View == "List",
				OnActivated = function()
					self:updateGroups()
					self:setState({ View = "List" })
				end,
				Disabled = isModalActive,
			}),
			GridViewButton = Roact.createElement(TabButton, {
				LayoutOrder = 1,
				Size = UDim2.fromScale(0.5, 1),
				Text = "Grid View",
				Selected = self.state.View == "Grid",
				OnActivated = function()
					self:updateGroups()
					self:setState({ View = "Grid" })
				end,
				Disabled = isModalActive,
			}),
		}),
		Inner = Roact.createElement("Frame", {
			AnchorPoint = Vector2.new(0, 1),
			Position = UDim2.fromScale(0, 1),
			Size = UDim2.new(1, 0, 1, -31),
			BackgroundTransparency = 1,
		}, {
			Padding = Roact.createElement("UIPadding", {
				PaddingLeft = UDim.new(0, 4),
				PaddingRight = UDim.new(0, 4),
				PaddingTop = UDim.new(0, 4),
				PaddingBottom = UDim.new(0, 4),
			}),
			Views = Roact.createElement("Frame", {
				Size = UDim2.new(1, 0, 1, -32),
				BackgroundTransparency = 1,
			}, {
				List = self.state.View == "List" and Roact.createElement(ListView, {
					Groups = self.state.Groups,
					SelectedGroupName = self.state.SelectedGroupName,
					SetSelectedGroupName = function(name)
						self:setState({ SelectedGroupName = name })
					end,
					SetGroupsCollidable = self.setGroupsCollidable,
					Disabled = isModalActive,
				}),
				Grid = self.state.View == "Grid" and Roact.createElement(GridView, {
					Groups = self.state.Groups,
					SetGroupsCollidable = self.setGroupsCollidable,
					Disabled = isModalActive,
				}),
			}),
			Actions = Roact.createElement("Frame", {
				AnchorPoint = Vector2.new(0, 1),
				Position = UDim2.fromScale(0, 1),
				Size = UDim2.new(1, 0, 0, 28),
				BackgroundTransparency = 1,
			}, {
				Layout = Roact.createElement("UIListLayout", {
					SortOrder = Enum.SortOrder.LayoutOrder,
					FillDirection = Enum.FillDirection.Horizontal,
					Padding = UDim.new(0, 4),
				}),
				Create = Roact.createElement(MainButton, {
					LayoutOrder = 0,
					Size = UDim2.new(0, 28, 1, 0),
					Text = "",
					OnActivated = function()
						self:setState({ CreatingGroup = true })
					end,
					Disabled = isModalActive or isMaxGroups,
				}, {
					Icon = Roact.createElement("ImageLabel", {
						AnchorPoint = Vector2.new(0.5, 0.5),
						Position = UDim2.fromScale(0.5, 0.5),
						Size = UDim2.fromOffset(16, 16),
						BackgroundTransparency = 1,
						Image = "rbxassetid://6688839343",
						ImageTransparency = (isModalActive or isMaxGroups) and 0.75 or 0,
					}),
				}),
				AddTo = self.state.View == "List" and Roact.createElement(AddToGroup, {
					BatchSetCollisionGroup = self.batchSetCollisionGroup,
					SelectedGroupName = self.state.SelectedGroupName,
					Disabled = isModalActive,
				}),
				Rename = self.state.View == "List" and Roact.createElement(Button, {
					LayoutOrder = 2,
					Size = UDim2.new(0.32, -18, 1, 0),
					Text = "Rename",
					OnActivated = function()
						self:setState({ RenamingGroup = true })
					end,
					Disabled = self.state.SelectedGroupName == "Default" or isModalActive,
				}),
				Delete = self.state.View == "List" and Roact.createElement(Button, {
					LayoutOrder = 3,
					Size = UDim2.new(0.3, -18, 1, 0),
					Text = "Delete",
					OnActivated = function()
						self:setState({ DeletingGroup = true })
					end,
					Disabled = self.state.SelectedGroupName == "Default" or isModalActive,
				}),
			}),
		}),
	})
end

return App
