local F={}local q=game:GetService("\084\119\101\101\110\083\101\114\118\105\099\101")local Z=60 local s=Z*60 local Q=s*24 function updateTimerUI(F,N)if F and(F.Parent and F.Visible)then local t=F:FindFirstChild("\070\117\108\108\084\105\109\101\114\070\114\097\109\101")local T=F:FindFirstChild("\083\105\109\112\108\101\084\105\109\101\114\070\114\097\109\101")if not t or not T then warn("\067\111\117\108\100 \110\111\116 \102\105\110\100 \070\117\108\108\084\105\109\101\114\070\114\097\109\101 \111\114 \083\105\109\112\108\101\084\105\109\101\114\070\114\097\109\101")return end local b=T:FindFirstChild("\083\105\109\112\108\101\084\101\120\116\076\097\098\101\108")if not b then warn("\067\111\117\108\100 \110\111\116 \102\105\110\100 \083\105\109\112\108\101\084\105\109\101\114\070\114\097\109\101\046\084\101\120\116\076\097\098\101\108")end local h=t:FindFirstChild("\070\105\114\115\116\080\097\105\114\084\101\120\116\076\097\098\101\108")local z=t:FindFirstChild("\083\101\099\111\110\100\080\097\105\114\084\101\120\116\076\097\098\101\108")local B=t:FindFirstChild("\084\104\105\114\100\080\097\105\114\084\101\120\116\076\097\098\101\108")if not h or not z or not B then warn("\067\111\117\108\100 \110\111\116 \102\105\110\100 \070\105\114\115\116\080\097\105\114\084\101\120\116\076\097\098\101\108 \111\114 \083\101\099\111\110\100\080\097\105\114\084\101\120\116\076\097\098\101\108 \111\114 \084\104\105\114\100\080\097\105\114\084\101\120\116\076\097\098\101\108")end local W=h:FindFirstChild("\083\117\098\084\101\120\116")local U=z:FindFirstChild("\083\117\098\084\101\120\116")local r=B:FindFirstChild("\083\117\098\084\101\120\116")if not W or not U or not r then warn("\067\111\117\108\100 \110\111\116 \102\105\110\100 \097 \'\083\117\098\084\101\120\116\' \084\101\120\116\076\097\098\101\108 \102\111\114 \101\105\116\104\101\114 \070\105\114\115\116\080\097\105\114\084\101\120\116\076\097\098\101\108\044 \083\101\099\111\110\100\080\097\105\114\084\101\120\116\076\097\098\101\108\044 \111\114 \084\104\105\114\100\080\097\105\114\084\101\120\116\076\097\098\101\108")end local l=math.floor(N/Q)local o=N-l*Q local m=math.floor(o/s)o=o-m*s local R=math.floor(o/Z)o=o-R*Z local P=math.floor(o)local x=false if l>0 then t.Visible=true T.Visible=false h.Text=string.format("\037\048\050\105",l)W.Text="\068\065\089"z.Text=string.format("\037\048\050\105",m)U.Text="\072\079\085\082"B.Text=string.format("\037\048\050\105",R)r.Text="\077\073\078"elseif N>10 then t.Visible=true T.Visible=false h.Text=string.format("\037\048\050\105",m)W.Text="\072\079\085\082"z.Text=string.format("\037\048\050\105",R)U.Text="\077\073\078"B.Text=string.format("\037\048\050\105",P)r.Text="\083\069\067"elseif N>0 then t.Visible=false T.Visible=true b.Text=string.format("\037\048\049\105",P)if F.Name=="\084\105\109\101\114\066\117\098\098\108\101\085\073"then b.TextScaled=false x=true end if F:GetAttribute("\076\097\115\116\083\101\099\111\110\100")~=P then F:SetAttribute("\076\097\115\116\083\101\099\111\110\100",P)local Z=b:clone()Z.Name=Z.Name.."\080\117\108\115\101"local s=Instance.new("\085\073\083\099\097\108\101")s.Parent=Z Z.Parent=T;(q:Create(Z,TweenInfo.new(.8,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),{TextTransparency=1})):Play();(q:Create(s,TweenInfo.new(.8,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),{Scale=3})):Play()task.delay(1,function()if Z then Z:Destroy()end end)end else t.Visible=false T.Visible=true b.Text="\083\116\097\114\116\105\110\103 \078\111\119\046\046\046"b.TextScaled=true end if F.Name=="\084\105\109\101\114\066\117\098\098\108\101\085\073"then if x then F.Size=UDim2.new(0,110,1,0)else F.Size=UDim2.new(0,200,1,0)end end if F.Name=="\084\105\109\101\114\083\099\114\101\101\110\085\073"then local q=F:FindFirstChild("\083\116\097\114\116\105\110\103\073\110\084\101\120\116\076\097\098\101\108")if q then q.Visible=N>0 end end end end function findSurfaceGui(F)if F and F.Parent then if F:IsA("\083\117\114\102\097\099\101\071\117\105")then return F else return findSurfaceGui(F.Parent)end else warn("\067\111\117\108\100 \110\111\116 \102\105\110\100 \097\110 \097\110\115\099\101\115\116\111\114 \115\117\114\102\097\099\101\071\117\105 \102\111\114 \066\111\097\114\100\084\105\109\101\114\085\073\046")end end function updateTimerVisuals(F)local q=F.UniversalEventDate.UnixTimestampMillis/1000-workspace:GetServerTimeNow()if q>0 then F.PrimedTimerReached=true elseif F.PrimedTimerReached then F.PrimedTimerReached=false if typeof(F.TimerReachedFunction)=="\102\117\110\099\116\105\111\110"then F.TimerReachedFunction()end end if F.AutomaticallyDisableAfter and q*-1>F.AutomaticallyDisableAfter then F.Enabled=false end if F.BubbleTimerUI then local q=true if F.Enabled and(F.DisableBubbleWhenBoardVisible and F.BoardTimerUI)then local Z=findSurfaceGui(F.BoardTimerUI)if Z then local s=Z.Adornee or Z.Parent if s and s:IsA("\066\097\115\101\080\097\114\116")then local Q=workspace.CurrentCamera local N=(s.Position-Q.CFrame.Position).magnitude if N<=F.ReadableBoardDistance then local F=s.CFrame:VectorToWorldSpace(Vector3.FromNormalId(Z.Face))local N=F:Dot(Q.CFrame.LookVector)if N<-0.5 then local F=s.Position local Z,N=Q:WorldToScreenPoint(F)if N then q=false end end end end end end F.BubbleTimerUI.Visible=F.Enabled and q end if F.BoardTimerUI then F.BoardTimerUI.Visible=F.Enabled end if F.Enabled then updateTimerUI(F.BubbleTimerUI,q)updateTimerUI(F.BoardTimerUI,q)end end function updateTimers()for F,q in pairs(F)do updateTimerVisuals(q)end end local N=nil local t={Enabled=true,UniversalEventDate=DateTime.fromUniversalTime(2025,1,1,0,0,0,0),ReadableBoardDistance=120}t.__index=t local T={}function T.AddTimer(Z,q)local s=setmetatable(Z,t)for F,q in pairs(q)do s[F]=q end table.insert(F,s)if not N then N=(game:GetService("\082\117\110\083\101\114\118\105\099\101")).Heartbeat:Connect(updateTimers)end return s end function T.RemoveTimer(Z,q)local s=table.find(F,q)if s then updateTimerVisuals(q)table.remove(F,s)if#F<=0 and N then N:Disconnect()N=nil end return true else warn("\085\110\097\098\108\101 \116\111 \114\101\109\111\118\101 \116\105\109\101\114 \098\101\099\097\117\115\101 \105\116 \119\097\115 \110\111\116 \102\111\117\110\100\046")end end function T.GetTimers(q)local Z={}for F,q in ipairs(F)do Z[F]=q end return Z end return T


