-- 41458171

require(game.ReplicatedFirst.ReadyModule)("SaveModule")
local Commands = require(_G.Modules.CommandsModule)
Placement = require(_G.Modules.PlacementModule)
Debris = _G.Debris

SaveFrame = nil
Stroke = nil
SaveData = nil
SaveTabs = nil
SaveLists = nil
Templates = nil
PlayerSaveList = nil
SelectionBox = nil

parts = _G.Storage:WaitForChild("Parts")

SubassemblyData = ""
SubassemblyModel = nil

CurrentSaveData = ""
CurrentPlacementConfig = nil

CurrentSaveRoot = nil
LastInteractedPart = nil

SelectionBoxes = {}

local function OffloadSelection()
	if CurrentPlacementConfig then CurrentPlacementConfig:stop() end
	CurrentSaveRoot = nil
	CurrentSaveData = nil
	SelectionBox.Adornee = nil
	for _,object in pairs(SelectionBoxes) do
		object:Destroy()
	end
	SelectionBoxes = {}
end

local function LoadModel(key,group)
	wait()
	local raw = _G.Remotes.Load:InvokeServer(group,key)
	local success,result = pcall(function()
		return _G.Methods.saves.dataToModel(raw,false)
	end)
	local model
	if success then 
		model = result
	else
		_G.Error(string.format("Load Model Error (%s:%s): %s",key,group,result))
	end
	if not model then 
		return _G.Storage.Missing:Clone()
	end
	local primary = model:FindFirstChildWhichIsA("VehicleSeat",true)
	if primary then 
		model.PrimaryPart = primary
	end
	return model
end

local _tab = {}
_tab.__index = _tab

local _slot = {}
_slot.__index = _slot
_slot.__newindex_operations = {
	name = function(this,name)
		this.frame.NameBox.Text = name
	end,
	order = function(this,index)
		this.frame.LayoutOrder = tonumber(index) or 0
		this.frame.IdLabel.Text = tostring(index):upper()
		if index == "autosave" then
			this.frame.IdLabel.BackgroundColor3 = Color3.new(1, 0.768627, 0.223529)
		elseif index == "subassembly" then
			this.frame.IdLabel.BackgroundColor3 = Color3.new(0.623529, 0.0941176, 1)
		end
	end,
	on_mirror_clicked = function(this,callback)
		_G.Error("Mirror function unimplemented")
	end,
	on_name_change = function(this,callback)
		this.frame.NameBox.TextEditable = true
		this.frame.NameBox.FocusLost:Connect(function(enter)
			if enter then callback(this.frame.NameBox.Text) end
		end)
	end,
	on_save_clicked = function(this,callback)
		this.frame.SaveButton.Visible = true
		this.frame.SaveButton.Activated:Connect(function()
			callback(CurrentSaveData)
		end)
	end,
	on_load_clicked = function(this,callback)
		this.frame.LoadButton.Activated:Connect(function()
			callback(this)
		end)
	end,
	model = function(this,model)
		local old = this.viewport:FindFirstChildWhichIsA("Model")
		if old then old:Destroy() end
		if not model then
			this.frame.OldLabel.Visible = true
			this.frame.OldLabel.Text = "CORRUPT"
			return
		else
			if not (model.Name == "Model") then this.frame.NameBox.Text = model.Name end
		end
		model.Parent = this.viewport
		this.frame.OldLabel.Visible = model:HasTag("Old")
		this.frame.OldLabel.Text = model:GetAttribute("Version") or "Err"
		local cframe,size = model:GetBoundingBox()
		this.camera.CFrame = cframe * CFrame.fromEulerAnglesYXZ(-0.5,-2.5,0)
		this.camera.CFrame = this.camera.CFrame + this.camera.CFrame.LookVector * -(size.Magnitude/2)
	end
}
_slot.__newindex = function(this,index,value)
	if not _slot.__newindex_operations[index] then error("No index to set: "..index,2) end
	_slot.__newindex_operations[index](this,value)
end

_slot.show = function(this)
	this.frame.Visible = true
	_G.Tween:Create(this.frame.UIPadding,TweenInfo.new(0.5,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),{PaddingLeft = UDim.new(0,0)}):Play()
end

