-- Client Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")

-- Configuration
local CURRENCY_NAME = "Skira"
local CAMERA_ROTATION_SPEED = 0.1 -- Rotations per second

-- Remote Functions and Events
local PurchaseItemEvent = ReplicatedStorage:WaitForChild("PurchaseItem")
local IsLimitedItemAvailableFunction = ReplicatedStorage:WaitForChild("IsLimitedItemAvailable")
local LimitedItemStatusChangedEvent = ReplicatedStorage:WaitForChild("LimitedItemStatusChanged")
local GetPlayerCurrencyFunction = ReplicatedStorage:WaitForChild("GetPlayerCurrency")
local PurchaseFeedbackEvent = ReplicatedStorage:WaitForChild("PurchaseFeedback")
local GetShopItemsFunction = ReplicatedStorage:WaitForChild("GetShopItems")

-- Variables
local ITEMS_FOLDER = ReplicatedStorage:WaitForChild("Spawnable items")
local TEMPLATE_ELEMENT = Players.LocalPlayer.PlayerGui:WaitForChild("Display").Frame.SDS.Frame.List.ItemUI
local ELEMENTS_CONTAINER = Players.LocalPlayer.PlayerGui:WaitForChild("Display").Frame.SDS.Frame.List

-- Required UI elements
local REQUIRED_UI_ELEMENTS = {
	"Name",
	"Description",
	"Optional",
	"Buy",
	"ItemDisplay"
}

-- Shop items table (will be populated from the server)
local ShopItems = {}

-- Function to display warnings
local function WarnMessage(message)
	warn("[Shop System] " .. message)
end

-- Function to display messages to the player
local function DisplayMessage(message)
	-- Implement your own UI message system here
	-- For example, using a TextLabel or a notification GUI
	print("[Shop Message] " .. message)
end

-- Function to validate UI elements in the template
local function ValidateUIElement(template)
	local missingElements = {}
	for _, element in ipairs(REQUIRED_UI_ELEMENTS) do
		if not template:FindFirstChild(element) then
			table.insert(missingElements, element)
		end
	end
	if #missingElements > 0 then
		WarnMessage(string.format("Missing required UI elements: %s", table.concat(missingElements, ", ")))
		return false
	end
	return true
end

-- Function to validate shop item data
local function ValidateShopItem(itemData)
	if type(itemData) ~= "table" then
		WarnMessage("Invalid item data format")
		return false
	end
	local requiredFields = {"Name", "Description", "Price"}
	for _, field in ipairs(requiredFields) do
		if itemData[field] == nil then
			WarnMessage(string.format("Missing required field '%s' in item data", field))
			return false
		end
	end
	return true
end

-- Function to clean the item model for the viewport
local function CleanModel(model)
	local function recursiveClean(instance)
		if instance.Name == "Package" then
			instance:Destroy()
			return
		end

		if instance:IsA("Script") or 
			instance:IsA("LocalScript") or 
			instance:IsA("Sound") or 
			instance:IsA("StringValue") or 
			instance:IsA("ModuleScript") or 
			instance:IsA("RemoteEvent") or 
			instance:IsA("RemoteFunction") or 
			instance:IsA("BindableEvent") or 
			instance:IsA("BindableFunction") then
			instance:Destroy()
			return
		end

		if not (instance:IsA("Part") or 
			instance:IsA("MeshPart") or 
			instance:IsA("SpecialMesh") or 
			instance:IsA("Model") or 
			instance:IsA("Folder")) then
			instance:Destroy()
			return
		end

		for _, child in ipairs(instance:GetChildren()) do
			recursiveClean(child)
		end
	end

	recursiveClean(model)
end

