local _, ns = ...
local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme

local XPTracker = {
	Active = true,
	StartedAt = 0,
	AccumulatedTime = 0,
	Gained = 0,
	LastXP = nil,
	LastMaximum = nil,
	LastLevel = nil,
}
SlickUI.XPTracker = XPTracker

local function SafeNumber(value)
	if canaccessvalue and not canaccessvalue(value) then return nil end
	if issecretvalue and issecretvalue(value) then return nil end
	return type(value) == "number" and value or nil
end

local function Compact(value)
	if not value then return "?" end
	if AbbreviateLargeNumbers then return AbbreviateLargeNumbers(math.floor(value + 0.5)) end
	return tostring(math.floor(value + 0.5))
end

local function DurationText(seconds)
	if not seconds or seconds < 0 or seconds == math.huge then return "--" end
	seconds = math.floor(seconds + 0.5)
	local hours = math.floor(seconds / 3600)
	local minutes = math.floor(seconds / 60) % 60
	if hours > 0 then return string.format("%dh %02dm", hours, minutes) end
	return string.format("%dm %02ds", minutes, seconds % 60)
end

function XPTracker:IsMaxLevel()
	local level = SafeNumber(UnitLevel("player"))
	local maximumXP = SafeNumber(UnitXPMax("player"))
	if not level then return false end
	if maximumXP == 0 then return true end
	if type(GetMaxLevelForPlayerExpansion) == "function" then
		local maximumLevel = SafeNumber(GetMaxLevelForPlayerExpansion())
		if maximumLevel and level >= maximumLevel then return true end
	end
	return false
end

function XPTracker:GetElapsed()
	local elapsed = self.AccumulatedTime
	if self.Active then elapsed = elapsed + math.max(0, (GetTime() or self.StartedAt) - self.StartedAt) end
	return elapsed
end

function XPTracker:GetRate()
	local elapsed = self:GetElapsed()
	return elapsed > 0 and self.Gained / elapsed * 3600 or 0
end

function XPTracker:GetRemaining()
	local current, maximum = SafeNumber(UnitXP("player")), SafeNumber(UnitXPMax("player"))
	if not current or not maximum or maximum <= 0 then return nil end
	return math.max(0, maximum - current)
end

function XPTracker:GetETA()
	local remaining, rate = self:GetRemaining(), self:GetRate()
	if not remaining or rate <= 0 then return nil end
	return remaining / rate * 3600
end

function XPTracker:GetBarText()
	local level = SafeNumber(UnitLevel("player"))
	if self:IsMaxLevel() then return string.format("Level %s: |cffffd24aMax Level|r", level or "?") end
	local current, maximum = SafeNumber(UnitXP("player")), SafeNumber(UnitXPMax("player"))
	if not current or not maximum or maximum <= 0 then return string.format("Level: %s", level or "?") end
	local rate = self:GetRate()
	local eta = self:GetETA()
	return string.format("Level %d: %s/%s  XP/hr: |cff42df72%s|r  Next: |cff48b8ff%s|r", level or 0, Compact(current), Compact(maximum), rate > 0 and Compact(rate) or "--", DurationText(eta))
end

function XPTracker:BuildTooltip(tooltip)
	local level = SafeNumber(UnitLevel("player"))
	tooltip:AddLine("Experience Tracker", unpack(Theme.Colors.goldText))
	if self:IsMaxLevel() then
		tooltip:AddLine(string.format("Level %s - Maximum level reached", level or "?"), 0.40, 0.88, 0.45)
		return
	end
	local current, maximum = SafeNumber(UnitXP("player")), SafeNumber(UnitXPMax("player"))
	local remaining = self:GetRemaining()
	tooltip:AddDoubleLine("Current level", tostring(level or "?"), 1, 1, 1, 1, 1, 1)
	if current and maximum then tooltip:AddDoubleLine("Level progress", string.format("%s / %s", Compact(current), Compact(maximum)), 1, 1, 1, 0.40, 0.82, 1.00) end
	tooltip:AddDoubleLine("Session XP gained", Compact(self.Gained), 1, 1, 1, 0.40, 0.88, 0.45)
	tooltip:AddDoubleLine("XP per hour", self:GetRate() > 0 and Compact(self:GetRate()) or "--", 1, 1, 1, 0.40, 0.88, 0.45)
	tooltip:AddDoubleLine("XP remaining", remaining and Compact(remaining) or "--", 1, 1, 1, 1, 1, 1)
	tooltip:AddDoubleLine("Estimated time to level", DurationText(self:GetETA()), 1, 1, 1, 0.40, 0.72, 1.00)
	tooltip:AddDoubleLine("Tracking time", DurationText(self:GetElapsed()), 1, 1, 1, 1, 1, 1)
	if not self.Active then tooltip:AddLine("Tracking is paused.", 1, 0.78, 0.24) end
end

function XPTracker:Reset()
	self.Gained, self.AccumulatedTime = 0, 0
	self.StartedAt = GetTime() or 0
	self.Active = true
	self.LastXP = SafeNumber(UnitXP("player"))
	self.LastMaximum = SafeNumber(UnitXPMax("player"))
	self.LastLevel = SafeNumber(UnitLevel("player"))
	self:Refresh()
end

function XPTracker:SetActive(active)
	active = active == true
	if self.Active == active then return end
	if active then
		self.StartedAt = GetTime() or 0
	else
		self.AccumulatedTime = self:GetElapsed()
	end
	self.Active = active
	self:Refresh()
end

