local _, ns = ...

local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme

local QueueStatus = {
	ProposalDuration = 40,
	RefreshElapsed = 0,
	ActiveQueueKey = nil,
	QueueStartedAt = nil,
	QueueApiTimestamp = nil,
}
SlickUI.QueueStatus = QueueStatus

local QUEUE_CATEGORIES = {
	_G.LE_LFG_CATEGORY_LFD or 1,
	_G.LE_LFG_CATEGORY_RF or 2,
	_G.LE_LFG_CATEGORY_SCENARIO or 4,
}

local function FormatClock(seconds)
	seconds = math.max(0, math.floor((tonumber(seconds) or 0) + 0.5))
	local minutes = math.floor(seconds / 60)
	return string.format("%d:%02d", minutes, seconds % 60)
end

local function SafeDungeonName(dungeonID)
	if type(dungeonID) ~= "number" or type(GetLFGDungeonInfo) ~= "function" then
		return nil
	end
	local ok, name = pcall(GetLFGDungeonInfo, dungeonID)
	return ok and type(name) == "string" and name or nil
end

local function ReadQueueStats(category, dungeonID)
	if type(GetLFGQueueStats) ~= "function" then return nil end
	local ok, hasData, _, _, _, _, _, _, _, _, instanceName, _, _, _, _, _, queuedTime, activeID =
		pcall(GetLFGQueueStats, category, dungeonID)
	if not ok or hasData ~= true then return nil end
	return {
		category = category,
		dungeonID = type(activeID) == "number" and activeID or dungeonID,
		name = type(instanceName) == "string" and instanceName or nil,
		queuedTime = type(queuedTime) == "number" and queuedTime or nil,
	}
end

function QueueStatus:FindActiveQueue()
	for _, category in ipairs(QUEUE_CATEGORIES) do
		if type(GetLFGQueuedList) == "function" then
			local queued = {}
			local ok, returned = pcall(GetLFGQueuedList, category, queued)
			if ok then
				if type(returned) == "table" then queued = returned end
				for dungeonID in pairs(queued) do
					local entry = ReadQueueStats(category, dungeonID)
					if entry then return entry end
				end
			end
		end

		-- Some queue types do not expose a dungeon list. Keep the aggregate query
		-- as a fallback, but prefer the concrete queued IDs above because the
		-- category-wide values can briefly retain stale timing data.
		local direct = ReadQueueStats(category)
		if direct then return direct end
	end
	return nil
end

local function QueueKey(queue)
	return table.concat({ tostring(queue.category or "?"), tostring(queue.dungeonID or "?") }, ":")
end

function QueueStatus:ResetQueueTimer()
	self.ActiveQueueKey = nil
	self.QueueStartedAt = nil
	self.QueueApiTimestamp = nil
end

function QueueStatus:GetQueueElapsed(queue)
	local now = GetTime()
	local key = QueueKey(queue)
	if type(self.QueueStartedAt) ~= "number" then
		self.ActiveQueueKey = key
		self.QueueStartedAt = now
		self.QueueApiTimestamp = nil

		-- Blizzard normally returns a GetTime()-based queue timestamp. Restore it
		-- after /reload only when it describes a plausible active queue. Old or
		-- zero category data must never become a multi-hour waiting timer.
		local queuedAt = queue.queuedTime
		if type(queuedAt) == "number" then
			local elapsed = now - queuedAt
			if queuedAt > 0 and elapsed >= 0 and elapsed <= 43200 then
				self.QueueStartedAt = queuedAt
				self.QueueApiTimestamp = queuedAt
			end
		end
	else
		-- Blizzard can refresh the queue timestamp and even the reported active
		-- dungeon ID when its queue-eye tooltip is inspected. Neither indicates a
		-- new queue session. Keep the original timer until FindActiveQueue reports
		-- no queue and ResetQueueTimer explicitly clears the session.
		self.ActiveQueueKey = key
	end
	return math.max(0, now - self.QueueStartedAt)
end