-- Function to set up the item in the viewport
local function SetupItemInViewport(itemDisplay, itemModel)
	local existingViewport = itemDisplay:FindFirstChild("ViewportFrame")
	if existingViewport then
		existingViewport:Destroy()
	end

	local viewportFrame = Instance.new("ViewportFrame")
	viewportFrame.Name = "ViewportFrame"
	viewportFrame.Size = UDim2.new(1, 0, 1, 0)
	viewportFrame.BackgroundTransparency = 1
	viewportFrame.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	viewportFrame.Parent = itemDisplay

	local camera = Instance.new("Camera")
	camera.Parent = viewportFrame
	viewportFrame.CurrentCamera = camera

	local modelClone = itemModel:Clone()
	CleanModel(modelClone)
	modelClone.Parent = viewportFrame

	local center = Vector3.new(0, 0, 0)
	local size = Vector3.new(0, 0, 0)
	if modelClone:IsA("Model") then
		local boundingCFrame, boundingSize = modelClone:GetBoundingBox()
		center = boundingCFrame.Position
		size = boundingSize
	elseif modelClone:IsA("BasePart") then
		center = modelClone.Position
		size = modelClone.Size
	end

	local maxSize = math.max(size.X, size.Y, size.Z)
	local distance = maxSize * 2
	local cameraPosition = center + Vector3.new(0, 0, distance)
	camera.CFrame = CFrame.new(cameraPosition, center)

	local startTime = tick()
	local rotationConnection
	rotationConnection = RunService.RenderStepped:Connect(function()
		if not viewportFrame.Parent or not camera.Parent then
			rotationConnection:Disconnect()
			return
		end

		local elapsed = tick() - startTime
		local angle = elapsed * (2 * math.pi * CAMERA_ROTATION_SPEED)
		local newPosition = center + Vector3.new(
			math.cos(angle) * distance,
			0,
			math.sin(angle) * distance
		)
		camera.CFrame = CFrame.new(newPosition, center)
	end)
end

-- Function to set up the initial ViewportFrame in the template
local function SetupTemplateViewport()
	local template = TEMPLATE_ELEMENT
	if not template then
		warn("Template not found!")
		return
	end

	local itemDisplay = template:FindFirstChild("ItemDisplay")
	if not itemDisplay then
		itemDisplay = Instance.new("Frame")
		itemDisplay.Name = "ItemDisplay"
		itemDisplay.Size = UDim2.new(1, 0, 1, 0)
		itemDisplay.BackgroundTransparency = 1
		itemDisplay.Parent = template
	end

	local viewportFrame = Instance.new("ViewportFrame")
	viewportFrame.Name = "ViewportFrame"
	viewportFrame.Size = UDim2.new(1, 0, 1, 0)
	viewportFrame.BackgroundTransparency = 1
	viewportFrame.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	viewportFrame.Parent = itemDisplay

	local camera = Instance.new("Camera")
	camera.Parent = viewportFrame
	viewportFrame.CurrentCamera = camera
end

-- Function to get the player's currency
local function GetPlayerCurrency()
	if GetPlayerCurrencyFunction and GetPlayerCurrencyFunction:IsA("RemoteFunction") then
		local success, result = pcall(function()
			return GetPlayerCurrencyFunction:InvokeServer()
		end)
		if success then
			return result
		else
			WarnMessage("Failed to get player currency")
			return 0
		end
	else
		WarnMessage("GetPlayerCurrency RemoteFunction not found")
		return 0
	end
end

-- Function to check limited item availability
local function CheckLimitedItemAvailability(itemName)
	if IsLimitedItemAvailableFunction and IsLimitedItemAvailableFunction:IsA("RemoteFunction") then
		local success, result = pcall(function()
			return IsLimitedItemAvailableFunction:InvokeServer(itemName)
		end)
		if success then
			return result
		else
			WarnMessage("Failed to get availability for item: " .. itemName)
			return false
		end
	else
		WarnMessage("IsLimitedItemAvailable RemoteFunction not found")
		return false
	end
end