_tab.CreateSlot = function(this)
	local new = {}
	new.frame = Templates.Frame:Clone()
	new.frame.Parent = this.list
	new.raw = ""
	_G.Union(new.frame,{
		Parent = this.list,
		Visible = true,
	})
	new.frame.NameBox.TextEditable = false
	new.frame.SaveButton.Visible = false

	new.angle = 45

	new.viewport = new.frame.ViewportFrame
	new.undo = new.frame.UndoButton
	new.refresh = new.frame.RefreshButton

	new.camera = Instance.new("Camera")
	new.camera.Parent = new.viewport
	new.camera.CFrame = CFrame.Angles(0,math.rad(new.angle),0)
	new.viewport.CurrentCamera = new.camera

	local hover = false
	new.viewport.MouseEnter:Connect(function()
		hover = true
		new.viewport.BackgroundColor3 = Color3.new(0.75,0.75,0.75)
		while wait() and hover do
			new.camera.CFrame = CFrame.Angles(0,math.rad(2),0) * new.camera.CFrame
		end
	end)

	new.viewport.MouseLeave:Connect(function() 
		hover = false
		new.viewport.BackgroundColor3 = Color3.new(1,1,1)
	end)

	new.frame.Visible = false
	new.frame.OldLabel.Visible = false

	return setmetatable(new,_slot)
end

_tab.FocusSection = function(this)
	for _,list in pairs(SaveLists:GetChildren()) do
		if list:IsA("ScrollingFrame") then list.Visible = false end
	end
	this.list.Visible = true
	Stroke.Parent = this.tab
end

local function CreateSection(title)
	local new = setmetatable({},_tab)
	new.list = Templates.List:Clone()
	_G.Union(new.list,{
		Parent = SaveLists,
		Visible = false
	})
	new.tab = Templates.Tab:Clone()
	_G.Union(new.tab,{
		Parent = SaveTabs,
		Visible = true,
		Text = title
	})
	new.slots = {}
	new.tab.CloseButton.Activated:Connect(function()
		Stroke.Parent = PlayerSaveList
		new.tab:Destroy()
		new.list:Destroy()
		PlayerSaveList:FocusSection()
	end)
	new.tab.Activated:Connect(function() new:FocusSection() end)
	return new
end

