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

local ReputationTracker = {}
SlickUI.ReputationTracker = ReputationTracker

SlickUI.Capabilities = SlickUI.Capabilities or {}
SlickUI.Capabilities.reputationTracking = true

local snapshot, knownFactionIDs = {}, {}
local updateGeneration = 0

local function IsAccessible(value)
	if canaccessvalue and not canaccessvalue(value) then return false end
	if issecretvalue and issecretvalue(value) then return false end
	return true
end

local function SafeNumber(value)
	return type(value) == "number" and IsAccessible(value) and value or nil
end

local function SafeString(value)
	return type(value) == "string" and IsAccessible(value) and value or nil
end

local function GetFactionCount()
	if C_Reputation and C_Reputation.GetNumFactions then return SafeNumber(C_Reputation.GetNumFactions()) or 0 end
	if GetNumFactions then return SafeNumber(GetNumFactions()) or 0 end
	return 0
end

local function GetFactionData(index)
	if C_Reputation and C_Reputation.GetFactionDataByIndex then return C_Reputation.GetFactionDataByIndex(index) end
	if not GetFactionInfo then return nil end
	local name, _, _, _, _, standing, _, _, isHeader, _, hasRep, _, _, factionID = GetFactionInfo(index)
	return { name = name, currentStanding = standing, isHeader = isHeader, isHeaderWithRep = hasRep, factionID = factionID }
end

local function GetProgress(factionID, data)
	if C_Reputation and C_Reputation.IsMajorFaction and C_Reputation.IsMajorFaction(factionID)
		and C_MajorFactions and C_MajorFactions.GetMajorFactionData then
		local major = C_MajorFactions.GetMajorFactionData(factionID)
		local level = major and SafeNumber(major.renownLevel)
		local earned = major and SafeNumber(major.renownReputationEarned)
		if level and earned then return level * 1000000000 + earned end
	end
	if C_Reputation and C_Reputation.IsFactionParagon and C_Reputation.IsFactionParagon(factionID)
		and C_Reputation.GetFactionParagonInfo then
		local value = SafeNumber(C_Reputation.GetFactionParagonInfo(factionID))
		if value then return value end
	end
	if C_GossipInfo and C_GossipInfo.GetFriendshipReputation then
		local friendship = C_GossipInfo.GetFriendshipReputation(factionID)
		if type(friendship) == "table" and (SafeNumber(friendship.friendshipFactionID) or 0) > 0 then
			local standing = SafeNumber(friendship.standing)
			if standing then return standing end
		end
	end
	return SafeNumber(data.currentStanding)
end

local function AddFaction(result, data, index)
	if type(data) ~= "table" then return end
	local factionID, name = SafeNumber(data.factionID), SafeString(data.name)
	if not factionID or not name or (data.isHeader == true and data.isHeaderWithRep ~= true) then return end
	local progress = GetProgress(factionID, data)
	if not progress then return end
	knownFactionIDs[factionID] = true
	result[factionID] = {
		factionID = factionID, index = index, name = name, progress = progress,
		isWatched = data.isWatched == true,
	}
end

local function BuildVisibleSnapshot()
	local result = {}
	for index = 1, GetFactionCount() do AddFaction(result, GetFactionData(index), index) end
	return result
end

local function HeaderKey(data)
	if type(data) ~= "table" then return nil end
	local name = SafeString(data.name)
	return name and tostring(SafeNumber(data.factionID) or 0) .. "\031" .. name or nil
end