function QueueStatus:GetProposal()
	if type(GetLFGProposal) ~= "function" then return nil end
	local ok, exists, proposalID, _, _, name, _, _, hasResponded = pcall(GetLFGProposal)
	if not ok or exists ~= true then return nil end
	return {
		id = proposalID,
		name = type(name) == "string" and name or "Dungeon or Raid",
		hasResponded = hasResponded == true,
	}
end

function QueueStatus:SavePosition()
	if not self.Frame or not SlickUI.DB then return end
	local centerX, centerY = self.Frame:GetCenter()
	local uiX, uiY = UIParent:GetCenter()
	if type(centerX) == "number" and type(centerY) == "number" and type(uiX) == "number" and type(uiY) == "number" then
		SlickUI.DB.queueStatus.position.x = math.floor(centerX - uiX + 0.5)
		SlickUI.DB.queueStatus.position.y = math.floor(centerY - uiY + 0.5)
	end
end

function QueueStatus:RestorePosition()
	local position = SlickUI.DB.queueStatus.position
	self.Frame:ClearAllPoints()
	self.Frame:SetPoint("CENTER", UIParent, "CENTER", position.x or 0, position.y or -170)
end

function QueueStatus:CreateFrame()
	local frame = CreateFrame("Frame", "SlickUIQueueStatusFrame", UIParent)
	frame:SetSize(330, 76)
	frame:SetFrameStrata("DIALOG")
	frame:SetClampedToScreen(true)
	frame:SetMovable(true)
	frame:EnableMouse(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", function(self)
		if not InCombatLockdown() then self:StartMoving() end
	end)
	frame:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		QueueStatus:SavePosition()
	end)
	Theme:CreatePanel(frame)

	local icon = frame:CreateTexture(nil, "ARTWORK")
	icon:SetSize(42, 42)
	icon:SetPoint("LEFT", 12, 6)
	icon:SetTexture("Interface\\AddOns\\SlickUI\\Media\\Icons\\SlickUI_Icon.png")
	frame.Icon = icon

	local title = Theme:CreateText(frame, 14)
	title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, 2)
	title:SetPoint("RIGHT", frame, "RIGHT", -12, 0)
	title:SetJustifyH("LEFT")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	frame.Title = title

	local detail = Theme:CreateText(frame, 11)
	detail:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
	detail:SetPoint("RIGHT", frame, "RIGHT", -12, 0)
	detail:SetJustifyH("LEFT")
	frame.Detail = detail

	local bar = CreateFrame("StatusBar", nil, frame)
	bar:SetPoint("BOTTOMLEFT", 12, 9)
	bar:SetPoint("BOTTOMRIGHT", -12, 9)
	bar:SetHeight(10)
	bar:SetStatusBarTexture(Theme.Texture)
	bar:SetMinMaxValues(0, self.ProposalDuration)
	Theme:CreateBarBackground(bar)
	bar:Hide()
	frame.Bar = bar

	local barText = Theme:CreateText(bar, 10)
	barText:SetPoint("CENTER", bar, "CENTER", 0, 0)
	frame.BarText = barText

	frame:Hide()
	self.Frame = frame
	self:RestorePosition()
end

function QueueStatus:BeginProposal()
	local proposal = self:GetProposal()
	if not proposal then return end
	local settings = SlickUI.DB.queueStatus
	if settings.proposalID ~= proposal.id or type(settings.proposalExpiresAt) ~= "number" then
		settings.proposalID = proposal.id
		settings.proposalExpiresAt = GetTime() + self.ProposalDuration
	end
	self.Mode = "proposal"
	self:Refresh()
end

function QueueStatus:ClearProposal()
	local settings = SlickUI.DB.queueStatus
	settings.proposalID = nil
	settings.proposalExpiresAt = nil
	self.Mode = nil
	C_Timer.After(0.2, function() QueueStatus:Refresh() end)
end