-- Function to create a shop item UI element
local function CreateShopItem(template, itemData)
	if not template then
		WarnMessage("Shop item template is nil")
		return nil
	end

	if not ValidateShopItem(itemData) then
		WarnMessage(string.format("Invalid shop item data for item: %s", itemData.Name or "Unknown"))
		return nil
	end

	if not ValidateUIElement(template) then
		WarnMessage("Template validation failed")
		return nil
	end

	local newItem = template:Clone()
	newItem.Visible = true

	-- Set the size of the newItem to a fixed height
	newItem.Size = UDim2.new(1, 0, 0, 150) -- Adjust the height as needed

	-- Set up item UI elements (Name, Description, Buy button, etc.)
	local itemDisplay = newItem:FindFirstChild("ItemDisplay")
	if itemDisplay then
		local itemModel = ITEMS_FOLDER:FindFirstChild(itemData.Name)
		if itemModel then
			SetupItemInViewport(itemDisplay, itemModel)
		else
			WarnMessage("Item model not found: " .. itemData.Name)
		end
	end

	local nameLabel = newItem:FindFirstChild("Name")
	local descriptionLabel = newItem:FindFirstChild("Description")
	local buyButton = newItem:FindFirstChild("Buy")
	local optionalLabel = newItem:FindFirstChild("Optional")

	if nameLabel then nameLabel.Text = itemData.Name end
	if descriptionLabel then descriptionLabel.Text = itemData.Description end
	if buyButton then buyButton.Text = tostring(itemData.Price) .. " " .. CURRENCY_NAME end

	if optionalLabel then
		if itemData.New then
			optionalLabel.Text = "New!"
			optionalLabel.Visible = true
		elseif itemData.Limited then
			optionalLabel.Text = "Limited"
			optionalLabel.BackgroundColor3 = Color3.new(0.690196, 0.443137, 0.145098)
			optionalLabel.Visible = true
		else
			optionalLabel.Visible = false
		end
	end

	local originalBuyButtonColor
	if buyButton then
		originalBuyButtonColor = buyButton.BackgroundColor3  -- Store the original color

		local function updateBuyButton()
			local itemAvailable = true
			if itemData.Limited then
				itemAvailable = CheckLimitedItemAvailability(itemData.Name)
			end

			if itemAvailable then
				-- Item is available
				buyButton.Text = tostring(itemData.Price) .. " " .. CURRENCY_NAME
				buyButton.Active = true
				buyButton.AutoButtonColor = true
				buyButton.BackgroundColor3 = originalBuyButtonColor
			else
				-- Item is sold out
				buyButton.Text = "Out of Stock"
				buyButton.Active = false
				buyButton.AutoButtonColor = false
				buyButton.BackgroundColor3 = Color3.fromRGB(128, 128, 128)
			end
		end

		updateBuyButton()

		-- Listen for LimitedItemStatusChangedEvent if the item is limited
		if itemData.Limited and LimitedItemStatusChangedEvent and LimitedItemStatusChangedEvent:IsA("RemoteEvent") then
			LimitedItemStatusChangedEvent.OnClientEvent:Connect(function(changedItemName)
				if changedItemName == itemData.Name then
					updateBuyButton()
				end
			end)
		elseif itemData.Limited then
			WarnMessage("LimitedItemStatusChangedEvent not found or not a RemoteEvent")
		end

		-- Set up buy button behavior
		buyButton.MouseButton1Click:Connect(function()
			local playerCurrency = GetPlayerCurrency()

			if playerCurrency < itemData.Price then
				-- Display message to the player that they don't have enough money
				local insufficientFundsMessage = "You don't have enough " .. CURRENCY_NAME .. " to buy " .. itemData.Name .. "."
				DisplayMessage(insufficientFundsMessage)
				return
			end

			-- For limited items, check availability again before buying
			if itemData.Limited then
				local itemAvailable = CheckLimitedItemAvailability(itemData.Name)
				if not itemAvailable then
					-- Item is sold out
					DisplayMessage("This item is sold out.")
					updateBuyButton()
					return
				end
			end

			if PurchaseItemEvent and PurchaseItemEvent:IsA("RemoteEvent") then
				PurchaseItemEvent:FireServer(itemData.Name)
				-- Optionally, disable the buy button to prevent multiple clicks
				buyButton.Active = false
			else
				WarnMessage("PurchaseItem RemoteEvent not found or is not a RemoteEvent")
			end
		end)
	end

	newItem.Parent = ELEMENTS_CONTAINER -- Ensure items are parented to the ScrollingFrame

	return newItem