local function DiscoverAllFactions()
	if not C_Reputation or not C_Reputation.ExpandAllFactionHeaders or not C_Reputation.CollapseFactionHeader then
		return BuildVisibleSnapshot()
	end
	local legacyWasShown
	if C_Reputation.AreLegacyReputationsShown and C_Reputation.SetLegacyReputationsShown then
		legacyWasShown = C_Reputation.AreLegacyReputationsShown()
		if legacyWasShown == false then pcall(C_Reputation.SetLegacyReputationsShown, true) end
	end
	local collapsed = {}
	for index = 1, GetFactionCount() do
		local data = GetFactionData(index)
		if type(data) == "table" and data.isHeader == true and data.isCollapsed == true then
			local key = HeaderKey(data)
			if key then collapsed[key] = true end
		end
	end
	pcall(C_Reputation.ExpandAllFactionHeaders)
	local result = BuildVisibleSnapshot()
	for index = GetFactionCount(), 1, -1 do
		local data = GetFactionData(index)
		local key = type(data) == "table" and data.isHeader == true and HeaderKey(data) or nil
		if key and collapsed[key] then pcall(C_Reputation.CollapseFactionHeader, index) end
	end
	if legacyWasShown == false then pcall(C_Reputation.SetLegacyReputationsShown, false) end
	return result
end

local function BuildSnapshot()
	if C_Reputation and C_Reputation.GetFactionDataByID and next(knownFactionIDs) then
		local result = {}
		for factionID in pairs(knownFactionIDs) do AddFaction(result, C_Reputation.GetFactionDataByID(factionID)) end
		for factionID, data in pairs(BuildVisibleSnapshot()) do result[factionID] = data end
		return result
	end
	return BuildVisibleSnapshot()
end

local function SetWatchedFaction(factionID, index)
	if C_Reputation and C_Reputation.SetWatchedFactionByID then return pcall(C_Reputation.SetWatchedFactionByID, factionID) end
	if C_Reputation and C_Reputation.SetWatchedFactionByIndex and index then return pcall(C_Reputation.SetWatchedFactionByIndex, index) end
	if SetWatchedFactionIndex and index then return pcall(SetWatchedFactionIndex, index) end
	return false
end

function ReputationTracker:IsEnabled()
	return SlickUI.DB and SlickUI.DB.reputationTracker and SlickUI.DB.reputationTracker.enabled ~= false
end

function ReputationTracker:SetEnabled(enabled)
	SlickUI.DB.reputationTracker.enabled = enabled == true
	snapshot = DiscoverAllFactions()
end

function ReputationTracker:GetStatus()
	local apiAvailable = (C_Reputation and C_Reputation.GetFactionDataByIndex
		and (C_Reputation.SetWatchedFactionByID or C_Reputation.SetWatchedFactionByIndex))
		or (GetFactionInfo and SetWatchedFactionIndex)
	return { enabled = self:IsEnabled(), apiAvailable = apiAvailable and true or false }
end

function ReputationTracker:ProcessUpdate()
	local current = BuildSnapshot()
	if not self:IsEnabled() then snapshot = current; return end
	local gained, largestGain
	largestGain = 0
	for factionID, newData in pairs(current) do
		local oldData = snapshot[factionID]
		local gain = oldData and newData.progress - oldData.progress or 0
		if gain > largestGain then largestGain, gained = gain, newData end
	end
	snapshot = current
	if gained and not gained.isWatched and SetWatchedFaction(gained.factionID, gained.index) then
		SlickUI:Print("Now tracking " .. gained.name .. " reputation.")
	end
end

function ReputationTracker:QueueUpdate()
	updateGeneration = updateGeneration + 1
	local generation = updateGeneration
	C_Timer.After(0.15, function()
		if generation == updateGeneration then ReputationTracker:ProcessUpdate() end
	end)
end

function ReputationTracker:Initialize()
	snapshot = DiscoverAllFactions()
	local events = CreateFrame("Frame")
	events:RegisterEvent("UPDATE_FACTION")
	events:RegisterEvent("PLAYER_ENTERING_WORLD")
	events:SetScript("OnEvent", function(_, event)
		if event == "PLAYER_ENTERING_WORLD" then snapshot = DiscoverAllFactions() else ReputationTracker:QueueUpdate() end
	end)
	self.EventFrame = events
end

SlickUI:RegisterModule("ReputationTracker", ReputationTracker)
