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

local PetsAndMounts = {}
SlickUI.PetsAndMounts = PetsAndMounts

local MACRO_NAME = "SlickMount"

-- Mount journal type IDs are stable data supplied by Blizzard. Unknown types
-- remain eligible when no type filter is selected instead of being discarded.
local GroundTypes = { [230] = true, [241] = true, [242] = true, [269] = true, [284] = true, [408] = true }
local FlyingTypes = { [231] = true, [247] = true, [248] = true, [398] = true, [402] = true, [424] = true }
local AquaticTypes = { [232] = true, [254] = true, [407] = true, [412] = true }

local function IsReadable(value)
	return (not canaccessvalue or canaccessvalue(value)) and (not issecretvalue or not issecretvalue(value))
end

local function IsRideAlongMount(mountID, isForDragonriding)
	if IsReadable(isForDragonriding) and isForDragonriding == true then return true end
	local _, description, source = C_MountJournal.GetMountInfoExtraByID(mountID)
	local text = string.lower((IsReadable(description) and description or "") .. " " .. (IsReadable(source) and source or ""))
	return text:find("passenger", 1, true) ~= nil or text:find("ride along", 1, true) ~= nil
end

function PetsAndMounts:RebuildMountPool(silent)
	local pool = {}
	if not C_MountJournal or not C_MountJournal.GetMountIDs then return pool end
	for _, mountID in ipairs(C_MountJournal.GetMountIDs() or {}) do
		local _, _, _, _, isUsable, _, _, _, _, shouldHide, isCollected, _, isForDragonriding = C_MountJournal.GetMountInfoByID(mountID)
		if IsReadable(isCollected) and isCollected == true and IsReadable(shouldHide) and not shouldHide then
			local _, _, _, _, mountTypeID = C_MountJournal.GetMountInfoExtraByID(mountID)
			pool[#pool + 1] = {
				id = mountID,
				ground = IsReadable(mountTypeID) and GroundTypes[mountTypeID] == true,
				flying = IsReadable(mountTypeID) and FlyingTypes[mountTypeID] == true,
				aquatic = IsReadable(mountTypeID) and AquaticTypes[mountTypeID] == true,
				rideAlong = IsRideAlongMount(mountID, isForDragonriding),
				usable = IsReadable(isUsable) and isUsable == true,
			}
		end
	end
	SlickUI.DB.petsAndMounts.mountPool = pool
	if self.QueueNextMount then
		if #pool > 0 then self:QueueNextMount(self:GetEligibleMounts()) else self.NextMountID = nil end
		C_Timer.After(0, function() PetsAndMounts:RefreshMacroIcon() end)
	end
	if not silent then SlickUI:Print("Stored " .. #pool .. " collected mounts for random selection.") end
	if SlickUI.Options and SlickUI.Options.RefreshPetsAndMountsControls then SlickUI.Options:RefreshPetsAndMountsControls() end
	return pool
end

local function MatchesFilters(entry, filters)
	local any = filters.ground or filters.flying or filters.aquatic or filters.rideAlong
	if not any then return true end
	-- Preserve forward compatibility with new Blizzard mount-type IDs when the
	-- player has left every broad category enabled.
	if filters.ground and filters.flying and filters.aquatic and filters.rideAlong then return true end
	return (filters.ground and entry.ground)
		or (filters.flying and entry.flying)
		or (filters.aquatic and entry.aquatic)
		or (filters.rideAlong and entry.rideAlong)
end

function PetsAndMounts:GetEligibleMounts()
	local pool = SlickUI.DB.petsAndMounts.mountPool
	if type(pool) ~= "table" or #pool == 0 then pool = self:RebuildMountPool(true) end
	local eligible, filters = {}, SlickUI.DB.petsAndMounts.filters
	for _, entry in ipairs(pool or {}) do
		local _, _, _, _, usable, _, _, _, _, hidden, collected = C_MountJournal.GetMountInfoByID(entry.id)
		if IsReadable(usable) and usable == true and IsReadable(collected) and collected == true
			and IsReadable(hidden) and not hidden and MatchesFilters(entry, filters) then
			eligible[#eligible + 1] = entry.id
		end
	end
	return eligible
end

function PetsAndMounts:QueueNextMount(eligible, previousID)
	if not eligible or #eligible == 0 then self.NextMountID = nil; return nil end
	local choices = eligible
	if previousID and #eligible > 1 then
		choices = {}
		for _, mountID in ipairs(eligible) do
			if mountID ~= previousID then choices[#choices + 1] = mountID end
		end
	end
	self.NextMountID = choices[math.random(#choices)]
	return self.NextMountID
end

function PetsAndMounts:GetNextMountIcon()
	if not self.NextMountID then self:QueueNextMount(self:GetEligibleMounts()) end
	if self.NextMountID then
		local _, _, icon = C_MountJournal.GetMountInfoByID(self.NextMountID)
		if IsReadable(icon) and icon then return icon end
	end
	return "INV_Misc_QuestionMark"
end

function PetsAndMounts:RefreshMacroIcon()
	if InCombatLockdown() then return end
	local index = GetMacroIndexByName(MACRO_NAME)
	if index and index > 0 then
		EditMacro(index, MACRO_NAME, self:GetNextMountIcon(), "/click SlickUIRandomMountButton", false)
	end
end

function PetsAndMounts:SummonRandomMount()
	if InCombatLockdown() then SlickUI:Print("Mounts cannot be summoned during combat."); return end
	local eligible = self:GetEligibleMounts()
	if #eligible == 0 then
		SlickUI:Print("No currently usable collected mounts match the selected filters.")
		return
	end
	local selected = self.NextMountID
	local valid = false
	for _, mountID in ipairs(eligible) do if mountID == selected then valid = true; break end end
	if not valid then selected = self:QueueNextMount(eligible) end
	C_MountJournal.SummonByID(selected)
	self:QueueNextMount(eligible, selected)
	C_Timer.After(0, function() PetsAndMounts:RefreshMacroIcon() end)
end

function PetsAndMounts:SummonRandomPet()
	if InCombatLockdown() then SlickUI:Print("A random pet cannot be summoned during combat."); return end
	if C_PetJournal and C_PetJournal.SummonRandomPet then C_PetJournal.SummonRandomPet(false) end
end

function PetsAndMounts:CreateOrUpdateMacro()
	if InCombatLockdown() then SlickUI:Print("The random mount macro cannot be changed during combat."); return end
	local body = "/click SlickUIRandomMountButton"
	self:QueueNextMount(self:GetEligibleMounts())
	local icon = self:GetNextMountIcon()
	local index = GetMacroIndexByName(MACRO_NAME)
	if index and index > 0 then
		index = EditMacro(index, MACRO_NAME, icon, body, false) or index
	else
		index = CreateMacro(MACRO_NAME, icon, body, false)
		if not index then SlickUI:Print("No general macro slot is available for SlickMount."); return end
	end
	ClearCursor()
	PickupMacro(index)
	SlickUI:Print("The SlickMount macro is on your cursor. Drop it onto an action bar.")
end

function PetsAndMounts:Initialize()
	local mountButton = CreateFrame("Button", "SlickUIRandomMountButton", UIParent)
	mountButton:SetSize(1, 1); mountButton:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", -20, -20)
	mountButton:SetAlpha(0); mountButton:SetScript("OnClick", function() PetsAndMounts:SummonRandomMount() end)
	self.MountButton = mountButton
	C_Timer.After(2, function() PetsAndMounts:RebuildMountPool(true) end)
end

SlickUI:RegisterModule("PetsAndMounts", PetsAndMounts)