end

-- Function to update the CanvasSize of the ScrollingFrame
local function UpdateCanvasSize()
	local layout = ELEMENTS_CONTAINER:FindFirstChildWhichIsA("UIGridStyleLayout")
	if layout then
		local contentSize = layout.AbsoluteContentSize
		local padding = ELEMENTS_CONTAINER:FindFirstChildOfClass("UIPadding")
		if padding then
			contentSize = Vector2.new(
				contentSize.X + padding.PaddingLeft.Offset + padding.PaddingRight.Offset,
				contentSize.Y + padding.PaddingTop.Offset + padding.PaddingBottom.Offset
			)
		end
		ELEMENTS_CONTAINER.CanvasSize = UDim2.new(0, contentSize.X, 0, contentSize.Y + 400)
		print("Updated CanvasSize to:", ELEMENTS_CONTAINER.CanvasSize) -- Debug print
	else
		print("Layout not found in ELEMENTS_CONTAINER")
	end
end

-- Function to initialize the shop
local function InitializeShop()
	local success, err = pcall(function()
		local template = TEMPLATE_ELEMENT
		if not template then
			error("Shop template not found")
		end

		SetupTemplateViewport()
		template.Visible = false

		-- Ensure a layout exists
		local layout = ELEMENTS_CONTAINER:FindFirstChildWhichIsA("UIGridStyleLayout")
		if not layout then
			layout = Instance.new("UIListLayout")
			layout.FillDirection = Enum.FillDirection.Vertical
			layout.SortOrder = Enum.SortOrder.LayoutOrder
			layout.Padding = UDim.new(0, 5)
			layout.Parent = ELEMENTS_CONTAINER
		end

		-- Add UIPadding if not present
		local padding = ELEMENTS_CONTAINER:FindFirstChildOfClass("UIPadding")
		if not padding then
			padding = Instance.new("UIPadding")
			padding.PaddingTop = UDim.new(0, 5)
			padding.PaddingBottom = UDim.new(0, 5)
			padding.PaddingLeft = UDim.new(0, 5)
			padding.PaddingRight = UDim.new(0, 5)
			padding.Parent = ELEMENTS_CONTAINER
		end

		-- Connect to changes in AbsoluteContentSize to update CanvasSize
		layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(UpdateCanvasSize)

		-- Request shop items from the server
		if GetShopItemsFunction and GetShopItemsFunction:IsA("RemoteFunction") then
			local success, result = pcall(function()
				return GetShopItemsFunction:InvokeServer()
			end)
			if success and type(result) == "table" then
				ShopItems = result
			else
				WarnMessage("Failed to get shop items from server")
				return
			end
		else
			WarnMessage("GetShopItems RemoteFunction not found")
			return
		end

		-- Sort items: New items first, then Limited, then others
		table.sort(ShopItems, function(a, b)
			if a.New ~= b.New then
				return a.New
			elseif a.Limited ~= b.Limited then
				return a.Limited
			else
				return a.Name < b.Name
			end
		end)

		-- Populate the shop items
		for _, itemData in ipairs(ShopItems) do
			local item = CreateShopItem(template, itemData)
			if not item then
				WarnMessage(string.format("Failed to create shop item: %s", itemData.Name or "Unknown"))
			end
		end

		-- Update CanvasSize after adding items
		UpdateCanvasSize()
	end)

	if not success then
		WarnMessage(string.format("Failed to initialize shop: %s", err))
	end
end