function XPTracker:OnXPUpdate()
	local current = SafeNumber(UnitXP("player"))
	local maximum = SafeNumber(UnitXPMax("player"))
	local level = SafeNumber(UnitLevel("player"))
	if not current or not maximum or not level then return end
	if self.LastXP == nil then self.LastXP, self.LastMaximum, self.LastLevel = current, maximum, level return end
	local delta
	if level > self.LastLevel then delta = math.max(0, (self.LastMaximum or 0) - self.LastXP) + current
	else delta = math.max(0, current - self.LastXP) end
	if self.Active then self.Gained = self.Gained + delta end
	self.LastXP, self.LastMaximum, self.LastLevel = current, maximum, level
	self:Refresh()
end

function XPTracker:CreateWindow()
	if self.Window then return self.Window end
	local frame = CreateFrame("Frame", "SlickUIXPTrackerFrame", UIParent, "BackdropTemplate")
	frame:SetSize(390, 245)
	local position = SlickUI.DB.xpTracker.windowPosition
	frame:SetPoint("CENTER", UIParent, "CENTER", position.x or 0, position.y or 180)
	frame:SetFrameStrata("DIALOG")
	frame:SetClampedToScreen(true)
	frame:SetMovable(true)
	frame:EnableMouse(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", frame.StartMoving)
	frame:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local x, y = self:GetCenter()
		local ux, uy = UIParent:GetCenter()
		if x and y and ux and uy then position.x, position.y = x - ux, y - uy end
	end)
	Theme:CreatePanel(frame)
	frame:Hide()
	table.insert(UISpecialFrames, frame:GetName())
	local title = Theme:CreateText(frame, 16)
	title:SetPoint("TOPLEFT", 16, -14)
	title:SetText("SlickUI XP Tracker")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -3, -3)
	local body = Theme:CreateText(frame, 11)
	body:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -14)
	body:SetJustifyH("LEFT")
	body:SetSpacing(5)
	frame.Body = body
	local toggle = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	toggle:SetSize(120, 24)
	toggle:SetPoint("BOTTOMLEFT", 16, 16)
	toggle:SetScript("OnClick", function() XPTracker:SetActive(not XPTracker.Active) end)
	frame.Toggle = toggle
	local reset = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	reset:SetSize(120, 24)
	reset:SetPoint("LEFT", toggle, "RIGHT", 10, 0)
	reset:SetText("Reset Session")
	reset:SetScript("OnClick", function() XPTracker:Reset() end)
	self.Window = frame
	return frame
end

function XPTracker:RefreshWindow()
	if not self.Window then return end
	local current, maximum = SafeNumber(UnitXP("player")), SafeNumber(UnitXPMax("player"))
	local level = SafeNumber(UnitLevel("player"))
	if self:IsMaxLevel() then
		self.Window.Body:SetText(string.format("Level: |cffffd24a%s|r\n\n|cff42df72Maximum level reached.|r", level or "?"))
	else
		local progress = current and maximum and maximum > 0 and current / maximum * 100 or 0
		self.Window.Body:SetText(table.concat({
			string.format("Level: |cffffd24a%s|r    Progress: |cff48b8ff%.1f%%|r", level or "?", progress),
			string.format("Current XP: %s / %s", Compact(current), Compact(maximum)),
			"Session XP gained: |cff42df72" .. Compact(self.Gained) .. "|r",
			"XP per hour: |cff42df72" .. (self:GetRate() > 0 and Compact(self:GetRate()) or "--") .. "|r",
			"Time until next level: |cff48b8ff" .. DurationText(self:GetETA()) .. "|r",
			"Tracking time: " .. DurationText(self:GetElapsed()),
		}, "\n"))
	end
	self.Window.Toggle:SetText(self.Active and "Pause Tracking" or "Resume Tracking")
	self.Window.Toggle:SetEnabled(not self:IsMaxLevel())
end

function XPTracker:Refresh()
	self:RefreshWindow()
	if SlickUI.InformationBars then SlickUI.InformationBars:UpdateBlocks() end
end

function XPTracker:ToggleWindow()
	if self:IsMaxLevel() then SlickUI:Print("This character is already at maximum level.") return end
	local frame = self:CreateWindow()
	if frame:IsShown() then frame:Hide() else self:RefreshWindow(); frame:Show(); frame:Raise() end
end

function XPTracker:Initialize()
	self.StartedAt = GetTime() or 0
	self.LastXP = SafeNumber(UnitXP("player"))
	self.LastMaximum = SafeNumber(UnitXPMax("player"))
	self.LastLevel = SafeNumber(UnitLevel("player"))
	local frame = CreateFrame("Frame")
	frame:RegisterEvent("PLAYER_XP_UPDATE")
	frame:RegisterEvent("PLAYER_LEVEL_UP")
	frame:RegisterEvent("PLAYER_ENTERING_WORLD")
	frame:SetScript("OnEvent", function(_, event, unit)
		if event == "PLAYER_XP_UPDATE" and unit ~= "player" then return end
		if event == "PLAYER_ENTERING_WORLD" then
			XPTracker.LastXP = SafeNumber(UnitXP("player"))
			XPTracker.LastMaximum = SafeNumber(UnitXPMax("player"))
			XPTracker.LastLevel = SafeNumber(UnitLevel("player"))
		else
			C_Timer.After(0, function() XPTracker:OnXPUpdate() end)
		end
	end)
	frame:SetScript("OnUpdate", function(_, elapsed)
		XPTracker.Elapsed = (XPTracker.Elapsed or 0) + elapsed
		if XPTracker.Elapsed >= 1 then XPTracker.Elapsed = 0; XPTracker:RefreshWindow() end
	end)
	self.EventFrame = frame
end

SlickUI:RegisterModule("XPTracker", XPTracker)