local countdownTimersList = {}

local tweenService = game:GetService("TweenService")

local secondsPerMinute = 60
local secondsPerHour = secondsPerMinute * 60
local secondsPerDay = secondsPerHour * 24

function updateTimerUI(timerUI, timeUntil)
	if timerUI and timerUI.Parent and timerUI.Visible then
		-- Verify children exist
		local fullTimerFrame = timerUI:FindFirstChild("FullTimerFrame")
		local simpleTimerFrame = timerUI:FindFirstChild("SimpleTimerFrame")
		if not fullTimerFrame or not simpleTimerFrame then
			warn("Could not find FullTimerFrame or SimpleTimerFrame")
			return
		end
		local simpleTimerTextLabel = simpleTimerFrame:FindFirstChild("SimpleTextLabel")
		if not simpleTimerTextLabel then
			warn("Could not find SimpleTimerFrame.TextLabel")
		end
		local firstPairTextLabel = fullTimerFrame:FindFirstChild("FirstPairTextLabel")
		local secondPairTextLabel = fullTimerFrame:FindFirstChild("SecondPairTextLabel")
		local thirdPairTextLabel = fullTimerFrame:FindFirstChild("ThirdPairTextLabel")
		if not firstPairTextLabel or not secondPairTextLabel or not thirdPairTextLabel then
			warn("Could not find FirstPairTextLabel or SecondPairTextLabel or ThirdPairTextLabel")
		end
		local firstSubTextLabel = firstPairTextLabel:FindFirstChild("SubText")
		local secondSubTextLabel = secondPairTextLabel:FindFirstChild("SubText")
		local thirdSubTextLabel = thirdPairTextLabel:FindFirstChild("SubText")
		if not firstSubTextLabel or not secondSubTextLabel or not thirdSubTextLabel then
			warn(
				"Could not find a 'SubText' TextLabel for either FirstPairTextLabel, SecondPairTextLabel, or ThirdPairTextLabel"
			)
		end

		local days = math.floor(timeUntil / secondsPerDay)
		local remaining = timeUntil - (days * secondsPerDay)
		local hours = math.floor(remaining / secondsPerHour)
		remaining = remaining - (hours * secondsPerHour)
		local minutes = math.floor(remaining / secondsPerMinute)
		remaining = remaining - (minutes * secondsPerMinute)
		local seconds = math.floor(remaining)

		local shouldBeMini = false

		if days > 0 then
			-- Full counter with days
			fullTimerFrame.Visible = true
			simpleTimerFrame.Visible = false
			firstPairTextLabel.Text = string.format("%02i", days)
			firstSubTextLabel.Text = "DAY"
			secondPairTextLabel.Text = string.format("%02i", hours)
			secondSubTextLabel.Text = "HOUR"
			thirdPairTextLabel.Text = string.format("%02i", minutes)
			thirdSubTextLabel.Text = "MIN"
		elseif timeUntil > 10 then
			-- Full Counter with hours
			fullTimerFrame.Visible = true
			simpleTimerFrame.Visible = false
			firstPairTextLabel.Text = string.format("%02i", hours)
			firstSubTextLabel.Text = "HOUR"
			secondPairTextLabel.Text = string.format("%02i", minutes)
			secondSubTextLabel.Text = "MIN"
			thirdPairTextLabel.Text = string.format("%02i", seconds)
			thirdSubTextLabel.Text = "SEC"
		elseif timeUntil > 0 then
			-- Final Countdown from 10 seconds
			fullTimerFrame.Visible = false
			simpleTimerFrame.Visible = true
			simpleTimerTextLabel.Text = string.format("%01i", seconds)
			if timerUI.Name == "TimerBubbleUI" then
				simpleTimerTextLabel.TextScaled = false
				shouldBeMini = true
			end
			if timerUI:GetAttribute("LastSecond") ~= seconds then
				timerUI:SetAttribute("LastSecond", seconds)
				-- Pulse the countdown
				local pulse = simpleTimerTextLabel:clone()
				pulse.Name = pulse.Name .. "Pulse"
				local pulseUIScale = Instance.new("UIScale")
				pulseUIScale.Parent = pulse
				pulse.Parent = simpleTimerFrame
				tweenService:Create(
					pulse,
					TweenInfo.new(0.8, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
					{ TextTransparency = 1 }
				):Play()
				tweenService:Create(
					pulseUIScale,
					TweenInfo.new(0.8, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
					{ Scale = 3 }
				):Play()
				task.delay(1, function()
					if pulse then
						pulse:Destroy()
					end
				end)
			end
		else
			-- Starting now or time has passed
			fullTimerFrame.Visible = false
			simpleTimerFrame.Visible = true
			simpleTimerTextLabel.Text = "Starting Now..."
			simpleTimerTextLabel.TextScaled = true
		end

		if timerUI.Name == "TimerBubbleUI" then
			if shouldBeMini then
				timerUI.Size = UDim2.new(0, 110, 1, 0)
			else
				timerUI.Size = UDim2.new(0, 200, 1, 0)
			end
		end

		if timerUI.Name == "TimerScreenUI" then
			local startingInTextLabel = timerUI:FindFirstChild("StartingInTextLabel")
			if startingInTextLabel then
				startingInTextLabel.Visible = timeUntil > 0
			end
		end
	end
end

function findSurfaceGui(subject) -- Will go up the parent graph looking for a surface gui and the part it is attached to.
	if subject and subject.Parent then
		if subject:IsA("SurfaceGui") then
			return subject
		else
			return findSurfaceGui(subject.Parent)
		end
	else
		warn("Could not find an anscestor surfaceGui for BoardTimerUI.")
	end
end

function updateTimerVisuals(timer)
	local timeUntil = timer.UniversalEventDate.UnixTimestampMillis / 1000 - workspace:GetServerTimeNow()

	-- Fire TimerReachedFunction if necessary
	if timeUntil > 0 then
		timer.PrimedTimerReached = true
	elseif timer.PrimedTimerReached then
		timer.PrimedTimerReached = false
		if typeof(timer.TimerReachedFunction) == "function" then
			timer.TimerReachedFunction()
		end
	end

	if timer.AutomaticallyDisableAfter and timeUntil * -1 > timer.AutomaticallyDisableAfter then
		timer.Enabled = false
	end
	if timer.BubbleTimerUI then
		local shouldBeVisible = true
		-- Check to see if we should hide the bubbleTimerUi because the board is reasonably visible 
		if timer.Enabled and timer.DisableBubbleWhenBoardVisible and timer.BoardTimerUI then
			local surfaceGui = findSurfaceGui(timer.BoardTimerUI)
			if surfaceGui then
				local boardPart = surfaceGui.Adornee or surfaceGui.Parent
				if boardPart and boardPart:IsA("BasePart") then
					-- Check to make sure board is close enough to read
					local camera = workspace.CurrentCamera
					local boardCameraDistance = (boardPart.Position - camera.CFrame.Position).magnitude
					if boardCameraDistance <= timer.ReadableBoardDistance then
						-- Check if board is facing player
						local surfaceVector = boardPart.CFrame:VectorToWorldSpace(Vector3.FromNormalId(surfaceGui.Face))
						local cameraDirectionMatch = surfaceVector:Dot(camera.CFrame.LookVector)
						if cameraDirectionMatch < -0.5 then
							-- Check if the board is on screen
							local worldPoint = boardPart.Position
							local _, onScreen = camera:WorldToScreenPoint(worldPoint)
							if onScreen then
								shouldBeVisible = false
							end
						end
					end
				end
			end
		end
		timer.BubbleTimerUI.Visible = timer.Enabled and shouldBeVisible
	end
	if timer.BoardTimerUI then
		timer.BoardTimerUI.Visible = timer.Enabled
	end
	if timer.Enabled then
		updateTimerUI(timer.BubbleTimerUI, timeUntil)
		updateTimerUI(timer.BoardTimerUI, timeUntil)
	end
end

function updateTimers()
	for _, timer in pairs(countdownTimersList) do
		updateTimerVisuals(timer)
	end
end

local heartbeatConn = nil

local timerMetatable = {
	Enabled = true,
	UniversalEventDate = DateTime.fromUniversalTime(2025, 1, 1, 0, 0, 0, 0),
	ReadableBoardDistance = 120,
}
timerMetatable.__index = timerMetatable

local countdownTimersModule = {}

function countdownTimersModule:AddTimer(timerPropertiesTable)
	local timer = setmetatable(self, timerMetatable)
	for property, value in pairs(timerPropertiesTable) do
		timer[property] = value
	end
	table.insert(countdownTimersList, timer)

	if not heartbeatConn then
		heartbeatConn = game:GetService("RunService").Heartbeat:Connect(updateTimers)
	end

	return timer
end

function countdownTimersModule:RemoveTimer(timer)
	local timerIndex = table.find(countdownTimersList, timer)
	if timerIndex then
		updateTimerVisuals(timer)
		table.remove(countdownTimersList, timerIndex)

		if #countdownTimersList <= 0 and heartbeatConn then
			heartbeatConn:Disconnect()
			heartbeatConn = nil
		end

		return true
	else
		warn("Unable to remove timer because it was not found.")
	end
end

function countdownTimersModule:GetTimers()
	local newList = {}
	for i, v in ipairs(countdownTimersList) do
		newList[i] = v
	end
	return newList
end

return countdownTimersModule