_G.SaveIndices = {}
local function CreateUserSection(userid,modify,close)
	userid = tostring(userid or _G.UserId)
	local title = (game.Players:GetPlayerByUserId(tonumber(userid) or 0) or {DisplayName = userid}).DisplayName
	local section = CreateSection(title)
	if userid == tostring(_G.UserId) then section:FocusSection() end
	local save_info = {}
	local save_info_locked = false
	local saves = {}
	local slots = {}
	if not userid:match(":") and not close then section.tab.CloseButton.Visible = false end
	local function update_save_info(callback)
		repeat
			wait()
		until not save_info_locked
		save_info_locked = true
		if callback then callback(save_info) end
		_G.Remotes.SetSaveInfo:InvokeServer(save_info,userid)
		save_info = _G.Remotes.GetSaveInfo:InvokeServer(userid)
		for index,name in pairs(save_info) do 
			local slot = slots[tostring(index)] or slots[index]
			if slot then 
				slot.name = name
			end 
		end
		save_info_locked = false
	end
	spawn(function()
		save_info = _G.Remotes.GetSaveInfo:InvokeServer(userid)
		local indices = {}
		if not save_info then return end
		for index,_ in pairs(save_info) do
			table.insert(indices,index)
		end
		_G.SaveIndices[userid] = indices
		if not userid:match(":") then
			table.sort(indices,function(before,after)
				before = tonumber(before)
				after = tonumber(after)
				if not before then return true end
				if not after then return false end
				return before < after
			end)
		end
		for _,index in pairs(indices) do
			spawn(function()
				local name = save_info[index]
				index = tostring(index)
				local slot = section:CreateSlot()
				slots[index] = slot
				local model = LoadModel(index,userid)
				saves[index] = model
				_G.Union(slot,{
					name = name,
					order = index,
					on_name_change = function(text)
						update_save_info(function()
							save_info[tonumber(index) or index] = text
						end)
					end,
					on_load_clicked = function()
						if not saves[index] or not saves[index].PrimaryPart then _G.Events.Notification:Fire("Vehicle Load Failed","There is no vehicle.") return end
						OffloadSelection()
						CurrentPlacementConfig = Placement.begin(saves[index]:Clone(),{
							confirm = function(config)
								_G.Remotes.Place:InvokeServer(config.model.PrimaryPart.CFrame,index,userid)
								config:stop()
							end,
							stopped = function(config)
								config.model:Destroy()
								CurrentPlacementConfig = nil
							end,
							disabled = {
								resize = true
							}
						})
					end,
					model = model
				})
				if modify then
					slot.on_save_clicked = function()
						if (not CurrentSaveData) or (#CurrentSaveData == 0) then
							_G.Events.Notification:Fire("Vehicle Save Failed","Please select a vehicle to save") 
							return
						end
						_G.Remotes.Save:InvokeServer(CurrentSaveData,index,userid)
						model = LoadModel(index,userid)
						saves[index] = model
						slot.model = model
						OffloadSelection()
					end
					slot.viewport.MouseEnter:Connect(function()
						slot.undo.Visible = true
						slot.refresh.Visible = true
					end)
					slot.viewport.MouseLeave:Connect(function()
						slot.undo.Visible = false
						slot.refresh.Visible = false
					end)
				end
				slot.undo.Activated:Connect(function()
					CreateUserSection(userid..":"..index,false)
				end)
				slot.refresh.Activated:Connect(function()
					model = LoadModel(index,userid)
					saves[index] = model
					slot.model = model
					OffloadSelection()
				end)
				slot:show()
			end)
			wait(0.2)
		end

		if tostring(_G.UserId) == userid then -- autosave
			spawn(function()
				wait(5)
				repeat
					if not LastInteractedPart then continue end
					local part = LastInteractedPart.part
					if part and part.Parent and _G.Methods.playerOwnsPart(game.Players.LocalPlayer,part) then
						_G.Events.Notification:Fire("Auto Saving","Attempting Autosave")
						CurrentSaveData = _G.Methods.saves.modelToData(part)
						_G.Remotes.Save:InvokeServer(CurrentSaveData,"autosave",userid)
						_G.Events.Save:Fire(CurrentSaveData)

						local selection_template = Instance.new("SelectionBox")
						for index,item in pairs({
							SurfaceTransparency = 0.75,
							SurfaceColor3 = Color3.fromRGB(231, 72, 183),
							LineThickness = 0,
							}) do
							selection_template[index] = item
						end

						_G.Methods.forEachConnectedObject(part,function(model)
							wait()
							local selection = selection_template:Clone()
							selection.Adornee = model
							selection.Parent = model
							Debris:AddItem(selection,0.1)
						end)

						update_save_info(function()
							save_info["autosave"] = tostring(os.date("%c",os.time()))
						end)

						local model = LoadModel("autosave",userid)
						slots["autosave"].model = model
						saves["autosave"] = model
					end
				until not wait(60*2.5)
			end)
		end
	end)
	return section,saves
end

_G.Events.Create.Event:Connect(function(part)
	LastInteractedPart = part
end)

local function RenderStepped(delta) -- General procedure for allowing us to pick a part from a vehicle to later select (per frame)
	if CurrentPlacementConfig then return end
	local result = _G.Methods.getMouseHit()
	if result and result.Instance then
		local part
		if _G.Methods.playerOwnsPart(_G.Player,result.Instance) then
			part = result.Instance
		elseif _G.Methods.playerOwnsPart(_G.Player,result.Instance.Parent) then
			part = result.Instance.Parent
		end
		SelectionBox.Adornee = part
		CurrentSaveRoot = part
	end
end

function InitTool(script)
	SelectionBox = script:WaitForChild("SelectionBox")

	local renderSteppedEvent = nil

	script.Parent.Activated:Connect(function()
		if CurrentSaveRoot then
			local root = CurrentSaveRoot
			
			OffloadSelection()
			
			CurrentSaveData = _G.Methods.saves.modelToData(root)
			
			local selection_template = Instance.new("SelectionBox")
			for index,item in pairs({
				SurfaceTransparency = 0.75,
				SurfaceColor3 = Color3.fromRGB(154, 46, 231),
				LineThickness = 0
				}) do
				selection_template[index] = item
			end
			
			_G.Methods.forEachConnectedObject(root,function(model)
				local selection = selection_template:Clone()
				selection.Adornee = model
				selection.Parent = model
				table.insert(SelectionBoxes,selection)
			end)
		end
	end)
	script.Parent.Unequipped:Connect(function()
		OffloadSelection()
		SaveFrame.Visible = false
		SelectionBox.Adornee = nil
		if renderSteppedEvent then renderSteppedEvent:Disconnect() end
	end)

	script.Parent.Equipped:Connect(function()
		SaveFrame.Visible = true
		if renderSteppedEvent then renderSteppedEvent:Disconnect() end
		renderSteppedEvent = game:GetService("RunService").RenderStepped:Connect(RenderStepped)
	end)
end

local player_saves
function InitUI(script)
	SaveFrame = script.Parent
	Stroke = script:WaitForChild("UIStroke")
	SaveData = script.Parent:WaitForChild("SaveData")
	SaveTabs = script.Parent:WaitForChild("SaveTabs")
	SaveLists = script.Parent:WaitForChild("SaveLists")
	Templates = script.Parent:WaitForChild("Template")

	SaveFrame.Visible = false
	Templates.Frame.Visible = false

	PlayerSaveList,player_saves = CreateUserSection(_G.UserId,true)
	for i = 1,2 do
		local SubSlot = PlayerSaveList:CreateSlot()
		SubSlot.order = string.format("SUB %i",i)
		SubSlot.name = string.format("Subassembly %i",i)
		SubSlot.on_save_clicked = function()
			if (not CurrentSaveData) or (#CurrentSaveData == 0) then
				_G.Events.Notification:Fire("Vehicle Save Failed","Please select a vehicle to save") 
				return
			end
			SubassemblyData = CurrentSaveData
			SubassemblyModel = _G.Methods.saves.dataToModel(CurrentSaveData,false)
			SubSlot.model = SubassemblyModel
			OffloadSelection()
		end
		SubSlot.on_mirror_clicked = function()
			OffloadSelection()
			if not SubassemblyData then return end
			if SubassemblyModel then SubassemblyModel:Destroy() end
			SubassemblyModel = _G.Methods.saves.dataToModel(SubassemblyData,false,true)
			SubSlot.model = SubassemblyModel
		end
		SubSlot.on_load_clicked = function()
			OffloadSelection()
			if not SubassemblyModel then return end
			CurrentPlacementConfig = Placement.begin(SubassemblyModel:Clone(),{
				confirm = function(config)
					_G.Events.Notification:Fire("Subassembly Placing!")
					_G.Remotes.PlaceSubassembly:InvokeServer(config.model.PrimaryPart.CFrame,SubassemblyData,config.touching)
					config:stop()
				end,
				stopped = function(config)
					config.model:Destroy()
					CurrentPlacementConfig = nil
				end,
			})
		end
		SubSlot:show()
	end
	CreateUserSection("public",_G.Remotes.Admin:InvokeServer())
end

Commands.new("open save (.+)","open save <userid>: Opens saves in your tool.",function(userid)
	CreateUserSection(userid,false)
	_G.Events.Notification:Fire("Opened Read-Only Save",userid)
end)
Commands.new("open write save (.+)","open write save <userid>: Opens write saves in your tool.",function(userid)
	CreateUserSection(userid,true)
	_G.Events.Notification:Fire("Opened Write Save",userid)
end)
Commands.new("export save (.+) (.+)","export save <userid> <number>: Prints out your save",function(userid,key)
	_G.Events.Notification:Fire("Save Export",tostring(_G.Remotes.Load:InvokeServer(userid,key)))
end)
Commands.new("show indices","show indices: Prints out save indices",function()
	local result = ""
	local indices = _G.SaveIndices
	for userid,tab in pairs(indices) do
		result = result .. "Userid: "..tostring(userid).."\n"
		for index,item in pairs(tab) do
			result = result .. "  "..tostring(index)..": "..tostring(item).."\n"
		end
	end
	print(result)
	_G.Events.Notification:Fire("Save Indicies",result)
end)

return getfenv()