-- Handle purchase feedback messages from the server
if PurchaseFeedbackEvent and PurchaseFeedbackEvent:IsA("RemoteEvent") then
	PurchaseFeedbackEvent.OnClientEvent:Connect(function(message)
		DisplayMessage(message)
	end)
else
	WarnMessage("PurchaseFeedbackEvent not found or not a RemoteEvent")
end

InitializeShop()

can you make it so that the shop fetches the money from the module ?
-- ModuleScript: SkiraManager

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local SkiraDataStore = DataStoreService:GetDataStore("PlayerSkiraDataStore")

local SkiraManager = {}

-- Table to hold player data in memory
local playerData = {}

function SkiraManager:GetPlayerSkira(player)
	return playerData[player.UserId] or 0
end

function SkiraManager:AddPlayerSkira(player, amount)
	local userId = player.UserId
	if not playerData[userId] then
		playerData[userId] = 0
	end
	playerData[userId] = playerData[userId] + amount
end

function SkiraManager:SetPlayerSkira(player, amount)
	playerData[player.UserId] = amount
end

function SkiraManager:SavePlayerData(player)
	local userId = player.UserId
	local success, err = pcall(function()
		SkiraDataStore:SetAsync(userId, playerData[userId])
	end)
	if not success then
		warn("Failed to save data for player " .. player.Name .. ": " .. err)
	end
end

function SkiraManager:LoadPlayerData(player)
	local userId = player.UserId
	local success, data = pcall(function()
		return SkiraDataStore:GetAsync(userId)
	end)
	if success then
		-- If data exists, assign it to playerData
		playerData[userId] = data or 0
	else
		warn("Failed to load data for player " .. player.Name)
		playerData[userId] = 0
	end
end

function SkiraManager:SaveAllPlayerData()
	for _, player in ipairs(Players:GetPlayers()) do
		self:SavePlayerData(player)
	end
end

function SkiraManager:PlayerAdded(player)
	self:LoadPlayerData(player)
end

function SkiraManager:PlayerRemoving(player)
	self:SavePlayerData(player)
	playerData[player.UserId] = nil
end

return SkiraManager

-- Server Script: SkiraSystem

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- Require the SkiraManager module
local SkiraManager = require(game.ServerScriptService.SkiraManager)

-- Create or get RemoteEvent for Skira updates
local SkiraUpdateEvent = ReplicatedStorage:FindFirstChild("SkiraUpdateEvent")
if not SkiraUpdateEvent then
	SkiraUpdateEvent = Instance.new("RemoteEvent")
	SkiraUpdateEvent.Name = "SkiraUpdateEvent"
	SkiraUpdateEvent.Parent = ReplicatedStorage
end

-- Define Skira accumulation rate
local SKIRA_RATE = 1 -- 1 Skira
local TIME_INTERVAL = 30 -- Every 30 seconds

-- Function to update Skira amount to the client
local function updateClientSkira(player)
	local skiraAmount = SkiraManager:GetPlayerSkira(player)
	SkiraUpdateEvent:FireClient(player, skiraAmount)
end

-- Function to increment Skira every 30 seconds
local function startSkiraAccumulation(player)
	local function accumulateSkira()
		while player.Parent do
			wait(TIME_INTERVAL)
			SkiraManager:AddPlayerSkira(player, SKIRA_RATE)
			-- Update client
			updateClientSkira(player)
		end
	end
	-- Start the accumulation in a separate thread
	coroutine.wrap(accumulateSkira)()
end

-- Player Added event
Players.PlayerAdded:Connect(function(player)
	SkiraManager:PlayerAdded(player)
	-- Send initial Skira amount to client
	updateClientSkira(player)
	startSkiraAccumulation(player)
end)

-- Player Removing event
Players.PlayerRemoving:Connect(function(player)
	SkiraManager:PlayerRemoving(player)
end)

-- Bind saving data on server shutdown
game:BindToClose(function()
	SkiraManager:SaveAllPlayerData()
end)