function QueueStatus:ShowQueue(queue)
	local elapsed = self:GetQueueElapsed(queue)
	local name = queue.name or SafeDungeonName(queue.dungeonID) or "Dungeon or Raid Finder"
	self.Frame.Title:SetText("Queued: " .. name)
	self.Frame.Detail:SetText("Waiting " .. FormatClock(elapsed))
	self.Frame.Icon:SetTexture("Interface\\AddOns\\SlickUI\\Media\\Icons\\SlickUI_Icon.png")
	self.Frame.Bar:Hide()
	self.Frame:Show()
end

function QueueStatus:ShowProposal(proposal)
	local settings = SlickUI.DB.queueStatus
	local remaining = math.max(0, (tonumber(settings.proposalExpiresAt) or GetTime()) - GetTime())
	self.Frame.Title:SetText(proposal.hasResponded and "Accepted — Waiting for others" or "Group Found — Accept Now!")
	self.Frame.Detail:SetText(proposal.name)
	self.Frame.Icon:SetTexture("Interface\\AddOns\\SlickUI\\Media\\Icons\\SlickUI_Icon.png")
	self.Frame.Bar:SetMinMaxValues(0, self.ProposalDuration)
	self.Frame.Bar:SetValue(remaining)
	if remaining > 20 then
		self.Frame.Bar:SetStatusBarColor(0.16, 0.82, 0.38, 1)
	elseif remaining > 10 then
		self.Frame.Bar:SetStatusBarColor(1.00, 0.68, 0.10, 1)
	else
		self.Frame.Bar:SetStatusBarColor(0.92, 0.07, 0.045, 1)
	end
	self.Frame.BarText:SetText(FormatClock(remaining) .. " remaining")
	self.Frame.Bar:Show()
	self.Frame:Show()
end

function QueueStatus:Refresh()
	if not self.Frame or not SlickUI.DB.queueStatus.enabled then
		if self.Frame then self.Frame:Hide() end
		return
	end

	local proposal = self:GetProposal()
	if proposal then
		if type(SlickUI.DB.queueStatus.proposalExpiresAt) ~= "number" then self:BeginProposal() return end
		self.Mode = "proposal"
		self:ShowProposal(proposal)
		return
	end

	if self.Mode == "proposal" then
		SlickUI.DB.queueStatus.proposalID = nil
		SlickUI.DB.queueStatus.proposalExpiresAt = nil
		self.Mode = nil
	end

	local queue = self:FindActiveQueue()
	if queue then
		self.Mode = "queue"
		self:ShowQueue(queue)
	else
		self:ResetQueueTimer()
		self.Mode = nil
		self.Frame:Hide()
	end
end

function QueueStatus:Initialize()
	self:CreateFrame()
	local events = CreateFrame("Frame")
	for _, event in ipairs({
		"LFG_UPDATE", "LFG_QUEUE_STATUS_UPDATE", "LFG_PROPOSAL_SHOW", "LFG_PROPOSAL_UPDATE",
		"LFG_PROPOSAL_DONE", "LFG_PROPOSAL_FAILED", "LFG_PROPOSAL_SUCCEEDED", "PLAYER_ENTERING_WORLD",
	}) do
		events:RegisterEvent(event)
	end
	events:SetScript("OnEvent", function(_, event)
		if event == "LFG_PROPOSAL_SHOW" then
			QueueStatus:BeginProposal()
		elseif event == "LFG_PROPOSAL_DONE" or event == "LFG_PROPOSAL_FAILED" or event == "LFG_PROPOSAL_SUCCEEDED" then
			QueueStatus:ClearProposal()
		else
			QueueStatus:Refresh()
		end
	end)
	self.Frame:SetScript("OnUpdate", function(_, elapsed)
		QueueStatus.RefreshElapsed = QueueStatus.RefreshElapsed + elapsed
		if QueueStatus.RefreshElapsed >= 0.2 then
			QueueStatus.RefreshElapsed = 0
			QueueStatus:Refresh()
		end
	end)
	self.EventFrame = events
	self:Refresh()
end

SlickUI:RegisterModule("QueueStatus", QueueStatus)
