--[[ Use Example

local countdownTimersModule = require(game:GetService("ReplicatedStorage"):WaitForChild("ClientCountdownModule"))

local localPlayer = game:GetService("Players").LocalPlayer
local countdownTimerGUI = localPlayer:WaitForChild("PlayerGui"):WaitForChild("CountdownTimerGUI")

local bubbleTimerUI = countdownTimerGUI:WaitForChild("TopFrameUI"):WaitForChild("TimerBubbleUI")
local boardTimerUI = workspace
	:WaitForChild("CountdownBoardPart")
	:WaitForChild("SurfaceGui")
	:WaitForChild("TimerScreenUI")

function clientTimerReachedFunc()
	print("Timer Reached!!")
end

local timer = countdownTimersModule:AddTimer({
	Enabled = true,
	UniversalEventDate = DateTime.fromUniversalTime(2022, 4, 28, 14, 30, 0, 0), -- UTC (year, month, day, hour, minute)
	BubbleTimerUI = bubbleTimerUI,
	BoardTimerUI = boardTimerUI,
	AutomaticallyDisableAfter = 10, -- Seconds after the event date in which the timer will disable itself.
	DisableBubbleWhenBoardVisible = true,
	TimerReachedFunction = clientTimerReachedFunc,
	ReadableBoardDistance = 100,
})

-- Just example code for controlling the Enabled state
timer.Enabled = false
wait(2)
timer.Enabled = true

]]

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